diff --git a/.sisyphus/evidence/team-mode/task-2-category-prompt-required.txt b/.sisyphus/evidence/team-mode/task-2-category-prompt-required.txt new file mode 100644 index 000000000..d3f55481c --- /dev/null +++ b/.sisyphus/evidence/team-mode/task-2-category-prompt-required.txt @@ -0,0 +1,7 @@ +bun test v1.3.12 (700fc117) + + 1 pass + 3 filtered out + 0 fail + 1 expect() calls +Ran 1 test across 1 file. [64.00ms] diff --git a/.sisyphus/evidence/team-mode/task-2-disc-both.txt b/.sisyphus/evidence/team-mode/task-2-disc-both.txt new file mode 100644 index 000000000..1174095e7 --- /dev/null +++ b/.sisyphus/evidence/team-mode/task-2-disc-both.txt @@ -0,0 +1,7 @@ +bun test v1.3.12 (700fc117) + + 1 pass + 3 filtered out + 0 fail + 1 expect() calls +Ran 1 test across 1 file. [65.00ms] diff --git a/.sisyphus/evidence/team-mode/task-2-disc-category.txt b/.sisyphus/evidence/team-mode/task-2-disc-category.txt new file mode 100644 index 000000000..7aed175f2 --- /dev/null +++ b/.sisyphus/evidence/team-mode/task-2-disc-category.txt @@ -0,0 +1,7 @@ +bun test v1.3.12 (700fc117) + + 1 pass + 3 filtered out + 0 fail + 3 expect() calls +Ran 1 test across 1 file. [69.00ms] diff --git a/.sisyphus/evidence/team-mode/task-2-eligibility-registry.txt b/.sisyphus/evidence/team-mode/task-2-eligibility-registry.txt new file mode 100644 index 000000000..749f3d548 --- /dev/null +++ b/.sisyphus/evidence/team-mode/task-2-eligibility-registry.txt @@ -0,0 +1,7 @@ +bun test v1.3.12 (700fc117) + + 1 pass + 3 filtered out + 0 fail + 12 expect() calls +Ran 1 test across 1 file. [61.00ms] diff --git a/.sisyphus/notepads/team-mode/learnings.md b/.sisyphus/notepads/team-mode/learnings.md new file mode 100644 index 000000000..97e13ba01 --- /dev/null +++ b/.sisyphus/notepads/team-mode/learnings.md @@ -0,0 +1,10 @@ +## 2026-04-18 Task 2: types module + +- `MemberSchema` needs `.strict()` on the base shape so the discriminatedUnion rejects members that mix `category` and `subagent_type`. +- `backendType` and `isActive` defaults are part of the schema contract, so tests should use `toMatchObject` instead of exact object equality. +- The eligibility registry must preserve the plan strings verbatim, especially the hard-reject messages for Momus verification. +## Task 12 learnings + +- `git worktree remove` can leave prunable entries behind, so pruning after removal keeps the repo index tidy. +- For testability, a tiny git command runner hook made git-unavailable coverage simpler than mocking Bun directly. +- Detached worktrees need unique temp paths in tests to avoid cross-run collisions. diff --git a/AGENTS.md b/AGENTS.md index 64bed7618..02af070b0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,17 +1,17 @@ # oh-my-opencode — OpenCode Plugin -**Generated:** 2026-04-11 | **Commit:** f5dc1c0e | **Branch:** dev +**Generated:** 2026-04-18 | **Commit:** 2892ca4a | **Branch:** dev ## OVERVIEW -OpenCode plugin (npm: `oh-my-opencode`) extending Claude Code with multi-agent orchestration, 52 lifecycle hooks, 26 tools, skill/command/MCP systems, Hashline edit tool, IntentGate classifier, and Claude Code compatibility. ~1600 TypeScript source files. Dual-published as `oh-my-opencode` + `oh-my-openagent` during transition. +OpenCode plugin (npm: `oh-my-opencode`, dual-published as `oh-my-openagent` during transition) extending Claude Code with 11 agents, 52 lifecycle hooks, 26 tools, 3-tier MCP system (built-in + .mcp.json + skill-embedded), Hashline LINE#ID edit tool, IntentGate classifier, and Claude Code compatibility. 1766 TypeScript source files, 377k LOC, 104 barrel index.ts files. Entry: `src/index.ts` → 5-step init (loadConfig → createManagers → createTools → createHooks → createPluginInterface). ## STRUCTURE ``` oh-my-opencode/ ├── src/ -│ ├── index.ts # Plugin entry: loadConfig → createManagers → createTools → createHooks → createPluginInterface +│ ├── index.ts # Plugin entry: default export `pluginModule`, shape `{ id, server }` │ ├── plugin-config.ts # JSONC multi-level config: user → project → defaults (Zod v4) │ ├── agents/ # 11 agents (Sisyphus, Hephaestus, Oracle, Librarian, Explore, Atlas, Prometheus, Metis, Momus, Multimodal-Looker, Sisyphus-Junior) │ ├── hooks/ # 52 lifecycle hooks across dedicated modules and standalone files @@ -33,7 +33,7 @@ oh-my-opencode/ ## INITIALIZATION FLOW ``` -OhMyOpenCodePlugin(ctx) +pluginModule.server(input, options) ├─→ loadPluginConfig() # JSONC parse → project/user merge → Zod validate → migrate ├─→ createManagers() # TmuxSessionManager, BackgroundManager, SkillMcpManager, ConfigHandler ├─→ createTools() # SkillContext + AvailableCategories + ToolRegistry (26 tools) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 65c0de98d..f1ded421d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,8 +56,7 @@ If English isn't your first language, don't worry! We value your contributions r ### Prerequisites - **Bun** (latest version) - The only supported package manager -- **TypeScript 5.7.3+** - For type checking and declarations -- **OpenCode 1.0.150+** - For testing the plugin +- **TypeScript** - Strict mode for type checking and declarations ### Development Setup @@ -110,17 +109,17 @@ After making changes, you can test your local build in OpenCode: ``` oh-my-opencode/ ├── src/ -│ ├── index.ts # Plugin entry (OhMyOpenCodePlugin) +│ ├── index.ts # Plugin entry (V1 PluginModule, default export) │ ├── plugin-config.ts # JSONC multi-level config (Zod v4) │ ├── agents/ # 11 agents (Sisyphus, Hephaestus, Oracle, Librarian, Explore, Atlas, Prometheus, Metis, Momus, Multimodal-Looker, Sisyphus-Junior) -│ ├── hooks/ # Lifecycle hooks for orchestration, recovery, UX, and context management -│ ├── tools/ # 26 tools across 15 directories +│ ├── hooks/ # 52 lifecycle hooks across 55 dedicated modules +│ ├── tools/ # 26 tools across 16 directories │ ├── mcp/ # 3 built-in remote MCPs (websearch, context7, grep_app) │ ├── features/ # 19 feature modules (background-agent, skill-loader, tmux, MCP-OAuth, etc.) │ ├── config/ # Zod v4 schema system │ ├── shared/ # Cross-cutting utilities │ ├── cli/ # CLI: install, run, doctor, mcp-oauth (Commander.js) -│ ├── plugin/ # 8 OpenCode hook handlers + hook composition +│ ├── plugin/ # 10 OpenCode hook handlers + 52 hook composition │ └── plugin-handlers/ # 6-phase config loading pipeline ├── packages/ # Monorepo: comment-checker, opencode-sdk └── dist/ # Build output (ESM + .d.ts) @@ -184,7 +183,7 @@ import type { AgentConfig } from "./types"; export const myAgent: AgentConfig = { name: "my-agent", - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", description: "Description of what this agent does", prompt: `Your agent's system prompt here`, temperature: 0.1, diff --git a/README.ja.md b/README.ja.md index b9cea1105..a8fe8e1e1 100644 --- a/README.ja.md +++ b/README.ja.md @@ -115,6 +115,10 @@ https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/heads/dev/do curl -s https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/heads/dev/docs/guide/installation.md ``` +**注記**: 公開されているパッケージおよびバイナリ名は `oh-my-opencode` を使用してください。`opencode.json` 内では、互換性レイヤーがプラグインエントリ `oh-my-openagent` を優先しますが、従来の `oh-my-opencode` エントリも警告付きで読み込まれます。プラグイン設定ファイルは依然として `oh-my-opencode.json` または `oh-my-opencode.jsonc` を使用するのが一般的で、移行期間中は従来のファイル名と改名後のファイル名の両方が認識されます。 + +匿名のテレメトリは、インストールとランタイムの信頼性向上のためにデフォルトで有効になっています。これは PostHog を使用し、生のホスト名ではなくハッシュ化されたインストール識別子を使用します。無効化するには `OMO_SEND_ANONYMOUS_TELEMETRY=0` または `OMO_DISABLE_POSTHOG=1` を設定してください。[プライバシーポリシー](docs/legal/privacy-policy.md)と[利用規約](docs/legal/terms-of-service.md)をご覧ください。 + --- ## このREADMEをスキップする @@ -166,11 +170,11 @@ Read this and tell me why it's not just another boilerplate: https://raw.githubu -**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).* -**Prometheus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**) はあなたの戦略プランナーです。インタビューモードで動作し、コードに触れる前に質問をしてスコープを特定し、詳細な計画を構築します。 +**Prometheus** (`claude-opus-4-7` / **`kimi-k2.5`** / **`glm-5`**) はあなたの戦略プランナーです。インタビューモードで動作し、コードに触れる前に質問をしてスコープを特定し、詳細な計画を構築します。 すべてのエージェントは、それぞれのモデルの強みに合わせてチューニングされています。手動でモデルを切り替える必要はありません。[詳しくはこちら →](docs/guide/overview.md) diff --git a/README.ko.md b/README.ko.md index 19524e98c..1e3a8294f 100644 --- a/README.ko.md +++ b/README.ko.md @@ -109,6 +109,10 @@ https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/heads/dev/do curl -s https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/heads/dev/docs/guide/installation.md ``` +**참고**: 배포된 패키지와 바이너리 이름은 `oh-my-opencode`를 사용하세요. `opencode.json` 내부에서는 호환성 레이어가 이제 플러그인 엔트리 `oh-my-openagent`를 우선시하며, 레거시 `oh-my-opencode` 엔트리는 경고와 함께 여전히 로드됩니다. 플러그인 설정 파일은 여전히 일반적으로 `oh-my-opencode.json` 또는 `oh-my-opencode.jsonc`를 사용하며, 전환 기간 동안 레거시와 변경된 basename 모두 인식됩니다. + +익명 텔레메트리는 설치 및 런타임 안정성 개선을 위해 기본적으로 활성화되어 있습니다. PostHog를 사용하며 해시된 설치 식별자를 사용하고 원시 호스트명은 절대 사용하지 않습니다. `OMO_SEND_ANONYMOUS_TELEMETRY=0` 또는 `OMO_DISABLE_POSTHOG=1`로 비활성화할 수 있습니다. [개인정보처리방침](docs/legal/privacy-policy.md)과 [서비스 이용약관](docs/legal/terms-of-service.md)을 참조하세요. + --- ## 이 README 건너뛰기 @@ -160,11 +164,11 @@ Read this and tell me why it's not just another boilerplate: https://raw.githubu -**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).* -**Prometheus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**)는 당신의 전략 플래너입니다. 인터뷰 모드로 작동합니다. 코드 한 줄 만지기 전에 질문을 던져 스코프를 파악하고 상세한 계획부터 세웁니다. +**Prometheus** (`claude-opus-4-7` / **`kimi-k2.5`** / **`glm-5`**)는 당신의 전략 플래너입니다. 인터뷰 모드로 작동합니다. 코드 한 줄 만지기 전에 질문을 던져 스코프를 파악하고 상세한 계획부터 세웁니다. 모든 에이전트는 해당 모델의 특장점에 맞춰 튜닝되어 있습니다. 수동으로 모델 바꿔가며 뻘짓하지 마세요. [더 알아보기 →](docs/guide/overview.md) diff --git a/README.md b/README.md index 8aec143a3..8f7644cc3 100644 --- a/README.md +++ b/README.md @@ -166,11 +166,11 @@ Even only with following subscriptions, ultrawork will work well (this project i -**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.* -**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) diff --git a/README.ru.md b/README.ru.md index 540643acd..65af04c3a 100644 --- a/README.ru.md +++ b/README.ru.md @@ -101,6 +101,10 @@ https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/heads/dev/do curl -s https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/heads/dev/docs/guide/installation.md ``` +**Примечание**: Используйте опубликованное имя пакета и бинарника `oh-my-opencode`. Внутри `opencode.json` слой совместимости теперь предпочитает точку входа плагина `oh-my-openagent`, в то время как устаревшие записи `oh-my-opencode` все еще загружаются с предупреждением. Файлы конфигурации плагина по-прежнему часто используют `oh-my-opencode.json` или `oh-my-opencode.jsonc`, и как устаревшие, так и переименованные базовые имена распознаются во время переходного периода. + +Анонимная телеметрия включена по умолчанию для улучшения надежности установки и работы. Она использует PostHog с хешированным идентификатором установки, никогда не используя исходное имя хоста, и может быть отключена с помощью `OMO_SEND_ANONYMOUS_TELEMETRY=0` или `OMO_DISABLE_POSTHOG=1`. См. [Политику конфиденциальности](docs/legal/privacy-policy.md) и [Условия обслуживания](docs/legal/terms-of-service.md). + ------ ## Пропустите этот README @@ -150,11 +154,11 @@ Read this and tell me why it's not just another boilerplate: https://raw.githubu
-**Sisyphus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**) — главный оркестратор. Он планирует, делегирует задачи специалистам и доводит их до завершения с агрессивным параллельным выполнением. Он не останавливается на полпути. +**Sisyphus** (`claude-opus-4-7` / **`kimi-k2.5`** / **`glm-5`**) — главный оркестратор. Он планирует, делегирует задачи специалистам и доводит их до завершения с агрессивным параллельным выполнением. Он не останавливается на полпути. **Hephaestus** (`gpt-5.4`) — автономный глубокий исполнитель. Дайте ему цель, а не рецепт. Он исследует кодовую базу, изучает паттерны и выполняет задачи сквозным образом без лишних подсказок. *Законный Мастер.* -**Prometheus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**) — стратегический планировщик. Режим интервью: задаёт вопросы, определяет объём работ и формирует детальный план до того, как написана хотя бы одна строка кода. +**Prometheus** (`claude-opus-4-7` / **`kimi-k2.5`** / **`glm-5`**) — стратегический планировщик. Режим интервью: задаёт вопросы, определяет объём работ и формирует детальный план до того, как написана хотя бы одна строка кода. Каждый агент настроен под сильные стороны своей модели. Никакого ручного переключения между моделями. Подробнее → diff --git a/README.zh-cn.md b/README.zh-cn.md index 2b2a3ab4c..2d80093bd 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -116,6 +116,10 @@ https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/heads/dev/do curl -s https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/heads/dev/docs/guide/installation.md ``` +**注意**:请使用已发布的包名和二进制名 `oh-my-opencode`。在 `opencode.json` 中,兼容性层现在优先使用插件入口 `oh-my-openagent`,而旧的 `oh-my-opencode` 条目仍会加载并显示警告。插件配置文件通常仍使用 `oh-my-opencode.json` 或 `oh-my-opencode.jsonc`,在过渡期间新旧两种文件名都会被识别。 + +匿名遥测默认开启,用于帮助提升安装和运行时的可靠性。它使用 PostHog,并采用哈希化的安装标识符,绝不会使用原始主机名,可通过 `OMO_SEND_ANONYMOUS_TELEMETRY=0` 或 `OMO_DISABLE_POSTHOG=1` 禁用。详见 [隐私政策](docs/legal/privacy-policy.md) 和 [服务条款](docs/legal/terms-of-service.md)。 + --- ## 跳过这个 README 吧 @@ -167,11 +171,11 @@ Read this and tell me why it's not just another boilerplate: https://raw.githubu -**Sisyphus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**) 是你的主指挥官。他负责制定计划、分配任务给专家团队,并以极其激进的并行策略推动任务直至完成。他从不半途而废。 +**Sisyphus** (`claude-opus-4-7` / **`kimi-k2.5`** / **`glm-5`**) 是你的主指挥官。他负责制定计划、分配任务给专家团队,并以极其激进的并行策略推动任务直至完成。他从不半途而废。 **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) diff --git a/assets/oh-my-opencode.schema.json b/assets/oh-my-opencode.schema.json index 607988931..056c84342 100644 --- a/assets/oh-my-opencode.schema.json +++ b/assets/oh-my-opencode.schema.json @@ -14,6 +14,13 @@ "default_run_agent": { "type": "string" }, + "agent_definitions": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, "disabled_mcps": { "type": "array", "items": { @@ -5666,11 +5673,6 @@ "minimum": 1, "maximum": 9007199254740991 }, - "maxDescendants": { - "type": "integer", - "minimum": 1, - "maximum": 9007199254740991 - }, "staleTimeoutMs": { "type": "number", "minimum": 60000 diff --git a/bun.lock b/bun.lock index f7e37f78b..77c29ca5b 100644 --- a/bun.lock +++ b/bun.lock @@ -21,26 +21,29 @@ "picomatch": "^4.0.2", "posthog-node": "^5.29.2", "vscode-jsonrpc": "^8.2.0", - "zod": "^4.3.0", }, "devDependencies": { "@types/js-yaml": "^4.0.9", "@types/picomatch": "^3.0.2", "bun-types": "1.3.11", "typescript": "^5.7.3", + "zod": "^4.3.0", }, "optionalDependencies": { - "oh-my-opencode-darwin-arm64": "3.17.0", - "oh-my-opencode-darwin-x64": "3.17.0", - "oh-my-opencode-darwin-x64-baseline": "3.17.0", - "oh-my-opencode-linux-arm64": "3.17.0", - "oh-my-opencode-linux-arm64-musl": "3.17.0", - "oh-my-opencode-linux-x64": "3.17.0", - "oh-my-opencode-linux-x64-baseline": "3.17.0", - "oh-my-opencode-linux-x64-musl": "3.17.0", - "oh-my-opencode-linux-x64-musl-baseline": "3.17.0", - "oh-my-opencode-windows-x64": "3.17.0", - "oh-my-opencode-windows-x64-baseline": "3.17.0", + "oh-my-opencode-darwin-arm64": "3.17.4", + "oh-my-opencode-darwin-x64": "3.17.4", + "oh-my-opencode-darwin-x64-baseline": "3.17.4", + "oh-my-opencode-linux-arm64": "3.17.4", + "oh-my-opencode-linux-arm64-musl": "3.17.4", + "oh-my-opencode-linux-x64": "3.17.4", + "oh-my-opencode-linux-x64-baseline": "3.17.4", + "oh-my-opencode-linux-x64-musl": "3.17.4", + "oh-my-opencode-linux-x64-musl-baseline": "3.17.4", + "oh-my-opencode-windows-x64": "3.17.4", + "oh-my-opencode-windows-x64-baseline": "3.17.4", + }, + "peerDependencies": { + "zod": "^4.0.0", }, }, }, @@ -238,27 +241,27 @@ "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], - "oh-my-opencode-darwin-arm64": ["oh-my-opencode-darwin-arm64@3.17.0", "", { "os": "darwin", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-d8VHKSjR4gWwQ7rvYn2bU+v+I3KlcgqGc0R38WCn4ZiyfTJECcOVzhOVKt4hKfJgbH2uNjpJ5eM41jPP6oJRjA=="], + "oh-my-opencode-darwin-arm64": ["oh-my-opencode-darwin-arm64@3.17.4", "", { "os": "darwin", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-N135KhfHom/qiP3lgMHfY8DvRNVyOzZMuUs6p6uYTekLduSg3i72Pnc2WyNTZEKFX2yehaLjC5ireY8SnRCbdg=="], - "oh-my-opencode-darwin-x64": ["oh-my-opencode-darwin-x64@3.17.0", "", { "os": "darwin", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-m4ir/TpacyobUFQ9xcKHq1Tn4JLHvwrOWmoJk59VQPDMUIaGWhfafgBuRFFKIkXIXRKBP1pEnV+PYGolPRBUGg=="], + "oh-my-opencode-darwin-x64": ["oh-my-opencode-darwin-x64@3.17.4", "", { "os": "darwin", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-LSh5o4oC7ItuIoqd7s1UCAVZ5I7JftEBgeLoatUeto/8by1O6MYvm12ljjP8HIXLsnfi3nJfipqLyXAiiHDHPQ=="], - "oh-my-opencode-darwin-x64-baseline": ["oh-my-opencode-darwin-x64-baseline@3.17.0", "", { "os": "darwin", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-ej4XZdt3aRpUL3mSIp5SHRDmoTdeojdgkxqF5s/6Gv79NomCIhQLNVs6yRhUihrWkVwclAkKXHM6+UkGNbWQQw=="], + "oh-my-opencode-darwin-x64-baseline": ["oh-my-opencode-darwin-x64-baseline@3.17.4", "", { "os": "darwin", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-caGra13pBdRoV/jCdRWZNeu8XUHUgIxBVn9guAJfT9bZ7AoBurqwO0wgJHUFghOydTdFxPBOGbSOYzJY3Hco5Q=="], - "oh-my-opencode-linux-arm64": ["oh-my-opencode-linux-arm64@3.17.0", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-xww4j2wRwxA0M7YpM5DT9ZScEajW9aDr5+NNoIqJQwIRCkKShmDvyhxbi/zTXLiyhu0HNySoLfN5iqfhIvNl9w=="], + "oh-my-opencode-linux-arm64": ["oh-my-opencode-linux-arm64@3.17.4", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-P9BAlcybNmJn7ZEq4pKI/qeeP6eUJd0/M/unP+FCjKJE/UwY0YJTYS/Jf9PPZbLCgwbJErPglZe2Ku6t/NXAxQ=="], - "oh-my-opencode-linux-arm64-musl": ["oh-my-opencode-linux-arm64-musl@3.17.0", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-oR0EwN9jbhshhdomnV9zv5KNfsv0LWP3G21yhBkTPc2DtzUPQ0WEhG0qgbVPV8z9rq3HsB/L0CIg+/D/CGavPQ=="], + "oh-my-opencode-linux-arm64-musl": ["oh-my-opencode-linux-arm64-musl@3.17.4", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-F7HNYc/DygFsrraMbvXSQjb16NnC9EgtBsbWgHNkRm6UbxVHkWGIuVdHFEUJ1CqHPm2C/9xIuKJ5jiZrtEXqaA=="], - "oh-my-opencode-linux-x64": ["oh-my-opencode-linux-x64@3.17.0", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-9uNbkDhJIsaTCxF+A0KOUxES0vasw+8LD9uEdN0BBgjtvQZ67XaHsPUZkSGBYzeTJgJVOImq/fwzINhqxfXahw=="], + "oh-my-opencode-linux-x64": ["oh-my-opencode-linux-x64@3.17.4", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-WgDiowJBI7nXxqFZDo3FbR0lRkxURrFbBjDVfpqj7jxRQfUrVtwedNjkgxCF8eBOQwoBrijTmxG40GiF4z219g=="], - "oh-my-opencode-linux-x64-baseline": ["oh-my-opencode-linux-x64-baseline@3.17.0", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-FaWPECkzdnT5CkHPgWsMbpsXK8vC+YgqxmBJ5SdwRb8aeRk0+ySF96dTp4rz6xkegmkwC7XXfmmW5bu9yQjzyg=="], + "oh-my-opencode-linux-x64-baseline": ["oh-my-opencode-linux-x64-baseline@3.17.4", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-BVJR1qiFe1WykrTBGYmd9XT387yR6VY8jupS/Pu0pqamRYBjeSlER4HQjOcrMY1XHJ/ygsspOcaWKJbSQ8Wcvw=="], - "oh-my-opencode-linux-x64-musl": ["oh-my-opencode-linux-x64-musl@3.17.0", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-JHEIjWvhB0Z5kHNhXirTsW7YzTb4IbV//LVCmQuhpoyDC7GeN3Wka3OPSBnTgnqw1SMvm0c6G7gNvDqeZNgzUg=="], + "oh-my-opencode-linux-x64-musl": ["oh-my-opencode-linux-x64-musl@3.17.4", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-qbLyLSc6bMAys6AwQnD4a3PR9KJNSDaMvA9DA9ARz9+yZ1tb7aA2JdEA24xAoxwct7k2EzxnQI+gssJJM4VUoQ=="], - "oh-my-opencode-linux-x64-musl-baseline": ["oh-my-opencode-linux-x64-musl-baseline@3.17.0", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-WxwUW0VWDle78U0xtF7lrcYUWB6EXf+lLBZJue3HWS4wpyTAYlynGgnZJCnG0l8bc9RLJIe4VvvmiZSFuxNIEg=="], + "oh-my-opencode-linux-x64-musl-baseline": ["oh-my-opencode-linux-x64-musl-baseline@3.17.4", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-ETqpbPN4HHc0wKfNSeAI2f0NE4nzUq+x85APomPRitVfTPxjdZbQd0TSc0O85vjT+kWj6cXjnHtviHB2BtxHog=="], - "oh-my-opencode-windows-x64": ["oh-my-opencode-windows-x64@3.17.0", "", { "os": "win32", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode.exe" } }, "sha512-XnIR++Kw1s+5MOZLqoBTCWyYaXT03JAR+5/7zYU1b5JEbQuM5L/zKXjUScXnVUHMkqtYpxUZLE7Nk6ldUyWNaA=="], + "oh-my-opencode-windows-x64": ["oh-my-opencode-windows-x64@3.17.4", "", { "os": "win32", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode.exe" } }, "sha512-RC34rbTJGtJeOvp2WTY4ZgVmtkjrduVmXCVMcIdgvQ53yNmNqx79nDITm9FVBA8Id02AHJbYmXGxKvr+XpHbNA=="], - "oh-my-opencode-windows-x64-baseline": ["oh-my-opencode-windows-x64-baseline@3.17.0", "", { "os": "win32", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode.exe" } }, "sha512-wyv/OHC/SrJVYGWMu9wD7+PUcmp87v38zBgMduqOn5PIUkmwIPOXF7tOdPeIQsch3/m/CK01RuZJ8NHAMA0bGA=="], + "oh-my-opencode-windows-x64-baseline": ["oh-my-opencode-windows-x64-baseline@3.17.4", "", { "os": "win32", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode.exe" } }, "sha512-pi43bhDpt6l1fnxkqYYkWCsec1RNxsWL7FZDXoLOGJq/0y3bobWiTNDhbEWNr+uJvOrMs/Sv3qpF1TmYeTvdiA=="], "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], diff --git a/docs/examples/coding-focused.jsonc b/docs/examples/coding-focused.jsonc index 5df5592bc..d697884f8 100644 --- a/docs/examples/coding-focused.jsonc +++ b/docs/examples/coding-focused.jsonc @@ -8,7 +8,7 @@ // Primary orchestrator: aggressive parallel delegation "sisyphus": { "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.", }, diff --git a/docs/examples/default.jsonc b/docs/examples/default.jsonc index d48f26e7d..160ef1405 100644 --- a/docs/examples/default.jsonc +++ b/docs/examples/default.jsonc @@ -7,8 +7,8 @@ "agents": { // Main orchestrator: handles delegation and drives tasks to completion "sisyphus": { - "model": "anthropic/claude-opus-4-6", - "ultrawork": { "model": "anthropic/claude-opus-4-6", "variant": "max" }, + "model": "anthropic/claude-opus-4-7", + "ultrawork": { "model": "anthropic/claude-opus-4-7", "variant": "max" }, }, // Deep autonomous worker: end-to-end implementation @@ -50,7 +50,7 @@ "categories": { "quick": { "model": "opencode/gpt-5-nano" }, "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" }, "visual-engineering": { "model": "google/gemini-3.1-pro", "variant": "high" }, "deep": { "model": "openai/gpt-5.4" }, diff --git a/docs/examples/planning-focused.jsonc b/docs/examples/planning-focused.jsonc index 48ab12c1b..407045244 100644 --- a/docs/examples/planning-focused.jsonc +++ b/docs/examples/planning-focused.jsonc @@ -7,8 +7,8 @@ "agents": { // Orchestrator: delegates to planning agents first "sisyphus": { - "model": "anthropic/claude-opus-4-6", - "ultrawork": { "model": "anthropic/claude-opus-4-6", "variant": "max" }, + "model": "anthropic/claude-opus-4-7", + "ultrawork": { "model": "anthropic/claude-opus-4-7", "variant": "max" }, "prompt_append": "Always consult prometheus and atlas for planning. Never rush to implementation.", }, @@ -20,7 +20,7 @@ // Primary planner: deep interview mode "prometheus": { - "model": "anthropic/claude-opus-4-6", + "model": "anthropic/claude-opus-4-7", "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.", }, @@ -43,7 +43,7 @@ // Plan review and refinement: heavily utilized "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.", }, @@ -98,7 +98,7 @@ "openai": 3, }, "modelConcurrency": { - "anthropic/claude-opus-4-6": 2, + "anthropic/claude-opus-4-7": 2, "openai/gpt-5.4": 2, }, }, diff --git a/docs/guide/agent-model-matching.md b/docs/guide/agent-model-matching.md index 8c3f50962..8c750a9b2 100644 --- a/docs/guide/agent-model-matching.md +++ b/docs/guide/agent-model-matching.md @@ -64,8 +64,8 @@ These agents have Claude-optimized prompts — long, detailed, mechanics-driven. | Agent | Role | Fallback Chain | Notes | | ------------ | ----------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------- | -| **Sisyphus** | Main orchestrator | 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 | Exact runtime chain from `src/shared/model-requirements.ts`. | -| **Metis** | Plan gap analyzer | 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 | 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-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 @@ -73,8 +73,8 @@ These agents ship separate prompts for Claude and GPT families. They auto-detect | Agent | Role | Fallback Chain | Notes | | -------------- | ----------------- | -------------------------------------- | -------------------------------------------------------------------- | -| **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 | Exact runtime chain from `src/shared/model-requirements.ts`. | -| **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 | 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`. | ### Deep Specialists → GPT @@ -82,9 +82,9 @@ These agents are built for GPT's principle-driven style. Their prompts assume au | Agent | Role | Fallback Chain | Notes | | -------------- | ----------------------- | -------------------------------------- | ------------------------------------------------ | -| **Hephaestus** | Autonomous deep worker | GPT-5.4 (medium) | Requires a GPT-capable provider. The craftsman. | -| **Oracle** | Architecture consultant | 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 | Exact runtime chain from `src/shared/model-requirements.ts`. | -| **Momus** | Ruthless 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 | Exact runtime chain from `src/shared/model-requirements.ts`. | +| **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-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-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 @@ -92,10 +92,10 @@ These agents do grep, search, and retrieval. They intentionally use the fastest, | Agent | Role | Fallback Chain | Notes | | --------------------- | ------------------ | ---------------------------------------------- | ----------------------------------------------------- | -| **Explore** | Fast codebase grep | 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 | Exact runtime chain from `src/shared/model-requirements.ts`. | -| **Librarian** | Docs/code search | opencode-go/minimax-m2.7 → opencode/minimax-m2.7-highspeed → anthropic\|opencode/claude-haiku-4-5 → opencode/gpt-5-nano | Exact runtime chain from `src/shared/model-requirements.ts`. | -| **Multimodal Looker** | Vision/screenshots | openai\|opencode/gpt-5.4 (medium) → opencode-go/kimi-k2.5 → zai-coding-plan/glm-4.6v → openai\|github-copilot\|opencode/gpt-5-nano | Exact runtime chain from `src/shared/model-requirements.ts`. | -| **Sisyphus-Junior** | Category executor | 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 → opencode/big-pickle | Exact runtime chain from `src/shared/model-requirements.ts`. | +| **Explore** | Fast codebase grep | github-copilot\|xai\|vercel/grok-code-fast-1 → opencode-go\|vercel/minimax-m2.7-highspeed → opencode\|vercel/minimax-m2.7 → anthropic\|opencode\|vercel/claude-haiku-4-5 → opencode\|vercel/gpt-5-nano | Exact runtime chain from `src/shared/model-requirements.ts`. | +| **Librarian** | Docs/code search | opencode-go\|vercel/minimax-m2.7 → opencode\|vercel/minimax-m2.7-highspeed → anthropic\|opencode\|vercel/claude-haiku-4-5 → opencode\|vercel/gpt-5-nano | Exact runtime chain from `src/shared/model-requirements.ts`. | +| **Multimodal Looker** | Vision/screenshots | openai\|opencode\|vercel/gpt-5.4 (medium) → opencode-go\|vercel/kimi-k2.5 → zai-coding-plan\|vercel/glm-4.6v → openai\|github-copilot\|opencode\|vercel/gpt-5-nano | Exact runtime chain from `src/shared/model-requirements.ts`. | +| **Sisyphus-Junior** | Category executor | 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 → opencode/big-pickle | Exact runtime chain from `src/shared/model-requirements.ts`. | --- @@ -107,7 +107,7 @@ Communicative, instruction-following, structured output. Best for agents that ne | 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 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. | @@ -169,17 +169,21 @@ When agents delegate work, they don't pick a model name — they pick a **catego | Category | When Used | Fallback Chain | | -------------------- | -------------------------- | -------------------------------------------- | -| `visual-engineering` | Frontend, UI, CSS, design | 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 | -| `ultrabrain` | Maximum reasoning needed | 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 | -| `deep` | Deep coding, complex logic | 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) | -| `artistry` | Creative, novel approaches | 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 | -| `quick` | Simple, fast tasks | 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-high` | General complex work | 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-low` | General standard work | 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 | -| `writing` | Text, docs, prose | 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 | +| `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-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-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-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 | +| `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 | +| `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 | See the [Orchestration System Guide](./orchestration.md) for how agents dispatch tasks to categories. +### Vercel AI Gateway fallback coverage + +`src/shared/model-requirements.ts` now includes `vercel` on nearly every gateway-compatible fallback entry across both agent and category chains. Treat it as a universal extra provider path for the listed model IDs, not as a different model family. If a row above shows `|vercel` in the provider set, that is the current source-of-truth runtime fallback, not a docs-only convenience alias. + --- ## Customization @@ -194,7 +198,7 @@ See the [Orchestration System Guide](./orchestration.md) for how agents dispatch // Main orchestrator: Claude Opus or Kimi K2.5 work best "sisyphus": { "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 @@ -213,7 +217,7 @@ See the [Orchestration System Guide](./orchestration.md) for how agents dispatch "categories": { "quick": { "model": "opencode/gpt-5-nano" }, "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": { "model": "google/gemini-3.1-pro", "variant": "high", @@ -230,7 +234,7 @@ See the [Orchestration System Guide](./orchestration.md) for how agents dispatch "zai-coding-plan": 10, }, "modelConcurrency": { - "anthropic/claude-opus-4-6": 2, + "anthropic/claude-opus-4-7": 2, "opencode/gpt-5-nano": 20, }, }, diff --git a/docs/guide/installation.md b/docs/guide/installation.md index a1d9076a8..582b5d8ba 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -14,8 +14,7 @@ https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/refs/heads/dev/do Run the interactive installer: ```bash -bunx oh-my-opencode install # recommended -npx oh-my-opencode install # alternative +bunx oh-my-opencode install ``` > **Note**: The CLI ships with standalone binaries for all major platforms. No runtime (Bun/Node.js) is required for CLI execution after installation. @@ -77,6 +76,14 @@ Ask the user these questions to determine CLI options: - If **yes** → `--opencode-go=yes` - If **no** → `--opencode-go=no` (default) +8. **Do you have a Kimi for Coding subscription?** + - If **yes** → `--kimi-for-coding=yes` + - If **no** → `--kimi-for-coding=no` (default) + +9. **Do you use Vercel AI Gateway?** + - If **yes** → `--vercel-ai-gateway=yes` + - If **no** → `--vercel-ai-gateway=no` (default) + **Provider selection is agent-specific.** The installer and runtime do not use one single global provider priority. Each agent resolves against its own fallback chain. MUST STRONGLY WARNING, WHEN USER SAID THEY DON'T HAVE CLAUDE SUBSCRIPTION, SISYPHUS AGENT MIGHT NOT WORK IDEALLY. @@ -100,7 +107,7 @@ Spawn a subagent to handle installation and report back - to save context. Based on user's answers, run the CLI installer with appropriate flags: ```bash -bunx oh-my-opencode install --no-tui --claude= --gemini= --copilot= [--openai=] [--opencode-go=] [--opencode-zen=] [--zai-coding-plan=] +bunx oh-my-opencode install --no-tui --claude= --gemini= --copilot= [--openai=] [--opencode-go=] [--opencode-zen=] [--zai-coding-plan=] [--kimi-for-coding=] [--vercel-ai-gateway=] [--skip-auth] ``` **Examples:** @@ -218,7 +225,7 @@ When GitHub Copilot is the best available provider, install-time defaults are ag | Agent | Model | | ------------- | ---------------------------------- | -| **Sisyphus** | `github-copilot/claude-opus-4.6` | +| **Sisyphus** | `github-copilot/claude-opus-4.7` | | **Oracle** | `github-copilot/gpt-5.4` | | **Explore** | `github-copilot/grok-code-fast-1` | | **Atlas** | `github-copilot/claude-sonnet-4.6` | @@ -240,13 +247,13 @@ If Z.ai is your main provider, the most important fallbacks are: #### 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: | Agent | Model | | ------------- | ---------------------------------------------------- | -| **Sisyphus** | `opencode/claude-opus-4-6` | +| **Sisyphus** | `opencode/claude-opus-4-7` | | **Oracle** | `opencode/gpt-5.4` | | **Explore** | `opencode/minimax-m2.7` | @@ -280,7 +287,7 @@ Not all models behave the same way. Understanding which models are "similar" hel | 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 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. | @@ -323,8 +330,8 @@ Based on your subscriptions, here's how the agents were configured: | 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`. | -| **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`. | +| **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-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): @@ -334,7 +341,7 @@ Priority: **Claude > GPT > Claude-like models** | 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 | **GPT-Native Agents** (built for GPT, don't override to Claude): @@ -342,8 +349,8 @@ Priority: **Claude > GPT > Claude-like models** | Agent | Role | Default Chain | Notes | | -------------- | ---------------------- | -------------------------------------- | ------------------------------------------------------ | | **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. | -| **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. | +| **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-7 (max) → google\|github-copilot\|opencode/gemini-3.1-pro (high) → opencode-go/glm-5 | Verification agent. GPT preferred. | **Utility Agents** (speed over intelligence): @@ -456,7 +463,7 @@ gh api --silent --method PUT /user/starred/code-yeongyu/oh-my-openagent >/dev/nu 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. diff --git a/docs/guide/orchestration.md b/docs/guide/orchestration.md index 5bf542cd2..0e21ce50a 100644 --- a/docs/guide/orchestration.md +++ b/docs/guide/orchestration.md @@ -15,6 +15,7 @@ Oh My OpenAgent's orchestration system transforms a simple AI agent into a coord **Decision Flow:** ``` + Is it a quick fix or simple task? └─ YES → Just prompt normally └─ NO → Is explaining the full context tedious? @@ -34,21 +35,21 @@ The orchestration system uses a three-layer architecture that solves context ove flowchart TB subgraph Planning["Planning Layer (Human + Prometheus)"] User[(" User")] - Prometheus[" Prometheus
(Planner)
Claude Opus 4.6"] - Metis[" Metis
(Consultant)
Claude Opus 4.6"] - Momus[" Momus
(Reviewer)
GPT-5.4"] + Prometheus[" Prometheus
(Planner)
claude-opus-4-7 / gpt-5.4 / glm-5"] + Metis[" Metis
(Consultant)
claude-opus-4-7 / gpt-5.4 / glm-5"] + Momus[" Momus
(Reviewer)
gpt-5.4 / claude-opus-4-7 / gemini-3.1-pro / glm-5"] end subgraph Execution["Execution Layer (Orchestrator)"] - Orchestrator[" Atlas
(Conductor)
Claude Sonnet 4.6"] + Orchestrator[" Atlas
(Conductor)
claude-sonnet-4-6 / kimi-k2.5 / gpt-5.4 / minimax-m2.7"] end subgraph Workers["Worker Layer (Specialized Agents)"] - Junior[" Sisyphus-Junior
(Task Executor)
Claude Sonnet 4.6"] - Oracle[" Oracle
(Architecture)
GPT-5.4"] - Explore[" Explore
(Codebase Grep)
Grok Code"] - Librarian[" Librarian
(Docs/OSS)
Gemini 3 Flash"] - Frontend[" Frontend
(UI/UX)
Gemini 3.1 Pro"] + Junior[" Sisyphus-Junior
(Task Executor)
claude-sonnet-4-6 / kimi-k2.5 / gpt-5.4 / minimax-m2.7"] + Oracle[" Oracle
(Architecture)
gpt-5.4 / gemini-3.1-pro / claude-opus-4-7 / glm-5"] + Explore[" Explore
(Codebase Grep)
grok-code-fast-1 / minimax-m2.7-highspeed / claude-haiku-4-5"] + Librarian[" Librarian
(Docs/OSS)
minimax-m2.7 / minimax-m2.7-highspeed / claude-haiku-4-5"] + Frontend[" visual-engineering
(category + frontend-ui-ux)
gemini-3.1-pro / glm-5 / claude-opus-4-7"] end User -->|"Describe work"| Prometheus @@ -61,11 +62,11 @@ flowchart TB User -->|"/start-work"| Orchestrator Plan -->|"Read"| Orchestrator - Orchestrator -->|"task(category)"| Junior - Orchestrator -->|"task(agent)"| Oracle - Orchestrator -->|"task(agent)"| Explore - Orchestrator -->|"task(agent)"| Librarian - Orchestrator -->|"task(agent)"| Frontend + Orchestrator -->|"task(category=deep/quick/unspecified-*)"| Junior + Orchestrator -->|"call_omo_agent(subagent_type=oracle)"| Oracle + Orchestrator -->|"call_omo_agent(subagent_type=explore)"| Explore + Orchestrator -->|"call_omo_agent(subagent_type=librarian)"| Librarian + Orchestrator -->|"task(category=visual-engineering, load_skills=[frontend-ui-ux])"| Frontend Junior -->|"Results + Learnings"| Orchestrator Oracle -->|"Advice"| Orchestrator @@ -74,6 +75,8 @@ flowchart TB Frontend -->|"UI code"| Orchestrator ``` +Model labels above show the current fallback stacks from `src/shared/model-requirements.ts`, not marketing names. + --- ## Planning: Prometheus + Metis + Momus @@ -240,7 +243,7 @@ Junior is the workhorse that actually writes code. Key characteristics: - **Verified**: Must pass lsp_diagnostics before completion - **Constrained**: Cannot modify plan files (READ-ONLY) -**Why Sonnet is Sufficient:** +**Why the fallback chain is sufficient:** Junior doesn't need to be the smartest - it needs to be reliable. With: @@ -249,7 +252,7 @@ Junior doesn't need to be the smartest - it needs to be reliable. With: 3. Clear MUST DO / MUST NOT DO constraints 4. Verification requirements -Even a mid-tier model executes precisely. The intelligence is in the **system**, not individual agents. +Even a mid-tier execution model works when the harness is strict. The current fallback order is `claude-sonnet-4-6` → `kimi-k2.5` → `gpt-5.4` → `minimax-m2.7` → `big-pickle`. The intelligence is in the **system**, not a single worker model. ### System Reminder Mechanism @@ -279,7 +282,7 @@ This "boulder pushing" mechanism is why the system is named after Sisyphus. ```typescript // OLD: Model name creates distributional bias 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:** @@ -293,16 +296,16 @@ task({ category: "quick", prompt: "..." }); // "Just get it done fast" ### Built-in Categories -| Category | Model | When to Use | -| -------------------- | ---------------------- | ----------------------------------------------------------- | -| `visual-engineering` | Gemini 3.1 Pro | Frontend, UI/UX, design, styling, animation | -| `ultrabrain` | GPT-5.4 (xhigh) | Deep logical reasoning, complex architecture decisions | -| `artistry` | Gemini 3.1 Pro (high) | Highly creative or artistic tasks, novel ideas | -| `quick` | GPT-5.4 Mini | Trivial tasks - single file changes, typo fixes | -| `deep` | GPT-5.4 (medium) | Goal-oriented autonomous problem-solving, thorough research | -| `unspecified-low` | Claude Sonnet 4.6 | Tasks that don't fit other categories, low effort | -| `unspecified-high` | Claude Opus 4.6 (max) | Tasks that don't fit other categories, high effort | -| `writing` | Gemini 3 Flash | Documentation, prose, technical writing | +| 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-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-7` → `glm-5` | Deep logical reasoning, complex architecture decisions | +| `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-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 | +| `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-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 | ### Skills: Domain-Specific Instructions @@ -317,7 +320,7 @@ task( ); task( - (category = "general"), + (category = "deep"), (load_skills = ["playwright"]), // Adds browser automation expertise (prompt = "..."), ); @@ -420,7 +423,7 @@ Atlas is automatically activated when you run `/start-work`. You don't need to m | Aspect | Hephaestus | Sisyphus + `ulw` / `ultrawork` | | --------------- | ------------------------------------------ | ---------------------------------------------------- | -| **Model** | GPT-5.4 (medium reasoning) | Claude Opus 4.6 / 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 | | **Best For** | Complex architectural work, deep reasoning | General complex tasks, "just do it" scenarios | | **Planning** | Self-plans during execution | Uses Prometheus plans if available | diff --git a/docs/guide/overview.md b/docs/guide/overview.md index 9ccf5bc4d..cf1bb783c 100644 --- a/docs/guide/overview.md +++ b/docs/guide/overview.md @@ -66,7 +66,7 @@ User Request └─→ [Category-based agents] — Specialized by task type ``` -When Sisyphus delegates to a subagent, it doesn't pick a model name. It picks a **category** — `visual-engineering`, `ultrabrain`, `quick`, `deep`. The category automatically maps to the right model. You touch nothing. +When Sisyphus delegates to a subagent, it doesn't pick a model name. It picks a **category** — `visual-engineering`, `ultrabrain`, `deep`, `artistry`, `quick`, `unspecified-low`, `unspecified-high`, `writing`. The category automatically maps to the right model. You touch nothing. For a deep dive into how agents collaborate, see the [Orchestration System Guide](./orchestration.md). @@ -82,12 +82,11 @@ Sisyphus is your main orchestrator. He plans, delegates to specialists, and driv **Recommended models:** -- **Claude Opus 4.6** — Best overall experience. Sisyphus was built with Claude-optimized prompts. -- **Claude Sonnet 4.6** — Good balance of capability and cost. +- **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. - **GLM 5** — Solid option, especially via Z.ai. -Sisyphus still works best on Claude-family models, Kimi, and GLM. 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 @@ -101,7 +100,7 @@ Use Hephaestus when you need deep architectural reasoning, complex debugging acr - **Multi-model orchestration.** Pure Codex is single-model. OmO routes different tasks to different models automatically. GPT for deep reasoning. Gemini for frontend. GPT-5.4 Mini for speed. The right brain for the right job. - **Background agents.** Fire 5+ agents in parallel. Something Codex simply cannot do. While one agent writes code, another researches patterns, another checks documentation. Like a real dev team. -- **Category system.** Tasks are routed by intent, not model name. `visual-engineering` gets Gemini. `ultrabrain` gets GPT-5.4. `quick` gets GPT-5.4 Mini. No manual juggling. +- **Category system.** Tasks are routed by intent, not model name. `visual-engineering` gets Gemini. `ultrabrain` gets GPT-5.4 xhigh. `deep` gets GPT-5.4. `artistry` gets Gemini. `quick` gets GPT-5.4 Mini. `unspecified-low` gets fast cheap models. `unspecified-high` gets Claude Opus. `writing` gets prose-optimized models. No manual juggling. - **Accumulated wisdom.** Subagents learn from previous results. Conventions discovered in task 1 are passed to task 5. Mistakes made early aren't repeated. The system gets smarter as it works. ### Prometheus: The Strategic Planner @@ -174,7 +173,7 @@ You can override specific agents or categories in your config: // Main orchestrator: Claude Opus or Kimi K2.5 work best "sisyphus": { "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 @@ -186,20 +185,32 @@ You can override specific agents or categories in your config: }, "categories": { - // Frontend work: Gemini dominates visual tasks + // Frontend/UI work: Gemini dominates visual tasks "visual-engineering": { "model": "google/gemini-3.1-pro", "variant": "high", }, - // General high-effort work - "unspecified-high": { "model": "anthropic/claude-opus-4-6", "variant": "max" }, + // Hard logic and architecture: GPT-5.4 xhigh + "ultrabrain": { "model": "openai/gpt-5.4", "variant": "xhigh" }, - // Quick tasks: use GPT-5.4-mini (fast and cheap) + // Autonomous research and execution + "deep": { "model": "openai/gpt-5.4", "variant": "high" }, + + // Creative and design work + "artistry": { "model": "google/gemini-3.1-pro", "variant": "high" }, + + // Quick tasks: fast and cheap "quick": { "model": "openai/gpt-5.4-mini" }, - // Deep reasoning: GPT-5.4 - "ultrabrain": { "model": "openai/gpt-5.4", "variant": "xhigh" }, + // Low-effort fallback: cheapest available + "unspecified-low": { "model": "openai/gpt-5.4-mini" }, + + // High-effort fallback: best available + "unspecified-high": { "model": "anthropic/claude-opus-4-7", "variant": "max" }, + + // Prose and documentation + "writing": { "model": "anthropic/claude-opus-4-7", "variant": "high" }, }, } ``` @@ -208,14 +219,14 @@ You can override specific agents or categories in your config: **Claude-like models** (instruction-following, structured output): -- Claude Opus 4.6, Claude Sonnet 4.6, Claude Haiku 4.5 +- Claude Opus 4.7, Claude Haiku 4.5 - Kimi K2.5 — behaves very similarly to Claude - GLM 5 — Claude-like behavior, good for broad tasks **GPT models** (explicit reasoning, principle-driven): - GPT-5.4 — deep coding powerhouse, required for Hephaestus and default for Oracle -- GPT-5-Nano — ultra-cheap, fast utility tasks +- GPT-5.4 Mini — fast and cheap utility tasks **Different-behavior models**: diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 5add06893..bc8892dd7 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -57,6 +57,7 @@ bunx oh-my-opencode install | `--zai-coding-plan ` | Z.ai Coding Plan subscription | | `--kimi-for-coding ` | Kimi for Coding subscription | | `--opencode-go ` | OpenCode Go subscription | +| `--vercel-ai-gateway ` | Vercel AI Gateway: no, yes (default: no) | | `--skip-auth` | Skip authentication setup hints | Anonymous telemetry uses PostHog with a hashed installation identifier. Disable it with `OMO_SEND_ANONYMOUS_TELEMETRY=0` or `OMO_DISABLE_POSTHOG=1`. See [Privacy Policy](../legal/privacy-policy.md). diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 0681677fc..04f510b6d 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -78,7 +78,7 @@ Here's a practical starting configuration: // Main orchestrator: Claude Opus or Kimi K2.5 work best "sisyphus": { "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 @@ -102,7 +102,7 @@ Here's a practical starting configuration: "unspecified-low": { "model": "anthropic/claude-sonnet-4-6" }, // 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": { "model": "google/gemini-3-flash" }, @@ -130,7 +130,7 @@ Here's a practical starting configuration: "zai-coding-plan": 10, }, "modelConcurrency": { - "anthropic/claude-opus-4-6": 2, + "anthropic/claude-opus-4-7": 2, "opencode/gpt-5-nano": 20, }, }, @@ -146,7 +146,7 @@ Here's a practical starting configuration: ### Agents -Override built-in agent settings. Available agents: `sisyphus`, `hephaestus`, `prometheus`, `oracle`, `librarian`, `explore`, `multimodal-looker`, `metis`, `momus`, `atlas`. +Override built-in agent settings. Available agents: `sisyphus`, `hephaestus`, `prometheus`, `oracle`, `librarian`, `explore`, `multimodal-looker`, `metis`, `momus`, `atlas`, `sisyphus-junior`. ```json { @@ -229,7 +229,7 @@ Control what tools an agent can use: { "agents": { "sisyphus": { - "model": "anthropic/claude-opus-4-6", + "model": "anthropic/claude-opus-4-7", "fallback_models": [ // Simple string fallback "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 | | `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-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 | > **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 | | --------------------- | ------------------- | ---------------------------------------------------------------------------- | -| **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)` | -| **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` | | **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` | -| **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` | -| **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` | -| **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` | +| **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-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-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` | #### Category Provider Chains | 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` | -| **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` | -| **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)` | -| **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` | +| **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-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-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-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` | | **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` | 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, "staleTimeoutMs": 180000, "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": { "sisyphus": { - "model": "anthropic/claude-opus-4-6", + "model": "anthropic/claude-opus-4-7", "fallback_models": [ "openai/gpt-5.4", { @@ -697,7 +697,7 @@ Define `fallback_models` per agent or category: { "agents": { "sisyphus": { - "model": "anthropic/claude-opus-4-6", + "model": "anthropic/claude-opus-4-7", "fallback_models": [ "openai/gpt-5.4", { @@ -798,7 +798,7 @@ Mix string entries and object entries when only some fallback models need specia { "agents": { "sisyphus": { - "model": "anthropic/claude-opus-4-6", + "model": "anthropic/claude-opus-4-7", "fallback_models": [ "openai/gpt-5.4", { @@ -832,7 +832,7 @@ Mix string entries and object entries when only some fallback models need specia "maxTokens": 12000 }, { - "model": "anthropic/claude-opus-4-6", + "model": "anthropic/claude-opus-4-7", "variant": "max", "temperature": 0.2 }, diff --git a/docs/reference/features.md b/docs/reference/features.md index 584c6312e..366554b6c 100644 --- a/docs/reference/features.md +++ b/docs/reference/features.md @@ -10,9 +10,9 @@ Core-agent tab cycling is deterministic via injected runtime order field. The fi | 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. | -| **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`. | | **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`. | @@ -20,9 +20,9 @@ Core-agent tab cycling is deterministic via injected runtime order field. The fi | 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`. | -| **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`. | -| **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`. | +| **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-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-7 (max)` → `google\|github-copilot\|opencode/gemini-3.1-pro (high)` → `opencode-go/glm-5`. | ### 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 | | `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-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 | ### Usage @@ -138,7 +138,7 @@ You can define custom categories in your plugin config file. During the rename t | Field | Type | Description | | ------------------- | ------- | --------------------------------------------------------------------------- | | `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`) | | `temperature` | number | Creativity level (0.0 ~ 2.0). Lower is more deterministic. | | `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 "deep-reasoning": { - "model": "anthropic/claude-opus-4-6", + "model": "anthropic/claude-opus-4-7", "thinking": { "type": "enabled", "budgetTokens": 32000, @@ -269,10 +269,11 @@ Skills provide specialized workflows with embedded MCP servers and detailed inst | ------------------ | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **git-master** | commit, rebase, squash, "who wrote", "when was X added" | Git expert. Detects commit styles, splits atomic commits, formulates rebase strategies. Three specializations: Commit Architect (atomic commits, dependency ordering, style detection), Rebase Surgeon (history rewriting, conflict resolution, branch cleanup), History Archaeologist (finding when/where specific changes were introduced). | | **playwright** | Browser tasks, testing, screenshots | Browser automation via Playwright MCP. MUST USE for browser verification, browsing, web scraping, testing, and screenshots. | -| **playwright-cli** | Browser tasks on Playwright CLI | Browser automation through the Playwright CLI integration. Useful when direct CLI scripting is preferred over MCP. | | **agent-browser** | Browser tasks on agent-browser | Browser automation via the `agent-browser` CLI. Covers navigation, snapshots, screenshots, network inspection, and scripted interactions. | | **dev-browser** | Stateful browser scripting | Browser automation with persistent page state for iterative workflows and authenticated sessions. | | **frontend-ui-ux** | UI/UX tasks, styling | Designer-turned-developer persona. Crafts stunning UI/UX even without design mockups. Emphasizes bold aesthetic direction, distinctive typography, cohesive color palettes. | +| **review-work** | "review work", "review my work", "QA my work" | Post-implementation review orchestrator. Launches 5 parallel background sub-agents for comprehensive review: goal verification, code quality, security, hands-on QA, and context mining. All must pass for review to pass. | +| **ai-slop-remover**| "remove AI slop", "de-AI", "humanize" | Removes AI-generated code smells from files while preserving functionality. Identifies and eliminates verbose comments, redundant error handling, over-engineered patterns, and generic AI phrasing. | #### git-master Core Principles diff --git a/docs/troubleshooting/ollama.md b/docs/troubleshooting/ollama.md index 986b7239b..92a310da4 100644 --- a/docs/troubleshooting/ollama.md +++ b/docs/troubleshooting/ollama.md @@ -67,7 +67,7 @@ The proper fix requires Claude Code SDK to: 3. Merge `tool_calls` from multiple lines 4. Return a single merged response -**Tracking**: https://github.com/code-yeongyu/oh-my-openagent/issues/1124 +**Tracking**: https://github.com/code-yeongyu/oh-my-openagent/issues/1124 (closed - documented workaround) ## Workaround Implementation @@ -114,7 +114,7 @@ curl -s http://localhost:11434/api/chat \ ## Related Issues -- **oh-my-openagent**: https://github.com/code-yeongyu/oh-my-openagent/issues/1124 +- **oh-my-openagent**: https://github.com/code-yeongyu/oh-my-openagent/issues/1124 (closed - workaround documented) - **Ollama API Docs**: https://github.com/ollama/ollama/blob/main/docs/api.md ## Getting Help diff --git a/package.json b/package.json index 81c26b7af..d00e0e274 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,13 @@ { "name": "oh-my-opencode", - "version": "3.17.0", + "version": "3.17.4", "description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools", "main": "./dist/index.js", "types": "dist/index.d.ts", "type": "module", "bin": { - "oh-my-opencode": "bin/oh-my-opencode.js" + "oh-my-opencode": "bin/oh-my-opencode.js", + "oh-my-openagent": "bin/oh-my-opencode.js" }, "files": [ "dist", @@ -21,7 +22,7 @@ "./schema.json": "./dist/oh-my-opencode.schema.json" }, "scripts": { - "build": "bun build src/index.ts --outdir dist --target bun --format esm --external @ast-grep/napi && tsc --emitDeclarationOnly && bun build src/cli/index.ts --outdir dist/cli --target bun --format esm --external @ast-grep/napi && bun run build:schema", + "build": "bun build src/index.ts --outdir dist --target bun --format esm --external @ast-grep/napi --external zod && tsc --emitDeclarationOnly && bun build src/cli/index.ts --outdir dist/cli --target bun --format esm --external @ast-grep/napi && bun run build:schema", "build:all": "bun run build && bun run build:binaries", "build:binaries": "bun run script/build-binaries.ts", "build:schema": "bun run script/build-schema.ts", @@ -69,32 +70,35 @@ "picocolors": "^1.1.1", "picomatch": "^4.0.2", "posthog-node": "^5.29.2", - "vscode-jsonrpc": "^8.2.0", - "zod": "^4.3.0" + "vscode-jsonrpc": "^8.2.0" }, "devDependencies": { "@types/js-yaml": "^4.0.9", "@types/picomatch": "^3.0.2", "bun-types": "1.3.11", - "typescript": "^5.7.3" + "typescript": "^5.7.3", + "zod": "^4.3.0" }, "optionalDependencies": { - "oh-my-opencode-darwin-arm64": "3.17.0", - "oh-my-opencode-darwin-x64": "3.17.0", - "oh-my-opencode-darwin-x64-baseline": "3.17.0", - "oh-my-opencode-linux-arm64": "3.17.0", - "oh-my-opencode-linux-arm64-musl": "3.17.0", - "oh-my-opencode-linux-x64": "3.17.0", - "oh-my-opencode-linux-x64-baseline": "3.17.0", - "oh-my-opencode-linux-x64-musl": "3.17.0", - "oh-my-opencode-linux-x64-musl-baseline": "3.17.0", - "oh-my-opencode-windows-x64": "3.17.0", - "oh-my-opencode-windows-x64-baseline": "3.17.0" + "oh-my-opencode-darwin-arm64": "3.17.4", + "oh-my-opencode-darwin-x64": "3.17.4", + "oh-my-opencode-darwin-x64-baseline": "3.17.4", + "oh-my-opencode-linux-arm64": "3.17.4", + "oh-my-opencode-linux-arm64-musl": "3.17.4", + "oh-my-opencode-linux-x64": "3.17.4", + "oh-my-opencode-linux-x64-baseline": "3.17.4", + "oh-my-opencode-linux-x64-musl": "3.17.4", + "oh-my-opencode-linux-x64-musl-baseline": "3.17.4", + "oh-my-opencode-windows-x64": "3.17.4", + "oh-my-opencode-windows-x64-baseline": "3.17.4" }, "overrides": {}, "trustedDependencies": [ "@ast-grep/cli", "@ast-grep/napi", "@code-yeongyu/comment-checker" - ] + ], + "peerDependencies": { + "zod": "^4.0.0" + } } diff --git a/packages/darwin-arm64/package.json b/packages/darwin-arm64/package.json index 6948df93f..ccf706faf 100644 --- a/packages/darwin-arm64/package.json +++ b/packages/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "oh-my-opencode-darwin-arm64", - "version": "3.17.0", + "version": "3.17.4", "description": "Platform-specific binary for oh-my-opencode (darwin-arm64)", "license": "MIT", "repository": { diff --git a/packages/darwin-x64-baseline/package.json b/packages/darwin-x64-baseline/package.json index 1d0cfac52..ac965d73f 100644 --- a/packages/darwin-x64-baseline/package.json +++ b/packages/darwin-x64-baseline/package.json @@ -1,6 +1,6 @@ { "name": "oh-my-opencode-darwin-x64-baseline", - "version": "3.17.0", + "version": "3.17.4", "description": "Platform-specific binary for oh-my-opencode (darwin-x64-baseline, no AVX2)", "license": "MIT", "repository": { diff --git a/packages/darwin-x64/package.json b/packages/darwin-x64/package.json index f781b2eb6..710360fdb 100644 --- a/packages/darwin-x64/package.json +++ b/packages/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "oh-my-opencode-darwin-x64", - "version": "3.17.0", + "version": "3.17.4", "description": "Platform-specific binary for oh-my-opencode (darwin-x64)", "license": "MIT", "repository": { diff --git a/packages/linux-arm64-musl/package.json b/packages/linux-arm64-musl/package.json index 12e0cd6da..ade0dd78f 100644 --- a/packages/linux-arm64-musl/package.json +++ b/packages/linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "oh-my-opencode-linux-arm64-musl", - "version": "3.17.0", + "version": "3.17.4", "description": "Platform-specific binary for oh-my-opencode (linux-arm64-musl)", "license": "MIT", "repository": { diff --git a/packages/linux-arm64/package.json b/packages/linux-arm64/package.json index 4aa3f2e4e..f4ac2294d 100644 --- a/packages/linux-arm64/package.json +++ b/packages/linux-arm64/package.json @@ -1,6 +1,6 @@ { "name": "oh-my-opencode-linux-arm64", - "version": "3.17.0", + "version": "3.17.4", "description": "Platform-specific binary for oh-my-opencode (linux-arm64)", "license": "MIT", "repository": { diff --git a/packages/linux-x64-baseline/package.json b/packages/linux-x64-baseline/package.json index cf7db7d3d..a0d51f8bc 100644 --- a/packages/linux-x64-baseline/package.json +++ b/packages/linux-x64-baseline/package.json @@ -1,6 +1,6 @@ { "name": "oh-my-opencode-linux-x64-baseline", - "version": "3.17.0", + "version": "3.17.4", "description": "Platform-specific binary for oh-my-opencode (linux-x64-baseline, no AVX2)", "license": "MIT", "repository": { diff --git a/packages/linux-x64-musl-baseline/package.json b/packages/linux-x64-musl-baseline/package.json index 83daa8252..3515050ab 100644 --- a/packages/linux-x64-musl-baseline/package.json +++ b/packages/linux-x64-musl-baseline/package.json @@ -1,6 +1,6 @@ { "name": "oh-my-opencode-linux-x64-musl-baseline", - "version": "3.17.0", + "version": "3.17.4", "description": "Platform-specific binary for oh-my-opencode (linux-x64-musl-baseline, no AVX2)", "license": "MIT", "repository": { diff --git a/packages/linux-x64-musl/package.json b/packages/linux-x64-musl/package.json index 42610eb50..528e60b0e 100644 --- a/packages/linux-x64-musl/package.json +++ b/packages/linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "oh-my-opencode-linux-x64-musl", - "version": "3.17.0", + "version": "3.17.4", "description": "Platform-specific binary for oh-my-opencode (linux-x64-musl)", "license": "MIT", "repository": { diff --git a/packages/linux-x64/package.json b/packages/linux-x64/package.json index baffc8402..621ba280b 100644 --- a/packages/linux-x64/package.json +++ b/packages/linux-x64/package.json @@ -1,6 +1,6 @@ { "name": "oh-my-opencode-linux-x64", - "version": "3.17.0", + "version": "3.17.4", "description": "Platform-specific binary for oh-my-opencode (linux-x64)", "license": "MIT", "repository": { diff --git a/packages/windows-x64-baseline/package.json b/packages/windows-x64-baseline/package.json index 66183b155..78a9ae8f9 100644 --- a/packages/windows-x64-baseline/package.json +++ b/packages/windows-x64-baseline/package.json @@ -1,6 +1,6 @@ { "name": "oh-my-opencode-windows-x64-baseline", - "version": "3.17.0", + "version": "3.17.4", "description": "Platform-specific binary for oh-my-opencode (windows-x64-baseline, no AVX2)", "license": "MIT", "repository": { diff --git a/packages/windows-x64/package.json b/packages/windows-x64/package.json index 3cfa8c721..8b6d80e6d 100644 --- a/packages/windows-x64/package.json +++ b/packages/windows-x64/package.json @@ -1,6 +1,6 @@ { "name": "oh-my-opencode-windows-x64", - "version": "3.17.0", + "version": "3.17.4", "description": "Platform-specific binary for oh-my-opencode (windows-x64)", "license": "MIT", "repository": { diff --git a/signatures/cla.json b/signatures/cla.json index dc4b60aef..cb1e65fe9 100644 --- a/signatures/cla.json +++ b/signatures/cla.json @@ -2743,6 +2743,142 @@ "created_at": "2026-04-12T01:51:16Z", "repoId": 1108837393, "pullRequestNo": 3358 + }, + { + "name": "Zireael", + "id": 3856578, + "comment_id": 4232913324, + "created_at": "2026-04-12T22:50:13Z", + "repoId": 1108837393, + "pullRequestNo": 3370 + }, + { + "name": "FuDesign2008", + "id": 908026, + "comment_id": 4232932576, + "created_at": "2026-04-12T23:02:42Z", + "repoId": 1108837393, + "pullRequestNo": 3371 + }, + { + "name": "matchai", + "id": 4658208, + "comment_id": 4233389606, + "created_at": "2026-04-13T02:27:55Z", + "repoId": 1108837393, + "pullRequestNo": 3376 + }, + { + "name": "mauriciozaffari", + "id": 26127, + "comment_id": 4238234599, + "created_at": "2026-04-13T17:07:26Z", + "repoId": 1108837393, + "pullRequestNo": 3398 + }, + { + "name": "kywoo26", + "id": 63901518, + "comment_id": 4238559975, + "created_at": "2026-04-13T18:04:07Z", + "repoId": 1108837393, + "pullRequestNo": 3402 + }, + { + "name": "garnetlyx", + "id": 12513503, + "comment_id": 4240950163, + "created_at": "2026-04-14T02:37:06Z", + "repoId": 1108837393, + "pullRequestNo": 3409 + }, + { + "name": "lightrabbit", + "id": 1521765, + "comment_id": 4242277206, + "created_at": "2026-04-14T08:13:57Z", + "repoId": 1108837393, + "pullRequestNo": 3415 + }, + { + "name": "fr1sk", + "id": 15851195, + "comment_id": 4243608500, + "created_at": "2026-04-14T11:45:25Z", + "repoId": 1108837393, + "pullRequestNo": 3419 + }, + { + "name": "grandmaster451", + "id": 90475406, + "comment_id": 4243713991, + "created_at": "2026-04-14T12:03:48Z", + "repoId": 1108837393, + "pullRequestNo": 3420 + }, + { + "name": "kithawk", + "id": 12224006, + "comment_id": 4245479006, + "created_at": "2026-04-14T16:19:42Z", + "repoId": 1108837393, + "pullRequestNo": 3428 + }, + { + "name": "orbisai0security", + "id": 242526317, + "comment_id": 4249340916, + "created_at": "2026-04-15T05:03:42Z", + "repoId": 1108837393, + "pullRequestNo": 3440 + }, + { + "name": "CHLK", + "id": 30882682, + "comment_id": 4252344048, + "created_at": "2026-04-15T13:10:30Z", + "repoId": 1108837393, + "pullRequestNo": 3455 + }, + { + "name": "omer-koren", + "id": 54630488, + "comment_id": 4257838546, + "created_at": "2026-04-16T06:34:57Z", + "repoId": 1108837393, + "pullRequestNo": 3470 + }, + { + "name": "EnochLi15", + "id": 38340798, + "comment_id": 4259785224, + "created_at": "2026-04-16T11:45:01Z", + "repoId": 1108837393, + "pullRequestNo": 3473 + }, + { + "name": "Disaster-Terminator", + "id": 47147571, + "comment_id": 4272328109, + "created_at": "2026-04-18T01:42:07Z", + "repoId": 1108837393, + "pullRequestNo": 3497 + }, + { + "name": "Netzhangheng", + "id": 25896014, + "comment_id": 4272702675, + "created_at": "2026-04-18T04:24:37Z", + "repoId": 1108837393, + "pullRequestNo": 3499 + }, + { + "name": "andomeder", + "id": 33397443, + "comment_id": 4273945668, + "created_at": "2026-04-18T14:55:50Z", + "repoId": 1108837393, + "pullRequestNo": 3514 } ] } \ No newline at end of file diff --git a/src/AGENTS.md b/src/AGENTS.md index 255bd8ea6..7db7929a7 100644 --- a/src/AGENTS.md +++ b/src/AGENTS.md @@ -1,6 +1,6 @@ # src/ — Plugin Source -**Generated:** 2026-04-11 +**Generated:** 2026-04-18 ## OVERVIEW @@ -10,7 +10,7 @@ Entry point `index.ts` orchestrates 5-step initialization: loadConfig → create | File | Purpose | |------|---------| -| `index.ts` | Plugin entry, exports `OhMyOpenCodePlugin` | +| `index.ts` | Plugin entry, default-exports `pluginModule: PluginModule` with `{ id, server }` | | `plugin-config.ts` | JSONC parse, multi-level merge, Zod v4 validation | | `create-managers.ts` | TmuxSessionManager, BackgroundManager, SkillMcpManager, ConfigHandler | | `create-tools.ts` | SkillContext + AvailableCategories + ToolRegistry (26 tools) | diff --git a/src/__tests__/perf/fixtures/in-tree/AGENTS.md b/src/__tests__/perf/fixtures/in-tree/AGENTS.md new file mode 100644 index 000000000..22257f9ad --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/AGENTS.md @@ -0,0 +1 @@ +# fixture root diff --git a/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/AGENTS.md b/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/AGENTS.md new file mode 100644 index 000000000..6bc3f0b2c --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/AGENTS.md @@ -0,0 +1 @@ +# fixture package diff --git a/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/src/file-16.ts b/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/src/file-16.ts new file mode 100644 index 000000000..dad26290a --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/src/file-16.ts @@ -0,0 +1 @@ +export const file16 = 16 diff --git a/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/src/file-17.ts b/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/src/file-17.ts new file mode 100644 index 000000000..01e60135f --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/src/file-17.ts @@ -0,0 +1 @@ +export const file17 = 17 diff --git a/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/src/file-18.ts b/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/src/file-18.ts new file mode 100644 index 000000000..000ce187b --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/src/file-18.ts @@ -0,0 +1 @@ +export const file18 = 18 diff --git a/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/src/file-19.ts b/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/src/file-19.ts new file mode 100644 index 000000000..43ebccb94 --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/src/file-19.ts @@ -0,0 +1 @@ +export const file19 = 19 diff --git a/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/src/file-20.ts b/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/src/file-20.ts new file mode 100644 index 000000000..763bfe44f --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/packages/pkg-one/src/file-20.ts @@ -0,0 +1 @@ +export const file20 = 20 diff --git a/src/__tests__/perf/fixtures/in-tree/src/AGENTS.md b/src/__tests__/perf/fixtures/in-tree/src/AGENTS.md new file mode 100644 index 000000000..df55bdcda --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/src/AGENTS.md @@ -0,0 +1 @@ +# fixture src diff --git a/src/__tests__/perf/fixtures/in-tree/src/app/file-01.ts b/src/__tests__/perf/fixtures/in-tree/src/app/file-01.ts new file mode 100644 index 000000000..8a4e4907d --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/src/app/file-01.ts @@ -0,0 +1 @@ +export const file01 = 1 diff --git a/src/__tests__/perf/fixtures/in-tree/src/app/file-02.ts b/src/__tests__/perf/fixtures/in-tree/src/app/file-02.ts new file mode 100644 index 000000000..20ca96c14 --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/src/app/file-02.ts @@ -0,0 +1 @@ +export const file02 = 2 diff --git a/src/__tests__/perf/fixtures/in-tree/src/app/file-03.ts b/src/__tests__/perf/fixtures/in-tree/src/app/file-03.ts new file mode 100644 index 000000000..b7a0ab9bd --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/src/app/file-03.ts @@ -0,0 +1 @@ +export const file03 = 3 diff --git a/src/__tests__/perf/fixtures/in-tree/src/app/file-04.ts b/src/__tests__/perf/fixtures/in-tree/src/app/file-04.ts new file mode 100644 index 000000000..5917ea7a4 --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/src/app/file-04.ts @@ -0,0 +1 @@ +export const file04 = 4 diff --git a/src/__tests__/perf/fixtures/in-tree/src/app/file-05.ts b/src/__tests__/perf/fixtures/in-tree/src/app/file-05.ts new file mode 100644 index 000000000..7c842b808 --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/src/app/file-05.ts @@ -0,0 +1 @@ +export const file05 = 5 diff --git a/src/__tests__/perf/fixtures/in-tree/src/app/file-06.ts b/src/__tests__/perf/fixtures/in-tree/src/app/file-06.ts new file mode 100644 index 000000000..b48d2d1cd --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/src/app/file-06.ts @@ -0,0 +1 @@ +export const file06 = 6 diff --git a/src/__tests__/perf/fixtures/in-tree/src/app/file-07.ts b/src/__tests__/perf/fixtures/in-tree/src/app/file-07.ts new file mode 100644 index 000000000..9de6f660f --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/src/app/file-07.ts @@ -0,0 +1 @@ +export const file07 = 7 diff --git a/src/__tests__/perf/fixtures/in-tree/src/app/file-08.ts b/src/__tests__/perf/fixtures/in-tree/src/app/file-08.ts new file mode 100644 index 000000000..2f24a3912 --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/src/app/file-08.ts @@ -0,0 +1 @@ +export const file08 = 8 diff --git a/src/__tests__/perf/fixtures/in-tree/src/app/file-09.ts b/src/__tests__/perf/fixtures/in-tree/src/app/file-09.ts new file mode 100644 index 000000000..2c4cddbd4 --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/src/app/file-09.ts @@ -0,0 +1 @@ +export const file09 = 9 diff --git a/src/__tests__/perf/fixtures/in-tree/src/app/file-10.ts b/src/__tests__/perf/fixtures/in-tree/src/app/file-10.ts new file mode 100644 index 000000000..1d329a0dc --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/src/app/file-10.ts @@ -0,0 +1 @@ +export const file10 = 10 diff --git a/src/__tests__/perf/fixtures/in-tree/src/lib/file-11.ts b/src/__tests__/perf/fixtures/in-tree/src/lib/file-11.ts new file mode 100644 index 000000000..eb1a64844 --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/src/lib/file-11.ts @@ -0,0 +1 @@ +export const file11 = 11 diff --git a/src/__tests__/perf/fixtures/in-tree/src/lib/file-12.ts b/src/__tests__/perf/fixtures/in-tree/src/lib/file-12.ts new file mode 100644 index 000000000..6dbff13ec --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/src/lib/file-12.ts @@ -0,0 +1 @@ +export const file12 = 12 diff --git a/src/__tests__/perf/fixtures/in-tree/src/lib/file-13.ts b/src/__tests__/perf/fixtures/in-tree/src/lib/file-13.ts new file mode 100644 index 000000000..5a46ab064 --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/src/lib/file-13.ts @@ -0,0 +1 @@ +export const file13 = 13 diff --git a/src/__tests__/perf/fixtures/in-tree/src/lib/file-14.ts b/src/__tests__/perf/fixtures/in-tree/src/lib/file-14.ts new file mode 100644 index 000000000..32824f748 --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/src/lib/file-14.ts @@ -0,0 +1 @@ +export const file14 = 14 diff --git a/src/__tests__/perf/fixtures/in-tree/src/lib/file-15.ts b/src/__tests__/perf/fixtures/in-tree/src/lib/file-15.ts new file mode 100644 index 000000000..c0d19485f --- /dev/null +++ b/src/__tests__/perf/fixtures/in-tree/src/lib/file-15.ts @@ -0,0 +1 @@ +export const file15 = 15 diff --git a/src/__tests__/perf/plugin-init.test.ts b/src/__tests__/perf/plugin-init.test.ts new file mode 100644 index 000000000..1450b6557 --- /dev/null +++ b/src/__tests__/perf/plugin-init.test.ts @@ -0,0 +1,121 @@ +import { cpSync, mkdirSync, mkdtempSync, rmSync } from "node:fs" +import { tmpdir } from "node:os" +import { join } from "node:path" + +import type { PluginInput } from "@opencode-ai/plugin" +import { createOpencodeClient } from "@opencode-ai/sdk" +import { describe, expect, it } from "bun:test" + +type InitMetrics = { + coldMs: number + warmMs: [number, number] + medianMs: number +} + +function getMedian(values: number[]): number { + const sorted = [...values].sort((left, right) => left - right) + return sorted[Math.floor(sorted.length / 2)] ?? 0 +} + +function createPluginInput(directory: string): PluginInput { + const client = createOpencodeClient({ directory }) + + return { + client, + project: { + id: `perf-${Date.now()}`, + worktree: directory, + time: { created: Date.now() }, + }, + directory, + worktree: directory, + serverUrl: new URL("http://localhost"), + $: Bun.$, + } +} + +async function importFreshPluginModule(): Promise<(typeof import("../../index"))["default"]> { + const token = `${Date.now()}-${Math.random()}` + return (await import(`../../index?perf=${token}`)).default +} + +async function measureInitMetrics(directory: string): Promise { + const pluginModule = await importFreshPluginModule() + const measurements: number[] = [] + + for (let index = 0; index < 3; index += 1) { + const input = createPluginInput(directory) + const start = performance.now() + await pluginModule.server(input, {}) + measurements.push(performance.now() - start) + } + + return { + coldMs: measurements[0] ?? 0, + warmMs: [measurements[1] ?? 0, measurements[2] ?? 0], + medianMs: getMedian(measurements), + } +} + +async function measureScenario( + label: string, + populateDirectory: (directory: string) => void, +): Promise { + const rootDirectory = mkdtempSync(join(tmpdir(), "perf-d09-")) + const projectDirectory = join(rootDirectory, label) + const configDirectory = join(rootDirectory, "opencode-config") + const previousConfigDirectory = process.env.OPENCODE_CONFIG_DIR + + mkdirSync(configDirectory, { recursive: true }) + process.env.OPENCODE_CONFIG_DIR = configDirectory + + try { + populateDirectory(projectDirectory) + return await measureInitMetrics(projectDirectory) + } finally { + if (previousConfigDirectory === undefined) { + delete process.env.OPENCODE_CONFIG_DIR + } else { + process.env.OPENCODE_CONFIG_DIR = previousConfigDirectory + } + + rmSync(rootDirectory, { recursive: true, force: true }) + } +} + +function logMetrics(label: string, metrics: InitMetrics): void { + console.info( + `${label}: cold=${metrics.coldMs.toFixed(1)}ms warm=[${metrics.warmMs.map((value) => value.toFixed(1)).join(", ")}] median=${metrics.medianMs.toFixed(1)}ms`, + ) +} + +describe("plugin init performance", () => { + it("stays within the empty project init budget", async () => { + // given + const metrics = await measureScenario("empty-project", (directory) => { + mkdirSync(directory, { recursive: true }) + }) + + // when + logMetrics("empty-project", metrics) + + // then + // regression budget + expect(metrics.medianMs).toBeLessThan(500) + }) + + it("stays within the in-tree fixture init budget", async () => { + // given + const fixtureDirectory = new URL("./fixtures/in-tree/", import.meta.url) + const metrics = await measureScenario("in-tree-fixture", (directory) => { + cpSync(fixtureDirectory, directory, { recursive: true }) + }) + + // when + logMetrics("in-tree-fixture", metrics) + + // then + // regression budget + expect(metrics.medianMs).toBeLessThan(700) + }) +}) diff --git a/src/agents/AGENTS.md b/src/agents/AGENTS.md index 137658b1f..f92c44406 100644 --- a/src/agents/AGENTS.md +++ b/src/agents/AGENTS.md @@ -1,6 +1,6 @@ # src/agents/ — 11 Agent Definitions -**Generated:** 2026-04-11 +**Generated:** 2026-04-18 ## OVERVIEW @@ -10,16 +10,16 @@ Agent factories following `createXXXAgent(model) → AgentConfig` pattern. Each | 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 | -| **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 | | **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 | -| **Metis** | claude-opus-4-6 max | **0.3** | subagent | gpt-5.4 high -> gemini-3.1-pro high | Pre-planning consultant | -| **Momus** | gpt-5.4 xhigh | 0.1 | subagent | claude-opus-4-6 max -> gemini-3.1-pro high | Plan reviewer | +| **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-7 max -> gemini-3.1-pro high | Plan reviewer | | **Atlas** | claude-sonnet-4-6 | 0.1 | primary | gpt-5.4 medium | Todo-list orchestrator | -| **Prometheus** | claude-opus-4-6 max | 0.1 | — | 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 | ## TOOL RESTRICTIONS diff --git a/src/agents/agent-identity.test.ts b/src/agents/agent-identity.test.ts index 1247cda17..2c6a568a8 100644 --- a/src/agents/agent-identity.test.ts +++ b/src/agents/agent-identity.test.ts @@ -57,7 +57,7 @@ describe("Sisyphus prompt identity", () => { describe("#given a Sisyphus agent created with default model", () => { describe("#when checking the prompt", () => { 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("") expect(config.prompt).toContain("Sisyphus") @@ -65,7 +65,7 @@ describe("Sisyphus prompt identity", () => { }) 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 identityIndex = prompt.indexOf("") const roleIndex = prompt.indexOf("") @@ -115,7 +115,7 @@ describe("Agent identity preservation through overrides", () => { describe("#given a Sisyphus agent with prompt_append override", () => { describe("#when merging the override", () => { 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" }) expect(merged.prompt).toContain("") @@ -129,7 +129,7 @@ describe("Agent identity preservation through overrides", () => { describe("#given a Sisyphus agent with model override only", () => { describe("#when merging the override", () => { 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" }) expect(merged.prompt).toContain("") diff --git a/src/agents/atlas/default-prompt-sections.ts b/src/agents/atlas/default-prompt-sections.ts index 46e2634f7..24ba9f807 100644 --- a/src/agents/atlas/default-prompt-sections.ts +++ b/src/agents/atlas/default-prompt-sections.ts @@ -150,16 +150,16 @@ task( ### 3.5 Handle Failures (USE RESUME) -**CRITICAL: When re-delegating, ALWAYS use \`session_id\` parameter.** +**CRITICAL: When re-delegating, ALWAYS use \`task_id\` parameter.** -Every \`task()\` output includes a session_id. STORE IT. +Every \`task()\` output includes a task_id. STORE IT. If task fails: 1. Identify what went wrong 2. **Resume the SAME session** - subagent has full context already: \`\`\`typescript task( - session_id="ses_xyz789", // Session from failed task + task_id="ses_xyz789", // Task ID from failed task load_skills=[...], prompt="FAILED: {error}. Fix by: {specific instruction}" ) @@ -167,7 +167,7 @@ If task fails: 3. Maximum 3 retry attempts with the SAME session 4. If blocked after 3 attempts: Document and continue to independent tasks -**Why session_id is MANDATORY for failures:** +**Why task_id is MANDATORY for failures:** - Subagent already read all files, knows the context - No repeated exploration = 70%+ token savings - Subagent knows what approaches already failed @@ -292,6 +292,6 @@ export const DEFAULT_ATLAS_CRITICAL_RULES = ` - Pass inherited wisdom to every subagent - Parallelize independent tasks - Verify with your own tools -- **Store session_id from every delegation output** -- **Use \`session_id="{session_id}"\` for retries, fixes, and follow-ups** +- **Store task_id from every delegation output** +- **Use \`task_id="{task_id}"\` for retries, fixes, and follow-ups** ` diff --git a/src/agents/atlas/gemini-prompt-sections.ts b/src/agents/atlas/gemini-prompt-sections.ts index 7a84e3d73..2ca4c2bc2 100644 --- a/src/agents/atlas/gemini-prompt-sections.ts +++ b/src/agents/atlas/gemini-prompt-sections.ts @@ -164,10 +164,10 @@ Count remaining **top-level task** checkboxes. Ignore nested verification/eviden ### 3.5 Handle Failures -**CRITICAL: Use \`session_id\` for retries.** +**CRITICAL: Use \`task_id\` for retries.** \`\`\`typescript -task(session_id="ses_xyz789", load_skills=[...], prompt="FAILED: {error}. Fix by: {instruction}") +task(task_id="ses_xyz789", load_skills=[...], prompt="FAILED: {error}. Fix by: {instruction}") \`\`\` - Maximum 3 retries per task diff --git a/src/agents/atlas/gpt-prompt-sections.ts b/src/agents/atlas/gpt-prompt-sections.ts index 96977f777..1a9f39c26 100644 --- a/src/agents/atlas/gpt-prompt-sections.ts +++ b/src/agents/atlas/gpt-prompt-sections.ts @@ -169,10 +169,10 @@ Count remaining **top-level task** checkboxes. Ignore nested verification/eviden ### 3.5 Handle Failures -**CRITICAL: Use \`session_id\` for retries.** +**CRITICAL: Use \`task_id\` for retries.** \`\`\`typescript -task(session_id="ses_xyz789", load_skills=[...], prompt="FAILED: {error}. Fix by: {instruction}") +task(task_id="ses_xyz789", load_skills=[...], prompt="FAILED: {error}. Fix by: {instruction}") \`\`\` - Maximum 3 retries per task diff --git a/src/agents/builtin-agents/sisyphus-agent.test.ts b/src/agents/builtin-agents/sisyphus-agent.test.ts index e55fea125..e7289f6c0 100644 --- a/src/agents/builtin-agents/sisyphus-agent.test.ts +++ b/src/agents/builtin-agents/sisyphus-agent.test.ts @@ -43,7 +43,7 @@ describe("maybeCreateSisyphusConfig", () => { // given const agentOverrides: AgentOverrides = { sisyphus: { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", permission: { apply_patch: "allow", }, @@ -55,8 +55,8 @@ describe("maybeCreateSisyphusConfig", () => { const config = maybeCreateSisyphusConfig({ disabledAgents: [], agentOverrides, - availableModels: new Set(["anthropic/claude-opus-4-6"]), - systemDefaultModel: "anthropic/claude-opus-4-6", + availableModels: new Set(["anthropic/claude-opus-4-7"]), + systemDefaultModel: "anthropic/claude-opus-4-7", isFirstRunNoCache: false, availableAgents: [], availableSkills: [], @@ -67,7 +67,7 @@ describe("maybeCreateSisyphusConfig", () => { // then 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 expect(config?.permission).toHaveProperty("apply_patch", "allow"); }); diff --git a/src/agents/custom-agent-orchestrator-visibility.test.ts b/src/agents/custom-agent-orchestrator-visibility.test.ts index c0b709e4b..b526d3f9f 100644 --- a/src/agents/custom-agent-orchestrator-visibility.test.ts +++ b/src/agents/custom-agent-orchestrator-visibility.test.ts @@ -2,13 +2,13 @@ import { describe, expect, spyOn, test } from "bun:test" import { createBuiltinAgents } from "./builtin-agents" 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", () => { test("#given runtime custom agents #when orchestrator prompts are built #then custom agents are not advertised for automatic delegation", async () => { //#given 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 { diff --git a/src/agents/dynamic-agent-core-sections.ts b/src/agents/dynamic-agent-core-sections.ts index dc91fd480..416750a54 100644 --- a/src/agents/dynamic-agent-core-sections.ts +++ b/src/agents/dynamic-agent-core-sections.ts @@ -182,7 +182,7 @@ Multi-step task? **ALWAYS consult Plan Agent first.** Do NOT start implementatio - Single-file fix or trivial change → proceed directly - Anything else (2+ steps, unclear scope, architecture) → \`task(subagent_type="plan", ...)\` FIRST -- Use \`session_id\` to resume the same Plan Agent - ask follow-up questions aggressively +- Use \`task_id\` to resume the same Plan Agent - ask follow-up questions aggressively - If ANY part of the task is ambiguous, ask Plan Agent before guessing Plan Agent returns a structured work breakdown with parallel execution opportunities. Follow it.` diff --git a/src/agents/dynamic-agent-prompt-builder.test.ts b/src/agents/dynamic-agent-prompt-builder.test.ts index 7fab72a75..7e4b57305 100644 --- a/src/agents/dynamic-agent-prompt-builder.test.ts +++ b/src/agents/dynamic-agent-prompt-builder.test.ts @@ -211,7 +211,7 @@ describe("buildParallelDelegationSection", () => { it("#given Claude model #when building #then returns empty", () => { //#given - const model = "anthropic/claude-opus-4-6" + const model = "anthropic/claude-opus-4-7" const categories = [deepCategory] //#when @@ -244,7 +244,7 @@ describe("buildNonClaudePlannerSection", () => { //#then expect(result).toContain("Plan Agent") - expect(result).toContain("session_id") + expect(result).toContain("task_id") expect(result).toContain("Multi-step") }) diff --git a/src/agents/explore.ts b/src/agents/explore.ts index c62cc9993..3449ee346 100644 --- a/src/agents/explore.ts +++ b/src/agents/explore.ts @@ -25,13 +25,10 @@ export const EXPLORE_PROMPT_METADATA: AgentPromptMetadata = { } export function createExploreAgent(model: string): AgentConfig { - const restrictions = createAgentToolRestrictions([ - "write", - "edit", - "apply_patch", - "task", - "call_omo_agent", - ]) + const restrictions = createAgentToolRestrictions( + ["write", "edit", "apply_patch", "task", "call_omo_agent"], + ["lsp_symbols", "lsp_goto_definition", "lsp_find_references", "lsp_diagnostics", "ast_grep_search"], + ) return { description: diff --git a/src/agents/hephaestus/agent.test.ts b/src/agents/hephaestus/agent.test.ts index 7818e0361..5721f006a 100644 --- a/src/agents/hephaestus/agent.test.ts +++ b/src/agents/hephaestus/agent.test.ts @@ -56,7 +56,7 @@ describe("getHephaestusPromptSource", () => { test("returns 'gpt' for non-GPT models and undefined", () => { // given - const model1 = "anthropic/claude-opus-4-6"; + const model1 = "anthropic/claude-opus-4-7"; const model2 = undefined; // when @@ -124,7 +124,7 @@ describe("getHephaestusPrompt", () => { test("Claude model returns generic GPT prompt (Hephaestus default)", () => { // given - const model = "anthropic/claude-opus-4-6"; + const model = "anthropic/claude-opus-4-7"; // when const prompt = getHephaestusPrompt(model); @@ -149,7 +149,7 @@ describe("getHephaestusPrompt", () => { test("useTaskSystem=false includes Todo Discipline for Claude models", () => { // given - const model = "anthropic/claude-opus-4-6"; + const model = "anthropic/claude-opus-4-7"; // when const prompt = getHephaestusPrompt(model, false); @@ -239,7 +239,7 @@ describe("createHephaestusAgent", () => { // given const gpt54Model = "openai/gpt-5.4"; const gptGenericModel = "openai/gpt-4o"; - const claudeModel = "anthropic/claude-opus-4-6"; + const claudeModel = "anthropic/claude-opus-4-7"; // when const gpt54Config = createHephaestusAgent(gpt54Model); @@ -322,7 +322,7 @@ describe("maybeCreateHephaestusConfig GPT apply_patch guard", () => { // given const agentOverrides: AgentOverrides = { hephaestus: { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", permission: { apply_patch: "allow", }, @@ -334,8 +334,8 @@ describe("maybeCreateHephaestusConfig GPT apply_patch guard", () => { const config = maybeCreateHephaestusConfig({ disabledAgents: [], agentOverrides, - availableModels: new Set(["anthropic/claude-opus-4-6"]), - systemDefaultModel: "anthropic/claude-opus-4-6", + availableModels: new Set(["anthropic/claude-opus-4-7"]), + systemDefaultModel: "anthropic/claude-opus-4-7", isFirstRunNoCache: false, availableAgents: [], availableSkills: [], @@ -346,7 +346,7 @@ describe("maybeCreateHephaestusConfig GPT apply_patch guard", () => { // then 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"); }); }); diff --git a/src/agents/hephaestus/gpt-5-3-codex.ts b/src/agents/hephaestus/gpt-5-3-codex.ts index 2ca2964f7..28127bcc8 100644 --- a/src/agents/hephaestus/gpt-5-3-codex.ts +++ b/src/agents/hephaestus/gpt-5-3-codex.ts @@ -409,9 +409,9 @@ After delegation, ALWAYS verify: works as expected? follows codebase pattern? MU Every \`task()\` output includes a session_id. **USE IT for follow-ups.** -- **Task failed/incomplete** - \`session_id="{id}", prompt="Fix: {error}"\` -- **Follow-up on result** - \`session_id="{id}", prompt="Also: {question}"\` -- **Verification failed** - \`session_id="{id}", prompt="Failed: {error}. Fix."\` +- **Task failed/incomplete** - \`task_id="{id}", prompt="Fix: {error}"\` +- **Follow-up on result** - \`task_id="{id}", prompt="Also: {question}"\` +- **Verification failed** - \`task_id="{id}", prompt="Failed: {error}. Fix."\` ${ oracleSection diff --git a/src/agents/hephaestus/gpt-5-4.ts b/src/agents/hephaestus/gpt-5-4.ts index a88b6ea0f..eec4e18b4 100644 --- a/src/agents/hephaestus/gpt-5-4.ts +++ b/src/agents/hephaestus/gpt-5-4.ts @@ -312,10 +312,10 @@ Every delegation prompt needs these 6 sections: After delegation, verify by reading every file the subagent touched. Check: works as expected? follows codebase pattern? Do not trust self-reports. -Every \`task()\` returns a session_id. Use it for all follow-ups: -- Task failed/incomplete: \`session_id="{id}", prompt="Fix: {error}"\` -- Follow-up on result: \`session_id="{id}", prompt="Also: {question}"\` -- Verification failed: \`session_id="{id}", prompt="Failed: {error}. Fix."\` +Every \`task()\` returns a task_id. Use it for all follow-ups: +- Task failed/incomplete: \`task_id="{id}", prompt="Fix: {error}"\` +- Follow-up on result: \`task_id="{id}", prompt="Also: {question}"\` +- Verification failed: \`task_id="{id}", prompt="Failed: {error}. Fix."\` This preserves full context, avoids repeated exploration, saves 70%+ tokens. diff --git a/src/agents/hephaestus/gpt.ts b/src/agents/hephaestus/gpt.ts index cf1a3ea91..712bb9536 100644 --- a/src/agents/hephaestus/gpt.ts +++ b/src/agents/hephaestus/gpt.ts @@ -277,11 +277,11 @@ After delegation, ALWAYS verify: works as expected? follows codebase pattern? MU ### Session Continuity -Every \`task()\` output includes a session_id. **USE IT for follow-ups.** +Every \`task()\` output includes a task_id. **USE IT for follow-ups.** -- **Task failed/incomplete** - \`session_id="{id}", prompt="Fix: {error}"\` -- **Follow-up on result** - \`session_id="{id}", prompt="Also: {question}"\` -- **Verification failed** - \`session_id="{id}", prompt="Failed: {error}. Fix."\` +- **Task failed/incomplete** - \`task_id="{id}", prompt="Fix: {error}"\` +- **Follow-up on result** - \`task_id="{id}", prompt="Also: {question}"\` +- **Verification failed** - \`task_id="{id}", prompt="Failed: {error}. Fix."\` ${ oracleSection diff --git a/src/agents/sisyphus.ts b/src/agents/sisyphus.ts index 55b6c1c21..81a863d54 100644 --- a/src/agents/sisyphus.ts +++ b/src/agents/sisyphus.ts @@ -317,15 +317,15 @@ AFTER THE WORK YOU DELEGATED SEEMS DONE, ALWAYS VERIFY THE RESULTS AS FOLLOWING: ### Session Continuity (MANDATORY) -Every \`task()\` output includes a session_id. **USE IT.** +Every \`task()\` output includes a task_id. **USE IT.** **ALWAYS continue when:** -- Task failed/incomplete → \`session_id=\"{session_id}\", prompt=\"Fix: {specific error}\"\` -- Follow-up question on result → \`session_id=\"{session_id}\", prompt=\"Also: {question}\"\` -- Multi-turn with same agent → \`session_id=\"{session_id}\"\` - NEVER start fresh -- Verification failed → \`session_id=\"{session_id}\", prompt=\"Failed verification: {error}. Fix.\"\` +- Task failed/incomplete → \`task_id=\"{task_id}\", prompt=\"Fix: {specific error}\"\` +- Follow-up question on result → \`task_id=\"{task_id}\", prompt=\"Also: {question}\"\` +- Multi-turn with same agent → \`task_id=\"{task_id}\"\` - NEVER start fresh +- Verification failed → \`task_id=\"{task_id}\", prompt=\"Failed verification: {error}. Fix.\"\` -**Why session_id is CRITICAL:** +**Why task_id is CRITICAL:** - Subagent has FULL conversation context preserved - No repeated file reads, exploration, or setup - Saves 70%+ tokens on follow-ups @@ -336,10 +336,10 @@ Every \`task()\` output includes a session_id. **USE IT.** task(category="quick", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix the type error in auth.ts...") // CORRECT: Resume preserves everything -task(session_id="ses_abc123", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix: Type error on line 42") +task(task_id="ses_abc123", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix: Type error on line 42") \`\`\` -**After EVERY delegation, STORE the session_id for potential continuation.** +**After EVERY delegation, STORE the task_id for potential continuation.** ### Code Changes: - Match existing patterns (if codebase is disciplined) diff --git a/src/agents/sisyphus/default.ts b/src/agents/sisyphus/default.ts index 14895b124..52e237f7c 100644 --- a/src/agents/sisyphus/default.ts +++ b/src/agents/sisyphus/default.ts @@ -389,15 +389,15 @@ AFTER THE WORK YOU DELEGATED SEEMS DONE, ALWAYS VERIFY THE RESULTS AS FOLLOWING: ### Session Continuity (MANDATORY) -Every \`task()\` output includes a session_id. **USE IT.** +Every \`task()\` output includes a task_id. **USE IT.** **ALWAYS continue when:** -- Task failed/incomplete → \`session_id="{session_id}", prompt="Fix: {specific error}"\` -- Follow-up question on result → \`session_id="{session_id}", prompt="Also: {question}"\` -- Multi-turn with same agent → \`session_id="{session_id}"\` - NEVER start fresh -- Verification failed → \`session_id="{session_id}", prompt="Failed verification: {error}. Fix."\` +- Task failed/incomplete → \`task_id="{task_id}", prompt="Fix: {specific error}"\` +- Follow-up question on result → \`task_id="{task_id}", prompt="Also: {question}"\` +- Multi-turn with same agent → \`task_id="{task_id}"\` - NEVER start fresh +- Verification failed → \`task_id="{task_id}", prompt="Failed verification: {error}. Fix."\` -**Why session_id is CRITICAL:** +**Why task_id is CRITICAL:** - Subagent has FULL conversation context preserved - No repeated file reads, exploration, or setup - Saves 70%+ tokens on follow-ups @@ -408,10 +408,10 @@ Every \`task()\` output includes a session_id. **USE IT.** task(category="quick", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix the type error in auth.ts...") // CORRECT: Resume preserves everything -task(session_id="ses_abc123", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix: Type error on line 42") +task(task_id="ses_abc123", load_skills=[], run_in_background=false, description="Fix type error", prompt="Fix: Type error on line 42") \`\`\` -**After EVERY delegation, STORE the session_id for potential continuation.** +**After EVERY delegation, STORE the task_id for potential continuation.** ### Code Changes: - Match existing patterns (if codebase is disciplined) diff --git a/src/agents/sisyphus/gpt-5-4.ts b/src/agents/sisyphus/gpt-5-4.ts index 9e8219015..4667e3466 100644 --- a/src/agents/sisyphus/gpt-5-4.ts +++ b/src/agents/sisyphus/gpt-5-4.ts @@ -387,10 +387,10 @@ Post-delegation: delegation never substitutes for verification. Always run \` { }); 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("openai/o1")).toBe(false); }); @@ -64,7 +64,7 @@ describe("isGptModel", () => { }); 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("litellm/anthropic.claude-opus-4-5")).toBe(false); }); @@ -75,7 +75,7 @@ describe("isGptModel", () => { }); 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", () => { 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("opencode-go/kimi-k2.5")).toBe(false); }); @@ -116,7 +116,7 @@ describe("isGlmModel", () => { test("#given non-GLM models #then returns 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); }); }); @@ -156,11 +156,11 @@ describe("isGeminiModel", () => { }); 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); }); 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); }); }); diff --git a/src/agents/utils.test.ts b/src/agents/utils.test.ts index b63a34827..bd8bb5740 100644 --- a/src/agents/utils.test.ts +++ b/src/agents/utils.test.ts @@ -7,7 +7,7 @@ import * as connectedProvidersCache from "../shared/connected-providers-cache" import * as modelAvailability from "../shared/model-availability" 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"] async function importFreshBuiltinAgentsModule(): Promise { @@ -32,7 +32,7 @@ describe("createBuiltinAgents with model overrides", () => { // #given const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue( new Set([ - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", "kimi-for-coding/k2p5", "opencode/kimi-k2.5-free", "zai-coding-plan/glm-5", @@ -45,7 +45,7 @@ describe("createBuiltinAgents with model overrides", () => { const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], {}) // #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.reasoningEffort).toBeUndefined() } finally { @@ -170,7 +170,7 @@ describe("createBuiltinAgents with model overrides", () => { test("Sisyphus is created on first run when no availableModels or cache exist", async () => { // #given - const systemDefaultModel = "anthropic/claude-opus-4-6" + const systemDefaultModel = "anthropic/claude-opus-4-7" const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null) const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set()) @@ -180,7 +180,7 @@ describe("createBuiltinAgents with model overrides", () => { // #then 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 { cacheSpy.mockRestore() fetchSpy.mockRestore() @@ -299,7 +299,7 @@ describe("createBuiltinAgents with model overrides", () => { // #given const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue( new Set([ - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", "kimi-for-coding/k2p5", "opencode/kimi-k2.5-free", "zai-coding-plan/glm-5", @@ -341,7 +341,7 @@ describe("createBuiltinAgents with model overrides", () => { test("excludes hidden custom agents from orchestrator prompts", async () => { // #given const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue( - new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"]) + new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"]) ) const customAgentSummaries = [ @@ -377,7 +377,7 @@ describe("createBuiltinAgents with model overrides", () => { test("excludes disabled custom agents from orchestrator prompts", async () => { // #given const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue( - new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"]) + new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"]) ) const customAgentSummaries = [ @@ -413,7 +413,7 @@ describe("createBuiltinAgents with model overrides", () => { test("excludes custom agents when disabledAgents contains their name (case-insensitive)", async () => { // #given const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue( - new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"]) + new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"]) ) const disabledAgents = ["ReSeArChEr"] @@ -449,7 +449,7 @@ describe("createBuiltinAgents with model overrides", () => { test("does not advertise duplicate custom agents case-insensitively", async () => { // #given 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 = [ @@ -481,7 +481,7 @@ describe("createBuiltinAgents with model overrides", () => { test("does not surface custom agent strings in orchestrator prompts", async () => { // #given 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 = [ @@ -555,7 +555,7 @@ describe("createBuiltinAgents without systemDefaultModel", () => { ]) const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue( new Set([ - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", "kimi-for-coding/k2p5", "opencode/kimi-k2.5-free", "zai-coding-plan/glm-5", @@ -569,7 +569,7 @@ describe("createBuiltinAgents without systemDefaultModel", () => { // #then 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 { cacheSpy.mockRestore() fetchSpy.mockRestore() @@ -590,7 +590,7 @@ describe("createBuiltinAgents with requiresProvider gating (hephaestus)", () => const providers = options?.connectedProviders ?? [] return providers.includes("openai") ? new Set(["openai/gpt-5.3-codex"]) - : new Set(["anthropic/claude-opus-4-6"]) + : new Set(["anthropic/claude-opus-4-7"]) }) try { @@ -609,7 +609,7 @@ describe("createBuiltinAgents with requiresProvider gating (hephaestus)", () => test("hephaestus is not created when no required provider is connected", async () => { // #given - only anthropic models available, not in hephaestus requiresProvider 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"]) @@ -699,10 +699,10 @@ describe("createBuiltinAgents with requiresProvider gating (hephaestus)", () => test("hephaestus is created when explicit config provided even if provider unavailable", async () => { // #given const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue( - new Set(["anthropic/claude-opus-4-6"]) + new Set(["anthropic/claude-opus-4-7"]) ) const overrides = { - hephaestus: { model: "anthropic/claude-opus-4-6" }, + hephaestus: { model: "anthropic/claude-opus-4-7" }, } try { @@ -781,7 +781,7 @@ describe("Sisyphus and Librarian environment context toggle", () => { beforeEach(() => { 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(() => { 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 () => { // #given const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue( - new Set(["anthropic/claude-opus-4-6"]) + new Set(["anthropic/claude-opus-4-7"]) ) try { @@ -918,7 +918,7 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => { // #then 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 { cacheSpy.mockRestore() fetchSpy.mockRestore() @@ -929,7 +929,7 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => { // #given const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set()) const overrides = { - sisyphus: { model: "anthropic/claude-opus-4-6" }, + sisyphus: { model: "anthropic/claude-opus-4-7" }, } try { @@ -1039,7 +1039,7 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => { describe("buildAgent with category and skills", () => { const { buildAgent } = require("./agent-builder") - const TEST_MODEL = "anthropic/claude-opus-4-6" + const TEST_MODEL = "anthropic/claude-opus-4-7" beforeEach(() => { clearSkillCache() diff --git a/src/cli/AGENTS.md b/src/cli/AGENTS.md index 7ac648408..47b61eb49 100644 --- a/src/cli/AGENTS.md +++ b/src/cli/AGENTS.md @@ -1,6 +1,6 @@ # src/cli/ — CLI: install, run, doctor, mcp-oauth -**Generated:** 2026-04-11 +**Generated:** 2026-04-18 ## OVERVIEW diff --git a/src/cli/__snapshots__/model-fallback.test.ts.snap b/src/cli/__snapshots__/model-fallback.test.ts.snap index 3e1b2f50c..dc2bac7a6 100644 --- a/src/cli/__snapshots__/model-fallback.test.ts.snap +++ b/src/cli/__snapshots__/model-fallback.test.ts.snap @@ -69,67 +69,62 @@ exports[`generateModelConfig single native provider uses Claude models when only "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "agents": { "atlas": { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "explore": { - "fallback_models": [ - { - "model": "anthropic/claude-haiku-4.5", - }, - ], "model": "anthropic/claude-haiku-4-5", }, "metis": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "momus": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "multimodal-looker": { "model": "opencode/gpt-5-nano", }, "oracle": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "prometheus": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus-junior": { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, }, "categories": { "deep": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "quick": { - "model": "anthropic/claude-haiku-4.5", + "model": "anthropic/claude-haiku-4-5", }, "ultrabrain": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "unspecified-high": { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "unspecified-low": { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "visual-engineering": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "writing": { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, }, } @@ -140,68 +135,63 @@ exports[`generateModelConfig single native provider uses Claude models with isMa "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "agents": { "atlas": { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "explore": { - "fallback_models": [ - { - "model": "anthropic/claude-haiku-4.5", - }, - ], "model": "anthropic/claude-haiku-4-5", }, "metis": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "momus": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "multimodal-looker": { "model": "opencode/gpt-5-nano", }, "oracle": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "prometheus": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus-junior": { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, }, "categories": { "deep": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "quick": { - "model": "anthropic/claude-haiku-4.5", + "model": "anthropic/claude-haiku-4-5", }, "ultrabrain": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "unspecified-high": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "unspecified-low": { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "visual-engineering": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "writing": { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, }, } @@ -526,14 +516,9 @@ exports[`generateModelConfig all native providers uses preferred models from fal "variant": "medium", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "explore": { - "fallback_models": [ - { - "model": "anthropic/claude-haiku-4.5", - }, - ], "model": "anthropic/claude-haiku-4-5", }, "hephaestus": { @@ -547,13 +532,13 @@ exports[`generateModelConfig all native providers uses preferred models from fal "variant": "high", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "momus": { "fallback_models": [ { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { @@ -580,7 +565,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal "variant": "high", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, ], @@ -597,7 +582,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal "model": "google/gemini-3.1-pro-preview", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus": { @@ -607,7 +592,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal "variant": "medium", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus-junior": { @@ -617,14 +602,14 @@ exports[`generateModelConfig all native providers uses preferred models from fal "variant": "medium", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, }, "categories": { "artistry": { "fallback_models": [ { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { @@ -637,7 +622,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal "deep": { "fallback_models": [ { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { @@ -651,7 +636,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal "quick": { "fallback_models": [ { - "model": "anthropic/claude-haiku-4.5", + "model": "anthropic/claude-haiku-4-5", }, { "model": "google/gemini-3-flash-preview", @@ -666,7 +651,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal "variant": "high", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, ], @@ -683,7 +668,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal "model": "google/gemini-3-flash-preview", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "unspecified-low": { "fallback_models": [ @@ -695,12 +680,12 @@ exports[`generateModelConfig all native providers uses preferred models from fal "model": "google/gemini-3-flash-preview", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "visual-engineering": { "fallback_models": [ { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, ], @@ -710,7 +695,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal "writing": { "fallback_models": [ { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, ], "model": "google/gemini-3-flash-preview", @@ -730,14 +715,9 @@ exports[`generateModelConfig all native providers uses preferred models with isM "variant": "medium", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "explore": { - "fallback_models": [ - { - "model": "anthropic/claude-haiku-4.5", - }, - ], "model": "anthropic/claude-haiku-4-5", }, "hephaestus": { @@ -751,13 +731,13 @@ exports[`generateModelConfig all native providers uses preferred models with isM "variant": "high", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "momus": { "fallback_models": [ { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { @@ -784,7 +764,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM "variant": "high", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, ], @@ -801,7 +781,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM "model": "google/gemini-3.1-pro-preview", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus": { @@ -811,7 +791,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM "variant": "medium", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus-junior": { @@ -821,14 +801,14 @@ exports[`generateModelConfig all native providers uses preferred models with isM "variant": "medium", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, }, "categories": { "artistry": { "fallback_models": [ { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { @@ -841,7 +821,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM "deep": { "fallback_models": [ { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { @@ -855,7 +835,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM "quick": { "fallback_models": [ { - "model": "anthropic/claude-haiku-4.5", + "model": "anthropic/claude-haiku-4-5", }, { "model": "google/gemini-3-flash-preview", @@ -870,7 +850,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM "variant": "high", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, ], @@ -884,7 +864,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM "variant": "high", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "unspecified-low": { @@ -897,12 +877,12 @@ exports[`generateModelConfig all native providers uses preferred models with isM "model": "google/gemini-3-flash-preview", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "visual-engineering": { "fallback_models": [ { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, ], @@ -912,7 +892,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM "writing": { "fallback_models": [ { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, ], "model": "google/gemini-3-flash-preview", @@ -956,13 +936,13 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on "variant": "high", }, ], - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, "momus": { "fallback_models": [ { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -989,7 +969,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on "variant": "high", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -1006,7 +986,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on "model": "opencode/gemini-3.1-pro", }, ], - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, "sisyphus": { @@ -1025,7 +1005,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on "model": "opencode/big-pickle", }, ], - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, "sisyphus-junior": { @@ -1045,7 +1025,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on "artistry": { "fallback_models": [ { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -1058,7 +1038,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on "deep": { "fallback_models": [ { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -1090,7 +1070,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on "variant": "high", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -1127,7 +1107,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on "model": "opencode/glm-5", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -1181,13 +1161,13 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is "variant": "high", }, ], - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, "momus": { "fallback_models": [ { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -1214,7 +1194,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is "variant": "high", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -1231,7 +1211,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is "model": "opencode/gemini-3.1-pro", }, ], - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, "sisyphus": { @@ -1250,7 +1230,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is "model": "opencode/big-pickle", }, ], - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, "sisyphus-junior": { @@ -1270,7 +1250,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is "artistry": { "fallback_models": [ { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -1283,7 +1263,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is "deep": { "fallback_models": [ { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -1315,7 +1295,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is "variant": "high", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -1335,7 +1315,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is "model": "opencode/kimi-k2.5", }, ], - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, "unspecified-low": { @@ -1356,7 +1336,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is "model": "opencode/glm-5", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -1407,13 +1387,13 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when "variant": "high", }, ], - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, "momus": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { @@ -1434,7 +1414,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when "variant": "high", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, ], @@ -1451,7 +1431,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when "model": "github-copilot/gemini-3.1-pro-preview", }, ], - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, "sisyphus": { @@ -1461,7 +1441,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when "variant": "medium", }, ], - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, "sisyphus-junior": { @@ -1478,7 +1458,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when "artistry": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { @@ -1491,7 +1471,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when "deep": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { @@ -1516,7 +1496,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when "ultrabrain": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, ], @@ -1542,7 +1522,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when "visual-engineering": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, ], @@ -1593,13 +1573,13 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with "variant": "high", }, ], - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, "momus": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { @@ -1620,7 +1600,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with "variant": "high", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, ], @@ -1637,7 +1617,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with "model": "github-copilot/gemini-3.1-pro-preview", }, ], - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, "sisyphus": { @@ -1647,7 +1627,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with "variant": "medium", }, ], - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, "sisyphus-junior": { @@ -1664,7 +1644,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with "artistry": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { @@ -1677,7 +1657,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with "deep": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { @@ -1702,7 +1682,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with "ultrabrain": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, ], @@ -1716,7 +1696,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with "variant": "high", }, ], - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, "unspecified-low": { @@ -1730,7 +1710,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with "visual-engineering": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, ], @@ -1885,16 +1865,13 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen "variant": "medium", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "explore": { "fallback_models": [ { "model": "opencode/minimax-m2.7", }, - { - "model": "anthropic/claude-haiku-4.5", - }, { "model": "opencode/claude-haiku-4-5", }, @@ -1911,7 +1888,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen "metis": { "fallback_models": [ { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -1919,17 +1896,17 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen "variant": "high", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "momus": { "fallback_models": [ { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -1956,11 +1933,11 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen "variant": "high", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -1970,7 +1947,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen "prometheus": { "fallback_models": [ { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -1981,13 +1958,13 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen "model": "opencode/gemini-3.1-pro", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus": { "fallback_models": [ { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -2004,7 +1981,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen "model": "opencode/big-pickle", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus-junior": { @@ -2020,18 +1997,18 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen "model": "opencode/big-pickle", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, }, "categories": { "artistry": { "fallback_models": [ { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -2044,11 +2021,11 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen "deep": { "fallback_models": [ { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -2062,7 +2039,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen "quick": { "fallback_models": [ { - "model": "anthropic/claude-haiku-4.5", + "model": "anthropic/claude-haiku-4-5", }, { "model": "opencode/claude-haiku-4-5", @@ -2083,11 +2060,11 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen "variant": "high", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -2107,7 +2084,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen "model": "opencode/gemini-3-flash", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "unspecified-low": { "fallback_models": [ @@ -2122,7 +2099,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen "model": "opencode/gemini-3-flash", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "visual-engineering": { "fallback_models": [ @@ -2130,11 +2107,11 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen "model": "opencode/glm-5", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -2144,7 +2121,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen "writing": { "fallback_models": [ { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, { "model": "opencode/claude-sonnet-4-6", @@ -2202,7 +2179,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb "variant": "high", }, ], - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, "momus": { @@ -2212,7 +2189,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb "variant": "xhigh", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { @@ -2246,7 +2223,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb "variant": "high", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, ], @@ -2267,7 +2244,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb "model": "github-copilot/gemini-3.1-pro-preview", }, ], - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, "sisyphus": { @@ -2281,7 +2258,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb "variant": "medium", }, ], - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, "sisyphus-junior": { @@ -2302,7 +2279,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb "artistry": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { @@ -2322,7 +2299,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb "variant": "medium", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { @@ -2354,7 +2331,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb "variant": "high", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, ], @@ -2388,7 +2365,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb "visual-engineering": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, ], @@ -2412,41 +2389,36 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + ZAI combinat "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "agents": { "atlas": { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "explore": { - "fallback_models": [ - { - "model": "anthropic/claude-haiku-4.5", - }, - ], "model": "anthropic/claude-haiku-4-5", }, "librarian": { "fallback_models": [ { - "model": "anthropic/claude-haiku-4.5", + "model": "anthropic/claude-haiku-4-5", }, ], "model": "zai-coding-plan/glm-4.7", }, "metis": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "momus": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "multimodal-looker": { "model": "zai-coding-plan/glm-4.6v", }, "oracle": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "prometheus": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus": { @@ -2455,42 +2427,42 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + ZAI combinat "model": "zai-coding-plan/glm-5", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus-junior": { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, }, "categories": { "deep": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "quick": { - "model": "anthropic/claude-haiku-4.5", + "model": "anthropic/claude-haiku-4-5", }, "ultrabrain": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "unspecified-high": { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "unspecified-low": { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "visual-engineering": { "fallback_models": [ { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, ], "model": "zai-coding-plan/glm-5", }, "writing": { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, }, } @@ -2501,18 +2473,13 @@ exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combi "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", "agents": { "atlas": { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "explore": { - "fallback_models": [ - { - "model": "anthropic/claude-haiku-4.5", - }, - ], "model": "anthropic/claude-haiku-4-5", }, "metis": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "momus": { @@ -2522,7 +2489,7 @@ exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combi "variant": "high", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "multimodal-looker": { @@ -2531,7 +2498,7 @@ exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combi "oracle": { "fallback_models": [ { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, ], @@ -2544,22 +2511,22 @@ exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combi "model": "google/gemini-3.1-pro-preview", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus": { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus-junior": { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, }, "categories": { "artistry": { "fallback_models": [ { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, ], @@ -2573,7 +2540,7 @@ exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combi "variant": "high", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "quick": { @@ -2582,12 +2549,12 @@ exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combi "model": "google/gemini-3-flash-preview", }, ], - "model": "anthropic/claude-haiku-4.5", + "model": "anthropic/claude-haiku-4-5", }, "ultrabrain": { "fallback_models": [ { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, ], @@ -2600,7 +2567,7 @@ exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combi "model": "google/gemini-3-flash-preview", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "unspecified-low": { "fallback_models": [ @@ -2608,12 +2575,12 @@ exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combi "model": "google/gemini-3-flash-preview", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "visual-engineering": { "fallback_models": [ { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, ], @@ -2623,7 +2590,7 @@ exports[`generateModelConfig mixed provider scenarios uses Gemini + Claude combi "writing": { "fallback_models": [ { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, ], "model": "google/gemini-3-flash-preview", @@ -2693,7 +2660,7 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider "metis": { "fallback_models": [ { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -2705,7 +2672,7 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider "variant": "high", }, ], - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, "momus": { @@ -2715,11 +2682,11 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider "variant": "xhigh", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -2764,11 +2731,11 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider "variant": "high", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -2778,7 +2745,7 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider "prometheus": { "fallback_models": [ { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -2796,13 +2763,13 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider "model": "opencode/gemini-3.1-pro", }, ], - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, "sisyphus": { "fallback_models": [ { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -2826,7 +2793,7 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider "model": "opencode/big-pickle", }, ], - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, "sisyphus-junior": { @@ -2857,11 +2824,11 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider "variant": "high", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -2881,11 +2848,11 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider "variant": "medium", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -2934,11 +2901,11 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider "variant": "high", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -2994,11 +2961,11 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider "model": "opencode/glm-5", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -3048,7 +3015,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "variant": "medium", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "explore": { "fallback_models": [ @@ -3058,9 +3025,6 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe { "model": "opencode/minimax-m2.7", }, - { - "model": "anthropic/claude-haiku-4.5", - }, { "model": "opencode/claude-haiku-4-5", }, @@ -3090,7 +3054,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "model": "opencode/minimax-m2.7-highspeed", }, { - "model": "anthropic/claude-haiku-4.5", + "model": "anthropic/claude-haiku-4-5", }, { "model": "opencode/claude-haiku-4-5", @@ -3104,11 +3068,11 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "metis": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -3124,7 +3088,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "variant": "high", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "momus": { @@ -3138,15 +3102,15 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "variant": "xhigh", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -3210,15 +3174,15 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "variant": "high", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -3228,11 +3192,11 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "prometheus": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -3257,17 +3221,17 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "model": "opencode/gemini-3.1-pro", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -3295,7 +3259,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "model": "opencode/big-pickle", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus-junior": { @@ -3322,7 +3286,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "model": "opencode/big-pickle", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, }, "categories": { @@ -3337,15 +3301,15 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "variant": "high", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -3372,15 +3336,15 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "variant": "medium", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -3408,7 +3372,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "model": "opencode/gpt-5.4-mini", }, { - "model": "anthropic/claude-haiku-4.5", + "model": "anthropic/claude-haiku-4-5", }, { "model": "github-copilot/claude-haiku-4.5", @@ -3450,15 +3414,15 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "variant": "high", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -3491,7 +3455,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "model": "opencode/gemini-3-flash", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "unspecified-low": { "fallback_models": [ @@ -3519,7 +3483,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "model": "opencode/gemini-3-flash", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "visual-engineering": { "fallback_models": [ @@ -3538,15 +3502,15 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "model": "opencode/glm-5", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -3562,7 +3526,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe "model": "opencode/gemini-3-flash", }, { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, { "model": "github-copilot/claude-sonnet-4.6", @@ -3602,7 +3566,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "variant": "medium", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "explore": { "fallback_models": [ @@ -3612,9 +3576,6 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is { "model": "opencode/minimax-m2.7", }, - { - "model": "anthropic/claude-haiku-4.5", - }, { "model": "opencode/claude-haiku-4-5", }, @@ -3644,7 +3605,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "model": "opencode/minimax-m2.7-highspeed", }, { - "model": "anthropic/claude-haiku-4.5", + "model": "anthropic/claude-haiku-4-5", }, { "model": "opencode/claude-haiku-4-5", @@ -3658,11 +3619,11 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "metis": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -3678,7 +3639,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "variant": "high", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "momus": { @@ -3692,15 +3653,15 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "variant": "xhigh", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -3764,15 +3725,15 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "variant": "high", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -3782,11 +3743,11 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "prometheus": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -3811,17 +3772,17 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "model": "opencode/gemini-3.1-pro", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -3849,7 +3810,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "model": "opencode/big-pickle", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "sisyphus-junior": { @@ -3876,7 +3837,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "model": "opencode/big-pickle", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, }, "categories": { @@ -3891,15 +3852,15 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "variant": "high", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -3926,15 +3887,15 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "variant": "medium", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -3962,7 +3923,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "model": "opencode/gpt-5.4-mini", }, { - "model": "anthropic/claude-haiku-4.5", + "model": "anthropic/claude-haiku-4-5", }, { "model": "github-copilot/claude-haiku-4.5", @@ -4004,15 +3965,15 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "variant": "high", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -4022,11 +3983,11 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "unspecified-high": { "fallback_models": [ { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, { @@ -4051,7 +4012,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "model": "opencode/kimi-k2.5", }, ], - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, "unspecified-low": { @@ -4080,7 +4041,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "model": "opencode/gemini-3-flash", }, ], - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, "visual-engineering": { "fallback_models": [ @@ -4099,15 +4060,15 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "model": "opencode/glm-5", }, { - "model": "anthropic/claude-opus-4.6", + "model": "anthropic/claude-opus-4-7", "variant": "max", }, { - "model": "github-copilot/claude-opus-4.6", + "model": "github-copilot/claude-opus-4.7", "variant": "max", }, { - "model": "opencode/claude-opus-4-6", + "model": "opencode/claude-opus-4-7", "variant": "max", }, ], @@ -4123,7 +4084,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is "model": "opencode/gemini-3-flash", }, { - "model": "anthropic/claude-sonnet-4.6", + "model": "anthropic/claude-sonnet-4-6", }, { "model": "github-copilot/claude-sonnet-4.6", @@ -4137,3 +4098,587 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is }, } `; + +exports[`generateModelConfig Vercel AI Gateway provider uses vercel/ model strings when only Vercel AI Gateway is available 1`] = ` +{ + "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", + "agents": { + "atlas": { + "fallback_models": [ + { + "model": "vercel/moonshotai/kimi-k2.5", + }, + { + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + { + "model": "vercel/minimax/minimax-m2.7", + }, + ], + "model": "vercel/anthropic/claude-sonnet-4.6", + }, + "explore": { + "fallback_models": [ + { + "model": "vercel/xai/grok-code-fast-1", + }, + { + "model": "vercel/minimax/minimax-m2.7", + }, + { + "model": "vercel/anthropic/claude-haiku-4.5", + }, + { + "model": "vercel/openai/gpt-5-nano", + }, + ], + "model": "vercel/minimax/minimax-m2.7-highspeed", + }, + "hephaestus": { + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + "librarian": { + "fallback_models": [ + { + "model": "vercel/minimax/minimax-m2.7-highspeed", + }, + { + "model": "vercel/anthropic/claude-haiku-4.5", + }, + { + "model": "vercel/openai/gpt-5-nano", + }, + ], + "model": "vercel/minimax/minimax-m2.7", + }, + "metis": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.4", + "variant": "high", + }, + { + "model": "vercel/zai/glm-5", + }, + ], + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + "momus": { + "fallback_models": [ + { + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + { + "model": "vercel/google/gemini-3.1-pro-preview", + "variant": "high", + }, + { + "model": "vercel/zai/glm-5", + }, + ], + "model": "vercel/openai/gpt-5.4", + "variant": "xhigh", + }, + "multimodal-looker": { + "fallback_models": [ + { + "model": "vercel/moonshotai/kimi-k2.5", + }, + { + "model": "vercel/zai/glm-4.6v", + }, + { + "model": "vercel/openai/gpt-5-nano", + }, + ], + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + "oracle": { + "fallback_models": [ + { + "model": "vercel/google/gemini-3.1-pro-preview", + "variant": "high", + }, + { + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + { + "model": "vercel/zai/glm-5", + }, + ], + "model": "vercel/openai/gpt-5.4", + "variant": "high", + }, + "prometheus": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.4", + "variant": "high", + }, + { + "model": "vercel/zai/glm-5", + }, + { + "model": "vercel/google/gemini-3.1-pro-preview", + }, + ], + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + "sisyphus": { + "fallback_models": [ + { + "model": "vercel/moonshotai/kimi-k2.5", + }, + { + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + { + "model": "vercel/zai/glm-5", + }, + ], + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + "sisyphus-junior": { + "fallback_models": [ + { + "model": "vercel/moonshotai/kimi-k2.5", + }, + { + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + { + "model": "vercel/minimax/minimax-m2.7", + }, + ], + "model": "vercel/anthropic/claude-sonnet-4.6", + }, + }, + "categories": { + "artistry": { + "fallback_models": [ + { + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + { + "model": "vercel/openai/gpt-5.4", + }, + ], + "model": "vercel/google/gemini-3.1-pro-preview", + "variant": "high", + }, + "deep": { + "fallback_models": [ + { + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + { + "model": "vercel/google/gemini-3.1-pro-preview", + "variant": "high", + }, + ], + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + "quick": { + "fallback_models": [ + { + "model": "vercel/anthropic/claude-haiku-4.5", + }, + { + "model": "vercel/google/gemini-3-flash", + }, + { + "model": "vercel/minimax/minimax-m2.7", + }, + { + "model": "vercel/openai/gpt-5-nano", + }, + ], + "model": "vercel/openai/gpt-5.4-mini", + }, + "ultrabrain": { + "fallback_models": [ + { + "model": "vercel/google/gemini-3.1-pro-preview", + "variant": "high", + }, + { + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + { + "model": "vercel/zai/glm-5", + }, + ], + "model": "vercel/openai/gpt-5.4", + "variant": "xhigh", + }, + "unspecified-high": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.3-codex", + "variant": "medium", + }, + { + "model": "vercel/moonshotai/kimi-k2.5", + }, + { + "model": "vercel/google/gemini-3-flash", + }, + { + "model": "vercel/minimax/minimax-m2.7", + }, + ], + "model": "vercel/anthropic/claude-sonnet-4.6", + }, + "unspecified-low": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.3-codex", + "variant": "medium", + }, + { + "model": "vercel/moonshotai/kimi-k2.5", + }, + { + "model": "vercel/google/gemini-3-flash", + }, + { + "model": "vercel/minimax/minimax-m2.7", + }, + ], + "model": "vercel/anthropic/claude-sonnet-4.6", + }, + "visual-engineering": { + "fallback_models": [ + { + "model": "vercel/zai/glm-5", + }, + { + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + ], + "model": "vercel/google/gemini-3.1-pro-preview", + "variant": "high", + }, + "writing": { + "fallback_models": [ + { + "model": "vercel/moonshotai/kimi-k2.5", + }, + { + "model": "vercel/anthropic/claude-sonnet-4.6", + }, + { + "model": "vercel/minimax/minimax-m2.7", + }, + ], + "model": "vercel/google/gemini-3-flash", + }, + }, +} +`; + +exports[`generateModelConfig Vercel AI Gateway provider uses vercel/ model strings with isMax20 flag 1`] = ` +{ + "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json", + "agents": { + "atlas": { + "fallback_models": [ + { + "model": "vercel/moonshotai/kimi-k2.5", + }, + { + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + { + "model": "vercel/minimax/minimax-m2.7", + }, + ], + "model": "vercel/anthropic/claude-sonnet-4.6", + }, + "explore": { + "fallback_models": [ + { + "model": "vercel/xai/grok-code-fast-1", + }, + { + "model": "vercel/minimax/minimax-m2.7", + }, + { + "model": "vercel/anthropic/claude-haiku-4.5", + }, + { + "model": "vercel/openai/gpt-5-nano", + }, + ], + "model": "vercel/minimax/minimax-m2.7-highspeed", + }, + "hephaestus": { + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + "librarian": { + "fallback_models": [ + { + "model": "vercel/minimax/minimax-m2.7-highspeed", + }, + { + "model": "vercel/anthropic/claude-haiku-4.5", + }, + { + "model": "vercel/openai/gpt-5-nano", + }, + ], + "model": "vercel/minimax/minimax-m2.7", + }, + "metis": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.4", + "variant": "high", + }, + { + "model": "vercel/zai/glm-5", + }, + ], + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + "momus": { + "fallback_models": [ + { + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + { + "model": "vercel/google/gemini-3.1-pro-preview", + "variant": "high", + }, + { + "model": "vercel/zai/glm-5", + }, + ], + "model": "vercel/openai/gpt-5.4", + "variant": "xhigh", + }, + "multimodal-looker": { + "fallback_models": [ + { + "model": "vercel/moonshotai/kimi-k2.5", + }, + { + "model": "vercel/zai/glm-4.6v", + }, + { + "model": "vercel/openai/gpt-5-nano", + }, + ], + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + "oracle": { + "fallback_models": [ + { + "model": "vercel/google/gemini-3.1-pro-preview", + "variant": "high", + }, + { + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + { + "model": "vercel/zai/glm-5", + }, + ], + "model": "vercel/openai/gpt-5.4", + "variant": "high", + }, + "prometheus": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.4", + "variant": "high", + }, + { + "model": "vercel/zai/glm-5", + }, + { + "model": "vercel/google/gemini-3.1-pro-preview", + }, + ], + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + "sisyphus": { + "fallback_models": [ + { + "model": "vercel/moonshotai/kimi-k2.5", + }, + { + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + { + "model": "vercel/zai/glm-5", + }, + ], + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + "sisyphus-junior": { + "fallback_models": [ + { + "model": "vercel/moonshotai/kimi-k2.5", + }, + { + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + { + "model": "vercel/minimax/minimax-m2.7", + }, + ], + "model": "vercel/anthropic/claude-sonnet-4.6", + }, + }, + "categories": { + "artistry": { + "fallback_models": [ + { + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + { + "model": "vercel/openai/gpt-5.4", + }, + ], + "model": "vercel/google/gemini-3.1-pro-preview", + "variant": "high", + }, + "deep": { + "fallback_models": [ + { + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + { + "model": "vercel/google/gemini-3.1-pro-preview", + "variant": "high", + }, + ], + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + "quick": { + "fallback_models": [ + { + "model": "vercel/anthropic/claude-haiku-4.5", + }, + { + "model": "vercel/google/gemini-3-flash", + }, + { + "model": "vercel/minimax/minimax-m2.7", + }, + { + "model": "vercel/openai/gpt-5-nano", + }, + ], + "model": "vercel/openai/gpt-5.4-mini", + }, + "ultrabrain": { + "fallback_models": [ + { + "model": "vercel/google/gemini-3.1-pro-preview", + "variant": "high", + }, + { + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + { + "model": "vercel/zai/glm-5", + }, + ], + "model": "vercel/openai/gpt-5.4", + "variant": "xhigh", + }, + "unspecified-high": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.4", + "variant": "high", + }, + { + "model": "vercel/zai/glm-5", + }, + { + "model": "vercel/moonshotai/kimi-k2.5", + }, + ], + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + "unspecified-low": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.3-codex", + "variant": "medium", + }, + { + "model": "vercel/moonshotai/kimi-k2.5", + }, + { + "model": "vercel/google/gemini-3-flash", + }, + { + "model": "vercel/minimax/minimax-m2.7", + }, + ], + "model": "vercel/anthropic/claude-sonnet-4.6", + }, + "visual-engineering": { + "fallback_models": [ + { + "model": "vercel/zai/glm-5", + }, + { + "model": "vercel/anthropic/claude-opus-4.7", + "variant": "max", + }, + ], + "model": "vercel/google/gemini-3.1-pro-preview", + "variant": "high", + }, + "writing": { + "fallback_models": [ + { + "model": "vercel/moonshotai/kimi-k2.5", + }, + { + "model": "vercel/anthropic/claude-sonnet-4.6", + }, + { + "model": "vercel/minimax/minimax-m2.7", + }, + ], + "model": "vercel/google/gemini-3-flash", + }, + }, +} +`; diff --git a/src/cli/cli-installer.telemetry.test.ts b/src/cli/cli-installer.telemetry.test.ts index c4bdee652..c1b8eb8ac 100644 --- a/src/cli/cli-installer.telemetry.test.ts +++ b/src/cli/cli-installer.telemetry.test.ts @@ -22,6 +22,7 @@ describe("runCliInstaller telemetry isolation", () => { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, }), spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true), spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.4.0"), diff --git a/src/cli/cli-installer.test.ts b/src/cli/cli-installer.test.ts index 38514fcf0..3fbb21f20 100644 --- a/src/cli/cli-installer.test.ts +++ b/src/cli/cli-installer.test.ts @@ -37,6 +37,7 @@ describe("runCliInstaller", () => { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, }), spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true), spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.3.9"), @@ -83,6 +84,7 @@ describe("runCliInstaller", () => { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, }), spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true), spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.4.0"), diff --git a/src/cli/cli-installer.ts b/src/cli/cli-installer.ts index 5db015531..72d5d8d7c 100644 --- a/src/cli/cli-installer.ts +++ b/src/cli/cli-installer.ts @@ -138,7 +138,8 @@ export async function runCliInstaller(args: InstallArgs, version: string): Promi !config.hasOpenAI && !config.hasGemini && !config.hasCopilot && - !config.hasOpencodeZen + !config.hasOpencodeZen && + !config.hasVercelAiGateway ) { printWarning("No model providers configured. Using opencode/big-pickle as fallback.") } diff --git a/src/cli/cli-program.ts b/src/cli/cli-program.ts index 15d3d0489..4835495a5 100644 --- a/src/cli/cli-program.ts +++ b/src/cli/cli-program.ts @@ -33,6 +33,7 @@ program .option("--zai-coding-plan ", "Z.ai Coding Plan subscription: no, yes (default: no)") .option("--kimi-for-coding ", "Kimi For Coding subscription: no, yes (default: no)") .option("--opencode-go ", "OpenCode Go subscription: no, yes (default: no)") + .option("--vercel-ai-gateway ", "Vercel AI Gateway: no, yes (default: no)") .option("--skip-auth", "Skip authentication setup hints") .addHelpText("after", ` Examples: @@ -40,14 +41,15 @@ Examples: $ bunx oh-my-opencode install --no-tui --claude=max20 --openai=yes --gemini=yes --copilot=no $ bunx oh-my-opencode install --no-tui --claude=no --gemini=no --copilot=yes --opencode-zen=yes -Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai > Kimi): +Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai > Kimi > Vercel): Claude Native anthropic/ models (Opus, Sonnet, Haiku) OpenAI Native openai/ models (GPT-5.4 for Oracle) Gemini Native google/ models (Gemini 3.1 Pro, Flash) Copilot github-copilot/ models (fallback) - OpenCode Zen opencode/ models (opencode/claude-opus-4-6, etc.) - Z.ai zai-coding-plan/glm-5 (visual-engineering fallback) + OpenCode Zen opencode/ models (opencode/claude-opus-4-7, etc.) + Z.ai zai-coding-plan/glm-5 (visual-engineering fallback) Kimi kimi-for-coding/k2p5 (Sisyphus/Prometheus fallback) + Vercel vercel/ models (universal proxy, always last fallback) `) .action(async (options) => { const args: InstallArgs = { @@ -60,6 +62,7 @@ Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai > Kimi): zaiCodingPlan: options.zaiCodingPlan, kimiForCoding: options.kimiForCoding, opencodeGo: options.opencodeGo, + vercelAiGateway: options.vercelAiGateway, skipAuth: options.skipAuth ?? false, } const exitCode = await install(args) diff --git a/src/cli/config-manager/detect-current-config.ts b/src/cli/config-manager/detect-current-config.ts index f158e18e2..e1fb39823 100644 --- a/src/cli/config-manager/detect-current-config.ts +++ b/src/cli/config-manager/detect-current-config.ts @@ -12,6 +12,7 @@ function detectProvidersFromOmoConfig(): { hasZaiCodingPlan: boolean hasKimiForCoding: boolean hasOpencodeGo: boolean + hasVercelAiGateway: boolean } { const omoConfigPath = getOmoConfigPath() if (!existsSync(omoConfigPath)) { @@ -21,6 +22,7 @@ function detectProvidersFromOmoConfig(): { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, } } @@ -34,6 +36,7 @@ function detectProvidersFromOmoConfig(): { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, } } @@ -43,8 +46,9 @@ function detectProvidersFromOmoConfig(): { const hasZaiCodingPlan = configStr.includes('"zai-coding-plan/') const hasKimiForCoding = configStr.includes('"kimi-for-coding/') const hasOpencodeGo = configStr.includes('"opencode-go/') + const hasVercelAiGateway = configStr.includes('"vercel/') - return { hasOpenAI, hasOpencodeZen, hasZaiCodingPlan, hasKimiForCoding, hasOpencodeGo } + return { hasOpenAI, hasOpencodeZen, hasZaiCodingPlan, hasKimiForCoding, hasOpencodeGo, hasVercelAiGateway } } catch { return { hasOpenAI: true, @@ -52,6 +56,7 @@ function detectProvidersFromOmoConfig(): { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, } } } @@ -78,6 +83,7 @@ export function detectCurrentConfig(): DetectedConfig { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, } const { format, path } = detectConfigFormat() @@ -106,12 +112,13 @@ export function detectCurrentConfig(): DetectedConfig { const providers = openCodeConfig.provider as Record | undefined result.hasGemini = providers ? "google" in providers : false - const { hasOpenAI, hasOpencodeZen, hasZaiCodingPlan, hasKimiForCoding, hasOpencodeGo } = detectProvidersFromOmoConfig() + const { hasOpenAI, hasOpencodeZen, hasZaiCodingPlan, hasKimiForCoding, hasOpencodeGo, hasVercelAiGateway } = detectProvidersFromOmoConfig() result.hasOpenAI = hasOpenAI result.hasOpencodeZen = hasOpencodeZen result.hasZaiCodingPlan = hasZaiCodingPlan result.hasKimiForCoding = hasKimiForCoding result.hasOpencodeGo = hasOpencodeGo + result.hasVercelAiGateway = hasVercelAiGateway return result } diff --git a/src/cli/config-manager/generate-omo-config.test.ts b/src/cli/config-manager/generate-omo-config.test.ts index dbec67c00..8b4a1dde1 100644 --- a/src/cli/config-manager/generate-omo-config.test.ts +++ b/src/cli/config-manager/generate-omo-config.test.ts @@ -18,6 +18,7 @@ describe("generateOmoConfig - model fallback system", () => { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, } //#when @@ -25,8 +26,8 @@ describe("generateOmoConfig - model fallback system", () => { //#then expect([ - "github-copilot/claude-opus-4.6", - "github-copilot/claude-opus-4-6", + "github-copilot/claude-opus-4.7", + "github-copilot/claude-opus-4-7", ]).toContain((result.agents as Record).sisyphus.model) }) @@ -42,6 +43,7 @@ describe("generateOmoConfig - model fallback system", () => { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, } //#when @@ -64,6 +66,7 @@ describe("generateOmoConfig - model fallback system", () => { hasZaiCodingPlan: true, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, } //#when @@ -71,7 +74,7 @@ describe("generateOmoConfig - model fallback system", () => { //#then expect((result.agents as Record).librarian.model).toBe("zai-coding-plan/glm-4.7") - expect((result.agents as Record).sisyphus.model).toBe("anthropic/claude-opus-4-6") + expect((result.agents as Record).sisyphus.model).toBe("anthropic/claude-opus-4-7") }) test("uses native OpenAI models when only ChatGPT available", () => { @@ -86,6 +89,7 @@ describe("generateOmoConfig - model fallback system", () => { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, } //#when @@ -110,6 +114,7 @@ describe("generateOmoConfig - model fallback system", () => { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, } //#when @@ -126,7 +131,7 @@ describe("generateOmoConfig - model fallback system", () => { }> //#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([ { model: "openai/gpt-5.4", @@ -136,7 +141,7 @@ describe("generateOmoConfig - model fallback system", () => { expect(categories.deep.model).toBe("openai/gpt-5.4") expect(categories.deep.fallback_models).toEqual([ { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", variant: "max", }, ]) @@ -154,6 +159,7 @@ describe("generateOmoConfig - model fallback system", () => { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, } //#when @@ -175,6 +181,7 @@ describe("generateOmoConfig - model fallback system", () => { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, } //#when diff --git a/src/cli/config-manager/write-omo-config.test.ts b/src/cli/config-manager/write-omo-config.test.ts index 10ccf7a27..7b7ba6409 100644 --- a/src/cli/config-manager/write-omo-config.test.ts +++ b/src/cli/config-manager/write-omo-config.test.ts @@ -20,6 +20,7 @@ const installConfig: InstallConfig = { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, } function getRecord(value: unknown): Record { diff --git a/src/cli/doctor/AGENTS.md b/src/cli/doctor/AGENTS.md new file mode 100644 index 000000000..5ba601afe --- /dev/null +++ b/src/cli/doctor/AGENTS.md @@ -0,0 +1,82 @@ +# src/cli/doctor/ — Health Diagnostics (25 Check Files) + +**Generated:** 2026-04-18 + +## OVERVIEW + +`bunx oh-my-opencode doctor` — parallel diagnostic checks across 4 categories (System, Config, Tools, Models). Catches broken installs, config typos, missing dependencies, provider misconfigurations before they become runtime errors. + +## COMMAND FLAGS + +```bash +bunx oh-my-opencode doctor # Full diagnostics (all 4 categories) +bunx oh-my-opencode doctor --status # Compact dashboard (status only) +bunx oh-my-opencode doctor --verbose # Deep details (model resolution traces) +bunx oh-my-opencode doctor --json # Machine-readable output +``` + +## CHECK CATEGORIES + +| Category | File | Validates | +|----------|------|-----------| +| **SYSTEM** | `checks/system.ts` | OpenCode binary found + version ≥1.0.150, plugin registered in opencode.json, loaded plugin version matches installed | +| **CONFIG** | `checks/config.ts` | JSONC validity, Zod schema passes, no unknown keys, model override syntax correct | +| **TOOLS** | `checks/tools.ts` | AST-Grep CLI + NAPI, comment-checker binary, LSP servers reachable, GitHub CLI auth, built-in MCP reachability | +| **MODELS** | `checks/model-resolution.ts` | models.json cache exists, per-agent fallback resolution, category overrides valid, provider availability | + +## SUPPORTING CHECK FILES (25 total) + +``` +checks/ +├── index.ts # Registration +├── system.ts # Main System aggregator +├── system-binary.ts # OpenCode binary discovery (PATH + desktop app) +├── system-plugin.ts # opencode.json plugin entry detection +├── system-loaded-version.ts # Cache vs npm latest +├── config.ts # Main Config aggregator +├── tools.ts # Main Tools aggregator +├── dependencies.ts # AST-Grep CLI/NAPI + comment-checker presence +├── tools-gh.ts # gh cli install + auth status +├── tools-lsp.ts # LSP server enumeration +├── tools-mcp.ts # Built-in + user MCP reachability +├── model-resolution.ts # Main Models aggregator +├── model-resolution-cache.ts # models.json presence + freshness +├── model-resolution-config.ts # oh-my-opencode.jsonc parse +├── model-resolution-effective-model.ts # Per-agent fallback chain trace +├── model-resolution-variant.ts # Model variant (max, high, medium) handling +├── model-resolution-details.ts # Verbose output formatter +└── model-resolution-types.ts # Shared types +``` + +## EXECUTION FLOW + +``` +doctor command + → runner.ts: parallel check execution with 30s per-check timeout + → checks/index.ts registers all 4 category checks + → each check returns: { status: "ok" | "warn" | "error", detail: string } + → formatter.ts: render to stdout (text/status/json) + → exit code: 0 (all ok) | 1 (errors) | 2 (warnings only) +``` + +## KEY FILES + +| File | Purpose | +|------|---------| +| `index.ts` | CLI command entry, flag parsing | +| `runner.ts` | Parallel `Promise.allSettled()` orchestration, 30s timeout per check | +| `formatter.ts` | Pretty printing: colored status, hierarchical output | +| `types.ts` | `DoctorCheck`, `CheckResult`, `DoctorReport` types | + +## HOW TO ADD A CHECK + +1. Create `src/cli/doctor/checks/{name}.ts` exporting check function matching `DoctorCheck` +2. Register in `checks/index.ts` +3. Category-level aggregator (system/config/tools/model-resolution) invokes it +4. Return `{ status, detail }` — no throws, all errors caught by runner + +## EXIT CODES + +- `0`: All checks passed (or only info messages) +- `1`: One or more errors — plugin will likely not work +- `2`: Warnings only — plugin works with degraded features diff --git a/src/cli/doctor/checks/model-resolution-cache.test.ts b/src/cli/doctor/checks/model-resolution-cache.test.ts index df6d68f16..58b0ce3d4 100644 --- a/src/cli/doctor/checks/model-resolution-cache.test.ts +++ b/src/cli/doctor/checks/model-resolution-cache.test.ts @@ -34,7 +34,7 @@ describe("loadAvailableModelsFromCache", () => { join(tempDir, "cache", "opencode", "models.json"), JSON.stringify({ 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": {} } }, }) ) diff --git a/src/cli/doctor/checks/model-resolution.test.ts b/src/cli/doctor/checks/model-resolution.test.ts index b64b1fa10..2d1c09919 100644 --- a/src/cli/doctor/checks/model-resolution.test.ts +++ b/src/cli/doctor/checks/model-resolution.test.ts @@ -14,7 +14,7 @@ describe("model-resolution check", () => { // then: Should have agent entries const sisyphus = info.agents.find((a) => a.name === "sisyphus") 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") }) @@ -42,7 +42,7 @@ describe("model-resolution check", () => { // given: User has override for oracle agent const mockConfig = { 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 const oracle = info.agents.find((a) => a.name === "oracle") expect(oracle).toBeDefined() - expect(oracle!.userOverride).toBe("anthropic/claude-opus-4-6") - expect(oracle!.effectiveResolution).toBe("User override: anthropic/claude-opus-4-6") + expect(oracle!.userOverride).toBe("anthropic/claude-opus-4-7") + expect(oracle!.effectiveResolution).toBe("User override: anthropic/claude-opus-4-7") }) it("shows user override for category when configured", async () => { @@ -169,13 +169,13 @@ describe("model-resolution check", () => { const info = getModelResolutionInfoWithOverrides({ 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") 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({ resolutionMode: "alias-backed", canonicalization: { diff --git a/src/cli/install-validators.ts b/src/cli/install-validators.ts index be429eee6..695747edf 100644 --- a/src/cli/install-validators.ts +++ b/src/cli/install-validators.ts @@ -40,6 +40,7 @@ export function formatConfigSummary(config: InstallConfig): string { lines.push(formatProvider("OpenCode Zen", config.hasOpencodeZen, "opencode/ models")) lines.push(formatProvider("Z.ai Coding Plan", config.hasZaiCodingPlan, "Librarian/Multimodal")) lines.push(formatProvider("Kimi For Coding", config.hasKimiForCoding, "Sisyphus/Prometheus fallback")) + lines.push(formatProvider("Vercel AI Gateway", config.hasVercelAiGateway, "universal proxy")) lines.push("") lines.push(color.dim("─".repeat(40))) @@ -153,6 +154,10 @@ export function validateNonTuiArgs(args: InstallArgs): { valid: boolean; errors: errors.push(`Invalid --kimi-for-coding value: ${args.kimiForCoding} (expected: no, yes)`) } + if (args.vercelAiGateway !== undefined && !["no", "yes"].includes(args.vercelAiGateway)) { + errors.push(`Invalid --vercel-ai-gateway value: ${args.vercelAiGateway} (expected: no, yes)`) + } + return { valid: errors.length === 0, errors } } @@ -167,6 +172,7 @@ export function argsToConfig(args: InstallArgs): InstallConfig { hasZaiCodingPlan: args.zaiCodingPlan === "yes", hasKimiForCoding: args.kimiForCoding === "yes", hasOpencodeGo: args.opencodeGo === "yes", + hasVercelAiGateway: args.vercelAiGateway === "yes", } } @@ -179,6 +185,7 @@ export function detectedToInitialValues(detected: DetectedConfig): { zaiCodingPlan: BooleanArg kimiForCoding: BooleanArg opencodeGo: BooleanArg + vercelAiGateway: BooleanArg } { let claude: ClaudeSubscription = "no" if (detected.hasClaude) { @@ -194,5 +201,6 @@ kimiForCoding: BooleanArg zaiCodingPlan: detected.hasZaiCodingPlan ? "yes" : "no", kimiForCoding: detected.hasKimiForCoding ? "yes" : "no", opencodeGo: detected.hasOpencodeGo ? "yes" : "no", + vercelAiGateway: detected.hasVercelAiGateway ? "yes" : "no", } } diff --git a/src/cli/model-fallback-types.ts b/src/cli/model-fallback-types.ts index b195f698a..6daa24388 100644 --- a/src/cli/model-fallback-types.ts +++ b/src/cli/model-fallback-types.ts @@ -11,6 +11,7 @@ export interface ProviderAvailability { zai: boolean kimiForCoding: boolean opencodeGo: boolean + vercelAiGateway: boolean isMaxPlan: boolean } diff --git a/src/cli/model-fallback.test.ts b/src/cli/model-fallback.test.ts index 09d002bcb..67fa83fd7 100644 --- a/src/cli/model-fallback.test.ts +++ b/src/cli/model-fallback.test.ts @@ -16,6 +16,7 @@ function createConfig(overrides: Partial = {}): InstallConfig { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, ...overrides, } } @@ -380,7 +381,7 @@ describe("generateModelConfig", () => { const result = generateModelConfig(config) // #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", () => { @@ -397,7 +398,7 @@ describe("generateModelConfig", () => { const result = generateModelConfig(config) // #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", () => { @@ -572,13 +573,9 @@ describe("generateModelConfig", () => { // #when generateModelConfig is called const result = generateModelConfig(config) - // #then explore should not have fallback_models (only one chain entry matches) + // #then explore should not have fallback_models (only one distinct chain entry matches) expect(result.agents?.explore?.model).toBe("anthropic/claude-haiku-4-5") - expect(result.agents?.explore?.fallback_models).toEqual([ - { - model: "anthropic/claude-haiku-4.5", - }, - ]) + expect(result.agents?.explore?.fallback_models).toBeUndefined() }) test("librarian includes fallback_models when opencode-go and Claude are both available", () => { @@ -607,6 +604,74 @@ describe("generateModelConfig", () => { }) }) + describe("Vercel AI Gateway provider", () => { + test("uses vercel/ model strings when only Vercel AI Gateway is available", () => { + // #given only Vercel AI Gateway is available + const config = createConfig({ hasVercelAiGateway: true }) + + // #when generateModelConfig is called + const result = generateModelConfig(config) + + // #then should use vercel// format + expect(result).toMatchSnapshot() + }) + + test("uses vercel/ model strings with isMax20 flag", () => { + // #given Vercel AI Gateway is available with Max 20 plan + const config = createConfig({ hasVercelAiGateway: true, isMax20: true }) + + // #when generateModelConfig is called + const result = generateModelConfig(config) + + // #then should use higher capability models via gateway + expect(result).toMatchSnapshot() + }) + + test("explore uses vercel/minimax/minimax-m2.7-highspeed when only gateway available", () => { + // #given only Vercel AI Gateway is available + const config = createConfig({ hasVercelAiGateway: true }) + + // #when generateModelConfig is called + const result = generateModelConfig(config) + + // #then explore should use gateway-routed minimax (preferred over claude-haiku) + expect(result.agents?.explore?.model).toBe("vercel/minimax/minimax-m2.7-highspeed") + }) + + test("librarian uses vercel/minimax/minimax-m2.7 when only gateway available", () => { + // #given only Vercel AI Gateway is available + const config = createConfig({ hasVercelAiGateway: true }) + + // #when generateModelConfig is called + const result = generateModelConfig(config) + + // #then librarian should use gateway-routed minimax (preferred over claude-haiku) + expect(result.agents?.librarian?.model).toBe("vercel/minimax/minimax-m2.7") + }) + + test("Hephaestus is created when only Vercel AI Gateway is available", () => { + // #given only Vercel AI Gateway is available + const config = createConfig({ hasVercelAiGateway: true }) + + // #when generateModelConfig is called + const result = generateModelConfig(config) + + // #then hephaestus should be created with gateway-routed gpt-5.4 + expect(result.agents?.hephaestus?.model).toBe("vercel/openai/gpt-5.4") + }) + + test("native providers take priority over gateway", () => { + // #given Claude and Vercel AI Gateway are both available + const config = createConfig({ hasClaude: true, hasVercelAiGateway: true }) + + // #when generateModelConfig is called + const result = generateModelConfig(config) + + // #then should prefer native anthropic over gateway + expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4-7") + }) + }) + describe("schema URL", () => { test("always includes correct schema URL", () => { // #given any config diff --git a/src/cli/model-fallback.ts b/src/cli/model-fallback.ts index 5dabb9fc1..088c4515e 100644 --- a/src/cli/model-fallback.ts +++ b/src/cli/model-fallback.ts @@ -105,7 +105,8 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig { avail.copilot || avail.zai || avail.kimiForCoding || - avail.opencodeGo + avail.opencodeGo || + avail.vercelAiGateway if (!hasAnyProvider) { return { $schema: SCHEMA_URL, @@ -130,6 +131,8 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig { agentConfig = { model: "opencode-go/minimax-m2.7" } } else if (avail.zai) { agentConfig = { model: ZAI_MODEL } + } else if (avail.vercelAiGateway) { + agentConfig = { model: "vercel/minimax/minimax-m2.7" } } if (agentConfig) { agents[role] = attachAllFallbackModels(agentConfig, req.fallbackChain, avail) @@ -147,6 +150,8 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig { agentConfig = { model: "opencode-go/minimax-m2.7" } } else if (avail.copilot) { agentConfig = { model: "github-copilot/gpt-5-mini" } + } else if (avail.vercelAiGateway) { + agentConfig = { model: "vercel/minimax/minimax-m2.7-highspeed" } } else { agentConfig = { model: "opencode/gpt-5-nano" } } diff --git a/src/cli/openai-only-model-catalog.test.ts b/src/cli/openai-only-model-catalog.test.ts index 9d516dfdd..da544156c 100644 --- a/src/cli/openai-only-model-catalog.test.ts +++ b/src/cli/openai-only-model-catalog.test.ts @@ -14,6 +14,7 @@ function createConfig(overrides: Partial = {}): InstallConfig { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, ...overrides, } } diff --git a/src/cli/provider-availability.ts b/src/cli/provider-availability.ts index 6d17422f7..f9ac8189b 100644 --- a/src/cli/provider-availability.ts +++ b/src/cli/provider-availability.ts @@ -13,6 +13,7 @@ export function toProviderAvailability(config: InstallConfig): ProviderAvailabil zai: config.hasZaiCodingPlan, kimiForCoding: config.hasKimiForCoding, opencodeGo: config.hasOpencodeGo, + vercelAiGateway: config.hasVercelAiGateway, isMaxPlan: config.isMax20, } } @@ -27,6 +28,7 @@ export function isProviderAvailable(provider: string, availability: ProviderAvai "zai-coding-plan": availability.zai, "kimi-for-coding": availability.kimiForCoding, "opencode-go": availability.opencodeGo, + vercel: availability.vercelAiGateway, } return mapping[provider] ?? false } diff --git a/src/cli/provider-model-id-transform.test.ts b/src/cli/provider-model-id-transform.test.ts index ddfdd6e2f..0a1b57030 100644 --- a/src/cli/provider-model-id-transform.test.ts +++ b/src/cli/provider-model-id-transform.test.ts @@ -1,229 +1,353 @@ import { describe, expect, test } from "bun:test" import { transformModelForProvider } from "./provider-model-id-transform" +import { transformModelForProvider as transformSharedModelForProvider } from "../shared/provider-model-id-transform" describe("transformModelForProvider", () => { - describe("github-copilot provider", () => { - test("transforms claude-opus-4-6 to claude-opus-4.6", () => { - // #given github-copilot provider and claude-opus-4-6 model - const provider = "github-copilot" - const model = "claude-opus-4-6" + describe("github-copilot provider", () => { + test("transforms claude-opus-4-7 to claude-opus-4.7", () => { + // #given github-copilot provider and claude-opus-4-7 model + const provider = "github-copilot" + const model = "claude-opus-4-7" - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) - // #then should transform to claude-opus-4.6 - expect(result).toBe("claude-opus-4.6") - }) + // #then should transform to claude-opus-4.7 + expect(result).toBe("claude-opus-4.7") + }) - test("transforms claude-sonnet-4-5 to claude-sonnet-4.5", () => { - // #given github-copilot provider and claude-sonnet-4-5 model - const provider = "github-copilot" - const model = "claude-sonnet-4-5" + test("transforms claude-sonnet-4-5 to claude-sonnet-4.5", () => { + // #given github-copilot provider and claude-sonnet-4-5 model + const provider = "github-copilot" + const model = "claude-sonnet-4-5" - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) - // #then should transform to claude-sonnet-4.5 - expect(result).toBe("claude-sonnet-4.5") - }) + // #then should transform to claude-sonnet-4.5 + expect(result).toBe("claude-sonnet-4.5") + }) - test("transforms claude-haiku-4-5 to claude-haiku-4.5", () => { - // #given github-copilot provider and claude-haiku-4-5 model - const provider = "github-copilot" - const model = "claude-haiku-4-5" + test("transforms claude-haiku-4-5 to claude-haiku-4.5", () => { + // #given github-copilot provider and claude-haiku-4-5 model + const provider = "github-copilot" + const model = "claude-haiku-4-5" - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) - // #then should transform to claude-haiku-4.5 - expect(result).toBe("claude-haiku-4.5") - }) + // #then should transform to claude-haiku-4.5 + expect(result).toBe("claude-haiku-4.5") + }) - test("transforms gemini-3.1-pro to gemini-3.1-pro-preview", () => { - // #given github-copilot provider and gemini-3.1-pro model - const provider = "github-copilot" - const model = "gemini-3.1-pro" + test("transforms gemini-3.1-pro to gemini-3.1-pro-preview", () => { + // #given github-copilot provider and gemini-3.1-pro model + const provider = "github-copilot" + const model = "gemini-3.1-pro" - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) - // #then should transform to gemini-3.1-pro-preview - expect(result).toBe("gemini-3.1-pro-preview") - }) + // #then should transform to gemini-3.1-pro-preview + expect(result).toBe("gemini-3.1-pro-preview") + }) - test("transforms gemini-3-flash to gemini-3-flash-preview", () => { - // #given github-copilot provider and gemini-3-flash model - const provider = "github-copilot" - const model = "gemini-3-flash" + test("transforms gemini-3-flash to gemini-3-flash-preview", () => { + // #given github-copilot provider and gemini-3-flash model + const provider = "github-copilot" + const model = "gemini-3-flash" - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) - // #then should transform to gemini-3-flash-preview - expect(result).toBe("gemini-3-flash-preview") - }) + // #then should transform to gemini-3-flash-preview + expect(result).toBe("gemini-3-flash-preview") + }) - test("prevents double transformation of gemini-3.1-pro-preview", () => { - // #given github-copilot provider and gemini-3.1-pro-preview model (already transformed) - const provider = "github-copilot" - const model = "gemini-3.1-pro-preview" + test("prevents double transformation of gemini-3.1-pro-preview", () => { + // #given github-copilot provider and gemini-3.1-pro-preview model (already transformed) + const provider = "github-copilot" + const model = "gemini-3.1-pro-preview" - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) - // #then should NOT become gemini-3.1-pro-preview-preview - expect(result).toBe("gemini-3.1-pro-preview") - }) + // #then should NOT become gemini-3.1-pro-preview-preview + expect(result).toBe("gemini-3.1-pro-preview") + }) - test("prevents double transformation of gemini-3-flash-preview", () => { - // #given github-copilot provider and gemini-3-flash-preview model (already transformed) - const provider = "github-copilot" - const model = "gemini-3-flash-preview" + test("prevents double transformation of gemini-3-flash-preview", () => { + // #given github-copilot provider and gemini-3-flash-preview model (already transformed) + const provider = "github-copilot" + const model = "gemini-3-flash-preview" - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) - // #then should NOT become gemini-3-flash-preview-preview - expect(result).toBe("gemini-3-flash-preview") - }) - }) + // #then should NOT become gemini-3-flash-preview-preview + expect(result).toBe("gemini-3-flash-preview") + }) + }) - describe("google provider", () => { - test("transforms gemini-3-flash to gemini-3-flash-preview", () => { - // #given google provider and gemini-3-flash model - const provider = "google" - const model = "gemini-3-flash" + describe("google provider", () => { + test("transforms gemini-3-flash to gemini-3-flash-preview", () => { + // #given google provider and gemini-3-flash model + const provider = "google" + const model = "gemini-3-flash" - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) - // #then should transform to gemini-3-flash-preview - expect(result).toBe("gemini-3-flash-preview") - }) + // #then should transform to gemini-3-flash-preview + expect(result).toBe("gemini-3-flash-preview") + }) - test("transforms gemini-3.1-pro to gemini-3.1-pro-preview", () => { - // #given google provider and gemini-3.1-pro model - const provider = "google" - const model = "gemini-3.1-pro" + test("transforms gemini-3.1-pro to gemini-3.1-pro-preview", () => { + // #given google provider and gemini-3.1-pro model + const provider = "google" + const model = "gemini-3.1-pro" - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) - // #then should transform to gemini-3.1-pro-preview - expect(result).toBe("gemini-3.1-pro-preview") - }) + // #then should transform to gemini-3.1-pro-preview + expect(result).toBe("gemini-3.1-pro-preview") + }) - test("passes through other gemini models unchanged", () => { - // #given google provider and gemini-2.5-flash model - const provider = "google" - const model = "gemini-2.5-flash" + test("passes through other gemini models unchanged", () => { + // #given google provider and gemini-2.5-flash model + const provider = "google" + const model = "gemini-2.5-flash" - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) - // #then should pass through unchanged - expect(result).toBe("gemini-2.5-flash") - }) + // #then should pass through unchanged + expect(result).toBe("gemini-2.5-flash") + }) - test("prevents double transformation of gemini-3-flash-preview", () => { - // #given google provider and gemini-3-flash-preview model (already transformed) - const provider = "google" - const model = "gemini-3-flash-preview" + test("prevents double transformation of gemini-3-flash-preview", () => { + // #given google provider and gemini-3-flash-preview model (already transformed) + const provider = "google" + const model = "gemini-3-flash-preview" - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) - // #then should NOT become gemini-3-flash-preview-preview - expect(result).toBe("gemini-3-flash-preview") - }) + // #then should NOT become gemini-3-flash-preview-preview + expect(result).toBe("gemini-3-flash-preview") + }) - test("prevents double transformation of gemini-3.1-pro-preview", () => { - // #given google provider and gemini-3.1-pro-preview model (already transformed) - const provider = "google" - const model = "gemini-3.1-pro-preview" + test("prevents double transformation of gemini-3.1-pro-preview", () => { + // #given google provider and gemini-3.1-pro-preview model (already transformed) + const provider = "google" + const model = "gemini-3.1-pro-preview" - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) - // #then should NOT become gemini-3.1-pro-preview-preview - expect(result).toBe("gemini-3.1-pro-preview") - }) + // #then should NOT become gemini-3.1-pro-preview-preview + expect(result).toBe("gemini-3.1-pro-preview") + }) - test("does not transform claude models for google provider", () => { - // #given google provider and claude-opus-4-6 model - const provider = "google" - const model = "claude-opus-4-6" + test("does not transform claude models for google provider", () => { + // #given google provider and claude-opus-4-7 model + const provider = "google" + const model = "claude-opus-4-7" - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) - // #then should pass through unchanged (google doesn't use claude) - expect(result).toBe("claude-opus-4-6") - }) - }) + // #then should pass through unchanged (google doesn't use claude) + expect(result).toBe("claude-opus-4-7") + }) + }) - describe("anthropic provider", () => { - test("transforms claude-opus-4-6 to claude-opus-4.6", () => { - // #given anthropic provider and claude-opus-4-6 model - const provider = "anthropic" - const model = "claude-opus-4-6" + describe("anthropic provider", () => { + test("preserves hyphenated claude-opus-4-7 for config output (regression: installer must not write dotted IDs)", () => { + // #given anthropic provider and claude-opus-4-7 model + const provider = "anthropic" + const model = "claude-opus-4-7" - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) - // #then should transform to claude-opus-4.6 - expect(result).toBe("claude-opus-4.6") - }) + // #then should keep hyphenated form so Anthropic provider resolution succeeds on fresh installs + expect(result).toBe("claude-opus-4-7") + }) - test("transforms claude-sonnet-4-6 to claude-sonnet-4.6", () => { - // #given anthropic provider and claude-sonnet-4-6 model - const provider = "anthropic" - const model = "claude-sonnet-4-6" + test("preserves hyphenated claude-sonnet-4-6 for config output", () => { + // #given anthropic provider and claude-sonnet-4-6 model + const provider = "anthropic" + const model = "claude-sonnet-4-6" - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) - // #then should transform to claude-sonnet-4.6 - expect(result).toBe("claude-sonnet-4.6") - }) + // #then should keep hyphenated form + expect(result).toBe("claude-sonnet-4-6") + }) - test("transforms claude-haiku-4-5 to claude-haiku-4.5", () => { - // #given anthropic provider and claude-haiku-4-5 model - const provider = "anthropic" - const model = "claude-haiku-4-5" + test("preserves hyphenated claude-haiku-4-5 for config output", () => { + // #given anthropic provider and claude-haiku-4-5 model + const provider = "anthropic" + const model = "claude-haiku-4-5" - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) - // #then should transform to claude-haiku-4.5 - expect(result).toBe("claude-haiku-4.5") - }) - }) + // #then should keep hyphenated form + expect(result).toBe("claude-haiku-4-5") + }) + }) - describe("unknown provider", () => { - test("passes model through unchanged for unknown provider", () => { - // #given unknown provider and any model - const provider = "unknown-provider" - const model = "some-model" + describe("vercel provider", () => { + test("prepends anthropic/ and applies anthropic transform for claude models", () => { + // #given vercel provider and claude-opus-4-7 model + // #when transformModelForProvider is called + const result = transformModelForProvider("vercel", "claude-opus-4-7") - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + // #then should produce anthropic/claude-opus-4.7 + expect(result).toBe("anthropic/claude-opus-4.7") + }) - // #then should pass through unchanged - expect(result).toBe("some-model") - }) + test("prepends anthropic/ and applies anthropic transform for claude-sonnet", () => { + // #given vercel provider and claude-sonnet-4-6 model + // #when transformModelForProvider is called + const result = transformModelForProvider("vercel", "claude-sonnet-4-6") - test("passes gemini-3-flash through unchanged for unknown provider", () => { - // #given unknown provider and gemini-3-flash model - const provider = "unknown-provider" - const model = "gemini-3-flash" + // #then should produce anthropic/claude-sonnet-4.6 + expect(result).toBe("anthropic/claude-sonnet-4.6") + }) - // #when transformModelForProvider is called - const result = transformModelForProvider(provider, model) + test("prepends anthropic/ and applies anthropic transform for claude-haiku", () => { + // #given vercel provider and claude-haiku-4-5 model + // #when transformModelForProvider is called + const result = transformModelForProvider("vercel", "claude-haiku-4-5") - // #then should pass through unchanged (no transformation for unknown provider) - expect(result).toBe("gemini-3-flash") - }) - }) + // #then should produce anthropic/claude-haiku-4.5 + expect(result).toBe("anthropic/claude-haiku-4.5") + }) + + test("prepends openai/ for gpt models", () => { + // #given vercel provider and gpt-5.4 model + // #when transformModelForProvider is called + const result = transformModelForProvider("vercel", "gpt-5.4") + + // #then should produce openai/gpt-5.4 + expect(result).toBe("openai/gpt-5.4") + }) + + test("prepends google/ and applies google transform for gemini models", () => { + // #given vercel provider and gemini-3.1-pro model + // #when transformModelForProvider is called + const result = transformModelForProvider("vercel", "gemini-3.1-pro") + + // #then should produce google/gemini-3.1-pro-preview + expect(result).toBe("google/gemini-3.1-pro-preview") + }) + + test("prepends google/ without -preview for gemini-3-flash", () => { + // #given vercel provider and gemini-3-flash model + // #when transformModelForProvider is called + const result = transformModelForProvider("vercel", "gemini-3-flash") + + // #then should produce google/gemini-3-flash (gateway does not use -preview for this model) + expect(result).toBe("google/gemini-3-flash") + }) + + test("prepends xai/ for grok models", () => { + // #given vercel provider and grok-code-fast-1 model + // #when transformModelForProvider is called + const result = transformModelForProvider("vercel", "grok-code-fast-1") + + // #then should produce xai/grok-code-fast-1 + expect(result).toBe("xai/grok-code-fast-1") + }) + + test("delegates to sub-provider when model already has sub-provider prefix", () => { + // #given vercel provider and anthropic/claude-opus-4-7 (already prefixed) + // #when transformModelForProvider is called + const result = transformModelForProvider("vercel", "anthropic/claude-opus-4-7") + + // #then should apply anthropic transform within the prefix + expect(result).toBe("anthropic/claude-opus-4.7") + }) + + test("prepends minimax/ for minimax models", () => { + // #given vercel provider and minimax-m2.7 model + // #when transformModelForProvider is called + const result = transformModelForProvider("vercel", "minimax-m2.7") + + // #then should produce minimax/minimax-m2.7 + expect(result).toBe("minimax/minimax-m2.7") + }) + + test("prepends moonshotai/ for kimi models", () => { + // #given vercel provider and kimi-k2.5 model + // #when transformModelForProvider is called + const result = transformModelForProvider("vercel", "kimi-k2.5") + + // #then should produce moonshotai/kimi-k2.5 + expect(result).toBe("moonshotai/kimi-k2.5") + }) + + test("prepends zai/ for glm models", () => { + // #given vercel provider and glm-5 model + // #when transformModelForProvider is called + const result = transformModelForProvider("vercel", "glm-5") + + // #then should produce zai/glm-5 + expect(result).toBe("zai/glm-5") + }) + + test("passes through unknown models without sub-provider prefix", () => { + // #given vercel provider and an unknown model name + // #when transformModelForProvider is called + const result = transformModelForProvider("vercel", "big-pickle") + + // #then should pass through unchanged + expect(result).toBe("big-pickle") + }) + }) + + describe("unknown provider", () => { + test("passes model through unchanged for unknown provider", () => { + // #given unknown provider and any model + const provider = "unknown-provider" + const model = "some-model" + + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) + + // #then should pass through unchanged + expect(result).toBe("some-model") + }) + + test("passes gemini-3-flash through unchanged for unknown provider", () => { + // #given unknown provider and gemini-3-flash model + const provider = "unknown-provider" + const model = "gemini-3-flash" + + // #when transformModelForProvider is called + const result = transformModelForProvider(provider, model) + + // #then should pass through unchanged (no transformation for unknown provider) + expect(result).toBe("gemini-3-flash") + }) + }) + + 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 + const cliResult = transformModelForProvider("anthropic", "claude-opus-4-7") + const sharedResult = transformSharedModelForProvider("anthropic", "claude-opus-4-7") + + // #when both are called with the same anthropic claude input + // #then the CLI preserves hyphenated form for config output, + // the shared runtime transform converts dash→dot for API calls + expect(transformModelForProvider).not.toBe(transformSharedModelForProvider) + expect(cliResult).toBe("claude-opus-4-7") + expect(sharedResult).toBe("claude-opus-4.7") + }) }) diff --git a/src/cli/provider-model-id-transform.ts b/src/cli/provider-model-id-transform.ts index e6cb06231..fda947c8c 100644 --- a/src/cli/provider-model-id-transform.ts +++ b/src/cli/provider-model-id-transform.ts @@ -1 +1,66 @@ -export { transformModelForProvider } from "../shared/provider-model-id-transform" +function inferSubProvider(model: string): string | undefined { + if (model.startsWith("claude-")) return "anthropic" + if (model.startsWith("gpt-")) return "openai" + if (model.startsWith("gemini-")) return "google" + if (model.startsWith("grok-")) return "xai" + if (model.startsWith("minimax-")) return "minimax" + if (model.startsWith("kimi-")) return "moonshotai" + if (model.startsWith("glm-")) return "zai" + return undefined +} + +const CLAUDE_VERSION_DOT = /claude-(\w+)-(\d+)-(\d+)/g +const GEMINI_31_PRO_PREVIEW = /gemini-3\.1-pro(?!-)/g +const GEMINI_3_FLASH_PREVIEW = /gemini-3-flash(?!-)/g + +function claudeVersionDot(model: string): string { + return model.replace(CLAUDE_VERSION_DOT, "claude-$1-$2.$3") +} + +function applyGatewayTransforms(model: string): string { + return claudeVersionDot(model).replace( + GEMINI_31_PRO_PREVIEW, + "gemini-3.1-pro-preview", + ) +} + +export function transformModelForProvider(provider: string, model: string): string { + if (provider === "vercel") { + const slashIndex = model.indexOf("/") + if (slashIndex !== -1) { + const subProvider = model.substring(0, slashIndex) + const subModel = model.substring(slashIndex + 1) + return `${subProvider}/${applyGatewayTransforms(subModel)}` + } + + const subProvider = inferSubProvider(model) + if (subProvider) { + return `${subProvider}/${applyGatewayTransforms(model)}` + } + + return model + } + + if (provider === "github-copilot") { + return claudeVersionDot(model) + .replace(GEMINI_31_PRO_PREVIEW, "gemini-3.1-pro-preview") + .replace(GEMINI_3_FLASH_PREVIEW, "gemini-3-flash-preview") + } + + if (provider === "google") { + return model + .replace(GEMINI_31_PRO_PREVIEW, "gemini-3.1-pro-preview") + .replace(GEMINI_3_FLASH_PREVIEW, "gemini-3-flash-preview") + } + + if (provider === "anthropic") { + // Installer writes hyphenated IDs (claude-opus-4-7) to the config. The + // runtime provider-model-id-transform converts dash→dot when calling the + // Anthropic API. Keeping the dotted form in the config breaks fresh + // installs with ProviderModelNotFoundError because Anthropic's provider + // registers models under hyphenated IDs. + return model + } + + return model +} diff --git a/src/cli/refresh-model-capabilities.test.ts b/src/cli/refresh-model-capabilities.test.ts index 800cf7e54..bfed41969 100644 --- a/src/cli/refresh-model-capabilities.test.ts +++ b/src/cli/refresh-model-capabilities.test.ts @@ -49,7 +49,7 @@ describe("refreshModelCapabilities", () => { sourceUrl: "https://override.example/api.json", models: { "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 = "" diff --git a/src/cli/run/agent-resolver.ts b/src/cli/run/agent-resolver.ts index 9bb820412..b9dd27a64 100644 --- a/src/cli/run/agent-resolver.ts +++ b/src/cli/run/agent-resolver.ts @@ -1,7 +1,7 @@ import pc from "picocolors" import type { RunOptions } from "./types" import type { OhMyOpenCodeConfig } from "../../config" -import { getAgentConfigKey, getAgentDisplayName } from "../../shared/agent-display-names" +import { getAgentConfigKey, getAgentDisplayName, getAgentRuntimeName } from "../../shared/agent-display-names" const CORE_AGENT_ORDER = ["sisyphus", "hephaestus", "prometheus", "atlas"] as const const DEFAULT_AGENT = "sisyphus" @@ -21,11 +21,12 @@ const normalizeAgentName = (agent?: string): ResolvedAgent | undefined => { const configKey = getAgentConfigKey(trimmed) const displayName = getAgentDisplayName(configKey) + const runtimeName = getAgentRuntimeName(configKey) const isKnownAgent = displayName !== configKey return { configKey, - resolvedName: isKnownAgent ? displayName : trimmed, + resolvedName: isKnownAgent ? runtimeName : trimmed, } } @@ -61,27 +62,28 @@ export const resolveRunAgent = ( envAgent ?? configAgent ?? { configKey: DEFAULT_AGENT, - resolvedName: getAgentDisplayName(DEFAULT_AGENT), + resolvedName: getAgentRuntimeName(DEFAULT_AGENT), } if (isAgentDisabled(resolved.configKey, pluginConfig)) { const fallback = pickFallbackAgent(pluginConfig) - const fallbackName = getAgentDisplayName(fallback) + const fallbackDisplayName = getAgentDisplayName(fallback) + const fallbackRuntimeName = getAgentRuntimeName(fallback) const fallbackDisabled = isAgentDisabled(fallback, pluginConfig) if (fallbackDisabled) { console.log( pc.yellow( - `Requested agent "${resolved.resolvedName}" is disabled and no enabled core agent was found. Proceeding with "${fallbackName}".` + `Requested agent "${resolved.resolvedName}" is disabled and no enabled core agent was found. Proceeding with "${fallbackDisplayName}".` ) ) - return fallbackName + return fallbackRuntimeName } console.log( pc.yellow( - `Requested agent "${resolved.resolvedName}" is disabled. Falling back to "${fallbackName}".` + `Requested agent "${resolved.resolvedName}" is disabled. Falling back to "${fallbackDisplayName}".` ) ) - return fallbackName + return fallbackRuntimeName } return resolved.resolvedName diff --git a/src/cli/run/message-part-delta.test.ts b/src/cli/run/message-part-delta.test.ts index 6d7fefa6e..09f0c2d84 100644 --- a/src/cli/run/message-part-delta.test.ts +++ b/src/cli/run/message-part-delta.test.ts @@ -98,7 +98,7 @@ describe("message.part.delta handling", () => { sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", variant: "max", }, }, @@ -113,7 +113,7 @@ describe("message.part.delta handling", () => { //#then const rendered = stdoutSpy.mock.calls.map((call) => String(call[0] ?? "")).join("") 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("Sisyphus - Ultraworker") stdoutSpy.mockRestore() @@ -128,7 +128,7 @@ describe("message.part.delta handling", () => { { type: "message.updated", 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", 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", 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", 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", 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", 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", 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", 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", 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" }, }, }, { diff --git a/src/cli/run/poll-for-completion.test.ts b/src/cli/run/poll-for-completion.test.ts index dea0a9ff3..670c6ba05 100644 --- a/src/cli/run/poll-for-completion.test.ts +++ b/src/cli/run/poll-for-completion.test.ts @@ -235,6 +235,27 @@ describe("pollForCompletion", () => { expect(result).toBe(0) }) + it("treats missing main session status as idle when status API omits idle sessions", async () => { + //#given - latest opencode omits idle sessions from the status map + const ctx = createMockContext({ + statuses: {}, + }) + const eventState = createEventState() + eventState.mainSessionIdle = false + eventState.hasReceivedMeaningfulWork = true + const abortController = new AbortController() + + //#when + const result = await pollForCompletion(ctx, eventState, abortController, { + pollIntervalMs: 10, + requiredConsecutive: 2, + minStabilizationMs: 10, + }) + + //#then - missing entry is treated as idle instead of hanging forever + expect(result).toBe(0) + }) + it("allows silent completion after stabilization when no meaningful work is received", async () => { //#given - session is idle and stable but no assistant message/tool event arrived const ctx = createMockContext() diff --git a/src/cli/run/poll-for-completion.ts b/src/cli/run/poll-for-completion.ts index 51c1dfc13..f393b5b9d 100644 --- a/src/cli/run/poll-for-completion.ts +++ b/src/cli/run/poll-for-completion.ts @@ -193,6 +193,9 @@ async function getMainSessionStatus( statusesRes, {} as Record ) + if (!(ctx.sessionID in statuses)) { + return "idle" + } const status = statuses[ctx.sessionID]?.type if (status === "idle" || status === "busy" || status === "retry") { return status diff --git a/src/cli/run/runner.test.ts b/src/cli/run/runner.test.ts index e0eb7ec3d..12a52bf15 100644 --- a/src/cli/run/runner.test.ts +++ b/src/cli/run/runner.test.ts @@ -3,6 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test" import { OhMyOpenCodeConfigSchema, type OhMyOpenCodeConfig } from "../../config" import { resolveRunAgent } from "./agent-resolver" +import { getAgentRuntimeName } from "../../shared/agent-display-names" const createConfig = (overrides: Partial = {}): OhMyOpenCodeConfig => OhMyOpenCodeConfigSchema.parse(overrides) @@ -31,7 +32,7 @@ describe("resolveRunAgent", () => { ) // then - expect(agent).toBe("Hephaestus - Deep Agent") + expect(agent).toBe(getAgentRuntimeName("hephaestus")) }) it("uses env agent over config", () => { @@ -43,7 +44,7 @@ describe("resolveRunAgent", () => { const agent = resolveRunAgent({ message: "test" }, config, env) // then - expect(agent).toBe("Atlas - Plan Executor") + expect(agent).toBe(getAgentRuntimeName("atlas")) }) it("uses config agent over default", () => { @@ -54,7 +55,7 @@ describe("resolveRunAgent", () => { const agent = resolveRunAgent({ message: "test" }, config, {}) // then - expect(agent).toBe("Prometheus - Plan Builder") + expect(agent).toBe(getAgentRuntimeName("prometheus")) }) it("falls back to sisyphus when none set", () => { @@ -65,7 +66,7 @@ describe("resolveRunAgent", () => { const agent = resolveRunAgent({ message: "test" }, config, {}) // then - expect(agent).toBe("Sisyphus - Ultraworker") + expect(agent).toBe(getAgentRuntimeName("sisyphus")) }) it("skips disabled sisyphus for next available core agent", () => { @@ -76,10 +77,10 @@ describe("resolveRunAgent", () => { const agent = resolveRunAgent({ message: "test" }, config, {}) // then - expect(agent).toBe("Hephaestus - Deep Agent") + expect(agent).toBe(getAgentRuntimeName("hephaestus")) }) - it("maps display-name style default_run_agent values to canonical display names", () => { + it("maps display-name style default_run_agent values to canonical runtime names", () => { // given const config = createConfig({ default_run_agent: "Sisyphus - Ultraworker" }) @@ -87,7 +88,7 @@ describe("resolveRunAgent", () => { const agent = resolveRunAgent({ message: "test" }, config, {}) // then - expect(agent).toBe("Sisyphus - Ultraworker") + expect(agent).toBe(getAgentRuntimeName("sisyphus")) }) }) diff --git a/src/cli/run/server-connection.test.ts b/src/cli/run/server-connection.test.ts index 90bad1812..2cd4ed660 100644 --- a/src/cli/run/server-connection.test.ts +++ b/src/cli/run/server-connection.test.ts @@ -3,6 +3,7 @@ import { describe, it, expect, mock, beforeEach, afterEach, afterAll } from "bun import * as originalSdk from "@opencode-ai/sdk" import * as originalPortUtils from "../../shared/port-utils" import * as originalBinaryResolver from "./opencode-binary-resolver" +import * as originalServerAuth from "../../shared/opencode-server-auth" const originalConsole = globalThis.console @@ -13,11 +14,15 @@ const mockCreateOpencode = mock(() => server: { url: "http://127.0.0.1:4096", close: mockServerClose }, }) ) -const mockCreateOpencodeClient = mock(() => ({ session: {} })) +const mockCreateOpencodeClient = mock((options?: { baseUrl?: string }) => ({ + session: {}, + baseUrl: options?.baseUrl, +})) const mockIsPortAvailable = mock(() => Promise.resolve(true)) const mockGetAvailableServerPort = mock(() => Promise.resolve({ port: 4096, wasAutoSelected: false })) const mockConsoleLog = mock(() => {}) const mockWithWorkingOpencodePath = mock((startServer: () => Promise) => startServer()) +const mockInjectServerAuthIntoClient = mock(() => {}) mock.module("@opencode-ai/sdk", () => ({ createOpencode: mockCreateOpencode, @@ -34,10 +39,15 @@ mock.module("./opencode-binary-resolver", () => ({ withWorkingOpencodePath: mockWithWorkingOpencodePath, })) +mock.module("../../shared/opencode-server-auth", () => ({ + injectServerAuthIntoClient: mockInjectServerAuthIntoClient, +})) + afterAll(() => { mock.module("@opencode-ai/sdk", () => originalSdk) mock.module("../../shared/port-utils", () => originalPortUtils) mock.module("./opencode-binary-resolver", () => originalBinaryResolver) + mock.module("../../shared/opencode-server-auth", () => originalServerAuth) mock.restore() }) @@ -52,6 +62,7 @@ describe("createServerConnection", () => { mockServerClose.mockClear() mockConsoleLog.mockClear() mockWithWorkingOpencodePath.mockClear() + mockInjectServerAuthIntoClient.mockClear() globalThis.console = { ...console, log: mockConsoleLog } as typeof console }) @@ -59,6 +70,49 @@ describe("createServerConnection", () => { globalThis.console = originalConsole }) + it("attach mode injects auth only for loopback URLs", async () => { + // given + const signal = new AbortController().signal + + // when + const localhostResult = await createServerConnection({ attach: "http://localhost:8080", signal }) + const loopbackResult = await createServerConnection({ attach: "http://127.0.0.1:8080", signal }) + const anyBindResult = await createServerConnection({ attach: "http://0.0.0.0:8080", signal }) + const remoteResult = await createServerConnection({ attach: "https://example.com", signal }) + + // then + expect(mockCreateOpencodeClient).toHaveBeenCalledWith({ baseUrl: "http://localhost:8080" }) + expect(mockCreateOpencodeClient).toHaveBeenCalledWith({ baseUrl: "http://127.0.0.1:8080" }) + expect(mockCreateOpencodeClient).toHaveBeenCalledWith({ baseUrl: "http://0.0.0.0:8080" }) + expect(mockCreateOpencodeClient).toHaveBeenCalledWith({ baseUrl: "https://example.com" }) + expect(mockInjectServerAuthIntoClient).toHaveBeenCalledTimes(3) + expect(mockInjectServerAuthIntoClient).toHaveBeenNthCalledWith(1, localhostResult.client) + expect(mockInjectServerAuthIntoClient).toHaveBeenNthCalledWith(2, loopbackResult.client) + expect(mockInjectServerAuthIntoClient).toHaveBeenNthCalledWith(3, anyBindResult.client) + expect(mockInjectServerAuthIntoClient).not.toHaveBeenCalledWith(remoteResult.client) + expect(mockWithWorkingOpencodePath).not.toHaveBeenCalled() + localhostResult.cleanup() + loopbackResult.cleanup() + anyBindResult.cleanup() + remoteResult.cleanup() + expect(mockServerClose).not.toHaveBeenCalled() + }) + + it("attach mode skips auth injection for invalid attach URLs", async () => { + // given + const signal = new AbortController().signal + const attachUrl = "not-a-url" + + // when + const result = await createServerConnection({ attach: attachUrl, signal }) + + // then + expect(mockCreateOpencodeClient).toHaveBeenCalledWith({ baseUrl: attachUrl }) + expect(mockInjectServerAuthIntoClient).not.toHaveBeenCalled() + result.cleanup() + expect(mockServerClose).not.toHaveBeenCalled() + }) + it("attach mode returns client with no-op cleanup", async () => { // given const signal = new AbortController().signal @@ -68,8 +122,6 @@ describe("createServerConnection", () => { const result = await createServerConnection({ attach: attachUrl, signal }) // then - expect(mockCreateOpencodeClient).toHaveBeenCalledWith({ baseUrl: attachUrl }) - expect(mockWithWorkingOpencodePath).not.toHaveBeenCalled() expect(result.client).toBeDefined() expect(result.cleanup).toBeDefined() result.cleanup() diff --git a/src/cli/run/server-connection.ts b/src/cli/run/server-connection.ts index bf658ff05..f92aa1a08 100644 --- a/src/cli/run/server-connection.ts +++ b/src/cli/run/server-connection.ts @@ -1,9 +1,21 @@ import { createOpencode, createOpencodeClient } from "@opencode-ai/sdk" import pc from "picocolors" import type { ServerConnection } from "./types" +import { injectServerAuthIntoClient } from "../../shared/opencode-server-auth" import { getAvailableServerPort, isPortAvailable, DEFAULT_SERVER_PORT } from "../../shared/port-utils" import { withWorkingOpencodePath } from "./opencode-binary-resolver" +const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1", "[::1]", "0.0.0.0"]) + +function isLoopbackAttachUrl(url: string): boolean { + try { + const parsed = new URL(url) + return LOOPBACK_HOSTS.has(parsed.hostname) + } catch { + return false + } +} + function isPortStartFailure(error: unknown, port: number): boolean { if (!(error instanceof Error)) { return false @@ -40,6 +52,9 @@ export async function createServerConnection(options: { if (attach !== undefined) { console.log(pc.dim("Attaching to existing server at"), pc.cyan(attach)) const client = createOpencodeClient({ baseUrl: attach }) + if (isLoopbackAttachUrl(attach)) { + injectServerAuthIntoClient(client) + } return { client, cleanup: () => {} } } @@ -66,12 +81,14 @@ export async function createServerConnection(options: { console.log(pc.dim("Port"), pc.cyan(port.toString()), pc.dim("became occupied, attaching to existing server")) const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${port}` }) + injectServerAuthIntoClient(client) return { client, cleanup: () => {} } } } console.log(pc.dim("Port"), pc.cyan(port.toString()), pc.dim("is occupied, attaching to existing server")) const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${port}` }) + injectServerAuthIntoClient(client) return { client, cleanup: () => {} } } @@ -93,6 +110,7 @@ export async function createServerConnection(options: { console.log(pc.dim("Port range exhausted, attaching to existing server on"), pc.cyan(DEFAULT_SERVER_PORT.toString())) const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${DEFAULT_SERVER_PORT}` }) + injectServerAuthIntoClient(client) return { client, cleanup: () => {} } } diff --git a/src/cli/tui-install-prompts.ts b/src/cli/tui-install-prompts.ts index c2e5a3b49..9638dfd4d 100644 --- a/src/cli/tui-install-prompts.ts +++ b/src/cli/tui-install-prompts.ts @@ -74,7 +74,7 @@ export async function promptInstallConfig(detected: DetectedConfig): Promise { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, }), spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true), spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.3.9"), @@ -92,6 +93,7 @@ describe("runTuiInstaller", () => { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, }), spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true), spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.4.0"), @@ -105,6 +107,7 @@ describe("runTuiInstaller", () => { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, }), spyOn(configManager, "addPluginToOpenCodeConfig").mockResolvedValue({ success: true, diff --git a/src/cli/tui-installer.ts b/src/cli/tui-installer.ts index 4272557c0..ef4b9088a 100644 --- a/src/cli/tui-installer.ts +++ b/src/cli/tui-installer.ts @@ -77,7 +77,7 @@ export async function runTuiInstaller(args: InstallArgs, version: string): Promi ) } - if (!config.hasClaude && !config.hasOpenAI && !config.hasGemini && !config.hasCopilot && !config.hasOpencodeZen) { + if (!config.hasClaude && !config.hasOpenAI && !config.hasGemini && !config.hasCopilot && !config.hasOpencodeZen && !config.hasVercelAiGateway) { p.log.warn("No model providers configured. Using opencode/big-pickle as fallback.") } diff --git a/src/cli/types.ts b/src/cli/types.ts index a8f785cb0..c61f96bd4 100644 --- a/src/cli/types.ts +++ b/src/cli/types.ts @@ -11,6 +11,7 @@ export interface InstallArgs { zaiCodingPlan?: BooleanArg kimiForCoding?: BooleanArg opencodeGo?: BooleanArg + vercelAiGateway?: BooleanArg skipAuth?: boolean } @@ -24,6 +25,7 @@ export interface InstallConfig { hasZaiCodingPlan: boolean hasKimiForCoding: boolean hasOpencodeGo: boolean + hasVercelAiGateway: boolean } export interface ConfigMergeResult { @@ -44,4 +46,5 @@ export interface DetectedConfig { hasZaiCodingPlan: boolean hasKimiForCoding: boolean hasOpencodeGo: boolean + hasVercelAiGateway: boolean } diff --git a/src/config/AGENTS.md b/src/config/AGENTS.md index 517ccc9a1..d180669b3 100644 --- a/src/config/AGENTS.md +++ b/src/config/AGENTS.md @@ -1,6 +1,6 @@ # src/config/ — Zod v4 Schema System -**Generated:** 2026-04-11 +**Generated:** 2026-04-18 ## OVERVIEW diff --git a/src/config/schema/agent-definitions.ts b/src/config/schema/agent-definitions.ts new file mode 100644 index 000000000..328238e2c --- /dev/null +++ b/src/config/schema/agent-definitions.ts @@ -0,0 +1,5 @@ +import { z } from "zod" + +export const AgentDefinitionPathSchema = z.string().min(1) + +export const AgentDefinitionsConfigSchema = z.array(AgentDefinitionPathSchema).optional() diff --git a/src/config/schema/background-task.test.ts b/src/config/schema/background-task.test.ts index ddfcbafec..e7f1a3f8c 100644 --- a/src/config/schema/background-task.test.ts +++ b/src/config/schema/background-task.test.ts @@ -27,30 +27,6 @@ describe("BackgroundTaskConfigSchema", () => { }) }) - describe("maxDescendants", () => { - describe("#given valid maxDescendants (50)", () => { - test("#when parsed #then returns correct value", () => { - const result = BackgroundTaskConfigSchema.parse({ maxDescendants: 50 }) - - expect(result.maxDescendants).toBe(50) - }) - }) - - describe("#given maxDescendants below minimum (0)", () => { - test("#when parsed #then throws ZodError", () => { - let thrownError: unknown - - try { - BackgroundTaskConfigSchema.parse({ maxDescendants: 0 }) - } catch (error) { - thrownError = error - } - - expect(thrownError).toBeInstanceOf(ZodError) - }) - }) - }) - describe("syncPollTimeoutMs", () => { describe("#given valid syncPollTimeoutMs (120000)", () => { test("#when parsed #then returns correct value", () => { diff --git a/src/config/schema/background-task.ts b/src/config/schema/background-task.ts index 44d16b505..4b62612fa 100644 --- a/src/config/schema/background-task.ts +++ b/src/config/schema/background-task.ts @@ -11,7 +11,6 @@ export const BackgroundTaskConfigSchema = z.object({ providerConcurrency: z.record(z.string(), z.number().min(0)).optional(), modelConcurrency: z.record(z.string(), z.number().min(0)).optional(), maxDepth: z.number().int().min(1).optional(), - maxDescendants: z.number().int().min(1).optional(), /** Stale timeout in milliseconds - interrupt tasks with no activity for this duration (default: 180000 = 3 minutes, minimum: 60000 = 1 minute) */ staleTimeoutMs: z.number().min(60000).optional(), /** Timeout for tasks that never received any progress update, falling back to startedAt (default: 1800000 = 30 minutes, minimum: 60000 = 1 minute) */ diff --git a/src/config/schema/oh-my-opencode-config.ts b/src/config/schema/oh-my-opencode-config.ts index eb9299769..e62413d26 100644 --- a/src/config/schema/oh-my-opencode-config.ts +++ b/src/config/schema/oh-my-opencode-config.ts @@ -1,6 +1,7 @@ import { z } from "zod" import { AnyMcpNameSchema } from "../../mcp/types" import { BuiltinSkillNameSchema } from "./agent-names" +import { AgentDefinitionsConfigSchema } from "./agent-definitions" import { AgentOverridesSchema } from "./agent-overrides" import { BabysittingConfigSchema } from "./babysitting" import { BackgroundTaskConfigSchema } from "./background-task" @@ -29,6 +30,8 @@ export const OhMyOpenCodeConfigSchema = z.object({ new_task_system_enabled: z.boolean().optional(), /** Default agent name for `oh-my-opencode run` (env: OPENCODE_DEFAULT_AGENT) */ default_run_agent: z.string().optional(), + /** Paths to external agent definition files (.md or .json) */ + agent_definitions: AgentDefinitionsConfigSchema, disabled_mcps: z.array(AnyMcpNameSchema).optional(), disabled_agents: z.array(z.string()).optional(), disabled_skills: z.array(BuiltinSkillNameSchema).optional(), diff --git a/src/create-hooks.ts b/src/create-hooks.ts index 0e40ad480..436f8e2b9 100644 --- a/src/create-hooks.ts +++ b/src/create-hooks.ts @@ -2,6 +2,7 @@ import type { AvailableSkill } from "./agents/dynamic-agent-prompt-builder" import type { HookName, OhMyOpenCodeConfig } from "./config" import type { LoadedSkill } from "./features/opencode-skill-loader/types" import type { BackgroundManager } from "./features/background-agent" +import type { ModelFallbackControllerAccessor } from "./hooks/model-fallback" import type { PluginContext } from "./plugin/types" import type { ModelCacheState } from "./plugin-state" @@ -36,6 +37,7 @@ export function createHooks(args: { pluginConfig: OhMyOpenCodeConfig modelCacheState: ModelCacheState backgroundManager: BackgroundManager + modelFallbackControllerAccessor?: ModelFallbackControllerAccessor isHookEnabled: (hookName: HookName) => boolean safeHookEnabled: boolean mergedSkills: LoadedSkill[] @@ -46,6 +48,7 @@ export function createHooks(args: { pluginConfig, modelCacheState, backgroundManager, + modelFallbackControllerAccessor, isHookEnabled, safeHookEnabled, mergedSkills, @@ -56,6 +59,7 @@ export function createHooks(args: { ctx, pluginConfig, modelCacheState, + modelFallbackControllerAccessor, isHookEnabled, safeHookEnabled, }) diff --git a/src/create-managers.ts b/src/create-managers.ts index d40896343..9c0013fd4 100644 --- a/src/create-managers.ts +++ b/src/create-managers.ts @@ -5,6 +5,7 @@ import type { PluginContext, TmuxConfig } from "./plugin/types" import type { SubagentSessionCreatedEvent } from "./features/background-agent" import { BackgroundManager } from "./features/background-agent" import { SkillMcpManager } from "./features/skill-mcp-manager" +import { createModelFallbackControllerAccessor } from "./hooks/model-fallback" import { initTaskToastManager } from "./features/task-toast-manager" import { TmuxSessionManager } from "./features/tmux-subagent" import * as openclawRuntimeDispatch from "./openclaw/runtime-dispatch" @@ -12,6 +13,7 @@ import { registerManagerForCleanup } from "./features/background-agent/process-c import { createConfigHandler } from "./plugin-handlers" import { log } from "./shared" import { markServerRunningInProcess } from "./shared/tmux/tmux-utils/server-health" +import type { ModelFallbackControllerAccessor } from "./hooks/model-fallback" type CreateManagersDeps = { BackgroundManagerClass: typeof BackgroundManager @@ -38,6 +40,7 @@ export type Managers = { backgroundManager: BackgroundManager skillMcpManager: SkillMcpManager configHandler: ReturnType + modelFallbackControllerAccessor: ModelFallbackControllerAccessor } export function createManagers(args: { @@ -119,11 +122,13 @@ export function createManagers(args: { pluginConfig, modelCacheState, }) + const modelFallbackControllerAccessor = createModelFallbackControllerAccessor() return { tmuxSessionManager, backgroundManager, skillMcpManager, configHandler, + modelFallbackControllerAccessor, } } diff --git a/src/create-tools.ts b/src/create-tools.ts index 5ac5a7e2f..6a9bc3941 100644 --- a/src/create-tools.ts +++ b/src/create-tools.ts @@ -22,7 +22,7 @@ type CreateToolsResult = { export async function createTools(args: { ctx: PluginContext pluginConfig: OhMyOpenCodeConfig - managers: Pick + managers: Pick }): Promise { const { ctx, pluginConfig, managers } = args diff --git a/src/features/AGENTS.md b/src/features/AGENTS.md index ff920a1bd..5deea8450 100644 --- a/src/features/AGENTS.md +++ b/src/features/AGENTS.md @@ -1,6 +1,6 @@ # src/features/ — 19 Feature Modules -**Generated:** 2026-04-11 +**Generated:** 2026-04-18 ## OVERVIEW diff --git a/src/features/background-agent/AGENTS.md b/src/features/background-agent/AGENTS.md index bac2302fe..6c6761eee 100644 --- a/src/features/background-agent/AGENTS.md +++ b/src/features/background-agent/AGENTS.md @@ -44,7 +44,7 @@ Both must agree before marking a task complete. Prevents premature completion on ## 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) - FIFO queue: tasks wait in order when slots full - Slot released on: completion, error, cancellation diff --git a/src/features/background-agent/compaction-aware-message-resolver.test.ts b/src/features/background-agent/compaction-aware-message-resolver.test.ts index 07fc8b448..4ad9a33cf 100644 --- a/src/features/background-agent/compaction-aware-message-resolver.test.ts +++ b/src/features/background-agent/compaction-aware-message-resolver.test.ts @@ -83,7 +83,7 @@ describe("findNearestMessageExcludingCompaction", () => { // given const message = { 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)) @@ -94,18 +94,18 @@ describe("findNearestMessageExcludingCompaction", () => { expect(result).not.toBeNull() expect(result?.agent).toBe("sisyphus") 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", () => { // given const compactionMessage = { agent: "compaction", - model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7" }, } const validMessage = { 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, "001.json"), JSON.stringify(validMessage)) @@ -125,12 +125,12 @@ describe("findNearestMessageExcludingCompaction", () => { writeFileSync(join(tempDir, "002.json"), JSON.stringify({ id: compactionMessageID, 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({ id: "msg_001", agent: "sisyphus", - model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7" }, })) mkdirSync(partDir, { recursive: true }) writeFileSync(join(partDir, "prt_0001.json"), JSON.stringify({ type: "compaction" })) diff --git a/src/features/background-agent/concurrency.test.ts b/src/features/background-agent/concurrency.test.ts index 682d6029a..45150f79b 100644 --- a/src/features/background-agent/concurrency.test.ts +++ b/src/features/background-agent/concurrency.test.ts @@ -94,7 +94,7 @@ describe("ConcurrencyManager.getConcurrencyLimit", () => { // when 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") // then diff --git a/src/features/background-agent/index.ts b/src/features/background-agent/index.ts index e1d1a9b73..d21b2faa8 100644 --- a/src/features/background-agent/index.ts +++ b/src/features/background-agent/index.ts @@ -1,2 +1,4 @@ export * from "./types" export { BackgroundManager, type SubagentSessionCreatedEvent, type OnSubagentSessionCreated } from "./manager" +export { waitForTaskSessionID } from "./wait-for-task-session" +export type { WaitForTaskSessionIDOptions } from "./wait-for-task-session" diff --git a/src/features/background-agent/manager.test.ts b/src/features/background-agent/manager.test.ts index 7ad6fea39..8c855ebcf 100644 --- a/src/features/background-agent/manager.test.ts +++ b/src/features/background-agent/manager.test.ts @@ -855,7 +855,7 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () => { info: { 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 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() }) @@ -913,7 +913,7 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () => } const currentMessage: CurrentMessage = { agent: "sisyphus", - model: { providerID: "anthropic", modelID: "claude-opus-4.6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4.7" }, } // when @@ -921,7 +921,7 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () => // then - uses currentMessage values, not task.parentModel/parentAgent 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 () => { @@ -1155,7 +1155,7 @@ describe("BackgroundManager.notifyParentSession - notifications toggle", () => { agent: "explore", model: { providerID: "anthropic", - modelID: "claude-opus-4.6", + modelID: "claude-opus-4.7", variant: "high", }, }, @@ -1211,7 +1211,7 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => { agent: "explore", model: { providerID: "anthropic", - modelID: "claude-opus-4.6", + modelID: "claude-opus-4.7", variant: "max", }, }, @@ -1231,7 +1231,7 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => { status: "completed", startedAt: 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])) @@ -1272,7 +1272,7 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => { status: "completed", startedAt: 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])) @@ -1349,7 +1349,7 @@ describe("BackgroundManager.tryCompleteTask", () => { test("should release concurrency and clear key on completion", async () => { // given - const concurrencyKey = "anthropic/claude-opus-4.6" + const concurrencyKey = "anthropic/claude-opus-4.7" const concurrencyManager = getConcurrencyManager(manager) await concurrencyManager.acquire(concurrencyKey) @@ -1378,7 +1378,7 @@ describe("BackgroundManager.tryCompleteTask", () => { test("should prevent double completion and double release", async () => { // given - const concurrencyKey = "anthropic/claude-opus-4.6" + const concurrencyKey = "anthropic/claude-opus-4.7" const concurrencyManager = getConcurrencyManager(manager) await concurrencyManager.acquire(concurrencyKey) @@ -1508,7 +1508,7 @@ describe("BackgroundManager.tryCompleteTask", () => { test("should release task concurrencyKey when startTask throws after assigning it", async () => { // given - const concurrencyKey = "anthropic/claude-opus-4.6" + const concurrencyKey = "anthropic/claude-opus-4.7" const concurrencyManager = getConcurrencyManager(manager) const task = createMockTask({ @@ -1524,7 +1524,7 @@ describe("BackgroundManager.tryCompleteTask", () => { agent: task.agent, parentSessionID: task.parentSessionID, 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) 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 () => { //#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({ id: "task-zombie-session", @@ -1561,7 +1561,7 @@ describe("BackgroundManager.tryCompleteTask", () => { agent: task.agent, parentSessionID: task.parentSessionID, 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) 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 () => { // given - const concurrencyKey = "anthropic/claude-opus-4.6" + const concurrencyKey = "anthropic/claude-opus-4.7" const concurrencyManager = getConcurrencyManager(manager) const task = createMockTask({ @@ -1601,7 +1601,7 @@ describe("BackgroundManager.tryCompleteTask", () => { agent: task.agent, parentSessionID: task.parentSessionID, 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) getQueuesByKey(manager).set(concurrencyKey, [{ task, input }]) @@ -2104,7 +2104,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { agent: "test-agent", parentSessionID: "parent-session", parentMessageID: "parent-message", - model: { providerID: "anthropic", modelID: "claude-opus-4.6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4.7" }, } const launchInputWithoutModel = { description: "Test task without model", @@ -2124,7 +2124,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { expect(taskWithModel.status).toBe("pending") expect(taskWithoutModel.status).toBe("pending") 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[1].agent).toBe("test-agent") expect("model" in promptBodies[1]).toBe(false) @@ -2327,7 +2327,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { await expect(result).rejects.toThrow("background_task.maxDepth=3") }) - test("should block launches when maxDescendants is reached", async () => { + test("allows multiple descendants without a root spawn cap", async () => { // given manager.shutdown() manager = new BackgroundManager( @@ -2337,7 +2337,6 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { }), directory: tmpdir(), } as unknown as PluginInput, - { maxDescendants: 1 }, ) const input = { @@ -2354,10 +2353,10 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { const result = manager.launch(input) // then - await expect(result).rejects.toThrow("background_task.maxDescendants=1") + await expect(result).resolves.toBeDefined() }) - test("should consume descendant quota for reserved sync spawns", async () => { + test("allows spawn assertions after reserveSubagentSpawn without a root spawn cap", async () => { // given manager.shutdown() manager = new BackgroundManager( @@ -2367,7 +2366,6 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { }), directory: tmpdir(), } as unknown as PluginInput, - { maxDescendants: 1 }, ) await manager.reserveSubagentSpawn("session-root") @@ -2376,7 +2374,10 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { const result = manager.assertCanSpawn("session-root") // then - await expect(result).rejects.toThrow("background_task.maxDescendants=1") + await expect(result).resolves.toMatchObject({ + rootSessionID: "session-root", + childDepth: 1, + }) }) test("should fail closed when session lineage lookup fails", async () => { @@ -2392,7 +2393,6 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { ), directory: tmpdir(), } as unknown as PluginInput, - { maxDescendants: 1 }, ) const input = { @@ -2407,10 +2407,10 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { const result = manager.launch(input) // then - await expect(result).rejects.toThrow("background_task.maxDescendants cannot be enforced safely") + await expect(result).rejects.toThrow("background_task.maxDepth cannot be enforced safely") }) - test("should release descendant quota when queued task is cancelled before session starts", async () => { + test("allows replacement launch when a queued task is cancelled before session starts", async () => { // given manager.shutdown() manager = new BackgroundManager( @@ -2420,7 +2420,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { }), directory: tmpdir(), } as unknown as PluginInput, - { defaultConcurrency: 1, maxDescendants: 2 }, + { defaultConcurrency: 1 }, ) const input = { @@ -2445,7 +2445,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { expect(replacementTask.status).toBe("pending") }) - test("should release descendant quota when session creation fails before session starts", async () => { + test("allows retry after session creation fails before session starts", async () => { // given let createAttempts = 0 manager.shutdown() @@ -2472,7 +2472,6 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { }, directory: tmpdir(), } as unknown as PluginInput, - { maxDescendants: 1 }, ) const input = { @@ -2887,7 +2886,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { } }) - test("should release descendant quota when task completes", async () => { + test("allows relaunch after task completes", async () => { manager.shutdown() manager = new BackgroundManager( { @@ -2896,7 +2895,6 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { }), directory: tmpdir(), } as unknown as PluginInput, - { maxDescendants: 1 }, ) stubNotifyParentSession(manager) @@ -2920,7 +2918,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { await expect(manager.launch(input)).resolves.toBeDefined() }) - test("should release descendant quota when running task is cancelled", async () => { + test("allows relaunch after running task is cancelled", async () => { manager.shutdown() manager = new BackgroundManager( { @@ -2929,7 +2927,6 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { }), directory: tmpdir(), } as unknown as PluginInput, - { maxDescendants: 1 }, ) const input = { @@ -2950,7 +2947,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { await expect(manager.launch(input)).resolves.toBeDefined() }) - test("should release descendant quota when task errors", async () => { + test("allows relaunch after task errors", async () => { manager.shutdown() manager = new BackgroundManager( { @@ -2959,7 +2956,6 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { }), directory: tmpdir(), } as unknown as PluginInput, - { maxDescendants: 1 }, ) const input = { @@ -2984,7 +2980,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { await expect(manager.launch(input)).resolves.toBeDefined() }) - test("should not double-decrement quota when pending task is cancelled", async () => { + test("allows repeated relaunch after pending tasks are cancelled", async () => { manager.shutdown() manager = new BackgroundManager( { @@ -2993,7 +2989,6 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { }), directory: tmpdir(), } as unknown as PluginInput, - { maxDescendants: 2 }, ) const input = { @@ -3250,7 +3245,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => { description: "Task 1", prompt: "Do something", agent: "test-agent", - model: { providerID: "anthropic", modelID: "claude-opus-4.6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4.7" }, parentSessionID: "parent-session", parentMessageID: "parent-message", } @@ -4230,7 +4225,7 @@ describe("BackgroundManager.handleEvent - session.deleted cascade", () => { describe("BackgroundManager.handleEvent - session.error", () => { 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" }, ] @@ -4254,7 +4249,7 @@ describe("BackgroundManager.handleEvent - session.error", () => { agent: "sisyphus", status: "running", 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, attemptCount: 0, }) @@ -4399,7 +4394,7 @@ describe("BackgroundManager.handleEvent - session.error", () => { //#given const manager = createBackgroundManager() const concurrencyManager = getConcurrencyManager(manager) - const concurrencyKey = "anthropic/claude-opus-4.6-thinking" + const concurrencyKey = "anthropic/claude-opus-4.7-thinking" await concurrencyManager.acquire(concurrencyKey) stubProcessKey(manager) @@ -4411,7 +4406,7 @@ describe("BackgroundManager.handleEvent - session.error", () => { description: "task that should retry", concurrencyKey, 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" }, ], }) @@ -4425,7 +4420,7 @@ describe("BackgroundManager.handleEvent - session.error", () => { name: "UnknownError", data: { 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\"}}", }, }, }, @@ -4436,7 +4431,7 @@ describe("BackgroundManager.handleEvent - session.error", () => { expect(task.attemptCount).toBe(1) expect(task.model).toEqual({ providerID: "anthropic", - modelID: "claude-opus-4.6", + modelID: "claude-opus-4.7", variant: "max", }) expect(task.concurrencyKey).toBeUndefined() @@ -4474,7 +4469,7 @@ describe("BackgroundManager.handleEvent - session.error", () => { expect(task.attemptCount).toBe(1) expect(task.model).toEqual({ providerID: "anthropic", - modelID: "claude-opus-4.6", + modelID: "claude-opus-4.7", variant: "max", }) @@ -4502,7 +4497,7 @@ describe("BackgroundManager.handleEvent - session.error", () => { name: "UnknownError", data: { 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\"}}", }, }, } @@ -4519,7 +4514,7 @@ describe("BackgroundManager.handleEvent - session.error", () => { expect(task.attemptCount).toBe(1) expect(task.model).toEqual({ providerID: "anthropic", - modelID: "claude-opus-4.6", + modelID: "claude-opus-4.7", variant: "max", }) diff --git a/src/features/background-agent/manager.ts b/src/features/background-agent/manager.ts index a59ea9530..f418580c2 100644 --- a/src/features/background-agent/manager.ts +++ b/src/features/background-agent/manager.ts @@ -72,8 +72,6 @@ import { } from "./loop-detector" import { createSubagentDepthLimitError, - createSubagentDescendantLimitError, - getMaxRootSessionSpawnBudget, getMaxSubagentDepth, resolveSubagentSpawnContext, type SubagentSpawnContext, @@ -218,16 +216,6 @@ export class BackgroundManager { }) } - const maxRootSessionSpawnBudget = getMaxRootSessionSpawnBudget(this.config) - const descendantCount = this.rootDescendantCounts.get(spawnContext.rootSessionID) ?? 0 - if (descendantCount >= maxRootSessionSpawnBudget) { - throw createSubagentDescendantLimitError({ - rootSessionID: spawnContext.rootSessionID, - descendantCount, - maxDescendants: maxRootSessionSpawnBudget, - }) - } - return spawnContext } diff --git a/src/features/background-agent/process-cleanup.test-helpers.ts b/src/features/background-agent/process-cleanup.test-helpers.ts new file mode 100644 index 000000000..c0a3dfa2d --- /dev/null +++ b/src/features/background-agent/process-cleanup.test-helpers.ts @@ -0,0 +1,27 @@ +type ProcessCleanupEvent = + | NodeJS.Signals + | "beforeExit" + | "exit" + | "uncaughtException" + | "unhandledRejection" + +export function getNewListener( + signal: ProcessCleanupEvent, + existingListeners: Function[], +): () => void { + const listener = process + .listeners(signal) + .find((registeredListener) => !existingListeners.includes(registeredListener)) + + if (typeof listener !== "function") { + throw new Error(`Expected a ${signal} listener to be registered`) + } + + return listener +} + +export async function flushMicrotasks(): Promise { + for (let iteration = 0; iteration < 10; iteration += 1) { + await Promise.resolve() + } +} diff --git a/src/features/background-agent/process-cleanup.test.ts b/src/features/background-agent/process-cleanup.test.ts index 7d01aaa21..a9ce35435 100644 --- a/src/features/background-agent/process-cleanup.test.ts +++ b/src/features/background-agent/process-cleanup.test.ts @@ -1,3 +1,5 @@ +/// + import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test" import { @@ -5,42 +7,17 @@ import { registerManagerForCleanup, unregisterManagerForCleanup, } from "./process-cleanup" +import { flushMicrotasks, getNewListener } from "./process-cleanup.test-helpers" type CleanupManager = { shutdown: () => void | Promise } -type ProcessCleanupEvent = NodeJS.Signals | "beforeExit" | "exit" - -function getNewListener( - signal: ProcessCleanupEvent, - existingListeners: Function[], -): () => void { - const listener = process - .listeners(signal) - .find((registeredListener) => !existingListeners.includes(registeredListener)) - - expect(listener).toBeDefined() - - if (typeof listener !== "function") { - throw new Error(`Expected a ${signal} listener to be registered`) - } - - return listener -} - -async function flushMicrotasks(): Promise { - for (let iteration = 0; iteration < 10; iteration += 1) { - await Promise.resolve() - } -} - describe("#given process cleanup registration", () => { const registeredManagers: CleanupManager[] = [] - const originalExitCode = process.exitCode beforeEach(() => { - process.exitCode = originalExitCode + process.exitCode = 0 registeredManagers.length = 0 _resetForTesting() }) @@ -50,7 +27,7 @@ describe("#given process cleanup registration", () => { unregisterManagerForCleanup(manager) } - process.exitCode = originalExitCode + process.exitCode = 0 _resetForTesting() }) @@ -92,13 +69,7 @@ describe("#given process cleanup registration", () => { test("#when cleanup finishes after SIGINT #then the fallback exit timer is cleared", async () => { const sigintListenersBefore = process.listeners("SIGINT") - const timeoutHandle = setTimeout(() => undefined, 0) - clearTimeout(timeoutHandle) - - const setTimeoutImplementation: typeof setTimeout = () => timeoutHandle - const setTimeoutSpy = spyOn(globalThis, "setTimeout").mockImplementation( - setTimeoutImplementation, - ) + const setTimeoutSpy = spyOn(globalThis, "setTimeout") const clearTimeoutSpy = spyOn(globalThis, "clearTimeout") try { @@ -117,11 +88,10 @@ describe("#given process cleanup registration", () => { await flushMicrotasks() expect(setTimeoutSpy).toHaveBeenCalledTimes(1) - expect(clearTimeoutSpy).toHaveBeenCalledWith(timeoutHandle) + expect(clearTimeoutSpy).toHaveBeenCalledTimes(1) } finally { setTimeoutSpy.mockRestore() clearTimeoutSpy.mockRestore() - clearTimeout(timeoutHandle) } }) }) @@ -163,6 +133,32 @@ describe("#given process cleanup registration", () => { expect(process.listeners("SIGINT")).toHaveLength(sigintListenersAfterFirstRegistration) }) + + test("#given two managers registered #when uncaughtException fires #then both shutdowns called", async () => { + const exitSpy = spyOn(process, "exit").mockImplementation((code?: number): never => { + throw new Error(`Unexpected process.exit(${String(code)})`) + }) + const shutdownOne = mock(() => {}) + const shutdownTwo = mock(() => {}) + const managerOne = { shutdown: shutdownOne } + const managerTwo = { shutdown: shutdownTwo } + registeredManagers.push(managerOne, managerTwo) + + try { + registerManagerForCleanup(managerOne) + registerManagerForCleanup(managerTwo) + + process.emit("uncaughtException", new Error("boom")) + await flushMicrotasks() + + expect(shutdownOne).toHaveBeenCalledTimes(1) + expect(shutdownTwo).toHaveBeenCalledTimes(1) + expect(process.exitCode).toBe(1) + expect(exitSpy).not.toHaveBeenCalled() + } finally { + exitSpy.mockRestore() + } + }) }) describe("#given cleanup managers are unregistered", () => { @@ -202,5 +198,88 @@ describe("#given process cleanup registration", () => { expect(remainingManagerShutdown).toHaveBeenCalledTimes(1) expect(removedManagerShutdown).not.toHaveBeenCalled() }) + + test("#given uncaughtException handler registered #when manager is unregistered via unregisterManagerForCleanup #then subsequent events do not invoke that manager", () => { + const uncaughtExceptionListenersBefore = process.listeners("uncaughtException") + const shutdown = mock(() => {}) + const manager = { shutdown } + registeredManagers.push(manager) + + registerManagerForCleanup(manager) + expect(process.listeners("uncaughtException")).toHaveLength( + uncaughtExceptionListenersBefore.length + 1, + ) + + unregisterManagerForCleanup(manager) + registeredManagers.length = 0 + process.emit("uncaughtException", new Error("boom")) + + expect(shutdown).not.toHaveBeenCalled() + }) + }) + + describe("#given uncaught exception and rejection cleanup", () => { + test("#given manager registered AND process emits uncaughtException #when event fires #then manager.shutdown() called AND process.exitCode set to 1", async () => { + const exitSpy = spyOn(process, "exit").mockImplementation((code?: number): never => { + throw new Error(`Unexpected process.exit(${String(code)})`) + }) + const shutdown = mock(() => {}) + const manager = { shutdown } + registeredManagers.push(manager) + + try { + registerManagerForCleanup(manager) + + process.emit("uncaughtException", new Error("boom")) + await flushMicrotasks() + + expect(shutdown).toHaveBeenCalledTimes(1) + expect(process.exitCode).toBe(1) + expect(exitSpy).not.toHaveBeenCalled() + } finally { + exitSpy.mockRestore() + } + }) + + test("#given manager registered AND process emits unhandledRejection #when event fires #then manager.shutdown() called AND process.exitCode set to 1", async () => { + const exitSpy = spyOn(process, "exit").mockImplementation((code?: number): never => { + throw new Error(`Unexpected process.exit(${String(code)})`) + }) + const shutdown = mock(() => {}) + const manager = { shutdown } + registeredManagers.push(manager) + + try { + registerManagerForCleanup(manager) + + process.emit("unhandledRejection", new Error("boom"), Promise.resolve()) + await flushMicrotasks() + + expect(shutdown).toHaveBeenCalledTimes(1) + expect(process.exitCode).toBe(1) + expect(exitSpy).not.toHaveBeenCalled() + } finally { + exitSpy.mockRestore() + } + }) + + test("#given _resetForTesting() called #when event fires #then no cleanup runs", () => { + const uncaughtExceptionListenersBefore = process.listeners("uncaughtException") + const shutdown = mock(() => {}) + const manager = { shutdown } + + registerManagerForCleanup(manager) + expect(process.listeners("uncaughtException")).toHaveLength( + uncaughtExceptionListenersBefore.length + 1, + ) + + _resetForTesting() + process.emit("uncaughtException", new Error("boom")) + + expect(shutdown).not.toHaveBeenCalled() + expect(process.listeners("uncaughtException")).toHaveLength( + uncaughtExceptionListenersBefore.length, + ) + }) }) }) diff --git a/src/features/background-agent/process-cleanup.ts b/src/features/background-agent/process-cleanup.ts index 29be1958e..20f8fab00 100644 --- a/src/features/background-agent/process-cleanup.ts +++ b/src/features/background-agent/process-cleanup.ts @@ -1,33 +1,51 @@ import { log } from "../../shared" -type ProcessCleanupEvent = NodeJS.Signals | "beforeExit" | "exit" +type ProcessCleanupSignal = NodeJS.Signals | "beforeExit" | "exit" +type ProcessCleanupErrorEvent = "uncaughtException" | "unhandledRejection" + +function scheduleForcedExit(cleanupResult: void | Promise, exitCode: number): void { + process.exitCode = exitCode + const exitTimeout = setTimeout(() => process.exit(), 6000) + void Promise.resolve(cleanupResult).finally(() => { + clearTimeout(exitTimeout) + }) +} function registerProcessSignal( - signal: ProcessCleanupEvent, + signal: ProcessCleanupSignal, handler: () => void | Promise, exitAfter: boolean ): () => void { const listener = () => { const cleanupResult = handler() if (exitAfter) { - process.exitCode = 0 - const exitTimeout = setTimeout(() => process.exit(), 6000) - void Promise.resolve(cleanupResult).finally(() => { - clearTimeout(exitTimeout) - }) + scheduleForcedExit(cleanupResult, 0) } } process.on(signal, listener) return listener } +function registerErrorEvent( + signal: ProcessCleanupErrorEvent, + handler: (error: unknown) => void | Promise +): (error: unknown) => void { + const listener = (error: unknown) => { + log(`[background-agent] ${signal} received during shutdown cleanup:`, error) + scheduleForcedExit(handler(error), 1) + } + process.on(signal, listener) + return listener +} + interface CleanupTarget { shutdown(): void | Promise } const cleanupManagers = new Set() let cleanupRegistered = false -const cleanupHandlers = new Map void>() +const cleanupSignalHandlers = new Map void>() +const cleanupErrorHandlers = new Map void>() export function registerManagerForCleanup(manager: CleanupTarget): void { cleanupManagers.add(manager) @@ -59,9 +77,9 @@ export function registerManagerForCleanup(manager: CleanupTarget): void { return cleanupPromise } - const registerSignal = (signal: ProcessCleanupEvent, exitAfter: boolean): void => { + const registerSignal = (signal: ProcessCleanupSignal, exitAfter: boolean): void => { const listener = registerProcessSignal(signal, cleanupAll, exitAfter) - cleanupHandlers.set(signal, listener) + cleanupSignalHandlers.set(signal, listener) } registerSignal("SIGINT", true) @@ -71,6 +89,8 @@ export function registerManagerForCleanup(manager: CleanupTarget): void { } registerSignal("beforeExit", false) registerSignal("exit", false) + cleanupErrorHandlers.set("uncaughtException", registerErrorEvent("uncaughtException", cleanupAll)) + cleanupErrorHandlers.set("unhandledRejection", registerErrorEvent("unhandledRejection", cleanupAll)) } export function unregisterManagerForCleanup(manager: CleanupTarget): void { @@ -78,10 +98,14 @@ export function unregisterManagerForCleanup(manager: CleanupTarget): void { if (cleanupManagers.size > 0) return - for (const [signal, listener] of cleanupHandlers.entries()) { + for (const [signal, listener] of cleanupSignalHandlers.entries()) { process.off(signal, listener) } - cleanupHandlers.clear() + for (const [signal, listener] of cleanupErrorHandlers.entries()) { + process.off(signal, listener) + } + cleanupSignalHandlers.clear() + cleanupErrorHandlers.clear() cleanupRegistered = false } @@ -90,9 +114,13 @@ export function _resetForTesting(): void { for (const manager of [...cleanupManagers]) { cleanupManagers.delete(manager) } - for (const [signal, listener] of cleanupHandlers.entries()) { + for (const [signal, listener] of cleanupSignalHandlers.entries()) { process.off(signal, listener) } - cleanupHandlers.clear() + for (const [signal, listener] of cleanupErrorHandlers.entries()) { + process.off(signal, listener) + } + cleanupSignalHandlers.clear() + cleanupErrorHandlers.clear() cleanupRegistered = false } diff --git a/src/features/background-agent/spawner.test.ts b/src/features/background-agent/spawner.test.ts index b1f486c52..eb2cc294b 100644 --- a/src/features/background-agent/spawner.test.ts +++ b/src/features/background-agent/spawner.test.ts @@ -577,3 +577,84 @@ describe("background-agent spawner fallback model promotion", () => { expect(promptCalls[0]?.body?.agent).toBe("sisyphus-junior") }) }) + +describe("background-agent spawner tmux callback ordering", () => { + test("fires promptAsync before tmux callback resolves (no blocking)", async () => { + //#given + const events: string[] = [] + let resolveTmuxCallback: () => void = () => {} + const tmuxCallbackPromise = new Promise((resolve) => { + resolveTmuxCallback = resolve + }) + + const client = { + session: { + get: async () => ({ data: { directory: "/tmp/test" } }), + create: async () => { + events.push("session.create") + return { data: { id: "ses_blocking_tmux" } } + }, + promptAsync: async () => { + events.push("promptAsync") + return { data: {} } + }, + }, + } as any + + const onSubagentSessionCreated = mock(async () => { + events.push("tmux.callback.start") + await tmuxCallbackPromise + events.push("tmux.callback.end") + }) + + const task = createTask({ + description: "Blocking tmux test", + prompt: "Do work", + agent: "general", + parentSessionID: "ses_parent", + parentMessageID: "msg_parent", + }) + + const item = { + task, + input: { + description: task.description, + prompt: task.prompt, + agent: task.agent, + parentSessionID: task.parentSessionID, + parentMessageID: task.parentMessageID, + }, + } + + const ctx = { + client, + directory: "/tmp/test", + concurrencyManager: { release: () => {} }, + tmuxEnabled: true, + onSubagentSessionCreated, + onTaskError: () => {}, + } + + const originalTmux = process.env.TMUX + process.env.TMUX = "/tmp/fake-tmux-socket" + + try { + //#when + await startTask(item as any, ctx as any) + await new Promise((resolve) => setTimeout(resolve, 20)) + + //#then + expect(events).toContain("session.create") + expect(events).toContain("promptAsync") + expect(events).toContain("tmux.callback.start") + const promptIdx = events.indexOf("promptAsync") + const tmuxStartIdx = events.indexOf("tmux.callback.start") + expect(promptIdx < tmuxStartIdx).toBe(true) + expect(events).not.toContain("tmux.callback.end") + } finally { + resolveTmuxCallback() + if (originalTmux === undefined) delete process.env.TMUX + else process.env.TMUX = originalTmux + } + }) +}) diff --git a/src/features/background-agent/spawner.ts b/src/features/background-agent/spawner.ts index 675aeb5d9..ab6aaa2f1 100644 --- a/src/features/background-agent/spawner.ts +++ b/src/features/background-agent/spawner.ts @@ -1,6 +1,5 @@ import type { BackgroundTask, LaunchInput, ResumeInput } from "./types" import type { OpencodeClient, OnSubagentSessionCreated, QueueItem } from "./constants" -import { TMUX_CALLBACK_DELAY_MS } from "./constants" import { log, getAgentToolRestrictions, promptWithModelSuggestionRetry, createInternalAgentTextPart } from "../../shared" import { applySessionPromptParams } from "../../shared/session-prompt-params-helpers" import { subagentSessions } from "../claude-code-session-state" @@ -115,29 +114,6 @@ export async function startTask( const sessionID = createResult.data.id subagentSessions.add(sessionID) - log("[background-agent] tmux callback check", { - hasCallback: !!onSubagentSessionCreated, - tmuxEnabled, - isInsideTmux: isInsideTmux(), - sessionID, - parentID: input.parentSessionID, - }) - - if (onSubagentSessionCreated && tmuxEnabled && isInsideTmux()) { - log("[background-agent] Invoking tmux callback NOW", { sessionID }) - await onSubagentSessionCreated({ - sessionID, - parentID: input.parentSessionID, - title: input.description, - }).catch((err) => { - log("[background-agent] Failed to spawn tmux pane:", err) - }) - log("[background-agent] tmux callback completed, waiting") - await new Promise(r => setTimeout(r, TMUX_CALLBACK_DELAY_MS)) - } else { - log("[background-agent] SKIP tmux callback - conditions not met") - } - task.status = "running" task.startedAt = new Date() task.sessionID = sessionID @@ -188,7 +164,8 @@ export async function startTask( parts: [createInternalAgentTextPart(input.prompt)], } - promptWithModelSuggestionRetry(client, { + // Must fire BEFORE tmux callback: attach client needs session activity to render TUI. + const promptChain = promptWithModelSuggestionRetry(client, { path: { id: sessionID }, body: promptBody, }).catch(async (error) => { @@ -214,6 +191,29 @@ export async function startTask( log("[background-agent] promptAsync error:", error) onTaskError(task, error instanceof Error ? error : new Error(String(error))) }) + + void promptChain + + log("[background-agent] tmux callback check", { + hasCallback: !!onSubagentSessionCreated, + tmuxEnabled, + isInsideTmux: isInsideTmux(), + sessionID, + parentID: input.parentSessionID, + }) + + if (onSubagentSessionCreated && tmuxEnabled && isInsideTmux()) { + log("[background-agent] Invoking tmux callback (fire-and-forget)", { sessionID }) + void onSubagentSessionCreated({ + sessionID, + parentID: input.parentSessionID, + title: input.description, + }).catch((err) => { + log("[background-agent] Failed to spawn tmux pane:", err) + }) + } else { + log("[background-agent] SKIP tmux callback - conditions not met") + } } export async function resumeTask( diff --git a/src/features/background-agent/subagent-spawn-limits.test.ts b/src/features/background-agent/subagent-spawn-limits.test.ts index e158c0dad..e3094c551 100644 --- a/src/features/background-agent/subagent-spawn-limits.test.ts +++ b/src/features/background-agent/subagent-spawn-limits.test.ts @@ -5,9 +5,6 @@ import { getMaxSubagentDepth, DEFAULT_MAX_SUBAGENT_DEPTH, createSubagentDepthLimitError, - createSubagentDescendantLimitError, - getMaxRootSessionSpawnBudget, - DEFAULT_MAX_ROOT_SESSION_SPAWN_BUDGET, } from "./subagent-spawn-limits" function createMockClient(sessionGet: OpencodeClient["session"]["get"]): OpencodeClient { @@ -62,7 +59,7 @@ describe("resolveSubagentSpawnContext", () => { const result = resolveSubagentSpawnContext(client, "parent-session") // then - await expect(result).rejects.toThrow(/background_task\.maxDescendants cannot be enforced safely.*lookup failed/) + await expect(result).rejects.toThrow(/background_task\.maxDepth cannot be enforced safely.*lookup failed/) }) }) @@ -77,7 +74,7 @@ describe("resolveSubagentSpawnContext", () => { const result = resolveSubagentSpawnContext(client, "parent-session") // then - await expect(result).rejects.toThrow(/background_task\.maxDescendants cannot be enforced safely.*No session data returned/) + await expect(result).rejects.toThrow(/background_task\.maxDepth cannot be enforced safely.*No session data returned/) }) }) @@ -209,20 +206,6 @@ describe("getMaxSubagentDepth", () => { }) }) -describe("getMaxRootSessionSpawnBudget", () => { - test("returns DEFAULT_MAX_ROOT_SESSION_SPAWN_BUDGET when no config", () => { - expect(getMaxRootSessionSpawnBudget()).toBe(DEFAULT_MAX_ROOT_SESSION_SPAWN_BUDGET) - }) - - test("returns config.maxDescendants when provided", () => { - expect(getMaxRootSessionSpawnBudget({ maxDescendants: 10 })).toBe(10) - }) - - test("default is 50", () => { - expect(DEFAULT_MAX_ROOT_SESSION_SPAWN_BUDGET).toBe(50) - }) -}) - describe("createSubagentDepthLimitError", () => { test("includes childDepth, maxDepth, and session IDs in message", () => { const error = createSubagentDepthLimitError({ @@ -239,18 +222,3 @@ describe("createSubagentDepthLimitError", () => { expect(error.message).toContain("spawn blocked") }) }) - -describe("createSubagentDescendantLimitError", () => { - test("includes descendant count, max, and root session ID", () => { - const error = createSubagentDescendantLimitError({ - rootSessionID: "root-789", - descendantCount: 50, - maxDescendants: 50, - }) - - expect(error.message).toContain("root-789") - expect(error.message).toContain("50") - expect(error.message).toContain("maxDescendants=50") - expect(error.message).toContain("spawn blocked") - }) -}) diff --git a/src/features/background-agent/subagent-spawn-limits.ts b/src/features/background-agent/subagent-spawn-limits.ts index c53a0e358..9483f3247 100644 --- a/src/features/background-agent/subagent-spawn-limits.ts +++ b/src/features/background-agent/subagent-spawn-limits.ts @@ -2,7 +2,6 @@ import type { BackgroundTaskConfig } from "../../config/schema" import type { OpencodeClient } from "./constants" export const DEFAULT_MAX_SUBAGENT_DEPTH = 3 -export const DEFAULT_MAX_ROOT_SESSION_SPAWN_BUDGET = 50 export interface SubagentSpawnContext { rootSessionID: string @@ -14,10 +13,6 @@ export function getMaxSubagentDepth(config?: BackgroundTaskConfig): number { return config?.maxDepth ?? DEFAULT_MAX_SUBAGENT_DEPTH } -export function getMaxRootSessionSpawnBudget(config?: BackgroundTaskConfig): number { - return config?.maxDescendants ?? DEFAULT_MAX_ROOT_SESSION_SPAWN_BUDGET -} - export async function resolveSubagentSpawnContext( client: OpencodeClient, parentSessionID: string, @@ -53,7 +48,7 @@ export async function resolveSubagentSpawnContext( } catch (error) { const reason = error instanceof Error ? error.message : String(error) throw new Error( - `Subagent spawn blocked: failed to resolve session lineage for ${parentSessionID}, so background_task.maxDescendants cannot be enforced safely. ${reason}` + `Subagent spawn blocked: failed to resolve session lineage for ${parentSessionID}, so background_task.maxDepth cannot be enforced safely. ${reason}` ) } @@ -84,14 +79,3 @@ export function createSubagentDepthLimitError(input: { `Subagent spawn blocked: child depth ${childDepth} exceeds background_task.maxDepth=${maxDepth}. Parent session: ${parentSessionID}. Root session: ${rootSessionID}. Continue in an existing subagent session instead of spawning another.` ) } - -export function createSubagentDescendantLimitError(input: { - rootSessionID: string - descendantCount: number - maxDescendants: number -}): Error { - const { rootSessionID, descendantCount, maxDescendants } = input - return new Error( - `Subagent spawn blocked: root session ${rootSessionID} already has ${descendantCount} descendants, which meets background_task.maxDescendants=${maxDescendants}. Reuse an existing session instead of spawning another.` - ) -} diff --git a/src/features/background-agent/task-poller.test.ts b/src/features/background-agent/task-poller.test.ts index 1811051c5..532fd6e57 100644 --- a/src/features/background-agent/task-poller.test.ts +++ b/src/features/background-agent/task-poller.test.ts @@ -648,7 +648,7 @@ describe("checkAndInterruptStaleTasks", () => { const task = createRunningTask({ startedAt: new Date(Date.now() - 15 * 60 * 1000), progress: undefined, - concurrencyKey: "anthropic/claude-opus-4-6", + concurrencyKey: "anthropic/claude-opus-4-7", }) //#when @@ -661,7 +661,7 @@ describe("checkAndInterruptStaleTasks", () => { }) //#then - expect(releaseMock).toHaveBeenCalledWith("anthropic/claude-opus-4-6") + expect(releaseMock).toHaveBeenCalledWith("anthropic/claude-opus-4-7") expect(task.concurrencyKey).toBeUndefined() }) diff --git a/src/features/background-agent/wait-for-task-session.test.ts b/src/features/background-agent/wait-for-task-session.test.ts new file mode 100644 index 000000000..812d9f700 --- /dev/null +++ b/src/features/background-agent/wait-for-task-session.test.ts @@ -0,0 +1,95 @@ +import { describe, expect, test } from "bun:test" + +import type { BackgroundTaskStatus } from "./types" +import { waitForTaskSessionID } from "./wait-for-task-session" + +interface TaskSnapshot { + sessionID?: string + status?: BackgroundTaskStatus +} + +function createManager(responses: TaskSnapshot[]) { + let index = 0 + + return { + getTask(_taskID: string): TaskSnapshot { + const response = responses[Math.min(index, responses.length - 1)] + index += 1 + return response + }, + } +} + +describe("waitForTaskSessionID", () => { + test("#given task already has a session id #when waiting #then it returns immediately", async () => { + // given + const manager = createManager([{ sessionID: "ses_ready_123", status: "running" }]) + + // when + const sessionID = await waitForTaskSessionID(manager, "bg_ready") + + // then + expect(sessionID).toBe("ses_ready_123") + }) + + test("#given session appears later #when waiting #then it polls until resolved", async () => { + // given + const manager = createManager([ + { status: "running" }, + { status: "running" }, + { sessionID: "ses_late_123", status: "running" }, + ]) + + // when + const sessionID = await waitForTaskSessionID(manager, "bg_late", { + intervalMs: 1, + timeoutMs: 20, + }) + + // then + expect(sessionID).toBe("ses_late_123") + }) + + test("#given aborted signal #when waiting #then it returns undefined", async () => { + // given + const controller = new AbortController() + controller.abort() + const manager = createManager([{ status: "running" }]) + + // when + const sessionID = await waitForTaskSessionID(manager, "bg_abort", { + signal: controller.signal, + }) + + // then + expect(sessionID).toBeUndefined() + }) + + test("#given task never resolves #when waiting past timeout #then it returns undefined", async () => { + // given + const manager = createManager([{ status: "running" }, { status: "running" }, { status: "running" }]) + + // when + const sessionID = await waitForTaskSessionID(manager, "bg_timeout", { + intervalMs: 1, + timeoutMs: 3, + }) + + // then + expect(sessionID).toBeUndefined() + }) + + test.each(["error", "cancelled", "interrupt"] satisfies BackgroundTaskStatus[])( + "#given %s task state #when waiting #then it returns undefined", + async (status: BackgroundTaskStatus) => { + // given + const manager = createManager([{ status }]) + + // when + const sessionID = await waitForTaskSessionID(manager, `bg_${status}`) + + // then + expect(sessionID).toBeUndefined() + } + ) +}) diff --git a/src/features/background-agent/wait-for-task-session.ts b/src/features/background-agent/wait-for-task-session.ts new file mode 100644 index 000000000..eb5fe49d8 --- /dev/null +++ b/src/features/background-agent/wait-for-task-session.ts @@ -0,0 +1,68 @@ +import { getTimingConfig } from "../../tools/delegate-task/timing" +import type { BackgroundTaskStatus } from "./types" + +type SessionWaitTerminalStatus = Extract +type AbortSignalLike = { aborted: boolean } + +interface TaskReader { + getTask(taskID: string): { sessionID?: string; status?: BackgroundTaskStatus } | undefined +} + +export interface WaitForTaskSessionIDOptions { + timeoutMs?: number + intervalMs?: number + signal?: AbortSignalLike +} + +function isTerminalStatus(status: BackgroundTaskStatus | undefined): status is SessionWaitTerminalStatus { + return status === "error" || status === "cancelled" || status === "interrupt" +} + +function waitForInterval(intervalMs: number): Promise { + return new Promise(resolve => { + const scheduler = globalThis as { setTimeout: (handler: () => void, timeout?: number) => unknown } + scheduler.setTimeout(resolve, intervalMs) + }) +} + +export async function waitForTaskSessionID( + manager: TaskReader, + taskID: string, + options: WaitForTaskSessionIDOptions = {} +): Promise { + const timing = getTimingConfig() + const timeoutMs = options.timeoutMs ?? timing.WAIT_FOR_SESSION_TIMEOUT_MS + const intervalMs = options.intervalMs ?? timing.WAIT_FOR_SESSION_INTERVAL_MS + + if (options.signal?.aborted) { + return undefined + } + + const initialTask = manager.getTask(taskID) + if (initialTask?.sessionID) { + return initialTask.sessionID + } + if (isTerminalStatus(initialTask?.status)) { + return undefined + } + + const deadline = Date.now() + timeoutMs + + while (Date.now() < deadline) { + if (options.signal?.aborted) { + return undefined + } + + await waitForInterval(intervalMs) + + const task = manager.getTask(taskID) + if (task?.sessionID) { + return task.sessionID + } + if (isTerminalStatus(task?.status)) { + return undefined + } + } + + return undefined +} diff --git a/src/features/claude-code-agent-loader/agent-definitions-loader.test.ts b/src/features/claude-code-agent-loader/agent-definitions-loader.test.ts new file mode 100644 index 000000000..76960b93a --- /dev/null +++ b/src/features/claude-code-agent-loader/agent-definitions-loader.test.ts @@ -0,0 +1,293 @@ +import { describe, test, expect, beforeEach, afterEach } from "bun:test" +import { mkdtempSync, writeFileSync, rmSync } from "fs" +import { join } from "path" +import { tmpdir } from "os" + +import { loadAgentDefinitions, parseMarkdownAgentFile } from "./agent-definitions-loader" + +describe("agent-definitions-loader", () => { + let tempDir: string + + beforeEach(() => { + tempDir = mkdtempSync(join(tmpdir(), "agent-definitions-test-")) + }) + + afterEach(() => { + rmSync(tempDir, { recursive: true, force: true }) + }) + + describe("#parseMarkdownAgentFile", () => { + test("parses valid markdown agent file", () => { + const filePath = join(tempDir, "test-agent.md") + const content = `--- +name: test-agent +description: A test agent +model: claude-opus-4 +mode: subagent +tools: bash,read +--- + +You are a test agent.` + + writeFileSync(filePath, content, "utf-8") + + const result = parseMarkdownAgentFile(filePath, "definition-file") + + expect(result).not.toBeNull() + expect(result?.name).toBe("test-agent") + expect(result?.config.description).toBe("(definition-file) A test agent") + expect(result?.config.mode).toBe("subagent") + expect(result?.config.prompt).toBe("You are a test agent.") + expect(result?.config.tools).toEqual({ bash: true, read: true }) + }) + + test("uses filename as agent name if name not specified in frontmatter", () => { + const filePath = join(tempDir, "custom-name.md") + const content = `--- +description: No name specified +--- + +Prompt content.` + + writeFileSync(filePath, content, "utf-8") + + const result = parseMarkdownAgentFile(filePath, "definition-file") + + expect(result).not.toBeNull() + expect(result?.name).toBe("custom-name") + }) + + test("returns null for missing file", () => { + const filePath = join(tempDir, "missing.md") + const result = parseMarkdownAgentFile(filePath, "definition-file") + + expect(result).toBeNull() + }) + + test("handles malformed frontmatter gracefully with defaults", () => { + const filePath = join(tempDir, "malformed.md") + const content = `--- +invalid: yaml: content: here +--- + +Prompt.` + + writeFileSync(filePath, content, "utf-8") + + const result = parseMarkdownAgentFile(filePath, "definition-file") + + expect(result).not.toBeNull() + expect(result?.name).toBe("malformed") + expect(result?.config.mode).toBe("subagent") + expect(result?.config.prompt).toBe("Prompt.") + }) + + test("strips .MD extension case-insensitively for agent name", () => { + const filePath = join(tempDir, "UpperCase.MD") + const content = `--- +description: Mixed case extension +--- + +Prompt content.` + + writeFileSync(filePath, content, "utf-8") + + const result = parseMarkdownAgentFile(filePath, "definition-file") + + expect(result).not.toBeNull() + expect(result?.name).toBe("UpperCase") + }) + + test("defaults mode to subagent when not specified", () => { + const filePath = join(tempDir, "no-mode.md") + const content = `--- +name: no-mode-agent +--- + +Prompt.` + + writeFileSync(filePath, content, "utf-8") + + const result = parseMarkdownAgentFile(filePath, "definition-file") + + expect(result).not.toBeNull() + expect(result?.config.mode).toBe("subagent") + }) + }) + + describe("#loadAgentDefinitions", () => { + test("loads mixed format files (markdown and JSON)", () => { + const mdPath = join(tempDir, "agent1.md") + const jsonPath = join(tempDir, "agent2.json") + + writeFileSync( + mdPath, + `--- +name: md-agent +--- + +Markdown agent prompt.`, + "utf-8" + ) + + writeFileSync( + jsonPath, + JSON.stringify({ + name: "json-agent", + prompt: "JSON agent prompt.", + }), + "utf-8" + ) + + const result = loadAgentDefinitions([mdPath, jsonPath], "definition-file") + + expect(Object.keys(result)).toHaveLength(2) + expect(result["md-agent"]).toBeDefined() + expect(result["json-agent"]).toBeDefined() + expect(result["md-agent"].prompt).toBe("Markdown agent prompt.") + expect(result["json-agent"].prompt).toBe("JSON agent prompt.") + }) + + test("silently skips missing files with warning log", () => { + const validPath = join(tempDir, "valid.md") + const missingPath = join(tempDir, "missing.md") + + writeFileSync( + validPath, + `--- +name: valid-agent +--- + +Valid prompt.`, + "utf-8" + ) + + const result = loadAgentDefinitions([validPath, missingPath], "definition-file") + + expect(Object.keys(result)).toHaveLength(1) + expect(result["valid-agent"]).toBeDefined() + }) + + test("silently skips malformed files with warning log", () => { + const validPath = join(tempDir, "valid.jsonc") + const malformedPath = join(tempDir, "malformed.json") + + writeFileSync( + validPath, + JSON.stringify({ + name: "valid-agent", + prompt: "Valid prompt.", + }), + "utf-8" + ) + + writeFileSync(malformedPath, "{ invalid json", "utf-8") + + const result = loadAgentDefinitions([validPath, malformedPath], "definition-file") + + expect(Object.keys(result)).toHaveLength(1) + expect(result["valid-agent"]).toBeDefined() + }) + + test("last-write-wins for duplicate agent names", () => { + const path1 = join(tempDir, "agent-v1.md") + const path2 = join(tempDir, "agent-v2.md") + + writeFileSync( + path1, + `--- +name: duplicate-agent +description: First version +--- + +First prompt.`, + "utf-8" + ) + + writeFileSync( + path2, + `--- +name: duplicate-agent +description: Second version +--- + +Second prompt.`, + "utf-8" + ) + + const result = loadAgentDefinitions([path1, path2], "definition-file") + + expect(Object.keys(result)).toHaveLength(1) + expect(result["duplicate-agent"].description).toBe("(definition-file) Second version") + expect(result["duplicate-agent"].prompt).toBe("Second prompt.") + }) + + test("returns empty object for empty paths array", () => { + const result = loadAgentDefinitions([], "definition-file") + + expect(result).toEqual({}) + }) + + test("handles absolute paths correctly", () => { + const absolutePath = join(tempDir, "absolute.md") + + writeFileSync( + absolutePath, + `--- +name: absolute-agent +--- + +Absolute path prompt.`, + "utf-8" + ) + + const result = loadAgentDefinitions([absolutePath], "definition-file") + + expect(Object.keys(result)).toHaveLength(1) + expect(result["absolute-agent"]).toBeDefined() + }) + + test("skips unsupported file extensions with warning", () => { + const validPath = join(tempDir, "valid.md") + const unsupportedPath = join(tempDir, "unsupported.txt") + + writeFileSync( + validPath, + `--- +name: valid-agent +--- + +Valid prompt.`, + "utf-8" + ) + + writeFileSync(unsupportedPath, "Some text file content.", "utf-8") + + const result = loadAgentDefinitions([validPath, unsupportedPath], "definition-file") + + expect(Object.keys(result)).toHaveLength(1) + expect(result["valid-agent"]).toBeDefined() + }) + + test("supports JSONC format with comments", () => { + const jsoncPath = join(tempDir, "agent.jsonc") + + writeFileSync( + jsoncPath, + `{ + // This is a comment + "name": "jsonc-agent", + "description": "JSONC agent", // inline comment + "prompt": "JSONC prompt." +}`, + "utf-8" + ) + + const result = loadAgentDefinitions([jsoncPath], "definition-file") + + expect(Object.keys(result)).toHaveLength(1) + expect(result["jsonc-agent"]).toBeDefined() + expect(result["jsonc-agent"].prompt).toBe("JSONC prompt.") + }) + }) +}) diff --git a/src/features/claude-code-agent-loader/agent-definitions-loader.ts b/src/features/claude-code-agent-loader/agent-definitions-loader.ts new file mode 100644 index 000000000..383f69387 --- /dev/null +++ b/src/features/claude-code-agent-loader/agent-definitions-loader.ts @@ -0,0 +1,87 @@ +import { existsSync, readFileSync } from "fs" +import { basename, extname } from "path" +import { parseFrontmatter } from "../../shared/frontmatter" +import { log } from "../../shared/logger" +import { parseToolsConfig } from "../../shared/parse-tools-config" +import { parseJsonAgentFile } from "./json-agent-loader" +import { mapClaudeModelToOpenCode } from "./claude-model-mapper" +import type { AgentScope, AgentFrontmatter, ClaudeCodeAgentConfig, LoadedAgent } from "./types" + +export function parseMarkdownAgentFile(filePath: string, scope: AgentScope): LoadedAgent | null { + try { + if (!existsSync(filePath)) { + return null + } + + const content = readFileSync(filePath, "utf-8") + const { data, body } = parseFrontmatter(content) + + const fileName = basename(filePath) + const agentName = fileName.replace(/\.md$/i, "") + const name = data.name || agentName + const originalDescription = data.description || "" + + const formattedDescription = `(${scope}) ${originalDescription}` + + const mappedModelOverride = mapClaudeModelToOpenCode(data.model) + const modelString = mappedModelOverride + ? `${mappedModelOverride.providerID}/${mappedModelOverride.modelID}` + : undefined + + const config: ClaudeCodeAgentConfig = { + description: formattedDescription, + mode: data.mode || "subagent", + prompt: body.trim(), + ...(modelString ? { model: modelString } : {}), + } + + const toolsConfig = parseToolsConfig(data.tools) + if (toolsConfig) { + config.tools = toolsConfig + } + + return { + name, + path: filePath, + config, + scope, + } + } catch { + return null + } +} + +export function loadAgentDefinitions( + paths: string[], + scope: AgentScope +): Record { + const result: Record = Object.create(null) + + for (const filePath of paths) { + if (!existsSync(filePath)) { + log(`[agent-definitions-loader] File not found, skipping: ${filePath}`) + continue + } + + const ext = extname(filePath).toLowerCase() + let agent: LoadedAgent | null = null + + if (ext === ".md") { + agent = parseMarkdownAgentFile(filePath, scope) + } else if (ext === ".json" || ext === ".jsonc") { + agent = parseJsonAgentFile(filePath, scope) + } else { + log(`[agent-definitions-loader] Unsupported file extension: ${ext} for ${filePath}`) + continue + } + + if (!agent) { + log(`[agent-definitions-loader] Failed to parse agent file: ${filePath}`) + continue + } + + result[agent.name] = agent.config + } + + return result +} diff --git a/src/features/claude-code-agent-loader/claude-model-mapper.test.ts b/src/features/claude-code-agent-loader/claude-model-mapper.test.ts index b5f1983a9..97693761f 100644 --- a/src/features/claude-code-agent-loader/claude-model-mapper.test.ts +++ b/src/features/claude-code-agent-loader/claude-model-mapper.test.ts @@ -23,8 +23,8 @@ describe("mapClaudeModelToOpenCode", () => { 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", () => { - expect(mapClaudeModelToOpenCode("opus")).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" }) + it("#when called with opus #then maps to anthropic claude-opus-4-7 object", () => { + 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", () => { @@ -47,8 +47,8 @@ describe("mapClaudeModelToOpenCode", () => { 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", () => { - expect(mapClaudeModelToOpenCode("claude-opus-4-6")).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" }) + it("#when called with claude-opus-4-7 #then adds anthropic object format", () => { + 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", () => { diff --git a/src/features/claude-code-agent-loader/claude-model-mapper.ts b/src/features/claude-code-agent-loader/claude-model-mapper.ts index 736a5f303..7737de5f7 100644 --- a/src/features/claude-code-agent-loader/claude-model-mapper.ts +++ b/src/features/claude-code-agent-loader/claude-model-mapper.ts @@ -5,7 +5,7 @@ const ANTHROPIC_PREFIX = "anthropic/" const CLAUDE_CODE_ALIAS_MAP = new Map([ ["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`], ]) diff --git a/src/features/claude-code-agent-loader/index.ts b/src/features/claude-code-agent-loader/index.ts index 644158c42..670e416a4 100644 --- a/src/features/claude-code-agent-loader/index.ts +++ b/src/features/claude-code-agent-loader/index.ts @@ -1,2 +1,5 @@ export * from "./types" export * from "./loader" +export * from "./agent-definitions-loader" +export * from "./opencode-config-agents-reader" +export * from "./json-agent-loader" diff --git a/src/features/claude-code-agent-loader/json-agent-loader.test.ts b/src/features/claude-code-agent-loader/json-agent-loader.test.ts new file mode 100644 index 000000000..b3f6a4f5c --- /dev/null +++ b/src/features/claude-code-agent-loader/json-agent-loader.test.ts @@ -0,0 +1,139 @@ +import { describe, test, expect, afterEach } from "bun:test" +import { mkdtempSync, writeFileSync, rmSync } from "fs" +import { join } from "path" +import { tmpdir } from "os" +import { parseJsonAgentFile } from "./json-agent-loader" + +describe("json-agent-loader", () => { + const dirs: string[] = [] + + afterEach(() => { + for (const dir of dirs) { + rmSync(dir, { recursive: true, force: true }) + } + dirs.length = 0 + }) + + function trackDir(dir: string): string { + dirs.push(dir) + return dir + } + + test("parses valid JSON agent file", () => { + const dir = trackDir(mkdtempSync(join(tmpdir(), "json-agent-loader-test-"))) + const filePath = join(dir, "agent.json") + + writeFileSync(filePath, JSON.stringify({ + name: "test-agent", + description: "A test agent", + prompt: "You are a test agent.", + tools: ["Bash", "Read"], + model: "claude-3-5-sonnet-20241022", + mode: "subagent", + }), "utf-8") + + const result = parseJsonAgentFile(filePath, "definition-file") + + expect(result).not.toBeNull() + expect(result?.name).toBe("test-agent") + expect(result?.path).toBe(filePath) + expect(result?.scope).toBe("definition-file") + expect(result?.config.description).toBe("(definition-file) A test agent") + expect(result?.config.prompt).toBe("You are a test agent.") + expect(result?.config.mode).toBe("subagent") + expect(result?.config.tools).toEqual({ bash: true, read: true }) + }) + + test("parses JSONC with comments", () => { + const dir = trackDir(mkdtempSync(join(tmpdir(), "json-agent-loader-test-"))) + const filePath = join(dir, "agent.jsonc") + + writeFileSync(filePath, `{ + // Agent name + "name": "commented-agent", + "description": "Agent with comments", + "prompt": "Do something.", + "tools": ["Bash"], // Tools for the agent + // Model specification + "model": "claude-3-5-sonnet-20241022" +}`, "utf-8") + + const result = parseJsonAgentFile(filePath, "definition-file") + + expect(result).not.toBeNull() + expect(result?.name).toBe("commented-agent") + expect(result?.config.tools).toEqual({ bash: true }) + }) + + test("returns null when required fields are missing (name)", () => { + const dir = trackDir(mkdtempSync(join(tmpdir(), "json-agent-loader-test-"))) + const filePath = join(dir, "agent.json") + + writeFileSync(filePath, JSON.stringify({ + description: "Missing name", + prompt: "You are an agent.", + }), "utf-8") + + const result = parseJsonAgentFile(filePath, "definition-file") + expect(result).toBeNull() + }) + + test("returns null when required fields are missing (prompt)", () => { + const dir = trackDir(mkdtempSync(join(tmpdir(), "json-agent-loader-test-"))) + const filePath = join(dir, "agent.json") + + writeFileSync(filePath, JSON.stringify({ + name: "missing-prompt", + description: "Missing prompt", + }), "utf-8") + + const result = parseJsonAgentFile(filePath, "definition-file") + expect(result).toBeNull() + }) + + test("defaults optional fields correctly", () => { + const dir = trackDir(mkdtempSync(join(tmpdir(), "json-agent-loader-test-"))) + const filePath = join(dir, "agent.json") + + writeFileSync(filePath, JSON.stringify({ + name: "minimal-agent", + prompt: "You are minimal.", + }), "utf-8") + + const result = parseJsonAgentFile(filePath, "definition-file") + + expect(result).not.toBeNull() + expect(result?.config.description).toBe("(definition-file) ") + expect(result?.config.mode).toBe("subagent") + expect(result?.config.tools).toBeUndefined() + expect(result?.config.model).toBeUndefined() + }) + + test("handles tools as string comma-separated list", () => { + const dir = trackDir(mkdtempSync(join(tmpdir(), "json-agent-loader-test-"))) + const filePath = join(dir, "agent.json") + + writeFileSync(filePath, JSON.stringify({ + name: "string-tools-agent", + prompt: "You are an agent.", + tools: "Bash, Read, Grep", + }), "utf-8") + + const result = parseJsonAgentFile(filePath, "definition-file") + + expect(result?.config.tools).toEqual({ bash: true, read: true, grep: true }) + }) + + test("returns null for malformed JSON", () => { + const dir = trackDir(mkdtempSync(join(tmpdir(), "json-agent-loader-test-"))) + const filePath = join(dir, "agent.json") + + writeFileSync(filePath, `{ + "name": "broken", + "prompt": "incomplete json`, + "utf-8") + + const result = parseJsonAgentFile(filePath, "definition-file") + expect(result).toBeNull() + }) +}) diff --git a/src/features/claude-code-agent-loader/json-agent-loader.ts b/src/features/claude-code-agent-loader/json-agent-loader.ts new file mode 100644 index 000000000..fa135fa6f --- /dev/null +++ b/src/features/claude-code-agent-loader/json-agent-loader.ts @@ -0,0 +1,53 @@ +import { existsSync, readFileSync } from "fs" +import { parseJsoncSafe } from "../../shared/jsonc-parser" +import { parseToolsConfig } from "../../shared/parse-tools-config" +import { mapClaudeModelToOpenCode } from "./claude-model-mapper" +import type { AgentScope, AgentJsonDefinition, ClaudeCodeAgentConfig, LoadedAgent } from "./types" + +export function parseJsonAgentFile(filePath: string, scope: AgentScope): LoadedAgent | null { + try { + if (!existsSync(filePath)) { + return null + } + + const content = readFileSync(filePath, "utf-8") + const { data } = parseJsoncSafe(content) + + if (!data) { + return null + } + + if (!data.name || !data.prompt) { + return null + } + + const originalDescription = data.description ?? "" + const formattedDescription = `(${scope}) ${originalDescription}` + + const mappedModelOverride = mapClaudeModelToOpenCode(data.model) + const modelString = mappedModelOverride + ? `${mappedModelOverride.providerID}/${mappedModelOverride.modelID}` + : undefined + + const config: ClaudeCodeAgentConfig = { + description: formattedDescription, + mode: data.mode ?? "subagent", + prompt: data.prompt.trim(), + ...(modelString ? { model: modelString } : {}), + } + + const toolsConfig = parseToolsConfig(data.tools) + if (toolsConfig) { + config.tools = toolsConfig + } + + return { + name: data.name, + path: filePath, + config, + scope, + } + } catch { + return null + } +} diff --git a/src/features/claude-code-agent-loader/loader.test.ts b/src/features/claude-code-agent-loader/loader.test.ts new file mode 100644 index 000000000..8a6a1cace --- /dev/null +++ b/src/features/claude-code-agent-loader/loader.test.ts @@ -0,0 +1,275 @@ +import { describe, test, expect, beforeEach, afterEach } from "bun:test"; +import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "fs"; +import { join } from "path"; +import { tmpdir } from "os"; + +import { + loadUserAgents, + loadProjectAgents, + loadOpencodeGlobalAgents, + loadOpencodeProjectAgents, +} from "./loader"; + +/** + * Creates a temporary directory tree for testing agent loading. + * Returns the root dir with `.claude/agents/` and `.opencode/agents/` subdirs + * pre-created, containing the specified agent files. + */ +function createProjectWithAgents( + agents: { + claudeAgents?: Array<{ filename: string; content: string }>; + opencodeAgents?: Array<{ filename: string; content: string }>; + } = {}, +): string { + const root = mkdtempSync(join(tmpdir(), "agent-loader-test-")); + if (agents.claudeAgents) { + const dir = join(root, ".claude", "agents"); + mkdirSync(dir, { recursive: true }); + for (const { filename, content } of agents.claudeAgents) { + writeFileSync(join(dir, filename), content, "utf-8"); + } + } + if (agents.opencodeAgents) { + const dir = join(root, ".opencode", "agents"); + mkdirSync(dir, { recursive: true }); + for (const { filename, content } of agents.opencodeAgents) { + writeFileSync(join(dir, filename), content, "utf-8"); + } + } + return root; +} + +const BASIC_AGENT = `--- +name: test-agent +description: A test agent +tools: Bash,Read +--- +You are a test agent.`; + +const MINIMAL_AGENT = `--- +description: Minimal agent +--- +Do minimal things.`; + +const NO_FRONTMATTER_AGENT = `Just a prompt with no frontmatter.`; + +describe("claude-code-agent-loader", () => { + const dirs: string[] = []; + + afterEach(() => { + for (const dir of dirs) { + rmSync(dir, { recursive: true, force: true }); + } + dirs.length = 0; + }); + + function trackDir(dir: string): string { + dirs.push(dir); + return dir; + } + + describe("loadProjectAgents", () => { + test("loads agents from /.claude/agents", () => { + const root = trackDir( + createProjectWithAgents({ + claudeAgents: [{ filename: "my-agent.md", content: BASIC_AGENT }], + }), + ); + + const result = loadProjectAgents(root); + + expect(Object.keys(result)).toEqual(["test-agent"]); + expect(result["test-agent"].description).toBe("(project) A test agent"); + expect(result["test-agent"].mode).toBe("subagent"); + expect(result["test-agent"].prompt).toBe("You are a test agent."); + expect(result["test-agent"].tools).toEqual({ bash: true, read: true }); + }); + + test("uses filename as agent name when frontmatter name is absent", () => { + const root = trackDir( + createProjectWithAgents({ + claudeAgents: [ + { filename: "fallback-name.md", content: MINIMAL_AGENT }, + ], + }), + ); + + const result = loadProjectAgents(root); + + expect(Object.keys(result)).toEqual(["fallback-name"]); + expect(result["fallback-name"].description).toBe( + "(project) Minimal agent", + ); + }); + + test("handles agent with no frontmatter", () => { + const root = trackDir( + createProjectWithAgents({ + claudeAgents: [{ filename: "raw.md", content: NO_FRONTMATTER_AGENT }], + }), + ); + + const result = loadProjectAgents(root); + + expect(Object.keys(result)).toEqual(["raw"]); + expect(result["raw"].prompt).toBe("Just a prompt with no frontmatter."); + }); + + test("returns empty object when project has no .claude/agents directory", () => { + const root = trackDir(mkdtempSync(join(tmpdir(), "agent-loader-test-"))); + + const result = loadProjectAgents(root); + + expect(result).toEqual({}); + }); + + test("ignores non-markdown files", () => { + const root = trackDir( + createProjectWithAgents({ + claudeAgents: [ + { filename: "good.md", content: BASIC_AGENT }, + { filename: "bad.txt", content: "not a markdown file" }, + { filename: "also-bad.json", content: "{}" }, + ], + }), + ); + + const result = loadProjectAgents(root); + + expect(Object.keys(result)).toEqual(["test-agent"]); + }); + + test("loads multiple agents", () => { + const root = trackDir( + createProjectWithAgents({ + claudeAgents: [ + { filename: "agent-a.md", content: BASIC_AGENT }, + { + filename: "agent-b.md", + content: `---\nname: second-agent\ndescription: Another agent\n---\nDo other things.`, + }, + ], + }), + ); + + const result = loadProjectAgents(root); + + expect(Object.keys(result).sort()).toEqual([ + "second-agent", + "test-agent", + ]); + }); + }); + + describe("loadOpencodeProjectAgents", () => { + test("loads agents from /.opencode/agents", () => { + const root = trackDir( + createProjectWithAgents({ + opencodeAgents: [{ filename: "oc-agent.md", content: BASIC_AGENT }], + }), + ); + + const result = loadOpencodeProjectAgents(root); + + expect(Object.keys(result)).toEqual(["test-agent"]); + expect(result["test-agent"].description).toBe( + "(opencode-project) A test agent", + ); + expect(result["test-agent"].mode).toBe("subagent"); + expect(result["test-agent"].prompt).toBe("You are a test agent."); + }); + + test("returns empty object when project has no .opencode/agents directory", () => { + const root = trackDir(mkdtempSync(join(tmpdir(), "agent-loader-test-"))); + + const result = loadOpencodeProjectAgents(root); + + expect(result).toEqual({}); + }); + }); + + describe("loadUserAgents", () => { + test("returns empty object when pointed at dir without agents/", () => { + const root = trackDir(mkdtempSync(join(tmpdir(), "agent-loader-test-"))) + // Temporarily set env var — best-effort in parallel test runner + const prev = process.env.CLAUDE_CONFIG_DIR + try { + process.env.CLAUDE_CONFIG_DIR = root + const result = loadUserAgents() + expect(result).toEqual({}) + } finally { + if (prev !== undefined) process.env.CLAUDE_CONFIG_DIR = prev + else delete process.env.CLAUDE_CONFIG_DIR + } + }) + }) + + describe("loadOpencodeGlobalAgents", () => { + test("returns empty object when pointed at dir without agents/", () => { + const root = trackDir(mkdtempSync(join(tmpdir(), "agent-loader-test-"))) + const prev = process.env.OPENCODE_CONFIG_DIR + try { + process.env.OPENCODE_CONFIG_DIR = root + const result = loadOpencodeGlobalAgents() + expect(result).toEqual({}) + } finally { + if (prev !== undefined) process.env.OPENCODE_CONFIG_DIR = prev + else delete process.env.OPENCODE_CONFIG_DIR + } + }) + }) + + describe("tools parsing", () => { + test("parses comma-separated tools into boolean record", () => { + const agentWithTools = `---\nname: tooled\ndescription: Has tools\ntools: Bash,Read,Edit\n---\nDo things.`; + const root = trackDir( + createProjectWithAgents({ + claudeAgents: [{ filename: "tooled.md", content: agentWithTools }], + }), + ); + + const result = loadProjectAgents(root); + + expect(result["tooled"].tools).toEqual({ + bash: true, + read: true, + edit: true, + }); + }); + + test("omits tools when frontmatter tools field is absent", () => { + const agentNoTools = `---\nname: no-tools\ndescription: No tools\n---\nDo things.`; + const root = trackDir( + createProjectWithAgents({ + claudeAgents: [{ filename: "no-tools.md", content: agentNoTools }], + }), + ); + + const result = loadProjectAgents(root); + + expect(result["no-tools"].tools).toBeUndefined(); + }); + }); + + describe("scope labeling", () => { + test("project and opencode-project loaders apply correct scope prefixes", () => { + const root = trackDir(mkdtempSync(join(tmpdir(), "agent-loader-scope-"))) + const content = `---\nname: scoped\ndescription: Scoped agent\n---\nPrompt.` + + const claudeProjectDir = join(root, "project", ".claude", "agents") + const ocProjectDir = join(root, "project", ".opencode", "agents") + + mkdirSync(claudeProjectDir, { recursive: true }) + mkdirSync(ocProjectDir, { recursive: true }) + + writeFileSync(join(claudeProjectDir, "a.md"), content, "utf-8") + writeFileSync(join(ocProjectDir, "a.md"), content, "utf-8") + + const project = loadProjectAgents(join(root, "project")) + const ocProject = loadOpencodeProjectAgents(join(root, "project")) + + expect(project["scoped"].description).toBe("(project) Scoped agent") + expect(ocProject["scoped"].description).toBe("(opencode-project) Scoped agent") + }) + }) +}); diff --git a/src/features/claude-code-agent-loader/loader.ts b/src/features/claude-code-agent-loader/loader.ts index d2ef89b03..380f479eb 100644 --- a/src/features/claude-code-agent-loader/loader.ts +++ b/src/features/claude-code-agent-loader/loader.ts @@ -1,23 +1,10 @@ -import { existsSync, readdirSync, readFileSync } from "fs" -import { join, basename } from "path" -import { parseFrontmatter } from "../../shared/frontmatter" +import { existsSync, readdirSync } from "fs" +import { join } from "path" import { isMarkdownFile } from "../../shared/file-utils" import { getClaudeConfigDir } from "../../shared" -import type { AgentScope, AgentFrontmatter, ClaudeCodeAgentConfig, LoadedAgent } from "./types" -import { mapClaudeModelToOpenCode } from "./claude-model-mapper" - -function parseToolsConfig(toolsStr?: string): Record | undefined { - if (!toolsStr) return undefined - - const tools = toolsStr.split(",").map((t) => t.trim()).filter(Boolean) - if (tools.length === 0) return undefined - - const result: Record = {} - for (const tool of tools) { - result[tool.toLowerCase()] = true - } - return result -} +import type { AgentScope, ClaudeCodeAgentConfig, LoadedAgent } from "./types" +import { getOpenCodeConfigDir } from "../../shared/opencode-config-dir" +import { parseMarkdownAgentFile } from "./agent-definitions-loader" function loadAgentsFromDir(agentsDir: string, scope: AgentScope): LoadedAgent[] { if (!existsSync(agentsDir)) { @@ -31,42 +18,10 @@ function loadAgentsFromDir(agentsDir: string, scope: AgentScope): LoadedAgent[] if (!isMarkdownFile(entry)) continue const agentPath = join(agentsDir, entry.name) - const agentName = basename(entry.name, ".md") + const agent = parseMarkdownAgentFile(agentPath, scope) - try { - const content = readFileSync(agentPath, "utf-8") - const { data, body } = parseFrontmatter(content) - - const name = data.name || agentName - const originalDescription = data.description || "" - - const formattedDescription = `(${scope}) ${originalDescription}` - - const mappedModelOverride = mapClaudeModelToOpenCode(data.model) - const modelString = mappedModelOverride - ? `${mappedModelOverride.providerID}/${mappedModelOverride.modelID}` - : undefined - - const config: ClaudeCodeAgentConfig = { - description: formattedDescription, - mode: data.mode || "subagent", - prompt: body.trim(), - ...(modelString ? { model: modelString } : {}), - } - - const toolsConfig = parseToolsConfig(data.tools) - if (toolsConfig) { - config.tools = toolsConfig - } - - agents.push({ - name, - path: agentPath, - config, - scope, - }) - } catch { - continue + if (agent) { + agents.push(agent) } } @@ -77,7 +32,7 @@ export function loadUserAgents(): Record { const userAgentsDir = join(getClaudeConfigDir(), "agents") const agents = loadAgentsFromDir(userAgentsDir, "user") - const result: Record = {} + const result: Record = Object.create(null) for (const agent of agents) { result[agent.name] = agent.config } @@ -88,7 +43,30 @@ export function loadProjectAgents(directory?: string): Record = {} + const result: Record = Object.create(null) + for (const agent of agents) { + result[agent.name] = agent.config + } + return result +} + +export function loadOpencodeGlobalAgents(): Record { + const configDir = getOpenCodeConfigDir({ binary: "opencode" }) + const opencodeAgentsDir = join(configDir, "agents") + const agents = loadAgentsFromDir(opencodeAgentsDir, "opencode") + + const result: Record = Object.create(null) + for (const agent of agents) { + result[agent.name] = agent.config + } + return result +} + +export function loadOpencodeProjectAgents(directory?: string): Record { + const opencodeProjectDir = join(directory ?? process.cwd(), ".opencode", "agents") + const agents = loadAgentsFromDir(opencodeProjectDir, "opencode-project") + + const result: Record = Object.create(null) for (const agent of agents) { result[agent.name] = agent.config } diff --git a/src/features/claude-code-agent-loader/opencode-config-agents-reader.test.ts b/src/features/claude-code-agent-loader/opencode-config-agents-reader.test.ts new file mode 100644 index 000000000..1e1fce79a --- /dev/null +++ b/src/features/claude-code-agent-loader/opencode-config-agents-reader.test.ts @@ -0,0 +1,342 @@ +import { describe, expect, it, beforeEach, afterEach, spyOn } from "bun:test" +import * as fs from "node:fs" +import * as os from "node:os" +import * as path from "node:path" + +import * as configDir from "../../shared/opencode-config-dir" +import { readOpencodeConfigAgents } from "./opencode-config-agents-reader" + +describe("readOpencodeConfigAgents", () => { + let mockGlobalConfigDir = "" + let configDirSpy: ReturnType + + beforeEach(() => { + mockGlobalConfigDir = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-mock-global-")) + configDirSpy = spyOn(configDir, "getOpenCodeConfigDir").mockReturnValue(mockGlobalConfigDir) + }) + + afterEach(() => { + configDirSpy.mockRestore() + fs.rmSync(mockGlobalConfigDir, { recursive: true, force: true }) + }) + + it("returns empty record when no opencode.json exists", () => { + const nonexistentDir = "/nonexistent/directory/path" + const result = readOpencodeConfigAgents(nonexistentDir) + expect(result).toEqual({}) + }) + + it("reads inline agents from opencode.json", () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-test-")) + const opencodeDir = path.join(tempDir, ".opencode") + fs.mkdirSync(opencodeDir, { recursive: true }) + + const configPath = path.join(opencodeDir, "opencode.json") + fs.writeFileSync( + configPath, + JSON.stringify({ + agents: { + "my-agent": { + description: "Custom agent", + model: "claude-opus-4-7", + mode: "subagent", + prompt: "You are a helpful assistant", + }, + }, + }) + ) + + const result = readOpencodeConfigAgents(tempDir) + + expect(result).toHaveProperty("my-agent") + expect(result["my-agent"].description).toBe("(opencode-config) Custom agent") + expect(result["my-agent"].mode).toBe("subagent") + expect(result["my-agent"].prompt).toBe("You are a helpful assistant") + expect(result["my-agent"].model).toBeDefined() + + fs.rmSync(tempDir, { recursive: true }) + }) + + it("reads agents from opencode.jsonc with comments", () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-test-")) + const opencodeDir = path.join(tempDir, ".opencode") + fs.mkdirSync(opencodeDir, { recursive: true }) + + const configPath = path.join(opencodeDir, "opencode.jsonc") + fs.writeFileSync( + configPath, + `{ + // Define agents + "agents": { + "test-agent": { + "description": "Test agent", + "prompt": "Test prompt" + } + } +} +` + ) + + const result = readOpencodeConfigAgents(tempDir) + + expect(Object.keys(result).length).toBeGreaterThan(0) + expect(result).toHaveProperty("test-agent") + expect(result["test-agent"].description).toBe("(opencode-config) Test agent") + expect(result["test-agent"].prompt).toBe("Test prompt") + + fs.rmSync(tempDir, { recursive: true }) + }) + + it("handles malformed opencode.json gracefully", () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-test-")) + const opencodeDir = path.join(tempDir, ".opencode") + fs.mkdirSync(opencodeDir, { recursive: true }) + + const configPath = path.join(opencodeDir, "opencode.json") + fs.writeFileSync(configPath, "{ invalid json ") + + const result = readOpencodeConfigAgents(tempDir) + expect(result).toEqual({}) + + fs.rmSync(tempDir, { recursive: true }) + }) + + it("maps Claude model names correctly", () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-test-")) + const opencodeDir = path.join(tempDir, ".opencode") + fs.mkdirSync(opencodeDir, { recursive: true }) + + const configPath = path.join(opencodeDir, "opencode.json") + fs.writeFileSync( + configPath, + JSON.stringify({ + agents: { + "sonnet-agent": { + description: "Sonnet", + model: "sonnet", + prompt: "test", + }, + "opus-agent": { + description: "Opus", + model: "opus", + prompt: "test", + }, + }, + }) + ) + + const result = readOpencodeConfigAgents(tempDir) + + expect(result["sonnet-agent"].model).toBeDefined() + expect(result["opus-agent"].model).toBeDefined() + + fs.rmSync(tempDir, { recursive: true }) + }) + + it("handles agent_definitions file paths", () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-test-")) + const opencodeDir = path.join(tempDir, ".opencode") + fs.mkdirSync(opencodeDir, { recursive: true }) + + const agentDefFile = path.join(opencodeDir, "agents.json") + fs.writeFileSync( + agentDefFile, + JSON.stringify({ + name: "definition-agent", + description: "From definition file", + prompt: "File-based agent prompt", + }) + ) + + const configPath = path.join(opencodeDir, "opencode.json") + fs.writeFileSync( + configPath, + JSON.stringify({ + agent_definitions: ["./agents.json"], + }) + ) + + const result = readOpencodeConfigAgents(tempDir) + + if (Object.keys(result).length > 0) { + expect(result).toHaveProperty("definition-agent") + expect(result["definition-agent"].description).toContain("From definition file") + } + + fs.rmSync(tempDir, { recursive: true }) + }) + + it("merges inline and definition agents, with inline taking precedence", () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-test-")) + const opencodeDir = path.join(tempDir, ".opencode") + fs.mkdirSync(opencodeDir, { recursive: true }) + + const agentDefFile = path.join(opencodeDir, "agents.json") + fs.writeFileSync( + agentDefFile, + JSON.stringify({ + name: "shared-agent", + description: "From definition file", + prompt: "Definition prompt", + }) + ) + + const configPath = path.join(opencodeDir, "opencode.json") + fs.writeFileSync( + configPath, + JSON.stringify({ + agents: { + "shared-agent": { + description: "From inline", + prompt: "Inline prompt", + }, + }, + agent_definitions: ["./agents.json"], + }) + ) + + const result = readOpencodeConfigAgents(tempDir) + + expect(result["shared-agent"].description).toBe("(opencode-config) From inline") + expect(result["shared-agent"].prompt).toBe("Inline prompt") + + fs.rmSync(tempDir, { recursive: true }) + }) + + it("parses tools as both string and array formats", () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-test-")) + const opencodeDir = path.join(tempDir, ".opencode") + fs.mkdirSync(opencodeDir, { recursive: true }) + + const configPath = path.join(opencodeDir, "opencode.json") + fs.writeFileSync( + configPath, + JSON.stringify({ + agents: { + "string-tools": { + description: "Tools as string", + tools: "tool1, tool2, tool3", + prompt: "test", + }, + "array-tools": { + description: "Tools as array", + tools: ["bash", "read"], + prompt: "test", + }, + }, + }) + ) + + const result = readOpencodeConfigAgents(tempDir) + + expect(result["string-tools"].tools).toEqual({ + tool1: true, + tool2: true, + tool3: true, + }) + + expect(result["array-tools"].tools).toEqual({ + bash: true, + read: true, + }) + + fs.rmSync(tempDir, { recursive: true }) + }) + + it("supports agent key as fallback when agents key is not present", () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-test-")) + const opencodeDir = path.join(tempDir, ".opencode") + fs.mkdirSync(opencodeDir, { recursive: true }) + + const configPath = path.join(opencodeDir, "opencode.json") + fs.writeFileSync( + configPath, + JSON.stringify({ + agent: { + "fallback-agent": { + description: "Using agent key", + mode: "subagent", + prompt: "Fallback prompt", + }, + }, + }) + ) + + const result = readOpencodeConfigAgents(tempDir) + + expect(result).toHaveProperty("fallback-agent") + expect(result["fallback-agent"].description).toBe("(opencode-config) Using agent key") + expect(result["fallback-agent"].prompt).toBe("Fallback prompt") + + fs.rmSync(tempDir, { recursive: true }) + }) + + it("prioritizes project-level opencode.json over user-level", () => { + const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-project-")) + const projectOpencodeDir = path.join(projectDir, ".opencode") + fs.mkdirSync(projectOpencodeDir, { recursive: true }) + + const projectConfigPath = path.join(projectOpencodeDir, "opencode.json") + fs.writeFileSync( + projectConfigPath, + JSON.stringify({ + agents: { + "project-agent": { + description: "From project", + prompt: "Project prompt", + }, + }, + }) + ) + + const result = readOpencodeConfigAgents(projectDir) + + expect(result).toHaveProperty("project-agent") + expect(result["project-agent"].description).toBe("(opencode-config) From project") + + fs.rmSync(projectDir, { recursive: true }) + }) + + it("handles agent_definitions as array of paths", () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-test-")) + const opencodeDir = path.join(tempDir, ".opencode") + fs.mkdirSync(opencodeDir, { recursive: true }) + + const agentDef1 = path.join(opencodeDir, "agents1.json") + fs.writeFileSync( + agentDef1, + JSON.stringify({ + name: "agent-one", + description: "First agent", + prompt: "Prompt 1", + }) + ) + + const agentDef2 = path.join(opencodeDir, "agents2.json") + fs.writeFileSync( + agentDef2, + JSON.stringify({ + name: "agent-two", + description: "Second agent", + prompt: "Prompt 2", + }) + ) + + const configPath = path.join(opencodeDir, "opencode.json") + fs.writeFileSync( + configPath, + JSON.stringify({ + agent_definitions: ["./agents1.json", "./agents2.json"], + }) + ) + + const result = readOpencodeConfigAgents(tempDir) + + if (Object.keys(result).length >= 2) { + expect(result).toHaveProperty("agent-one") + expect(result).toHaveProperty("agent-two") + } + + fs.rmSync(tempDir, { recursive: true }) + }) +}) diff --git a/src/features/claude-code-agent-loader/opencode-config-agents-reader.ts b/src/features/claude-code-agent-loader/opencode-config-agents-reader.ts new file mode 100644 index 000000000..480fdbe68 --- /dev/null +++ b/src/features/claude-code-agent-loader/opencode-config-agents-reader.ts @@ -0,0 +1,125 @@ +import * as fs from "node:fs" +import * as path from "node:path" + +import { getOpenCodeConfigDir } from "../../shared/opencode-config-dir" +import { parseJsoncSafe } from "../../shared/jsonc-parser" +import { parseToolsConfig } from "../../shared/parse-tools-config" +import { resolveAgentDefinitionPaths } from "../../shared/resolve-agent-definition-paths" +import { loadAgentDefinitions } from "./agent-definitions-loader" +import { mapClaudeModelToOpenCode } from "./claude-model-mapper" +import type { ClaudeCodeAgentConfig } from "./types" + +interface OpencodeConfigWithAgents { + agents?: Record + agent?: Record + agent_definitions?: string | string[] +} + +function getConfigPaths(directory: string): string[] { + const globalConfigDir = getOpenCodeConfigDir({ binary: "opencode" }) + const paths = [ + path.join(directory, ".opencode", "opencode.json"), + path.join(directory, ".opencode", "opencode.jsonc"), + path.join(globalConfigDir, "opencode.json"), + path.join(globalConfigDir, "opencode.jsonc"), + ] + + return paths +} + +function convertInlineAgent(agentData: unknown): ClaudeCodeAgentConfig | null { + if (!agentData || typeof agentData !== "object") { + return null + } + + const agent = agentData as Record + + const description = agent.description ? `(opencode-config) ${String(agent.description)}` : "(opencode-config) " + + const mappedModel = mapClaudeModelToOpenCode( + agent.model ? String(agent.model) : undefined + ) + const modelString = mappedModel + ? `${mappedModel.providerID}/${mappedModel.modelID}` + : undefined + + const VALID_MODES = ["subagent", "primary", "all"] as const + const rawMode = typeof agent.mode === "string" ? agent.mode : undefined + const mode = rawMode && (VALID_MODES as readonly string[]).includes(rawMode) + ? (rawMode as "subagent" | "primary" | "all") + : "subagent" + + const config: ClaudeCodeAgentConfig = { + description, + mode, + prompt: agent.prompt ? String(agent.prompt) : "", + ...(modelString ? { model: modelString } : {}), + } + + const toolsConfig = parseToolsConfig(agent.tools) + if (toolsConfig) { + config.tools = toolsConfig + } + + return config +} + +export function readOpencodeConfigAgents(directory: string): Record { + const result: Record = Object.create(null) + + for (const configPath of getConfigPaths(directory)) { + try { + if (!fs.existsSync(configPath)) continue + + const content = fs.readFileSync(configPath, "utf-8") + const parseResult = parseJsoncSafe(content) + + if (!parseResult.data) continue + + const configDir = path.dirname(configPath) + + const agentsToLoad = parseResult.data.agents || parseResult.data.agent + + if (agentsToLoad && typeof agentsToLoad === "object") { + for (const [agentName, agentData] of Object.entries(agentsToLoad)) { + if (Object.hasOwn(result, agentName)) continue + const converted = convertInlineAgent(agentData) + if (converted) { + result[agentName] = converted + } + } + } + + if (parseResult.data.agent_definitions) { + const definitionPaths = extractDefinitionPaths(parseResult.data.agent_definitions) + const resolvedPaths = resolveAgentDefinitionPaths(definitionPaths, configDir, directory) + + const definitionAgents = loadAgentDefinitions(resolvedPaths, "opencode-config") + + for (const [name, config] of Object.entries(definitionAgents)) { + if (!Object.hasOwn(result, name)) { + result[name] = config + } + } + } + } catch { + continue + } + } + + return result +} + +function extractDefinitionPaths(definitionPaths: unknown): string[] { + if (typeof definitionPaths === "string") { + return [definitionPaths] + } + + if (Array.isArray(definitionPaths)) { + return definitionPaths + .filter((p) => typeof p === "string") + .map((p) => p as string) + } + + return [] +} diff --git a/src/features/claude-code-agent-loader/types.ts b/src/features/claude-code-agent-loader/types.ts index 7db570637..db5287243 100644 --- a/src/features/claude-code-agent-loader/types.ts +++ b/src/features/claude-code-agent-loader/types.ts @@ -1,6 +1,6 @@ import type { AgentConfig } from "@opencode-ai/sdk" -export type AgentScope = "user" | "project" +export type AgentScope = "user" | "project" | "opencode" | "opencode-project" | "definition-file" | "opencode-config" export type ClaudeCodeAgentConfig = Omit & { model?: string | { providerID: string; modelID: string } @@ -14,6 +14,15 @@ export interface AgentFrontmatter { mode?: "subagent" | "primary" | "all" } +export interface AgentJsonDefinition { + name: string + description?: string + model?: string + tools?: string | string[] + mode?: "subagent" | "primary" | "all" + prompt: string +} + export interface LoadedAgent { name: string path: string diff --git a/src/features/claude-code-command-loader/loader-cache.ts b/src/features/claude-code-command-loader/loader-cache.ts new file mode 100644 index 000000000..9f0d4d195 --- /dev/null +++ b/src/features/claude-code-command-loader/loader-cache.ts @@ -0,0 +1,37 @@ +import { promises as fs } from "fs" +import { resolve } from "path" + +import type { CommandDefinition } from "./types" + +const commandLoaderCache = new Map>>() + +export async function getCommandLoaderCacheKey(directory?: string): Promise { + const resolvedDirectory = resolve(directory ?? process.cwd()) + + try { + return await fs.realpath(resolvedDirectory) + } catch { + return resolvedDirectory + } +} + +export function getCachedCommands( + cacheKey: string, +): Promise> | undefined { + return commandLoaderCache.get(cacheKey) +} + +export function setCachedCommands( + cacheKey: string, + commands: Promise>, +): void { + commandLoaderCache.set(cacheKey, commands) +} + +export function deleteCachedCommands(cacheKey: string): void { + commandLoaderCache.delete(cacheKey) +} + +export function clearCommandLoaderCache(): void { + commandLoaderCache.clear() +} diff --git a/src/features/claude-code-command-loader/loader.test.ts b/src/features/claude-code-command-loader/loader.test.ts index be7928d3f..b674f8ff9 100644 --- a/src/features/claude-code-command-loader/loader.test.ts +++ b/src/features/claude-code-command-loader/loader.test.ts @@ -1,9 +1,10 @@ import { execFileSync } from "node:child_process" -import { afterEach, beforeEach, describe, expect, it } from "bun:test" +import { promises as fs } from "node:fs" +import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test" import { mkdirSync, rmSync, writeFileSync } from "node:fs" import { tmpdir } from "node:os" import { join } from "node:path" -import { loadOpencodeGlobalCommands, loadOpencodeProjectCommands } from "./loader" +import * as loader from "./loader" const TEST_DIR = join(tmpdir(), `claude-code-command-loader-${Date.now()}`) @@ -16,19 +17,41 @@ function writeCommand(directory: string, name: string, description: string): voi } describe("claude-code command loader", () => { + let originalClaudeConfigDir: string | undefined let originalOpencodeConfigDir: string | undefined beforeEach(() => { mkdirSync(TEST_DIR, { recursive: true }) + originalClaudeConfigDir = process.env.CLAUDE_CONFIG_DIR originalOpencodeConfigDir = process.env.OPENCODE_CONFIG_DIR + + const claudeConfigDir = join(TEST_DIR, "claude-config") + const opencodeConfigDir = join(TEST_DIR, "opencode-config") + process.env.CLAUDE_CONFIG_DIR = claudeConfigDir + process.env.OPENCODE_CONFIG_DIR = opencodeConfigDir + + if ("clearCommandLoaderCache" in loader && typeof loader.clearCommandLoaderCache === "function") { + loader.clearCommandLoaderCache() + } }) afterEach(() => { + if (originalClaudeConfigDir === undefined) { + delete process.env.CLAUDE_CONFIG_DIR + } else { + process.env.CLAUDE_CONFIG_DIR = originalClaudeConfigDir + } + if (originalOpencodeConfigDir === undefined) { delete process.env.OPENCODE_CONFIG_DIR } else { process.env.OPENCODE_CONFIG_DIR = originalOpencodeConfigDir } + + if ("clearCommandLoaderCache" in loader && typeof loader.clearCommandLoaderCache === "function") { + loader.clearCommandLoaderCache() + } + rmSync(TEST_DIR, { recursive: true, force: true }) }) @@ -39,7 +62,7 @@ describe("claude-code command loader", () => { writeCommand(join(projectDir, ".opencode", "commands"), "ancestor", "Ancestor command") // when - const commands = await loadOpencodeProjectCommands(childDir) + const commands = await loader.loadOpencodeProjectCommands(childDir) // then expect(commands.ancestor?.description).toBe("(opencode-project) Ancestor command") @@ -50,7 +73,7 @@ describe("claude-code command loader", () => { writeCommand(join(TEST_DIR, ".opencode", "command"), "singular", "Singular command") // when - const commands = await loadOpencodeProjectCommands(TEST_DIR) + const commands = await loader.loadOpencodeProjectCommands(TEST_DIR) // then expect(commands.singular?.description).toBe("(opencode-project) Singular command") @@ -66,7 +89,7 @@ describe("claude-code command loader", () => { writeCommand(projectDir, "duplicate", "Nearest command") // when - const commands = await loadOpencodeProjectCommands(childDir) + const commands = await loader.loadOpencodeProjectCommands(childDir) // then expect(commands.duplicate?.description).toBe("(opencode-project) Nearest command") @@ -79,7 +102,7 @@ describe("claude-code command loader", () => { writeCommand(join(opencodeConfigDir, "commands"), "global-plural", "Global plural command") // when - const commands = await loadOpencodeGlobalCommands() + const commands = await loader.loadOpencodeGlobalCommands() // then expect(commands["global-plural"]?.description).toBe("(opencode) Global plural command") @@ -94,7 +117,7 @@ describe("claude-code command loader", () => { writeCommand(join(profileConfigDir, "commands"), "duplicate-global", "Profile global command") // when - const commands = await loadOpencodeGlobalCommands() + const commands = await loader.loadOpencodeGlobalCommands() // then expect(commands["duplicate-global"]?.description).toBe("(opencode) Profile global command") @@ -114,7 +137,7 @@ describe("claude-code command loader", () => { writeCommand(join(TEST_DIR, ".opencode", "commands"), "outside", "Outside command") // when - const commands = await loadOpencodeProjectCommands(nestedDirectory) + const commands = await loader.loadOpencodeProjectCommands(nestedDirectory) // then expect(commands["deploy/staging"]?.description).toBe("(opencode-project) Deploy staging") @@ -122,4 +145,38 @@ describe("claude-code command loader", () => { expect(commands.outside).toBeUndefined() expect(commands["deploy:staging"]).toBeUndefined() }) + + it("#given commands nested under an excluded basename #when loadProjectCommands is called #then it skips the excluded directory contents", async () => { + // given + writeCommand(join(TEST_DIR, ".claude", "commands"), "real", "Real command") + writeCommand( + join(TEST_DIR, ".claude", "commands", "node_modules"), + "fake", + "Fake command", + ) + + // when + const commands = await loader.loadProjectCommands(TEST_DIR) + + // then + expect(commands.real?.description).toBe("(project) Real command") + expect(commands.fake).toBeUndefined() + }) + + it("#given a previously loaded directory #when loadAllCommands is called twice #then the second call reuses the cached result without readdir calls", async () => { + // given + writeCommand(join(TEST_DIR, ".claude", "commands"), "cached", "Cached command") + const readdirSpy = spyOn(fs, "readdir") + + // when + const firstCommands = await loader.loadAllCommands(TEST_DIR) + const firstReaddirCount = readdirSpy.mock.calls.length + const secondCommands = await loader.loadAllCommands(TEST_DIR) + + // then + expect(firstCommands.cached?.description).toBe("(project) Cached command") + expect(secondCommands).toEqual(firstCommands) + expect(firstReaddirCount).toBeGreaterThan(0) + expect(readdirSpy.mock.calls.length).toBe(firstReaddirCount) + }) }) diff --git a/src/features/claude-code-command-loader/loader.ts b/src/features/claude-code-command-loader/loader.ts index b052f56bd..6ee178b66 100644 --- a/src/features/claude-code-command-loader/loader.ts +++ b/src/features/claude-code-command-loader/loader.ts @@ -4,13 +4,23 @@ import { parseFrontmatter } from "../../shared/frontmatter" import { sanitizeModelField } from "../../shared/model-sanitizer" import { isMarkdownFile } from "../../shared/file-utils" import { + EXCLUDED_DIRS, findProjectOpencodeCommandDirs, getClaudeConfigDir, getOpenCodeCommandDirs, } from "../../shared" import { log } from "../../shared/logger" +import { + clearCommandLoaderCache, + deleteCachedCommands, + getCachedCommands, + getCommandLoaderCacheKey, + setCachedCommands, +} from "./loader-cache" import type { CommandScope, CommandDefinition, CommandFrontmatter, LoadedCommand } from "./types" +export { clearCommandLoaderCache } + async function loadCommandsFromDir( commandsDir: string, scope: CommandScope, @@ -48,6 +58,7 @@ async function loadCommandsFromDir( for (const entry of entries) { if (entry.isDirectory()) { + if (EXCLUDED_DIRS.has(entry.name)) continue if (entry.name.startsWith(".")) continue const subDirPath = join(commandsDir, entry.name) const subPrefix = prefix ? `${prefix}/${entry.name}` : entry.name @@ -159,11 +170,26 @@ export async function loadOpencodeProjectCommands(directory?: string): Promise> { - const [user, project, global, projectOpencode] = await Promise.all([ + const cacheKey = await getCommandLoaderCacheKey(directory) + const cachedCommands = getCachedCommands(cacheKey) + if (cachedCommands) { + return cachedCommands + } + + const loadCommandsPromise = Promise.all([ loadUserCommands(), loadProjectCommands(directory), loadOpencodeGlobalCommands(), loadOpencodeProjectCommands(directory), ]) - return { ...projectOpencode, ...global, ...project, ...user } + .then(([user, project, global, projectOpencode]) => { + return { ...projectOpencode, ...global, ...project, ...user } + }) + .catch((error) => { + deleteCachedCommands(cacheKey) + throw error + }) + + setCachedCommands(cacheKey, loadCommandsPromise) + return loadCommandsPromise } diff --git a/src/features/claude-code-mcp-loader/AGENTS.md b/src/features/claude-code-mcp-loader/AGENTS.md new file mode 100644 index 000000000..593ca22de --- /dev/null +++ b/src/features/claude-code-mcp-loader/AGENTS.md @@ -0,0 +1,78 @@ +# src/features/claude-code-mcp-loader/ — Tier 2 MCP Loader (.mcp.json) + +**Generated:** 2026-04-18 + +## OVERVIEW + +11 files. Loads `.mcp.json` files from project/user scopes and expands `${VAR}` env vars. Feeds Tier 2 of the 3-tier MCP system into `mcp-config-handler.ts` during Phase 5 of config loading. + +## WHY IT EXISTS + +Claude Code ecosystem ships MCPs via `.mcp.json` files with `${VAR}` env var placeholders. OmO consumes these unchanged so existing Claude Code MCP configs work. + +## LOAD PIPELINE + +``` +loadMcpConfigs(ctx) + → scope-filter.ts: discover .mcp.json at project + user scopes + → loader.ts: parse JSON + → env-expander.ts: replace ${VAR} with process.env[VAR] + → transformer.ts: map Claude Code format → OpenCode McpLocal / McpRemote shape + → return LoadedMcpServer[] +``` + +## MCP FORMAT + +```jsonc +// .mcp.json +{ + "mcpServers": { + "my-stdio": { + "type": "stdio", + "command": "node", + "args": ["server.js"], + "env": { + "API_KEY": "${MY_API_KEY}" + } + }, + "my-http": { + "type": "http", // "sse" legacy → mapped to http + "url": "https://example.com/mcp", + "headers": { + "Authorization": "Bearer ${MY_TOKEN}" + } + } + } +} +``` + +## KEY FILES + +| File | Purpose | +|------|---------| +| `index.ts` | Barrel: `loadMcpConfigs`, types | +| `loader.ts` | `loadMcpConfigs()` main entry | +| `types.ts` | `ClaudeCodeMcpServer`, `LoadedMcpServer`, `McpScope` | +| `env-expander.ts` | `expandEnvVarsInObject()` — recursive `${VAR}` substitution | +| `transformer.ts` | Claude Code format → OpenCode `Mcp` shape | +| `scope-filter.ts` | Project vs user scope precedence | + +## THREE-TIER MCP CONTEXT + +| Tier | Loader | Scope | +|------|--------|-------| +| 1. Built-in | `src/mcp/` `createBuiltinMcps()` | Global, 3 remote HTTP MCPs | +| 2. **Claude Code** | **This module** | **From `.mcp.json`, project + user** | +| 3. Skill-embedded | `src/features/skill-mcp-manager/` | Per-session, from SKILL.md YAML | + +## SECURITY + +- **Env var allowlist**: `mcp_env_allowlist` config restricts which env vars can be expanded +- **No shell execution**: `${VAR}` is string replacement only, not shell `$()` +- **Secrets redaction**: `env-cleaner.ts` (in skill-mcp-manager) filters known secret patterns from logs + +## RELATED + +- Phase 5 integration: `src/plugin-handlers/mcp-config-handler.ts` +- Skill-embedded MCPs (Tier 3): `src/features/skill-mcp-manager/` +- Built-in MCPs (Tier 1): `src/mcp/` diff --git a/src/features/claude-code-plugin-loader/AGENTS.md b/src/features/claude-code-plugin-loader/AGENTS.md new file mode 100644 index 000000000..ae6cd3158 --- /dev/null +++ b/src/features/claude-code-plugin-loader/AGENTS.md @@ -0,0 +1,78 @@ +# src/features/claude-code-plugin-loader/ — Unified Claude Code Plugin Loader + +**Generated:** 2026-04-18 + +## OVERVIEW + +16 files. Full Claude Code plugin compatibility layer. Discovers and loads ALL plugin components (commands, agents, skills, hooks, MCP servers, LSP servers) from `.opencode/plugins/` and `~/.claude/plugins/`. + +## WHY IT EXISTS + +Claude Code plugins ship commands/agents/skills as separate files with `plugin.json` manifest. OmO uses this loader to ingest them into its own registry so existing Claude Code plugins work unchanged under OmO. + +## LOAD PIPELINE + +``` +loadAllPluginComponents(ctx) + → discoverPlugins() # scan .opencode/plugins + ~/.claude/plugins + → readPluginManifest(plugin.json) # parse name/version/commands/agents/skills/hooks/mcpServers + → loadPluginCommands() + → loadPluginAgents() + → loadPluginSkills() + → loadPluginHooks() # register hook handlers + → loadPluginMcpServers() # feed into mcp-config-handler (tier 2) + → loadPluginLspServers() + → return LoadedPluginBundle +``` + +Called from `src/plugin-handlers/plugin-components-loader.ts` during Phase 2 of config handler (10s timeout with error isolation — one broken plugin does not sink the plugin load). + +## KEY FILES + +| File | Purpose | +|------|---------| +| `index.ts` | Barrel: `loadAllPluginComponents`, `PluginManifest`, `ClaudeSettings` types | +| `plugin-discovery.ts` | Find plugin directories across scopes | +| `plugin-manifest-parser.ts` | Parse `plugin.json` with Zod validation | +| `command-loader.ts` | Load commands from `commands/` or `COMMANDS.md` | +| `agent-loader.ts` | Load agents from `agents/` or `AGENTS.md` frontmatter | +| `skill-loader.ts` | Load skills from `skills/` or `SKILL.md` | +| `hook-loader.ts` | Load hooks config from `hooks/` or manifest | +| `mcp-loader.ts` | Extract MCP server configs | +| `lsp-loader.ts` | Extract LSP server configs | +| `settings-loader.ts` | Parse Claude Code `settings.json` | + +## PLUGIN MANIFEST (plugin.json) + +```jsonc +{ + "name": "my-plugin", + "version": "1.0.0", + "description": "...", + "commands": ["./commands"], // or string[] of paths + "agents": ["./agents"], + "skills": ["./skills"], + "hooks": "./hooks/config.json", + "mcpServers": "./.mcp.json", + "lspServers": "./lsp" +} +``` + +## SCOPES + +| Scope | Path | Priority | +|-------|------|----------| +| `project` | `.opencode/plugins/` | Highest | +| `local` | `~/.opencode/plugins/` | Medium | +| `user` | `~/.claude/plugins/` | Medium | +| `managed` | Built-in | Lowest | + +## ERROR ISOLATION + +Each plugin loads in isolation — if one fails (bad manifest, missing file, syntax error), others still load. Errors surface as warnings in `bunx oh-my-opencode doctor`. + +## RELATED + +- Phase 2 loader: `src/plugin-handlers/plugin-components-loader.ts` +- Tier 2 MCP integration: `src/features/claude-code-mcp-loader/` +- Claude Code compat hooks: `src/hooks/claude-code-hooks/` diff --git a/src/features/claude-code-plugin-loader/agent-loader.ts b/src/features/claude-code-plugin-loader/agent-loader.ts index 1aff57e0c..128b9f229 100644 --- a/src/features/claude-code-plugin-loader/agent-loader.ts +++ b/src/features/claude-code-plugin-loader/agent-loader.ts @@ -3,27 +3,11 @@ import { basename, join } from "path" import { parseFrontmatter } from "../../shared/frontmatter" import { isMarkdownFile } from "../../shared/file-utils" import { log } from "../../shared/logger" +import { parseToolsConfig } from "../../shared/parse-tools-config" import type { AgentFrontmatter, ClaudeCodeAgentConfig } from "../claude-code-agent-loader/types" import { mapClaudeModelToOpenCode } from "../claude-code-agent-loader/claude-model-mapper" import type { LoadedPlugin } from "./types" -function parseToolsConfig(toolsStr?: string): Record | undefined { - if (!toolsStr) return undefined - - const tools = toolsStr - .split(",") - .map((tool) => tool.trim()) - .filter(Boolean) - - if (tools.length === 0) return undefined - - const result: Record = {} - for (const tool of tools) { - result[tool.toLowerCase()] = true - } - return result -} - export function loadPluginAgents(plugins: LoadedPlugin[]): Record { const agents: Record = {} diff --git a/src/features/claude-code-session-state/state.test.ts b/src/features/claude-code-session-state/state.test.ts index 69c482b40..c6898adbf 100644 --- a/src/features/claude-code-session-state/state.test.ts +++ b/src/features/claude-code-session-state/state.test.ts @@ -150,6 +150,28 @@ describe("claude-code-session-state", () => { expect(resolveRegisteredAgentName("Atlas - Plan Executor")).toBe("\u200B\u200B\u200B\u200BAtlas - Plan Executor") }) + test("should resolve legacy parenthesized names to registered agent", () => { + // given - agent registered with new display name format + registerAgentName("\u200BSisyphus - Ultraworker") + + // when - historical session has old parenthesized format + const resolved = resolveRegisteredAgentName("Sisyphus (Ultraworker)") + + // then - resolves to registered name via config key lookup + expect(resolved).toBe("\u200BSisyphus - Ultraworker") + }) + + test("should resolve bare lowercase name from historical session", () => { + // given - agent registered with new display name + registerAgentName("Prometheus - Plan Builder") + + // when - old session stored just "prometheus" + const resolved = resolveRegisteredAgentName("prometheus") + + // then + expect(resolved).toBe("Prometheus - Plan Builder") + }) + describe("#given atlas display name with zero-width prefix", () => { describe("#when checking registration without the zero-width prefix", () => { test("#then it treats the display name as registered", () => { diff --git a/src/features/claude-code-session-state/state.ts b/src/features/claude-code-session-state/state.ts index 496d655fd..049366166 100644 --- a/src/features/claude-code-session-state/state.ts +++ b/src/features/claude-code-session-state/state.ts @@ -52,7 +52,19 @@ export function resolveRegisteredAgentName(name: string | undefined): string | u } const normalizedName = normalizeRegisteredAgentName(name) - return registeredAgentAliases.get(normalizedName) ?? normalizeStoredAgentName(name) + const directMatch = registeredAgentAliases.get(normalizedName) + if (directMatch !== undefined) return directMatch + + // Resolve legacy/capitalized agent names (e.g. "Sisyphus (Ultraworker)") + // to their config key, then look up the registered alias for that key. + const configKey = getAgentConfigKey(name) + const normalizedConfigKey = normalizeRegisteredAgentName(configKey) + if (normalizedConfigKey !== normalizedName) { + const aliasMatch = registeredAgentAliases.get(normalizedConfigKey) + if (aliasMatch !== undefined) return aliasMatch + } + + return normalizeStoredAgentName(name) } /** @internal For testing only */ diff --git a/src/features/opencode-skill-loader/async-loader.ts b/src/features/opencode-skill-loader/async-loader.ts index 141d5aa6f..ee866696b 100644 --- a/src/features/opencode-skill-loader/async-loader.ts +++ b/src/features/opencode-skill-loader/async-loader.ts @@ -87,7 +87,7 @@ export async function loadSkillFromPathAsync( const mcpJsonMcp = await loadMcpJsonFromDirAsync(resolvedPath) const mcpConfig = mcpJsonMcp || frontmatterMcp - const baseName = data.name || defaultName + const baseName = String(data.name || defaultName) const skillName = namePrefix ? `${namePrefix}/${baseName}` : baseName const originalDescription = data.description || "" const isOpencodeSource = scope === "opencode" || scope === "opencode-project" diff --git a/src/features/opencode-skill-loader/loaded-skill-from-path.ts b/src/features/opencode-skill-loader/loaded-skill-from-path.ts index c94855828..4097f6917 100644 --- a/src/features/opencode-skill-loader/loaded-skill-from-path.ts +++ b/src/features/opencode-skill-loader/loaded-skill-from-path.ts @@ -25,7 +25,7 @@ export async function loadSkillFromPath(options: { const mcpJsonMcp = await loadMcpJsonFromDir(options.resolvedPath) const mcpConfig = mcpJsonMcp || frontmatterMcp - const baseName = data.name || options.defaultName + const baseName = String(data.name || options.defaultName) const skillName = namePrefix ? `${namePrefix}/${baseName}` : baseName const originalDescription = data.description || "" const isOpencodeSource = options.scope === "opencode" || options.scope === "opencode-project" diff --git a/src/features/opencode-skill-loader/skill-content.test.ts b/src/features/opencode-skill-loader/skill-content.test.ts index 64d6d5bf4..dedf74413 100644 --- a/src/features/opencode-skill-loader/skill-content.test.ts +++ b/src/features/opencode-skill-loader/skill-content.test.ts @@ -3,12 +3,19 @@ import { describe, it, expect, beforeEach, afterEach } from "bun:test" import { join } from "node:path" import { tmpdir } from "node:os" -import { resolveSkillContent, resolveMultipleSkills, resolveSkillContentAsync, resolveMultipleSkillsAsync } from "./skill-content" +import { + clearSkillCache, + resolveSkillContent, + resolveMultipleSkills, + resolveSkillContentAsync, + resolveMultipleSkillsAsync, +} from "./skill-content" let originalEnv: Record let testConfigDir: string beforeEach(() => { + clearSkillCache() originalEnv = { CLAUDE_CONFIG_DIR: process.env.CLAUDE_CONFIG_DIR, OPENCODE_CONFIG_DIR: process.env.OPENCODE_CONFIG_DIR, @@ -20,6 +27,7 @@ beforeEach(() => { }) afterEach(() => { + clearSkillCache() for (const [key, value] of Object.entries(originalEnv)) { if (value !== undefined) { process.env[key] = value diff --git a/src/features/task-toast-manager/manager.test.ts b/src/features/task-toast-manager/manager.test.ts index d99698347..92ab524a0 100644 --- a/src/features/task-toast-manager/manager.test.ts +++ b/src/features/task-toast-manager/manager.test.ts @@ -203,7 +203,7 @@ describe("TaskToastManager", () => { description: "Task with inherited model", agent: "sisyphus-junior", 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 @@ -213,7 +213,7 @@ describe("TaskToastManager", () => { expect(mockClient.tui.showToast).toHaveBeenCalled() const call = mockClient.tui.showToast.mock.calls[0][0] 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)") }) diff --git a/src/features/team-mode/index.ts b/src/features/team-mode/index.ts new file mode 100644 index 000000000..6aa6742a6 --- /dev/null +++ b/src/features/team-mode/index.ts @@ -0,0 +1,2 @@ +export * from "./types" +export * from "./team-worktree" diff --git a/src/features/team-mode/team-layout-tmux/index.ts b/src/features/team-mode/team-layout-tmux/index.ts new file mode 100644 index 000000000..8858d5a4b --- /dev/null +++ b/src/features/team-mode/team-layout-tmux/index.ts @@ -0,0 +1 @@ +export { canVisualize, createTeamLayout, removeTeamLayout } from "./layout" diff --git a/src/features/team-mode/team-layout-tmux/layout.test.ts b/src/features/team-mode/team-layout-tmux/layout.test.ts new file mode 100644 index 000000000..aa9a90ff5 --- /dev/null +++ b/src/features/team-mode/team-layout-tmux/layout.test.ts @@ -0,0 +1,94 @@ +import { beforeEach, describe, expect, mock, test } from "bun:test" + +type LayoutModule = typeof import("./layout") + +const spawnMock = mock(() => ({ + exited: Promise.resolve(0), + stdout: new ReadableStream({ start(controller) { controller.enqueue(new TextEncoder().encode("%1\n")); controller.close() } }), + stderr: new ReadableStream({ start(controller) { controller.close() } }), +})) + +const layoutSpecifier = import.meta.resolve("./layout") +const spawnProcessSpecifier = import.meta.resolve("../../../shared/tmux/tmux-utils/spawn-process") +const tmuxPathResolverSpecifier = import.meta.resolve("../../../tools/interactive-bash/tmux-path-resolver") +const sharedSpecifier = import.meta.resolve("../../../shared") + +function registerModuleMocks(): void { + mock.module(spawnProcessSpecifier, () => ({ spawn: spawnMock })) + mock.module(tmuxPathResolverSpecifier, () => ({ getTmuxPath: mock(() => Promise.resolve("tmux")) })) + mock.module(sharedSpecifier, () => ({ log: mock(() => undefined) })) +} + +async function loadLayoutModule(): Promise { + const module = await import(`${layoutSpecifier}?test=${crypto.randomUUID()}`) + return module as LayoutModule +} + +describe("team-layout-tmux", () => { + beforeEach(() => { + registerModuleMocks() + spawnMock.mockClear() + process.env.TMUX = "/tmp/tmux-1" + }) + + test("returns null and makes no tmux calls when visualization unavailable", async () => { + // given + delete process.env.TMUX + const { createTeamLayout, canVisualize } = await loadLayoutModule() + + // when + const result = await createTeamLayout("run-1", [], {} as never) + + // then + expect(canVisualize()).toBe(false) + expect(result).toBeNull() + expect(spawnMock).toHaveBeenCalledTimes(0) + }) + + test("creates focus and grid windows", async () => { + // given + const { createTeamLayout } = await loadLayoutModule() + const members = [ + { name: "lead", sessionId: "s1", color: "red" }, + { name: "m2", sessionId: "s2" }, + { name: "m3", sessionId: "s3" }, + ] + + // when + await createTeamLayout("run-2", members, {} as never) + + // then + expect(spawnMock.mock.calls.flatMap((call) => call[0] as Array)).toContain("new-session") + expect(spawnMock.mock.calls.flatMap((call) => call[0] as Array)).toContain("new-window") + expect(spawnMock.mock.calls.flatMap((call) => call[0] as Array)).toContain("split-window") + expect(spawnMock.mock.calls.flatMap((call) => call[0] as Array)).toContain("select-layout") + expect(spawnMock.mock.calls.flatMap((call) => call[0] as Array)).toContain("select-pane") + }) + + test("returns null when tmux command fails", async () => { + // given + const { createTeamLayout } = await loadLayoutModule() + spawnMock.mockImplementationOnce(() => ({ + exited: Promise.resolve(1), + stdout: new ReadableStream({ start(controller) { controller.close() } }), + stderr: new ReadableStream({ start(controller) { controller.close() } }), + })) + + // when + const result = await createTeamLayout("run-3", [{ name: "lead", sessionId: "s1" }], {} as never) + + // then + expect(result).toBeNull() + }) + + test("cleans up the tmux session", async () => { + // given + const { removeTeamLayout } = await loadLayoutModule() + + // when + await removeTeamLayout("run-4", {} as never) + + // then + expect(spawnMock.mock.calls.some((call) => (call[0] as Array).includes("kill-session"))).toBe(true) + }) +}) diff --git a/src/features/team-mode/team-layout-tmux/layout.ts b/src/features/team-mode/team-layout-tmux/layout.ts new file mode 100644 index 000000000..f414ccbfe --- /dev/null +++ b/src/features/team-mode/team-layout-tmux/layout.ts @@ -0,0 +1,120 @@ +import { spawn } from "../../../shared/tmux/tmux-utils/spawn-process" +import { log } from "../../../shared" +import { getTmuxPath } from "../../../tools/interactive-bash/tmux-path-resolver" +import type { TmuxSessionManager } from "../../tmux-subagent/manager" + +type TeamLayoutMember = { name: string; sessionId: string; color?: string } + +type TeamLayoutResult = { + focusWindowId: string + gridWindowId: string + panesByMember: Record +} + +export function canVisualize(): boolean { + return process.env.TMUX !== undefined +} + +async function runTmux(tmuxPath: string, args: Array): Promise<{ success: boolean; output: string }> { + const proc = spawn([tmuxPath, ...args], { stdout: "pipe", stderr: "pipe" }) + const outputPromise = new Response(proc.stdout).text() + const exitCode = await proc.exited + const output = await outputPromise + + if (exitCode !== 0) { + return { success: false, output: output.trim() } + } + + return { success: true, output: output.trim() } +} + +async function createWindow( + tmuxPath: string, + sessionName: string, + windowName: string, + layout: "main-vertical" | "tiled", + members: Array, +): Promise<{ windowId: string; panesByMember: Record } | null> { + const base = await runTmux(tmuxPath, ["new-window", "-d", "-P", "-F", "#{window_id}", "-t", sessionName, "-n", windowName]) + if (!base.success || !base.output) return null + + const panesByMember: Record = {} + const [lead, ...rest] = members + if (!lead) return null + + const leadPane = await runTmux(tmuxPath, ["list-panes", "-t", `${sessionName}:${base.output}`, "-F", "#{pane_id}"]) + if (!leadPane.success || !leadPane.output) return null + panesByMember[lead.name] = leadPane.output.split("\n")[0] ?? "" + + for (const member of rest) { + const split = await runTmux(tmuxPath, ["split-window", "-d", "-P", "-F", "#{pane_id}", "-t", panesByMember[lead.name] ?? base.output, "sh", "-c", "cat >/dev/null"]) + if (!split.success || !split.output) return null + panesByMember[member.name] = split.output + } + + const layoutResult = await runTmux(tmuxPath, ["select-layout", "-t", `${sessionName}:${base.output}`, layout]) + if (!layoutResult.success) return null + + for (const member of members) { + const paneId = panesByMember[member.name] + if (!paneId) return null + const label = member.color ? `${member.name} ${member.color}` : member.name + const titleResult = await runTmux(tmuxPath, ["select-pane", "-t", paneId, "-T", label]) + if (!titleResult.success) return null + await runTmux(tmuxPath, ["set-option", "-t", paneId, "pane-border-status", "top"]) + await runTmux(tmuxPath, ["set-option", "-t", paneId, "pane-border-format", `#{pane_title} ${label}`]) + await runTmux(tmuxPath, ["pipe-pane", "-I", "-t", paneId, "cat >/dev/null"]) + } + + return { windowId: base.output, panesByMember } +} + +export async function createTeamLayout( + teamRunId: string, + members: Array, + tmuxMgr: TmuxSessionManager, +): Promise { + if (!canVisualize()) { + log("tmux visualization unavailable, skipping") + return null + } + + try { + void tmuxMgr + const tmuxPath = await getTmuxPath() + if (!tmuxPath) { + log("tmux visualization unavailable, skipping") + return null + } + + const sessionName = `omo-team-${teamRunId}` + const created = await runTmux(tmuxPath, ["new-session", "-d", "-s", sessionName, "-P", "-F", "#{window_id}"]) + if (!created.success || !created.output) return null + + const focus = await createWindow(tmuxPath, sessionName, "focus", "main-vertical", members) + const grid = await createWindow(tmuxPath, sessionName, "grid", "tiled", members) + if (!focus || !grid) return null + + return { + focusWindowId: focus.windowId, + gridWindowId: grid.windowId, + panesByMember: focus.panesByMember, + } + } catch (error) { + log("tmux visualization unavailable, skipping", { error: String(error) }) + return null + } +} + +export async function removeTeamLayout(teamRunId: string, tmuxMgr: TmuxSessionManager): Promise { + void tmuxMgr + if (!canVisualize()) return + + try { + const tmuxPath = await getTmuxPath() + if (!tmuxPath) return + await runTmux(tmuxPath, ["kill-session", "-t", `omo-team-${teamRunId}`]) + } catch { + return + } +} diff --git a/src/features/team-mode/team-worktree/cleanup.test.ts b/src/features/team-mode/team-worktree/cleanup.test.ts new file mode 100644 index 000000000..f47c63af9 --- /dev/null +++ b/src/features/team-mode/team-worktree/cleanup.test.ts @@ -0,0 +1,31 @@ +/// + +import { afterAll, expect, test } from "bun:test" +import fs from "node:fs/promises" +import os from "node:os" +import path from "node:path" + +import { findOrphanWorktrees } from "./cleanup" + +const temporaryDirectories: string[] = [] + +afterAll(async () => { + for (const directory of temporaryDirectories) { + await fs.rm(directory, { recursive: true, force: true }) + } +}) + +test("given runtime mismatch when findOrphanWorktrees then returns orphan paths", async () => { + // given + const baseDir = await fs.mkdtemp(path.join(os.tmpdir(), "team-worktree-orphans-")) + temporaryDirectories.push(baseDir) + await fs.mkdir(path.join(baseDir, "worktrees", "t1", "m1"), { recursive: true }) + await fs.mkdir(path.join(baseDir, "runtime", "t1"), { recursive: true }) + await fs.writeFile(path.join(baseDir, "runtime", "t1", "state.json"), JSON.stringify({ status: "deleted" })) + + // when + const result = await findOrphanWorktrees(baseDir, {}) + + // then + expect(result).toEqual([path.join(baseDir, "worktrees", "t1", "m1")]) +}) diff --git a/src/features/team-mode/team-worktree/cleanup.ts b/src/features/team-mode/team-worktree/cleanup.ts new file mode 100644 index 000000000..673ecebc1 --- /dev/null +++ b/src/features/team-mode/team-worktree/cleanup.ts @@ -0,0 +1,77 @@ +import fs from "node:fs/promises" +import path from "node:path" + +import type { TeamModeConfig } from "./manager" + +async function runGit(args: string[]): Promise<{ code: number; stderr: string }> { + const process = Bun.spawn({ cmd: ["git", ...args], stdout: "pipe", stderr: "pipe" }) + const [exitCode, stderrText] = await Promise.all([process.exited, new Response(process.stderr).text()]) + return { code: exitCode, stderr: stderrText } +} + +export async function removeWorktree(worktreePath: string): Promise { + await fs.rm(worktreePath, { recursive: true, force: true }) + + const rootLookup = await Bun.spawn({ + cmd: ["git", "-C", worktreePath, "rev-parse", "--show-superproject-working-tree"], + stdout: "pipe", + stderr: "pipe", + }) + const [rootExitCode, rootStdout] = await Promise.all([ + rootLookup.exited, + new Response(rootLookup.stdout).text(), + new Response(rootLookup.stderr).text(), + ]) + const result = + rootExitCode === 0 && rootStdout.trim().length > 0 + ? await runGit(["-C", rootStdout.trim(), "worktree", "remove", "--force", worktreePath]) + : await runGit(["worktree", "remove", "--force", worktreePath]) + + if ( + result.code !== 0 && + !result.stderr.includes("not a worktree") && + !result.stderr.includes("not a working tree") && + !result.stderr.includes("already removed") + ) { + throw new Error(result.stderr.trim() || "git worktree remove failed") + } + + if (rootExitCode === 0 && rootStdout.trim().length > 0) { + await runGit(["-C", rootStdout.trim(), "worktree", "prune"]) + } +} + +export async function findOrphanWorktrees(baseDir: string, _config: TeamModeConfig): Promise { + const orphanWorktrees: string[] = [] + const worktreesDir = path.join(baseDir, "worktrees") + + let teamRunDirectories: string[] + try { + teamRunDirectories = await fs.readdir(worktreesDir) + } catch { + return orphanWorktrees + } + + for (const teamRunId of teamRunDirectories) { + const teamRunPath = path.join(worktreesDir, teamRunId) + const memberNames = await fs.readdir(teamRunPath).catch(() => []) + + for (const memberName of memberNames) { + const worktreePath = path.join(teamRunPath, memberName) + const statePath = path.join(baseDir, "runtime", teamRunId, "state.json") + + try { + const stateContents = await fs.readFile(statePath, "utf8") + const state = JSON.parse(stateContents) as { status?: string } + + if (state.status !== "active" && state.status !== "shutdown_requested") { + orphanWorktrees.push(worktreePath) + } + } catch { + orphanWorktrees.push(worktreePath) + } + } + } + + return orphanWorktrees +} diff --git a/src/features/team-mode/team-worktree/index.ts b/src/features/team-mode/team-worktree/index.ts new file mode 100644 index 000000000..e311ff757 --- /dev/null +++ b/src/features/team-mode/team-worktree/index.ts @@ -0,0 +1,2 @@ +export { GitUnavailableError, createWorktree, isGitAvailable, validateWorktreeSpec } from "./manager" +export { findOrphanWorktrees, removeWorktree } from "./cleanup" diff --git a/src/features/team-mode/team-worktree/manager.test.ts b/src/features/team-mode/team-worktree/manager.test.ts new file mode 100644 index 000000000..b1e98556b --- /dev/null +++ b/src/features/team-mode/team-worktree/manager.test.ts @@ -0,0 +1,104 @@ +/// + +import { afterAll, beforeAll, describe, expect, mock, test } from "bun:test" +import { randomUUID } from "node:crypto" +import fs from "node:fs/promises" +import os from "node:os" +import path from "node:path" + +import { GitUnavailableError, createWorktree, setGitCommandRunnerForTests, validateWorktreeSpec } from "./manager" +import { removeWorktree } from "./cleanup" + +const temporaryDirectories: string[] = [] + +async function initGitRepo(): Promise { + const repositoryRoot = await fs.mkdtemp(path.join(os.tmpdir(), "team-worktree-")) + temporaryDirectories.push(repositoryRoot) + Bun.spawnSync(["git", "init"], { cwd: repositoryRoot, stdout: "pipe", stderr: "pipe" }) + await fs.writeFile(path.join(repositoryRoot, "README.md"), "hello\n") + Bun.spawnSync(["git", "add", "README.md"], { cwd: repositoryRoot, stdout: "pipe", stderr: "pipe" }) + Bun.spawnSync(["git", "config", "user.email", "test@example.com"], { cwd: repositoryRoot, stdout: "pipe", stderr: "pipe" }) + Bun.spawnSync(["git", "config", "user.name", "Test User"], { cwd: repositoryRoot, stdout: "pipe", stderr: "pipe" }) + Bun.spawnSync(["git", "commit", "-m", "init"], { cwd: repositoryRoot, stdout: "pipe", stderr: "pipe" }) + return repositoryRoot +} + +beforeAll(() => { + mock.restore() +}) + +afterAll(async () => { + for (const directory of temporaryDirectories) { + await fs.rm(directory, { recursive: true, force: true }) + } + mock.restore() +}) + +describe("team-worktree manager", () => { + test("given tmp git repo when createWorktree then registers detached worktree", async () => { + // given + const repositoryRoot = await initGitRepo() + const worktreePath = `../worktree-${randomUUID()}` + const worktreeDirectory = path.resolve(repositoryRoot, worktreePath) + + // when + const resultPath = await createWorktree(repositoryRoot, "t1", "m1", worktreePath, {}) + + // then + expect(resultPath).toBe(worktreeDirectory) + await expect(fs.stat(worktreeDirectory)).resolves.toBeDefined() + const listResult = Bun.spawnSync(["git", "worktree", "list"], { cwd: repositoryRoot, stdout: "pipe", stderr: "pipe" }) + expect(new TextDecoder().decode(listResult.stdout)).toContain(worktreeDirectory) + const headResult = Bun.spawnSync(["git", "-C", worktreeDirectory, "rev-parse", "HEAD"], { stdout: "pipe", stderr: "pipe" }) + const repoHeadResult = Bun.spawnSync(["git", "-C", repositoryRoot, "rev-parse", "HEAD"], { stdout: "pipe", stderr: "pipe" }) + expect(new TextDecoder().decode(headResult.stdout).trim()).toBe(new TextDecoder().decode(repoHeadResult.stdout).trim()) + }) + + test("validateWorktreeSpec rejects bare name", () => { + // given + const worktreePath = "feature-x" + + // when + const validate = () => validateWorktreeSpec(worktreePath) + + // then + expect(validate).toThrow("worktreePath must be a filesystem path (relative './...', '../...' or absolute '/...')") + }) + + test("given git unavailable when createWorktree then throws unavailable error", async () => { + // given + const repositoryRoot = await initGitRepo() + setGitCommandRunnerForTests(async (args) => { + if (args[0] === "--version") { + return { code: 1, stderr: "git missing" } + } + + return { code: 0, stderr: "" } + }) + + // when + const create = createWorktree(repositoryRoot, "t1", "m1", `../worktree-${randomUUID()}`, {}) + + // then + await expect(create).rejects.toBeInstanceOf(GitUnavailableError) + setGitCommandRunnerForTests(async (args) => { + if (args[0] === "--version") { + return { code: 0, stderr: "" } + } + + return { code: 0, stderr: "" } + }) + }) + + test("given created worktree when removeWorktree then directory disappears", async () => { + // given + const repositoryRoot = await initGitRepo() + const worktreePath = await createWorktree(repositoryRoot, "t1", "m1", `../worktree-${randomUUID()}`, {}) + + // when + await removeWorktree(worktreePath) + + // then + await expect(fs.stat(worktreePath)).rejects.toThrow() + }) +}) diff --git a/src/features/team-mode/team-worktree/manager.ts b/src/features/team-mode/team-worktree/manager.ts new file mode 100644 index 000000000..359df4cd0 --- /dev/null +++ b/src/features/team-mode/team-worktree/manager.ts @@ -0,0 +1,62 @@ +import path from "node:path" + +export type TeamModeConfig = { + worktreeBaseDir?: string +} + +export class GitUnavailableError extends Error { + constructor() { + super("git required for worktree members") + this.name = "GitUnavailableError" + } +} + +function countParentSegments(spec: string): number { + return spec.split("/").filter((segment) => segment === "..").length +} + +async function runGit(args: string[], cwd?: string): Promise<{ code: number; stderr: string }> { + const process = Bun.spawn({ cmd: ["git", ...args], cwd, stdout: "pipe", stderr: "pipe" }) + const [exitCode, stderrBytes] = await Promise.all([process.exited, new Response(process.stderr).text()]) + return { code: exitCode, stderr: stderrBytes } +} + +let gitCommandRunner = runGit + +export function setGitCommandRunnerForTests(runner: typeof runGit): void { + gitCommandRunner = runner +} + +export async function isGitAvailable(): Promise { + const result = await gitCommandRunner(["--version"]) + return result.code === 0 +} + +export function validateWorktreeSpec(spec: string): void { + if (!/^(\.\.?\/|\/).+/.test(spec) || countParentSegments(spec) > 2) { + throw new Error("worktreePath must be a filesystem path (relative './...', '../...' or absolute '/...')") + } +} + +export async function createWorktree( + repoRoot: string, + _teamRunId: string, + _memberName: string, + worktreePath: string, + _config: TeamModeConfig, +): Promise { + validateWorktreeSpec(worktreePath) + + if (!(await isGitAvailable())) { + throw new GitUnavailableError() + } + + const absolutePath = path.isAbsolute(worktreePath) ? worktreePath : path.resolve(repoRoot, worktreePath) + const result = await gitCommandRunner(["-C", repoRoot, "worktree", "add", "--detach", absolutePath]) + + if (result.code !== 0) { + throw new Error(result.stderr.trim() || "git worktree add failed") + } + + return absolutePath +} diff --git a/src/features/team-mode/types.test.ts b/src/features/team-mode/types.test.ts new file mode 100644 index 000000000..042f220e5 --- /dev/null +++ b/src/features/team-mode/types.test.ts @@ -0,0 +1,96 @@ +import { describe, expect, test } from "bun:test" +import { + AGENT_ELIGIBILITY_REGISTRY, + CategoryMemberSchema, + MemberSchema, + SubagentMemberSchema, +} from "./types" + +describe("team-mode types", () => { + test("member category branch parses and narrows", () => { + // given + const member = { kind: "category", name: "m1", category: "deep", prompt: "impl X" } + + // when + const result = MemberSchema.safeParse(member) + + // then + expect(result.success).toBe(true) + if (result.success) { + expect(result.data).toMatchObject(member) + expect(result.data).toMatchObject({ kind: "category", category: "deep" }) + } + }) + + test("both kinds rejected", () => { + // given + const member = { + kind: "category", + name: "m1", + category: "deep", + subagent_type: "sisyphus", + prompt: "impl X", + } + + // when + const result = MemberSchema.safeParse(member) + + // then + expect(result.success).toBe(false) + }) + + test("category requires prompt", () => { + // given + const member = { kind: "category", name: "m1", category: "deep" } + + // when + const result = CategoryMemberSchema.safeParse(member) + + // then + expect(result.success).toBe(false) + }) + + test("eligibility registry shape", () => { + // given + const entries = Object.entries(AGENT_ELIGIBILITY_REGISTRY) + + // when + const verdictCounts = entries.reduce( + (counts, [, value]) => { + counts[value.verdict] += 1 + return counts + }, + { eligible: 0, conditional: 0, "hard-reject": 0 }, + ) + + // then + expect(entries).toHaveLength(11) + expect(verdictCounts).toEqual({ eligible: 3, conditional: 1, "hard-reject": 7 }) + expect(AGENT_ELIGIBILITY_REGISTRY.hephaestus.rejectionMessage).toBe( + "Agent 'hephaestus' lacks teammate permission. Either apply D-36 (add teammate: \"allow\" in tool-config-handler.ts) or use subagent_type: \"sisyphus\" instead.", + ) + expect(AGENT_ELIGIBILITY_REGISTRY.oracle.rejectionMessage).toBe( + "Agent 'oracle' is read-only (cannot write files). Team members must write to mailbox inbox files. Use delegate-task with subagent_type: 'oracle' for read-only analysis instead.", + ) + expect(AGENT_ELIGIBILITY_REGISTRY.librarian.rejectionMessage).toBe( + "Agent 'librarian' is read-only (write/edit denied). Cannot write to mailbox as team member. Use delegate-task for research queries instead.", + ) + expect(AGENT_ELIGIBILITY_REGISTRY.explore.rejectionMessage).toBe( + "Agent 'explore' is read-only (write/edit denied). Cannot write to mailbox as team member. Use delegate-task for codebase exploration instead.", + ) + expect(AGENT_ELIGIBILITY_REGISTRY["multimodal-looker"].rejectionMessage).toBe( + "Agent 'multimodal-looker' has read-only tool access (only 'read' allowed). Cannot write to mailbox as team member.", + ) + expect(AGENT_ELIGIBILITY_REGISTRY.metis.rejectionMessage).toBe( + "Agent 'metis' is read-only (pre-planning consultant). Cannot write to mailbox as team member. Use delegate-task for pre-planning analysis instead.", + ) + expect(AGENT_ELIGIBILITY_REGISTRY.momus.rejectionMessage).toBe( + "Agent 'momus' is read-only (plan reviewer). Cannot write to mailbox as team member. Use delegate-task for plan review instead.", + ) + expect(AGENT_ELIGIBILITY_REGISTRY.prometheus.rejectionMessage).toBe( + "Agent 'prometheus' is plan-mode-only; can only write to .sisyphus/*.md (enforced by prometheusMdOnly hook). Cannot write to team mailbox. Use category: 'plan' instead.", + ) + expect(CategoryMemberSchema).toBeDefined() + expect(SubagentMemberSchema).toBeDefined() + }) +}) diff --git a/src/features/team-mode/types.ts b/src/features/team-mode/types.ts new file mode 100644 index 000000000..2b2da3f2a --- /dev/null +++ b/src/features/team-mode/types.ts @@ -0,0 +1,190 @@ +import { z } from "zod" + +export const MESSAGE_KINDS = [ + "message", + "shutdown_request", + "shutdown_approved", + "shutdown_rejected", + "announcement", +] as const + +export const MEMBER_KINDS = ["category", "subagent_type"] as const + +export const TASK_STATUSES = ["pending", "claimed", "in_progress", "completed", "deleted"] as const + +export const RUNTIME_STATUSES = [ + "creating", + "active", + "shutdown_requested", + "deleting", + "deleted", + "failed", + "orphaned", +] as const + +const MemberBaseSchema = z.object({ + name: z.string().min(1).regex(/^[a-z0-9-]+$/), + cwd: z.string().optional(), + worktreePath: z.string().optional(), + subscriptions: z.array(z.string()).optional(), + backendType: z.enum(["in-process", "tmux"]).default("in-process"), + color: z.string().optional(), + isActive: z.boolean().default(true), +}).strict() + +export const CategoryMemberSchema = MemberBaseSchema.extend({ + kind: z.literal("category"), + category: z.string().min(1), + prompt: z.string().min(1), +}) + +export const SubagentMemberSchema = MemberBaseSchema.extend({ + kind: z.literal("subagent_type"), + subagent_type: z.string().min(1), + prompt: z.string().optional(), +}) + +export const MemberSchema = z.discriminatedUnion("kind", [CategoryMemberSchema, SubagentMemberSchema]) + +const TeamReferenceSchema = z.object({ + path: z.string(), + description: z.string().optional(), +}).strict() + +export const TeamSpecSchema = z.object({ + version: z.literal(1), + name: z.string().min(1).regex(/^[a-z0-9-]+$/), + description: z.string().optional(), + createdAt: z.number().int().positive(), + leadAgentId: z.string(), + teamAllowedPaths: z.array(z.string()).optional(), + sessionPermission: z.string().optional(), + members: z.array(MemberSchema).min(1).max(8), +}) + +export const MessageSchema = z.object({ + version: z.literal(1), + messageId: z.string().uuid(), + from: z.string(), + to: z.string(), + kind: z.enum(MESSAGE_KINDS), + body: z.string().max(32 * 1024), + summary: z.string().optional(), + references: z.array(TeamReferenceSchema).optional(), + timestamp: z.number().int().positive(), + correlationId: z.string().uuid().optional(), + color: z.string().optional(), +}) + +export const TaskSchema = z.object({ + version: z.literal(1), + id: z.string(), + subject: z.string(), + description: z.string(), + activeForm: z.string().optional(), + status: z.enum(TASK_STATUSES), + owner: z.string().optional(), + blocks: z.array(z.string()).default([]), + blockedBy: z.array(z.string()).default([]), + metadata: z.record(z.string(), z.unknown()).optional(), + createdAt: z.number().int().positive(), + updatedAt: z.number().int().positive(), + claimedAt: z.number().int().positive().optional(), +}) + +const RuntimeStateMemberSchema = z.object({ + name: z.string(), + sessionId: z.string().optional(), + tmuxPaneId: z.string().optional(), + agentType: z.enum(["leader", "general-purpose"]), + status: z.enum(["pending", "running", "idle", "errored", "completed", "shutdown_approved"]), + color: z.string().optional(), + worktreePath: z.string().optional(), + lastInjectedTurnMarker: z.string().optional(), + pendingInjectedMessageIds: z.array(z.string()).default([]), +}).strict() + +const RuntimeBoundsSchema = z.object({ + maxMembers: z.number().int().default(8), + maxParallelMembers: z.number().int().default(4), + maxMessagesPerRun: z.number().int().default(10000), + maxWallClockMinutes: z.number().int().default(120), + maxMemberTurns: z.number().int().default(500), +}).strict() + +const ShutdownRequestSchema = z.object({ + memberId: z.string(), + requestedAt: z.number().int().positive(), + approvedAt: z.number().int().positive().optional(), + rejectedReason: z.string().optional(), +}).strict() + +export const RuntimeStateSchema = z.object({ + version: z.literal(1), + teamRunId: z.string().uuid(), + teamName: z.string(), + specSource: z.enum(["project", "user"]), + createdAt: z.number().int().positive(), + status: z.enum(RUNTIME_STATUSES), + leadSessionId: z.string().optional(), + members: z.array(RuntimeStateMemberSchema), + shutdownRequests: z.array(ShutdownRequestSchema).default([]), + bounds: RuntimeBoundsSchema, +}) + +export const AGENT_ELIGIBILITY_REGISTRY: Readonly> = { + sisyphus: { verdict: "eligible" }, + hephaestus: { + verdict: "conditional", + rejectionMessage: + "Agent 'hephaestus' lacks teammate permission. Either apply D-36 (add teammate: \"allow\" in tool-config-handler.ts) or use subagent_type: \"sisyphus\" instead.", + }, + oracle: { + verdict: "hard-reject", + rejectionMessage: + "Agent 'oracle' is read-only (cannot write files). Team members must write to mailbox inbox files. Use delegate-task with subagent_type: 'oracle' for read-only analysis instead.", + }, + librarian: { + verdict: "hard-reject", + rejectionMessage: + "Agent 'librarian' is read-only (write/edit denied). Cannot write to mailbox as team member. Use delegate-task for research queries instead.", + }, + explore: { + verdict: "hard-reject", + rejectionMessage: + "Agent 'explore' is read-only (write/edit denied). Cannot write to mailbox as team member. Use delegate-task for codebase exploration instead.", + }, + "multimodal-looker": { + verdict: "hard-reject", + rejectionMessage: + "Agent 'multimodal-looker' has read-only tool access (only 'read' allowed). Cannot write to mailbox as team member.", + }, + metis: { + verdict: "hard-reject", + rejectionMessage: + "Agent 'metis' is read-only (pre-planning consultant). Cannot write to mailbox as team member. Use delegate-task for pre-planning analysis instead.", + }, + momus: { + verdict: "hard-reject", + rejectionMessage: + "Agent 'momus' is read-only (plan reviewer). Cannot write to mailbox as team member. Use delegate-task for plan review instead.", + }, + atlas: { verdict: "eligible" }, + prometheus: { + verdict: "hard-reject", + rejectionMessage: + "Agent 'prometheus' is plan-mode-only; can only write to .sisyphus/*.md (enforced by prometheusMdOnly hook). Cannot write to team mailbox. Use category: 'plan' instead.", + }, + "sisyphus-junior": { verdict: "eligible" }, +} as const + +export type TeamSpec = z.infer +export type Member = z.infer +export type CategoryMember = z.infer +export type SubagentMember = z.infer +export type Message = z.infer +export type Task = z.infer +export type RuntimeState = z.infer diff --git a/src/features/tmux-subagent/cleanup.ts b/src/features/tmux-subagent/cleanup.ts deleted file mode 100644 index 414ad00bc..000000000 --- a/src/features/tmux-subagent/cleanup.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { TmuxConfig } from "../../config/schema" -import { log } from "../../shared" -import type { TrackedSession } from "./types" -import { queryWindowState } from "./pane-state-querier" -import { executeAction } from "./action-executor" - -export async function cleanupTmuxSessions(params: { - tmuxConfig: TmuxConfig - serverUrl: string - sourcePaneId: string | undefined - sessions: Map - stopPolling: () => void -}): Promise { - params.stopPolling() - - if (params.sessions.size === 0) { - log("[tmux-session-manager] cleanup complete") - return - } - - log("[tmux-session-manager] closing all panes", { count: params.sessions.size }) - const state = params.sourcePaneId ? await queryWindowState(params.sourcePaneId) : null - - if (state) { - const closePromises = Array.from(params.sessions.values()).map((tracked) => - executeAction( - { type: "close", paneId: tracked.paneId, sessionId: tracked.sessionId }, - { config: params.tmuxConfig, serverUrl: params.serverUrl, windowState: state }, - ).catch((error) => - log("[tmux-session-manager] cleanup error for pane", { - paneId: tracked.paneId, - error: String(error), - }), - ), - ) - - await Promise.all(closePromises) - } - - params.sessions.clear() - log("[tmux-session-manager] cleanup complete") -} diff --git a/src/features/tmux-subagent/event-handlers.ts b/src/features/tmux-subagent/event-handlers.ts index 0991d10e2..2916c7439 100644 --- a/src/features/tmux-subagent/event-handlers.ts +++ b/src/features/tmux-subagent/event-handlers.ts @@ -1,6 +1,2 @@ export { coerceSessionCreatedEvent } from "./session-created-event" export type { SessionCreatedEvent } from "./session-created-event" -export { handleSessionCreated } from "./session-created-handler" -export type { SessionCreatedHandlerDeps } from "./session-created-handler" -export { handleSessionDeleted } from "./session-deleted-handler" -export type { SessionDeletedHandlerDeps } from "./session-deleted-handler" diff --git a/src/features/tmux-subagent/index.ts b/src/features/tmux-subagent/index.ts index e900555fb..cba66fa6b 100644 --- a/src/features/tmux-subagent/index.ts +++ b/src/features/tmux-subagent/index.ts @@ -1,10 +1,7 @@ export * from "./manager" export * from "./event-handlers" export * from "./polling" -export * from "./cleanup" export * from "./session-created-event" -export * from "./session-created-handler" -export * from "./session-deleted-handler" export * from "./polling-constants" export * from "./session-status-parser" export * from "./session-message-count" diff --git a/src/features/tmux-subagent/manager.test.ts b/src/features/tmux-subagent/manager.test.ts index 8c47097f4..e63f4bf4d 100644 --- a/src/features/tmux-subagent/manager.test.ts +++ b/src/features/tmux-subagent/manager.test.ts @@ -57,6 +57,8 @@ const mockSpawnTmuxSession = mock<( success: true, paneId: '%isolated-session', })) +const mockKillTmuxSessionIfExists = mock<(sessionName: string) => Promise>(async () => true) +const mockSweepStaleOmoAgentSessions = mock<() => Promise>(async () => 0) const mockIsInsideTmux = mock<() => boolean>(() => true) const mockGetCurrentPaneId = mock<() => string | undefined>(() => '%0') @@ -99,6 +101,9 @@ mock.module('../../shared/tmux', () => { SESSION_READY_TIMEOUT_MS: 500, spawnTmuxWindow: mockSpawnTmuxWindow, spawnTmuxSession: mockSpawnTmuxSession, + killTmuxSessionIfExists: mockKillTmuxSessionIfExists, + getIsolatedSessionName: (pid: number = 12345) => `omo-agents-${pid}`, + sweepStaleOmoAgentSessions: mockSweepStaleOmoAgentSessions, } }) @@ -1056,6 +1061,27 @@ describe('TmuxSessionManager', () => { logSpy.mockRestore() }) }) + + test('#given session.status never reports session ready #when onSessionCreated runs #then pane is tracked immediately without blocking', async () => { + // given + mockIsInsideTmux.mockReturnValue(true) + mockQueryWindowState.mockImplementation(async () => createWindowState()) + + const { TmuxSessionManager } = await import('./manager') + const ctx = createMockContext({ sessionStatusResult: { data: {} } }) + const config = createTmuxConfig({ enabled: true }) + const manager = new TmuxSessionManager(ctx, config, mockTmuxDeps) + const event = createSessionCreatedEvent('ses_fast_track', 'ses_parent', 'Fast Track') + + // when + const start = Date.now() + await manager.onSessionCreated(event) + const elapsed = Date.now() - start + + // then + expect(elapsed < 500).toBe(true) + expect(getTrackedSessions(manager).has('ses_fast_track')).toBe(true) + }) }) describe('onSessionDeleted', () => { @@ -1831,6 +1857,141 @@ describe('TmuxSessionManager', () => { // then expect(mockExecuteAction).toHaveBeenCalledTimes(2) }) + + test('#given tmux isolation is "session" #when cleanup runs #then killTmuxSessionIfExists is invoked for the per-pid isolated session', async () => { + // given + mockKillTmuxSessionIfExists.mockClear() + const { TmuxSessionManager } = await import('./manager') + const manager = new TmuxSessionManager(createMockContext(), createTmuxConfig({ + enabled: true, + isolation: 'session', + }), mockTmuxDeps) + + // when + await manager.cleanup() + + // then + expect(mockKillTmuxSessionIfExists).toHaveBeenCalledTimes(1) + expect(mockKillTmuxSessionIfExists.mock.calls[0]?.[0]).toMatch(/^omo-agents-\d+$/) + }) + + test('#given two manager instances #when both cleanup #then each kills its own isolated session name, not a shared one', async () => { + // given + mockKillTmuxSessionIfExists.mockClear() + const { TmuxSessionManager } = await import('./manager') + const managerA = new TmuxSessionManager(createMockContext(), createTmuxConfig({ + enabled: true, + isolation: 'session', + }), mockTmuxDeps) + const managerB = new TmuxSessionManager(createMockContext(), createTmuxConfig({ + enabled: true, + isolation: 'session', + }), mockTmuxDeps) + + // when + await managerA.cleanup() + await managerB.cleanup() + + // then + expect(mockKillTmuxSessionIfExists).toHaveBeenCalledTimes(2) + const firstTarget = mockKillTmuxSessionIfExists.mock.calls[0]?.[0] + const secondTarget = mockKillTmuxSessionIfExists.mock.calls[1]?.[0] + expect(firstTarget).toMatch(/^omo-agents-\d+$/) + expect(secondTarget).toMatch(/^omo-agents-\d+$/) + }) + + test('#given tmux isolation is "inline" #when cleanup runs #then killTmuxSessionIfExists is NOT invoked', async () => { + // given + mockKillTmuxSessionIfExists.mockClear() + const { TmuxSessionManager } = await import('./manager') + const manager = new TmuxSessionManager(createMockContext(), createTmuxConfig({ + enabled: true, + isolation: 'inline', + }), mockTmuxDeps) + + // when + await manager.cleanup() + + // then + expect(mockKillTmuxSessionIfExists).toHaveBeenCalledTimes(0) + }) + + test('#given tmux isolation is "window" #when cleanup runs #then killTmuxSessionIfExists is NOT invoked', async () => { + // given + mockKillTmuxSessionIfExists.mockClear() + const { TmuxSessionManager } = await import('./manager') + const manager = new TmuxSessionManager(createMockContext(), createTmuxConfig({ + enabled: true, + isolation: 'window', + }), mockTmuxDeps) + + // when + await manager.cleanup() + + // then + expect(mockKillTmuxSessionIfExists).toHaveBeenCalledTimes(0) + }) + + test('#given sweepStaleOmoAgentSessions throws on first onSessionCreated #when second onSessionCreated fires #then sweep is retried instead of skipped forever', async () => { + // given + mockSweepStaleOmoAgentSessions.mockClear() + mockSweepStaleOmoAgentSessions.mockImplementationOnce(async () => { + throw new Error('simulated sweep failure') + }) + mockIsInsideTmux.mockReturnValue(true) + const { TmuxSessionManager } = await import('./manager') + const manager = new TmuxSessionManager(createMockContext(), createTmuxConfig({ + enabled: true, + isolation: 'session', + }), mockTmuxDeps) + + // when + await manager.onSessionCreated(createSessionCreatedEvent('ses_first', 'ses_parent', 'First')) + await manager.onSessionCreated(createSessionCreatedEvent('ses_second', 'ses_parent', 'Second')) + + // then + expect(mockSweepStaleOmoAgentSessions).toHaveBeenCalledTimes(2) + }) + + test('#given sweepStaleOmoAgentSessions succeeds #when additional onSessionCreated events fire in same process #then sweep runs exactly once', async () => { + // given + mockSweepStaleOmoAgentSessions.mockClear() + mockSweepStaleOmoAgentSessions.mockImplementation(async () => 0) + mockIsInsideTmux.mockReturnValue(true) + const { TmuxSessionManager } = await import('./manager') + const manager = new TmuxSessionManager(createMockContext(), createTmuxConfig({ + enabled: true, + isolation: 'session', + }), mockTmuxDeps) + + // when + await manager.onSessionCreated(createSessionCreatedEvent('ses_a', 'ses_parent', 'A')) + await manager.onSessionCreated(createSessionCreatedEvent('ses_b', 'ses_parent', 'B')) + await manager.onSessionCreated(createSessionCreatedEvent('ses_c', 'ses_parent', 'C')) + + // then + expect(mockSweepStaleOmoAgentSessions).toHaveBeenCalledTimes(1) + }) + + test('#given killTmuxSessionIfExists throws #when cleanup runs #then cleanup still completes without throwing', async () => { + // given + mockKillTmuxSessionIfExists.mockClear() + mockKillTmuxSessionIfExists.mockImplementationOnce(async () => { + throw new Error('simulated teardown failure') + }) + const { TmuxSessionManager } = await import('./manager') + const manager = new TmuxSessionManager(createMockContext(), createTmuxConfig({ + enabled: true, + isolation: 'session', + }), mockTmuxDeps) + + // when + const cleanupPromise = manager.cleanup() + + // then + await expect(cleanupPromise).resolves.toBeUndefined() + expect(mockKillTmuxSessionIfExists).toHaveBeenCalledTimes(1) + }) }) }) diff --git a/src/features/tmux-subagent/manager.ts b/src/features/tmux-subagent/manager.ts index a31f668bf..3340fb55d 100644 --- a/src/features/tmux-subagent/manager.ts +++ b/src/features/tmux-subagent/manager.ts @@ -10,6 +10,9 @@ import { SESSION_READY_TIMEOUT_MS, spawnTmuxWindow, spawnTmuxSession, + killTmuxSessionIfExists, + getIsolatedSessionName, + sweepStaleOmoAgentSessions, } from "../../shared/tmux" import { queryWindowState } from "./pane-state-querier" import { decideSpawnActions, decideCloseAction, type SessionMapping } from "./decision-engine" @@ -63,6 +66,8 @@ export class TmuxSessionManager { private isolatedContainerPaneId: string | undefined private isolatedWindowPaneId: string | undefined private isolatedContainerNullStateCount = 0 + private staleSweepCompleted = false + private staleSweepInProgress = false constructor(ctx: PluginInput, tmuxConfig: TmuxConfig, deps: TmuxUtilDeps = defaultTmuxDeps) { this.client = ctx.client this.tmuxConfig = tmuxConfig @@ -89,7 +94,8 @@ export class TmuxSessionManager { this.pollingManager = new TmuxPollingManager( this.client, this.sessions, - this.closeSessionById.bind(this) + this.closeSessionById.bind(this), + this.retryPendingCloses.bind(this) ) log("[tmux-session-manager] initialized", { configEnabled: this.tmuxConfig.enabled, @@ -511,7 +517,6 @@ export class TmuxSessionManager { if (deferred.retryIsolatedContainer) { const isolatedPaneId = await this.spawnInIsolatedContainer(sessionId, deferred.title) if (isolatedPaneId) { - const sessionReady = await this.waitForSessionReady(sessionId) this.sessions.set( sessionId, createTrackedSession({ @@ -525,8 +530,8 @@ export class TmuxSessionManager { log("[tmux-session-manager] deferred session attached in isolated window", { sessionId, paneId: isolatedPaneId, - sessionReady, }) + this.logSessionReadinessInBackground(sessionId) return } } @@ -585,14 +590,6 @@ export class TmuxSessionManager { return } - const sessionReady = await this.waitForSessionReady(sessionId) - if (!sessionReady) { - log("[tmux-session-manager] deferred session not ready after timeout", { - sessionId, - paneId: result.spawnedPaneId, - }) - } - this.sessions.set( sessionId, createTrackedSession({ @@ -606,18 +603,27 @@ export class TmuxSessionManager { log("[tmux-session-manager] deferred session attached", { sessionId, paneId: result.spawnedPaneId, - sessionReady, + }) + this.logSessionReadinessInBackground(sessionId) + } + + private logSessionReadinessInBackground(sessionId: string): void { + void this.waitForSessionReady(sessionId).catch((error) => { + log("[tmux-session-manager] background readiness probe failed", { + sessionId, + error: String(error), + }) }) } private async waitForSessionReady(sessionId: string): Promise { const startTime = Date.now() - + while (Date.now() - startTime < SESSION_READY_TIMEOUT_MS) { try { const statusResult = await this.client.session.status({ path: undefined }) const allStatuses = normalizeSDKResponse(statusResult, {} as Record) - + if (allStatuses[sessionId]) { log("[tmux-session-manager] session ready", { sessionId, @@ -629,10 +635,10 @@ export class TmuxSessionManager { } catch (err) { log("[tmux-session-manager] session status check error", { error: String(err) }) } - + await new Promise((resolve) => setTimeout(resolve, SESSION_READY_POLL_INTERVAL_MS)) } - + log("[tmux-session-manager] session ready timeout", { sessionId, timeoutMs: SESSION_READY_TIMEOUT_MS, @@ -665,6 +671,7 @@ export class TmuxSessionManager { return } + await this.sweepStaleIsolatedSessionsOnce() await this.retryPendingCloses() if ( @@ -682,7 +689,6 @@ export class TmuxSessionManager { try { const isolatedPaneId = await this.spawnInIsolatedContainer(sessionId, title) if (isolatedPaneId) { - const sessionReady = await this.waitForSessionReady(sessionId) this.sessions.set( sessionId, createTrackedSession({ sessionId, paneId: isolatedPaneId, description: title }), @@ -691,8 +697,8 @@ export class TmuxSessionManager { log("[tmux-session-manager] first subagent spawned in isolated window", { sessionId, paneId: isolatedPaneId, - sessionReady, }) + this.logSessionReadinessInBackground(sessionId) return } @@ -773,15 +779,6 @@ export class TmuxSessionManager { } if (result.success && result.spawnedPaneId) { - const sessionReady = await this.waitForSessionReady(sessionId) - - if (!sessionReady) { - log("[tmux-session-manager] session not ready after timeout, tracking anyway", { - sessionId, - paneId: result.spawnedPaneId, - }) - } - this.sessions.set( sessionId, createTrackedSession({ @@ -793,9 +790,9 @@ export class TmuxSessionManager { log("[tmux-session-manager] pane spawned and tracked", { sessionId, paneId: result.spawnedPaneId, - sessionReady, }) this.pollingManager.startPolling() + this.logSessionReadinessInBackground(sessionId) } else { log("[tmux-session-manager] spawn failed", { success: result.success, @@ -964,6 +961,49 @@ export class TmuxSessionManager { this.isolatedContainerPaneId = undefined this.isolatedWindowPaneId = undefined + if (this.tmuxConfig.isolation === "session") { + const isolatedSessionName = getIsolatedSessionName() + try { + const killed = await killTmuxSessionIfExists(isolatedSessionName) + log("[tmux-session-manager] isolated session teardown", { + session: isolatedSessionName, + killed, + }) + } catch (error) { + log("[tmux-session-manager] isolated session teardown failed", { + session: isolatedSessionName, + error: String(error), + }) + } + } + + this.staleSweepCompleted = false + this.staleSweepInProgress = false + log("[tmux-session-manager] cleanup complete") } + + private async sweepStaleIsolatedSessionsOnce(): Promise { + if (this.staleSweepCompleted) return + if (this.staleSweepInProgress) return + if (this.tmuxConfig.isolation !== "session") { + this.staleSweepCompleted = true + return + } + + this.staleSweepInProgress = true + try { + const killed = await sweepStaleOmoAgentSessions() + if (killed > 0) { + log("[tmux-session-manager] stale isolated sessions swept", { killed }) + } + this.staleSweepCompleted = true + } catch (error) { + log("[tmux-session-manager] stale sweep failed", { + error: String(error), + }) + } finally { + this.staleSweepInProgress = false + } + } } diff --git a/src/features/tmux-subagent/polling-manager.ts b/src/features/tmux-subagent/polling-manager.ts index d7a972d40..1a74be801 100644 --- a/src/features/tmux-subagent/polling-manager.ts +++ b/src/features/tmux-subagent/polling-manager.ts @@ -16,7 +16,8 @@ export class TmuxPollingManager { constructor( private client: OpencodeClient, private sessions: Map, - private closeSessionById: (sessionId: string) => Promise + private closeSessionById: (sessionId: string) => Promise, + private retryPendingCloses?: () => Promise ) {} handleEvent(event: { type: string; properties?: Record }): void { @@ -134,6 +135,14 @@ export class TmuxPollingManager { log("[tmux-session-manager] closing session due to poll", { sessionId }) await this.closeSessionById(sessionId) } + + if (this.retryPendingCloses) { + try { + await this.retryPendingCloses() + } catch (err) { + log("[tmux-session-manager] retry pending closes failed", { error: String(err) }) + } + } } catch (err) { log("[tmux-session-manager] poll error", { error: String(err) }) } finally { diff --git a/src/features/tmux-subagent/session-created-handler.ts b/src/features/tmux-subagent/session-created-handler.ts deleted file mode 100644 index 6dd1f21eb..000000000 --- a/src/features/tmux-subagent/session-created-handler.ts +++ /dev/null @@ -1,175 +0,0 @@ -import type { PluginInput } from "@opencode-ai/plugin" -import type { TmuxConfig } from "../../config/schema" -import type { CapacityConfig, TrackedSession } from "./types" -import { log } from "../../shared" -import { queryWindowState } from "./pane-state-querier" -import { decideSpawnActions, type SessionMapping } from "./decision-engine" -import { executeActions } from "./action-executor" -import type { SessionCreatedEvent } from "./session-created-event" -import { createTrackedSession } from "./tracked-session-state" - -type OpencodeClient = PluginInput["client"] - -export interface SessionCreatedHandlerDeps { - client: OpencodeClient - tmuxConfig: TmuxConfig - serverUrl: string - sourcePaneId: string | undefined - sessions: Map - pendingSessions: Set - isInsideTmux: () => boolean - isEnabled: () => boolean - getCapacityConfig: () => CapacityConfig - getSessionMappings: () => SessionMapping[] - waitForSessionReady: (sessionId: string) => Promise - startPolling: () => void -} - -export async function handleSessionCreated( - deps: SessionCreatedHandlerDeps, - event: SessionCreatedEvent, -): Promise { - const enabled = deps.isEnabled() - log("[tmux-session-manager] onSessionCreated called", { - enabled, - tmuxConfigEnabled: deps.tmuxConfig.enabled, - isInsideTmux: deps.isInsideTmux(), - eventType: event.type, - infoId: event.properties?.info?.id, - infoParentID: event.properties?.info?.parentID, - }) - - if (!enabled) return - if (event.type !== "session.created") return - - const info = event.properties?.info - if (!info?.id || !info?.parentID) return - - const sessionId = info.id - const title = info.title ?? "Subagent" - - if (deps.sessions.has(sessionId) || deps.pendingSessions.has(sessionId)) { - log("[tmux-session-manager] session already tracked or pending", { sessionId }) - return - } - - if (!deps.sourcePaneId) { - log("[tmux-session-manager] no source pane id") - return - } - - deps.pendingSessions.add(sessionId) - - try { - const state = await queryWindowState(deps.sourcePaneId) - if (!state) { - log("[tmux-session-manager] failed to query window state") - return - } - - log("[tmux-session-manager] window state queried", { - windowWidth: state.windowWidth, - mainPane: state.mainPane?.paneId, - agentPaneCount: state.agentPanes.length, - agentPanes: state.agentPanes.map((p) => p.paneId), - }) - - const decision = decideSpawnActions( - state, - sessionId, - title, - deps.getCapacityConfig(), - deps.getSessionMappings(), - ) - - log("[tmux-session-manager] spawn decision", { - canSpawn: decision.canSpawn, - reason: decision.reason, - actionCount: decision.actions.length, - actions: decision.actions.map((a) => { - if (a.type === "close") return { type: "close", paneId: a.paneId } - if (a.type === "replace") { - return { type: "replace", paneId: a.paneId, newSessionId: a.newSessionId } - } - return { type: "spawn", sessionId: a.sessionId } - }), - }) - - if (!decision.canSpawn) { - log("[tmux-session-manager] cannot spawn", { reason: decision.reason }) - return - } - - const result = await executeActions(decision.actions, { - config: deps.tmuxConfig, - serverUrl: deps.serverUrl, - windowState: state, - }) - - for (const { action, result: actionResult } of result.results) { - if (action.type === "close" && actionResult.success) { - deps.sessions.delete(action.sessionId) - log("[tmux-session-manager] removed closed session from cache", { - sessionId: action.sessionId, - }) - } - if (action.type === "replace" && actionResult.success) { - deps.sessions.delete(action.oldSessionId) - log("[tmux-session-manager] removed replaced session from cache", { - oldSessionId: action.oldSessionId, - newSessionId: action.newSessionId, - }) - } - } - - if (!result.success || !result.spawnedPaneId) { - log("[tmux-session-manager] spawn failed", { - success: result.success, - results: result.results.map((r) => ({ - type: r.action.type, - success: r.result.success, - error: r.result.error, - })), - }) - return - } - - const sessionReady = await deps.waitForSessionReady(sessionId) - if (!sessionReady) { - log("[tmux-session-manager] session not ready after timeout, closing spawned pane", { - sessionId, - paneId: result.spawnedPaneId, - }) - - await executeActions( - [{ type: "close", paneId: result.spawnedPaneId, sessionId }], - { - config: deps.tmuxConfig, - serverUrl: deps.serverUrl, - windowState: state, - }, - ) - - return - } - - deps.sessions.set( - sessionId, - createTrackedSession({ - sessionId, - paneId: result.spawnedPaneId, - description: title, - }), - ) - - log("[tmux-session-manager] pane spawned and tracked", { - sessionId, - paneId: result.spawnedPaneId, - sessionReady, - }) - - deps.startPolling() - } finally { - deps.pendingSessions.delete(sessionId) - } -} diff --git a/src/features/tmux-subagent/session-deleted-handler.ts b/src/features/tmux-subagent/session-deleted-handler.ts deleted file mode 100644 index f832cf481..000000000 --- a/src/features/tmux-subagent/session-deleted-handler.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { TmuxConfig } from "../../config/schema" -import type { TrackedSession } from "./types" -import { log } from "../../shared" -import { queryWindowState } from "./pane-state-querier" -import { decideCloseAction, type SessionMapping } from "./decision-engine" -import { executeAction } from "./action-executor" - -export interface SessionDeletedHandlerDeps { - tmuxConfig: TmuxConfig - serverUrl: string - sourcePaneId: string | undefined - sessions: Map - isEnabled: () => boolean - getSessionMappings: () => SessionMapping[] - stopPolling: () => void -} - -export async function handleSessionDeleted( - deps: SessionDeletedHandlerDeps, - event: { sessionID: string }, -): Promise { - if (!deps.isEnabled()) return - if (!deps.sourcePaneId) return - - const tracked = deps.sessions.get(event.sessionID) - if (!tracked) return - - log("[tmux-session-manager] onSessionDeleted", { sessionId: event.sessionID }) - - const state = await queryWindowState(deps.sourcePaneId) - if (!state) { - deps.sessions.delete(event.sessionID) - return - } - - const closeAction = decideCloseAction(state, event.sessionID, deps.getSessionMappings()) - if (closeAction) { - await executeAction(closeAction, { - config: deps.tmuxConfig, - serverUrl: deps.serverUrl, - windowState: state, - }) - } - - deps.sessions.delete(event.sessionID) - - if (deps.sessions.size === 0) { - deps.stopPolling() - } -} diff --git a/src/features/tool-metadata-store/index.ts b/src/features/tool-metadata-store/index.ts index f9c4e28ad..cc89a9f72 100644 --- a/src/features/tool-metadata-store/index.ts +++ b/src/features/tool-metadata-store/index.ts @@ -5,3 +5,10 @@ export { storeToolMetadata, } from "./store" export type { PendingToolMetadata } from "./store" +export { resolveToolCallID } from "./resolve-tool-call-id" +export type { ToolCallIDCarrier } from "./resolve-tool-call-id" +export { buildTaskMetadataBlock, extractTaskLink, parseTaskMetadataBlock } from "./task-metadata-contract" +export type { TaskLink } from "./task-metadata-contract" +export { publishToolMetadata } from "./publish-tool-metadata" +export { recoverToolMetadata } from "./recover-tool-metadata" +export type { ToolMetadataPublisherContext } from "./publish-tool-metadata" diff --git a/src/features/tool-metadata-store/integration.test.ts b/src/features/tool-metadata-store/integration.test.ts new file mode 100644 index 000000000..2604a5edf --- /dev/null +++ b/src/features/tool-metadata-store/integration.test.ts @@ -0,0 +1,96 @@ +import { beforeEach, describe, expect, test } from "bun:test" + +import { clearPendingStore, getPendingStoreSize } from "./store" +import { publishToolMetadata } from "./publish-tool-metadata" +import { recoverToolMetadata } from "./recover-tool-metadata" + +describe("tool-metadata-store integration", () => { + beforeEach(() => { + clearPendingStore() + }) + + test("#given stored metadata #when publishing then recovering #then the round trip preserves the payload", async () => { + // given + const payload = { title: "Task", metadata: { sessionId: "ses_child" } } + + // when + await publishToolMetadata({ sessionID: "ses_parent", callID: "call_123" }, payload) + const recovered = recoverToolMetadata("ses_parent", { callID: "call_123" }) + + // then + expect(recovered).toEqual(payload) + }) + + test("#given call id casing mismatch #when publishing and recovering #then canonical resolution still matches", async () => { + // given + const payload = { title: "Task", metadata: { sessionId: "ses_child" } } + + // when + await publishToolMetadata({ sessionID: "ses_parent", callId: "call_case" }, payload) + const recovered = recoverToolMetadata("ses_parent", { callID: "call_case" }) + + // then + expect(recovered).toEqual(payload) + }) + + test("#given blank call id #when publishing #then nothing is stored", async () => { + // given + const payload = { title: "Task" } + + // when + const result = await publishToolMetadata({ sessionID: "ses_parent", callID: " " }, payload) + const recovered = recoverToolMetadata("ses_parent", { callID: "call_blank" }) + + // then + expect(result).toEqual({ stored: false }) + expect(recovered).toBeUndefined() + expect(getPendingStoreSize()).toBe(0) + }) + + test("#given missing call id #when publishing #then nothing is stored", async () => { + // given + const payload = { title: "Task" } + + // when + const result = await publishToolMetadata({ sessionID: "ses_parent" }, payload) + + // then + expect(result).toEqual({ stored: false }) + expect(getPendingStoreSize()).toBe(0) + }) + + test("#given same session with different call ids #when publishing twice #then each entry stays isolated", async () => { + // given + await publishToolMetadata({ sessionID: "ses_parent", callID: "call_a" }, { title: "A" }) + await publishToolMetadata({ sessionID: "ses_parent", callID: "call_b" }, { title: "B" }) + + // when + const recoveredA = recoverToolMetadata("ses_parent", { callID: "call_a" }) + const recoveredB = recoverToolMetadata("ses_parent", { callID: "call_b" }) + + // then + expect(recoveredA).toEqual({ title: "A" }) + expect(recoveredB).toEqual({ title: "B" }) + }) + + test("#given stale metadata #when a fresh entry is stored after the timeout #then stale entries are cleaned up", async () => { + // given + const originalDateNow = Date.now + let now = 0 + Date.now = () => now + + try { + await publishToolMetadata({ sessionID: "ses_parent", callID: "call_old" }, { title: "Old" }) + now = 15 * 60 * 1000 + 1 + + // when + await publishToolMetadata({ sessionID: "ses_parent", callID: "call_new" }, { title: "New" }) + + // then + expect(recoverToolMetadata("ses_parent", { callID: "call_old" })).toBeUndefined() + expect(recoverToolMetadata("ses_parent", { callID: "call_new" })).toEqual({ title: "New" }) + } finally { + Date.now = originalDateNow + } + }) +}) diff --git a/src/features/tool-metadata-store/publish-tool-metadata.test.ts b/src/features/tool-metadata-store/publish-tool-metadata.test.ts new file mode 100644 index 000000000..704fe2392 --- /dev/null +++ b/src/features/tool-metadata-store/publish-tool-metadata.test.ts @@ -0,0 +1,76 @@ +import { beforeEach, describe, expect, test } from "bun:test" + +import { clearPendingStore, consumeToolMetadata } from "./store" +import { publishToolMetadata } from "./publish-tool-metadata" + +describe("publishToolMetadata", () => { + beforeEach(() => { + clearPendingStore() + }) + + test("#given metadata context and call id #when publishing #then it awaits metadata and stores the payload", async () => { + // given + const calls: string[] = [] + let metadataFinished = false + const payload = { title: "Task", metadata: { sessionId: "ses_child" } } + + // when + const result = await publishToolMetadata( + { + sessionID: "ses_parent", + callID: "call_123", + metadata: async input => { + calls.push(input.title ?? "") + await new Promise(resolve => setTimeout(resolve, 1)) + metadataFinished = true + }, + }, + payload + ) + + // then + expect(result).toEqual({ stored: true }) + expect(metadataFinished).toBe(true) + expect(calls).toEqual(["Task"]) + expect(consumeToolMetadata("ses_parent", "call_123")).toEqual(payload) + }) + + test("#given legacy call id variant #when publishing #then it stores with the canonical resolver", async () => { + // given + const payload = { title: "Task", metadata: { sessionId: "ses_child" } } + + // when + const result = await publishToolMetadata( + { + sessionID: "ses_parent", + callId: " call_legacy ", + }, + payload + ) + + // then + expect(result).toEqual({ stored: true }) + expect(consumeToolMetadata("ses_parent", "call_legacy")).toEqual(payload) + }) + + test("#given missing call id #when publishing #then it still emits metadata but skips storing", async () => { + // given + let metadataCalls = 0 + + // when + const result = await publishToolMetadata( + { + sessionID: "ses_parent", + metadata: () => { + metadataCalls += 1 + }, + }, + { title: "Task" } + ) + + // then + expect(result).toEqual({ stored: false }) + expect(metadataCalls).toBe(1) + expect(consumeToolMetadata("ses_parent", "call_missing")).toBeUndefined() + }) +}) diff --git a/src/features/tool-metadata-store/publish-tool-metadata.ts b/src/features/tool-metadata-store/publish-tool-metadata.ts new file mode 100644 index 000000000..84e69155e --- /dev/null +++ b/src/features/tool-metadata-store/publish-tool-metadata.ts @@ -0,0 +1,28 @@ +import { log } from "../../shared/logger" +import { resolveToolCallID, type ToolCallIDCarrier } from "./resolve-tool-call-id" +import { storeToolMetadata, type PendingToolMetadata } from "./store" + +export interface ToolMetadataPublisherContext extends ToolCallIDCarrier { + sessionID: string + metadata?: (input: PendingToolMetadata) => void | Promise +} + +export async function publishToolMetadata( + ctx: ToolMetadataPublisherContext, + payload: PendingToolMetadata +): Promise<{ stored: boolean }> { + await ctx.metadata?.(payload) + + const callID = resolveToolCallID(ctx) + if (!callID) { + log("[tool-metadata-store] Skipping metadata store publish because tool call ID is unavailable", { + sessionID: ctx.sessionID, + hasTitle: typeof payload.title === "string", + hasMetadata: payload.metadata !== undefined, + }) + return { stored: false } + } + + storeToolMetadata(ctx.sessionID, callID, payload) + return { stored: true } +} diff --git a/src/features/tool-metadata-store/recover-tool-metadata.test.ts b/src/features/tool-metadata-store/recover-tool-metadata.test.ts new file mode 100644 index 000000000..e97e87eea --- /dev/null +++ b/src/features/tool-metadata-store/recover-tool-metadata.test.ts @@ -0,0 +1,47 @@ +import { beforeEach, describe, expect, test } from "bun:test" + +import { recoverToolMetadata } from "./recover-tool-metadata" +import { clearPendingStore, storeToolMetadata } from "./store" + +describe("recoverToolMetadata", () => { + beforeEach(() => { + clearPendingStore() + }) + + test("#given stored metadata and call id variant #when recovering #then it finds the stored payload", () => { + // given + const payload = { title: "Recovered", metadata: { sessionId: "ses_child" } } + storeToolMetadata("ses_parent", "call_123", payload) + + // when + const recovered = recoverToolMetadata("ses_parent", { callId: " call_123 " }) + + // then + expect(recovered).toEqual(payload) + }) + + test("#given direct string call id #when recovering #then it consumes the stored payload", () => { + // given + const payload = { title: "Recovered" } + storeToolMetadata("ses_parent", "call_456", payload) + + // when + const recovered = recoverToolMetadata("ses_parent", "call_456") + + // then + expect(recovered).toEqual(payload) + }) + + test("#given missing or blank call id #when recovering #then it returns undefined", () => { + // given + storeToolMetadata("ses_parent", "call_789", { title: "Recovered" }) + + // when + const missing = recoverToolMetadata("ses_parent", undefined) + const blank = recoverToolMetadata("ses_parent", { callID: " " }) + + // then + expect(missing).toBeUndefined() + expect(blank).toBeUndefined() + }) +}) diff --git a/src/features/tool-metadata-store/recover-tool-metadata.ts b/src/features/tool-metadata-store/recover-tool-metadata.ts new file mode 100644 index 000000000..17a2278e6 --- /dev/null +++ b/src/features/tool-metadata-store/recover-tool-metadata.ts @@ -0,0 +1,18 @@ +import { consumeToolMetadata, type PendingToolMetadata } from "./store" +import { resolveToolCallID, type ToolCallIDCarrier } from "./resolve-tool-call-id" + +export function recoverToolMetadata( + sessionID: string, + source: ToolCallIDCarrier | string | undefined +): PendingToolMetadata | undefined { + if (typeof source === "string") { + return consumeToolMetadata(sessionID, source) + } + + const callID = source ? resolveToolCallID(source) : undefined + if (!callID) { + return undefined + } + + return consumeToolMetadata(sessionID, callID) +} diff --git a/src/features/tool-metadata-store/resolve-tool-call-id.test.ts b/src/features/tool-metadata-store/resolve-tool-call-id.test.ts new file mode 100644 index 000000000..2ad63a516 --- /dev/null +++ b/src/features/tool-metadata-store/resolve-tool-call-id.test.ts @@ -0,0 +1,88 @@ +import { describe, expect, test } from "bun:test" + +import { resolveToolCallID, type ToolCallIDCarrier } from "./resolve-tool-call-id" + +describe("resolveToolCallID", () => { + function makeCtx(overrides: Partial = {}): ToolCallIDCarrier { + return { + ...overrides, + } + } + + test("#given callID is set #when resolving #then it returns callID", () => { + // given + const ctx = makeCtx({ callID: "call_abc" }) + + // when + const result = resolveToolCallID(ctx) + + // then + expect(result).toBe("call_abc") + }) + + test("#given only callId is set #when resolving #then it returns callId", () => { + // given + const ctx = makeCtx({ callId: "call_def" }) + + // when + const result = resolveToolCallID(ctx) + + // then + expect(result).toBe("call_def") + }) + + test("#given only call_id is set #when resolving #then it returns call_id", () => { + // given + const ctx = makeCtx({ call_id: "call_ghi" }) + + // when + const result = resolveToolCallID(ctx) + + // then + expect(result).toBe("call_ghi") + }) + + test("#given surrounding whitespace #when resolving #then it trims the value", () => { + // given + const ctx = makeCtx({ callID: " call_trimmed " }) + + // when + const result = resolveToolCallID(ctx) + + // then + expect(result).toBe("call_trimmed") + }) + + test("#given blank callID #when resolving #then it returns undefined", () => { + // given + const ctx = makeCtx({ callID: "" }) + + // when + const result = resolveToolCallID(ctx) + + // then + expect(result).toBeUndefined() + }) + + test("#given whitespace callID #when resolving #then it returns undefined", () => { + // given + const ctx = makeCtx({ callID: " " }) + + // when + const result = resolveToolCallID(ctx) + + // then + expect(result).toBeUndefined() + }) + + test("#given no call id variants #when resolving #then it returns undefined", () => { + // given + const ctx = makeCtx() + + // when + const result = resolveToolCallID(ctx) + + // then + expect(result).toBeUndefined() + }) +}) diff --git a/src/features/tool-metadata-store/resolve-tool-call-id.ts b/src/features/tool-metadata-store/resolve-tool-call-id.ts new file mode 100644 index 000000000..d5879ae5f --- /dev/null +++ b/src/features/tool-metadata-store/resolve-tool-call-id.ts @@ -0,0 +1,26 @@ +import { log } from "../../shared/logger" + +export interface ToolCallIDCarrier { + callID?: string + callId?: string + call_id?: string +} + +function normalizeCallID(value: unknown): string | undefined { + if (typeof value !== "string") { + return undefined + } + + const trimmed = value.trim() + return trimmed === "" ? undefined : trimmed +} + +export function resolveToolCallID(ctx: ToolCallIDCarrier): string | undefined { + const resolved = normalizeCallID(ctx.callID) ?? normalizeCallID(ctx.callId) ?? normalizeCallID(ctx.call_id) + + if (!resolved) { + log("[tool-metadata-store] Missing tool call ID for metadata correlation") + } + + return resolved +} diff --git a/src/features/tool-metadata-store/task-metadata-contract.test.ts b/src/features/tool-metadata-store/task-metadata-contract.test.ts new file mode 100644 index 000000000..0b69fc13e --- /dev/null +++ b/src/features/tool-metadata-store/task-metadata-contract.test.ts @@ -0,0 +1,116 @@ +import { describe, expect, test } from "bun:test" + +import { buildTaskMetadataBlock, extractTaskLink, parseTaskMetadataBlock } from "./task-metadata-contract" + +describe("buildTaskMetadataBlock", () => { + test("#given only session id #when building #then it preserves the frozen block format", () => { + // given + const link = { sessionId: "ses_abc" } + + // when + const block = buildTaskMetadataBlock(link) + + // then + expect(block).toBe("\nsession_id: ses_abc\n") + }) + + test("#given extended task metadata #when building #then it emits optional lines in order", () => { + // given + const link = { + sessionId: "ses_bg_123", + taskId: "ses_bg_123", + backgroundTaskId: "bg_123", + agent: "explore", + category: "quick", + } + + // when + const block = buildTaskMetadataBlock(link) + + // then + expect(block).toBe( + "\nsession_id: ses_bg_123\ntask_id: ses_bg_123\nbackground_task_id: bg_123\nsubagent: explore\ncategory: quick\n" + ) + }) +}) + +describe("parseTaskMetadataBlock", () => { + test("#given a task metadata block #when parsing #then it extracts the structured link", () => { + // given + const text = "\nsession_id: ses_sync_123\ntask_id: task_123\nbackground_task_id: bg_123\nsubagent: oracle\ncategory: deep\n" + + // when + const parsed = parseTaskMetadataBlock(text) + + // then + expect(parsed).toEqual({ + sessionId: "ses_sync_123", + taskId: "task_123", + backgroundTaskId: "bg_123", + agent: "oracle", + category: "deep", + }) + }) + + test("#given text without metadata #when parsing #then it returns an empty link", () => { + // given + const text = "Task completed without metadata" + + // when + const parsed = parseTaskMetadataBlock(text) + + // then + expect(parsed).toEqual({}) + }) +}) + +describe("extractTaskLink", () => { + test("#given metadata session aliases #when extracting #then metadata wins over output text", () => { + // given + const metadata = { + sessionID: "ses_meta_123", + task_id: "task_meta_123", + background_task_id: "bg_meta_123", + subagent: "atlas", + category: "unspecified-high", + } + const output = "\nsession_id: ses_text_456\n" + + // when + const extracted = extractTaskLink(metadata, output) + + // then + expect(extracted).toEqual({ + sessionId: "ses_meta_123", + taskId: "task_meta_123", + backgroundTaskId: "bg_meta_123", + agent: "atlas", + category: "unspecified-high", + }) + }) + + test("#given missing metadata #when extracting #then it falls back to task metadata text", () => { + // given + const output = "Task completed.\n\n\nsession_id: ses_text_456\nsubagent: oracle\n" + + // when + const extracted = extractTaskLink(undefined, output) + + // then + expect(extracted).toEqual({ + sessionId: "ses_text_456", + agent: "oracle", + }) + }) + + test("#given explicit session id output #when extracting #then it preserves Session ID compatibility", () => { + // given + const output = "Background task launched.\n\nSession ID: ses_bg_789" + + // when + const extracted = extractTaskLink(undefined, output) + + // then + expect(extracted).toEqual({ sessionId: "ses_bg_789" }) + }) +}) diff --git a/src/features/tool-metadata-store/task-metadata-contract.ts b/src/features/tool-metadata-store/task-metadata-contract.ts new file mode 100644 index 000000000..4044482e5 --- /dev/null +++ b/src/features/tool-metadata-store/task-metadata-contract.ts @@ -0,0 +1,144 @@ +import { log } from "../../shared/logger" + +export interface TaskLink { + sessionId?: string + taskId?: string + backgroundTaskId?: string + agent?: string + category?: string +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null +} + +function readString(value: unknown): string | undefined { + if (typeof value !== "string") { + return undefined + } + + const trimmed = value.trim() + return trimmed === "" ? undefined : trimmed +} + +function readSessionIdFromMetadata(metadata: Record): string | undefined { + return readString(metadata.sessionId) ?? readString(metadata.sessionID) ?? readString(metadata.session_id) +} + +function readTaskIdFromMetadata(metadata: Record): string | undefined { + return readString(metadata.taskId) ?? readString(metadata.taskID) ?? readString(metadata.task_id) +} + +function readBackgroundTaskIdFromMetadata(metadata: Record): string | undefined { + return readString(metadata.backgroundTaskId) + ?? readString(metadata.backgroundTaskID) + ?? readString(metadata.background_task_id) +} + +function readAgentFromMetadata(metadata: Record): string | undefined { + return readString(metadata.agent) ?? readString(metadata.subagent) +} + +function readCategoryFromMetadata(metadata: Record): string | undefined { + return readString(metadata.category) +} + +function extractTaskMetadataContent(text: string): string | undefined { + const blocks = [...text.matchAll(/([\s\S]*?)<\/task_metadata>/gi)] + return blocks.at(-1)?.[1] +} + +function extractExplicitSessionId(text: string): string | undefined { + const matches = [...text.matchAll(/Session ID:\s*(ses_[a-zA-Z0-9_-]+)/g)] + return matches.at(-1)?.[1] +} + +export function buildTaskMetadataBlock(link: TaskLink): string { + const lines: string[] = [] + + if (link.sessionId) { + lines.push(`session_id: ${link.sessionId}`) + } + if (link.taskId) { + lines.push(`task_id: ${link.taskId}`) + } + if (link.backgroundTaskId) { + lines.push(`background_task_id: ${link.backgroundTaskId}`) + } + if (link.agent) { + lines.push(`subagent: ${link.agent}`) + } + if (link.category) { + lines.push(`category: ${link.category}`) + } + + return `\n${lines.join("\n")}\n` +} + +export function parseTaskMetadataBlock(text: string): TaskLink { + const blockContent = extractTaskMetadataContent(text) ?? text + const lines = blockContent + .split("\n") + .map(line => line.trim()) + .filter(Boolean) + + const parsed: TaskLink = {} + + for (const line of lines) { + const separatorIndex = line.indexOf(":") + if (separatorIndex === -1) { + continue + } + + const key = line.slice(0, separatorIndex).trim().toLowerCase() + const value = readString(line.slice(separatorIndex + 1)) + + if (!value) { + continue + } + + if (key === "session_id") { + parsed.sessionId = value + } else if (key === "task_id") { + parsed.taskId = value + } else if (key === "background_task_id") { + parsed.backgroundTaskId = value + } else if (key === "subagent" || key === "agent") { + parsed.agent = value + } else if (key === "category") { + parsed.category = value + } + } + + return parsed +} + +export function extractTaskLink(metadata: unknown, outputText: string): TaskLink { + if (isRecord(metadata)) { + const metadataLink: TaskLink = { + sessionId: readSessionIdFromMetadata(metadata), + taskId: readTaskIdFromMetadata(metadata), + backgroundTaskId: readBackgroundTaskIdFromMetadata(metadata), + agent: readAgentFromMetadata(metadata), + category: readCategoryFromMetadata(metadata), + } + + if (metadataLink.sessionId || metadataLink.taskId || metadataLink.backgroundTaskId || metadataLink.agent || metadataLink.category) { + return metadataLink + } + } + + const parsed = parseTaskMetadataBlock(outputText) + if (parsed.sessionId || parsed.taskId || parsed.backgroundTaskId || parsed.agent || parsed.category) { + log("[tool-metadata-store] Falling back to parsing") + return parsed + } + + const explicitSessionId = extractExplicitSessionId(outputText) + if (explicitSessionId) { + log("[tool-metadata-store] Falling back to explicit Session ID parsing") + return { sessionId: explicitSessionId } + } + + return {} +} diff --git a/src/generated/model-capabilities.generated.json b/src/generated/model-capabilities.generated.json index 4d51ec888..b307aeadd 100644 --- a/src/generated/model-capabilities.generated.json +++ b/src/generated/model-capabilities.generated.json @@ -1,223 +1,12 @@ { - "generatedAt": "2026-03-25T13:44:08.677Z", + "generatedAt": "2026-04-17T05:38:15.814Z", "sourceUrl": "https://models.dev/api.json", "models": { - "nvidia/llama-3.3-70b-instruct-fp8": { - "id": "nvidia/Llama-3.3-70B-Instruct-FP8", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "microsoft/phi-4-multimodal-instruct": { - "id": "microsoft/phi-4-multimodal-instruct", - "family": "phi", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - }, - "temperature": true - }, - "intfloat/multilingual-e5-large-instruct": { - "id": "intfloat/multilingual-e5-large-instruct", - "family": "text-embedding", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 512, - "output": 1024 - }, - "temperature": false - }, - "moonshotai/kimi-k2.5": { - "id": "moonshotai/kimi-k2.5", - "family": "kimi", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 65536, - "input": 256000 - }, - "temperature": true - }, - "kblab/kb-whisper-large": { - "id": "KBLab/kb-whisper-large", - "family": "whisper", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 480000, - "output": 4800 - }, - "temperature": false - }, - "qwen/qwen3-30b-a3b-instruct-2507-fp8": { - "id": "Qwen/Qwen3-30B-A3B-Instruct-2507-FP8", + "qwen3-235b-a22b": { + "id": "qwen3-235b-a22b", "family": "qwen", "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 64000, - "output": 64000 - } - }, - "qwen/qwen3-embedding-8b": { - "id": "Qwen/Qwen3-Embedding-8B", - "family": "qwen", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 4096, - "input": 32768 - }, - "temperature": false - }, - "qwen/qwen3-vl-30b-a3b-instruct": { - "id": "qwen/qwen3-vl-30b-a3b-instruct", - "family": "qwen", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - }, - "temperature": true - }, - "mistralai/voxtral-small-24b-2507": { - "id": "mistralai/voxtral-small-24b-2507", - "family": "voxtral", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 6400 - }, - "temperature": true - }, - "mistralai/devstral-small-2-24b-instruct-2512": { - "id": "mistralai/devstral-small-2-24b-instruct-2512", - "family": "devstral", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "mistralai/magistral-small-2509": { - "id": "mistralai/Magistral-Small-2509", - "family": "magistral-small", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "openai/gpt-oss-120b": { - "id": "openai/gpt-oss-120b", - "family": "gpt-oss", - "reasoning": true, + "temperature": true, "toolCall": true, "modalities": { "input": [ @@ -229,172 +18,18 @@ }, "limit": { "context": 128000, - "output": 16384, - "input": 128000 - }, - "temperature": true + "output": 32000 + } }, - "openai/whisper-large-v3": { - "id": "openai/whisper-large-v3", - "family": "whisper", + "grok-4.1": { + "id": "grok-4.1", "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 0, - "output": 4096 - }, - "temperature": false - }, - "glm-5": { - "id": "glm-5", - "family": "glm", - "reasoning": true, "temperature": true, "toolCall": true, "modalities": { "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 16384 - } - }, - "glm-4.5-air": { - "id": "glm-4.5-air", - "family": "glm-air", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "glm-4.5": { - "id": "glm-4.5", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "glm-4.5-flash": { - "id": "glm-4.5-flash", - "family": "glm-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 98304 - } - }, - "glm-4.7-flash": { - "id": "glm-4.7-flash", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 203000, - "output": 203000 - } - }, - "glm-4.6": { - "id": "glm-4.6", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "glm-4.7": { - "id": "glm-4.7", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 198000, - "output": 198000 - } - }, - "glm-5-turbo": { - "id": "glm-5-turbo", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -402,234 +37,11 @@ }, "limit": { "context": 200000, - "output": 131072 - } - }, - "glm-4.5v": { - "id": "glm-4.5v", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 64000, - "output": 16384 - } - }, - "glm-4.6v": { - "id": "glm-4.6v", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "minimax-m2.5": { - "id": "MiniMax-M2.5", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "input": 196601, - "output": 131072 - } - }, - "qwen3-coder-next": { - "id": "qwen3-coder-next", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536, - "input": 262144 - } - }, - "kimi-k2.5": { - "id": "kimi-k2.5", - "family": "kimi", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - } - }, - "qwen3-max-2026-01-23": { - "id": "qwen3-max-2026-01-23", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32768, - "input": 256000 - } - }, - "qwen3.5-plus": { - "id": "qwen3.5-plus", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 65536 - } - }, - "qwen3-coder-plus": { - "id": "qwen3-coder-plus", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - } - }, - "xiaomi/mimo-v2-omni": { - "id": "xiaomi/mimo-v2-omni", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - }, - "family": "mimo" - }, - "xiaomi/mimo-v2-flash-free": { - "id": "xiaomi/mimo-v2-flash-free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262000, "output": 64000 } }, - "xiaomi/mimo-v2-flash": { - "id": "xiaomi/mimo-v2-flash", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32768, - "input": 256000 - }, - "family": "mimo" - }, - "xiaomi/mimo-v2-pro": { - "id": "xiaomi/mimo-v2-pro", + "minimax-m2": { + "id": "MiniMax-M2", "reasoning": true, "temperature": true, "toolCall": true, @@ -642,201 +54,14 @@ ] }, "limit": { - "context": 1000000, - "output": 128000 + "context": 196608, + "output": 128000, + "input": 200000 }, - "family": "mimo" + "family": "minimax" }, - "kuaishou/kat-coder-pro-v1-free": { - "id": "kuaishou/kat-coder-pro-v1-free", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 64000 - } - }, - "kuaishou/kat-coder-pro-v1": { - "id": "kuaishou/kat-coder-pro-v1", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 64000 - } - }, - "stepfun/step-3.5-flash-free": { - "id": "stepfun/step-3.5-flash-free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 64000 - } - }, - "stepfun/step-3.5-flash": { - "id": "stepfun/step-3.5-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - }, - "family": "step" - }, - "stepfun/step-3": { - "id": "stepfun/step-3", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 64000 - } - }, - "inclusionai/ling-1t": { - "id": "inclusionai/ling-1t", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 64000 - } - }, - "inclusionai/ring-1t": { - "id": "inclusionai/ring-1t", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 64000 - } - }, - "volcengine/doubao-seed-1.8": { - "id": "volcengine/doubao-seed-1.8", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 64000 - } - }, - "volcengine/doubao-seed-2.0-pro": { - "id": "volcengine/doubao-seed-2.0-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 64000 - } - }, - "volcengine/doubao-seed-2.0-mini": { - "id": "volcengine/doubao-seed-2.0-mini", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 64000 - } - }, - "volcengine/doubao-seed-code": { - "id": "volcengine/doubao-seed-code", + "grok-4-1-fast-reasoning": { + "id": "grok-4-1-fast-reasoning", "reasoning": true, "temperature": true, "toolCall": true, @@ -850,108 +75,34 @@ ] }, "limit": { - "context": 256000, - "output": 64000 - } - }, - "volcengine/doubao-seed-2.0-lite": { - "id": "volcengine/doubao-seed-2.0-lite", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] + "context": 128000, + "output": 8192, + "input": 128000 }, - "limit": { - "context": 256000, - "output": 64000 - } + "family": "grok" }, - "volcengine/doubao-seed-2.0-code": { - "id": "volcengine/doubao-seed-2.0-code", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32000 - } - }, - "deepseek/deepseek-v3.2": { - "id": "deepseek/deepseek-v3.2", + "gemini-2.5-flash-nothink": { + "id": "gemini-2.5-flash-nothink", + "family": "gemini-flash", "reasoning": false, "temperature": true, "toolCall": true, "modalities": { "input": [ "text", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 163000, - "output": 65536, - "input": 163000 - }, - "family": "deepseek" - }, - "deepseek/deepseek-chat": { - "id": "deepseek/deepseek-chat", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 163840 - } - }, - "deepseek/deepseek-v3.2-exp": { - "id": "deepseek/deepseek-v3.2-exp", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, + "context": 1000000, "output": 65536 - }, - "family": "deepseek" + } }, - "moonshotai/kimi-k2-0905": { - "id": "moonshotai/kimi-k2-0905", + "kimi-k2-0905-preview": { + "id": "kimi-k2-0905-preview", "reasoning": false, "temperature": true, "toolCall": true, @@ -964,52 +115,13 @@ ] }, "limit": { - "context": 131072, - "output": 26215 + "context": 262144, + "output": 262144 }, "family": "kimi" }, - "moonshotai/kimi-k2-thinking": { - "id": "moonshotai/kimi-k2-thinking", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 262144, - "input": 256000 - }, - "family": "kimi-thinking" - }, - "moonshotai/kimi-k2-thinking-turbo": { - "id": "moonshotai/kimi-k2-thinking-turbo", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262114, - "output": 262114 - }, - "family": "kimi-thinking" - }, - "baidu/ernie-5.0-thinking-preview": { - "id": "baidu/ernie-5.0-thinking-preview", + "claude-opus-4-5-20251101": { + "id": "claude-opus-4-5-20251101", "reasoning": true, "temperature": true, "toolCall": true, @@ -1017,223 +129,7 @@ "input": [ "text", "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 64000 - } - }, - "google/gemini-2.5-flash": { - "id": "google/gemini-2.5-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio", - "image", - "pdf", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65535 - }, - "family": "gemini-flash" - }, - "google/gemini-3.1-flash-lite-preview": { - "id": "google/gemini-3.1-flash-lite-preview", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio", - "image", - "pdf", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - }, - "family": "gemini" - }, - "google/gemini-3-flash-preview": { - "id": "google/gemini-3-flash-preview", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048756, - "output": 65536, - "input": 1048756 - }, - "family": "gemini-flash" - }, - "google/gemini-2.5-flash-lite": { - "id": "google/gemini-2.5-flash-lite", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio", - "image", - "pdf", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65535 - }, - "family": "gemini-flash-lite" - }, - "google/gemini-3.1-pro-preview": { - "id": "google/gemini-3.1-pro-preview", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio", - "image", - "pdf", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - }, - "family": "gemini" - }, - "google/gemini-3-pro-image-preview": { - "id": "google/gemini-3-pro-image-preview", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "image", - "text" - ] - }, - "limit": { - "context": 65536, - "output": 32768 - } - }, - "google/gemini-3-pro-preview": { - "id": "google/gemini-3-pro-preview", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio", - "image", - "pdf", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - }, - "family": "gemini-pro" - }, - "google/gemini-2.5-pro": { - "id": "google/gemini-2.5-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio", - "image", - "pdf", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - }, - "family": "gemini-pro" - }, - "z-ai/glm-5": { - "id": "z-ai/glm-5", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 131072 - }, - "family": "glm" - }, - "z-ai/glm-4.7-flashx": { - "id": "z-ai/glm-4.7-flashx", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" + "pdf" ], "output": [ "text" @@ -1241,3830 +137,13 @@ }, "limit": { "context": 200000, - "output": 64000 - } - }, - "z-ai/glm-4.5-air": { - "id": "z-ai/glm-4.5-air", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 98304 - }, - "family": "glm-air" - }, - "z-ai/glm-4.5": { - "id": "z-ai/glm-4.5", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 98304 - }, - "family": "glm" - }, - "z-ai/glm-4.6v-flash-free": { - "id": "z-ai/glm-4.6v-flash-free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "z-ai/glm-4.6": { - "id": "z-ai/glm-4.6", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 65535, + "output": 64000, "input": 200000 }, - "family": "glm" - }, - "z-ai/glm-4.7": { - "id": "z-ai/glm-4.7", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 65535 - }, - "family": "glm" - }, - "z-ai/glm-4.7-flash-free": { - "id": "z-ai/glm-4.7-flash-free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "z-ai/glm-4.6v-flash": { - "id": "z-ai/glm-4.6v-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "z-ai/glm-5-turbo": { - "id": "z-ai/glm-5-turbo", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 128000 - } - }, - "z-ai/glm-4.6v": { - "id": "z-ai/glm-4.6v", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "qwen/qwen3.5-flash": { - "id": "qwen/qwen3.5-flash", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1020000, - "output": 1020000 - } - }, - "qwen/qwen3.5-plus": { - "id": "Qwen/Qwen3.5-Plus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 65536 - }, - "family": "qwen" - }, - "qwen/qwen3-max": { - "id": "qwen/qwen3-max", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - }, - "family": "qwen" - }, - "qwen/qwen3-coder-plus": { - "id": "qwen/qwen3-coder-plus", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 65536 - } - }, - "x-ai/grok-code-fast-1": { - "id": "x-ai/grok-code-fast-1", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 131072, - "input": 256000 - }, - "family": "grok" - }, - "x-ai/grok-4-fast": { - "id": "x-ai/grok-4-fast", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 131072, - "input": 2000000 - }, - "family": "grok" - }, - "x-ai/grok-4": { - "id": "x-ai/grok-4", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 51200 - }, - "family": "grok" - }, - "x-ai/grok-4.1-fast-non-reasoning": { - "id": "x-ai/grok-4.1-fast-non-reasoning", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 2000000 - } - }, - "x-ai/grok-4.1-fast": { - "id": "x-ai/grok-4.1-fast", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 131072, - "input": 2000000 - }, - "family": "grok" - }, - "x-ai/grok-4.2-fast": { - "id": "x-ai/grok-4.2-fast", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 30000 - } - }, - "x-ai/grok-4.2-fast-non-reasoning": { - "id": "x-ai/grok-4.2-fast-non-reasoning", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 30000 - } - }, - "openai/gpt-5.3-codex": { - "id": "openai/gpt-5.3-codex", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000, - "input": 272000 - }, - "family": "gpt" - }, - "openai/gpt-5-codex": { - "id": "openai/gpt-5-codex", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32768, - "input": 256000 - }, - "family": "gpt-codex" - }, - "openai/gpt-5.2-codex": { - "id": "openai/gpt-5.2-codex", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000, - "input": 400000 - }, - "family": "gpt-codex" - }, - "openai/gpt-5.1": { - "id": "openai/gpt-5.1", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000, - "input": 400000 - }, - "family": "gpt" - }, - "openai/gpt-5.1-chat": { - "id": "openai/gpt-5.1-chat", - "reasoning": true, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000, - "input": 400000 - }, - "family": "gpt" - }, - "openai/gpt-5.1-codex-mini": { - "id": "openai/gpt-5.1-codex-mini", - "reasoning": true, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000, - "input": 400000 - }, - "family": "gpt-codex-mini" - }, - "openai/gpt-5.2": { - "id": "openai/gpt-5.2", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000, - "input": 400000 - }, - "family": "gpt" - }, - "openai/gpt-5": { - "id": "openai/gpt-5", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000, - "input": 400000 - }, - "family": "gpt" - }, - "openai/gpt-5.4": { - "id": "openai/gpt-5.4", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1050000, - "output": 128000, - "input": 922000 - }, - "family": "gpt" - }, - "openai/gpt-5.4-pro": { - "id": "openai/gpt-5.4-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1050000, - "output": 128000, - "input": 922000 - }, - "family": "gpt" - }, - "openai/gpt-5.3-chat": { - "id": "openai/gpt-5.3-chat", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384, - "input": 111616 - }, - "family": "gpt" - }, - "openai/gpt-5.1-codex": { - "id": "openai/gpt-5.1-codex", - "reasoning": true, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000, - "input": 400000 - }, - "family": "gpt-codex" - }, - "openai/gpt-5.2-pro": { - "id": "openai/gpt-5.2-pro", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000, - "input": 400000 - }, - "family": "gpt-pro" - }, - "openai/gpt-5.4-nano": { - "id": "openai/gpt-5.4-nano", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000, - "input": 272000 - }, - "family": "gpt" - }, - "openai/gpt-5.4-mini": { - "id": "openai/gpt-5.4-mini", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000, - "input": 272000 - }, - "family": "gpt" - }, - "minimax/minimax-m2.5-lightning": { - "id": "minimax/minimax-m2.5-lightning", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "minimax/minimax-m2.1": { - "id": "minimax/minimax-m2.1", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 131072, - "input": 200000 - }, - "family": "minimax" - }, - "minimax/minimax-m2.7": { - "id": "minimax/minimax-m2.7", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072, - "input": 204800 - }, - "family": "minimax" - }, - "minimax/minimax-m2.7-highspeed": { - "id": "minimax/minimax-m2.7-highspeed", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131100 - }, - "family": "minimax" - }, - "minimax/minimax-m2": { - "id": "minimax/minimax-m2", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 196608, - "output": 196608 - }, - "family": "minimax" - }, - "minimax/minimax-m2.5": { - "id": "MiniMax/MiniMax-M2.5", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072, - "input": 204800 - }, - "family": "minimax" - }, - "anthropic/claude-3.5-sonnet": { - "id": "anthropic/claude-3.5-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192 - }, - "family": "claude-sonnet" - }, - "anthropic/claude-3.7-sonnet": { - "id": "anthropic/claude-3.7-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - }, - "family": "claude-sonnet" - }, - "anthropic/claude-opus-4.1": { - "id": "anthropic/claude-opus-4.1", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - }, "family": "claude-opus" }, - "anthropic/claude-sonnet-4.6": { - "id": "anthropic/claude-sonnet-4.6", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000, - "input": 1000000 - }, - "family": "claude-sonnet" - }, - "anthropic/claude-haiku-4.5": { - "id": "anthropic/claude-haiku-4.5", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - }, - "family": "claude-haiku" - }, - "anthropic/claude-3.5-haiku": { - "id": "anthropic/claude-3.5-haiku", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192 - }, - "family": "claude-haiku" - }, - "anthropic/claude-opus-4.5": { - "id": "anthropic/claude-opus-4.5", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - }, - "family": "claude-opus" - }, - "anthropic/claude-opus-4": { - "id": "anthropic/claude-opus-4", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - }, - "family": "claude-opus" - }, - "anthropic/claude-sonnet-4": { - "id": "anthropic/claude-sonnet-4", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - }, - "family": "claude-sonnet" - }, - "anthropic/claude-sonnet-4.5": { - "id": "anthropic/claude-sonnet-4.5", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - }, - "family": "claude-sonnet" - }, - "anthropic/claude-opus-4.6": { - "id": "anthropic/claude-opus-4.6", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000, - "input": 1000000 - }, - "family": "claude-opus" - }, - "zai-org/glm-4.6": { - "id": "zai-org/GLM-4.6", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 131072 - } - }, - "deepseek-ai/deepseek-r1-0528": { - "id": "deepseek-ai/DeepSeek-R1-0528", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 163840, - "input": 128000 - } - }, - "intel/qwen3-coder-480b-a35b-instruct-int4-mixed-ar": { - "id": "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 106000, - "output": 4096 - } - }, - "moonshotai/kimi-k2-instruct-0905": { - "id": "moonshotai/Kimi-K2-Instruct-0905", - "family": "kimi", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144, - "input": 256000 - } - }, - "meta-llama/llama-3.2-90b-vision-instruct": { - "id": "meta-llama/llama-3.2-90b-vision-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384, - "input": 131072 - } - }, - "meta-llama/llama-3.3-70b-instruct": { - "id": "meta-llama/llama-3.3-70b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384, - "input": 131072 - } - }, - "meta-llama/llama-4-maverick-17b-128e-instruct-fp8": { - "id": "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 32768 - } - }, - "qwen/qwen3-next-80b-a3b-instruct": { - "id": "Qwen/Qwen3-Next-80B-A3B-Instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "qwen/qwen3-235b-a22b-thinking-2507": { - "id": "Qwen/Qwen3-235B-A22B-Thinking-2507", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "qwen/qwen2.5-vl-32b-instruct": { - "id": "Qwen/Qwen2.5-VL-32B-Instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 16384 - } - }, - "mistralai/mistral-nemo-instruct-2407": { - "id": "mistralai/Mistral-Nemo-Instruct-2407", - "family": "mistral-nemo", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 8192, - "input": 16384 - } - }, - "mistralai/magistral-small-2506": { - "id": "mistralai/Magistral-Small-2506", - "family": "magistral-small", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "mistralai/mistral-large-instruct-2411": { - "id": "mistralai/Mistral-Large-Instruct-2411", - "family": "mistral-large", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "mistralai/devstral-small-2505": { - "id": "mistralai/Devstral-Small-2505", - "family": "devstral", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 8192, - "input": 32768 - } - }, - "openai/gpt-oss-20b": { - "id": "openai/gpt-oss-20b", - "family": "gpt-oss", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072, - "input": 128000 - } - }, - "nvidia/nemotron-3-super-120b-a12b": { - "id": "nvidia/nemotron-3-super-120b-a12b", - "family": "nemotron", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144, - "input": 256000 - } - }, - "nvidia/llama-3.1-nemotron-70b-instruct": { - "id": "nvidia/llama-3.1-nemotron-70b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "nvidia/llama-3.1-nemotron-ultra-253b-v1": { - "id": "nvidia/Llama-3.1-Nemotron-Ultra-253B-v1", - "family": "nemotron", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384, - "input": 128000 - } - }, - "nvidia/llama-3.1-nemotron-51b-instruct": { - "id": "nvidia/llama-3.1-nemotron-51b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "nvidia/parakeet-tdt-0.6b-v2": { - "id": "nvidia/parakeet-tdt-0.6b-v2", - "family": "parakeet", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 0, - "output": 4096 - } - }, - "nvidia/nvidia-nemotron-nano-9b-v2": { - "id": "nvidia/nvidia-nemotron-nano-9b-v2", - "family": "nemotron", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384, - "input": 128000 - } - }, - "nvidia/llama-embed-nemotron-8b": { - "id": "nvidia/llama-embed-nemotron-8b", - "family": "llama", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 2048 - } - }, - "nvidia/llama-3.3-nemotron-super-49b-v1.5": { - "id": "nvidia/llama-3.3-nemotron-super-49b-v1.5", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 26215 - } - }, - "nvidia/llama-3.3-nemotron-super-49b-v1": { - "id": "nvidia/Llama-3.3-Nemotron-Super-49B-v1", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384, - "input": 128000 - }, - "family": "nemotron" - }, - "nvidia/llama3-chatqa-1.5-70b": { - "id": "nvidia/llama3-chatqa-1.5-70b", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "nvidia/cosmos-nemotron-34b": { - "id": "nvidia/cosmos-nemotron-34b", - "family": "nemotron", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "nvidia/nemoretriever-ocr-v1": { - "id": "nvidia/nemoretriever-ocr-v1", - "family": "nemoretriever", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 0, - "output": 4096 - } - }, - "nvidia/nemotron-4-340b-instruct": { - "id": "nvidia/nemotron-4-340b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "nvidia/nemotron-3-nano-30b-a3b": { - "id": "nvidia/nemotron-3-nano-30b-a3b", - "family": "nemotron", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 262144, - "input": 256000 - } - }, - "microsoft/phi-3-small-128k-instruct": { - "id": "microsoft/phi-3-small-128k-instruct", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - }, - "family": "phi" - }, - "microsoft/phi-3-medium-128k-instruct": { - "id": "microsoft/phi-3-medium-128k-instruct", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - }, - "family": "phi" - }, - "microsoft/phi-3.5-moe-instruct": { - "id": "microsoft/phi-3.5-moe-instruct", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - }, - "family": "phi" - }, - "microsoft/phi-3-vision-128k-instruct": { - "id": "microsoft/phi-3-vision-128k-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "microsoft/phi-4-mini-instruct": { - "id": "microsoft/phi-4-mini-instruct", - "family": "phi", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "microsoft/phi-3.5-vision-instruct": { - "id": "microsoft/phi-3.5-vision-instruct", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - }, - "family": "phi" - }, - "microsoft/phi-3-medium-4k-instruct": { - "id": "microsoft/phi-3-medium-4k-instruct", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4096, - "output": 1024 - }, - "family": "phi" - }, - "microsoft/phi-3-small-8k-instruct": { - "id": "microsoft/phi-3-small-8k-instruct", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 2048 - }, - "family": "phi" - }, - "minimaxai/minimax-m2.1": { - "id": "MiniMaxAI/MiniMax-M2.1", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 196608, - "output": 196608, - "input": 120000 - } - }, - "minimaxai/minimax-m2.5": { - "id": "MiniMaxAI/MiniMax-M2.5", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 196608, - "output": 196608 - } - }, - "deepseek-ai/deepseek-v3.1": { - "id": "deepseek-ai/DeepSeek-V3.1", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 65536, - "input": 128000 - } - }, - "deepseek-ai/deepseek-r1": { - "id": "deepseek-ai/DeepSeek-R1", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 164000, - "output": 164000 - }, - "family": "deepseek-thinking" - }, - "deepseek-ai/deepseek-v3.1-terminus": { - "id": "deepseek-ai/DeepSeek-V3.1-Terminus", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 65536, - "input": 128000 - } - }, - "deepseek-ai/deepseek-coder-6.7b-instruct": { - "id": "deepseek-ai/deepseek-coder-6.7b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "deepseek-ai/deepseek-v3.2": { - "id": "deepseek-ai/DeepSeek-V3.2", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 65536, - "input": 160000 - } - }, - "moonshotai/kimi-k2-instruct": { - "id": "moonshotai/kimi-k2-instruct", - "family": "kimi", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 8192, - "input": 256000 - } - }, - "google/codegemma-7b": { - "id": "google/codegemma-7b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "google/gemma-2-2b-it": { - "id": "google/gemma-2-2b-it", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 4096, - "input": 8000 - } - }, - "google/gemma-3-1b-it": { - "id": "google/gemma-3-1b-it", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "google/gemma-2-27b-it": { - "id": "google/gemma-2-27b-it", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 2048 - } - }, - "google/gemma-3n-e2b-it": { - "id": "google/gemma-3n-e2b-it", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "google/codegemma-1.1-7b": { - "id": "google/codegemma-1.1-7b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "google/gemma-3n-e4b-it": { - "id": "google/gemma-3n-e4b-it", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 6554 - }, - "family": "gemma" - }, - "google/gemma-3-12b-it": { - "id": "google/gemma-3-12b-it", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - }, - "family": "gemma" - }, - "google/gemma-3-27b-it": { - "id": "google/gemma-3-27b-it", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 65536, - "input": 100000 - } - }, - "z-ai/glm4.7": { - "id": "z-ai/glm4.7", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "z-ai/glm5": { - "id": "z-ai/glm5", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 131000 - } - }, - "stepfun-ai/step-3.5-flash": { - "id": "stepfun-ai/step-3.5-flash", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000, - "input": 256000 - }, - "family": "step" - }, - "qwen/qwen3-next-80b-a3b-thinking": { - "id": "qwen/qwen3-next-80b-a3b-thinking", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768, - "input": 120000 - } - }, - "qwen/qwen3-coder-480b-a35b-instruct": { - "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 66536 - } - }, - "qwen/qwq-32b": { - "id": "qwen/qwq-32b", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - }, - "family": "qwen" - }, - "qwen/qwen2.5-coder-7b-instruct": { - "id": "qwen/qwen2.5-coder-7b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 6554 - } - }, - "qwen/qwen3.5-397b-a17b": { - "id": "qwen/qwen3.5-397b-a17b", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 258048, - "output": 65536, - "input": 258048 - } - }, - "qwen/qwen2.5-coder-32b-instruct": { - "id": "Qwen/Qwen2.5-Coder-32B-Instruct", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - }, - "family": "qwen" - }, - "qwen/qwen3-235b-a22b": { - "id": "Qwen/Qwen3-235B-A22B", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 40960, - "output": 40960 - } - }, - "meta/llama-3.1-70b-instruct": { - "id": "meta/llama-3.1-70b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "meta/llama-3.3-70b-instruct": { - "id": "meta/llama-3.3-70b-instruct", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - }, - "family": "llama" - }, - "meta/llama-4-scout-17b-16e-instruct": { - "id": "meta/llama-4-scout-17b-16e-instruct", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - }, - "family": "llama" - }, - "meta/llama-3.2-11b-vision-instruct": { - "id": "meta/llama-3.2-11b-vision-instruct", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - }, - "family": "llama" - }, - "meta/llama3-8b-instruct": { - "id": "meta/llama3-8b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "meta/codellama-70b": { - "id": "meta/codellama-70b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "meta/llama-3.2-1b-instruct": { - "id": "meta/llama-3.2-1b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16000, - "output": 4096 - }, - "family": "llama" - }, - "meta/llama-3.1-405b-instruct": { - "id": "meta/llama-3.1-405b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "meta/llama3-70b-instruct": { - "id": "meta/llama3-70b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "meta/llama-4-maverick-17b-128e-instruct": { - "id": "meta/llama-4-maverick-17b-128e-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "mistralai/mistral-large-3-675b-instruct-2512": { - "id": "mistralai/mistral-large-3-675b-instruct-2512", - "family": "mistral-large", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 256000, - "input": 262144 - } - }, - "mistralai/mamba-codestral-7b-v0.1": { - "id": "mistralai/mamba-codestral-7b-v0.1", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "mistralai/codestral-22b-instruct-v0.1": { - "id": "mistralai/codestral-22b-instruct-v0.1", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "mistralai/mistral-large-2-instruct": { - "id": "mistralai/mistral-large-2-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "mistralai/ministral-14b-instruct-2512": { - "id": "mistralai/ministral-14b-instruct-2512", - "family": "ministral", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768, - "input": 262144 - } - }, - "mistralai/mistral-small-3.1-24b-instruct-2503": { - "id": "mistralai/mistral-small-3.1-24b-instruct-2503", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "mistralai/devstral-2-123b-instruct-2512": { - "id": "mistralai/devstral-2-123b-instruct-2512", - "family": "devstral", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536, - "input": 262144 - } - }, - "black-forest-labs/flux.1-dev": { - "id": "black-forest-labs/flux.1-dev", - "family": "flux", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 4096, - "output": 0 - } - }, - "deepseek-ai/deepseek-r1-distill-llama-70b": { - "id": "deepseek-ai/DeepSeek-R1-Distill-Llama-70B", - "family": "deepseek-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "moonshotai/kimi-k2": { - "id": "moonshotai/kimi-k2", - "family": "kimi", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 26215 - } - }, - "qwen/qwen3-coder": { - "id": "qwen/qwen3-coder", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 52429 - } - }, - "openai/gpt-4.1": { - "id": "openai/gpt-4.1", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1047576, - "output": 32768, - "input": 1047576 - } - }, - "openai/gpt-5-mini": { - "id": "openai/gpt-5-mini", - "family": "gpt-mini", - "reasoning": true, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000, - "input": 400000 - } - }, - "openai/gpt-5-nano": { - "id": "openai/gpt-5-nano", - "family": "gpt-nano", - "reasoning": true, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000, - "input": 400000 - } - }, - "kimi-k2": { - "id": "kimi-k2", - "family": "kimi", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "qwen3-max-preview": { - "id": "qwen3-max-preview", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 64000 - } - }, - "deepseek-v3": { - "id": "deepseek-v3", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 8192 - } - }, - "kimi-k2-0905": { - "id": "kimi-k2-0905", - "family": "kimi", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 16384 - } - }, - "qwen3-235b-a22b-instruct": { - "id": "qwen3-235b-a22b-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 64000 - } - }, - "deepseek-r1": { - "id": "deepseek-r1", - "family": "deepseek-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384, - "input": 128000 - } - }, - "qwen3-32b": { - "id": "qwen3-32b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "deepseek-v3.2": { - "id": "deepseek-v3.2", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "qwen3-235b": { - "id": "qwen3-235b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32000 - } - }, - "qwen3-vl-plus": { - "id": "qwen3-vl-plus", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - } - }, - "qwen3-235b-a22b-thinking-2507": { - "id": "qwen3-235b-a22b-thinking-2507", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "qwen3-max": { - "id": "qwen3-max", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "qwen/qwen3-30b-a3b-instruct-2507": { - "id": "Qwen/Qwen3-30B-A3B-Instruct-2507", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144, - "input": 120000 - } - }, - "qwen/qwen3-30b-a3b-thinking-2507": { - "id": "qwen/qwen3-30b-a3b-thinking-2507", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 6554, - "input": 120000 - } - }, - "qwen/qwen3-coder-30b-a3b-instruct": { - "id": "qwen/qwen3-coder-30b-a3b-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 160000, - "output": 32768, - "input": 120000 - } - }, - "qwen/qwen3-235b-a22b-instruct-2507": { - "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "zhipuai/glm-4.6": { - "id": "ZhipuAI/GLM-4.6", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 98304 - } - }, - "zhipuai/glm-4.5": { - "id": "ZhipuAI/GLM-4.5", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 98304 - } - }, - "cerebras-llama-4-maverick-17b-128e-instruct": { - "id": "cerebras-llama-4-maverick-17b-128e-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "llama-4-scout-17b-16e-instruct-fp8": { - "id": "llama-4-scout-17b-16e-instruct-fp8", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "llama-3.3-8b-instruct": { - "id": "llama-3.3-8b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "groq-llama-4-maverick-17b-128e-instruct": { - "id": "groq-llama-4-maverick-17b-128e-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "llama-3.3-70b-instruct": { - "id": "llama-3.3-70b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "cerebras-llama-4-scout-17b-16e-instruct": { - "id": "cerebras-llama-4-scout-17b-16e-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "llama-4-maverick-17b-128e-instruct-fp8": { - "id": "llama-4-maverick-17b-128e-instruct-fp8", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "mistral/mistral-nemo-12b-instruct": { - "id": "mistral/mistral-nemo-12b-instruct", - "family": "mistral-nemo", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16000, - "output": 4096 - } - }, - "google/gemma-3": { - "id": "google/gemma-3", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 125000, - "output": 4096 - } - }, - "qwen/qwen3-embedding-4b": { - "id": "Qwen/Qwen3-Embedding-4B", - "family": "qwen", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 2048 - } - }, - "qwen/qwen-2.5-7b-vision-instruct": { - "id": "qwen/qwen-2.5-7b-vision-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 125000, - "output": 4096 - } - }, - "meta/llama-3.2-3b-instruct": { - "id": "meta/llama-3.2-3b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16000, - "output": 4096 - } - }, - "meta/llama-3.1-8b-instruct": { - "id": "meta/llama-3.1-8b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16000, - "output": 4096 - } - }, - "osmosis/osmosis-structure-0.6b": { - "id": "osmosis/osmosis-structure-0.6b", - "family": "osmosis", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4000, - "output": 2048 - } - }, - "zai-org/glm-4.7-flash": { - "id": "zai-org/GLM-4.7-Flash", - "family": "glm-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 65535, - "input": 200000 - } - }, - "zai-org/glm-4.7": { - "id": "zai-org/glm-4.7", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 128000, - "input": 200000 - } - }, - "zai-org/glm-4.6v": { - "id": "zai-org/GLM-4.6V", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 65536 - } - }, - "zai-org/glm-4.5": { - "id": "zai-org/glm-4.5", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 98304, - "input": 124000 - } - }, - "zai-org/glm-5": { - "id": "zai-org/glm-5", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 128000, - "input": 200000 - } - }, - "minimaxai/minimax-m2": { - "id": "MiniMaxAI/MiniMax-M2", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - } - }, - "meta-llama/llama-3.1-8b-instruct-turbo": { - "id": "meta-llama/Llama-3.1-8B-Instruct-Turbo", - "family": "llama", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "meta-llama/llama-3.1-70b-instruct-turbo": { - "id": "meta-llama/Llama-3.1-70B-Instruct-Turbo", - "family": "llama", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "meta-llama/llama-4-scout-17b-16e-instruct": { - "id": "meta-llama/Llama-4-Scout-17B-16E-Instruct", - "family": "llama", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 64000, - "output": 64000 - }, - "temperature": true - }, - "meta-llama/llama-3.1-70b-instruct": { - "id": "meta-llama/llama-3.1-70b-instruct", - "family": "llama", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 26215 - }, - "temperature": true - }, - "meta-llama/llama-3.1-8b-instruct": { - "id": "meta-llama/llama-3.1-8b-instruct", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384, - "input": 131072 - }, - "temperature": true - }, - "meta-llama/llama-3.3-70b-instruct-turbo": { - "id": "meta-llama/Llama-3.3-70B-Instruct-Turbo", - "family": "llama", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - }, - "temperature": true - }, - "qwen/qwen3-coder-480b-a35b-instruct-turbo": { - "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 66536 - } - }, - "anthropic/claude-3-7-sonnet-latest": { - "id": "anthropic/claude-3-7-sonnet-latest", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "anthropic/claude-4-opus": { - "id": "anthropic/claude-4-opus", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - } - }, - "perplexity/sonar": { - "id": "perplexity/sonar", - "family": "sonar", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 127072, - "output": 25415 - } - }, - "anthropic/claude-opus-4-6": { - "id": "anthropic/claude-opus-4-6", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 - } - }, - "anthropic/claude-sonnet-4-6": { - "id": "anthropic/claude-sonnet-4-6", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 - } - }, - "anthropic/claude-haiku-4-5": { - "id": "anthropic/claude-haiku-4-5", - "family": "claude-haiku", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 62000 - } - }, - "anthropic/claude-opus-4-5": { - "id": "anthropic/claude-opus-4-5", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "anthropic/claude-sonnet-4-5": { - "id": "anthropic/claude-sonnet-4-5", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - } - }, - "xai/grok-4-1-fast-non-reasoning": { - "id": "xai/grok-4-1-fast-non-reasoning", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 30000 - } - }, - "mimo-v2-omni": { - "id": "mimo-v2-omni", - "family": "mimo", + "gemini-2.5-flash-lite-preview-09-2025": { + "id": "gemini-2.5-flash-lite-preview-09-2025", "reasoning": true, "temperature": true, "toolCall": true, @@ -5081,1150 +160,11 @@ ] }, "limit": { - "context": 256000, - "output": 128000 - } - }, - "mimo-v2-flash": { - "id": "mimo-v2-flash", - "family": "mimo", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] + "context": 1048576, + "output": 65536, + "input": 1048756 }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "mimo-v2-pro": { - "id": "mimo-v2-pro", - "family": "mimo", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 - } - }, - "hf:minimaxai/minimax-m2.5": { - "id": "hf:MiniMaxAI/MiniMax-M2.5", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 191488, - "output": 65536 - } - }, - "hf:minimaxai/minimax-m2": { - "id": "hf:MiniMaxAI/MiniMax-M2", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 196608, - "output": 131000 - } - }, - "hf:minimaxai/minimax-m2.1": { - "id": "hf:MiniMaxAI/MiniMax-M2.1", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "hf:deepseek-ai/deepseek-r1": { - "id": "hf:deepseek-ai/DeepSeek-R1", - "family": "deepseek-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "hf:deepseek-ai/deepseek-r1-0528": { - "id": "hf:deepseek-ai/DeepSeek-R1-0528", - "family": "deepseek-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "hf:deepseek-ai/deepseek-v3.1": { - "id": "hf:deepseek-ai/DeepSeek-V3.1", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "hf:deepseek-ai/deepseek-v3.2": { - "id": "hf:deepseek-ai/DeepSeek-V3.2", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 162816, - "input": 162816, - "output": 8000 - } - }, - "hf:deepseek-ai/deepseek-v3-0324": { - "id": "hf:deepseek-ai/DeepSeek-V3-0324", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "hf:deepseek-ai/deepseek-v3": { - "id": "hf:deepseek-ai/DeepSeek-V3", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "hf:deepseek-ai/deepseek-v3.1-terminus": { - "id": "hf:deepseek-ai/DeepSeek-V3.1-Terminus", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "hf:moonshotai/kimi-k2-instruct-0905": { - "id": "hf:moonshotai/Kimi-K2-Instruct-0905", - "family": "kimi", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - } - }, - "hf:moonshotai/kimi-k2.5": { - "id": "hf:moonshotai/Kimi-K2.5", - "family": "kimi", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "hf:moonshotai/kimi-k2-thinking": { - "id": "hf:moonshotai/Kimi-K2-Thinking", - "family": "kimi-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "hf:openai/gpt-oss-120b": { - "id": "hf:openai/gpt-oss-120b", - "family": "gpt-oss", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "hf:nvidia/kimi-k2.5-nvfp4": { - "id": "hf:nvidia/Kimi-K2.5-NVFP4", - "family": "kimi", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "hf:meta-llama/llama-4-scout-17b-16e-instruct": { - "id": "hf:meta-llama/Llama-4-Scout-17B-16E-Instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 328000, - "output": 4096 - } - }, - "hf:meta-llama/llama-3.1-405b-instruct": { - "id": "hf:meta-llama/Llama-3.1-405B-Instruct", - "family": "llama", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "hf:meta-llama/llama-3.1-70b-instruct": { - "id": "hf:meta-llama/Llama-3.1-70B-Instruct", - "family": "llama", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "hf:meta-llama/llama-3.1-8b-instruct": { - "id": "hf:meta-llama/Llama-3.1-8B-Instruct", - "family": "llama", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "hf:meta-llama/llama-3.3-70b-instruct": { - "id": "hf:meta-llama/Llama-3.3-70B-Instruct", - "family": "llama", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "hf:meta-llama/llama-4-maverick-17b-128e-instruct-fp8": { - "id": "hf:meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 524000, - "output": 4096 - } - }, - "hf:zai-org/glm-4.7-flash": { - "id": "hf:zai-org/GLM-4.7-Flash", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 196608, - "output": 65536 - } - }, - "hf:zai-org/glm-4.6": { - "id": "hf:zai-org/GLM-4.6", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "hf:zai-org/glm-4.7": { - "id": "hf:zai-org/GLM-4.7", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "hf:qwen/qwen3-235b-a22b-thinking-2507": { - "id": "hf:Qwen/Qwen3-235B-A22B-Thinking-2507", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32000 - } - }, - "hf:qwen/qwen2.5-coder-32b-instruct": { - "id": "hf:Qwen/Qwen2.5-Coder-32B-Instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "hf:qwen/qwen3-coder-480b-a35b-instruct": { - "id": "hf:Qwen/Qwen3-Coder-480B-A35B-Instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32000 - } - }, - "hf:qwen/qwen3-235b-a22b-instruct-2507": { - "id": "hf:Qwen/Qwen3-235B-A22B-Instruct-2507", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32000 - } - }, - "zai-org/glm-4.7-fp8": { - "id": "zai-org/GLM-4.7-FP8", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "input": 124000, - "output": 65535 - } - }, - "zai-org/glm-4.5-air": { - "id": "zai-org/GLM-4.5-Air", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 124000, - "output": 131072 - }, - "family": "glm" - }, - "nvidia/llama-3_1-nemotron-ultra-253b-v1": { - "id": "nvidia/Llama-3_1-Nemotron-Ultra-253B-v1", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 120000, - "output": 4096 - } - }, - "nvidia/nemotron-nano-v2-12b": { - "id": "nvidia/Nemotron-Nano-V2-12b", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "input": 30000, - "output": 4096 - } - }, - "nvidia/nvidia-nemotron-3-nano-30b-a3b": { - "id": "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "input": 30000, - "output": 4096 - } - }, - "nousresearch/hermes-4-405b": { - "id": "nousresearch/hermes-4-405b", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 120000, - "output": 26215 - }, - "family": "hermes" - }, - "nousresearch/hermes-4-70b": { - "id": "NousResearch/Hermes-4-70B", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 120000, - "output": 131072 - }, - "family": "nousresearch" - }, - "baai/bge-en-icl": { - "id": "BAAI/bge-en-icl", - "family": "text-embedding", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 0 - } - }, - "baai/bge-multilingual-gemma2": { - "id": "BAAI/bge-multilingual-gemma2", - "family": "text-embedding", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "input": 8192, - "output": 0 - } - }, - "primeintellect/intellect-3": { - "id": "PrimeIntellect/INTELLECT-3", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 120000, - "output": 8192 - } - }, - "deepseek-ai/deepseek-v3-0324-fast": { - "id": "deepseek-ai/DeepSeek-V3-0324-fast", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 120000, - "output": 8192 - } - }, - "deepseek-ai/deepseek-v3-0324": { - "id": "deepseek-ai/DeepSeek-V3-0324", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "input": 120000, - "output": 163840 - }, - "family": "deepseek" - }, - "deepseek-ai/deepseek-r1-0528-fast": { - "id": "deepseek-ai/DeepSeek-R1-0528-fast", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "intfloat/e5-mistral-7b-instruct": { - "id": "intfloat/e5-mistral-7b-instruct", - "family": "mistral", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4096, - "input": 32768, - "output": 4096 - } - }, - "moonshotai/kimi-k2.5-fast": { - "id": "moonshotai/Kimi-K2.5-fast", - "family": "kimi", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "input": 256000, - "output": 8192 - } - }, - "google/gemma-3-27b-it-fast": { - "id": "google/gemma-3-27b-it-fast", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 110000, - "input": 100000, - "output": 8192 - } - }, - "google/gemma-2-9b-it-fast": { - "id": "google/gemma-2-9b-it-fast", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "input": 8000, - "output": 4096 - } - }, - "meta-llama/meta-llama-3.1-8b-instruct": { - "id": "meta-llama/Meta-Llama-3.1-8B-Instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 33000, - "input": 120000, - "output": 4000 - }, - "family": "llama" - }, - "meta-llama/llama-guard-3-8b": { - "id": "meta-llama/llama-guard-3-8b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 8000, - "output": 26215 - } - }, - "meta-llama/llama-3.3-70b-instruct-fast": { - "id": "meta-llama/Llama-3.3-70B-Instruct-fast", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 120000, - "output": 8192 - } - }, - "meta-llama/meta-llama-3.1-8b-instruct-fast": { - "id": "meta-llama/Meta-Llama-3.1-8B-Instruct-fast", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 120000, - "output": 4096 - } - }, - "qwen/qwen3-32b": { - "id": "Qwen/Qwen3-32B", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 40960, - "input": 120000, - "output": 40960 - }, - "family": "qwen" - }, - "qwen/qwen2.5-vl-72b-instruct": { - "id": "qwen/qwen2.5-vl-72b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 120000, - "output": 32768 - }, - "family": "qwen" - }, - "qwen/qwen3-32b-fast": { - "id": "Qwen/Qwen3-32B-fast", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 120000, - "output": 8192 - } - }, - "qwen/qwen2.5-coder-7b-fast": { - "id": "Qwen/Qwen2.5-Coder-7B-fast", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 120000, - "output": 8192 - } - }, - "black-forest-labs/flux-dev": { - "id": "black-forest-labs/flux-dev", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 77, - "input": 77, - "output": 0 - } - }, - "black-forest-labs/flux-schnell": { - "id": "black-forest-labs/flux-schnell", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 77, - "input": 77, - "output": 0 - } - }, - "claude-4.5-haiku": { - "id": "claude-4.5-haiku", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192 - }, - "family": "claude-haiku" - }, - "claude-3.5-sonnet": { - "id": "claude-3.5-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8200 - } + "family": "gemini-flash-lite" }, "qwen3-235b-a22b-instruct-2507": { "id": "qwen3-235b-a22b-instruct-2507", @@ -6240,193 +180,14 @@ ] }, "limit": { - "context": 128000, - "output": 16384 - }, - "family": "qwen" - }, - "claude-3.7-sonnet": { - "id": "claude-3.7-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - }, - "family": "claude-sonnet" - }, - "qwen3-next-80b-a3b-thinking": { - "id": "qwen3-next-80b-a3b-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - }, - "family": "qwen" - }, - "claude-4.0-sonnet": { - "id": "claude-4.0-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "qwen-vl-max-2025-01-25": { - "id": "qwen-vl-max-2025-01-25", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "doubao-seed-1.6-thinking": { - "id": "doubao-seed-1.6-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32000 - } - }, - "qwen3-coder-480b-a35b-instruct": { - "id": "qwen3-coder-480b-a35b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - }, - "family": "qwen" - }, - "claude-4.5-sonnet": { - "id": "claude-4.5-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - }, - "family": "claude-sonnet" - }, - "qwen2.5-vl-7b-instruct": { - "id": "qwen2.5-vl-7b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, + "context": 262000, "output": 8192 - } - }, - "doubao-seed-2.0-pro": { - "id": "doubao-seed-2.0-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] }, - "limit": { - "context": 256000, - "output": 128000 - } + "family": "qwen" }, - "gemini-2.5-flash": { - "id": "gemini-2.5-flash", - "reasoning": true, + "mistral-large-2512": { + "id": "mistral-large-2512", + "reasoning": false, "temperature": true, "toolCall": false, "modalities": { @@ -6439,14 +200,13 @@ ] }, "limit": { - "context": 1048756, - "output": 65536, - "input": 1048756 + "context": 262144, + "output": 16384 }, - "family": "gemini-flash" + "family": "mistral" }, - "deepseek-v3.1": { - "id": "deepseek-v3.1", + "glm-4.7": { + "id": "glm-4.7", "reasoning": true, "temperature": true, "toolCall": true, @@ -6458,54 +218,91 @@ "text" ] }, + "limit": { + "context": 204800, + "output": 131072 + }, + "family": "glm" + }, + "doubao-seed-1-8-251215": { + "id": "doubao-seed-1-8-251215", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192, + "input": 128000 + } + }, + "chatgpt-4o-latest": { + "id": "chatgpt-4o-latest", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "deepseek-chat": { + "id": "deepseek-chat", + "family": "deepseek", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, "limit": { "context": 131072, - "output": 131072 - }, - "family": "deepseek" + "output": 8192, + "input": 128000 + } }, - "doubao-seed-1.6": { - "id": "doubao-seed-1.6", + "deepseek-v3.2-thinking": { + "id": "deepseek-v3.2-thinking", "reasoning": true, "temperature": true, "toolCall": true, "modalities": { "input": [ - "text", - "image", - "video" + "text" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 32000 + "context": 128000, + "output": 128000 } }, - "doubao-seed-2.0-mini": { - "id": "doubao-seed-2.0-mini", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32000 - } - }, - "claude-4.0-opus": { - "id": "claude-4.0-opus", + "gpt-5-thinking": { + "id": "gpt-5-thinking", "reasoning": true, "temperature": true, "toolCall": true, @@ -6519,32 +316,12 @@ ] }, "limit": { - "context": 200000, - "output": 32000 + "context": 400000, + "output": 128000 } }, - "qwen-turbo": { - "id": "qwen-turbo", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 16384, - "input": 1000000 - }, - "family": "qwen" - }, - "gemini-3.0-pro-preview": { - "id": "gemini-3.0-pro-preview", + "gemini-3-flash-preview": { + "id": "gemini-3-flash-preview", "reasoning": true, "temperature": true, "toolCall": true, @@ -6553,8 +330,72 @@ "text", "image", "video", - "pdf", - "audio" + "audio", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536, + "input": 128000 + }, + "family": "gemini-flash" + }, + "qwen-plus": { + "id": "qwen-plus", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32000, + "input": 995904 + } + }, + "gpt-5-mini": { + "id": "gpt-5-mini", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000, + "input": 272000 + }, + "family": "gpt-mini" + }, + "gemini-3-pro-preview": { + "id": "gemini-3-pro-preview", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" ], "output": [ "text" @@ -6562,50 +403,157 @@ }, "limit": { "context": 1000000, + "output": 65000, + "input": 128000 + }, + "family": "gemini-pro" + }, + "qwen3-max-2025-09-23": { + "id": "qwen3-max-2025-09-23", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 258048, + "output": 65536 + } + }, + "claude-sonnet-4-5-20250929": { + "id": "claude-sonnet-4-5-20250929", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000, + "input": 1000000 + }, + "family": "claude-sonnet" + }, + "qwen-flash": { + "id": "qwen-flash", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 32000 + }, + "family": "qwen" + }, + "gemini-2.5-pro": { + "id": "gemini-2.5-pro", + "family": "gemini-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536, + "input": 128000 + } + }, + "grok-4-1-fast-non-reasoning": { + "id": "grok-4-1-fast-non-reasoning", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192, + "input": 128000 + }, + "family": "grok" + }, + "claude-opus-4-5-20251101-thinking": { + "id": "claude-opus-4-5-20251101-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, "output": 64000 } }, - "deepseek-r1-0528": { - "id": "deepseek-r1-0528", + "gpt-5.2": { + "id": "gpt-5.2", "reasoning": true, "temperature": true, "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - }, - "family": "deepseek-thinking" - }, - "doubao-1.5-vision-pro": { - "id": "doubao-1.5-vision-pro", - "reasoning": false, - "temperature": true, - "toolCall": false, "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16000 - } + "context": 400000, + "output": 128000, + "input": 272000 + }, + "family": "gpt" }, - "gemini-3.0-pro-image-preview": { - "id": "gemini-3.0-pro-image-preview", + "gemini-3-pro-image-preview": { + "id": "gemini-3-pro-image-preview", "reasoning": false, "temperature": true, "toolCall": false, @@ -6620,54 +568,15 @@ ] }, "limit": { - "context": 32768, - "output": 8192 - } - }, - "qwen3.5-397b-a17b": { - "id": "qwen3.5-397b-a17b", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - }, - "family": "qwen" - }, - "gemini-2.5-flash-lite": { - "id": "gemini-2.5-flash-lite", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048756, - "output": 65536, + "context": 65536, + "output": 32768, "input": 1048756 }, - "family": "gemini-flash-lite" + "family": "gemini" }, - "claude-3.5-haiku": { - "id": "claude-3.5-haiku", + "qwen-max-latest": { + "id": "qwen-max-latest", + "family": "qwen", "reasoning": false, "temperature": true, "toolCall": true, @@ -6680,184 +589,11 @@ "text" ] }, - "limit": { - "context": 200000, - "output": 8192 - }, - "family": "claude-haiku" - }, - "gpt-oss-120b": { - "id": "gpt-oss-120b", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - }, - "family": "gpt-oss" - }, - "deepseek-v3-0324": { - "id": "deepseek-v3-0324", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000, - "input": 128000 - }, - "family": "deepseek" - }, - "doubao-1.5-pro-32k": { - "id": "doubao-1.5-pro-32k", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 8192, - "input": 32000 - } - }, - "qwen3-30b-a3b-instruct-2507": { - "id": "qwen3-30b-a3b-instruct-2507", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32768, - "input": 256000 - } - }, - "qwen2.5-vl-72b-instruct": { - "id": "qwen2.5-vl-72b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "qwen3-235b-a22b": { - "id": "qwen3-235b-a22b", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, "limit": { "context": 131072, - "output": 16384 - }, - "family": "qwen" - }, - "doubao-seed-2.0-lite": { - "id": "doubao-seed-2.0-lite", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, "output": 32000 } }, - "claude-4.1-opus": { - "id": "claude-4.1-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - } - }, - "doubao-1.5-thinking-pro": { - "id": "doubao-1.5-thinking-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16000 - } - }, "gemini-2.5-flash-image": { "id": "gemini-2.5-flash-image", "reasoning": true, @@ -6879,8 +615,153 @@ }, "family": "gemini-flash" }, - "minimax-m1": { - "id": "MiniMax-M1", + "glm-4.5": { + "id": "glm-4.5", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 98304 + }, + "family": "glm" + }, + "gemini-2.5-flash": { + "id": "gemini-2.5-flash", + "family": "gemini-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video", + "audio", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536, + "input": 1048756 + } + }, + "gpt-5.2-chat-latest": { + "id": "gpt-5.2-chat-latest", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16400 + }, + "family": "gpt" + }, + "doubao-seed-1-6-vision-250815": { + "id": "doubao-seed-1-6-vision-250815", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 32000 + } + }, + "minimax-m2.1": { + "id": "MiniMax-M2.1", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + }, + "family": "minimax" + }, + "gpt-5.1": { + "id": "gpt-5.1", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text", + "image", + "audio" + ] + }, + "limit": { + "context": 272000, + "output": 128000, + "input": 272000 + }, + "family": "gpt" + }, + "kimi-k2-thinking-turbo": { + "id": "kimi-k2-thinking-turbo", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + }, + "family": "kimi-thinking" + }, + "deepseek-reasoner": { + "id": "deepseek-reasoner", + "family": "deepseek-thinking", "reasoning": false, "temperature": true, "toolCall": false, @@ -6892,93 +773,34 @@ "text" ] }, - "limit": { - "context": 1000000, - "output": 131072, - "input": 1000000 - }, - "family": "minimax" - }, - "doubao-seed-1.6-flash": { - "id": "doubao-seed-1.6-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32000 - } - }, - "qwen3-vl-30b-a3b-thinking": { - "id": "qwen3-vl-30b-a3b-thinking", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, "limit": { "context": 128000, - "output": 32000 + "output": 64000, + "input": 64000 } }, - "doubao-seed-2.0-code": { - "id": "doubao-seed-2.0-code", + "grok-4-fast-reasoning": { + "id": "grok-4-fast-reasoning", "reasoning": true, "temperature": true, "toolCall": true, "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 128000 - } - }, - "qwen3-30b-a3b-thinking-2507": { - "id": "qwen3-30b-a3b-thinking-2507", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] + "context": 2000000, + "output": 30000 }, - "limit": { - "context": 126000, - "output": 32000 - } + "family": "grok" }, - "claude-4.5-opus": { - "id": "claude-4.5-opus", + "claude-opus-4-1-20250805-thinking": { + "id": "claude-opus-4-1-20250805-thinking", "reasoning": true, "temperature": true, "toolCall": true, @@ -6993,74 +815,12 @@ }, "limit": { "context": 200000, - "output": 64000 - }, - "family": "claude-opus" - }, - "gemini-2.0-flash-lite": { - "id": "gemini-2.0-flash-lite", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 8192, - "input": 1000000 - }, - "family": "gemini-flash-lite" - }, - "qwen3-next-80b-a3b-instruct": { - "id": "qwen3-next-80b-a3b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - }, - "family": "qwen" - }, - "gemini-3.0-flash-preview": { - "id": "gemini-3.0-flash-preview", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 + "output": 32000 } }, "qwen3-30b-a3b": { "id": "qwen3-30b-a3b", + "family": "qwen", "reasoning": false, "temperature": true, "toolCall": true, @@ -7076,11 +836,31 @@ "limit": { "context": 41000, "output": 41000 - }, - "family": "qwen" + } }, - "gpt-oss-20b": { - "id": "gpt-oss-20b", + "glm-4.5v": { + "id": "glm-4.5v", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 64000, + "output": 16384 + }, + "family": "glm" + }, + "glm-4.6": { + "id": "glm-4.6", "reasoning": true, "temperature": true, "toolCall": true, @@ -7093,55 +873,14 @@ ] }, "limit": { - "context": 131072, + "context": 204800, "output": 131072 }, - "family": "gpt-oss" + "family": "glm" }, - "kling-v2-6": { - "id": "kling-v2-6", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 99999999, - "output": 99999999 - } - }, - "gemini-2.5-pro": { - "id": "gemini-2.5-pro", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65535, - "input": 1048756 - }, - "family": "gemini-pro" - }, - "gemini-2.0-flash": { - "id": "gemini-2.0-flash", - "reasoning": false, + "gemini-2.5-flash-preview-09-2025": { + "id": "gemini-2.5-flash-preview-09-2025", + "reasoning": true, "temperature": true, "toolCall": true, "modalities": { @@ -7158,196 +897,13 @@ }, "limit": { "context": 1048576, - "output": 8192 + "output": 65536, + "input": 1048756 }, "family": "gemini-flash" }, - "qwen-max-2025-01-25": { - "id": "qwen-max-2025-01-25", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "deepseek/deepseek-v3.2-exp-thinking": { - "id": "deepseek/deepseek-v3.2-exp-thinking", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32000 - } - }, - "deepseek/deepseek-v3.1-terminus": { - "id": "deepseek/deepseek-v3.1-terminus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 32768 - }, - "family": "deepseek" - }, - "deepseek/deepseek-v3.2-251201": { - "id": "deepseek/deepseek-v3.2-251201", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32000 - } - }, - "deepseek/deepseek-math-v2": { - "id": "deepseek/deepseek-math-v2", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 160000, - "output": 160000 - } - }, - "deepseek/deepseek-v3.1-terminus-thinking": { - "id": "deepseek/deepseek-v3.1-terminus-thinking", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32000 - } - }, - "z-ai/autoglm-phone-9b": { - "id": "z-ai/autoglm-phone-9b", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 12800, - "output": 4096 - } - }, - "stepfun-ai/gelab-zero-4b-preview": { - "id": "stepfun-ai/gelab-zero-4b-preview", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 4096 - } - }, - "meituan/longcat-flash-lite": { - "id": "meituan/longcat-flash-lite", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 320000 - } - }, - "meituan/longcat-flash-chat": { - "id": "meituan/longcat-flash-chat", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - }, - "family": "longcat" - }, - "x-ai/grok-4-fast-reasoning": { - "id": "x-ai/grok-4-fast-reasoning", + "glm-4.6v": { + "id": "glm-4.6v", "reasoning": true, "temperature": true, "toolCall": true, @@ -7355,7 +911,6 @@ "input": [ "text", "image", - "audio", "video" ], "output": [ @@ -7363,33 +918,13 @@ ] }, "limit": { - "context": 2000000, - "output": 2000000 - } - }, - "x-ai/grok-4.1-fast-reasoning": { - "id": "x-ai/grok-4.1-fast-reasoning", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "context": 128000, + "output": 32768 }, - "limit": { - "context": 2000000, - "output": 131072, - "input": 2000000 - }, - "family": "grok" + "family": "glm" }, - "x-ai/grok-4-fast-non-reasoning": { - "id": "x-ai/grok-4-fast-non-reasoning", + "claude-opus-4-1-20250805": { + "id": "claude-opus-4-1-20250805", "reasoning": true, "temperature": true, "toolCall": true, @@ -7397,3051 +932,21 @@ "input": [ "text", "image", - "audio", - "video" + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 2000000 - } - }, - "minimax/minimax-m2.5-highspeed": { - "id": "minimax/minimax-m2.5-highspeed", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 0, - "output": 0 - }, - "family": "minimax" - }, - "qwen3-coder:480b": { - "id": "qwen3-coder:480b", - "family": "qwen", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "nemotron-3-nano:30b": { - "id": "nemotron-3-nano:30b", - "family": "nemotron", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 131072 - } - }, - "ministral-3:8b": { - "id": "ministral-3:8b", - "family": "ministral", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 128000 - } - }, - "gpt-oss:120b": { - "id": "gpt-oss:120b", - "family": "gpt-oss", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "devstral-2:123b": { - "id": "devstral-2:123b", - "family": "devstral", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "qwen3-vl:235b-instruct": { - "id": "qwen3-vl:235b-instruct", - "family": "qwen", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 131072 - } - }, - "gemini-3-flash-preview": { - "id": "gemini-3-flash-preview", - "family": "gemini-flash", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536, - "input": 128000 - }, - "temperature": true - }, - "minimax-m2.1": { - "id": "minimax-m2.1", - "family": "minimax", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 196000, - "output": 196000 - }, - "temperature": true - }, - "ministral-3:14b": { - "id": "ministral-3:14b", - "family": "ministral", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 128000 - } - }, - "qwen3-next:80b": { - "id": "qwen3-next:80b", - "family": "qwen", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - } - }, - "kimi-k2:1t": { - "id": "kimi-k2:1t", - "family": "kimi", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "gemma3:12b": { - "id": "gemma3:12b", - "family": "gemma", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "minimax-m2.7": { - "id": "MiniMax-M2.7", - "family": "minimax", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - }, - "temperature": true - }, - "gpt-oss:20b": { - "id": "gpt-oss:20b", - "family": "gpt-oss", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "kimi-k2-thinking": { - "id": "kimi-k2-thinking", - "family": "kimi", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 16384 - }, - "temperature": true - }, - "ministral-3:3b": { - "id": "ministral-3:3b", - "family": "ministral", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 128000 - } - }, - "qwen3.5:397b": { - "id": "qwen3.5:397b", - "family": "qwen", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 81920 - } - }, - "gemma3:27b": { - "id": "gemma3:27b", - "family": "gemma", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "minimax-m2": { - "id": "minimax-m2", - "family": "minimax", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 400000, + "context": 200000, + "output": 32000, "input": 200000 }, - "temperature": true + "family": "claude-opus" }, - "devstral-small-2:24b": { - "id": "devstral-small-2:24b", - "family": "devstral", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "nemotron-3-super": { - "id": "nemotron-3-super", - "family": "nemotron", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "cogito-2.1:671b": { - "id": "cogito-2.1:671b", - "family": "cogito", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 32000 - } - }, - "gemma3:4b": { - "id": "gemma3:4b", - "family": "gemma", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "deepseek-v3.1:671b": { - "id": "deepseek-v3.1:671b", - "family": "deepseek", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 163840 - } - }, - "mistral-large-3:675b": { - "id": "mistral-large-3:675b", - "family": "mistral-large", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "rnj-1:8b": { - "id": "rnj-1:8b", - "family": "rnj", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 4096 - } - }, - "qwen3-vl:235b": { - "id": "qwen3-vl:235b", - "family": "qwen", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - } - }, - "voxtral-small-24b-2507": { - "id": "voxtral-small-24b-2507", - "family": "voxtral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 16384 - } - }, - "mistral-small-3.2-24b-instruct-2506": { - "id": "mistral-small-3.2-24b-instruct-2506", - "family": "mistral-small", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "qwen3-embedding-8b": { - "id": "qwen3-embedding-8b", - "family": "qwen", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 4096 - } - }, - "bge-multilingual-gemma2": { - "id": "bge-multilingual-gemma2", - "family": "gemma", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8191, - "output": 3072 - } - }, - "deepseek-r1-distill-llama-70b": { - "id": "deepseek-r1-distill-llama-70b", - "family": "deepseek-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 16384 - } - }, - "qwen3-coder-30b-a3b-instruct": { - "id": "qwen3-coder-30b-a3b-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536, - "input": 128000 - } - }, - "whisper-large-v3": { - "id": "whisper-large-v3", - "family": "whisper", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 0, - "output": 4096 - } - }, - "llama-3.1-8b-instruct": { - "id": "llama-3.1-8b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 16384 - } - }, - "devstral-2-123b-instruct-2512": { - "id": "devstral-2-123b-instruct-2512", - "family": "devstral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 16384 - } - }, - "pixtral-12b-2409": { - "id": "pixtral-12b-2409", - "family": "pixtral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "mistral-nemo-instruct-2407": { - "id": "mistral-nemo-instruct-2407", - "family": "mistral-nemo", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 65536 - } - }, - "gemma-3-27b-it": { - "id": "Gemma-3-27B-it", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 16384, - "input": 32768 - } - }, - "workers-ai/@cf/zai-org/glm-4.7-flash": { - "id": "workers-ai/@cf/zai-org/glm-4.7-flash", - "family": "glm-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "workers-ai/@cf/nvidia/nemotron-3-120b-a12b": { - "id": "workers-ai/@cf/nvidia/nemotron-3-120b-a12b", - "family": "nemotron", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "workers-ai/@cf/ibm-granite/granite-4.0-h-micro": { - "id": "workers-ai/@cf/ibm-granite/granite-4.0-h-micro", - "family": "granite", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/baai/bge-small-en-v1.5": { - "id": "workers-ai/@cf/baai/bge-small-en-v1.5", - "family": "bge", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/baai/bge-large-en-v1.5": { - "id": "workers-ai/@cf/baai/bge-large-en-v1.5", - "family": "bge", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/baai/bge-reranker-base": { - "id": "workers-ai/@cf/baai/bge-reranker-base", - "family": "bge", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/baai/bge-m3": { - "id": "workers-ai/@cf/baai/bge-m3", - "family": "bge", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/baai/bge-base-en-v1.5": { - "id": "workers-ai/@cf/baai/bge-base-en-v1.5", - "family": "bge", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/pfnet/plamo-embedding-1b": { - "id": "workers-ai/@cf/pfnet/plamo-embedding-1b", - "family": "plamo", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/deepseek-ai/deepseek-r1-distill-qwen-32b": { - "id": "workers-ai/@cf/deepseek-ai/deepseek-r1-distill-qwen-32b", - "family": "deepseek-thinking", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/facebook/bart-large-cnn": { - "id": "workers-ai/@cf/facebook/bart-large-cnn", - "family": "bart", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/mistral/mistral-7b-instruct-v0.1": { - "id": "workers-ai/@cf/mistral/mistral-7b-instruct-v0.1", - "family": "mistral", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/myshell-ai/melotts": { - "id": "workers-ai/@cf/myshell-ai/melotts", - "family": "melotts", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/pipecat-ai/smart-turn-v2": { - "id": "workers-ai/@cf/pipecat-ai/smart-turn-v2", - "family": "smart-turn", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/moonshotai/kimi-k2.5": { - "id": "workers-ai/@cf/moonshotai/kimi-k2.5", - "family": "kimi", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "workers-ai/@cf/google/gemma-3-12b-it": { - "id": "workers-ai/@cf/google/gemma-3-12b-it", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/qwen/qwq-32b": { - "id": "workers-ai/@cf/qwen/qwq-32b", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/qwen/qwen3-30b-a3b-fp8": { - "id": "workers-ai/@cf/qwen/qwen3-30b-a3b-fp8", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/qwen/qwen2.5-coder-32b-instruct": { - "id": "workers-ai/@cf/qwen/qwen2.5-coder-32b-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/qwen/qwen3-embedding-0.6b": { - "id": "workers-ai/@cf/qwen/qwen3-embedding-0.6b", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/meta/llama-3.1-8b-instruct-fp8": { - "id": "workers-ai/@cf/meta/llama-3.1-8b-instruct-fp8", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/meta/llama-3-8b-instruct-awq": { - "id": "workers-ai/@cf/meta/llama-3-8b-instruct-awq", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/meta/llama-3.1-8b-instruct-awq": { - "id": "workers-ai/@cf/meta/llama-3.1-8b-instruct-awq", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/meta/llama-4-scout-17b-16e-instruct": { - "id": "workers-ai/@cf/meta/llama-4-scout-17b-16e-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/meta/llama-3.2-11b-vision-instruct": { - "id": "workers-ai/@cf/meta/llama-3.2-11b-vision-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/meta/llama-3.2-3b-instruct": { - "id": "workers-ai/@cf/meta/llama-3.2-3b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/meta/llama-guard-3-8b": { - "id": "workers-ai/@cf/meta/llama-guard-3-8b", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/meta/llama-3.2-1b-instruct": { - "id": "workers-ai/@cf/meta/llama-3.2-1b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/meta/llama-3.3-70b-instruct-fp8-fast": { - "id": "workers-ai/@cf/meta/llama-3.3-70b-instruct-fp8-fast", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/meta/llama-3.1-8b-instruct": { - "id": "workers-ai/@cf/meta/llama-3.1-8b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/meta/m2m100-1.2b": { - "id": "workers-ai/@cf/meta/m2m100-1.2b", - "family": "m2m", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/meta/llama-2-7b-chat-fp16": { - "id": "workers-ai/@cf/meta/llama-2-7b-chat-fp16", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/meta/llama-3-8b-instruct": { - "id": "workers-ai/@cf/meta/llama-3-8b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/mistralai/mistral-small-3.1-24b-instruct": { - "id": "workers-ai/@cf/mistralai/mistral-small-3.1-24b-instruct", - "family": "mistral-small", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/deepgram/aura-2-es": { - "id": "workers-ai/@cf/deepgram/aura-2-es", - "family": "aura", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/deepgram/nova-3": { - "id": "workers-ai/@cf/deepgram/nova-3", - "family": "nova", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/deepgram/aura-2-en": { - "id": "workers-ai/@cf/deepgram/aura-2-en", - "family": "aura", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/openai/gpt-oss-120b": { - "id": "workers-ai/@cf/openai/gpt-oss-120b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/openai/gpt-oss-20b": { - "id": "workers-ai/@cf/openai/gpt-oss-20b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/ai4bharat/indictrans2-en-indic-1b": { - "id": "workers-ai/@cf/ai4bharat/indictrans2-en-indic-1B", - "family": "indictrans", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/huggingface/distilbert-sst-2-int8": { - "id": "workers-ai/@cf/huggingface/distilbert-sst-2-int8", - "family": "distilbert", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "workers-ai/@cf/aisingapore/gemma-sea-lion-v4-27b-it": { - "id": "workers-ai/@cf/aisingapore/gemma-sea-lion-v4-27b-it", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "openai/gpt-4o-mini": { - "id": "openai/gpt-4o-mini", - "family": "gpt-mini", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384, - "input": 128000 - } - }, - "openai/o1": { - "id": "openai/o1", - "family": "o", - "reasoning": true, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000, - "input": 200000 - } - }, - "openai/o3": { - "id": "openai/o3", - "family": "o", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000, - "input": 200000 - } - }, - "openai/gpt-3.5-turbo": { - "id": "openai/gpt-3.5-turbo", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16385, - "output": 4096, - "input": 16385 - } - }, - "openai/o3-pro": { - "id": "openai/o3-pro", - "family": "o-pro", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000, - "input": 100000 - } - }, - "openai/gpt-4-turbo": { - "id": "openai/gpt-4-turbo", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096, - "input": 128000 - } - }, - "openai/o4-mini": { - "id": "openai/o4-mini", - "family": "o-mini", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000, - "input": 200000 - } - }, - "openai/o3-mini": { - "id": "openai/o3-mini", - "family": "o-mini", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000, - "input": 200000 - } - }, - "openai/gpt-4": { - "id": "openai/gpt-4", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8191, - "output": 4096 - } - }, - "openai/gpt-4o": { - "id": "openai/gpt-4o", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384, - "input": 128000 - } - }, - "anthropic/claude-opus-4-1": { - "id": "anthropic/claude-opus-4-1", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - } - }, - "anthropic/claude-3-sonnet": { - "id": "anthropic/claude-3-sonnet", - "family": "claude-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 4096 - } - }, - "anthropic/claude-3-5-haiku": { - "id": "anthropic/claude-3-5-haiku", - "family": "claude-haiku", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192 - } - }, - "anthropic/claude-3-haiku": { - "id": "anthropic/claude-3-haiku", - "family": "claude-haiku", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 4096 - } - }, - "anthropic/claude-3-opus": { - "id": "anthropic/claude-3-opus", - "family": "claude-opus", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 4096 - } - }, - "solar-pro2": { - "id": "solar-pro2", - "family": "solar-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 8192 - } - }, - "solar-mini": { - "id": "solar-mini", - "family": "solar-mini", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 4096 - } - }, - "solar-pro3": { - "id": "solar-pro3", - "family": "solar-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "mercury-2": { - "id": "mercury-2", - "family": "mercury", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 50000 - } - }, - "mercury": { - "id": "mercury", - "family": "mercury", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "mercury-edit": { - "id": "mercury-edit", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "mercury-coder": { - "id": "mercury-coder", - "family": "mercury", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "zai-org/glm-4.5-fp8": { - "id": "zai-org/GLM-4.5-FP8", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 65536 - } - }, - "qwen/qwen3-coder-480b-a35b-instruct-fp8": { - "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "minimax-m2.7-highspeed": { - "id": "MiniMax-M2.7-highspeed", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "minimax-m2.5-highspeed": { - "id": "MiniMax-M2.5-highspeed", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "zai-org/autoglm-phone-9b-multilingual": { - "id": "zai-org/autoglm-phone-9b-multilingual", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 65536 - } - }, - "zai-org/glm-4.5v": { - "id": "zai-org/glm-4.5v", - "family": "glmv", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 16384 - } - }, - "microsoft/wizardlm-2-8x22b": { - "id": "microsoft/wizardlm-2-8x22b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 8192, - "input": 65536 - }, - "family": "gpt" - }, - "minimaxai/minimax-m1-80k": { - "id": "MiniMaxAI/MiniMax-M1-80k", - "family": "minimax", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 131072, - "input": 1000000 - } - }, - "skywork/r1v4-lite": { - "id": "skywork/r1v4-lite", - "family": "skywork", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "gryphe/mythomax-l2-13b": { - "id": "Gryphe/MythoMax-L2-13b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4000, - "output": 4096, - "input": 4000 - }, - "family": "llama" - }, - "paddlepaddle/paddleocr-vl": { - "id": "PaddlePaddle/PaddleOCR-VL", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 16384 - } - }, - "baichuan/baichuan-m2-32b": { - "id": "baichuan/baichuan-m2-32b", - "family": "baichuan", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "kwaipilot/kat-coder-pro": { - "id": "kwaipilot/kat-coder-pro", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 128000 - } - }, - "kwaipilot/kat-coder": { - "id": "kwaipilot/kat-coder", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32000 - } - }, - "deepseek/deepseek-v3-turbo": { - "id": "deepseek/deepseek-v3-turbo", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 64000, - "output": 16000 - } - }, - "deepseek/deepseek-prover-v2-671b": { - "id": "deepseek/deepseek-prover-v2-671b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 160000, - "output": 16384, - "input": 160000 - }, - "family": "deepseek" - }, - "deepseek/deepseek-r1-turbo": { - "id": "deepseek/deepseek-r1-turbo", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 64000, - "output": 16000 - } - }, - "deepseek/deepseek-ocr-2": { - "id": "deepseek/deepseek-ocr-2", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "deepseek/deepseek-v3.1": { - "id": "deepseek/deepseek-v3.1", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 32768 - } - }, - "deepseek/deepseek-r1-0528": { - "id": "deepseek/deepseek-r1-0528", - "family": "deepseek-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 65536 - } - }, - "deepseek/deepseek-r1-0528-qwen3-8b": { - "id": "deepseek/deepseek-r1-0528-qwen3-8b", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32000 - } - }, - "deepseek/deepseek-r1-distill-llama-70b": { - "id": "deepseek/deepseek-r1-distill-llama-70b", - "family": "deepseek-thinking", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "deepseek/deepseek-v3-0324": { - "id": "deepseek/deepseek-v3-0324", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 163840 - } - }, - "deepseek/deepseek-ocr": { - "id": "deepseek/deepseek-ocr", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "baidu/ernie-4.5-vl-28b-a3b-thinking": { - "id": "baidu/ernie-4.5-vl-28b-a3b-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 65536 - } - }, - "baidu/ernie-4.5-vl-424b-a47b": { - "id": "baidu/ernie-4.5-vl-424b-a47b", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 123000, - "output": 16000 - }, - "family": "ernie" - }, - "baidu/ernie-4.5-vl-28b-a3b": { - "id": "baidu/ernie-4.5-vl-28b-a3b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 16384, - "input": 32768 - }, - "family": "ernie" - }, - "baidu/ernie-4.5-300b-a47b-paddle": { - "id": "baidu/ernie-4.5-300b-a47b-paddle", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 123000, - "output": 12000 - }, - "family": "ernie" - }, - "baidu/ernie-4.5-21b-a3b": { - "id": "baidu/ernie-4.5-21b-a3b", - "family": "ernie", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 120000, - "output": 8000 - } - }, - "baidu/ernie-4.5-21b-a3b-thinking": { - "id": "baidu/ernie-4.5-21b-a3b-thinking", - "family": "ernie", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 65536 - } - }, - "qwen/qwen3-4b-fp8": { - "id": "qwen/qwen3-4b-fp8", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 20000 - } - }, - "qwen/qwen3-32b-fp8": { - "id": "qwen/qwen3-32b-fp8", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 40960, - "output": 20000 - }, - "family": "qwen" - }, - "qwen/qwen3-30b-a3b-fp8": { - "id": "qwen/qwen3-30b-a3b-fp8", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 40960, - "output": 20000 - }, - "family": "qwen" - }, - "qwen/qwen3-coder-next": { - "id": "Qwen/Qwen3-Coder-Next", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "qwen/qwen3-vl-235b-a22b-instruct": { - "id": "Qwen/Qwen3-VL-235B-A22B-Instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - }, - "family": "qwen" - }, - "qwen/qwen-mt-plus": { - "id": "qwen/qwen-mt-plus", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 8192 - } - }, - "qwen/qwen3-omni-30b-a3b-instruct": { - "id": "Qwen/Qwen3-Omni-30B-A3B-Instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 66000, - "output": 66000 - } - }, - "qwen/qwen-2.5-72b-instruct": { - "id": "qwen/qwen-2.5-72b-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 16384 - } - }, - "qwen/qwen3-vl-30b-a3b-thinking": { - "id": "qwen/qwen3-vl-30b-a3b-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - }, - "family": "qwen" - }, - "qwen/qwen3-vl-235b-a22b-thinking": { - "id": "qwen/qwen3-vl-235b-a22b-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - }, - "family": "qwen" - }, - "qwen/qwen2.5-7b-instruct": { - "id": "Qwen/Qwen2.5-7B-Instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 33000, - "output": 4000 - }, - "family": "qwen" - }, - "qwen/qwen3-235b-a22b-fp8": { - "id": "qwen/qwen3-235b-a22b-fp8", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 40960, - "output": 20000 - }, - "family": "qwen" - }, - "qwen/qwen3-vl-8b-instruct": { - "id": "qwen/qwen3-vl-8b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - }, - "family": "qwen" - }, - "qwen/qwen3-8b-fp8": { - "id": "qwen/qwen3-8b-fp8", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 20000 - } - }, - "qwen/qwen3-omni-30b-a3b-thinking": { - "id": "Qwen/Qwen3-Omni-30B-A3B-Thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 66000, - "output": 66000 - }, - "family": "qwen" - }, - "meta-llama/llama-3-70b-instruct": { - "id": "meta-llama/llama-3-70b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8000 - } - }, - "meta-llama/llama-3-8b-instruct": { - "id": "meta-llama/llama-3-8b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 16384 - } - }, - "mistralai/mistral-nemo": { - "id": "mistralai/mistral-nemo", - "family": "mistral-nemo", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "sao10k/l3-70b-euryale-v2.1": { - "id": "sao10k/l3-70b-euryale-v2.1", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "sao10k/l31-70b-euryale-v2.2": { - "id": "sao10k/l31-70b-euryale-v2.2", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "sao10k/l3-8b-lunaris": { - "id": "sao10k/l3-8b-lunaris", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "sao10k/l3-8b-stheno-v3.2": { - "id": "Sao10K/L3-8B-Stheno-v3.2", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 8192, - "input": 16384 - }, - "family": "llama" - }, - "xiaomimimo/mimo-v2-flash": { - "id": "XiaomiMiMo/MiMo-V2-Flash", - "family": "mimo", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32000 - } - }, - "nousresearch/hermes-2-pro-llama-3-8b": { - "id": "nousresearch/hermes-2-pro-llama-3-8b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "gpt-5.3-codex": { - "id": "gpt-5.3-codex", - "family": "gpt-codex", + "gpt-5.1-chat-latest": { + "id": "gpt-5.1-chat-latest", "reasoning": true, "temperature": false, "toolCall": true, @@ -10454,663 +959,15 @@ "text" ] }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "gpt-5-codex": { - "id": "gpt-5-codex", - "family": "gpt-codex", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "gemini-3.1-pro": { - "id": "gemini-3.1-pro", - "family": "gemini-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video", - "audio", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - } - }, - "trinity-large-preview-free": { - "id": "trinity-large-preview-free", - "family": "trinity", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "gpt-5.1-codex-max": { - "id": "gpt-5.1-codex-max", - "family": "gpt-codex", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "kimi-k2.5-free": { - "id": "kimi-k2.5-free", - "family": "kimi-free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "claude-opus-4-1": { - "id": "claude-opus-4-1", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - } - }, - "grok-code": { - "id": "grok-code", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "nemotron-3-super-free": { - "id": "nemotron-3-super-free", - "family": "nemotron-free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 - } - }, - "claude-3-5-haiku": { - "id": "claude-3-5-haiku", - "family": "claude-haiku", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192 - } - }, - "gpt-5.2-codex": { - "id": "gpt-5.2-codex", - "family": "gpt-codex", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "claude-opus-4-6": { - "id": "claude-opus-4-6", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 128000 - } - }, - "mimo-v2-flash-free": { - "id": "mimo-v2-flash-free", - "family": "mimo-flash-free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "gemini-3-flash": { - "id": "gemini-3-flash", - "family": "gemini-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video", - "audio", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - } - }, - "claude-sonnet-4-6": { - "id": "claude-sonnet-4-6", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - } - }, - "gpt-5.1": { - "id": "gpt-5.1", - "family": "gpt", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio" - ], - "output": [ - "text", - "image", - "audio" - ] - }, - "limit": { - "context": 272000, - "input": 272000, - "output": 128000 - } - }, - "gpt-5.3-codex-spark": { - "id": "gpt-5.3-codex-spark", - "family": "gpt-codex-spark", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, "limit": { "context": 128000, - "input": 100000, - "output": 32000 - } - }, - "qwen3-coder": { - "id": "qwen3-coder", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, "output": 16384 - } - }, - "gpt-5.1-codex-mini": { - "id": "gpt-5.1-codex-mini", - "family": "gpt-codex", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } + "family": "gpt-codex" }, - "gpt-5.2": { - "id": "gpt-5.2", - "family": "gpt", + "claude-haiku-4-5-20251001": { + "id": "claude-haiku-4-5-20251001", "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "mimo-v2-omni-free": { - "id": "mimo-v2-omni-free", - "family": "mimo-omni-free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 64000 - } - }, - "minimax-m2.1-free": { - "id": "minimax-m2.1-free", - "family": "minimax-free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "mimo-v2-pro-free": { - "id": "mimo-v2-pro-free", - "family": "mimo-pro-free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 64000 - } - }, - "gpt-5": { - "id": "gpt-5", - "family": "gpt", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 272000, - "input": 272000, - "output": 128000 - } - }, - "glm-5-free": { - "id": "glm-5-free", - "family": "glm-free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "gpt-5.4": { - "id": "gpt-5.4", - "family": "gpt", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "gpt-5.4-pro": { - "id": "gpt-5.4-pro", - "family": "gpt-pro", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "claude-haiku-4-5": { - "id": "claude-haiku-4-5", - "family": "claude-haiku", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 200000 - } - }, - "gpt-5.1-codex": { - "id": "gpt-5.1-codex", - "family": "gpt-codex", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio" - ], - "output": [ - "text", - "image", - "audio" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "big-pickle": { - "id": "big-pickle", - "family": "big-pickle", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 128000 - } - }, - "minimax-m2.5-free": { - "id": "minimax-m2.5-free", - "family": "minimax-free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "claude-opus-4-5": { - "id": "claude-opus-4-5", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "claude-sonnet-4": { - "id": "claude-sonnet-4", - "family": "claude-sonnet", - "reasoning": false, "temperature": true, "toolCall": true, "modalities": { @@ -11126,12 +983,13 @@ "limit": { "context": 200000, "output": 64000, - "input": 128000 - } + "input": 200000 + }, + "family": "claude-haiku" }, - "glm-4.7-free": { - "id": "glm-4.7-free", - "family": "glm-free", + "minimax-m1": { + "id": "MiniMax-M1", + "family": "minimax", "reasoning": true, "temperature": true, "toolCall": true, @@ -11143,813 +1001,36 @@ "text" ] }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "gemini-3-pro": { - "id": "gemini-3-pro", - "family": "gemini-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video", - "audio", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - } - }, - "claude-sonnet-4-5": { - "id": "claude-sonnet-4-5", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "gpt-5.4-nano": { - "id": "gpt-5.4-nano", - "family": "gpt-nano", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "gpt-5-nano": { - "id": "gpt-5-nano", - "family": "gpt-nano", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 272000, - "input": 272000, - "output": 128000 - } - }, - "gpt-5.4-mini": { - "id": "gpt-5.4-mini", - "family": "gpt-mini", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "stabilityai/stablediffusionxl": { - "id": "stabilityai/stablediffusionxl", - "family": "stable-diffusion", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 200, - "output": 0 - } - }, - "ideogramai/ideogram-v2": { - "id": "ideogramai/ideogram-v2", - "family": "ideogram", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 150, - "output": 0 - } - }, - "ideogramai/ideogram": { - "id": "ideogramai/ideogram", - "family": "ideogram", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 150, - "output": 0 - } - }, - "ideogramai/ideogram-v2a-turbo": { - "id": "ideogramai/ideogram-v2a-turbo", - "family": "ideogram", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 150, - "output": 0 - } - }, - "ideogramai/ideogram-v2a": { - "id": "ideogramai/ideogram-v2a", - "family": "ideogram", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 150, - "output": 0 - } - }, - "novita/glm-4.7-flash": { - "id": "novita/glm-4.7-flash", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 65500 - } - }, - "novita/glm-4.7-n": { - "id": "novita/glm-4.7-n", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 205000, - "output": 131072 - } - }, - "novita/glm-4.6": { - "id": "novita/glm-4.6", - "family": "glm", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "novita/minimax-m2.1": { - "id": "novita/minimax-m2.1", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 205000, - "output": 131072 - } - }, - "novita/kimi-k2.5": { - "id": "novita/kimi-k2.5", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 262144 - } - }, - "novita/glm-4.7": { - "id": "novita/glm-4.7", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 205000, - "output": 131072 - } - }, - "novita/kimi-k2-thinking": { - "id": "novita/kimi-k2-thinking", - "family": "kimi", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 0 - } - }, - "novita/glm-4.6v": { - "id": "novita/glm-4.6v", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 32768 - } - }, - "google/gemini-3.1-pro": { - "id": "google/gemini-3.1-pro", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - } - }, - "google/lyria": { - "id": "google/lyria", - "family": "lyria", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "google/gemini-3-flash": { - "id": "google/gemini-3-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, "limit": { "context": 1000000, - "output": 64000 - }, - "family": "gemini-flash" - }, - "google/imagen-3": { - "id": "google/imagen-3", - "family": "imagen", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 480, - "output": 0 + "output": 80000, + "input": 1000000 } }, - "google/veo-3.1": { - "id": "google/veo-3.1", - "family": "veo", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 480, - "output": 0 - } - }, - "google/imagen-3-fast": { - "id": "google/imagen-3-fast", - "family": "imagen", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 480, - "output": 0 - } - }, - "google/nano-banana-pro": { - "id": "google/nano-banana-pro", - "family": "nano-banana", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 65536, - "output": 0 - } - }, - "google/veo-2": { - "id": "google/veo-2", - "family": "veo", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 480, - "output": 0 - } - }, - "google/imagen-4-ultra": { - "id": "google/imagen-4-ultra", - "family": "imagen", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 480, - "output": 0 - } - }, - "google/nano-banana": { - "id": "google/nano-banana", - "family": "nano-banana", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text", - "image" - ] - }, - "limit": { - "context": 65536, - "output": 0 - } - }, - "google/veo-3.1-fast": { - "id": "google/veo-3.1-fast", - "family": "veo", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 480, - "output": 0 - } - }, - "google/gemini-deep-research": { - "id": "google/gemini-deep-research", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 0 - } - }, - "google/veo-3": { - "id": "google/veo-3", - "family": "veo", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 480, - "output": 0 - } - }, - "google/imagen-4": { - "id": "google/imagen-4", - "family": "imagen", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 480, - "output": 0 - } - }, - "google/gemini-2.0-flash-lite": { - "id": "google/gemini-2.0-flash-lite", - "family": "gemini-flash-lite", + "qwen3-coder-480b-a35b-instruct": { + "id": "qwen3-coder-480b-a35b-instruct", "reasoning": false, "temperature": true, "toolCall": true, "modalities": { "input": [ - "text", - "image", - "audio", - "video", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 1048576, + "context": 262000, "output": 8192 - } - }, - "google/gemini-3.1-flash-lite": { - "id": "google/gemini-3.1-flash-lite", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video", - "audio" - ], - "output": [ - "text" - ] }, - "limit": { - "context": 1048576, - "output": 65536 - } + "family": "qwen" }, - "google/gemini-3-pro": { - "id": "google/gemini-3-pro", - "family": "gemini-pro", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - } - }, - "google/gemini-2.0-flash": { - "id": "google/gemini-2.0-flash", - "family": "gemini-flash", + "doubao-seed-code-preview-251028": { + "id": "doubao-seed-code-preview-251028", "reasoning": false, "temperature": true, "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 8192 - } - }, - "google/veo-3-fast": { - "id": "google/veo-3-fast", - "family": "veo", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 480, - "output": 0 - } - }, - "google/imagen-4-fast": { - "id": "google/imagen-4-fast", - "family": "imagen", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 480, - "output": 0 - } - }, - "lumalabs/ray2": { - "id": "lumalabs/ray2", - "family": "ray", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 5000, - "output": 0 - } - }, - "poetools/claude-code": { - "id": "poetools/claude-code", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "openai/gpt-5-pro": { - "id": "openai/gpt-5-pro", - "family": "gpt-pro", - "reasoning": true, - "temperature": false, - "toolCall": false, "modalities": { "input": [ "text", @@ -11960,321 +1041,16 @@ ] }, "limit": { - "context": 400000, - "output": 128000, - "input": 400000 + "context": 256000, + "output": 32000 } }, - "openai/gpt-5.1-codex-max": { - "id": "openai/gpt-5.1-codex-max", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000, - "input": 400000 - }, - "family": "gpt-codex" - }, - "openai/o3-deep-research": { - "id": "openai/o3-deep-research", - "family": "o", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000, - "input": 200000 - } - }, - "openai/o4-mini-deep-research": { - "id": "openai/o4-mini-deep-research", - "family": "o-mini", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000, - "input": 200000 - } - }, - "openai/gpt-5-chat": { - "id": "openai/gpt-5-chat", - "family": "gpt", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384, - "input": 111616 - } - }, - "openai/gpt-4-classic": { - "id": "openai/gpt-4-classic", - "family": "gpt", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 4096 - } - }, - "openai/gpt-5.3-instant": { - "id": "openai/gpt-5.3-instant", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 111616, - "output": 16384 - } - }, - "openai/gpt-image-1.5": { - "id": "openai/gpt-image-1.5", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 128000, - "output": 0 - } - }, - "openai/gpt-4.1-nano": { - "id": "openai/gpt-4.1-nano", + "gpt-4.1-nano": { + "id": "gpt-4.1-nano", "family": "gpt-nano", "reasoning": false, "temperature": true, "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1047576, - "output": 32768, - "input": 1047576 - } - }, - "openai/gpt-image-1-mini": { - "id": "openai/gpt-image-1-mini", - "family": "gpt", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "openai/sora-2-pro": { - "id": "openai/sora-2-pro", - "family": "sora", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "openai/gpt-4o-aug": { - "id": "openai/gpt-4o-aug", - "family": "gpt", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "openai/gpt-image-1": { - "id": "openai/gpt-image-1", - "family": "gpt", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 128000, - "output": 0 - } - }, - "openai/sora-2": { - "id": "openai/sora-2", - "family": "sora", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "openai/gpt-3.5-turbo-raw": { - "id": "openai/gpt-3.5-turbo-raw", - "family": "gpt", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4524, - "output": 2048 - } - }, - "openai/gpt-4o-mini-search": { - "id": "openai/gpt-4o-mini-search", - "family": "gpt-mini", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "openai/gpt-4.1-mini": { - "id": "openai/gpt-4.1-mini", - "family": "gpt-mini", - "reasoning": false, - "temperature": true, - "toolCall": true, "modalities": { "input": [ "text", @@ -12286,699 +1062,12 @@ }, "limit": { "context": 1047576, - "output": 32768, - "input": 1047576 - } - }, - "openai/o1-pro": { - "id": "openai/o1-pro", - "family": "o-pro", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000, - "input": 200000 - } - }, - "openai/chatgpt-4o-latest": { - "id": "openai/chatgpt-4o-latest", - "family": "gpt", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384, - "input": 128000 - } - }, - "openai/dall-e-3": { - "id": "openai/dall-e-3", - "family": "dall-e", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 800, - "output": 0 - } - }, - "openai/gpt-4o-search": { - "id": "openai/gpt-4o-search", - "family": "gpt", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "openai/gpt-4-classic-0314": { - "id": "openai/gpt-4-classic-0314", - "family": "gpt", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 4096 - } - }, - "openai/gpt-3.5-turbo-instruct": { - "id": "openai/gpt-3.5-turbo-instruct", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4095, - "output": 4096, - "input": 4096 - } - }, - "openai/gpt-5.2-instant": { - "id": "openai/gpt-5.2-instant", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "openai/o3-mini-high": { - "id": "openai/o3-mini-high", - "family": "o-mini", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000, - "input": 200000 - } - }, - "openai/gpt-5.1-instant": { - "id": "openai/gpt-5.1-instant", - "family": "gpt", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text", - "image" - ] - }, - "limit": { - "context": 128000, - "output": 16384, - "input": 111616 - } - }, - "topazlabs-co/topazlabs": { - "id": "topazlabs-co/topazlabs", - "family": "topazlabs", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 204, - "output": 0 - } - }, - "runwayml/runway": { - "id": "runwayml/runway", - "family": "runway", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 256, - "output": 0 - } - }, - "runwayml/runway-gen-4-turbo": { - "id": "runwayml/runway-gen-4-turbo", - "family": "runway", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "video" - ] - }, - "limit": { - "context": 256, - "output": 0 - } - }, - "anthropic/claude-sonnet-3.5-june": { - "id": "anthropic/claude-sonnet-3.5-june", - "family": "claude-sonnet", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 189096, - "output": 8192 - } - }, - "anthropic/claude-sonnet-3.5": { - "id": "anthropic/claude-sonnet-3.5", - "family": "claude-sonnet", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 189096, - "output": 8192 - } - }, - "anthropic/claude-haiku-3": { - "id": "anthropic/claude-haiku-3", - "family": "claude-haiku", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 189096, - "output": 8192 - } - }, - "anthropic/claude-haiku-3.5": { - "id": "anthropic/claude-haiku-3.5", - "family": "claude-haiku", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 189096, - "output": 8192 - } - }, - "anthropic/claude-sonnet-3.7": { - "id": "anthropic/claude-sonnet-3.7", - "family": "claude-sonnet", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 196608, - "output": 128000 - } - }, - "trytako/tako": { - "id": "trytako/tako", - "family": "tako", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2048, - "output": 0 - } - }, - "elevenlabs/elevenlabs-music": { - "id": "elevenlabs/elevenlabs-music", - "family": "elevenlabs", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 2000, - "output": 0 - } - }, - "elevenlabs/elevenlabs-v3": { - "id": "elevenlabs/elevenlabs-v3", - "family": "elevenlabs", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 128000, - "output": 0 - } - }, - "elevenlabs/elevenlabs-v2.5-turbo": { - "id": "elevenlabs/elevenlabs-v2.5-turbo", - "family": "elevenlabs", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 128000, - "output": 0 - } - }, - "cerebras/llama-3.1-8b-cs": { - "id": "cerebras/llama-3.1-8b-cs", - "reasoning": false, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "cerebras/gpt-oss-120b-cs": { - "id": "cerebras/gpt-oss-120b-cs", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "cerebras/qwen3-235b-2507-cs": { - "id": "cerebras/qwen3-235b-2507-cs", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "cerebras/llama-3.3-70b-cs": { - "id": "cerebras/llama-3.3-70b-cs", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "cerebras/qwen3-32b-cs": { - "id": "cerebras/qwen3-32b-cs", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "xai/grok-4-fast-reasoning": { - "id": "xai/grok-4-fast-reasoning", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 256000 - } - }, - "xai/grok-3": { - "id": "xai/grok-3", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "xai/grok-code-fast-1": { - "id": "xai/grok-code-fast-1", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 10000 - } - }, - "xai/grok-4.1-fast-reasoning": { - "id": "xai/grok-4.1-fast-reasoning", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 30000 - } - }, - "xai/grok-4": { - "id": "xai/grok-4", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 64000 - } - }, - "xai/grok-4.1-fast-non-reasoning": { - "id": "xai/grok-4.1-fast-non-reasoning", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 30000 - } - }, - "xai/grok-3-mini": { - "id": "xai/grok-3-mini", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "xai/grok-4-fast-non-reasoning": { - "id": "xai/grok-4-fast-non-reasoning", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 30000 - } - }, - "deepseek.r1-v1:0": { - "id": "deepseek.r1-v1:0", - "family": "deepseek-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, "output": 32768 } }, - "meta.llama3-1-70b-instruct-v1:0": { - "id": "meta.llama3-1-70b-instruct-v1:0", - "family": "llama", - "reasoning": false, + "deepseek-v3.2": { + "id": "deepseek-v3.2", + "reasoning": true, "temperature": true, "toolCall": true, "modalities": { @@ -12991,554 +1080,14 @@ }, "limit": { "context": 128000, - "output": 4096 - } - }, - "qwen.qwen3-coder-480b-a35b-v1:0": { - "id": "qwen.qwen3-coder-480b-a35b-v1:0", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 65536 - } - }, - "eu.anthropic.claude-sonnet-4-6": { - "id": "eu.anthropic.claude-sonnet-4-6", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - } - }, - "eu.anthropic.claude-haiku-4-5-20251001-v1:0": { - "id": "eu.anthropic.claude-haiku-4-5-20251001-v1:0", - "family": "claude-haiku", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "mistral.mistral-large-3-675b-instruct": { - "id": "mistral.mistral-large-3-675b-instruct", - "family": "mistral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 8192 - } - }, - "openai.gpt-oss-120b-1:0": { - "id": "openai.gpt-oss-120b-1:0", - "family": "gpt-oss", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "us.anthropic.claude-opus-4-20250514-v1:0": { - "id": "us.anthropic.claude-opus-4-20250514-v1:0", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - } - }, - "nvidia.nemotron-nano-12b-v2": { - "id": "nvidia.nemotron-nano-12b-v2", - "family": "nemotron", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "anthropic.claude-3-7-sonnet-20250219-v1:0": { - "id": "anthropic.claude-3-7-sonnet-20250219-v1:0", - "family": "claude-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192 - } - }, - "anthropic.claude-sonnet-4-6": { - "id": "anthropic.claude-sonnet-4-6", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - } - }, - "minimax.minimax-m2.1": { - "id": "minimax.minimax-m2.1", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "global.anthropic.claude-opus-4-5-20251101-v1:0": { - "id": "global.anthropic.claude-opus-4-5-20251101-v1:0", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "mistral.ministral-3-8b-instruct": { - "id": "mistral.ministral-3-8b-instruct", - "family": "ministral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "openai.gpt-oss-safeguard-20b": { - "id": "openai.gpt-oss-safeguard-20b", - "family": "gpt-oss", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "amazon.nova-lite-v1:0": { - "id": "amazon.nova-lite-v1:0", - "family": "nova-lite", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 300000, - "output": 8192 - } - }, - "eu.anthropic.claude-sonnet-4-5-20250929-v1:0": { - "id": "eu.anthropic.claude-sonnet-4-5-20250929-v1:0", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "mistral.pixtral-large-2502-v1:0": { - "id": "mistral.pixtral-large-2502-v1:0", - "family": "mistral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "google.gemma-3-12b-it": { - "id": "google.gemma-3-12b-it", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "meta.llama3-1-8b-instruct-v1:0": { - "id": "meta.llama3-1-8b-instruct-v1:0", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "mistral.devstral-2-123b": { - "id": "mistral.devstral-2-123b", - "family": "devstral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 8192 - } - }, - "anthropic.claude-sonnet-4-5-20250929-v1:0": { - "id": "anthropic.claude-sonnet-4-5-20250929-v1:0", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "meta.llama4-maverick-17b-instruct-v1:0": { - "id": "meta.llama4-maverick-17b-instruct-v1:0", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 16384 - } - }, - "mistral.ministral-3-14b-instruct": { - "id": "mistral.ministral-3-14b-instruct", - "family": "ministral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "minimax.minimax-m2": { - "id": "minimax.minimax-m2", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204608, "output": 128000 - } - }, - "amazon.nova-micro-v1:0": { - "id": "amazon.nova-micro-v1:0", - "family": "nova-micro", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] }, - "limit": { - "context": 128000, - "output": 8192 - } + "family": "deepseek" }, - "anthropic.claude-3-5-sonnet-20241022-v2:0": { - "id": "anthropic.claude-3-5-sonnet-20241022-v2:0", - "family": "claude-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192 - } - }, - "nvidia.nemotron-nano-3-30b": { - "id": "nvidia.nemotron-nano-3-30b", - "family": "nemotron", + "gpt-5-pro": { + "id": "gpt-5-pro", "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "anthropic.claude-sonnet-4-20250514-v1:0": { - "id": "anthropic.claude-sonnet-4-20250514-v1:0", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "qwen.qwen3-vl-235b-a22b": { - "id": "qwen.qwen3-vl-235b-a22b", - "family": "qwen", - "reasoning": false, - "temperature": true, + "temperature": false, "toolCall": true, "modalities": { "input": [ @@ -13550,1192 +1099,11 @@ ] }, "limit": { - "context": 262000, - "output": 262000 - } - }, - "global.anthropic.claude-opus-4-6-v1": { - "id": "global.anthropic.claude-opus-4-6-v1", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] + "context": 400000, + "output": 272000, + "input": 272000 }, - "limit": { - "context": 1000000, - "output": 128000 - } - }, - "writer.palmyra-x4-v1:0": { - "id": "writer.palmyra-x4-v1:0", - "family": "palmyra", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 122880, - "output": 8192 - } - }, - "minimax.minimax-m2.5": { - "id": "minimax.minimax-m2.5", - "family": "minimax-m2.5", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 131072 - } - }, - "amazon.nova-pro-v1:0": { - "id": "amazon.nova-pro-v1:0", - "family": "nova-pro", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 300000, - "output": 8192 - } - }, - "us.anthropic.claude-opus-4-5-20251101-v1:0": { - "id": "us.anthropic.claude-opus-4-5-20251101-v1:0", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "meta.llama3-2-90b-instruct-v1:0": { - "id": "meta.llama3-2-90b-instruct-v1:0", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "us.anthropic.claude-opus-4-6-v1": { - "id": "us.anthropic.claude-opus-4-6-v1", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 - } - }, - "google.gemma-3-4b-it": { - "id": "google.gemma-3-4b-it", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "anthropic.claude-opus-4-6-v1": { - "id": "anthropic.claude-opus-4-6-v1", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 - } - }, - "zai.glm-4.7-flash": { - "id": "zai.glm-4.7-flash", - "family": "glm-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 131072 - } - }, - "anthropic.claude-opus-4-20250514-v1:0": { - "id": "anthropic.claude-opus-4-20250514-v1:0", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - } - }, - "global.anthropic.claude-sonnet-4-6": { - "id": "global.anthropic.claude-sonnet-4-6", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - } - }, - "meta.llama3-2-1b-instruct-v1:0": { - "id": "meta.llama3-2-1b-instruct-v1:0", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 4096 - } - }, - "anthropic.claude-opus-4-1-20250805-v1:0": { - "id": "anthropic.claude-opus-4-1-20250805-v1:0", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - } - }, - "meta.llama4-scout-17b-instruct-v1:0": { - "id": "meta.llama4-scout-17b-instruct-v1:0", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 3500000, - "output": 16384 - } - }, - "deepseek.v3.2": { - "id": "deepseek.v3.2", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 81920 - } - }, - "deepseek.v3-v1:0": { - "id": "deepseek.v3-v1:0", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 81920 - } - }, - "mistral.ministral-3-3b-instruct": { - "id": "mistral.ministral-3-3b-instruct", - "family": "ministral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 8192 - } - }, - "global.anthropic.claude-haiku-4-5-20251001-v1:0": { - "id": "global.anthropic.claude-haiku-4-5-20251001-v1:0", - "family": "claude-haiku", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "nvidia.nemotron-nano-9b-v2": { - "id": "nvidia.nemotron-nano-9b-v2", - "family": "nemotron", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "writer.palmyra-x5-v1:0": { - "id": "writer.palmyra-x5-v1:0", - "family": "palmyra", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1040000, - "output": 8192 - } - }, - "meta.llama3-3-70b-instruct-v1:0": { - "id": "meta.llama3-3-70b-instruct-v1:0", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "zai.glm-4.7": { - "id": "zai.glm-4.7", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "moonshot.kimi-k2-thinking": { - "id": "moonshot.kimi-k2-thinking", - "family": "kimi-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "anthropic.claude-3-haiku-20240307-v1:0": { - "id": "anthropic.claude-3-haiku-20240307-v1:0", - "family": "claude-haiku", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 4096 - } - }, - "us.anthropic.claude-sonnet-4-5-20250929-v1:0": { - "id": "us.anthropic.claude-sonnet-4-5-20250929-v1:0", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "openai.gpt-oss-20b-1:0": { - "id": "openai.gpt-oss-20b-1:0", - "family": "gpt-oss", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "us.anthropic.claude-sonnet-4-6": { - "id": "us.anthropic.claude-sonnet-4-6", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - } - }, - "meta.llama3-2-11b-instruct-v1:0": { - "id": "meta.llama3-2-11b-instruct-v1:0", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "eu.anthropic.claude-opus-4-5-20251101-v1:0": { - "id": "eu.anthropic.claude-opus-4-5-20251101-v1:0", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "meta.llama3-1-405b-instruct-v1:0": { - "id": "meta.llama3-1-405b-instruct-v1:0", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "qwen.qwen3-next-80b-a3b": { - "id": "qwen.qwen3-next-80b-a3b", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262000, - "output": 262000 - } - }, - "us.anthropic.claude-sonnet-4-20250514-v1:0": { - "id": "us.anthropic.claude-sonnet-4-20250514-v1:0", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "qwen.qwen3-coder-30b-a3b-v1:0": { - "id": "qwen.qwen3-coder-30b-a3b-v1:0", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 131072 - } - }, - "us.anthropic.claude-haiku-4-5-20251001-v1:0": { - "id": "us.anthropic.claude-haiku-4-5-20251001-v1:0", - "family": "claude-haiku", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "qwen.qwen3-235b-a22b-2507-v1:0": { - "id": "qwen.qwen3-235b-a22b-2507-v1:0", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 131072 - } - }, - "openai.gpt-oss-safeguard-120b": { - "id": "openai.gpt-oss-safeguard-120b", - "family": "gpt-oss", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "anthropic.claude-3-5-sonnet-20240620-v1:0": { - "id": "anthropic.claude-3-5-sonnet-20240620-v1:0", - "family": "claude-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192 - } - }, - "mistral.voxtral-small-24b-2507": { - "id": "mistral.voxtral-small-24b-2507", - "family": "mistral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 8192 - } - }, - "anthropic.claude-haiku-4-5-20251001-v1:0": { - "id": "anthropic.claude-haiku-4-5-20251001-v1:0", - "family": "claude-haiku", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "meta.llama3-2-3b-instruct-v1:0": { - "id": "meta.llama3-2-3b-instruct-v1:0", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 4096 - } - }, - "google.gemma-3-27b-it": { - "id": "google.gemma-3-27b-it", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 8192 - } - }, - "us.anthropic.claude-opus-4-1-20250805-v1:0": { - "id": "us.anthropic.claude-opus-4-1-20250805-v1:0", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - } - }, - "global.anthropic.claude-sonnet-4-20250514-v1:0": { - "id": "global.anthropic.claude-sonnet-4-20250514-v1:0", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "anthropic.claude-3-5-haiku-20241022-v1:0": { - "id": "anthropic.claude-3-5-haiku-20241022-v1:0", - "family": "claude-haiku", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192 - } - }, - "zai.glm-5": { - "id": "zai.glm-5", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 131072 - } - }, - "eu.anthropic.claude-sonnet-4-20250514-v1:0": { - "id": "eu.anthropic.claude-sonnet-4-20250514-v1:0", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "anthropic.claude-opus-4-5-20251101-v1:0": { - "id": "anthropic.claude-opus-4-5-20251101-v1:0", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "eu.anthropic.claude-opus-4-6-v1": { - "id": "eu.anthropic.claude-opus-4-6-v1", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 - } - }, - "amazon.nova-premier-v1:0": { - "id": "amazon.nova-premier-v1:0", - "family": "nova", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 16384 - } - }, - "amazon.nova-2-lite-v1:0": { - "id": "amazon.nova-2-lite-v1:0", - "family": "nova", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "qwen.qwen3-32b-v1:0": { - "id": "qwen.qwen3-32b-v1:0", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 16384 - } - }, - "mistral.magistral-small-2509": { - "id": "mistral.magistral-small-2509", - "family": "magistral", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 40000 - } - }, - "moonshotai.kimi-k2.5": { - "id": "moonshotai.kimi-k2.5", - "family": "kimi", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "mistral.voxtral-mini-3b-2507": { - "id": "mistral.voxtral-mini-3b-2507", - "family": "mistral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "global.anthropic.claude-sonnet-4-5-20250929-v1:0": { - "id": "global.anthropic.claude-sonnet-4-5-20250929-v1:0", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "ring-1t": { - "id": "Ring-1T", - "family": "ring", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32000 - } - }, - "ling-1t": { - "id": "Ling-1T", - "family": "ling", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32000 - } - }, - "phi-3-small-8k-instruct": { - "id": "phi-3-small-8k-instruct", - "family": "phi", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 2048 - } + "family": "gpt-pro" }, "gpt-4o": { "id": "gpt-4o", @@ -14758,162 +1126,8 @@ "input": 64000 } }, - "codestral-2501": { - "id": "codestral-2501", - "family": "codestral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "mistral-small-2503": { - "id": "mistral-small-2503", - "family": "mistral-small", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "o1-mini": { - "id": "o1-mini", - "family": "o-mini", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 65536 - } - }, - "gpt-3.5-turbo-instruct": { - "id": "gpt-3.5-turbo-instruct", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4096, - "output": 4096 - } - }, - "gpt-4": { - "id": "gpt-4", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "gpt-3.5-turbo-1106": { - "id": "gpt-3.5-turbo-1106", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 16384 - } - }, - "phi-4-reasoning": { - "id": "phi-4-reasoning", - "family": "phi", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 4096 - } - }, - "phi-3-mini-128k-instruct": { - "id": "phi-3-mini-128k-instruct", - "family": "phi", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "gpt-5-mini": { - "id": "gpt-5-mini", - "family": "gpt-mini", + "gpt-5": { + "id": "gpt-5", "reasoning": true, "temperature": false, "toolCall": true, @@ -14930,429 +1144,28 @@ "context": 272000, "output": 128000, "input": 272000 - } + }, + "family": "gpt" }, - "grok-4-fast-non-reasoning": { - "id": "grok-4-fast-non-reasoning", - "family": "grok", - "reasoning": false, + "claude-sonnet-4-5-20250929-thinking": { + "id": "claude-sonnet-4-5-20250929-thinking", + "reasoning": true, "temperature": true, "toolCall": true, "modalities": { "input": [ "text", - "image" + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "output": 30000 - } - }, - "o3-mini": { - "id": "o3-mini", - "family": "o-mini", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000 - } - }, - "cohere-embed-v3-english": { - "id": "cohere-embed-v3-english", - "family": "cohere-embed", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 512, - "output": 1024 - } - }, - "phi-3-medium-4k-instruct": { - "id": "phi-3-medium-4k-instruct", - "family": "phi", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4096, - "output": 1024 - } - }, - "cohere-embed-v3-multilingual": { - "id": "cohere-embed-v3-multilingual", - "family": "cohere-embed", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 512, - "output": 1024 - } - }, - "gpt-3.5-turbo-0125": { - "id": "gpt-3.5-turbo-0125", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 16384 - } - }, - "phi-4-mini-reasoning": { - "id": "phi-4-mini-reasoning", - "family": "phi", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "mistral-large-2411": { - "id": "mistral-large-2411", - "family": "mistral-large", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "meta-llama-3.1-8b-instruct": { - "id": "meta-llama-3.1-8b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "o1-preview": { - "id": "o1-preview", - "family": "o", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "meta-llama-3.1-70b-instruct": { - "id": "meta-llama-3.1-70b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "phi-3-mini-4k-instruct": { - "id": "phi-3-mini-4k-instruct", - "family": "phi", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4096, - "output": 1024 - } - }, - "codex-mini": { - "id": "codex-mini", - "family": "gpt-codex-mini", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000 - } - }, - "phi-4-reasoning-plus": { - "id": "phi-4-reasoning-plus", - "family": "phi", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 4096 - } - }, - "gpt-4.1-mini": { - "id": "gpt-4.1-mini", - "family": "gpt-mini", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1047576, - "output": 32768 - } - }, - "phi-4": { - "id": "phi-4", - "family": "phi", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "o4-mini": { - "id": "o4-mini", - "family": "o-mini", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000 - } - }, - "gpt-4-32k": { - "id": "gpt-4-32k", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "grok-3-mini": { - "id": "grok-3-mini", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "cohere-embed-v-4-0": { - "id": "cohere-embed-v-4-0", - "family": "cohere-embed", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 1536 - } - }, - "mistral-nemo": { - "id": "mistral-nemo", - "family": "mistral-nemo", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "gpt-4-turbo": { - "id": "gpt-4-turbo", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 + "context": 1000000, + "output": 64000, + "input": 1000000 } }, "gpt-4.1": { @@ -15376,85 +1189,52 @@ "input": 64000 } }, - "model-router": { - "id": "model-router", - "family": "model-router", + "kimi-k2-thinking": { + "id": "kimi-k2-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + }, + "family": "kimi-thinking" + }, + "gemini-2.0-flash-lite": { + "id": "gemini-2.0-flash-lite", + "family": "gemini-flash-lite", "reasoning": false, + "temperature": true, "toolCall": true, "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 1048576, + "output": 8192, + "input": 1000000 } }, - "text-embedding-3-large": { - "id": "text-embedding-3-large", - "family": "text-embedding", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8191, - "output": 3072 - }, - "temperature": false - }, - "gpt-3.5-turbo-0613": { - "id": "gpt-3.5-turbo-0613", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 16384 - } - }, - "cohere-command-r-08-2024": { - "id": "cohere-command-r-08-2024", - "family": "command-r", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4000 - } - }, - "gpt-4.1-nano": { - "id": "gpt-4.1-nano", - "family": "gpt-nano", + "gpt-4.1-mini": { + "id": "gpt-4.1-mini", + "family": "gpt-mini", "reasoning": false, "temperature": true, "toolCall": true, @@ -15472,560 +1252,11 @@ "output": 32768 } }, - "deepseek-v3.2-speciale": { - "id": "deepseek-v3.2-speciale", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "phi-4-mini": { - "id": "phi-4-mini", - "family": "phi", + "grok-4-fast-non-reasoning": { + "id": "grok-4-fast-non-reasoning", "reasoning": false, "temperature": true, "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "text-embedding-3-small": { - "id": "text-embedding-3-small", - "family": "text-embedding", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8191, - "output": 1536 - }, - "temperature": false - }, - "gpt-3.5-turbo-0301": { - "id": "gpt-3.5-turbo-0301", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4096, - "output": 4096 - } - }, - "meta-llama-3-70b-instruct": { - "id": "meta-llama-3-70b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 2048 - } - }, - "llama-3.2-11b-vision-instruct": { - "id": "llama-3.2-11b-vision-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "o3": { - "id": "o3", - "family": "o", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000 - } - }, - "meta-llama-3-8b-instruct": { - "id": "meta-llama-3-8b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 2048 - } - }, - "gpt-5.1-chat": { - "id": "gpt-5.1-chat", - "family": "gpt-codex", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio" - ], - "output": [ - "text", - "image", - "audio" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "grok-4": { - "id": "grok-4", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 64000 - } - }, - "gpt-5-chat": { - "id": "gpt-5-chat", - "family": "gpt-codex", - "reasoning": true, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "gpt-5.2-chat": { - "id": "gpt-5.2-chat", - "family": "gpt-codex", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "cohere-command-r-plus-08-2024": { - "id": "cohere-command-r-plus-08-2024", - "family": "command-r", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4000 - } - }, - "meta-llama-3.1-405b-instruct": { - "id": "meta-llama-3.1-405b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "llama-4-scout-17b-16e-instruct": { - "id": "llama-4-scout-17b-16e-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "o1": { - "id": "o1", - "family": "o", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000 - } - }, - "mistral-medium-2505": { - "id": "mistral-medium-2505", - "family": "mistral-medium", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "cohere-command-a": { - "id": "cohere-command-a", - "family": "command-a", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 8000 - } - }, - "phi-3.5-mini-instruct": { - "id": "phi-3.5-mini-instruct", - "family": "phi", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "grok-code-fast-1": { - "id": "grok-code-fast-1", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 10000, - "input": 128000 - } - }, - "llama-3.2-90b-vision-instruct": { - "id": "llama-3.2-90b-vision-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "grok-3": { - "id": "grok-3", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "ministral-3b": { - "id": "ministral-3b", - "family": "ministral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "gpt-4-turbo-vision": { - "id": "gpt-4-turbo-vision", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "phi-3.5-moe-instruct": { - "id": "phi-3.5-moe-instruct", - "family": "phi", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "mai-ds-r1": { - "id": "mai-ds-r1", - "family": "mai", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "phi-4-multimodal": { - "id": "phi-4-multimodal", - "family": "phi", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "phi-3-medium-128k-instruct": { - "id": "phi-3-medium-128k-instruct", - "family": "phi", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "grok-4-fast-reasoning": { - "id": "grok-4-fast-reasoning", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, "modalities": { "input": [ "text", @@ -16038,31 +1269,12 @@ "limit": { "context": 2000000, "output": 30000 - } - }, - "text-embedding-ada-002": { - "id": "text-embedding-ada-002", - "family": "text-embedding", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] }, - "limit": { - "context": 8192, - "output": 1536 - }, - "temperature": false + "family": "grok" }, - "gpt-4o-mini": { - "id": "gpt-4o-mini", - "family": "gpt-mini", - "reasoning": false, + "doubao-seed-1-6-thinking-250715": { + "id": "doubao-seed-1-6-thinking-250715", + "reasoning": true, "temperature": true, "toolCall": true, "modalities": { @@ -16075,111 +1287,32 @@ ] }, "limit": { - "context": 128000, + "context": 256000, + "output": 16000 + } + }, + "ministral-14b-2512": { + "id": "ministral-14b-2512", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, "output": 16384 - } - }, - "phi-3-small-128k-instruct": { - "id": "phi-3-small-128k-instruct", - "family": "phi", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] }, - "limit": { - "context": 128000, - "output": 4096 - } + "family": "mistral" }, - "gpt-5-pro": { - "id": "gpt-5-pro", - "family": "gpt-pro", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 272000, - "input": 272000 - } - }, - "qwen-vl-plus": { - "id": "qwen-vl-plus", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "qwen-vl-max": { - "id": "qwen-vl-max", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "qwen3-14b": { - "id": "qwen3-14b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "qwen3-coder-flash": { - "id": "qwen3-coder-flash", + "qwen3-coder-plus": { + "id": "qwen3-coder-plus", "family": "qwen", "reasoning": false, "temperature": true, @@ -16194,11 +1327,72 @@ }, "limit": { "context": 1000000, - "output": 65536 + "output": 66000 } }, - "qwen3-vl-30b-a3b": { - "id": "qwen3-vl-30b-a3b", + "qwen-vl-ocr": { + "id": "qwen-vl-ocr", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 34096, + "output": 4096 + } + }, + "qwen-omni-turbo-realtime": { + "id": "qwen-omni-turbo-realtime", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text", + "audio" + ] + }, + "limit": { + "context": 32768, + "output": 2048 + } + }, + "qwen3-8b": { + "id": "qwen3-8b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "qwen3.5-397b-a17b": { + "id": "qwen3.5-397b-a17b", "family": "qwen", "reasoning": true, "temperature": true, @@ -16212,9 +1406,254 @@ "text" ] }, + "limit": { + "context": 256000, + "output": 64000 + } + }, + "qwq-plus": { + "id": "qwq-plus", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, "limit": { "context": 131072, - "output": 32768 + "output": 8192 + } + }, + "qwen-vl-plus": { + "id": "qwen-vl-plus", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32000 + } + }, + "qwen3-livetranslate-flash-realtime": { + "id": "qwen3-livetranslate-flash-realtime", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text", + "audio" + ] + }, + "limit": { + "context": 53248, + "output": 4096 + } + }, + "qwen3-32b": { + "id": "qwen3-32b", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + } + }, + "qwen-max": { + "id": "qwen-max", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32000, + "input": 32000 + } + }, + "qwen-omni-turbo": { + "id": "qwen-omni-turbo", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + } + }, + "qwen2-5-vl-7b-instruct": { + "id": "qwen2-5-vl-7b-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "qwen3.6-plus": { + "id": "qwen3.6-plus", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 65536 + } + }, + "qwen3-max": { + "id": "qwen3-max", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 32800 + } + }, + "qwen3-omni-flash": { + "id": "qwen3-omni-flash", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text", + "audio" + ] + }, + "limit": { + "context": 65536, + "output": 16384 + } + }, + "qwen2-5-72b-instruct": { + "id": "qwen2-5-72b-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "qwen3-vl-235b-a22b": { + "id": "qwen3-vl-235b-a22b", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 16384 } }, "qwen3-asr-flash": { @@ -16236,8 +1675,66 @@ "output": 4096 } }, - "qwen-max": { - "id": "qwen-max", + "qwen3-next-80b-a3b-thinking": { + "id": "qwen3-next-80b-a3b-thinking", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "qwen-mt-plus": { + "id": "qwen-mt-plus", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "output": 8192 + } + }, + "qwen-vl-max": { + "id": "qwen-vl-max", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32000 + } + }, + "qwen3-coder-flash": { + "id": "qwen3-coder-flash", "family": "qwen", "reasoning": false, "temperature": true, @@ -16251,9 +1748,8 @@ ] }, "limit": { - "context": 131072, - "output": 8192, - "input": 32000 + "context": 1000000, + "output": 65536 } }, "qwen2-5-7b-instruct": { @@ -16275,26 +1771,6 @@ "output": 8192 } }, - "qwen2-5-vl-72b-instruct": { - "id": "qwen2-5-vl-72b-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, "qwen2-5-14b-instruct": { "id": "qwen2-5-14b-instruct", "family": "qwen", @@ -16314,10 +1790,10 @@ "output": 8192 } }, - "qwen3-8b": { - "id": "qwen3-8b", + "qwen2-5-32b-instruct": { + "id": "qwen2-5-32b-instruct", "family": "qwen", - "reasoning": true, + "reasoning": false, "temperature": true, "toolCall": true, "modalities": { @@ -16333,9 +1809,69 @@ "output": 8192 } }, - "qvq-max": { - "id": "qvq-max", - "family": "qvq", + "qwen3-next-80b-a3b-instruct": { + "id": "qwen3-next-80b-a3b-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 129024, + "output": 32768 + } + }, + "qwen-plus-character-ja": { + "id": "qwen-plus-character-ja", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 512 + } + }, + "qwen3-omni-flash-realtime": { + "id": "qwen3-omni-flash-realtime", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text", + "audio" + ] + }, + "limit": { + "context": 65536, + "output": 16384 + } + }, + "qwen3-vl-30b-a3b": { + "id": "qwen3-vl-30b-a3b", + "family": "qwen", "reasoning": true, "temperature": true, "toolCall": true, @@ -16350,10 +1886,88 @@ }, "limit": { "context": 131072, + "output": 32768 + } + }, + "qwen3-vl-plus": { + "id": "qwen3-vl-plus", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "qwen3-coder-30b-a3b-instruct": { + "id": "qwen3-coder-30b-a3b-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, "output": 8192, "input": 128000 } }, + "qwen-turbo": { + "id": "qwen-turbo", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 8192, + "input": 1000000 + } + }, + "qwen-mt-turbo": { + "id": "qwen-mt-turbo", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "output": 8192 + } + }, "qwen2-5-omni-7b": { "id": "qwen2-5-omni-7b", "family": "qwen", @@ -16377,122 +1991,16 @@ "output": 2048 } }, - "qwen2-5-vl-7b-instruct": { - "id": "qwen2-5-vl-7b-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "qwen-omni-turbo-realtime": { - "id": "qwen-omni-turbo-realtime", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio" - ], - "output": [ - "text", - "audio" - ] - }, - "limit": { - "context": 32768, - "output": 2048 - } - }, - "qwen-omni-turbo": { - "id": "qwen-omni-turbo", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text", - "audio" - ] - }, - "limit": { - "context": 32768, - "output": 2048 - } - }, - "qwen-mt-plus": { - "id": "qwen-mt-plus", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 8192 - } - }, - "qwen3-livetranslate-flash-realtime": { - "id": "qwen3-livetranslate-flash-realtime", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text", - "audio" - ] - }, - "limit": { - "context": 53248, - "output": 4096 - } - }, - "qwen-plus": { - "id": "qwen-plus", + "qwen3.5-plus": { + "id": "qwen3.5-plus", "family": "qwen", "reasoning": true, "temperature": true, "toolCall": true, "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -16500,114 +2008,11 @@ }, "limit": { "context": 1000000, - "output": 32768, - "input": 995904 + "output": 65536 } }, - "qwen2-5-32b-instruct": { - "id": "qwen2-5-32b-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "qwen3-omni-flash": { - "id": "qwen3-omni-flash", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text", - "audio" - ] - }, - "limit": { - "context": 65536, - "output": 16384 - } - }, - "qwen-flash": { - "id": "qwen-flash", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 32768 - } - }, - "qwen2-5-72b-instruct": { - "id": "qwen2-5-72b-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "qwen3-omni-flash-realtime": { - "id": "qwen3-omni-flash-realtime", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio" - ], - "output": [ - "text", - "audio" - ] - }, - "limit": { - "context": 65536, - "output": 16384 - } - }, - "qwen-vl-ocr": { - "id": "qwen-vl-ocr", + "qwen2-5-vl-72b-instruct": { + "id": "qwen2-5-vl-72b-instruct", "family": "qwen", "reasoning": false, "temperature": true, @@ -16621,1061 +2026,15 @@ "text" ] }, - "limit": { - "context": 34096, - "output": 4096 - } - }, - "qwq-plus": { - "id": "qwq-plus", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "qwen3-vl-235b-a22b": { - "id": "qwen3-vl-235b-a22b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "qwen-plus-character-ja": { - "id": "qwen-plus-character-ja", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 512 - } - }, - "qwen-mt-turbo": { - "id": "qwen-mt-turbo", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 8192 - } - }, - "@cf/zai-org/glm-4.7-flash": { - "id": "@cf/zai-org/glm-4.7-flash", - "family": "glm-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "@cf/nvidia/nemotron-3-120b-a12b": { - "id": "@cf/nvidia/nemotron-3-120b-a12b", - "family": "nemotron", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "@cf/ibm-granite/granite-4.0-h-micro": { - "id": "@cf/ibm-granite/granite-4.0-h-micro", - "family": "granite", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/baai/bge-small-en-v1.5": { - "id": "@cf/baai/bge-small-en-v1.5", - "family": "bge", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/baai/bge-large-en-v1.5": { - "id": "@cf/baai/bge-large-en-v1.5", - "family": "bge", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/baai/bge-reranker-base": { - "id": "@cf/baai/bge-reranker-base", - "family": "bge", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/baai/bge-m3": { - "id": "@cf/baai/bge-m3", - "family": "bge", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/baai/bge-base-en-v1.5": { - "id": "@cf/baai/bge-base-en-v1.5", - "family": "bge", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/pfnet/plamo-embedding-1b": { - "id": "@cf/pfnet/plamo-embedding-1b", - "family": "plamo", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b": { - "id": "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b", - "family": "deepseek-thinking", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/facebook/bart-large-cnn": { - "id": "@cf/facebook/bart-large-cnn", - "family": "bart", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/mistral/mistral-7b-instruct-v0.1": { - "id": "@cf/mistral/mistral-7b-instruct-v0.1", - "family": "mistral", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/myshell-ai/melotts": { - "id": "@cf/myshell-ai/melotts", - "family": "melotts", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/pipecat-ai/smart-turn-v2": { - "id": "@cf/pipecat-ai/smart-turn-v2", - "family": "smart-turn", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/moonshotai/kimi-k2.5": { - "id": "@cf/moonshotai/kimi-k2.5", - "family": "kimi", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "@cf/google/gemma-3-12b-it": { - "id": "@cf/google/gemma-3-12b-it", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/qwen/qwq-32b": { - "id": "@cf/qwen/qwq-32b", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/qwen/qwen3-30b-a3b-fp8": { - "id": "@cf/qwen/qwen3-30b-a3b-fp8", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/qwen/qwen2.5-coder-32b-instruct": { - "id": "@cf/qwen/qwen2.5-coder-32b-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/qwen/qwen3-embedding-0.6b": { - "id": "@cf/qwen/qwen3-embedding-0.6b", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/meta/llama-3.1-8b-instruct-fp8": { - "id": "@cf/meta/llama-3.1-8b-instruct-fp8", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/meta/llama-3-8b-instruct-awq": { - "id": "@cf/meta/llama-3-8b-instruct-awq", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/meta/llama-3.1-8b-instruct-awq": { - "id": "@cf/meta/llama-3.1-8b-instruct-awq", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/meta/llama-4-scout-17b-16e-instruct": { - "id": "@cf/meta/llama-4-scout-17b-16e-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/meta/llama-3.2-11b-vision-instruct": { - "id": "@cf/meta/llama-3.2-11b-vision-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/meta/llama-3.2-3b-instruct": { - "id": "@cf/meta/llama-3.2-3b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/meta/llama-guard-3-8b": { - "id": "@cf/meta/llama-guard-3-8b", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/meta/llama-3.2-1b-instruct": { - "id": "@cf/meta/llama-3.2-1b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/meta/llama-3.3-70b-instruct-fp8-fast": { - "id": "@cf/meta/llama-3.3-70b-instruct-fp8-fast", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/meta/llama-3.1-8b-instruct": { - "id": "@cf/meta/llama-3.1-8b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/meta/m2m100-1.2b": { - "id": "@cf/meta/m2m100-1.2b", - "family": "m2m", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/meta/llama-2-7b-chat-fp16": { - "id": "@cf/meta/llama-2-7b-chat-fp16", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/meta/llama-3-8b-instruct": { - "id": "@cf/meta/llama-3-8b-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/mistralai/mistral-small-3.1-24b-instruct": { - "id": "@cf/mistralai/mistral-small-3.1-24b-instruct", - "family": "mistral-small", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/deepgram/aura-2-es": { - "id": "@cf/deepgram/aura-2-es", - "family": "aura", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/deepgram/nova-3": { - "id": "@cf/deepgram/nova-3", - "family": "nova", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/deepgram/aura-2-en": { - "id": "@cf/deepgram/aura-2-en", - "family": "aura", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/openai/gpt-oss-120b": { - "id": "@cf/openai/gpt-oss-120b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/openai/gpt-oss-20b": { - "id": "@cf/openai/gpt-oss-20b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/ai4bharat/indictrans2-en-indic-1b": { - "id": "@cf/ai4bharat/indictrans2-en-indic-1B", - "family": "indictrans", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/huggingface/distilbert-sst-2-int8": { - "id": "@cf/huggingface/distilbert-sst-2-int8", - "family": "distilbert", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "@cf/aisingapore/gemma-sea-lion-v4-27b-it": { - "id": "@cf/aisingapore/gemma-sea-lion-v4-27b-it", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "llama3-70b-8192": { - "id": "llama3-70b-8192", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "qwen-qwq-32b": { - "id": "qwen-qwq-32b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "llama-3.1-8b-instant": { - "id": "llama-3.1-8b-instant", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32678 - } - }, - "llama-guard-3-8b": { - "id": "llama-guard-3-8b", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "llama3-8b-8192": { - "id": "llama3-8b-8192", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "mistral-saba-24b": { - "id": "mistral-saba-24b", - "family": "mistral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, "limit": { "context": 32768, - "output": 32768 - } - }, - "llama-3.3-70b-versatile": { - "id": "llama-3.3-70b-versatile", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32678 - } - }, - "gemma2-9b-it": { - "id": "gemma2-9b-it", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, "output": 8192 } }, - "meta-llama/llama-guard-4-12b": { - "id": "meta-llama/llama-guard-4-12b", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 32768 - } - }, - "meta-llama/llama-4-maverick-17b-128e-instruct": { - "id": "meta-llama/llama-4-maverick-17b-128e-instruct", - "family": "llama", - "reasoning": false, + "qvq-max": { + "id": "qvq-max", + "family": "qvq", + "reasoning": true, "temperature": true, "toolCall": true, "modalities": { @@ -17689,69 +2048,13 @@ }, "limit": { "context": 131072, - "output": 8192 + "output": 8192, + "input": 128000 } }, - "zai-org/glm-5-fp8": { - "id": "zai-org/GLM-5-FP8", - "family": "glm", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 200000 - } - }, - "nvidia/nvidia-nemotron-3-super-120b-a12b-fp8": { - "id": "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-FP8", - "family": "nemotron", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "openpipe/qwen3-14b-instruct": { - "id": "OpenPipe/Qwen3-14B-Instruct", + "qwen3-14b": { + "id": "qwen3-14b", "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "coding-glm-4.7-free": { - "id": "coding-glm-4.7-free", - "family": "glm", "reasoning": true, "temperature": true, "toolCall": true, @@ -17764,406 +2067,13 @@ ] }, "limit": { - "context": 204800, - "output": 131072 + "context": 131072, + "output": 8192 } }, - "coding-minimax-m2.1-free": { - "id": "coding-minimax-m2.1-free", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "claude-opus-4-6-think": { - "id": "claude-opus-4-6-think", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 128000 - } - }, - "gemini-3-pro-preview-search": { - "id": "gemini-3-pro-preview-search", - "family": "gemini-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 65000 - } - }, - "deepseek-v3.2-think": { - "id": "deepseek-v3.2-think", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 64000 - } - }, - "gemini-3-pro-preview": { - "id": "gemini-3-pro-preview", - "family": "gemini-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048756, - "output": 65536, - "input": 1048756 - } - }, - "deepseek-v3.2-fast": { - "id": "deepseek-v3.2-fast", - "family": "deepseek", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "coding-glm-4.7": { - "id": "coding-glm-4.7", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "coding-glm-5-free": { - "id": "coding-glm-5-free", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "claude-sonnet-4-6-think": { - "id": "claude-sonnet-4-6-think", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "k2p5": { - "id": "k2p5", - "family": "kimi-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - } - }, - "devstral-medium-2507": { - "id": "devstral-medium-2507", - "family": "devstral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "labs-devstral-small-2512": { - "id": "labs-devstral-small-2512", - "family": "devstral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "devstral-medium-latest": { - "id": "devstral-medium-latest", - "family": "devstral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "open-mistral-7b": { - "id": "open-mistral-7b", - "family": "mistral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8000, - "output": 8000 - } - }, - "mistral-small-2506": { - "id": "mistral-small-2506", - "family": "mistral-small", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "codestral-latest": { - "id": "codestral-latest", - "family": "codestral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 4096 - } - }, - "ministral-8b-latest": { - "id": "ministral-8b-latest", - "family": "ministral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "magistral-small": { - "id": "magistral-small", - "family": "magistral-small", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "mistral-large-2512": { - "id": "mistral-large-2512", - "family": "mistral-large", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 262144 - } - }, - "ministral-3b-latest": { - "id": "ministral-3b-latest", - "family": "ministral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "mistral-embed": { - "id": "mistral-embed", - "family": "mistral-embed", + "qwen3-embedding-8b": { + "id": "qwen3-embedding-8b", + "family": "qwen", "reasoning": false, "temperature": false, "toolCall": false, @@ -18176,13 +2086,13 @@ ] }, "limit": { - "context": 8000, - "output": 3072 + "context": 32768, + "output": 4096 } }, - "devstral-small-2505": { - "id": "devstral-small-2505", - "family": "devstral", + "llama-3.3-70b-instruct": { + "id": "llama-3.3-70b-instruct", + "family": "llama", "reasoning": false, "temperature": true, "toolCall": true, @@ -18196,11 +2106,49 @@ }, "limit": { "context": 128000, - "output": 128000 + "output": 32768 } }, - "pixtral-12b": { - "id": "pixtral-12b", + "devstral-2-123b-instruct-2512": { + "id": "devstral-2-123b-instruct-2512", + "family": "devstral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 16384 + } + }, + "deepseek-r1-distill-llama-70b": { + "id": "deepseek-r1-distill-llama-70b", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "pixtral-12b-2409": { + "id": "pixtral-12b-2409", "family": "pixtral", "reasoning": false, "temperature": true, @@ -18216,18 +2164,38 @@ }, "limit": { "context": 128000, - "output": 128000 + "output": 4096 } }, - "open-mixtral-8x7b": { - "id": "open-mixtral-8x7b", - "family": "mixtral", + "whisper-large-v3": { + "id": "whisper-large-v3", + "family": "whisper", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 0, + "output": 4096 + } + }, + "voxtral-small-24b-2507": { + "id": "voxtral-small-24b-2507", + "family": "voxtral", "reasoning": false, "temperature": true, "toolCall": true, "modalities": { "input": [ - "text" + "text", + "audio" ], "output": [ "text" @@ -18235,12 +2203,12 @@ }, "limit": { "context": 32000, - "output": 32000 + "output": 16384 } }, - "pixtral-large-latest": { - "id": "pixtral-large-latest", - "family": "pixtral", + "gemma-3-27b-it": { + "id": "gemma-3-27b-it", + "family": "gemma", "reasoning": false, "temperature": true, "toolCall": true, @@ -18254,16 +2222,17 @@ ] }, "limit": { - "context": 128000, - "output": 128000 + "context": 131072, + "output": 8192, + "input": 32768 } }, - "devstral-2512": { - "id": "devstral-2512", - "family": "devstral", + "bge-multilingual-gemma2": { + "id": "bge-multilingual-gemma2", + "family": "gemma", "reasoning": false, - "temperature": true, - "toolCall": true, + "temperature": false, + "toolCall": false, "modalities": { "input": [ "text" @@ -18273,52 +2242,12 @@ ] }, "limit": { - "context": 262000, - "output": 262000 + "context": 8191, + "output": 3072 } }, - "mistral-large-latest": { - "id": "mistral-large-latest", - "family": "mistral-large", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "mistral-medium-2508": { - "id": "mistral-medium-2508", - "family": "mistral-medium", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "mistral-small-latest": { - "id": "mistral-small-latest", + "mistral-small-3.2-24b-instruct-2506": { + "id": "mistral-small-3.2-24b-instruct-2506", "family": "mistral-small", "reasoning": false, "temperature": true, @@ -18333,53 +2262,14 @@ ] }, "limit": { - "context": 128000, - "output": 16384 + "context": 131072, + "output": 131072 } }, - "open-mixtral-8x22b": { - "id": "open-mixtral-8x22b", - "family": "mixtral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 64000, - "output": 64000 - } - }, - "mistral-medium-latest": { - "id": "mistral-medium-latest", - "family": "mistral-medium", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "devstral-small-2507": { - "id": "devstral-small-2507", - "family": "devstral", - "reasoning": false, + "gpt-oss-120b": { + "id": "gpt-oss-120b", + "family": "gpt-oss", + "reasoning": true, "temperature": true, "toolCall": true, "modalities": { @@ -18395,449 +2285,27 @@ "output": 128000 } }, - "magistral-medium-latest": { - "id": "magistral-medium-latest", - "family": "magistral-medium", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "gpt-4o-2024-11-20": { - "id": "gpt-4o-2024-11-20", - "family": "gpt", + "mistral-nemo-instruct-2407": { + "id": "mistral-nemo-instruct-2407", + "family": "mistral-nemo", "reasoning": false, "temperature": true, "toolCall": true, "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "output": 16384 - } - }, - "claude-opus-4-5-20251101": { - "id": "claude-opus-4-5-20251101", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000, - "input": 200000 - } - }, - "gpt-5.2-chat-latest": { - "id": "gpt-5.2-chat-latest", - "family": "gpt-codex", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "grok-4-0709": { - "id": "grok-4-0709", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 8192 - } - }, - "gpt-5.3-codex-xhigh": { - "id": "gpt-5.3-codex-xhigh", - "family": "gpt", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "grok-4-1-fast-non-reasoning": { - "id": "grok-4-1-fast-non-reasoning", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 30000, - "input": 128000 - } - }, - "gemini-3.1-flash-lite-preview": { - "id": "gemini-3.1-flash-lite-preview", - "family": "gemini-flash-lite", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video", - "audio", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, + "context": 65536, "output": 65536 } }, - "claude-opus-4-20250514": { - "id": "claude-opus-4-20250514", - "family": "claude-opus", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000, - "input": 200000 - } - }, - "claude-sonnet-4-5-20250929": { - "id": "claude-sonnet-4-5-20250929", - "family": "claude-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000, - "input": 1000000 - } - }, - "o3-pro": { - "id": "o3-pro", - "family": "o-pro", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000 - } - }, - "gemini-3.1-pro-preview": { - "id": "gemini-3.1-pro-preview", - "family": "gemini-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video", - "audio", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536, - "input": 128000 - } - }, - "claude-3-7-sonnet-20250219": { - "id": "claude-3-7-sonnet-20250219", - "family": "claude-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 16000, - "input": 200000 - } - }, - "claude-haiku-4-5-20251001": { - "id": "claude-haiku-4-5-20251001", - "family": "claude-haiku", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000, - "input": 200000 - } - }, - "kimi-k2-turbo-preview": { - "id": "kimi-k2-turbo-preview", - "family": "kimi", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "qwen-2.5-coder-32b": { - "id": "qwen-2.5-coder-32b", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "route-llm": { - "id": "route-llm", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "gpt-5.3-chat-latest": { - "id": "gpt-5.3-chat-latest", - "family": "gpt", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000 - } - }, - "claude-sonnet-4-20250514": { - "id": "claude-sonnet-4-20250514", - "family": "claude-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000, - "input": 200000 - } - }, - "gpt-5.1-chat-latest": { - "id": "gpt-5.1-chat-latest", - "family": "gpt-codex", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "claude-opus-4-1-20250805": { - "id": "claude-opus-4-1-20250805", - "family": "claude-opus", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000, - "input": 200000 - } - }, - "meta-llama/meta-llama-3.1-405b-instruct-turbo": { - "id": "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo", + "llama-3.1-8b-instruct": { + "id": "llama-3.1-8b-instruct", "family": "llama", "reasoning": false, "temperature": true, @@ -18852,54 +2320,13 @@ }, "limit": { "context": 128000, - "output": 4096 + "output": 2048 } }, - "qwen/qwen2.5-72b-instruct": { - "id": "Qwen/Qwen2.5-72B-Instruct", - "family": "qwen", + "glm-4-flash": { + "id": "glm-4-flash", "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "accounts/fireworks/routers/kimi-k2p5-turbo": { - "id": "accounts/fireworks/routers/kimi-k2p5-turbo", - "family": "kimi-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "accounts/fireworks/models/kimi-k2-instruct": { - "id": "accounts/fireworks/models/kimi-k2-instruct", - "family": "kimi", - "reasoning": false, - "temperature": true, - "toolCall": true, + "toolCall": false, "modalities": { "input": [ "text" @@ -18910,75 +2337,37 @@ }, "limit": { "context": 128000, + "input": 128000, + "output": 4096 + } + }, + "meta-llama-3-1-8b-instruct-fp8": { + "id": "Meta-Llama-3-1-8B-Instruct-FP8", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, "output": 16384 } }, - "accounts/fireworks/models/glm-4p7": { - "id": "accounts/fireworks/models/glm-4p7", - "family": "glm", + "claude-opus-4-thinking:32000": { + "id": "claude-opus-4-thinking:32000", "reasoning": true, - "temperature": true, "toolCall": true, "modalities": { "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 198000, - "output": 198000 - } - }, - "accounts/fireworks/models/glm-5": { - "id": "accounts/fireworks/models/glm-5", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 131072 - } - }, - "accounts/fireworks/models/deepseek-v3p1": { - "id": "accounts/fireworks/models/deepseek-v3p1", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 163840 - } - }, - "accounts/fireworks/models/minimax-m2p1": { - "id": "accounts/fireworks/models/minimax-m2p1", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" @@ -18986,131 +2375,38 @@ }, "limit": { "context": 200000, - "output": 200000 + "input": 200000, + "output": 32000 } }, - "accounts/fireworks/models/glm-4p5-air": { - "id": "accounts/fireworks/models/glm-4p5-air", - "family": "glm-air", + "gemini-2.5-pro-preview-05-06": { + "id": "gemini-2.5-pro-preview-05-06", "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "accounts/fireworks/models/deepseek-v3p2": { - "id": "accounts/fireworks/models/deepseek-v3p2", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 160000, - "output": 160000 - } - }, - "accounts/fireworks/models/minimax-m2p5": { - "id": "accounts/fireworks/models/minimax-m2p5", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 196608, - "output": 196608 - } - }, - "accounts/fireworks/models/gpt-oss-120b": { - "id": "accounts/fireworks/models/gpt-oss-120b", - "family": "gpt-oss", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "accounts/fireworks/models/kimi-k2p5": { - "id": "accounts/fireworks/models/kimi-k2p5", - "family": "kimi-thinking", - "reasoning": true, - "temperature": true, "toolCall": true, "modalities": { "input": [ "text", "image", - "video" + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "output": 256000 - } - }, - "accounts/fireworks/models/kimi-k2-thinking": { - "id": "accounts/fireworks/models/kimi-k2-thinking", - "family": "kimi-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] + "context": 1048576, + "input": 1048756, + "output": 65536 }, - "limit": { - "context": 256000, - "output": 256000 - } + "family": "gemini-pro", + "temperature": true }, - "accounts/fireworks/models/glm-4p5": { - "id": "accounts/fireworks/models/glm-4p5", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, + "grok-3-mini-fast-beta": { + "id": "grok-3-mini-fast-beta", + "reasoning": false, + "toolCall": false, "modalities": { "input": [ "text" @@ -19121,32 +2417,13 @@ }, "limit": { "context": 131072, + "input": 131072, "output": 131072 } }, - "accounts/fireworks/models/gpt-oss-20b": { - "id": "accounts/fireworks/models/gpt-oss-20b", - "family": "gpt-oss", + "command-a-reasoning-08-2025": { + "id": "command-a-reasoning-08-2025", "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "step-3.5-flash": { - "id": "step-3.5-flash", - "reasoning": true, - "temperature": true, "toolCall": true, "modalities": { "input": [ @@ -19159,14 +2436,15 @@ "limit": { "context": 256000, "input": 256000, - "output": 256000 - } + "output": 32000 + }, + "family": "command-a", + "temperature": true }, - "step-2-16k": { - "id": "step-2-16k", - "reasoning": true, - "temperature": true, - "toolCall": true, + "brave": { + "id": "brave", + "reasoning": false, + "toolCall": false, "modalities": { "input": [ "text" @@ -19176,16 +2454,33 @@ ] }, "limit": { - "context": 16384, - "input": 16384, + "context": 8192, + "input": 8192, "output": 8192 } }, - "step-1-32k": { - "id": "step-1-32k", - "reasoning": true, - "temperature": true, - "toolCall": true, + "exa-research": { + "id": "exa-research", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "input": 8192, + "output": 8192 + } + }, + "llama-3.3-70b-nova": { + "id": "Llama-3.3-70B-Nova", + "reasoning": false, + "toolCall": false, "modalities": { "input": [ "text" @@ -19197,13298 +2492,11 @@ "limit": { "context": 32768, "input": 32768, - "output": 32768 - } - }, - "duo-chat-gpt-5-2-codex": { - "id": "duo-chat-gpt-5-2-codex", - "family": "gpt-codex", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "duo-chat-opus-4-6": { - "id": "duo-chat-opus-4-6", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - } - }, - "duo-chat-gpt-5-mini": { - "id": "duo-chat-gpt-5-mini", - "family": "gpt-mini", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "duo-chat-gpt-5-3-codex": { - "id": "duo-chat-gpt-5-3-codex", - "family": "gpt-codex", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "duo-chat-sonnet-4-5": { - "id": "duo-chat-sonnet-4-5", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "duo-chat-haiku-4-5": { - "id": "duo-chat-haiku-4-5", - "family": "claude-haiku", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "duo-chat-gpt-5-codex": { - "id": "duo-chat-gpt-5-codex", - "family": "gpt-codex", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "duo-chat-gpt-5-4-nano": { - "id": "duo-chat-gpt-5-4-nano", - "family": "gpt-nano", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "duo-chat-gpt-5-2": { - "id": "duo-chat-gpt-5-2", - "family": "gpt", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "duo-chat-gpt-5-4-mini": { - "id": "duo-chat-gpt-5-4-mini", - "family": "gpt-mini", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "duo-chat-sonnet-4-6": { - "id": "duo-chat-sonnet-4-6", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - } - }, - "duo-chat-gpt-5-4": { - "id": "duo-chat-gpt-5-4", - "family": "gpt", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1050000, - "input": 922000, - "output": 128000 - } - }, - "duo-chat-opus-4-5": { - "id": "duo-chat-opus-4-5", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "duo-chat-gpt-5-1": { - "id": "duo-chat-gpt-5-1", - "family": "gpt", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "nex-agi/deepseek-v3.1-nex-n1": { - "id": "nex-agi/deepseek-v3.1-nex-n1", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192, - "input": 128000 - } - }, - "deepseek-ai/deepseek-r1-distill-qwen-32b": { - "id": "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 131000 - } - }, - "deepseek-ai/deepseek-r1-distill-qwen-14b": { - "id": "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 131000 - } - }, - "deepseek-ai/deepseek-v3.2-exp": { - "id": "deepseek-ai/deepseek-v3.2-exp", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 65536, - "input": 163840 - } - }, - "deepseek-ai/deepseek-vl2": { - "id": "deepseek-ai/deepseek-vl2", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4000, - "output": 4000 - } - }, - "deepseek-ai/deepseek-v3": { - "id": "deepseek-ai/DeepSeek-V3", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 163840 - } - }, - "bytedance-seed/seed-oss-36b-instruct": { - "id": "ByteDance-Seed/Seed-OSS-36B-Instruct", - "family": "seed", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262000, - "output": 262000 - } - }, - "tencent/hunyuan-a13b-instruct": { - "id": "tencent/hunyuan-a13b-instruct", - "family": "hunyuan", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "tencent/hunyuan-mt-7b": { - "id": "tencent/Hunyuan-MT-7B", - "family": "hunyuan", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192, - "input": 8192 - } - }, - "inclusionai/ling-flash-2.0": { - "id": "inclusionAI/Ling-flash-2.0", - "family": "ling", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 131000 - } - }, - "inclusionai/ring-flash-2.0": { - "id": "inclusionAI/Ring-flash-2.0", - "family": "ring", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 131000 - } - }, - "inclusionai/ling-mini-2.0": { - "id": "inclusionAI/Ling-mini-2.0", - "family": "ling", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 131000 - } - }, - "baidu/ernie-4.5-300b-a47b": { - "id": "baidu/ernie-4.5-300b-a47b", - "family": "ernie", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384, - "input": 131072 - } - }, - "qwen/qwen3-vl-32b-instruct": { - "id": "qwen/qwen3-vl-32b-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "qwen/qwen2.5-vl-7b-instruct": { - "id": "Qwen/Qwen2.5-VL-7B-Instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 33000, - "output": 4000 - } - }, - "qwen/qwen2.5-32b-instruct": { - "id": "Qwen/Qwen2.5-32B-Instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 33000, - "output": 4000 - } - }, - "qwen/qwen3-8b": { - "id": "qwen/qwen3-8b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 40960, - "output": 8192 - } - }, - "qwen/qwen2.5-14b-instruct": { - "id": "Qwen/Qwen2.5-14B-Instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 33000, - "output": 4000 - } - }, - "qwen/qwen2.5-72b-instruct-128k": { - "id": "Qwen/Qwen2.5-72B-Instruct-128K", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 4000 - } - }, - "qwen/qwen3-omni-30b-a3b-captioner": { - "id": "Qwen/Qwen3-Omni-30B-A3B-Captioner", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 66000, - "output": 66000 - } - }, - "qwen/qwen3-vl-8b-thinking": { - "id": "qwen/qwen3-vl-8b-thinking", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "qwen/qwen3-vl-32b-thinking": { - "id": "Qwen/Qwen3-VL-32B-Thinking", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262000, - "output": 262000 - } - }, - "qwen/qwen3-14b": { - "id": "Qwen/Qwen3-14B", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 40960, - "output": 40960 - } - }, - "thudm/glm-4-32b-0414": { - "id": "THUDM/GLM-4-32B-0414", - "family": "glm", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 65536, - "input": 128000 - } - }, - "thudm/glm-4-9b-0414": { - "id": "THUDM/GLM-4-9B-0414", - "family": "glm", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 8000, - "input": 32000 - } - }, - "thudm/glm-z1-32b-0414": { - "id": "THUDM/GLM-Z1-32B-0414", - "family": "glm-z", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 65536, - "input": 128000 - } - }, - "thudm/glm-z1-9b-0414": { - "id": "THUDM/GLM-Z1-9B-0414", - "family": "glm-z", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 8000, - "input": 32000 - } - }, - "essentialai/rnj-1-instruct": { - "id": "essentialai/rnj-1-instruct", - "family": "rnj", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192, - "input": 128000 - } - }, - "deepseek-ai/deepseek-v3-1": { - "id": "deepseek-ai/DeepSeek-V3-1", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "qwen/qwen3-235b-a22b-instruct-2507-tput": { - "id": "Qwen/Qwen3-235B-A22B-Instruct-2507-tput", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "qwen/qwen3-coder-next-fp8": { - "id": "Qwen/Qwen3-Coder-Next-FP8", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "minimaxai/chat-completion/models/minimax-m2_5-high-throughput": { - "id": "minimaxai/chat-completion/models/MiniMax-M2_5-high-throughput", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "arcee_ai/afm/models/trinity-mini": { - "id": "arcee_ai/AFM/models/trinity-mini", - "family": "trinity-mini", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "deepseek-ai/deepseek-ocr/models/deepseek-ocr": { - "id": "deepseek-ai/deepseek-ocr/models/DeepSeek-OCR", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "clarifai/main/models/mm-poly-8b": { - "id": "clarifai/main/models/mm-poly-8b", - "family": "mm-poly", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 4096 - } - }, - "qwen/qwencoder/models/qwen3-coder-30b-a3b-instruct": { - "id": "qwen/qwenCoder/models/Qwen3-Coder-30B-A3B-Instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "qwen/qwenlm/models/qwen3-30b-a3b-instruct-2507": { - "id": "qwen/qwenLM/models/Qwen3-30B-A3B-Instruct-2507", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "qwen/qwenlm/models/qwen3-30b-a3b-thinking-2507": { - "id": "qwen/qwenLM/models/Qwen3-30B-A3B-Thinking-2507", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 131072 - } - }, - "mistralai/completion/models/ministral-3-14b-reasoning-2512": { - "id": "mistralai/completion/models/Ministral-3-14B-Reasoning-2512", - "family": "ministral", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "mistralai/completion/models/ministral-3-3b-reasoning-2512": { - "id": "mistralai/completion/models/Ministral-3-3B-Reasoning-2512", - "family": "ministral", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "openai/chat-completion/models/gpt-oss-120b-high-throughput": { - "id": "openai/chat-completion/models/gpt-oss-120b-high-throughput", - "family": "gpt-oss", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, "output": 16384 } }, - "openai/chat-completion/models/gpt-oss-20b": { - "id": "openai/chat-completion/models/gpt-oss-20b", - "family": "gpt-oss", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "baai/bge-reranker-v2-m3": { - "id": "BAAI/bge-reranker-v2-m3", - "family": "bge", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 512, - "output": 512 - } - }, - "intfloat/multilingual-e5-large": { - "id": "intfloat/multilingual-e5-large", - "family": "text-embedding", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 512, - "output": 1024 - } - }, - "mistralai/mistral-small-3.2-24b-instruct-2506": { - "id": "mistralai/Mistral-Small-3.2-24B-Instruct-2506", - "family": "mistral-small", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 8192 - } - }, - "lucidquery-nexus-coder": { - "id": "lucidquery-nexus-coder", - "family": "lucid", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 250000, - "output": 60000 - } - }, - "lucidnova-rf1-100b": { - "id": "lucidnova-rf1-100b", - "family": "nova", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 120000, - "output": 8000 - } - }, - "glm-4.6v-flash": { - "id": "glm-4.6v-flash", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "deepseek-reasoner": { - "id": "deepseek-reasoner", - "family": "deepseek-thinking", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 64000, - "output": 65536, - "input": 64000 - } - }, - "deepseek-chat": { - "id": "deepseek-chat", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192, - "input": 128000 - } - }, - "qwen/qwen3-30b-a3b-2507": { - "id": "qwen/qwen3-30b-a3b-2507", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 16384 - } - }, - "qwen/qwen3-coder-30b": { - "id": "qwen/qwen3-coder-30b", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "prime-intellect/intellect-3": { - "id": "prime-intellect/intellect-3", - "family": "intellect", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "nvidia/nemotron-nano-9b-v2:free": { - "id": "nvidia/nemotron-nano-9b-v2:free", - "family": "nemotron", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "nvidia/nemotron-nano-12b-v2-vl:free": { - "id": "nvidia/nemotron-nano-12b-v2-vl:free", - "family": "nemotron", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "nvidia/nemotron-3-nano-30b-a3b:free": { - "id": "nvidia/nemotron-3-nano-30b-a3b:free", - "family": "nemotron", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "nvidia/nemotron-nano-9b-v2": { - "id": "nvidia/nemotron-nano-9b-v2", - "family": "nemotron", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 26215 - } - }, - "nvidia/nemotron-3-super-120b-a12b-free": { - "id": "nvidia/nemotron-3-super-120b-a12b-free", - "family": "nemotron", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "arcee-ai/trinity-large-preview:free": { - "id": "arcee-ai/trinity-large-preview:free", - "family": "trinity", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 26200 - } - }, - "arcee-ai/trinity-mini:free": { - "id": "arcee-ai/trinity-mini:free", - "family": "trinity-mini", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "liquid/lfm-2.5-1.2b-thinking:free": { - "id": "liquid/lfm-2.5-1.2b-thinking:free", - "family": "liquid", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "liquid/lfm-2.5-1.2b-instruct:free": { - "id": "liquid/lfm-2.5-1.2b-instruct:free", - "family": "liquid", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "inception/mercury-2": { - "id": "inception/mercury-2", - "family": "mercury", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 50000 - } - }, - "inception/mercury": { - "id": "inception/mercury", - "family": "mercury", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32000 - } - }, - "inception/mercury-coder": { - "id": "inception/mercury-coder", - "family": "mercury", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32000 - } - }, - "sourceful/riverflow-v2-fast-preview": { - "id": "sourceful/riverflow-v2-fast-preview", - "family": "sourceful", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "sourceful/riverflow-v2-max-preview": { - "id": "sourceful/riverflow-v2-max-preview", - "family": "sourceful", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "sourceful/riverflow-v2-standard-preview": { - "id": "sourceful/riverflow-v2-standard-preview", - "family": "sourceful", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "stepfun/step-3.5-flash:free": { - "id": "stepfun/step-3.5-flash:free", - "family": "step", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "cognitivecomputations/dolphin-mistral-24b-venice-edition:free": { - "id": "cognitivecomputations/dolphin-mistral-24b-venice-edition:free", - "family": "mistral", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "deepseek/deepseek-v3.1-terminus:exacto": { - "id": "deepseek/deepseek-v3.1-terminus:exacto", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 65536 - } - }, - "deepseek/deepseek-v3.2-speciale": { - "id": "deepseek/deepseek-v3.2-speciale", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163000, - "output": 65536, - "input": 163000 - } - }, - "deepseek/deepseek-chat-v3.1": { - "id": "deepseek/deepseek-chat-v3.1", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 7168 - } - }, - "deepseek/deepseek-chat-v3-0324": { - "id": "deepseek/deepseek-chat-v3-0324", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 65536 - } - }, - "openrouter/free": { - "id": "openrouter/free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 32768 - } - }, - "moonshotai/kimi-k2-0905:exacto": { - "id": "moonshotai/kimi-k2-0905:exacto", - "family": "kimi", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 16384 - } - }, - "moonshotai/kimi-k2:free": { - "id": "moonshotai/kimi-k2:free", - "family": "kimi", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32800, - "output": 32800 - } - }, - "google/gemini-2.5-flash-lite-preview-09-2025": { - "id": "google/gemini-2.5-flash-lite-preview-09-2025", - "family": "gemini-flash-lite", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio", - "image", - "pdf", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - } - }, - "google/gemini-3.1-pro-preview-customtools": { - "id": "google/gemini-3.1-pro-preview-customtools", - "family": "gemini-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio", - "image", - "pdf", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - } - }, - "google/gemini-2.5-pro-preview-06-05": { - "id": "google/gemini-2.5-pro-preview-06-05", - "family": "gemini-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - } - }, - "google/gemma-3n-e4b-it:free": { - "id": "google/gemma-3n-e4b-it:free", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 2000 - } - }, - "google/gemini-2.5-flash-preview-09-2025": { - "id": "google/gemini-2.5-flash-preview-09-2025", - "family": "gemini-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - } - }, - "google/gemini-2.5-pro-preview-05-06": { - "id": "google/gemini-2.5-pro-preview-05-06", - "family": "gemini-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio", - "image", - "pdf", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65535 - } - }, - "google/gemma-3n-e2b-it:free": { - "id": "google/gemma-3n-e2b-it:free", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 2000 - } - }, - "google/gemini-2.0-flash-001": { - "id": "google/gemini-2.0-flash-001", - "family": "gemini-flash", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio", - "image", - "pdf", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 8192 - } - }, - "google/gemma-3-12b-it:free": { - "id": "google/gemma-3-12b-it:free", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 8192 - } - }, - "google/gemma-2-9b-it": { - "id": "google/gemma-2-9b-it", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 1639 - } - }, - "google/gemma-3-4b-it:free": { - "id": "google/gemma-3-4b-it:free", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 8192 - } - }, - "google/gemma-3-4b-it": { - "id": "google/gemma-3-4b-it", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 19200 - } - }, - "google/gemma-3-27b-it:free": { - "id": "google/gemma-3-27b-it:free", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "z-ai/glm-4.6:exacto": { - "id": "z-ai/glm-4.6:exacto", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 128000 - } - }, - "z-ai/glm-4.7-flash": { - "id": "z-ai/glm-4.7-flash", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 40551 - } - }, - "z-ai/glm-4.5-air:free": { - "id": "z-ai/glm-4.5-air:free", - "family": "glm-air", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 96000 - } - }, - "z-ai/glm-4.5v": { - "id": "z-ai/glm-4.5v", - "family": "glmv", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 64000, - "output": 96000, - "input": 64000 - } - }, - "qwen/qwen3-coder:free": { - "id": "qwen/qwen3-coder:free", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 66536 - } - }, - "qwen/qwen3-coder-flash": { - "id": "qwen/qwen3-coder-flash", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 65536 - } - }, - "qwen/qwen3-coder:exacto": { - "id": "qwen/qwen3-coder:exacto", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "qwen/qwen-2.5-coder-32b-instruct": { - "id": "qwen/qwen-2.5-coder-32b-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 8192 - } - }, - "qwen/qwen3.5-plus-02-15": { - "id": "qwen/qwen3.5-plus-02-15", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 65536 - } - }, - "qwen/qwen3-235b-a22b-07-25": { - "id": "qwen/qwen3-235b-a22b-07-25", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 131072 - } - }, - "qwen/qwen3-next-80b-a3b-instruct:free": { - "id": "qwen/qwen3-next-80b-a3b-instruct:free", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "qwen/qwen3-4b:free": { - "id": "qwen/qwen3-4b:free", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 40960, - "output": 40960 - } - }, - "x-ai/grok-3": { - "id": "x-ai/grok-3", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 26215 - } - }, - "x-ai/grok-3-mini-beta": { - "id": "x-ai/grok-3-mini-beta", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 26215 - } - }, - "x-ai/grok-3-mini": { - "id": "x-ai/grok-3-mini", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 26215 - } - }, - "x-ai/grok-4.20-multi-agent-beta": { - "id": "x-ai/grok-4.20-multi-agent-beta", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 32768 - } - }, - "x-ai/grok-4.20-beta": { - "id": "x-ai/grok-4.20-beta", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 32768 - } - }, - "x-ai/grok-3-beta": { - "id": "x-ai/grok-3-beta", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 26215 - } - }, - "meta-llama/llama-3.3-70b-instruct:free": { - "id": "meta-llama/llama-3.3-70b-instruct:free", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "meta-llama/llama-3.2-11b-vision-instruct": { - "id": "meta-llama/llama-3.2-11b-vision-instruct", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "meta-llama/llama-3.2-3b-instruct:free": { - "id": "meta-llama/llama-3.2-3b-instruct:free", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "mistralai/devstral-medium-2507": { - "id": "mistralai/devstral-medium-2507", - "family": "devstral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "mistralai/mistral-medium-3": { - "id": "mistralai/mistral-medium-3", - "family": "mistral-medium", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768, - "input": 131072 - } - }, - "mistralai/codestral-2508": { - "id": "mistralai/codestral-2508", - "family": "codestral", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32768, - "input": 256000 - } - }, - "mistralai/mistral-small-3.1-24b-instruct": { - "id": "mistralai/mistral-small-3.1-24b-instruct", - "family": "mistral-small", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 131072 - } - }, - "mistralai/devstral-2512": { - "id": "mistralai/devstral-2512", - "family": "devstral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "mistralai/mistral-small-3.2-24b-instruct": { - "id": "mistralai/mistral-small-3.2-24b-instruct", - "family": "mistral-small", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "mistralai/devstral-small-2507": { - "id": "mistralai/devstral-small-2507", - "family": "devstral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "mistralai/mistral-medium-3.1": { - "id": "mistralai/mistral-medium-3.1", - "family": "mistral-medium", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768, - "input": 131072 - } - }, - "openai/gpt-oss-120b:exacto": { - "id": "openai/gpt-oss-120b:exacto", - "family": "gpt-oss", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "openai/gpt-5.2-chat": { - "id": "openai/gpt-5.2-chat", - "family": "gpt", - "reasoning": true, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 16384, - "input": 400000 - } - }, - "openai/gpt-5-image": { - "id": "openai/gpt-5-image", - "family": "gpt", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "image", - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000 - } - }, - "openai/gpt-oss-20b:free": { - "id": "openai/gpt-oss-20b:free", - "family": "gpt-oss", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "openai/gpt-oss-safeguard-20b": { - "id": "openai/gpt-oss-safeguard-20b", - "family": "gpt-oss", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384, - "input": 128000 - } - }, - "openai/gpt-oss-120b:free": { - "id": "openai/gpt-oss-120b:free", - "family": "gpt-oss", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "minimax/minimax-m1": { - "id": "minimax/minimax-m1", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 40000 - } - }, - "minimax/minimax-01": { - "id": "minimax/minimax-01", - "family": "minimax", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000192, - "output": 16384, - "input": 1000192 - } - }, - "bytedance-seed/seedream-4.5": { - "id": "bytedance-seed/seedream-4.5", - "family": "seed", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 4096, - "output": 4096 - } - }, - "black-forest-labs/flux.2-pro": { - "id": "black-forest-labs/flux.2-pro", - "family": "flux", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 46864, - "output": 46864 - } - }, - "black-forest-labs/flux.2-flex": { - "id": "black-forest-labs/flux.2-flex", - "family": "flux", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 67344, - "output": 67344 - } - }, - "black-forest-labs/flux.2-max": { - "id": "black-forest-labs/flux.2-max", - "family": "flux", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 46864, - "output": 46864 - } - }, - "black-forest-labs/flux.2-klein-4b": { - "id": "black-forest-labs/flux.2-klein-4b", - "family": "flux", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 40960, - "output": 40960 - } - }, - "nousresearch/hermes-3-llama-3.1-405b:free": { - "id": "nousresearch/hermes-3-llama-3.1-405b:free", - "family": "hermes", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "ai21-labs/ai21-jamba-1.5-mini": { - "id": "ai21-labs/ai21-jamba-1.5-mini", - "family": "jamba", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 4096 - } - }, - "ai21-labs/ai21-jamba-1.5-large": { - "id": "ai21-labs/ai21-jamba-1.5-large", - "family": "jamba", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 4096 - } - }, - "microsoft/mai-ds-r1": { - "id": "microsoft/mai-ds-r1", - "family": "mai", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 8192 - } - }, - "microsoft/phi-3.5-mini-instruct": { - "id": "microsoft/phi-3.5-mini-instruct", - "family": "phi", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "microsoft/phi-4": { - "id": "microsoft/phi-4", - "family": "phi", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 16384 - } - }, - "microsoft/phi-3-mini-4k-instruct": { - "id": "microsoft/phi-3-mini-4k-instruct", - "family": "phi", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4096, - "output": 1024 - } - }, - "microsoft/phi-4-mini-reasoning": { - "id": "microsoft/phi-4-mini-reasoning", - "family": "phi", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "microsoft/phi-3-mini-128k-instruct": { - "id": "microsoft/phi-3-mini-128k-instruct", - "family": "phi", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "microsoft/phi-4-reasoning": { - "id": "microsoft/phi-4-reasoning", - "family": "phi", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "core42/jais-30b-chat": { - "id": "core42/jais-30b-chat", - "family": "jais", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 2048 - } - }, - "mistral-ai/ministral-3b": { - "id": "mistral-ai/ministral-3b", - "family": "ministral", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "mistral-ai/mistral-medium-2505": { - "id": "mistral-ai/mistral-medium-2505", - "family": "mistral-medium", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "mistral-ai/mistral-nemo": { - "id": "mistral-ai/mistral-nemo", - "family": "mistral-nemo", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "mistral-ai/mistral-large-2411": { - "id": "mistral-ai/mistral-large-2411", - "family": "mistral-large", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "mistral-ai/mistral-small-2503": { - "id": "mistral-ai/mistral-small-2503", - "family": "mistral-small", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "mistral-ai/codestral-2501": { - "id": "mistral-ai/codestral-2501", - "family": "codestral", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 8192 - } - }, - "deepseek/deepseek-r1": { - "id": "deepseek/deepseek-r1", - "family": "deepseek-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 64000, - "output": 16000 - } - }, - "meta/llama-3.2-90b-vision-instruct": { - "id": "meta/llama-3.2-90b-vision-instruct", - "family": "llama", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "meta/meta-llama-3.1-405b-instruct": { - "id": "meta/meta-llama-3.1-405b-instruct", - "family": "llama", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "meta/meta-llama-3-8b-instruct": { - "id": "meta/meta-llama-3-8b-instruct", - "family": "llama", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 2048 - } - }, - "meta/meta-llama-3-70b-instruct": { - "id": "meta/meta-llama-3-70b-instruct", - "family": "llama", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 2048 - } - }, - "meta/meta-llama-3.1-70b-instruct": { - "id": "meta/meta-llama-3.1-70b-instruct", - "family": "llama", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "meta/meta-llama-3.1-8b-instruct": { - "id": "meta/meta-llama-3.1-8b-instruct", - "family": "llama", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "meta/llama-4-maverick-17b-128e-instruct-fp8": { - "id": "meta/llama-4-maverick-17b-128e-instruct-fp8", - "family": "llama", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "openai/o1-preview": { - "id": "openai/o1-preview", - "family": "o", - "reasoning": true, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768, - "input": 128000 - } - }, - "openai/o1-mini": { - "id": "openai/o1-mini", - "family": "o-mini", - "reasoning": true, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 65536 - } - }, - "cohere/cohere-command-a": { - "id": "cohere/cohere-command-a", - "family": "command-a", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "cohere/cohere-command-r-plus-08-2024": { - "id": "cohere/cohere-command-r-plus-08-2024", - "family": "command-r", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "cohere/cohere-command-r": { - "id": "cohere/cohere-command-r", - "family": "command-r", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "cohere/cohere-command-r-08-2024": { - "id": "cohere/cohere-command-r-08-2024", - "family": "command-r", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "cohere/cohere-command-r-plus": { - "id": "cohere/cohere-command-r-plus", - "family": "command-r", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "qwen-max-latest": { - "id": "qwen-max-latest", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "qwen3-max-2025-09-23": { - "id": "qwen3-max-2025-09-23", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 258048, - "output": 65536 - } - }, - "gemini-2.5-flash-lite-preview-09-2025": { - "id": "gemini-2.5-flash-lite-preview-09-2025", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048756, - "output": 65536, - "input": 1048756 - }, - "family": "gemini-flash-lite" - }, - "claude-opus-4-1-20250805-thinking": { - "id": "claude-opus-4-1-20250805-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - } - }, - "gemini-2.5-flash-preview-09-2025": { - "id": "gemini-2.5-flash-preview-09-2025", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048756, - "output": 65536, - "input": 1048756 - }, - "family": "gemini-flash" - }, - "grok-4-1-fast-reasoning": { - "id": "grok-4-1-fast-reasoning", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192, - "input": 128000 - }, - "family": "grok" - }, - "kimi-k2-0905-preview": { - "id": "kimi-k2-0905-preview", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - }, - "family": "kimi" - }, - "claude-sonnet-4-5-20250929-thinking": { - "id": "claude-sonnet-4-5-20250929-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000, - "input": 1000000 - } - }, - "doubao-seed-1-6-vision-250815": { - "id": "doubao-seed-1-6-vision-250815", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32000 - } - }, - "doubao-seed-1-6-thinking-250715": { - "id": "doubao-seed-1-6-thinking-250715", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 16000 - } - }, - "doubao-seed-1-8-251215": { - "id": "doubao-seed-1-8-251215", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192, - "input": 128000 - } - }, - "ministral-14b-2512": { - "id": "ministral-14b-2512", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "gemini-2.5-flash-nothink": { - "id": "gemini-2.5-flash-nothink", - "family": "gemini-flash", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 65536 - } - }, - "claude-opus-4-5-20251101-thinking": { - "id": "claude-opus-4-5-20251101-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "gemini-3-pro-image-preview": { - "id": "gemini-3-pro-image-preview", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048756, - "output": 65536, - "input": 1048756 - } - }, - "gpt-5-thinking": { - "id": "gpt-5-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000 - } - }, - "deepseek-v3.2-thinking": { - "id": "deepseek-v3.2-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "chatgpt-4o-latest": { - "id": "chatgpt-4o-latest", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "kimi-k2-thinking-turbo": { - "id": "kimi-k2-thinking-turbo", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - }, - "family": "kimi-thinking" - }, - "doubao-seed-code-preview-251028": { - "id": "doubao-seed-code-preview-251028", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32000 - } - }, - "grok-4.1": { - "id": "grok-4.1", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "claude-sonnet-4.6": { - "id": "claude-sonnet-4.6", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 128000, - "output": 32000 - } - }, - "claude-haiku-4.5": { - "id": "claude-haiku-4.5", - "family": "claude-haiku", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 144000, - "input": 128000, - "output": 32000 - } - }, - "claude-opus-4.5": { - "id": "claude-opus-4.5", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 160000, - "input": 128000, - "output": 32000 - } - }, - "claude-sonnet-4.5": { - "id": "claude-sonnet-4.5", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 144000, - "input": 128000, - "output": 32000 - } - }, - "claude-opus-4.6": { - "id": "claude-opus-4.6", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 144000, - "input": 128000, - "output": 64000 - } - }, - "claude-opus-41": { - "id": "claude-opus-41", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 80000, - "output": 16000 - } - }, - "kimi-k2-0711-preview": { - "id": "kimi-k2-0711-preview", - "family": "kimi", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "gemini-embedding-001": { - "id": "gemini-embedding-001", - "family": "gemini", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2048, - "output": 3072 - } - }, - "gemini-3.1-pro-preview-customtools": { - "id": "gemini-3.1-pro-preview-customtools", - "family": "gemini-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video", - "audio", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - } - }, - "gemini-2.5-pro-preview-06-05": { - "id": "gemini-2.5-pro-preview-06-05", - "family": "gemini-pro", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048756, - "output": 65536, - "input": 1048756 - } - }, - "gemini-2.5-flash-preview-04-17": { - "id": "gemini-2.5-flash-preview-04-17", - "family": "gemini-flash", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048756, - "output": 65536, - "input": 1048756 - } - }, - "gemini-2.5-pro-preview-05-06": { - "id": "gemini-2.5-pro-preview-05-06", - "family": "gemini-pro", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048756, - "output": 65536, - "input": 1048756 - } - }, - "gemini-2.5-flash-preview-05-20": { - "id": "gemini-2.5-flash-preview-05-20", - "family": "gemini-flash", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048000, - "output": 65536, - "input": 1048000 - } - }, - "gemini-flash-latest": { - "id": "gemini-flash-latest", - "family": "gemini-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - } - }, - "gemini-2.5-flash-lite-preview-06-17": { - "id": "gemini-2.5-flash-lite-preview-06-17", - "family": "gemini-flash-lite", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048756, - "output": 65536, - "input": 1048756 - } - }, - "gemini-flash-lite-latest": { - "id": "gemini-flash-lite-latest", - "family": "gemini-flash-lite", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - } - }, - "zai-org/glm-5-maas": { - "id": "zai-org/glm-5-maas", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 131072 - } - }, - "zai-org/glm-4.7-maas": { - "id": "zai-org/glm-4.7-maas", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 128000 - } - }, - "deepseek-ai/deepseek-v3.1-maas": { - "id": "deepseek-ai/deepseek-v3.1-maas", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 32768 - } - }, - "qwen/qwen3-235b-a22b-instruct-2507-maas": { - "id": "qwen/qwen3-235b-a22b-instruct-2507-maas", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 16384 - } - }, - "meta/llama-4-maverick-17b-128e-instruct-maas": { - "id": "meta/llama-4-maverick-17b-128e-instruct-maas", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 524288, - "output": 8192 - } - }, - "meta/llama-3.3-70b-instruct-maas": { - "id": "meta/llama-3.3-70b-instruct-maas", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "openai/gpt-oss-20b-maas": { - "id": "openai/gpt-oss-20b-maas", - "family": "gpt-oss", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "openai/gpt-oss-120b-maas": { - "id": "openai/gpt-oss-120b-maas", - "family": "gpt-oss", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "gemma-3-27b": { - "id": "gemma-3-27b", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "qwen3-embedding-4b": { - "id": "qwen3-embedding-4b", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 2560 - } - }, - "qwen3-coder-30b-a3b": { - "id": "qwen3-coder-30b-a3b", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "gemini-3.1-flash-image-preview": { - "id": "gemini-3.1-flash-image-preview", - "family": "gemini-flash", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text", - "image" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "gemini-live-2.5-flash": { - "id": "gemini-live-2.5-flash", - "family": "gemini-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text", - "audio" - ] - }, - "limit": { - "context": 128000, - "output": 8000 - } - }, - "gemini-live-2.5-flash-preview-native-audio": { - "id": "gemini-live-2.5-flash-preview-native-audio", - "family": "gemini-flash", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "audio", - "video" - ], - "output": [ - "text", - "audio" - ] - }, - "limit": { - "context": 131072, - "output": 65536 - } - }, - "gemini-2.5-flash-preview-tts": { - "id": "gemini-2.5-flash-preview-tts", - "family": "gemini-flash", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 8000, - "output": 16000 - } - }, - "gemini-2.5-pro-preview-tts": { - "id": "gemini-2.5-pro-preview-tts", - "family": "gemini-flash", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "audio" - ] - }, - "limit": { - "context": 8000, - "output": 16000 - } - }, - "gemini-2.5-flash-image-preview": { - "id": "gemini-2.5-flash-image-preview", - "family": "gemini-flash", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text", - "image" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "gemini-1.5-flash-8b": { - "id": "gemini-1.5-flash-8b", - "family": "gemini-flash", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 8192 - } - }, - "gemini-1.5-flash": { - "id": "gemini-1.5-flash", - "family": "gemini-flash", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 8192 - } - }, - "gemini-1.5-pro": { - "id": "gemini-1.5-pro", - "family": "gemini-pro", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "audio", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 8192 - } - }, - "anthropic--claude-4.5-opus": { - "id": "anthropic--claude-4.5-opus", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "anthropic--claude-4-sonnet": { - "id": "anthropic--claude-4-sonnet", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "anthropic--claude-4.5-sonnet": { - "id": "anthropic--claude-4.5-sonnet", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "anthropic--claude-3-sonnet": { - "id": "anthropic--claude-3-sonnet", - "family": "claude-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 4096 - } - }, - "anthropic--claude-3.7-sonnet": { - "id": "anthropic--claude-3.7-sonnet", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "sonar": { - "id": "sonar", - "family": "sonar", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 127000, - "output": 128000, - "input": 127000 - } - }, - "anthropic--claude-3.5-sonnet": { - "id": "anthropic--claude-3.5-sonnet", - "family": "claude-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192 - } - }, - "sonar-deep-research": { - "id": "sonar-deep-research", - "family": "sonar-deep-research", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 60000, - "output": 128000, - "input": 60000 - } - }, - "anthropic--claude-4.6-sonnet": { - "id": "anthropic--claude-4.6-sonnet", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - } - }, - "anthropic--claude-4.5-haiku": { - "id": "anthropic--claude-4.5-haiku", - "family": "claude-haiku", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "anthropic--claude-3-opus": { - "id": "anthropic--claude-3-opus", - "family": "claude-opus", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 4096 - } - }, - "sonar-pro": { - "id": "sonar-pro", - "family": "sonar-pro", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 128000, - "input": 200000 - } - }, - "anthropic--claude-3-haiku": { - "id": "anthropic--claude-3-haiku", - "family": "claude-haiku", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 4096 - } - }, - "anthropic--claude-4.6-opus": { - "id": "anthropic--claude-4.6-opus", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 - } - }, - "anthropic--claude-4-opus": { - "id": "anthropic--claude-4-opus", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - } - }, - "google-gemma-3-27b-it": { - "id": "google-gemma-3-27b-it", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 198000, - "output": 16384 - } - }, - "openai-gpt-4o-2024-11-20": { - "id": "openai-gpt-4o-2024-11-20", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "claude-opus-45": { - "id": "claude-opus-45", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 198000, - "output": 49500 - } - }, - "zai-org-glm-5": { - "id": "zai-org-glm-5", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 198000, - "output": 32000 - } - }, - "zai-org-glm-4.7": { - "id": "zai-org-glm-4.7", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 198000, - "output": 16384 - } - }, - "zai-org-glm-4.6": { - "id": "zai-org-glm-4.6", - "family": "glm", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 198000, - "output": 16384 - } - }, - "openai-gpt-53-codex": { - "id": "openai-gpt-53-codex", - "family": "gpt-codex", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000 - } - }, - "kimi-k2-5": { - "id": "kimi-k2-5", - "family": "kimi", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 65536 - } - }, - "mistral-small-3-2-24b-instruct": { - "id": "mistral-small-3-2-24b-instruct", - "family": "mistral-small", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 16384 - } - }, - "mistral-31-24b": { - "id": "mistral-31-24b", - "family": "mistral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "grok-4-20-multi-agent-beta": { - "id": "grok-4-20-multi-agent-beta", - "family": "grok-beta", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 128000 - } - }, - "openai-gpt-54-pro": { - "id": "openai-gpt-54-pro", - "family": "gpt-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 - } - }, - "qwen3-4b": { - "id": "qwen3-4b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 4096 - } - }, - "grok-4-20-beta": { - "id": "grok-4-20-beta", - "family": "grok-beta", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 128000 - } - }, - "olafangensan-glm-4.7-flash-heretic": { - "id": "olafangensan-glm-4.7-flash-heretic", - "family": "glm-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 24000 - } - }, - "minimax-m25": { - "id": "minimax-m25", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 198000, - "output": 32768 - } - }, - "zai-org-glm-4.7-flash": { - "id": "zai-org-glm-4.7-flash", - "family": "glm-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "qwen3-coder-480b-a35b-instruct-turbo": { - "id": "qwen3-coder-480b-a35b-instruct-turbo", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 65536 - } - }, - "openai-gpt-oss-120b": { - "id": "openai-gpt-oss-120b", - "family": "gpt-oss", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "grok-41-fast": { - "id": "grok-41-fast", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 30000 - } - }, - "openai-gpt-52": { - "id": "openai-gpt-52", - "family": "gpt", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 65536 - } - }, - "openai-gpt-54": { - "id": "openai-gpt-54", - "family": "gpt", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 131072 - } - }, - "gemini-3-1-pro-preview": { - "id": "gemini-3-1-pro-preview", - "family": "gemini-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video", - "audio", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - } - }, - "openai-gpt-4o-mini-2024-07-18": { - "id": "openai-gpt-4o-mini-2024-07-18", - "family": "gpt-mini", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "llama-3.3-70b": { - "id": "llama-3.3-70b", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "qwen3-next-80b": { - "id": "qwen3-next-80b", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 16384 - } - }, - "hermes-3-llama-3.1-405b": { - "id": "hermes-3-llama-3.1-405b", - "family": "hermes", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "qwen3-5-9b": { - "id": "qwen3-5-9b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 65536 - } - }, - "minimax-m21": { - "id": "minimax-m21", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 198000, - "output": 32768 - } - }, - "qwen3-5-35b-a3b": { - "id": "qwen3-5-35b-a3b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 65536 - } - }, - "llama-3.2-3b": { - "id": "llama-3.2-3b", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "venice-uncensored": { - "id": "venice-uncensored", - "family": "venice", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384, - "input": 128000 - } - }, - "nvidia-nemotron-3-nano-30b-a3b": { - "id": "nvidia-nemotron-3-nano-30b-a3b", - "family": "nemotron", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "openai-gpt-52-codex": { - "id": "openai-gpt-52-codex", - "family": "gpt-codex", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 65536 - } - }, - "minimax-m27": { - "id": "minimax-m27", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 198000, - "output": 32768 - } - }, - "venice-uncensored-role-play": { - "id": "venice-uncensored-role-play", - "family": "venice", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "claude-sonnet-45": { - "id": "claude-sonnet-45", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 198000, - "output": 49500 - } - }, - "nova-2-lite-v1": { - "id": "nova-2-lite-v1", - "family": "nova-lite", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - } - }, - "nova-2-pro-v1": { - "id": "nova-2-pro-v1", - "family": "nova-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - } - }, - "glm-4.7-flashx": { - "id": "glm-4.7-flashx", - "family": "glm-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 131072 - } - }, - "public/deepseek-v3": { - "id": "public/deepseek-v3", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "public/deepseek-r1": { - "id": "public/deepseek-r1", - "family": "deepseek-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32000 - } - }, - "public/minimax-m25": { - "id": "public/minimax-m25", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "gpt-5-4": { - "id": "gpt-5-4", - "family": "gpt", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 272000, - "output": 128000 - } - }, - "deepseek-v3-2": { - "id": "deepseek-v3-2", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "minimax-m2-5": { - "id": "minimax-m2-5", - "family": "minimax", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 192000, - "output": 8192 - } - }, - "gpt-5-3-codex": { - "id": "gpt-5-3-codex", - "family": "gpt", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000 - } - }, - "meta-llama-3_3-70b-instruct": { - "id": "meta-llama-3_3-70b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "mistral-7b-instruct-v0.3": { - "id": "mistral-7b-instruct-v0.3", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 65536 - } - }, - "qwen2.5-coder-32b-instruct": { - "id": "qwen2.5-coder-32b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "mixtral-8x7b-instruct-v0.1": { - "id": "mixtral-8x7b-instruct-v0.1", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "neuralmagic/meta-llama-3.1-8b-instruct-fp8": { - "id": "neuralmagic/Meta-Llama-3.1-8B-Instruct-FP8", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "neuralmagic/mistral-nemo-instruct-2407-fp8": { - "id": "neuralmagic/Mistral-Nemo-Instruct-2407-FP8", - "family": "mistral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "qwen/qwen3-vl-embedding-8b": { - "id": "Qwen/Qwen3-VL-Embedding-8B", - "family": "qwen", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 4096 - } - }, - "qwen/qwen3-vl-235b-a22b-instruct-fp8": { - "id": "Qwen/Qwen3-VL-235B-A22B-Instruct-FP8", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 218000, - "output": 8192 - } - }, - "cortecs/llama-3.3-70b-instruct-fp8-dynamic": { - "id": "cortecs/Llama-3.3-70B-Instruct-FP8-Dynamic", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "speakleash/bielik-11b-v2.6-instruct": { - "id": "speakleash/Bielik-11B-v2.6-Instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 32000 - } - }, - "speakleash/bielik-11b-v3.0-instruct": { - "id": "speakleash/Bielik-11B-v3.0-Instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 32000 - } - }, - "anthropic/claude-3-7-sonnet": { - "id": "anthropic/claude-3-7-sonnet", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "xai/grok-4-fast": { - "id": "xai/grok-4-fast", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 64000 - } - }, - "pro/zai-org/glm-4.7": { - "id": "Pro/zai-org/GLM-4.7", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 205000, - "output": 205000 - } - }, - "pro/zai-org/glm-5": { - "id": "Pro/zai-org/GLM-5", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 205000, - "output": 205000 - } - }, - "pro/minimaxai/minimax-m2.5": { - "id": "Pro/MiniMaxAI/MiniMax-M2.5", - "family": "minimax", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 192000, - "output": 131000 - } - }, - "pro/minimaxai/minimax-m2.1": { - "id": "Pro/MiniMaxAI/MiniMax-M2.1", - "family": "minimax", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 197000, - "output": 131000 - } - }, - "pro/deepseek-ai/deepseek-r1": { - "id": "Pro/deepseek-ai/DeepSeek-R1", - "family": "deepseek-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 164000, - "output": 164000 - } - }, - "pro/deepseek-ai/deepseek-v3.2": { - "id": "Pro/deepseek-ai/DeepSeek-V3.2", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 164000, - "output": 164000 - } - }, - "pro/deepseek-ai/deepseek-v3": { - "id": "Pro/deepseek-ai/DeepSeek-V3", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 164000, - "output": 164000 - } - }, - "pro/deepseek-ai/deepseek-v3.1-terminus": { - "id": "Pro/deepseek-ai/DeepSeek-V3.1-Terminus", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 164000, - "output": 164000 - } - }, - "pro/moonshotai/kimi-k2-instruct-0905": { - "id": "Pro/moonshotai/Kimi-K2-Instruct-0905", - "family": "kimi", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262000, - "output": 262000 - } - }, - "pro/moonshotai/kimi-k2.5": { - "id": "Pro/moonshotai/Kimi-K2.5", - "family": "kimi", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262000, - "output": 262000 - } - }, - "pro/moonshotai/kimi-k2-thinking": { - "id": "Pro/moonshotai/Kimi-K2-Thinking", - "family": "kimi-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262000, - "output": 262000 - } - }, - "paddlepaddle/paddleocr-vl-1.5": { - "id": "PaddlePaddle/PaddleOCR-VL-1.5", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 16384 - } - }, - "kwaipilot/kat-dev": { - "id": "Kwaipilot/KAT-Dev", - "family": "kat-coder", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "deepseek-ai/deepseek-ocr": { - "id": "deepseek-ai/DeepSeek-OCR", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "ascend-tribe/pangu-pro-moe": { - "id": "ascend-tribe/pangu-pro-moe", - "family": "pangu", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "qwen/qwen3.5-9b": { - "id": "qwen/qwen3.5-9b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32768 - } - }, - "qwen/qwen3.5-122b-a10b": { - "id": "qwen/qwen3.5-122b-a10b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "qwen/qwen3.5-35b-a3b": { - "id": "qwen/qwen3.5-35b-a3b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "qwen/qwen3.5-4b": { - "id": "Qwen/Qwen3.5-4B", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "qwen/qwen3.5-27b": { - "id": "qwen/qwen3.5-27b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "gpt-5-chat-latest": { - "id": "gpt-5-chat-latest", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000, - "input": 272000 - } - }, - "llama-4-scout": { - "id": "llama-4-scout", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "codex-mini-latest": { - "id": "codex-mini-latest", - "family": "gpt-codex-mini", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000 - } - }, - "qwen2.5-coder-7b-fast": { - "id": "qwen2.5-coder-7b-fast", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 8192 - } - }, - "sonar-reasoning-pro": { - "id": "sonar-reasoning-pro", - "family": "sonar-reasoning", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 127000, - "output": 128000, - "input": 127000 - } - }, - "llama-3.1-8b-instruct-turbo": { - "id": "llama-3.1-8b-instruct-turbo", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "ernie-4.5-21b-a3b-thinking": { - "id": "ernie-4.5-21b-a3b-thinking", - "family": "ernie", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8000 - } - }, - "llama-prompt-guard-2-22m": { - "id": "llama-prompt-guard-2-22m", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 512, - "output": 2 - } - }, - "gpt-4.1-mini-2025-04-14": { - "id": "gpt-4.1-mini-2025-04-14", - "family": "gpt-mini", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1047576, - "output": 32768 - } - }, - "llama-guard-4": { - "id": "llama-guard-4", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 1024 - } - }, - "sonar-reasoning": { - "id": "sonar-reasoning", - "family": "sonar-reasoning", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 127000, - "output": 4096 - } - }, - "deepseek-v3.1-terminus": { - "id": "deepseek-v3.1-terminus", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "claude-3.5-sonnet-v2": { - "id": "claude-3.5-sonnet-v2", - "family": "claude-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192 - } - }, - "mistral-small": { - "id": "mistral-small", - "family": "mistral-small", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "qwen3-vl-235b-a22b-instruct": { - "id": "qwen3-vl-235b-a22b-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 16384 - } - }, - "qwen3-235b-a22b-thinking": { - "id": "qwen3-235b-a22b-thinking", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 81920 - } - }, - "claude-3-haiku-20240307": { - "id": "claude-3-haiku-20240307", - "family": "claude-haiku", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 4096 - } - }, - "kimi-k2-0711": { - "id": "kimi-k2-0711", - "family": "kimi", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "llama-4-maverick": { - "id": "llama-4-maverick", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "deepseek-tng-r1t2-chimera": { - "id": "deepseek-tng-r1t2-chimera", - "family": "deepseek-thinking", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 130000, - "output": 163840 - } - }, - "claude-opus-4": { - "id": "claude-opus-4", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - } - }, - "llama-prompt-guard-2-86m": { - "id": "llama-prompt-guard-2-86m", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 512, - "output": 2 - } - }, - "gemma-3-12b-it": { - "id": "gemma-3-12b-it", - "family": "gemma", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "hermes-2-pro-llama-3-8b": { - "id": "hermes-2-pro-llama-3-8b", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "zai/glm-5": { - "id": "zai/glm-5", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202800, - "output": 131072 - } - }, - "zai/glm-4.7-flashx": { - "id": "zai/glm-4.7-flashx", - "family": "glm-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 128000 - } - }, - "zai/glm-4.5-air": { - "id": "zai/glm-4.5-air", - "family": "glm-air", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 96000 - } - }, - "zai/glm-4.5": { - "id": "zai/glm-4.5", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "zai/glm-4.7-flash": { - "id": "zai/glm-4.7-flash", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 131000 - } - }, - "zai/glm-4.6": { - "id": "zai/glm-4.6", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 96000 - } - }, - "zai/glm-4.7": { - "id": "zai/glm-4.7", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 120000 - } - }, - "zai/glm-4.6v-flash": { - "id": "zai/glm-4.6v-flash", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 24000 - } - }, - "zai/glm-5-turbo": { - "id": "zai/glm-5-turbo", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202800, - "output": 131100 - } - }, - "zai/glm-4.5v": { - "id": "zai/glm-4.5v", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 66000, - "output": 66000 - } - }, - "zai/glm-4.6v": { - "id": "zai/glm-4.6v", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 24000 - } - }, - "nvidia/nemotron-nano-12b-v2-vl": { - "id": "nvidia/nemotron-nano-12b-v2-vl", - "family": "nemotron", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 26215 - } - }, - "arcee-ai/trinity-large-preview": { - "id": "arcee-ai/trinity-large-preview", - "family": "trinity", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 131000 - } - }, - "arcee-ai/trinity-mini": { - "id": "arcee-ai/trinity-mini", - "family": "trinity-mini", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192, - "input": 131072 - } - }, - "inception/mercury-coder-small": { - "id": "inception/mercury-coder-small", - "family": "mercury", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 16384 - } - }, - "voyage/voyage-3-large": { - "id": "voyage/voyage-3-large", - "family": "voyage", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 1536 - } - }, - "voyage/voyage-code-3": { - "id": "voyage/voyage-code-3", - "family": "voyage", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 1536 - } - }, - "voyage/voyage-law-2": { - "id": "voyage/voyage-law-2", - "family": "voyage", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 1536 - } - }, - "voyage/voyage-finance-2": { - "id": "voyage/voyage-finance-2", - "family": "voyage", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 1536 - } - }, - "voyage/voyage-code-2": { - "id": "voyage/voyage-code-2", - "family": "voyage", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 1536 - } - }, - "voyage/voyage-4-lite": { - "id": "voyage/voyage-4-lite", - "family": "voyage", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 0 - } - }, - "voyage/voyage-3.5-lite": { - "id": "voyage/voyage-3.5-lite", - "family": "voyage", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 1536 - } - }, - "voyage/voyage-4-large": { - "id": "voyage/voyage-4-large", - "family": "voyage", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 0 - } - }, - "voyage/voyage-3.5": { - "id": "voyage/voyage-3.5", - "family": "voyage", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 1536 - } - }, - "voyage/voyage-4": { - "id": "voyage/voyage-4", - "family": "voyage", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 0 - } - }, - "amazon/nova-2-lite": { - "id": "amazon/nova-2-lite", - "family": "nova", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 1000000 - } - }, - "amazon/titan-embed-text-v2": { - "id": "amazon/titan-embed-text-v2", - "family": "titan-embed", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 1536 - } - }, - "amazon/nova-lite": { - "id": "amazon/nova-lite", - "family": "nova-lite", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 300000, - "output": 8192 - } - }, - "amazon/nova-pro": { - "id": "amazon/nova-pro", - "family": "nova-pro", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 300000, - "output": 8192 - } - }, - "amazon/nova-micro": { - "id": "amazon/nova-micro", - "family": "nova-micro", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "alibaba/qwen-3-235b": { - "id": "alibaba/qwen-3-235b", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 40960, - "output": 16384 - } - }, - "alibaba/qwen3-max-preview": { - "id": "alibaba/qwen3-max-preview", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - } - }, - "alibaba/qwen3-next-80b-a3b-thinking": { - "id": "alibaba/qwen3-next-80b-a3b-thinking", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 65536 - } - }, - "alibaba/qwen3-max-thinking": { - "id": "alibaba/qwen3-max-thinking", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 65536 - } - }, - "alibaba/qwen3-vl-instruct": { - "id": "alibaba/qwen3-vl-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 129024 - } - }, - "alibaba/qwen3-embedding-8b": { - "id": "alibaba/qwen3-embedding-8b", - "family": "qwen", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "alibaba/qwen3-coder-next": { - "id": "alibaba/qwen3-coder-next", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "alibaba/qwen3-coder": { - "id": "alibaba/qwen3-coder", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 66536 - } - }, - "alibaba/qwen-3-30b": { - "id": "alibaba/qwen-3-30b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 40960, - "output": 16384 - } - }, - "alibaba/qwen3-embedding-0.6b": { - "id": "alibaba/qwen3-embedding-0.6b", - "family": "qwen", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "alibaba/qwen-3-14b": { - "id": "alibaba/qwen-3-14b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 40960, - "output": 16384 - } - }, - "alibaba/qwen3-235b-a22b-thinking": { - "id": "alibaba/qwen3-235b-a22b-thinking", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262114, - "output": 262114 - } - }, - "alibaba/qwen3-vl-thinking": { - "id": "alibaba/qwen3-vl-thinking", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 129024 - } - }, - "alibaba/qwen3.5-flash": { - "id": "alibaba/qwen3.5-flash", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - } - }, - "alibaba/qwen3-next-80b-a3b-instruct": { - "id": "alibaba/qwen3-next-80b-a3b-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - } - }, - "alibaba/qwen3.5-plus": { - "id": "alibaba/qwen3.5-plus", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000 - } - }, - "alibaba/qwen3-max": { - "id": "alibaba/qwen3-max", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - } - }, - "alibaba/qwen-3-32b": { - "id": "alibaba/qwen-3-32b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 40960, - "output": 16384 - } - }, - "alibaba/qwen3-coder-plus": { - "id": "alibaba/qwen3-coder-plus", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 1000000 - } - }, - "alibaba/qwen3-embedding-4b": { - "id": "alibaba/qwen3-embedding-4b", - "family": "qwen", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "alibaba/qwen3-coder-30b-a3b": { - "id": "alibaba/qwen3-coder-30b-a3b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 160000, - "output": 32768 - } - }, - "bfl/flux-pro-1.0-fill": { - "id": "bfl/flux-pro-1.0-fill", - "family": "flux", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 512, - "output": 0 - } - }, - "bfl/flux-pro-1.1": { - "id": "bfl/flux-pro-1.1", - "family": "flux", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 512, - "output": 0 - } - }, - "bfl/flux-kontext-max": { - "id": "bfl/flux-kontext-max", - "family": "flux", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 512, - "output": 0 - } - }, - "bfl/flux-kontext-pro": { - "id": "bfl/flux-kontext-pro", - "family": "flux", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 512, - "output": 0 - } - }, - "bfl/flux-pro-1.1-ultra": { - "id": "bfl/flux-pro-1.1-ultra", - "family": "flux", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 512, - "output": 0 - } - }, - "mistral/codestral-embed": { - "id": "mistral/codestral-embed", - "family": "codestral-embed", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 1536 - } - }, - "mistral/devstral-small-2": { - "id": "mistral/devstral-small-2", - "family": "devstral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "mistral/devstral-2": { - "id": "mistral/devstral-2", - "family": "devstral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "mistral/mistral-large-3": { - "id": "mistral/mistral-large-3", - "family": "mistral-large", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "mistral/mistral-embed": { - "id": "mistral/mistral-embed", - "family": "mistral-embed", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 1536 - } - }, - "mistral/ministral-14b": { - "id": "mistral/ministral-14b", - "family": "ministral", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 256000 - } - }, - "mistral/mistral-nemo": { - "id": "mistral/mistral-nemo", - "family": "mistral-nemo", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 60288, - "output": 16000 - } - }, - "mistral/mistral-medium": { - "id": "mistral/mistral-medium", - "family": "mistral-medium", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 64000 - } - }, - "mistral/devstral-small": { - "id": "mistral/devstral-small", - "family": "devstral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 64000 - } - }, - "mistral/codestral": { - "id": "mistral/codestral", - "family": "codestral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 4096 - } - }, - "mistral/mixtral-8x22b-instruct": { - "id": "mistral/mixtral-8x22b-instruct", - "family": "mixtral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 64000, - "output": 64000 - } - }, - "mistral/mistral-small": { - "id": "mistral/mistral-small", - "family": "mistral-small", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "mistral/ministral-8b": { - "id": "mistral/ministral-8b", - "family": "ministral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "mistral/pixtral-large": { - "id": "mistral/pixtral-large", - "family": "pixtral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "mistral/pixtral-12b": { - "id": "mistral/pixtral-12b", - "family": "pixtral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "mistral/magistral-small": { - "id": "mistral/magistral-small", - "family": "magistral-small", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "mistral/magistral-medium": { - "id": "mistral/magistral-medium", - "family": "magistral-medium", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "mistral/ministral-3b": { - "id": "mistral/ministral-3b", - "family": "ministral", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "kwaipilot/kat-coder-pro-v1": { - "id": "kwaipilot/kat-coder-pro-v1", - "family": "kat-coder", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32000 - } - }, - "deepseek/deepseek-v3": { - "id": "deepseek/deepseek-v3", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 16384 - } - }, - "deepseek/deepseek-v3.2-thinking": { - "id": "deepseek/deepseek-v3.2-thinking", - "family": "deepseek-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 64000 - } - }, - "moonshotai/kimi-k2-turbo": { - "id": "moonshotai/kimi-k2-turbo", - "family": "kimi", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 16384 - } - }, - "google/gemini-embedding-001": { - "id": "google/gemini-embedding-001", - "family": "gemini-embedding", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 1536 - } - }, - "google/imagen-4.0-fast-generate-001": { - "id": "google/imagen-4.0-fast-generate-001", - "family": "imagen", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 480, - "output": 0 - } - }, - "google/text-embedding-005": { - "id": "google/text-embedding-005", - "family": "text-embedding", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 1536 - } - }, - "google/imagen-4.0-ultra-generate-001": { - "id": "google/imagen-4.0-ultra-generate-001", - "family": "imagen", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 480, - "output": 0 - } - }, - "google/gemini-3.1-flash-image-preview": { - "id": "google/gemini-3.1-flash-image-preview", - "family": "gemini", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "image", - "text" - ] - }, - "limit": { - "context": 65536, - "output": 65536 - } - }, - "google/text-multilingual-embedding-002": { - "id": "google/text-multilingual-embedding-002", - "family": "text-embedding", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 1536 - } - }, - "google/gemini-embedding-2": { - "id": "google/gemini-embedding-2", - "family": "gemini-embedding", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "google/gemini-2.5-flash-image": { - "id": "google/gemini-2.5-flash-image", - "family": "gemini-flash", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "image", - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "google/gemini-3-pro-image": { - "id": "google/gemini-3-pro-image", - "family": "gemini-pro", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text", - "image" - ] - }, - "limit": { - "context": 65536, - "output": 32768 - } - }, - "google/gemini-2.5-flash-image-preview": { - "id": "google/gemini-2.5-flash-image-preview", - "family": "gemini-flash", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text", - "image" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "google/imagen-4.0-generate-001": { - "id": "google/imagen-4.0-generate-001", - "family": "imagen", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 480, - "output": 0 - } - }, - "meituan/longcat-flash-thinking": { - "id": "meituan/longcat-flash-thinking", - "family": "longcat", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "meituan/longcat-flash-thinking-2601": { - "id": "meituan/longcat-flash-thinking-2601", - "family": "longcat", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "bytedance/seed-1.6": { - "id": "bytedance/seed-1.6", - "family": "seed", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32000 - } - }, - "bytedance/seed-1.8": { - "id": "bytedance/seed-1.8", - "family": "seed", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 64000 - } - }, - "meta/llama-3.1-8b": { - "id": "meta/llama-3.1-8b", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "meta/llama-3.2-11b": { - "id": "meta/llama-3.2-11b", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "meta/llama-3.1-70b": { - "id": "meta/llama-3.1-70b", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "meta/llama-3.2-90b": { - "id": "meta/llama-3.2-90b", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "meta/llama-3.2-1b": { - "id": "meta/llama-3.2-1b", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "meta/llama-3.2-3b": { - "id": "meta/llama-3.2-3b", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192 - } - }, - "meta/llama-4-maverick": { - "id": "meta/llama-4-maverick", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "meta/llama-3.3-70b": { - "id": "meta/llama-3.3-70b", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "meta/llama-4-scout": { - "id": "meta/llama-4-scout", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "vercel/v0-1.5-md": { - "id": "vercel/v0-1.5-md", - "family": "v0", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32000 - } - }, - "vercel/v0-1.0-md": { - "id": "vercel/v0-1.0-md", - "family": "v0", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32000 - } - }, - "openai/text-embedding-ada-002": { - "id": "openai/text-embedding-ada-002", - "family": "text-embedding", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "input": 6656, - "output": 1536 - } - }, - "openai/gpt-4o-mini-search-preview": { - "id": "openai/gpt-4o-mini-search-preview", - "family": "gpt-mini", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 16384 - } - }, - "openai/text-embedding-3-small": { - "id": "openai/text-embedding-3-small", - "family": "text-embedding", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "input": 6656, - "output": 1536 - } - }, - "openai/text-embedding-3-large": { - "id": "openai/text-embedding-3-large", - "family": "text-embedding", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "input": 6656, - "output": 1536 - } - }, - "openai/gpt-5.1-thinking": { - "id": "openai/gpt-5.1-thinking", - "family": "gpt", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text", - "image" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "openai/codex-mini": { - "id": "openai/codex-mini", - "family": "gpt-codex-mini", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 100000, - "output": 100000 - } - }, - "morph/morph-v3-large": { - "id": "morph/morph-v3-large", - "family": "morph", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 131072 - } - }, - "morph/morph-v3-fast": { - "id": "morph/morph-v3-fast", - "family": "morph", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 81920, - "output": 38000 - } - }, - "cohere/embed-v4.0": { - "id": "cohere/embed-v4.0", - "family": "cohere-embed", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 1536 - } - }, - "cohere/command-a": { - "id": "cohere/command-a", - "family": "command", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 8192 - } - }, - "minimax/minimax-m2.1-lightning": { - "id": "minimax/minimax-m2.1-lightning", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "recraft/recraft-v2": { - "id": "recraft/recraft-v2", - "family": "recraft", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 512, - "output": 0 - } - }, - "recraft/recraft-v3": { - "id": "recraft/recraft-v3", - "family": "recraft", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 512, - "output": 0 - } - }, - "perplexity/sonar-reasoning-pro": { - "id": "perplexity/sonar-reasoning-pro", - "family": "sonar-reasoning", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 25600 - } - }, - "perplexity/sonar-reasoning": { - "id": "perplexity/sonar-reasoning", - "family": "sonar-reasoning", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 127000, - "output": 8000 - } - }, - "perplexity/sonar-pro": { - "id": "perplexity/sonar-pro", - "family": "sonar-pro", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8000 - } - }, - "anthropic/claude-3.5-sonnet-20240620": { - "id": "anthropic/claude-3.5-sonnet-20240620", - "family": "claude-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192 - } - }, - "xai/grok-4.20-non-reasoning-beta": { - "id": "xai/grok-4.20-non-reasoning-beta", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 2000000 - } - }, - "xai/grok-4.20-non-reasoning": { - "id": "xai/grok-4.20-non-reasoning", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 2000000 - } - }, - "xai/grok-imagine-image": { - "id": "xai/grok-imagine-image", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text", - "image" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "xai/grok-4.20-reasoning": { - "id": "xai/grok-4.20-reasoning", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 2000000 - } - }, - "xai/grok-4.20-reasoning-beta": { - "id": "xai/grok-4.20-reasoning-beta", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 2000000 - } - }, - "xai/grok-4.20-multi-agent": { - "id": "xai/grok-4.20-multi-agent", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 2000000 - } - }, - "xai/grok-imagine-image-pro": { - "id": "xai/grok-imagine-image-pro", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text", - "image" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "xai/grok-4.20-multi-agent-beta": { - "id": "xai/grok-4.20-multi-agent-beta", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 2000000 - } - }, - "xai/grok-3-fast": { - "id": "xai/grok-3-fast", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "xai/grok-3-mini-fast": { - "id": "xai/grok-3-mini-fast", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "xai/grok-2-vision": { - "id": "xai/grok-2-vision", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 4096 - } - }, - "gpt-4o-2024-05-13": { - "id": "gpt-4o-2024-05-13", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "o3-deep-research": { - "id": "o3-deep-research", - "family": "o", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000 - } - }, - "o4-mini-deep-research": { - "id": "o4-mini-deep-research", - "family": "o-mini", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000 - } - }, - "gpt-3.5-turbo": { - "id": "gpt-3.5-turbo", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16385, - "output": 4096 - } - }, - "o1-pro": { - "id": "o1-pro", - "family": "o-pro", - "reasoning": true, - "temperature": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000 - } - }, - "gpt-5.2-pro": { - "id": "gpt-5.2-pro", - "family": "gpt-pro", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 272000, - "output": 128000 - } - }, - "gpt-4o-2024-08-06": { - "id": "gpt-4o-2024-08-06", - "family": "gpt", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "auto": { - "id": "auto", - "family": "auto", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 32000 - } - }, - "morph-v3-fast": { - "id": "morph-v3-fast", - "family": "morph", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16000, - "output": 16000 - } - }, - "morph-v3-large": { - "id": "morph-v3-large", - "family": "morph", - "reasoning": false, - "temperature": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 32000 - } - }, - "c4ai-aya-expanse-32b": { - "id": "c4ai-aya-expanse-32b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4000 - } - }, - "command-a-03-2025": { - "id": "command-a-03-2025", - "family": "command-a", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 8000 - } - }, - "command-r7b-arabic-02-2025": { - "id": "command-r7b-arabic-02-2025", - "family": "command-r", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4000 - } - }, - "command-a-translate-08-2025": { - "id": "command-a-translate-08-2025", - "family": "command-a", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8000, - "output": 8000 - } - }, - "command-r-08-2024": { - "id": "command-r-08-2024", - "family": "command-r", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4000 - } - }, - "command-r-plus-08-2024": { - "id": "command-r-plus-08-2024", - "family": "command-r", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4000 - } - }, - "command-a-reasoning-08-2025": { - "id": "command-a-reasoning-08-2025", - "family": "command-a", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 8192, - "input": 256000 - } - }, - "c4ai-aya-expanse-8b": { - "id": "c4ai-aya-expanse-8b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8000, - "output": 4000 - } - }, - "c4ai-aya-vision-8b": { - "id": "c4ai-aya-vision-8b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16000, - "output": 4000 - } - }, - "c4ai-aya-vision-32b": { - "id": "c4ai-aya-vision-32b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16000, - "output": 4000 - } - }, - "command-r7b-12-2024": { - "id": "command-r7b-12-2024", - "family": "command-r", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4000 - } - }, - "command-a-vision-07-2025": { - "id": "command-a-vision-07-2025", - "family": "command-a", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8000 - } - }, - "v0-1.0-md": { - "id": "v0-1.0-md", - "family": "v0", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000, - "input": 200000 - } - }, - "v0-1.5-md": { - "id": "v0-1.5-md", - "family": "v0", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000, - "input": 200000 - } - }, - "v0-1.5-lg": { - "id": "v0-1.5-lg", - "family": "v0", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 64000, - "input": 1000000 - } - }, - "llama-3_1-nemotron-ultra-253b-v1": { - "id": "Llama-3_1-Nemotron-Ultra-253B-v1", - "family": "llama", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 4096 - } - }, - "deepseek-r1-distill-qwen-32b": { - "id": "deepseek-r1-distill-qwen-32b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 16384 - } - }, - "glm-5-fp8": { - "id": "GLM-5-FP8", - "family": "glm", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202000, - "output": 131072 - } - }, - "nvidia-nemotron-3-super-120b-a12b-nvfp4": { - "id": "NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4", - "family": "nemotron", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 260000, - "output": 8192 - } - }, - "nvidia/nemotron-120b-a12b": { - "id": "nvidia/Nemotron-120B-A12B", - "family": "nemotron", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32678 - } - }, - "claude-3-5-haiku-latest": { - "id": "claude-3-5-haiku-latest", - "family": "claude-haiku", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192 - } - }, - "claude-3-5-sonnet-20241022": { - "id": "claude-3-5-sonnet-20241022", - "family": "claude-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192, - "input": 200000 - } - }, - "claude-3-sonnet-20240229": { - "id": "claude-3-sonnet-20240229", - "family": "claude-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 4096 - } - }, - "claude-sonnet-4-0": { - "id": "claude-sonnet-4-0", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "claude-opus-4-0": { - "id": "claude-opus-4-0", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - } - }, - "claude-3-5-haiku-20241022": { - "id": "claude-3-5-haiku-20241022", - "family": "claude-haiku", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192, - "input": 200000 - } - }, - "claude-3-5-sonnet-20240620": { - "id": "claude-3-5-sonnet-20240620", - "family": "claude-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192, - "input": 200000 - } - }, - "claude-3-7-sonnet-latest": { - "id": "claude-3-7-sonnet-latest", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "claude-3-opus-20240229": { - "id": "claude-3-opus-20240229", - "family": "claude-opus", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 4096 - } - }, - "hunyuan-turbos": { - "id": "hunyuan-turbos", - "family": "hunyuan", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "tc-code-latest": { - "id": "tc-code-latest", - "family": "auto", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "hunyuan-t1": { - "id": "hunyuan-t1", - "family": "hunyuan", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "hunyuan-2.0-instruct": { - "id": "hunyuan-2.0-instruct", - "family": "hunyuan", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "hunyuan-2.0-thinking": { - "id": "hunyuan-2.0-thinking", - "family": "hunyuan", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "claude-sonnet-4-5@20250929": { - "id": "claude-sonnet-4-5@20250929", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "claude-opus-4-1@20250805": { - "id": "claude-opus-4-1@20250805", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - } - }, - "claude-3-7-sonnet@20250219": { - "id": "claude-3-7-sonnet@20250219", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "claude-opus-4@20250514": { - "id": "claude-opus-4@20250514", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 32000 - } - }, - "claude-opus-4-5@20251101": { - "id": "claude-opus-4-5@20251101", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "claude-3-5-haiku@20241022": { - "id": "claude-3-5-haiku@20241022", - "family": "claude-haiku", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192 - } - }, - "claude-sonnet-4@20250514": { - "id": "claude-sonnet-4@20250514", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "claude-3-5-sonnet@20241022": { - "id": "claude-3-5-sonnet@20241022", - "family": "claude-sonnet", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8192 - } - }, - "claude-opus-4-6@default": { - "id": "claude-opus-4-6@default", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 - } - }, - "claude-haiku-4-5@20251001": { - "id": "claude-haiku-4-5@20251001", - "family": "claude-haiku", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "claude-sonnet-4-6@default": { - "id": "claude-sonnet-4-6@default", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "giga-potato-thinking": { - "id": "giga-potato-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32000 - } - }, - "corethink:free": { - "id": "corethink:free", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 78000, - "output": 8192 - } - }, - "morph-warp-grep-v2": { - "id": "morph-warp-grep-v2", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32000 - } - }, - "giga-potato": { - "id": "giga-potato", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 32000 - } - }, - "allenai/olmo-2-0325-32b-instruct": { - "id": "allenai/olmo-2-0325-32b-instruct", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "allenai/olmo-3-7b-instruct": { - "id": "allenai/olmo-3-7b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 65536 - } - }, - "allenai/olmo-3-32b-think": { - "id": "allenai/olmo-3-32b-think", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 8192, - "input": 128000 - }, - "family": "allenai" - }, - "allenai/molmo-2-8b": { - "id": "allenai/molmo-2-8b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 36864, - "output": 36864, - "input": 36864 - }, - "family": "allenai" - }, - "allenai/olmo-3.1-32b-instruct": { - "id": "allenai/olmo-3.1-32b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 8192, - "input": 65536 - }, - "family": "allenai" - }, - "allenai/olmo-3-7b-think": { - "id": "allenai/olmo-3-7b-think", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 65536 - } - }, - "allenai/olmo-3.1-32b-think": { - "id": "allenai/olmo-3.1-32b-think", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 8192, - "input": 65536 - }, - "family": "allenai" - }, - "nvidia/nemotron-3-super-120b-a12b:free": { - "id": "nvidia/nemotron-3-super-120b-a12b:free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "ibm-granite/granite-4.0-h-micro": { - "id": "ibm-granite/granite-4.0-h-micro", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 32768 - } - }, - "arcee-ai/coder-large": { - "id": "arcee-ai/coder-large", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "arcee-ai/virtuoso-large": { - "id": "arcee-ai/virtuoso-large", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 64000 - } - }, - "arcee-ai/maestro-reasoning": { - "id": "arcee-ai/maestro-reasoning", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32000 - } - }, - "arcee-ai/spotlight": { - "id": "arcee-ai/spotlight", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 65537 - } - }, - "alfredpros/codellama-7b-instruct-solidity": { - "id": "alfredpros/codellama-7b-instruct-solidity", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4096, - "output": 4096 - } - }, - "liquid/lfm-2.2-6b": { - "id": "liquid/lfm-2.2-6b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "liquid/lfm-2-24b-a2b": { - "id": "liquid/lfm-2-24b-a2b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "liquid/lfm2-8b-a1b": { - "id": "liquid/lfm2-8b-a1b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "upstage/solar-pro-3": { - "id": "upstage/solar-pro-3", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "switchpoint/router": { - "id": "switchpoint/router", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "kilo-auto/balanced": { - "id": "kilo-auto/balanced", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "kilo-auto/free": { - "id": "kilo-auto/free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "kilo-auto/small": { - "id": "kilo-auto/small", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000 - } - }, - "kilo-auto/frontier": { - "id": "kilo-auto/frontier", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 - } - }, - "amazon/nova-micro-v1": { - "id": "amazon/nova-micro-v1", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 5120, - "input": 128000 - }, - "family": "nova-micro" - }, - "amazon/nova-lite-v1": { - "id": "amazon/nova-lite-v1", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 300000, - "output": 5120, - "input": 300000 - }, - "family": "nova-lite" - }, - "amazon/nova-premier-v1": { - "id": "amazon/nova-premier-v1", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 32000 - } - }, - "amazon/nova-2-lite-v1": { - "id": "amazon/nova-2-lite-v1", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 65535, - "input": 1000000 - }, - "family": "nova" - }, - "amazon/nova-pro-v1": { - "id": "amazon/nova-pro-v1", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 300000, - "output": 32000, - "input": 300000 - }, - "family": "nova-pro" - }, - "anthracite-org/magnum-v4-72b": { - "id": "anthracite-org/magnum-v4-72b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 8192, - "input": 16384 - }, - "family": "llama" - }, - "alibaba/tongyi-deepresearch-30b-a3b": { - "id": "alibaba/tongyi-deepresearch-30b-a3b", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "aion-labs/aion-1.0-mini": { - "id": "aion-labs/aion-1.0-mini", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192, - "input": 131072 - }, - "family": "deepseek" - }, - "aion-labs/aion-2.0": { - "id": "aion-labs/aion-2.0", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "aion-labs/aion-rp-llama-3.1-8b": { - "id": "aion-labs/aion-rp-llama-3.1-8b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 16384, - "input": 32768 - }, - "family": "llama" - }, - "aion-labs/aion-1.0": { - "id": "aion-labs/aion-1.0", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 8192, - "input": 65536 - }, - "family": "llama" - }, - "relace/relace-search": { - "id": "relace/relace-search", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 128000 - } - }, - "relace/relace-apply-3": { - "id": "relace/relace-apply-3", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 128000 - } - }, - "thedrummer/rocinante-12b": { - "id": "thedrummer/rocinante-12b", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "thedrummer/cydonia-24b-v4.1": { - "id": "thedrummer/cydonia-24b-v4.1", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 131072 - } - }, - "thedrummer/unslopnemo-12b": { - "id": "thedrummer/unslopnemo-12b", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "thedrummer/skyfall-36b-v2": { - "id": "thedrummer/skyfall-36b-v2", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "mancer/weaver": { - "id": "mancer/weaver", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8000, - "output": 2000 - } - }, - "deepseek/deepseek-r1-distill-qwen-32b": { - "id": "deepseek/deepseek-r1-distill-qwen-32b", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "alpindale/goliath-120b": { - "id": "alpindale/goliath-120b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 6144, - "output": 1024 - } - }, - "openrouter/hunter-alpha": { - "id": "openrouter/hunter-alpha", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 32000 - } - }, - "openrouter/auto": { - "id": "openrouter/auto", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio", - "image", - "pdf", - "text", - "video" - ], - "output": [ - "image", - "text" - ] - }, - "limit": { - "context": 2000000, - "output": 32768 - } - }, - "openrouter/healer-alpha": { - "id": "openrouter/healer-alpha", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio", - "image", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32000 - } - }, - "openrouter/bodybuilder": { - "id": "openrouter/bodybuilder", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "google/gemini-2.5-pro-preview": { - "id": "google/gemini-2.5-pro-preview", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio", - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536 - } - }, - "google/gemini-2.0-flash-lite-001": { - "id": "google/gemini-2.0-flash-lite-001", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio", - "image", - "pdf", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 8192 - } - }, - "z-ai/glm-4-32b": { - "id": "z-ai/glm-4-32b", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 32768 - } - }, - "deepcogito/cogito-v2.1-671b": { - "id": "deepcogito/cogito-v2.1-671b", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384, - "input": 128000 - }, - "family": "cogito" - }, - "bytedance/ui-tars-1.5-7b": { - "id": "bytedance/ui-tars-1.5-7b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 2048 - } - }, - "undi95/remm-slerp-l2-13b": { - "id": "undi95/remm-slerp-l2-13b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 6144, - "output": 4096, - "input": 6144 - }, - "family": "llama" - }, - "qwen/qwen-vl-plus": { - "id": "qwen/qwen-vl-plus", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "qwen/qwen-vl-max": { - "id": "qwen/qwen-vl-max", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "qwen/qwen-2.5-vl-7b-instruct": { - "id": "qwen/qwen-2.5-vl-7b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 6554 - } - }, - "qwen/qwen3-max-thinking": { - "id": "qwen/qwen3-max-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - } - }, - "qwen/qwen-max": { - "id": "qwen/qwen-max", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 8192 - } - }, - "qwen/qwen-turbo": { - "id": "qwen/qwen-turbo", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "qwen/qwen3-235b-a22b-2507": { - "id": "qwen/qwen3-235b-a22b-2507", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 52429 - } - }, - "qwen/qwen-2.5-7b-instruct": { - "id": "qwen/qwen-2.5-7b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 6554 - } - }, - "qwen/qwen-plus": { - "id": "qwen/qwen-plus", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 32768 - } - }, - "qwen/qwen-plus-2025-07-28": { - "id": "qwen/qwen-plus-2025-07-28", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 32768 - } - }, - "qwen/qwen3-30b-a3b": { - "id": "Qwen/Qwen3-30B-A3B", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 40960, - "output": 40960 - }, - "family": "qwen" - }, - "qwen/qwen-plus-2025-07-28:thinking": { - "id": "qwen/qwen-plus-2025-07-28:thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 32768 - } - }, - "qwen/qwen3.5-flash-02-23": { - "id": "qwen/qwen3.5-flash-02-23", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 65536 - } - }, - "eleutherai/llemma_7b": { - "id": "eleutherai/llemma_7b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4096, - "output": 4096 - } - }, - "x-ai/grok-code-fast-1:optimized:free": { - "id": "x-ai/grok-code-fast-1:optimized:free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 10000 - } - }, - "meta-llama/llama-4-scout": { - "id": "meta-llama/llama-4-scout", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 328000, - "output": 65536, - "input": 328000 - }, - "family": "llama" - }, - "meta-llama/llama-3.2-3b-instruct": { - "id": "meta-llama/llama-3.2-3b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192, - "input": 131072 - }, - "family": "llama" - }, - "meta-llama/llama-3.2-1b-instruct": { - "id": "meta-llama/llama-3.2-1b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 60000, - "output": 12000 - } - }, - "meta-llama/llama-3.1-405b-instruct": { - "id": "meta-llama/llama-3.1-405b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 26200 - } - }, - "meta-llama/llama-4-maverick": { - "id": "meta-llama/llama-4-maverick", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048576, - "output": 65536, - "input": 1048576 - }, - "family": "llama" - }, - "meta-llama/llama-3.1-405b": { - "id": "meta-llama/llama-3.1-405b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768 - } - }, - "tngtech/deepseek-r1t2-chimera": { - "id": "tngtech/deepseek-r1t2-chimera", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 163840 - } - }, - "mistralai/ministral-3b-2512": { - "id": "mistralai/ministral-3b-2512", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768, - "input": 131072 - }, - "family": "ministral" - }, - "mistralai/mistral-saba": { - "id": "mistralai/mistral-saba", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "output": 32768, - "input": 32000 - }, - "family": "mistral" - }, - "mistralai/mistral-small-24b-instruct-2501": { - "id": "mistralai/mistral-small-24b-instruct-2501", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 16384 - } - }, - "mistralai/pixtral-large-2411": { - "id": "mistralai/pixtral-large-2411", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "mistralai/mistral-small-creative": { - "id": "mistralai/mistral-small-creative", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 32768, - "input": 32768 - }, - "family": "mistral-small" - }, - "mistralai/mistral-large-2512": { - "id": "mistralai/mistral-large-2512", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 52429 - } - }, - "mistralai/ministral-8b-2512": { - "id": "mistralai/ministral-8b-2512", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768, - "input": 262144 - }, - "family": "ministral" - }, - "mistralai/ministral-14b-2512": { - "id": "mistralai/ministral-14b-2512", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768, - "input": 262144 - }, - "family": "ministral" - }, - "mistralai/devstral-medium": { - "id": "mistralai/devstral-medium", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 26215 - } - }, - "mistralai/mistral-large-2407": { - "id": "mistralai/mistral-large-2407", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "mistralai/devstral-small": { - "id": "mistralai/devstral-small", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 26215 - } - }, - "mistralai/mixtral-8x22b-instruct": { - "id": "mistralai/mixtral-8x22b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "output": 13108 - } - }, - "mistralai/mistral-large-2411": { - "id": "mistralai/mistral-large-2411", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 26215 - } - }, - "mistralai/mistral-7b-instruct-v0.1": { - "id": "mistralai/mistral-7b-instruct-v0.1", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2824, - "output": 565 - } - }, - "mistralai/mistral-large": { - "id": "mistralai/mistral-large", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 256000, - "input": 128000 - }, - "family": "mistral-large" - }, - "mistralai/mixtral-8x7b-instruct": { - "id": "mistralai/mixtral-8x7b-instruct", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 16384 - } - }, - "openai/gpt-4o-2024-11-20": { - "id": "openai/gpt-4o-2024-11-20", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384, - "input": 128000 - }, - "family": "gpt" - }, - "openai/gpt-4o:extended": { - "id": "openai/gpt-4o:extended", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 64000 - } - }, - "openai/gpt-4o-2024-05-13": { - "id": "openai/gpt-4o-2024-05-13", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "openai/gpt-4o-audio-preview": { - "id": "openai/gpt-4o-audio-preview", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "audio", - "text" - ], - "output": [ - "audio", - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "openai/gpt-4o-mini-2024-07-18": { - "id": "openai/gpt-4o-mini-2024-07-18", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "openai/gpt-audio": { - "id": "openai/gpt-audio", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "audio", - "text" - ], - "output": [ - "audio", - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "openai/gpt-3.5-turbo-16k": { - "id": "openai/gpt-3.5-turbo-16k", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16385, - "output": 4096 - } - }, - "openai/gpt-5-image-mini": { - "id": "openai/gpt-5-image-mini", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "image", - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000 - } - }, - "openai/gpt-4-turbo-preview": { - "id": "openai/gpt-4-turbo-preview", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096, - "input": 128000 - }, - "family": "gpt" - }, - "openai/gpt-3.5-turbo-0613": { - "id": "openai/gpt-3.5-turbo-0613", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4095, - "output": 4096 - } - }, - "openai/gpt-4-0314": { - "id": "openai/gpt-4-0314", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8191, - "output": 4096 - } - }, - "openai/gpt-audio-mini": { - "id": "openai/gpt-audio-mini", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "audio", - "text" - ], - "output": [ - "audio", - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384 - } - }, - "openai/gpt-4-1106-preview": { - "id": "openai/gpt-4-1106-preview", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "openai/gpt-4o-2024-08-06": { - "id": "openai/gpt-4o-2024-08-06", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384, - "input": 128000 - }, - "family": "gpt" - }, - "openai/o4-mini-high": { - "id": "openai/o4-mini-high", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 100000, - "input": 200000 - }, - "family": "o-mini" - }, - "openai/gpt-4o-search-preview": { - "id": "openai/gpt-4o-search-preview", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 16384, - "input": 128000 - }, - "family": "gpt" - }, - "cohere/command-r-08-2024": { - "id": "cohere/command-r-08-2024", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4000 - } - }, - "cohere/command-r-plus-08-2024": { - "id": "cohere/command-r-plus-08-2024", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4096, - "input": 128000 - }, - "family": "command-r" - }, - "cohere/command-r7b-12-2024": { - "id": "cohere/command-r7b-12-2024", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 4000 - } - }, - "minimax/minimax-m2-her": { - "id": "minimax/minimax-m2-her", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65532, - "output": 2048, - "input": 65532 - }, - "family": "minimax" - }, - "minimax/minimax-m2.5:free": { - "id": "minimax/minimax-m2.5:free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "sao10k/l3.1-70b-hanami-x1": { - "id": "Sao10K/L3.1-70B-Hanami-x1", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "output": 16384, - "input": 16384 - }, - "family": "llama" - }, - "sao10k/l3-lunaris-8b": { - "id": "sao10k/l3-lunaris-8b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "sao10k/l3.1-euryale-70b": { - "id": "sao10k/l3.1-euryale-70b", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "sao10k/l3-euryale-70b": { - "id": "sao10k/l3-euryale-70b", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 8192 - } - }, - "sao10k/l3.3-euryale-70b": { - "id": "sao10k/l3.3-euryale-70b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "writer/palmyra-x5": { - "id": "writer/palmyra-x5", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1040000, - "output": 8192 - } - }, - "perplexity/sonar-deep-research": { - "id": "perplexity/sonar-deep-research", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 25600 - } - }, - "perplexity/sonar-pro-search": { - "id": "perplexity/sonar-pro-search", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 8000 - } - }, - "bytedance-seed/seed-2.0-mini": { - "id": "bytedance-seed/seed-2.0-mini", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 131072 - } - }, - "bytedance-seed/seed-1.6": { - "id": "bytedance-seed/seed-1.6", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - } - }, - "bytedance-seed/seed-1.6-flash": { - "id": "bytedance-seed/seed-1.6-flash", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 32768 - } - }, - "bytedance-seed/seed-2.0-lite": { - "id": "bytedance-seed/seed-2.0-lite", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text", - "video" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 131072 - } - }, - "anthropic/claude-3.7-sonnet:thinking": { - "id": "anthropic/claude-3.7-sonnet:thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "pdf", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "output": 64000 - } - }, - "ai21/jamba-large-1.7": { - "id": "ai21/jamba-large-1.7", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "output": 4096 - } - }, - "kilo/auto": { - "id": "kilo/auto", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 128000 - } - }, - "kilo/auto-free": { - "id": "kilo/auto-free", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 204800, - "output": 131072 - } - }, - "kilo/auto-small": { - "id": "kilo/auto-small", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "image", - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "output": 128000 - } - }, - "inflection/inflection-3-productivity": { - "id": "inflection/inflection-3-productivity", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8000, - "output": 4096, - "input": 8000 - }, - "family": "gpt" - }, - "inflection/inflection-3-pi": { - "id": "inflection/inflection-3-pi", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8000, - "output": 4096, - "input": 8000 - }, - "family": "gpt" - }, - "nousresearch/hermes-3-llama-3.1-70b": { - "id": "nousresearch/hermes-3-llama-3.1-70b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 32768 - } - }, - "nousresearch/hermes-3-llama-3.1-405b": { - "id": "nousresearch/hermes-3-llama-3.1-405b", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 16384 - } - }, - "exa-research-pro": { - "id": "exa-research-pro", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 16384 - } - }, - "gemini-2.0-pro-exp-02-05": { - "id": "gemini-2.0-pro-exp-02-05", + "gemini-exp-1206": { + "id": "gemini-exp-1206", "reasoning": false, "toolCall": false, "modalities": { @@ -32506,27 +2514,8 @@ "output": 8192 } }, - "qwen-image": { - "id": "qwen-image", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "llama-3.3-70b-shakudo": { - "id": "Llama-3.3-70B-Shakudo", + "auto-model-basic": { + "id": "auto-model-basic", "reasoning": false, "toolCall": false, "modalities": { @@ -32538,215 +2527,13 @@ ] }, "limit": { - "context": 32768, - "input": 32768, - "output": 16384 + "context": 1000000, + "input": 1000000, + "output": 1000000 } }, - "ernie-4.5-8k-preview": { - "id": "ernie-4.5-8k-preview", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8000, - "input": 8000, - "output": 16384 - } - }, - "claude-3-7-sonnet-thinking:128000": { - "id": "claude-3-7-sonnet-thinking:128000", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 64000 - } - }, - "phi-4-multimodal-instruct": { - "id": "phi-4-multimodal-instruct", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 16384 - } - }, - "z-image-turbo": { - "id": "z-image-turbo", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "llama-3.3+(3v3.3)-70b-tenyxchat-daybreakstorywriter": { - "id": "Llama-3.3+(3v3.3)-70B-TenyxChat-DaybreakStorywriter", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "mistral-small-31-24b-instruct": { - "id": "mistral-small-31-24b-instruct", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 131072 - } - }, - "llama-3.3-70b-the-omega-directive-unslop-v2.0": { - "id": "Llama-3.3-70B-The-Omega-Directive-Unslop-v2.0", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "baichuan-m2": { - "id": "Baichuan-M2", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 32768 - } - }, - "doubao-1.5-vision-pro-32k": { - "id": "doubao-1.5-vision-pro-32k", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "input": 32000, - "output": 8192 - } - }, - "glm-4.5-air-derestricted-iceblink-v2-reextract": { - "id": "GLM-4.5-Air-Derestricted-Iceblink-v2-ReExtract", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 131072, - "output": 65536 - } - }, - "llama-3.3-70b-arliai-rpmax-v1.4": { - "id": "Llama-3.3-70B-ArliAI-RPMax-v1.4", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "jamba-large-1.6": { - "id": "jamba-large-1.6", + "jamba-mini": { + "id": "jamba-mini", "reasoning": false, "toolCall": false, "modalities": { @@ -32763,8 +2550,366 @@ "output": 4096 } }, - "llama-3.3-70b-aurora-borealis": { - "id": "Llama-3.3-70B-Aurora-Borealis", + "yi-large": { + "id": "yi-large", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "input": 32000, + "output": 4096 + } + }, + "auto-model-premium": { + "id": "auto-model-premium", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 1000000 + } + }, + "azure-gpt-4o": { + "id": "azure-gpt-4o", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + } + }, + "deepseek-v3-0324": { + "id": "deepseek-v3-0324", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 128000, + "output": 131072 + }, + "temperature": true, + "family": "deepseek" + }, + "claude-3-5-haiku-20241022": { + "id": "claude-3-5-haiku-20241022", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 8192 + }, + "family": "claude-haiku", + "temperature": true + }, + "doubao-seed-1-6-250615": { + "id": "doubao-seed-1-6-250615", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 16384 + } + }, + "ernie-x1.1-preview": { + "id": "ernie-x1.1-preview", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 64000, + "input": 64000, + "output": 8192 + } + }, + "ernie-5.0-thinking-preview": { + "id": "ernie-5.0-thinking-preview", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + } + }, + "glm-4-air-0111": { + "id": "glm-4-air-0111", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 4096 + } + }, + "fastgpt": { + "id": "fastgpt", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 32768 + } + }, + "doubao-seed-1-6-thinking-250615": { + "id": "doubao-seed-1-6-thinking-250615", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 16384 + } + }, + "gemini-2.0-flash-001": { + "id": "gemini-2.0-flash-001", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 8192 + } + }, + "claude-opus-4-1-thinking:32000": { + "id": "claude-opus-4-1-thinking:32000", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 32000 + } + }, + "llama-3.3-70b-rawmaw": { + "id": "Llama-3.3-70B-RAWMAW", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "glm-4.5-air-derestricted-steam": { + "id": "GLM-4.5-Air-Derestricted-Steam", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 220600, + "input": 220600, + "output": 65536 + } + }, + "claude-3-5-sonnet-20241022": { + "id": "claude-3-5-sonnet-20241022", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 8192 + }, + "family": "claude-sonnet", + "temperature": true + }, + "yi-medium-200k": { + "id": "yi-medium-200k", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 4096 + } + }, + "gemma-3-27b-arliai-rpmax-v3": { + "id": "Gemma-3-27B-ArliAI-RPMax-v3", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "phi-4-mini-instruct": { + "id": "phi-4-mini-instruct", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + } + }, + "llama-3.3+(3v3.3)-70b-tenyxchat-daybreakstorywriter": { + "id": "Llama-3.3+(3v3.3)-70B-TenyxChat-DaybreakStorywriter", "reasoning": false, "toolCall": false, "modalities": { @@ -32800,8 +2945,64 @@ "output": 16384 } }, - "llama-3.3-70b-magnum-v4-se": { - "id": "Llama-3.3-70B-Magnum-v4-SE", + "glm-z1-air": { + "id": "glm-z1-air", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "input": 32000, + "output": 16384 + } + }, + "claude-3-7-sonnet-thinking:128000": { + "id": "claude-3-7-sonnet-thinking:128000", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 64000 + } + }, + "glm-4-air": { + "id": "glm-4-air", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 4096 + } + }, + "llama-3.3-70b-miraifanfare": { + "id": "Llama-3.3-70B-MiraiFanfare", "reasoning": false, "toolCall": false, "modalities": { @@ -32818,44 +3019,119 @@ "output": 16384 } }, - "kat-coder-pro-v1": { - "id": "KAT-Coder-Pro-V1", - "reasoning": false, + "gemini-2.0-flash-thinking-exp-01-21": { + "id": "gemini-2.0-flash-thinking-exp-01-21", + "reasoning": true, "toolCall": false, "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 256000, - "input": 256000, - "output": 32768 - } - }, - "hunyuan-turbos-20250226": { - "id": "hunyuan-turbos-20250226", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 24000, - "input": 24000, + "context": 1000000, + "input": 1000000, "output": 8192 } }, - "jamba-large-1.7": { - "id": "jamba-large-1.7", + "magistral-small-2506": { + "id": "Magistral-Small-2506", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 32768 + } + }, + "doubao-1.5-pro-32k": { + "id": "doubao-1.5-pro-32k", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 32000, + "output": 12000 + }, + "temperature": true + }, + "venice-uncensored:web": { + "id": "venice-uncensored:web", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 80000, + "input": 80000, + "output": 16384 + } + }, + "glm-4": { + "id": "glm-4", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 4096 + } + }, + "qwen3-vl-235b-a22b-instruct-original": { + "id": "qwen3-vl-235b-a22b-instruct-original", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 32768 + } + }, + "jamba-large-1.6": { + "id": "jamba-large-1.6", "reasoning": false, "toolCall": false, "modalities": { @@ -32872,8 +3148,857 @@ "output": 4096 } }, - "mercury-coder-small": { - "id": "mercury-coder-small", + "qwen25-vl-72b-instruct": { + "id": "qwen25-vl-72b-instruct", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "input": 32000, + "output": 32768 + } + }, + "claude-sonnet-4-thinking:64000": { + "id": "claude-sonnet-4-thinking:64000", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 64000 + } + }, + "llama-3.3+(3.1v3.3)-70b-new-dawn-v1.1": { + "id": "Llama-3.3+(3.1v3.3)-70B-New-Dawn-v1.1", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "glm-4.5-air-derestricted-iceblink-reextract": { + "id": "GLM-4.5-Air-Derestricted-Iceblink-ReExtract", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 98304 + } + }, + "universal-summarizer": { + "id": "universal-summarizer", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 32768 + } + }, + "claude-sonnet-4-thinking:32768": { + "id": "claude-sonnet-4-thinking:32768", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 64000 + } + }, + "sarvan-medium": { + "id": "sarvan-medium", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + } + }, + "claude-3-7-sonnet-thinking:8192": { + "id": "claude-3-7-sonnet-thinking:8192", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 64000 + } + }, + "gemini-2.5-flash-preview-05-20": { + "id": "gemini-2.5-flash-preview-05-20", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "input": 1048000, + "output": 65536 + }, + "family": "gemini-flash", + "temperature": true + }, + "glm-4.5-air-derestricted-iceblink-v2-reextract": { + "id": "GLM-4.5-Air-Derestricted-Iceblink-v2-ReExtract", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 65536 + } + }, + "llama-3.3-70b-fallen-v1": { + "id": "Llama-3.3-70B-Fallen-v1", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "qwen3-vl-235b-a22b-thinking": { + "id": "qwen3-vl-235b-a22b-thinking", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 32768, + "output": 32768 + }, + "family": "qwen", + "temperature": true + }, + "claude-3-7-sonnet-thinking:32768": { + "id": "claude-3-7-sonnet-thinking:32768", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 64000 + } + }, + "claude-3-7-sonnet-thinking:1024": { + "id": "claude-3-7-sonnet-thinking:1024", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 64000 + } + }, + "llama-3.3-70b-vulpecula-r1": { + "id": "Llama-3.3-70B-Vulpecula-R1", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "claude-sonnet-4-thinking:8192": { + "id": "claude-sonnet-4-thinking:8192", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 64000 + } + }, + "llama-3.3-70b-ignition-v0.1": { + "id": "Llama-3.3-70B-Ignition-v0.1", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "glm-4-plus-0111": { + "id": "glm-4-plus-0111", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 4096 + } + }, + "kat-coder-air-v1": { + "id": "KAT-Coder-Air-V1", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 32768 + } + }, + "deepseek-r1-sambanova": { + "id": "deepseek-r1-sambanova", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 4096 + } + }, + "deepseek-r1": { + "id": "deepseek-r1", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "input": 128000, + "output": 163840 + }, + "family": "deepseek-thinking", + "temperature": true + }, + "doubao-1-5-thinking-pro-250415": { + "id": "doubao-1-5-thinking-pro-250415", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + } + }, + "sonar-pro": { + "id": "sonar-pro", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 16384 + }, + "family": "sonar", + "temperature": true + }, + "gemma-3-27b-it-abliterated": { + "id": "Gemma-3-27B-it-Abliterated", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 96000 + } + }, + "deepseek-chat-cheaper": { + "id": "deepseek-chat-cheaper", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 8192 + } + }, + "gemini-2.0-pro-exp-02-05": { + "id": "gemini-2.0-pro-exp-02-05", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2097152, + "input": 2097152, + "output": 8192 + } + }, + "azure-gpt-4o-mini": { + "id": "azure-gpt-4o-mini", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + } + }, + "llama-3.3-70b-ms-nevoria": { + "id": "Llama-3.3-70B-MS-Nevoria", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "claude-opus-4-thinking": { + "id": "claude-opus-4-thinking", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 32000 + } + }, + "llama-3.3-70b-sapphira-0.1": { + "id": "Llama-3.3-70B-Sapphira-0.1", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "doubao-seed-code-preview-latest": { + "id": "doubao-seed-code-preview-latest", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 16384 + } + }, + "llama-3.3-70b-arliai-rpmax-v1.4": { + "id": "Llama-3.3-70B-ArliAI-RPMax-v1.4", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "mistral-small-31-24b-instruct": { + "id": "mistral-small-31-24b-instruct", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 131072 + } + }, + "glm-4.1v-thinking-flashx": { + "id": "glm-4.1v-thinking-flashx", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 64000, + "input": 64000, + "output": 8192 + } + }, + "hunyuan-t1-latest": { + "id": "hunyuan-t1-latest", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 16384 + } + }, + "doubao-1-5-thinking-vision-pro-250428": { + "id": "doubao-1-5-thinking-vision-pro-250428", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + } + }, + "asi1-mini": { + "id": "asi1-mini", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + } + }, + "ernie-5.0-thinking-latest": { + "id": "ernie-5.0-thinking-latest", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + } + }, + "llama-3.3-70b-incandescent-malevolence": { + "id": "Llama-3.3-70B-Incandescent-Malevolence", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "llama-3.3-70b-damascus-r1": { + "id": "Llama-3.3-70B-Damascus-R1", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "gemma-3-27b-nidum-uncensored": { + "id": "Gemma-3-27B-Nidum-Uncensored", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 96000 + } + }, + "gemini-2.5-flash-lite-preview-09-2025-thinking": { + "id": "gemini-2.5-flash-lite-preview-09-2025-thinking", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048756, + "input": 1048756, + "output": 65536 + } + }, + "doubao-seed-2-0-pro-260215": { + "id": "doubao-seed-2-0-pro-260215", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 128000 + } + }, + "gemma-3-27b-cardprojector-v4": { + "id": "Gemma-3-27B-CardProjector-v4", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "jamba-mini-1.7": { + "id": "jamba-mini-1.7", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 4096 + } + }, + "llama-3.3-70b-forgotten-safeword-3.6": { + "id": "Llama-3.3-70B-Forgotten-Safeword-3.6", "reasoning": false, "toolCall": false, "modalities": { @@ -32909,234 +4034,16 @@ "output": 16384 } }, - "yi-medium-200k": { - "id": "yi-medium-200k", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 4096 - } - }, - "deepseek-chat-cheaper": { - "id": "deepseek-chat-cheaper", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 8192 - } - }, - "step-r1-v-mini": { - "id": "step-r1-v-mini", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 65536 - } - }, - "yi-lightning": { - "id": "yi-lightning", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 12000, - "input": 12000, - "output": 4096 - } - }, - "deepseek-reasoner-cheaper": { - "id": "deepseek-reasoner-cheaper", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 65536 - } - }, - "ernie-4.5-turbo-vl-32k": { - "id": "ernie-4.5-turbo-vl-32k", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "input": 32000, - "output": 16384 - } - }, - "llama-3.3-70b-ignition-v0.1": { - "id": "Llama-3.3-70B-Ignition-v0.1", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "glm-z1-air": { - "id": "glm-z1-air", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "input": 32000, - "output": 16384 - } - }, - "llama-3.3-70b-rawmaw": { - "id": "Llama-3.3-70B-RAWMAW", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "magistral-small-2506": { - "id": "Magistral-Small-2506", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 32768 - } - }, - "ernie-x1-turbo-32k": { - "id": "ernie-x1-turbo-32k", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "input": 32000, - "output": 16384 - } - }, - "deepseek-r1-sambanova": { - "id": "deepseek-r1-sambanova", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 4096 - } - }, - "claude-3-7-sonnet-thinking:1024": { - "id": "claude-3-7-sonnet-thinking:1024", + "gemini-2.5-pro-preview-06-05": { + "id": "gemini-2.5-pro-preview-06-05", "reasoning": true, "toolCall": true, "modalities": { "input": [ "text", "image", + "audio", + "video", "pdf" ], "output": [ @@ -33144,474 +4051,13 @@ ] }, "limit": { - "context": 200000, - "input": 200000, - "output": 64000 - } - }, - "llama-3.3-70b-magnum-v4-se-cirrus-x1-slerp": { - "id": "Llama-3.3-70B-Magnum-v4-SE-Cirrus-x1-SLERP", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] + "context": 1048576, + "input": 1048756, + "output": 65536 }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "llama-3.3-70b-arliai-rpmax-v3": { - "id": "Llama-3.3-70B-ArliAI-RPMax-v3", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "qwen-long": { - "id": "qwen-long", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 10000000, - "input": 10000000, - "output": 8192 - }, - "family": "qwen", + "family": "gemini-pro", "temperature": true }, - "llama-3.3-70b-progenitor-v3.3": { - "id": "Llama-3.3-70B-Progenitor-V3.3", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "glm-4.5-air-derestricted-iceblink-v2": { - "id": "GLM-4.5-Air-Derestricted-Iceblink-v2", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 158600, - "input": 158600, - "output": 65536 - } - }, - "study_gpt-chatgpt-4o-latest": { - "id": "study_gpt-chatgpt-4o-latest", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 16384 - } - }, - "qwq-32b": { - "id": "qwq-32b", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 128000, - "output": 8192 - }, - "family": "qwen", - "temperature": true - }, - "llama-3.3-70b-ms-nevoria": { - "id": "Llama-3.3-70B-MS-Nevoria", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "doubao-seed-1-6-250615": { - "id": "doubao-seed-1-6-250615", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "input": 256000, - "output": 16384 - } - }, - "glm-4": { - "id": "glm-4", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 4096 - } - }, - "azure-gpt-4-turbo": { - "id": "azure-gpt-4-turbo", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 4096 - } - }, - "llama-3.3-70b-legion-v2.1": { - "id": "Llama-3.3-70B-Legion-V2.1", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "claude-3-7-sonnet-thinking:32768": { - "id": "claude-3-7-sonnet-thinking:32768", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 64000 - } - }, - "asi1-mini": { - "id": "asi1-mini", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 16384 - } - }, - "gemini-exp-1206": { - "id": "gemini-exp-1206", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 2097152, - "input": 2097152, - "output": 8192 - } - }, - "brave": { - "id": "brave", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "input": 8192, - "output": 8192 - } - }, - "doubao-1-5-thinking-pro-250415": { - "id": "doubao-1-5-thinking-pro-250415", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 16384 - } - }, - "claude-sonnet-4-thinking:64000": { - "id": "claude-sonnet-4-thinking:64000", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "input": 1000000, - "output": 64000 - } - }, - "glm-4.5-air-derestricted-steam-reextract": { - "id": "GLM-4.5-Air-Derestricted-Steam-ReExtract", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 131072, - "output": 65536 - } - }, - "kimi-k2-instruct-fast": { - "id": "kimi-k2-instruct-fast", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 131072, - "output": 16384 - } - }, - "llama-3.3-70b-geneticlemonade-opus": { - "id": "Llama-3.3-70B-GeneticLemonade-Opus", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "gemma-3-27b-big-tiger-v3": { - "id": "Gemma-3-27B-Big-Tiger-v3", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "doubao-seed-2-0-mini-260215": { - "id": "doubao-seed-2-0-mini-260215", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "input": 256000, - "output": 32000 - } - }, - "glm-4-air": { - "id": "glm-4-air", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 4096 - } - }, - "glm-4.5-air-derestricted-iceblink-reextract": { - "id": "GLM-4.5-Air-Derestricted-Iceblink-ReExtract", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 131072, - "output": 98304 - } - }, "gemini-2.0-pro-reasoner": { "id": "gemini-2.0-pro-reasoner", "reasoning": false, @@ -33630,429 +4076,6 @@ "output": 65536 } }, - "gemini-2.0-flash-001": { - "id": "gemini-2.0-flash-001", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "input": 1000000, - "output": 8192 - } - }, - "glm-4-plus": { - "id": "glm-4-plus", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 4096 - } - }, - "gemini-2.0-flash-exp-image-generation": { - "id": "gemini-2.0-flash-exp-image-generation", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32767, - "input": 32767, - "output": 8192 - } - }, - "glm-4.5-air-derestricted": { - "id": "GLM-4.5-Air-Derestricted", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202600, - "input": 202600, - "output": 98304 - } - }, - "gemini-2.0-flash-thinking-exp-1219": { - "id": "gemini-2.0-flash-thinking-exp-1219", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32767, - "input": 32767, - "output": 8192 - } - }, - "glm-4.1v-thinking-flashx": { - "id": "glm-4.1v-thinking-flashx", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 64000, - "input": 64000, - "output": 8192 - } - }, - "llama-3.3-70b-strawberrylemonade-v1.0": { - "id": "Llama-3.3-70B-StrawberryLemonade-v1.0", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "llama-3.3-70b-fallen-v1": { - "id": "Llama-3.3-70B-Fallen-v1", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "gemma-3-27b-nidum-uncensored": { - "id": "Gemma-3-27B-Nidum-Uncensored", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 96000 - } - }, - "llama-3.3-70b-electranova-v1.0": { - "id": "Llama-3.3-70B-Electranova-v1.0", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "grok-3-fast-beta": { - "id": "grok-3-fast-beta", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 131072, - "output": 131072 - } - }, - "llama-3.3-70b-sapphira-0.1": { - "id": "Llama-3.3-70B-Sapphira-0.1", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "gemini-2.5-pro-preview-03-25": { - "id": "gemini-2.5-pro-preview-03-25", - "reasoning": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048756, - "input": 1048756, - "output": 65536 - } - }, - "step-2-16k-exp": { - "id": "step-2-16k-exp", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16000, - "input": 16000, - "output": 8192 - } - }, - "chroma": { - "id": "chroma", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "fastgpt": { - "id": "fastgpt", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 32768 - } - }, - "claude-sonnet-4-thinking:8192": { - "id": "claude-sonnet-4-thinking:8192", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "input": 1000000, - "output": 64000 - } - }, - "llama-3.3-70b-electra-r1": { - "id": "Llama-3.3-70B-Electra-R1", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "llama-3.3-70b-fallen-r1-v1": { - "id": "Llama-3.3-70B-Fallen-R1-v1", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "gemma-3-27b-it-abliterated": { - "id": "Gemma-3-27B-it-Abliterated", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 96000 - } - }, - "doubao-1.5-pro-256k": { - "id": "doubao-1.5-pro-256k", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "input": 256000, - "output": 16384 - } - }, - "claude-opus-4-thinking": { - "id": "claude-opus-4-thinking", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 32000 - } - }, - "doubao-1-5-thinking-vision-pro-250428": { - "id": "doubao-1-5-thinking-vision-pro-250428", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 16384 - } - }, "doubao-seed-2-0-lite-260215": { "id": "doubao-seed-2-0-lite-260215", "reasoning": false, @@ -34071,181 +4094,33 @@ "output": 32000 } }, - "qwen25-vl-72b-instruct": { - "id": "qwen25-vl-72b-instruct", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "input": 32000, - "output": 32768 - } - }, - "azure-gpt-4o": { - "id": "azure-gpt-4o", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 16384 - } - }, - "ernie-4.5-turbo-128k": { - "id": "ernie-4.5-turbo-128k", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 16384 - } - }, - "azure-o1": { - "id": "azure-o1", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 100000 - } - }, - "gemini-3-pro-preview-thinking": { - "id": "gemini-3-pro-preview-thinking", + "gemini-2.5-flash-lite-preview-06-17": { + "id": "gemini-2.5-flash-lite-preview-06-17", "reasoning": true, "toolCall": true, "modalities": { "input": [ "text", - "image" + "image", + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 1048756, + "context": 1048576, "input": 1048756, "output": 65536 - } - }, - "grok-3-mini-beta": { - "id": "grok-3-mini-beta", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] }, - "limit": { - "context": 131072, - "input": 131072, - "output": 131072 - } + "family": "gemini-flash-lite", + "temperature": true }, - "claude-opus-4-1-thinking": { - "id": "claude-opus-4-1-thinking", + "sonar-deep-research": { + "id": "sonar-deep-research", "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 32000 - } - }, - "gemini-2.5-flash-nothinking": { - "id": "gemini-2.5-flash-nothinking", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048756, - "input": 1048756, - "output": 65536 - } - }, - "claude-3-7-sonnet-thinking:8192": { - "id": "claude-3-7-sonnet-thinking:8192", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 64000 - } - }, - "auto-model-basic": { - "id": "auto-model-basic", - "reasoning": false, "toolCall": false, "modalities": { "input": [ @@ -34256,13 +4131,15 @@ ] }, "limit": { - "context": 1000000, - "input": 1000000, - "output": 1000000 - } + "context": 127000, + "input": 60000, + "output": 4096 + }, + "temperature": true, + "family": "sonar-deep-research" }, - "llama-3.3-70b-the-omega-directive-unslop-v2.1": { - "id": "Llama-3.3-70B-The-Omega-Directive-Unslop-v2.1", + "llama-3.3-70b-geneticlemonade-unleashed-v3": { + "id": "Llama-3.3-70B-GeneticLemonade-Unleashed-v3", "reasoning": false, "toolCall": false, "modalities": { @@ -34279,281 +4156,6 @@ "output": 16384 } }, - "glm-4-plus-0111": { - "id": "glm-4-plus-0111", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 4096 - } - }, - "llama-3.3-70b-bigger-body": { - "id": "Llama-3.3-70B-Bigger-Body", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "kat-coder-air-v1": { - "id": "KAT-Coder-Air-V1", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 32768 - } - }, - "doubao-seed-1-6-flash-250615": { - "id": "doubao-seed-1-6-flash-250615", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "input": 256000, - "output": 16384 - } - }, - "glm-4-air-0111": { - "id": "glm-4-air-0111", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 4096 - } - }, - "phi-4-mini-instruct": { - "id": "phi-4-mini-instruct", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 16384 - } - }, - "jamba-mini-1.6": { - "id": "jamba-mini-1.6", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "input": 256000, - "output": 4096 - } - }, - "kimi-thinking-preview": { - "id": "kimi-thinking-preview", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 16384 - } - }, - "claude-sonnet-4-thinking:1024": { - "id": "claude-sonnet-4-thinking:1024", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "input": 1000000, - "output": 64000 - } - }, - "llama-3.3-70b-incandescent-malevolence": { - "id": "Llama-3.3-70B-Incandescent-Malevolence", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "llama-3.3-70b-forgotten-safeword-3.6": { - "id": "Llama-3.3-70B-Forgotten-Safeword-3.6", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "step-2-mini": { - "id": "step-2-mini", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8000, - "input": 8000, - "output": 4096 - } - }, - "mistral-nemo-12b-instruct-2407": { - "id": "Mistral-Nemo-12B-Instruct-2407", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 16384 - } - }, - "baichuan4-turbo": { - "id": "Baichuan4-Turbo", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 32768 - } - }, - "ernie-5.0-thinking-latest": { - "id": "ernie-5.0-thinking-latest", - "reasoning": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 16384 - } - }, "gemma-3-27b-glitter": { "id": "Gemma-3-27B-Glitter", "reasoning": false, @@ -34572,28 +4174,8 @@ "output": 16384 } }, - "claude-opus-4-thinking:32000": { - "id": "claude-opus-4-thinking:32000", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 32000 - } - }, - "auto-model-premium": { - "id": "auto-model-premium", + "llama-3.3-70b-the-omega-directive-unslop-v2.1": { + "id": "Llama-3.3-70B-The-Omega-Directive-Unslop-v2.1", "reasoning": false, "toolCall": false, "modalities": { @@ -34605,74 +4187,15 @@ ] }, "limit": { - "context": 1000000, - "input": 1000000, - "output": 1000000 + "context": 32768, + "input": 32768, + "output": 16384 } }, - "gemini-2.0-flash-thinking-exp-01-21": { - "id": "gemini-2.0-flash-thinking-exp-01-21", - "reasoning": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "input": 1000000, - "output": 8192 - } - }, - "claude-sonnet-4-thinking:32768": { - "id": "claude-sonnet-4-thinking:32768", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "input": 1000000, - "output": 64000 - } - }, - "claude-opus-4-1-thinking:32768": { - "id": "claude-opus-4-1-thinking:32768", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 32000 - } - }, - "jamba-large": { - "id": "jamba-large", + "qwen3-30b-a3b-instruct-2507": { + "id": "qwen3-30b-a3b-instruct-2507", "reasoning": false, - "toolCall": false, + "toolCall": true, "modalities": { "input": [ "text" @@ -34682,616 +4205,12 @@ ] }, "limit": { - "context": 256000, + "context": 262000, "input": 256000, - "output": 4096 - } - }, - "llama-3.3-70b-miraifanfare": { - "id": "Llama-3.3-70B-MiraiFanfare", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "venice-uncensored:web": { - "id": "venice-uncensored:web", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 80000, - "input": 80000, - "output": 16384 - } - }, - "gemini-2.5-flash-lite-preview-09-2025-thinking": { - "id": "gemini-2.5-flash-lite-preview-09-2025-thinking", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1048756, - "input": 1048756, - "output": 65536 - } - }, - "ernie-x1-32k-preview": { - "id": "ernie-x1-32k-preview", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "input": 32000, - "output": 16384 - } - }, - "glm-z1-airx": { - "id": "glm-z1-airx", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "input": 32000, - "output": 16384 - } - }, - "ernie-x1.1-preview": { - "id": "ernie-x1.1-preview", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 64000, - "input": 64000, "output": 8192 - } - }, - "exa-research": { - "id": "exa-research", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] }, - "limit": { - "context": 8192, - "input": 8192, - "output": 8192 - } - }, - "llama-3.3-70b-mokume-gane-r1": { - "id": "Llama-3.3-70B-Mokume-Gane-R1", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "glm-4.1v-thinking-flash": { - "id": "glm-4.1v-thinking-flash", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 64000, - "input": 64000, - "output": 8192 - } - }, - "llama-3.3-70b-geneticlemonade-unleashed-v3": { - "id": "Llama-3.3-70B-GeneticLemonade-Unleashed-v3", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "llama-3.3-70b-predatorial-extasy": { - "id": "Llama-3.3-70B-Predatorial-Extasy", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "glm-4-airx": { - "id": "glm-4-airx", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8000, - "input": 8000, - "output": 4096 - } - }, - "doubao-seed-1-6-thinking-250615": { - "id": "doubao-seed-1-6-thinking-250615", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "input": 256000, - "output": 16384 - } - }, - "claude-3-7-sonnet-thinking": { - "id": "claude-3-7-sonnet-thinking", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 16000 - } - }, - "glm-4.5-air-derestricted-steam": { - "id": "GLM-4.5-Air-Derestricted-Steam", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 220600, - "input": 220600, - "output": 65536 - } - }, - "ernie-5.0-thinking-preview": { - "id": "ernie-5.0-thinking-preview", - "reasoning": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 16384 - } - }, - "claude-opus-4-thinking:1024": { - "id": "claude-opus-4-thinking:1024", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 32000 - } - }, - "llama-3.3-70b-strawberrylemonade-v1.2": { - "id": "Llama-3.3-70B-Strawberrylemonade-v1.2", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "llama-3.3-70b-vulpecula-r1": { - "id": "Llama-3.3-70B-Vulpecula-R1", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "glm-4.6-derestricted-v5": { - "id": "GLM-4.6-Derestricted-v5", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 131072, - "output": 8192 - } - }, - "llama-3.3-70b-cirrus-x1": { - "id": "Llama-3.3-70B-Cirrus-x1", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "llama-3.3-70b-arliai-rpmax-v2": { - "id": "Llama-3.3-70B-ArliAI-RPMax-v2", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "doubao-seed-code-preview-latest": { - "id": "doubao-seed-code-preview-latest", - "reasoning": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "input": 256000, - "output": 16384 - } - }, - "llama-3.3+(3.1v3.3)-70b-new-dawn-v1.1": { - "id": "Llama-3.3+(3.1v3.3)-70B-New-Dawn-v1.1", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "qwen3-vl-235b-a22b-thinking": { - "id": "qwen3-vl-235b-a22b-thinking", - "reasoning": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 32768 - } - }, - "claude-sonnet-4-thinking": { - "id": "claude-sonnet-4-thinking", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "input": 1000000, - "output": 64000 - } - }, - "qwen2.5-32b-eva-v0.2": { - "id": "Qwen2.5-32B-EVA-v0.2", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 24576, - "input": 24576, - "output": 8192 - } - }, - "llama-3.3-70b-cu-mai-r1": { - "id": "Llama-3.3-70B-Cu-Mai-R1", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "hidream": { - "id": "hidream", - "reasoning": false, "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "image" - ] - }, - "limit": { - "context": 0, - "output": 0 - } - }, - "auto-model": { - "id": "auto-model", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "input": 1000000, - "output": 1000000 - } - }, - "jamba-mini-1.7": { - "id": "jamba-mini-1.7", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "input": 256000, - "output": 4096 - } - }, - "doubao-seed-2-0-pro-260215": { - "id": "doubao-seed-2-0-pro-260215", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "input": 256000, - "output": 128000 - } - }, - "llama-3.3-70b-nova": { - "id": "Llama-3.3-70B-Nova", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } + "family": "qwen" }, "gemini-2.5-flash-preview-09-2025-thinking": { "id": "gemini-2.5-flash-preview-09-2025-thinking", @@ -35313,115 +4232,6 @@ "output": 65536 } }, - "llama-3.3-70b-sapphira-0.2": { - "id": "Llama-3.3-70B-Sapphira-0.2", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "auto-model-standard": { - "id": "auto-model-standard", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "input": 1000000, - "output": 1000000 - } - }, - "grok-3-mini-fast-beta": { - "id": "grok-3-mini-fast-beta", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 131072, - "output": 131072 - } - }, - "meta-llama-3-1-8b-instruct-fp8": { - "id": "Meta-Llama-3-1-8B-Instruct-FP8", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 16384 - } - }, - "step-3": { - "id": "step-3", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "input": 65536, - "output": 8192 - } - }, - "universal-summarizer": { - "id": "universal-summarizer", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 32768 - } - }, "deepclaude": { "id": "deepclaude", "reasoning": false, @@ -35441,85 +4251,8 @@ "output": 8192 } }, - "brave-pro": { - "id": "brave-pro", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "input": 8192, - "output": 8192 - } - }, - "claude-3-7-sonnet-reasoner": { - "id": "claude-3-7-sonnet-reasoner", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 8192 - } - }, - "claude-opus-4-thinking:8192": { - "id": "claude-opus-4-thinking:8192", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 32000 - } - }, - "claude-opus-4-thinking:32768": { - "id": "claude-opus-4-thinking:32768", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 32000 - } - }, - "glm-zero-preview": { - "id": "glm-zero-preview", + "ernie-4.5-8k-preview": { + "id": "ernie-4.5-8k-preview", "reasoning": false, "toolCall": false, "modalities": { @@ -35533,12 +4266,30 @@ "limit": { "context": 8000, "input": 8000, - "output": 4096 + "output": 16384 } }, - "azure-gpt-4o-mini": { - "id": "azure-gpt-4o-mini", + "doubao-seed-2-0-mini-260215": { + "id": "doubao-seed-2-0-mini-260215", "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 32000 + } + }, + "gemini-3-pro-preview-thinking": { + "id": "gemini-3-pro-preview-thinking", + "reasoning": true, "toolCall": true, "modalities": { "input": [ @@ -35549,14 +4300,72 @@ "text" ] }, + "limit": { + "context": 1048756, + "input": 1048756, + "output": 65536 + } + }, + "llama-3.3-70b-geneticlemonade-opus": { + "id": "Llama-3.3-70B-GeneticLemonade-Opus", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "v0-1.5-lg": { + "id": "v0-1.5-lg", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 512000, + "input": 1000000, + "output": 32000 + }, + "family": "v0", + "temperature": true + }, + "ernie-4.5-turbo-128k": { + "id": "ernie-4.5-turbo-128k", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, "limit": { "context": 128000, "input": 128000, "output": 16384 } }, - "deepseek-math-v2": { - "id": "deepseek-math-v2", + "kat-coder-pro-v1": { + "id": "KAT-Coder-Pro-V1", "reasoning": false, "toolCall": false, "modalities": { @@ -35568,50 +4377,14 @@ ] }, "limit": { - "context": 128000, - "input": 128000, - "output": 65536 + "context": 256000, + "input": 256000, + "output": 32768 } }, - "glm-4-long": { - "id": "glm-4-long", + "claude-3-5-sonnet-20240620": { + "id": "claude-3-5-sonnet-20240620", "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "input": 1000000, - "output": 4096 - } - }, - "glm-4.5-air-derestricted-iceblink": { - "id": "GLM-4.5-Air-Derestricted-Iceblink", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 131072, - "output": 98304 - } - }, - "claude-opus-4-1-thinking:1024": { - "id": "claude-opus-4-1-thinking:1024", - "reasoning": true, "toolCall": true, "modalities": { "input": [ @@ -35626,45 +4399,10 @@ "limit": { "context": 200000, "input": 200000, - "output": 32000 - } - }, - "qwen3-vl-235b-a22b-instruct-original": { - "id": "qwen3-vl-235b-a22b-instruct-original", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "output": 8192 }, - "limit": { - "context": 32768, - "input": 32768, - "output": 32768 - } - }, - "llama-3.3+(3.1v3.3)-70b-hanami-x1": { - "id": "Llama-3.3+(3.1v3.3)-70B-Hanami-x1", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } + "family": "claude-sonnet", + "temperature": true }, "claude-opus-4-1-thinking:8192": { "id": "claude-opus-4-1-thinking:8192", @@ -35686,8 +4424,26 @@ "output": 32000 } }, - "llama-3.3-70b-damascus-r1": { - "id": "Llama-3.3-70B-Damascus-R1", + "gemini-2.0-flash-exp-image-generation": { + "id": "gemini-2.0-flash-exp-image-generation", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32767, + "input": 32767, + "output": 8192 + } + }, + "llama-3.3-70b-magnum-v4-se": { + "id": "Llama-3.3-70B-Magnum-v4-SE", "reasoning": false, "toolCall": false, "modalities": { @@ -35704,8 +4460,8 @@ "output": 16384 } }, - "gemma-3-27b-arliai-rpmax-v3": { - "id": "Gemma-3-27B-ArliAI-RPMax-v3", + "glm-zero-preview": { + "id": "glm-zero-preview", "reasoning": false, "toolCall": false, "modalities": { @@ -35717,14 +4473,14 @@ ] }, "limit": { - "context": 32768, - "input": 32768, - "output": 16384 + "context": 8000, + "input": 8000, + "output": 4096 } }, - "gemini-2.5-flash-preview-05-20:thinking": { - "id": "gemini-2.5-flash-preview-05-20:thinking", - "reasoning": true, + "study_gpt-chatgpt-4o-latest": { + "id": "study_gpt-chatgpt-4o-latest", + "reasoning": false, "toolCall": false, "modalities": { "input": [ @@ -35735,52 +4491,14 @@ "text" ] }, - "limit": { - "context": 1048000, - "input": 1048000, - "output": 65536 - } - }, - "claude-opus-4-1-thinking:32000": { - "id": "claude-opus-4-1-thinking:32000", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, "limit": { "context": 200000, "input": 200000, - "output": 32000 - } - }, - "sarvan-medium": { - "id": "sarvan-medium", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, "output": 16384 } }, - "llama-3.3-70b-anthrobomination": { - "id": "Llama-3.3-70B-Anthrobomination", + "glm-4-airx": { + "id": "glm-4-airx", "reasoning": false, "toolCall": false, "modalities": { @@ -35792,49 +4510,13 @@ ] }, "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "baichuan4-air": { - "id": "Baichuan4-Air", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 32768 - } - }, - "jamba-mini": { - "id": "jamba-mini", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "input": 256000, + "context": 8000, + "input": 8000, "output": 4096 } }, - "kat-coder-exp-72b-1010": { - "id": "KAT-Coder-Exp-72B-1010", + "step-2-mini": { + "id": "step-2-mini", "reasoning": false, "toolCall": false, "modalities": { @@ -35846,9 +4528,9 @@ ] }, "limit": { - "context": 128000, - "input": 128000, - "output": 32768 + "context": 8000, + "input": 8000, + "output": 4096 } }, "gemini-2.5-flash-preview-04-17:thinking": { @@ -35870,26 +4552,8 @@ "output": 65536 } }, - "brave-research": { - "id": "brave-research", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 16384 - } - }, - "llama-3.3-70b-argunaut-1-sft": { - "id": "Llama-3.3-70B-Argunaut-1-SFT", + "llama-3.3-70b-mokume-gane-r1": { + "id": "Llama-3.3-70B-Mokume-Gane-R1", "reasoning": false, "toolCall": false, "modalities": { @@ -35906,8 +4570,44 @@ "output": 16384 } }, - "claude-opus-4-5-20251101:thinking": { - "id": "claude-opus-4-5-20251101:thinking", + "glm-z1-airx": { + "id": "glm-z1-airx", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "input": 32000, + "output": 16384 + } + }, + "jamba-mini-1.6": { + "id": "jamba-mini-1.6", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 4096 + } + }, + "claude-opus-4-1-thinking": { + "id": "claude-opus-4-1-thinking", "reasoning": true, "toolCall": true, "modalities": { @@ -35944,8 +4644,8 @@ "output": 131072 } }, - "azure-o3-mini": { - "id": "azure-o3-mini", + "llama-3.3-70b-legion-v2.1": { + "id": "Llama-3.3-70B-Legion-V2.1", "reasoning": false, "toolCall": false, "modalities": { @@ -35957,49 +4657,69 @@ ] }, "limit": { - "context": 200000, - "input": 200000, + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "sonar": { + "id": "sonar", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 130000, + "input": 127000, + "output": 16384 + }, + "family": "sonar", + "temperature": true + }, + "z-image-turbo": { + "id": "z-image-turbo", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 0, + "output": 0 + } + }, + "glm-4.5-air-derestricted-iceblink-v2": { + "id": "GLM-4.5-Air-Derestricted-Iceblink-v2", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 158600, + "input": 158600, "output": 65536 } }, - "qwq-32b-arliai-rpr-v1": { - "id": "QwQ-32B-ArliAI-RpR-v1", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 32768 - } - }, - "llama-3.3-70b-forgotten-abomination-v5.0": { - "id": "Llama-3.3-70B-Forgotten-Abomination-v5.0", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 16384 - } - }, - "doubao-seed-2-0-code-preview-260215": { - "id": "doubao-seed-2-0-code-preview-260215", + "jamba-large": { + "id": "jamba-large", "reasoning": false, "toolCall": false, "modalities": { @@ -36013,29 +4733,68 @@ "limit": { "context": 256000, "input": 256000, - "output": 128000 + "output": 4096 } }, - "llama-3.3-70b-mhnnn-x1": { - "id": "Llama-3.3-70B-Mhnnn-x1", + "claude-3-7-sonnet-reasoner": { + "id": "claude-3-7-sonnet-reasoner", "reasoning": false, "toolCall": false, "modalities": { "input": [ - "text" + "text", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 32768, - "input": 32768, + "context": 128000, + "input": 128000, + "output": 8192 + } + }, + "ernie-4.5-turbo-vl-32k": { + "id": "ernie-4.5-turbo-vl-32k", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "input": 32000, "output": 16384 } }, - "hunyuan-t1-latest": { - "id": "hunyuan-t1-latest", + "mistral-nemo-12b-instruct-2407": { + "id": "Mistral-Nemo-12B-Instruct-2407", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 16384 + } + }, + "doubao-seed-1-6-flash-250615": { + "id": "doubao-seed-1-6-flash-250615", "reasoning": false, "toolCall": false, "modalities": { @@ -36052,8 +4811,28 @@ "output": 16384 } }, - "gemma-3-27b-cardprojector-v4": { - "id": "Gemma-3-27B-CardProjector-v4", + "qwq-32b": { + "id": "qwq-32b", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 128000, + "output": 8192 + }, + "family": "qwen", + "temperature": true + }, + "llama-3.3-70b-strawberrylemonade-v1.2": { + "id": "Llama-3.3-70B-Strawberrylemonade-v1.2", "reasoning": false, "toolCall": false, "modalities": { @@ -36070,8 +4849,234 @@ "output": 16384 } }, - "glm-4-flash": { - "id": "glm-4-flash", + "gemini-2.5-flash-preview-04-17": { + "id": "gemini-2.5-flash-preview-04-17", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "input": 1048756, + "output": 65536 + }, + "family": "gemini-flash", + "temperature": true + }, + "ernie-x1-turbo-32k": { + "id": "ernie-x1-turbo-32k", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "input": 32000, + "output": 16384 + } + }, + "deepseek-math-v2": { + "id": "deepseek-math-v2", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 65536 + } + }, + "llama-3.3-70b-electranova-v1.0": { + "id": "Llama-3.3-70B-Electranova-v1.0", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "llama-3.3-70b-arliai-rpmax-v2": { + "id": "Llama-3.3-70B-ArliAI-RPMax-v2", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "qwen-image": { + "id": "qwen-image", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 2000, + "output": 4096 + }, + "family": "qwen" + }, + "llama-3.3-70b-cu-mai-r1": { + "id": "Llama-3.3-70B-Cu-Mai-R1", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "glm-4.5-air-derestricted-iceblink": { + "id": "GLM-4.5-Air-Derestricted-Iceblink", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 98304 + } + }, + "llama-3.3-70b-bigger-body": { + "id": "Llama-3.3-70B-Bigger-Body", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "llama-3.3+(3.1v3.3)-70b-hanami-x1": { + "id": "Llama-3.3+(3.1v3.3)-70B-Hanami-x1", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "hunyuan-turbos-20250226": { + "id": "hunyuan-turbos-20250226", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 24000, + "input": 24000, + "output": 8192 + } + }, + "glm-4.6-derestricted-v5": { + "id": "GLM-4.6-Derestricted-v5", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 8192 + } + }, + "glm-4-plus": { + "id": "glm-4-plus", "reasoning": false, "toolCall": false, "modalities": { @@ -36088,26 +5093,8 @@ "output": 4096 } }, - "learnlm-1.5-pro-experimental": { - "id": "learnlm-1.5-pro-experimental", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32767, - "input": 32767, - "output": 8192 - } - }, - "llama-3.3-70b-dark-ages-v0.1": { - "id": "Llama-3.3-70B-Dark-Ages-v0.1", + "gemma-3-27b-big-tiger-v3": { + "id": "Gemma-3-27B-Big-Tiger-v3", "reasoning": false, "toolCall": false, "modalities": { @@ -36124,8 +5111,8 @@ "output": 16384 } }, - "yi-large": { - "id": "yi-large", + "brave-research": { + "id": "brave-research", "reasoning": false, "toolCall": false, "modalities": { @@ -36137,13 +5124,72 @@ ] }, "limit": { - "context": 32000, - "input": 32000, - "output": 4096 + "context": 16384, + "input": 16384, + "output": 16384 } }, - "exa-answer": { - "id": "exa-answer", + "hidream": { + "id": "hidream", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 0, + "output": 0 + } + }, + "qwen3-max-2026-01-23": { + "id": "qwen3-max-2026-01-23", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "input": 256000, + "output": 65536 + }, + "family": "qwen", + "temperature": true + }, + "gemini-2.5-flash-nothinking": { + "id": "gemini-2.5-flash-nothinking", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048756, + "input": 1048756, + "output": 65536 + } + }, + "exa-research-pro": { + "id": "exa-research-pro", "reasoning": false, "toolCall": false, "modalities": { @@ -36155,9 +5201,48 @@ ] }, "limit": { - "context": 4096, - "input": 4096, - "output": 4096 + "context": 16384, + "input": 16384, + "output": 16384 + } + }, + "grok-3-fast-beta": { + "id": "grok-3-fast-beta", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 131072 + } + }, + "claude-opus-4-5-20251101:thinking": { + "id": "claude-opus-4-5-20251101:thinking", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 32000 } }, "gemini-2.5-pro-exp-03-25": { @@ -36179,6 +5264,1379 @@ "output": 65536 } }, + "claude-3-7-sonnet-thinking": { + "id": "claude-3-7-sonnet-thinking", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 16000 + } + }, + "claude-opus-4-thinking:8192": { + "id": "claude-opus-4-thinking:8192", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 32000 + } + }, + "claude-sonnet-4-thinking:1024": { + "id": "claude-sonnet-4-thinking:1024", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 64000 + } + }, + "llama-3.3-70b-magnum-v4-se-cirrus-x1-slerp": { + "id": "Llama-3.3-70B-Magnum-v4-SE-Cirrus-x1-SLERP", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "step-r1-v-mini": { + "id": "step-r1-v-mini", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 65536 + } + }, + "ernie-x1-32k-preview": { + "id": "ernie-x1-32k-preview", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "input": 32000, + "output": 16384 + } + }, + "llama-3.3-70b-strawberrylemonade-v1.0": { + "id": "Llama-3.3-70B-StrawberryLemonade-v1.0", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "kat-coder-exp-72b-1010": { + "id": "KAT-Coder-Exp-72B-1010", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 32768 + } + }, + "gemini-2.5-pro-preview-03-25": { + "id": "gemini-2.5-pro-preview-03-25", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048756, + "input": 1048756, + "output": 65536 + } + }, + "claude-opus-4-thinking:1024": { + "id": "claude-opus-4-thinking:1024", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 32000 + } + }, + "claude-sonnet-4-20250514": { + "id": "claude-sonnet-4-20250514", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 64000 + }, + "family": "claude-sonnet", + "temperature": true + }, + "llama-3.3-70b-progenitor-v3.3": { + "id": "Llama-3.3-70B-Progenitor-V3.3", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "qwen2.5-32b-eva-v0.2": { + "id": "Qwen2.5-32B-EVA-v0.2", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 24576, + "input": 24576, + "output": 8192 + } + }, + "brave-pro": { + "id": "brave-pro", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "input": 8192, + "output": 8192 + } + }, + "step-2-16k-exp": { + "id": "step-2-16k-exp", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16000, + "input": 16000, + "output": 8192 + } + }, + "llama-3.3-70b-fallen-r1-v1": { + "id": "Llama-3.3-70B-Fallen-R1-v1", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "claude-sonnet-4-thinking": { + "id": "claude-sonnet-4-thinking", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 64000 + } + }, + "doubao-1.5-pro-256k": { + "id": "doubao-1.5-pro-256k", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 16384 + } + }, + "claude-3-7-sonnet-20250219": { + "id": "claude-3-7-sonnet-20250219", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 64000 + }, + "family": "claude-sonnet", + "temperature": true + }, + "learnlm-1.5-pro-experimental": { + "id": "learnlm-1.5-pro-experimental", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32767, + "input": 32767, + "output": 8192 + } + }, + "chroma": { + "id": "chroma", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 0, + "output": 0 + } + }, + "llama-3.3-70b-predatorial-extasy": { + "id": "Llama-3.3-70B-Predatorial-Extasy", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "llama-3.3-70b-aurora-borealis": { + "id": "Llama-3.3-70B-Aurora-Borealis", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "llama-3.3-70b-arliai-rpmax-v3": { + "id": "Llama-3.3-70B-ArliAI-RPMax-v3", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "venice-uncensored": { + "id": "venice-uncensored", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "input": 128000, + "output": 8192 + }, + "family": "venice", + "temperature": true + }, + "step-3": { + "id": "step-3", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "input": 65536, + "output": 8192 + } + }, + "llama-3.3-70b-the-omega-directive-unslop-v2.0": { + "id": "Llama-3.3-70B-The-Omega-Directive-Unslop-v2.0", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "auto-model": { + "id": "auto-model", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 1000000 + } + }, + "claude-opus-4-1-thinking:32768": { + "id": "claude-opus-4-1-thinking:32768", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 32000 + } + }, + "llama-3.3-70b-shakudo": { + "id": "Llama-3.3-70B-Shakudo", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "baichuan4-air": { + "id": "Baichuan4-Air", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 32768 + } + }, + "kimi-thinking-preview": { + "id": "kimi-thinking-preview", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + } + }, + "llama-3.3-70b-mhnnn-x1": { + "id": "Llama-3.3-70B-Mhnnn-x1", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "claude-opus-4-thinking:32768": { + "id": "claude-opus-4-thinking:32768", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 32000 + } + }, + "llama-3.3-70b-argunaut-1-sft": { + "id": "Llama-3.3-70B-Argunaut-1-SFT", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "claude-opus-4-1-thinking:1024": { + "id": "claude-opus-4-1-thinking:1024", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 32000 + } + }, + "gemini-2.5-flash-lite": { + "id": "gemini-2.5-flash-lite", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "input": 1048756, + "output": 65536 + }, + "family": "gemini-flash-lite", + "temperature": true + }, + "phi-4-multimodal-instruct": { + "id": "phi-4-multimodal-instruct", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + } + }, + "doubao-seed-2-0-code-preview-260215": { + "id": "doubao-seed-2-0-code-preview-260215", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 128000 + } + }, + "deepseek-reasoner-cheaper": { + "id": "deepseek-reasoner-cheaper", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 65536 + } + }, + "exa-answer": { + "id": "exa-answer", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "input": 4096, + "output": 4096 + } + }, + "v0-1.0-md": { + "id": "v0-1.0-md", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 200000, + "output": 32000 + }, + "family": "v0", + "temperature": true + }, + "glm-4.1v-thinking-flash": { + "id": "glm-4.1v-thinking-flash", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 64000, + "input": 64000, + "output": 8192 + } + }, + "azure-o1": { + "id": "azure-o1", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 100000 + } + }, + "glm-4.5-air-derestricted": { + "id": "GLM-4.5-Air-Derestricted", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202600, + "input": 202600, + "output": 98304 + } + }, + "azure-o3-mini": { + "id": "azure-o3-mini", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 65536 + } + }, + "llama-3.3-70b-sapphira-0.2": { + "id": "Llama-3.3-70B-Sapphira-0.2", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "llama-3.3-70b-anthrobomination": { + "id": "Llama-3.3-70B-Anthrobomination", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "qwq-32b-arliai-rpr-v1": { + "id": "QwQ-32B-ArliAI-RpR-v1", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 32768 + } + }, + "claude-opus-4-20250514": { + "id": "claude-opus-4-20250514", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 32000 + }, + "family": "claude-opus", + "temperature": true + }, + "yi-lightning": { + "id": "yi-lightning", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 12000, + "input": 12000, + "output": 4096 + } + }, + "llama-3.3-70b-electra-r1": { + "id": "Llama-3.3-70B-Electra-R1", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "llama-3.3-70b-forgotten-abomination-v5.0": { + "id": "Llama-3.3-70B-Forgotten-Abomination-v5.0", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "llama-3.3-70b-cirrus-x1": { + "id": "Llama-3.3-70B-Cirrus-x1", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "grok-3-mini-beta": { + "id": "grok-3-mini-beta", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 131072 + } + }, + "auto-model-standard": { + "id": "auto-model-standard", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 1000000 + } + }, + "v0-1.5-md": { + "id": "v0-1.5-md", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 200000, + "output": 32000 + }, + "family": "v0", + "temperature": true + }, + "kimi-k2-instruct-fast": { + "id": "kimi-k2-instruct-fast", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 16384 + } + }, + "glm-4-long": { + "id": "glm-4-long", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 4096 + } + }, + "jamba-large-1.7": { + "id": "jamba-large-1.7", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 4096 + } + }, + "gemini-2.0-flash-thinking-exp-1219": { + "id": "gemini-2.0-flash-thinking-exp-1219", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32767, + "input": 32767, + "output": 8192 + } + }, + "azure-gpt-4-turbo": { + "id": "azure-gpt-4-turbo", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 4096 + } + }, + "baichuan-m2": { + "id": "Baichuan-M2", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 32768 + } + }, + "qwen-long": { + "id": "qwen-long", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 10000000, + "input": 10000000, + "output": 8192 + }, + "family": "qwen", + "temperature": true + }, + "sonar-reasoning-pro": { + "id": "sonar-reasoning-pro", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 127000, + "output": 16384 + }, + "family": "sonar", + "temperature": true + }, + "gemini-2.5-flash-preview-05-20:thinking": { + "id": "gemini-2.5-flash-preview-05-20:thinking", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048000, + "input": 1048000, + "output": 65536 + } + }, + "glm-4.5-air-derestricted-steam-reextract": { + "id": "GLM-4.5-Air-Derestricted-Steam-ReExtract", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 65536 + } + }, + "llama-3.3-70b-dark-ages-v0.1": { + "id": "Llama-3.3-70B-Dark-Ages-v0.1", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 16384 + } + }, + "baichuan4-turbo": { + "id": "Baichuan4-Turbo", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 32768 + } + }, + "doubao-1.5-vision-pro-32k": { + "id": "doubao-1.5-vision-pro-32k", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "input": 32000, + "output": 8192 + } + }, + "inflection/inflection-3-pi": { + "id": "inflection/inflection-3-pi", + "family": "gpt", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8000, + "input": 8000, + "output": 1024 + }, + "temperature": true + }, + "inflection/inflection-3-productivity": { + "id": "inflection/inflection-3-productivity", + "family": "gpt", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8000, + "input": 8000, + "output": 1024 + }, + "temperature": true + }, + "essentialai/rnj-1-instruct": { + "id": "essentialai/Rnj-1-Instruct", + "family": "rnj", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 128000, + "output": 32768 + }, + "temperature": true + }, "llm360/k2-think": { "id": "LLM360/K2-Think", "family": "kimi-thinking", @@ -36198,501 +6656,6 @@ "output": 32768 } }, - "abacusai/dracarys-72b-instruct": { - "id": "abacusai/Dracarys-72B-Instruct", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 8192 - } - }, - "envoid/llama-3.05-nemotron-tenyxchat-storybreaker-70b": { - "id": "Envoid/Llama-3.05-Nemotron-Tenyxchat-Storybreaker-70B", - "family": "nemotron", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 8192 - } - }, - "envoid/llama-3.05-nt-storybreaker-ministral-70b": { - "id": "Envoid/Llama-3.05-NT-Storybreaker-Ministral-70B", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 8192 - } - }, - "zai-org/glm-5:thinking": { - "id": "zai-org/glm-5:thinking", - "family": "glm", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 128000 - } - }, - "nvidia/llama-3.1-nemotron-70b-instruct-hf": { - "id": "nvidia/Llama-3.1-Nemotron-70B-Instruct-HF", - "family": "nemotron", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 8192 - } - }, - "nvidia/llama-3_3-nemotron-super-49b-v1_5": { - "id": "nvidia/Llama-3_3-Nemotron-Super-49B-v1_5", - "family": "nemotron", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 16384 - } - }, - "doctor-shotgun/ms3.2-24b-magnum-diamond": { - "id": "Doctor-Shotgun/MS3.2-24B-Magnum-Diamond", - "family": "mistral", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 32768 - } - }, - "arcee-ai/trinity-large": { - "id": "arcee-ai/trinity-large", - "family": "trinity", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 131072, - "output": 8192 - } - }, - "meganova-ai/manta-flash-1.0": { - "id": "meganova-ai/manta-flash-1.0", - "family": "nova", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 16384 - } - }, - "meganova-ai/manta-pro-1.0": { - "id": "meganova-ai/manta-pro-1.0", - "family": "nova", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 32768 - } - }, - "meganova-ai/manta-mini-1.0": { - "id": "meganova-ai/manta-mini-1.0", - "family": "nova", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "input": 8192, - "output": 8192 - } - }, - "xiaomi/mimo-v2-flash-original": { - "id": "xiaomi/mimo-v2-flash-original", - "family": "mimo", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "input": 256000, - "output": 32768 - } - }, - "xiaomi/mimo-v2-flash-thinking": { - "id": "xiaomi/mimo-v2-flash-thinking", - "family": "mimo", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "input": 256000, - "output": 32768 - } - }, - "xiaomi/mimo-v2-flash-thinking-original": { - "id": "xiaomi/mimo-v2-flash-thinking-original", - "family": "mimo", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 256000, - "input": 256000, - "output": 32768 - } - }, - "microsoft/mai-ds-r1-fp8": { - "id": "microsoft/MAI-DS-R1-FP8", - "family": "deepseek", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 8192 - } - }, - "failspy/meta-llama-3-70b-instruct-abliterated-v3.5": { - "id": "failspy/Meta-Llama-3-70B-Instruct-abliterated-v3.5", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "input": 8192, - "output": 8192 - } - }, - "featherless-ai/qwerky-72b": { - "id": "featherless-ai/Qwerky-72B", - "family": "qwerky", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "input": 32000, - "output": 8192 - } - }, - "tee/glm-5": { - "id": "TEE/glm-5", - "family": "glm", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 203000, - "input": 203000, - "output": 65535 - } - }, - "tee/deepseek-v3.1": { - "id": "TEE/deepseek-v3.1", - "family": "deepseek", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 164000, - "input": 164000, - "output": 8192 - } - }, - "tee/glm-4.7-flash": { - "id": "TEE/glm-4.7-flash", - "family": "glm-flash", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 203000, - "input": 203000, - "output": 65535 - } - }, - "tee/qwen3-coder": { - "id": "TEE/qwen3-coder", - "family": "qwen", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 32768 - } - }, - "tee/glm-4.6": { - "id": "TEE/glm-4.6", - "family": "glm", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 203000, - "input": 203000, - "output": 65535 - } - }, - "tee/deepseek-r1-0528": { - "id": "TEE/deepseek-r1-0528", - "family": "deepseek", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 65536 - } - }, - "tee/minimax-m2.1": { - "id": "TEE/minimax-m2.1", - "family": "minimax", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 131072 - } - }, - "tee/qwen3.5-397b-a17b": { - "id": "TEE/qwen3.5-397b-a17b", - "family": "qwen", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 258048, - "input": 258048, - "output": 65536 - } - }, - "tee/gpt-oss-120b": { - "id": "TEE/gpt-oss-120b", - "family": "gpt-oss", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 131072, - "output": 16384 - } - }, "tee/kimi-k2.5": { "id": "TEE/kimi-k2.5", "family": "kimi", @@ -36712,8 +6675,27 @@ "output": 65535 } }, - "tee/qwen3-30b-a3b-instruct-2507": { - "id": "TEE/qwen3-30b-a3b-instruct-2507", + "tee/glm-4.7": { + "id": "TEE/glm-4.7", + "family": "glm", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "input": 131000, + "output": 65535 + } + }, + "tee/qwen3.5-397b-a17b": { + "id": "TEE/qwen3.5-397b-a17b", "family": "qwen", "reasoning": false, "toolCall": false, @@ -36726,15 +6708,15 @@ ] }, "limit": { - "context": 262000, - "input": 262000, - "output": 32768 + "context": 258048, + "input": 258048, + "output": 65536 } }, - "tee/kimi-k2.5-thinking": { - "id": "TEE/kimi-k2.5-thinking", - "family": "kimi-thinking", - "reasoning": true, + "tee/glm-5": { + "id": "TEE/glm-5", + "family": "glm", + "reasoning": false, "toolCall": false, "modalities": { "input": [ @@ -36745,8 +6727,8 @@ ] }, "limit": { - "context": 128000, - "input": 128000, + "context": 203000, + "input": 203000, "output": 65535 } }, @@ -36770,8 +6752,46 @@ "output": 8192 } }, - "tee/deepseek-v3.2": { - "id": "TEE/deepseek-v3.2", + "tee/minimax-m2.1": { + "id": "TEE/minimax-m2.1", + "family": "minimax", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 131072 + } + }, + "tee/qwen3-30b-a3b-instruct-2507": { + "id": "TEE/qwen3-30b-a3b-instruct-2507", + "family": "qwen", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "input": 262000, + "output": 32768 + } + }, + "tee/deepseek-v3.1": { + "id": "TEE/deepseek-v3.1", "family": "deepseek", "reasoning": false, "toolCall": false, @@ -36786,45 +6806,7 @@ "limit": { "context": 164000, "input": 164000, - "output": 65536 - } - }, - "tee/glm-4.7": { - "id": "TEE/glm-4.7", - "family": "glm", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "input": 131000, - "output": 65535 - } - }, - "tee/kimi-k2-thinking": { - "id": "TEE/kimi-k2-thinking", - "family": "kimi-thinking", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 65535 + "output": 8192 } }, "tee/llama3-3-70b": { @@ -36846,6 +6828,44 @@ "output": 16384 } }, + "tee/glm-4.6": { + "id": "TEE/glm-4.6", + "family": "glm", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 203000, + "input": 203000, + "output": 65535 + } + }, + "tee/kimi-k2.5-thinking": { + "id": "TEE/kimi-k2.5-thinking", + "family": "kimi-thinking", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 65535 + } + }, "tee/gemma-3-27b-it": { "id": "TEE/gemma-3-27b-it", "family": "gemma", @@ -36865,6 +6885,25 @@ "output": 8192 } }, + "tee/deepseek-v3.2": { + "id": "TEE/deepseek-v3.2", + "family": "deepseek", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 164000, + "input": 164000, + "output": 65536 + } + }, "tee/gpt-oss-20b": { "id": "TEE/gpt-oss-20b", "family": "gpt-oss", @@ -36884,8 +6923,103 @@ "output": 8192 } }, - "anthracite-org/magnum-v2-72b": { - "id": "anthracite-org/magnum-v2-72b", + "tee/qwen3-coder": { + "id": "TEE/qwen3-coder", + "family": "qwen", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 32768 + } + }, + "tee/glm-4.7-flash": { + "id": "TEE/glm-4.7-flash", + "family": "glm-flash", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 203000, + "input": 203000, + "output": 65535 + } + }, + "tee/gpt-oss-120b": { + "id": "TEE/gpt-oss-120b", + "family": "gpt-oss", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 16384 + } + }, + "tee/deepseek-r1-0528": { + "id": "TEE/deepseek-r1-0528", + "family": "deepseek", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 65536 + } + }, + "tee/kimi-k2-thinking": { + "id": "TEE/kimi-k2-thinking", + "family": "kimi-thinking", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 65535 + } + }, + "cruciblelab/l3.3-70b-loki-v2.0": { + "id": "CrucibleLab/L3.3-70B-Loki-V2.0", "family": "llama", "reasoning": false, "toolCall": false, @@ -36900,6 +7034,470 @@ "limit": { "context": 16384, "input": 16384, + "output": 16384 + } + }, + "deepseek/deepseek-v3.2:thinking": { + "id": "deepseek/deepseek-v3.2:thinking", + "family": "deepseek", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163000, + "input": 163000, + "output": 65536 + } + }, + "deepseek/deepseek-prover-v2-671b": { + "id": "deepseek/deepseek-prover-v2-671b", + "family": "deepseek", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 160000, + "input": 160000, + "output": 160000 + }, + "temperature": true + }, + "deepseek/deepseek-v3.2-speciale": { + "id": "deepseek/deepseek-v3.2-speciale", + "family": "deepseek", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "input": 163000, + "output": 163840 + }, + "temperature": true + }, + "deepseek/deepseek-v3.2": { + "id": "deepseek/deepseek-v3.2", + "family": "deepseek", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163842, + "input": 163000, + "output": 8000 + }, + "temperature": true + }, + "doctor-shotgun/ms3.2-24b-magnum-diamond": { + "id": "Doctor-Shotgun/MS3.2-24B-Magnum-Diamond", + "family": "mistral", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 32768 + } + }, + "neversleep/llama-3-lumimaid-70b-v0.1": { + "id": "NeverSleep/Llama-3-Lumimaid-70B-v0.1", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 8192 + } + }, + "neversleep/lumimaid-v0.2-70b": { + "id": "NeverSleep/Lumimaid-v0.2-70B", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 8192 + } + }, + "steelskull/l3.3-cu-mai-r1-70b": { + "id": "Steelskull/L3.3-Cu-Mai-R1-70b", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 16384 + } + }, + "steelskull/l3.3-nevoria-r1-70b": { + "id": "Steelskull/L3.3-Nevoria-R1-70b", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 16384 + } + }, + "steelskull/l3.3-ms-evayale-70b": { + "id": "Steelskull/L3.3-MS-Evayale-70B", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 16384 + } + }, + "steelskull/l3.3-electra-r1-70b": { + "id": "Steelskull/L3.3-Electra-R1-70b", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 16384 + } + }, + "steelskull/l3.3-ms-nevoria-70b": { + "id": "Steelskull/L3.3-MS-Nevoria-70b", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 16384 + } + }, + "steelskull/l3.3-ms-evalebis-70b": { + "id": "Steelskull/L3.3-MS-Evalebis-70b", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 16384 + } + }, + "miromind-ai/mirothinker-v1.5-235b": { + "id": "miromind-ai/MiroThinker-v1.5-235B", + "family": "gpt", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "input": 32768, + "output": 8192 + }, + "temperature": true + }, + "pamanseau/openreasoning-nemotron-32b": { + "id": "pamanseau/OpenReasoning-Nemotron-32B", + "family": "nemotron", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 65536 + } + }, + "arcee-ai/trinity-mini": { + "id": "arcee-ai/trinity-mini", + "family": "trinity", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 131072 + }, + "temperature": true + }, + "arcee-ai/trinity-large": { + "id": "arcee-ai/trinity-large", + "family": "trinity", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 8192 + } + }, + "cognitivecomputations/dolphin-2.9.2-qwen2-72b": { + "id": "cognitivecomputations/dolphin-2.9.2-qwen2-72b", + "family": "qwen", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "input": 8192, + "output": 4096 + } + }, + "deepcogito/cogito-v1-preview-qwen-32b": { + "id": "deepcogito/cogito-v1-preview-qwen-32B", + "family": "qwen", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 32768 + } + }, + "deepcogito/cogito-v2.1-671b": { + "id": "deepcogito/cogito-v2.1-671b", + "family": "cogito", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 32768 + }, + "temperature": true + }, + "salesforce/llama-xlam-2-70b-fc-r": { + "id": "Salesforce/Llama-xLAM-2-70b-fc-r", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + } + }, + "nousresearch 2/hermes-4-405b:thinking": { + "id": "NousResearch 2/hermes-4-405b:thinking", + "family": "nousresearch", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 8192 + } + }, + "nousresearch 2/deephermes-3-mistral-24b-preview": { + "id": "NousResearch 2/DeepHermes-3-Mistral-24B-Preview", + "family": "nousresearch", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 32768 + } + }, + "nousresearch 2/hermes-4-70b:thinking": { + "id": "NousResearch 2/Hermes-4-70B:thinking", + "family": "nousresearch", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, "output": 8192 } }, @@ -36941,25 +7539,6 @@ "output": 8192 } }, - "nousresearch 2/deephermes-3-mistral-24b-preview": { - "id": "NousResearch 2/DeepHermes-3-Mistral-24B-Preview", - "family": "nousresearch", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 32768 - } - }, "nousresearch 2/hermes-4-70b": { "id": "NousResearch 2/hermes-4-70b", "family": "nousresearch", @@ -36979,48 +7558,10 @@ "output": 8192 } }, - "nousresearch 2/hermes-4-405b:thinking": { - "id": "NousResearch 2/hermes-4-405b:thinking", - "family": "nousresearch", + "soob3123/veiled-calla-12b": { + "id": "soob3123/Veiled-Calla-12B", + "family": "llama", "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 8192 - } - }, - "nousresearch 2/hermes-4-70b:thinking": { - "id": "NousResearch 2/Hermes-4-70B:thinking", - "family": "nousresearch", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 8192 - } - }, - "pamanseau/openreasoning-nemotron-32b": { - "id": "pamanseau/OpenReasoning-Nemotron-32B", - "family": "nemotron", - "reasoning": true, "toolCall": false, "modalities": { "input": [ @@ -37033,31 +7574,12 @@ "limit": { "context": 32768, "input": 32768, - "output": 65536 + "output": 8192 } }, - "deepseek-ai/deepseek-v3.2-exp-thinking": { - "id": "deepseek-ai/deepseek-v3.2-exp-thinking", - "family": "deepseek-thinking", - "reasoning": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "input": 163840, - "output": 65536 - } - }, - "deepseek-ai/deepseek-v3.1:thinking": { - "id": "deepseek-ai/DeepSeek-V3.1:thinking", - "family": "deepseek-thinking", + "soob3123/grayline-qwen3-8b": { + "id": "soob3123/GrayLine-Qwen3-8B", + "family": "qwen", "reasoning": false, "toolCall": false, "modalities": { @@ -37069,14 +7591,227 @@ ] }, "limit": { - "context": 128000, - "input": 128000, - "output": 65536 + "context": 16384, + "input": 16384, + "output": 32768 } }, - "deepseek-ai/deepseek-v3.1-terminus:thinking": { - "id": "deepseek-ai/DeepSeek-V3.1-Terminus:thinking", - "family": "deepseek-thinking", + "soob3123/amoral-gemma3-27b-v2": { + "id": "soob3123/amoral-gemma3-27B-v2", + "family": "gemma", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 8192 + } + }, + "nex-agi/deepseek-v3.1-nex-n1": { + "id": "nex-agi/DeepSeek-V3.1-Nex-N1", + "family": "deepseek", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "input": 128000, + "output": 131000 + }, + "temperature": true + }, + "envoid/llama-3.05-nt-storybreaker-ministral-70b": { + "id": "Envoid/Llama-3.05-NT-Storybreaker-Ministral-70B", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 8192 + } + }, + "envoid/llama-3.05-nemotron-tenyxchat-storybreaker-70b": { + "id": "Envoid/Llama-3.05-Nemotron-Tenyxchat-Storybreaker-70B", + "family": "nemotron", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 8192 + } + }, + "anthracite-org/magnum-v4-72b": { + "id": "anthracite-org/magnum-v4-72b", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 2048 + }, + "temperature": true + }, + "anthracite-org/magnum-v2-72b": { + "id": "anthracite-org/magnum-v2-72b", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 8192 + } + }, + "readyart/ms3.2-the-omega-directive-24b-unslop-v2.0": { + "id": "ReadyArt/MS3.2-The-Omega-Directive-24B-Unslop-v2.0", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 32768 + } + }, + "readyart/the-omega-abomination-l-70b-v1.0": { + "id": "ReadyArt/The-Omega-Abomination-L-70B-v1.0", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 16384 + } + }, + "undi95/remm-slerp-l2-13b": { + "id": "undi95/remm-slerp-l2-13b", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 6144, + "input": 6144, + "output": 4096 + }, + "temperature": true + }, + "marinaraspaghetti/nemomix-unleashed-12b": { + "id": "MarinaraSpaghetti/NemoMix-Unleashed-12B", + "family": "mistral-nemo", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 8192 + } + }, + "allenai/molmo-2-8b": { + "id": "allenai/molmo-2-8b", + "family": "allenai", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 36864, + "input": 36864, + "output": 36864 + } + }, + "allenai/olmo-3.1-32b-instruct": { + "id": "allenai/olmo-3.1-32b-instruct", + "family": "allenai", "reasoning": false, "toolCall": true, "modalities": { @@ -37088,9 +7823,244 @@ ] }, "limit": { - "context": 128000, + "context": 65536, + "input": 65536, + "output": 32768 + }, + "temperature": true + }, + "allenai/olmo-3.1-32b-think": { + "id": "allenai/olmo-3.1-32b-think", + "family": "allenai", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "input": 65536, + "output": 8192 + } + }, + "allenai/olmo-3-32b-think": { + "id": "allenai/olmo-3-32b-think", + "family": "allenai", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, "input": 128000, "output": 65536 + }, + "temperature": true + }, + "stepfun-ai/step-3.5-flash:thinking": { + "id": "stepfun-ai/step-3.5-flash:thinking", + "family": "step", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 256000 + } + }, + "stepfun-ai/step-3.5-flash": { + "id": "stepfun-ai/Step-3.5-Flash", + "family": "step", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "input": 256000, + "output": 262000 + }, + "temperature": true + }, + "zai-org/glm-4.7": { + "id": "zai-org/GLM-4.7", + "family": "glm", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 200000, + "output": 8192 + }, + "temperature": true + }, + "zai-org/glm-5": { + "id": "zai-org/GLM-5", + "family": "glm", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 205000, + "input": 200000, + "output": 205000 + }, + "temperature": true + }, + "zai-org/glm-5.1": { + "id": "zai-org/glm-5.1", + "family": "glm", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202000, + "input": 200000, + "output": 202000 + }, + "temperature": true + }, + "zai-org/glm-5.1:thinking": { + "id": "zai-org/glm-5.1:thinking", + "family": "glm", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 131072 + } + }, + "zai-org/glm-5:thinking": { + "id": "zai-org/glm-5:thinking", + "family": "glm", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 128000 + } + }, + "zai-org/glm-4.7-flash": { + "id": "zai-org/GLM-4.7-Flash", + "family": "glm-flash", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "input": 200000, + "output": 16384 + }, + "temperature": true + }, + "featherless-ai/qwerky-72b": { + "id": "featherless-ai/Qwerky-72B", + "family": "qwerky", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "input": 32000, + "output": 8192 + } + }, + "mlabonne/neuraldaredevil-8b-abliterated": { + "id": "mlabonne/NeuralDaredevil-8B-abliterated", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "input": 8192, + "output": 8192 } }, "raifle/sorcererlm-8x22b": { @@ -37113,8 +8083,371 @@ "output": 8192 } }, - "mlabonne/neuraldaredevil-8b-abliterated": { - "id": "mlabonne/NeuralDaredevil-8B-abliterated", + "mistralai/mixtral-8x7b-instruct-v0.1": { + "id": "mistralai/mixtral-8x7b-instruct-v0.1", + "family": "mixtral", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 32768 + } + }, + "mistralai/mistral-saba": { + "id": "mistralai/mistral-saba", + "family": "mistral", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32000, + "output": 32768 + }, + "temperature": true + }, + "mistralai/mistral-large-3-675b-instruct-2512": { + "id": "mistralai/mistral-large-3-675b-instruct-2512", + "family": "mistral-large", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "input": 262144, + "output": 262144 + }, + "temperature": true + }, + "mistralai/devstral-2-123b-instruct-2512": { + "id": "mistralai/devstral-2-123b-instruct-2512", + "family": "devstral", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "input": 262144, + "output": 262144 + }, + "temperature": true + }, + "mistralai/codestral-2508": { + "id": "mistralai/codestral-2508", + "family": "codestral", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 51200 + }, + "temperature": true + }, + "mistralai/ministral-14b-instruct-2512": { + "id": "mistralai/ministral-14b-instruct-2512", + "family": "ministral", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "input": 262144, + "output": 262144 + }, + "temperature": true + }, + "mistralai/mistral-tiny": { + "id": "mistralai/mistral-tiny", + "family": "mistral", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "input": 32000, + "output": 8192 + } + }, + "mistralai/ministral-8b-2512": { + "id": "mistralai/ministral-8b-2512", + "family": "ministral", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "input": 262144, + "output": 32768 + }, + "temperature": true + }, + "mistralai/mixtral-8x22b-instruct-v0.1": { + "id": "mistralai/mixtral-8x22b-instruct-v0.1", + "family": "mixtral", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "input": 65536, + "output": 32768 + } + }, + "mistralai/mistral-medium-3.1": { + "id": "mistralai/mistral-medium-3.1", + "family": "mistral-medium", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 26215 + }, + "temperature": true + }, + "mistralai/ministral-3b-2512": { + "id": "mistralai/ministral-3b-2512", + "family": "ministral", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 32768 + }, + "temperature": true + }, + "mistralai/mistral-nemo-instruct-2407": { + "id": "mistralai/Mistral-Nemo-Instruct-2407", + "family": "mistral", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 16384, + "output": 65536 + }, + "temperature": true + }, + "mistralai/mistral-medium-3": { + "id": "mistralai/mistral-medium-3", + "family": "mistral-medium", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 26215 + }, + "temperature": true + }, + "mistralai/mistral-7b-instruct": { + "id": "mistralai/mistral-7b-instruct", + "family": "mistral", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 8192 + } + }, + "mistralai/devstral-small-2505": { + "id": "mistralai/Devstral-Small-2505", + "family": "devstral", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 32768, + "output": 4096 + }, + "temperature": true + }, + "mistralai/mistral-small-creative": { + "id": "mistralai/mistral-small-creative", + "family": "mistral-small", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 32768 + } + }, + "mistralai/mistral-large": { + "id": "mistralai/mistral-large", + "family": "mistral-large", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 25600 + }, + "temperature": true + }, + "mistralai/ministral-14b-2512": { + "id": "mistralai/ministral-14b-2512", + "family": "ministral", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "input": 262144, + "output": 52429 + }, + "temperature": true + }, + "shisa-ai/shisa-v2.1-llama3.3-70b": { + "id": "shisa-ai/shisa-v2.1-llama3.3-70b", "family": "llama", "reasoning": false, "toolCall": false, @@ -37126,12 +8459,2423 @@ "text" ] }, + "limit": { + "context": 32768, + "input": 32768, + "output": 4096 + } + }, + "shisa-ai/shisa-v2-llama3.3-70b": { + "id": "shisa-ai/shisa-v2-llama3.3-70b", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + } + }, + "meta-llama/llama-3.3-70b-instruct": { + "id": "meta-llama/Llama-3.3-70B-Instruct", + "family": "llama", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 120000, + "output": 8192 + }, + "temperature": true + }, + "meta-llama/llama-4-scout": { + "id": "meta-llama/llama-4-scout", + "family": "llama", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 327680, + "input": 328000, + "output": 16384 + }, + "temperature": true + }, + "meta-llama/llama-4-maverick": { + "id": "meta-llama/llama-4-maverick", + "family": "llama", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "input": 1048576, + "output": 16384 + }, + "temperature": true + }, + "meta-llama/llama-3.2-90b-vision-instruct": { + "id": "meta-llama/Llama-3.2-90B-Vision-Instruct", + "family": "llama", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16000, + "input": 131072, + "output": 4096 + }, + "temperature": true + }, + "meta-llama/llama-3.2-3b-instruct": { + "id": "meta-llama/llama-3.2-3b-instruct", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 80000, + "input": 131072, + "output": 16384 + }, + "temperature": true + }, + "meta-llama/llama-3.1-8b-instruct": { + "id": "meta-llama/Llama-3.1-8B-Instruct", + "family": "llama", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 8000 + }, + "temperature": true + }, + "galrionsoftworks/mn-loosecannon-12b-v1": { + "id": "GalrionSoftworks/MN-LooseCannon-12B-v1", + "family": "mistral-nemo", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 8192 + } + }, + "baseten/kimi-k2-instruct-fp4": { + "id": "baseten/Kimi-K2-Instruct-FP4", + "family": "kimi", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 131072 + } + }, + "gryphe/mythomax-l2-13b": { + "id": "gryphe/mythomax-l2-13b", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "input": 4000, + "output": 4096 + }, + "temperature": true + }, + "x-ai/grok-4-fast:thinking": { + "id": "x-ai/grok-4-fast:thinking", + "family": "grok", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "input": 2000000, + "output": 131072 + } + }, + "x-ai/grok-4-07-09": { + "id": "x-ai/grok-4-07-09", + "family": "grok", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 131072 + } + }, + "x-ai/grok-4-fast": { + "id": "x-ai/grok-4-fast", + "family": "grok", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "input": 2000000, + "output": 30000 + }, + "temperature": true + }, + "x-ai/grok-code-fast-1": { + "id": "x-ai/grok-code-fast-1", + "family": "grok", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 10000 + }, + "temperature": true + }, + "x-ai/grok-4.1-fast": { + "id": "x-ai/grok-4.1-fast", + "family": "grok", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "input": 2000000, + "output": 30000 + }, + "temperature": true + }, + "x-ai/grok-4.1-fast-reasoning": { + "id": "x-ai/grok-4.1-fast-reasoning", + "family": "grok", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 20000000, + "input": 2000000, + "output": 2000000 + }, + "temperature": true + }, + "tencent/hunyuan-mt-7b": { + "id": "tencent/Hunyuan-MT-7B", + "family": "hunyuan", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 33000, + "input": 8192, + "output": 33000 + }, + "temperature": true + }, + "microsoft/wizardlm-2-8x22b": { + "id": "microsoft/wizardlm-2-8x22b", + "family": "gpt", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65535, + "input": 65536, + "output": 8000 + }, + "temperature": true + }, + "microsoft/mai-ds-r1-fp8": { + "id": "microsoft/MAI-DS-R1-FP8", + "family": "deepseek", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 8192 + } + }, + "cohere/command-r": { + "id": "cohere/command-r", + "family": "command-r", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 4096 + } + }, + "cohere/command-r-plus-08-2024": { + "id": "cohere/command-r-plus-08-2024", + "family": "command-r", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 4000 + }, + "temperature": true + }, + "chutesai/mistral-small-3.2-24b-instruct-2506": { + "id": "chutesai/Mistral-Small-3.2-24B-Instruct-2506", + "family": "chutesai", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 128000, + "output": 131072 + }, + "temperature": true + }, + "nvidia/llama-3.1-nemotron-ultra-253b-v1": { + "id": "nvidia/llama-3.1-nemotron-ultra-253b-v1", + "family": "llama", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 128000, + "output": 8192 + }, + "temperature": true + }, + "nvidia/nemotron-3-nano-30b-a3b": { + "id": "nvidia/nemotron-3-nano-30b-a3b", + "family": "nemotron", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "input": 256000, + "output": 262144 + }, + "temperature": true + }, + "nvidia/nvidia-nemotron-nano-9b-v2": { + "id": "nvidia/nvidia-nemotron-nano-9b-v2", + "family": "nemotron", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 128000, + "output": 131072 + }, + "temperature": true + }, + "nvidia/llama-3.1-nemotron-70b-instruct-hf": { + "id": "nvidia/Llama-3.1-Nemotron-70B-Instruct-HF", + "family": "nemotron", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 8192 + } + }, + "nvidia/llama-3.3-nemotron-super-49b-v1": { + "id": "nvidia/llama-3.3-nemotron-super-49b-v1", + "family": "nemotron", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 4096 + }, + "temperature": true + }, + "nvidia/llama-3_3-nemotron-super-49b-v1_5": { + "id": "nvidia/Llama-3_3-Nemotron-Super-49B-v1_5", + "family": "nemotron", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + } + }, + "thedrummer 2/anubis-70b-v1": { + "id": "TheDrummer 2/Anubis-70B-v1", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "input": 65536, + "output": 16384 + } + }, + "thedrummer 2/cydonia-24b-v4.3": { + "id": "TheDrummer 2/Cydonia-24B-v4.3", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 32768 + } + }, + "thedrummer 2/magidonia-24b-v4.3": { + "id": "TheDrummer 2/Magidonia-24B-v4.3", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 32768 + } + }, + "thedrummer 2/cydonia-24b-v4": { + "id": "TheDrummer 2/Cydonia-24B-v4", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 32768 + } + }, + "thedrummer 2/anubis-70b-v1.1": { + "id": "TheDrummer 2/Anubis-70B-v1.1", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 16384 + } + }, + "thedrummer 2/rocinante-12b-v1.1": { + "id": "TheDrummer 2/Rocinante-12B-v1.1", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 8192 + } + }, + "thedrummer 2/cydonia-24b-v4.1": { + "id": "TheDrummer 2/Cydonia-24B-v4.1", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 32768 + } + }, + "thedrummer 2/unslopnemo-12b-v4.1": { + "id": "TheDrummer 2/UnslopNemo-12B-v4.1", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 8192 + } + }, + "thedrummer 2/cydonia-24b-v2": { + "id": "TheDrummer 2/Cydonia-24B-v2", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 32768 + } + }, + "thedrummer 2/skyfall-36b-v2": { + "id": "TheDrummer 2/skyfall-36b-v2", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 64000, + "input": 64000, + "output": 32768 + } + }, + "deepseek-ai/deepseek-v3.1:thinking": { + "id": "deepseek-ai/DeepSeek-V3.1:thinking", + "family": "deepseek-thinking", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 65536 + } + }, + "deepseek-ai/deepseek-v3.1": { + "id": "deepseek-ai/DeepSeek-V3.1", + "family": "deepseek", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 164000, + "input": 128000, + "output": 164000 + }, + "temperature": true + }, + "deepseek-ai/deepseek-v3.1-terminus:thinking": { + "id": "deepseek-ai/DeepSeek-V3.1-Terminus:thinking", + "family": "deepseek-thinking", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 65536 + } + }, + "deepseek-ai/deepseek-v3.2-exp-thinking": { + "id": "deepseek-ai/deepseek-v3.2-exp-thinking", + "family": "deepseek-thinking", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "input": 163840, + "output": 65536 + } + }, + "deepseek-ai/deepseek-v3.2-exp": { + "id": "deepseek-ai/DeepSeek-V3.2-Exp", + "family": "deepseek", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 164000, + "input": 163840, + "output": 164000 + }, + "temperature": true + }, + "deepseek-ai/deepseek-r1-0528": { + "id": "deepseek-ai/deepseek-r1-0528", + "family": "deepseek-thinking", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 120000, + "output": 4096 + }, + "temperature": true + }, + "deepseek-ai/deepseek-v3.1-terminus": { + "id": "deepseek-ai/DeepSeek-V3.1-Terminus", + "family": "deepseek", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 164000, + "input": 128000, + "output": 164000 + }, + "temperature": true + }, + "openai/gpt-5.1-codex-max": { + "id": "openai/gpt-5.1-codex-max", + "family": "gpt", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + }, + "temperature": true + }, + "openai/gpt-5.2-chat": { + "id": "openai/gpt-5.2-chat", + "family": "gpt", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 111616, + "output": 16384 + }, + "temperature": true + }, + "openai/gpt-4o-mini-search-preview": { + "id": "openai/gpt-4o-mini-search-preview", + "family": "gpt-mini", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 111616, + "output": 16384 + }, + "temperature": true + }, + "openai/chatgpt-4o-latest": { + "id": "openai/chatgpt-4o-latest", + "family": "gpt", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 8192 + }, + "temperature": false + }, + "openai/gpt-5.2-pro": { + "id": "openai/gpt-5.2-pro", + "family": "gpt", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + }, + "temperature": true + }, + "openai/gpt-5-mini": { + "id": "openai/gpt-5-mini", + "family": "gpt-mini", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + }, + "temperature": true + }, + "openai/gpt-5-nano": { + "id": "openai/gpt-5-nano", + "family": "gpt-nano", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + }, + "temperature": true + }, + "openai/gpt-4-turbo": { + "id": "openai/gpt-4-turbo", + "family": "gpt", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 4096 + }, + "temperature": true + }, + "openai/gpt-5.2": { + "id": "openai/gpt-5.2", + "family": "gpt", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + }, + "temperature": true + }, + "openai/o3-mini-high": { + "id": "openai/o3-mini-high", + "family": "o-mini", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 100000 + }, + "temperature": false + }, + "openai/gpt-4o-mini": { + "id": "openai/gpt-4o-mini", + "family": "gpt-mini", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + }, + "temperature": true + }, + "openai/o4-mini-deep-research": { + "id": "openai/o4-mini-deep-research", + "family": "o-mini", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 100000 + }, + "temperature": false + }, + "openai/gpt-5.1-chat": { + "id": "openai/gpt-5.1-chat", + "family": "gpt-codex", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 400000, + "output": 16384 + }, + "temperature": true + }, + "openai/o4-mini": { + "id": "openai/o4-mini", + "family": "o-mini", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 100000 + }, + "temperature": false + }, + "openai/gpt-5.2-codex": { + "id": "openai/gpt-5.2-codex", + "family": "gpt-codex", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + }, + "temperature": true + }, + "openai/gpt-5.1-codex-mini": { + "id": "openai/gpt-5.1-codex-mini", + "family": "gpt", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + }, + "temperature": true + }, + "openai/o1-preview": { + "id": "openai/o1-preview", + "family": "o", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 32768 + }, + "temperature": false + }, + "openai/gpt-4o-2024-08-06": { + "id": "openai/gpt-4o-2024-08-06", + "family": "gpt", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "image", + "pdf", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + }, + "temperature": true + }, + "openai/gpt-5.1": { + "id": "openai/gpt-5.1", + "family": "gpt", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + }, + "temperature": true + }, + "openai/o1": { + "id": "openai/o1", + "family": "o", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 100000 + }, + "temperature": false + }, + "openai/gpt-3.5-turbo": { + "id": "openai/gpt-3.5-turbo", + "family": "gpt", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16385, + "input": 12289, + "output": 4096 + }, + "temperature": true + }, + "openai/o3-deep-research": { + "id": "openai/o3-deep-research", + "family": "o", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 100000, + "output": 100000 + }, + "temperature": false + }, + "openai/o3-mini": { + "id": "openai/o3-mini", + "family": "o-mini", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 100000 + }, + "temperature": false + }, + "openai/gpt-4-turbo-preview": { + "id": "openai/gpt-4-turbo-preview", + "family": "gpt", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 4096 + }, + "temperature": true + }, + "openai/o1-pro": { + "id": "openai/o1-pro", + "family": "o-pro", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 100000 + }, + "temperature": false + }, + "openai/gpt-5-codex": { + "id": "openai/gpt-5-codex", + "family": "gpt-codex", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + }, + "temperature": false + }, + "openai/gpt-5.1-chat-latest": { + "id": "openai/gpt-5.1-chat-latest", + "family": "gpt", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 400000, + "output": 16384 + } + }, + "openai/gpt-4o-search-preview": { + "id": "openai/gpt-4o-search-preview", + "family": "gpt", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + } + }, + "openai/gpt-4.1-nano": { + "id": "openai/gpt-4.1-nano", + "family": "gpt-nano", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 1047576, + "output": 16384 + }, + "temperature": true + }, + "openai/o4-mini-high": { + "id": "openai/o4-mini-high", + "family": "o-mini", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "pdf", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 100000 + } + }, + "openai/o3": { + "id": "openai/o3", + "family": "o", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 100000 + }, + "temperature": false + }, + "openai/gpt-oss-20b": { + "id": "openai/gpt-oss-20b", + "family": "gpt-oss", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 98304, + "output": 65536 + }, + "temperature": true + }, + "openai/gpt-5-pro": { + "id": "openai/gpt-5-pro", + "family": "gpt", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 400000, + "input": 128000, + "output": 272000 + }, + "temperature": true + }, + "openai/gpt-5.1-2025-11-13": { + "id": "openai/gpt-5.1-2025-11-13", + "family": "gpt", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 32768 + } + }, + "openai/gpt-4o": { + "id": "openai/gpt-4o", + "family": "gpt", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + }, + "temperature": true + }, + "openai/o3-mini-low": { + "id": "openai/o3-mini-low", + "family": "o-mini", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 100000 + } + }, + "openai/gpt-5": { + "id": "openai/gpt-5", + "family": "gpt", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + }, + "temperature": true + }, + "openai/gpt-oss-safeguard-20b": { + "id": "openai/gpt-oss-safeguard-20b", + "family": "gpt-oss", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 65536, + "output": 65536 + }, + "temperature": true + }, + "openai/o3-pro-2025-06-10": { + "id": "openai/o3-pro-2025-06-10", + "family": "o-pro", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 100000 + } + }, + "openai/gpt-oss-120b": { + "id": "openai/gpt-oss-120b", + "family": "gpt-oss", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 124000, + "output": 131072 + }, + "temperature": true + }, + "openai/gpt-5-chat-latest": { + "id": "openai/gpt-5-chat-latest", + "family": "gpt", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 400000, + "output": 128000 + } + }, + "openai/gpt-4.1": { + "id": "openai/gpt-4.1", + "family": "gpt", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 1047576, + "output": 16384 + }, + "temperature": true + }, + "openai/gpt-4.1-mini": { + "id": "openai/gpt-4.1-mini", + "family": "gpt-mini", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 1047576, + "output": 16384 + }, + "temperature": true + }, + "openai/gpt-5.1-codex": { + "id": "openai/gpt-5.1-codex", + "family": "gpt", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + }, + "temperature": true + }, + "openai/gpt-4o-2024-11-20": { + "id": "openai/gpt-4o-2024-11-20", + "family": "gpt", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "image", + "pdf", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 16384 + }, + "temperature": true + }, + "vongolachouko/starcannon-unleashed-12b-v1.0": { + "id": "VongolaChouko/Starcannon-Unleashed-12B-v1.0", + "family": "mistral-nemo", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 8192 + } + }, + "amazon/nova-lite-v1": { + "id": "amazon/nova-lite-v1", + "family": "nova-lite", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 300000, + "input": 300000, + "output": 5120 + }, + "temperature": true + }, + "amazon/nova-pro-v1": { + "id": "amazon/nova-pro-v1", + "family": "nova-pro", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 300000, + "input": 300000, + "output": 5120 + }, + "temperature": true + }, + "amazon/nova-2-lite-v1": { + "id": "amazon/nova-2-lite-v1", + "family": "nova", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "pdf", + "text", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 65535 + }, + "temperature": true + }, + "amazon/nova-micro-v1": { + "id": "amazon/nova-micro-v1", + "family": "nova-micro", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 5120 + }, + "temperature": true + }, + "sao10k/l3.3-70b-euryale-v2.3": { + "id": "Sao10K/L3.3-70B-Euryale-v2.3", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 20480, + "input": 20480, + "output": 16384 + } + }, + "sao10k/l3.1-70b-euryale-v2.2": { + "id": "Sao10K/L3.1-70B-Euryale-v2.2", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 20480, + "input": 20480, + "output": 16384 + } + }, + "sao10k/l3.1-70b-hanami-x1": { + "id": "sao10k/l3.1-70b-hanami-x1", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16000, + "input": 16384, + "output": 16000 + }, + "temperature": true + }, + "sao10k/l3-8b-stheno-v3.2": { + "id": "sao10k/L3-8B-Stheno-v3.2", + "family": "llama", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "input": 16384, + "output": 32000 + }, + "temperature": true + }, + "latitudegames/wayfarer-large-70b-llama-3.3": { + "id": "LatitudeGames/Wayfarer-Large-70B-Llama-3.3", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 16384 + } + }, + "z-ai/glm-4.6:thinking": { + "id": "z-ai/glm-4.6:thinking", + "family": "glm", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 65535 + } + }, + "z-ai/glm-4.5v": { + "id": "z-ai/glm-4.5v", + "family": "glm", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "input": 64000, + "output": 16384 + }, + "temperature": true + }, + "z-ai/glm-4.6": { + "id": "z-ai/glm-4.6", + "family": "glm", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "input": 200000, + "output": 204800 + }, + "temperature": true + }, + "z-ai/glm-4.5v:thinking": { + "id": "z-ai/glm-4.5v:thinking", + "family": "glmv", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 64000, + "input": 64000, + "output": 96000 + } + }, + "baidu/ernie-4.5-vl-28b-a3b": { + "id": "baidu/ernie-4.5-vl-28b-a3b", + "family": "ernie", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 30000, + "input": 32768, + "output": 8000 + }, + "temperature": true + }, + "baidu/ernie-4.5-300b-a47b": { + "id": "baidu/ERNIE-4.5-300B-A47B", + "family": "ernie", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "input": 131072, + "output": 131000 + }, + "temperature": true + }, + "dmind/dmind-1": { + "id": "dmind/dmind-1", + "family": "gpt", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 8192 + } + }, + "dmind/dmind-1-mini": { + "id": "dmind/dmind-1-mini", + "family": "gpt", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 8192 + } + }, + "infermatic/mn-12b-inferor-v0.0": { + "id": "Infermatic/MN-12B-Inferor-v0.0", + "family": "mistral-nemo", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 8192 + } + }, + "meituan-longcat/longcat-flash-chat-fp8": { + "id": "meituan-longcat/LongCat-Flash-Chat-FP8", + "family": "longcat", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 32768 + } + }, + "meganova-ai/manta-mini-1.0": { + "id": "meganova-ai/manta-mini-1.0", + "family": "nova", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, "limit": { "context": 8192, "input": 8192, "output": 8192 } }, + "meganova-ai/manta-pro-1.0": { + "id": "meganova-ai/manta-pro-1.0", + "family": "nova", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 32768 + } + }, + "meganova-ai/manta-flash-1.0": { + "id": "meganova-ai/manta-flash-1.0", + "family": "nova", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 16384 + } + }, + "minimax/minimax-m2.7": { + "id": "minimax/minimax-m2.7", + "family": "minimax", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "input": 204800, + "output": 131000 + }, + "temperature": true + }, + "minimax/minimax-01": { + "id": "minimax/minimax-01", + "family": "minimax", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000192, + "input": 1000192, + "output": 1000192 + }, + "temperature": true + }, + "minimax/minimax-m2.1": { + "id": "minimax/minimax-m2.1", + "family": "minimax", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "input": 200000, + "output": 131072 + }, + "temperature": true + }, + "minimax/minimax-m2-her": { + "id": "minimax/minimax-m2-her", + "family": "minimax", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "input": 65532, + "output": 2048 + }, + "temperature": true + }, + "minimax/minimax-m2.5": { + "id": "minimax/minimax-m2.5", + "family": "minimax-m2.5", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 204800, + "output": 131072 + }, + "temperature": true + }, + "qwen/qwen3.5-397b-a17b": { + "id": "Qwen/Qwen3.5-397B-A17B", + "family": "qwen", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "input": 258048, + "output": 130000 + }, + "temperature": true + }, "unsloth/gemma-3-1b-it": { "id": "unsloth/gemma-3-1b-it", "family": "unsloth", @@ -37214,201 +10958,132 @@ }, "temperature": true }, - "meituan-longcat/longcat-flash-chat-fp8": { - "id": "meituan-longcat/LongCat-Flash-Chat-FP8", - "family": "longcat", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 32768 - } - }, - "cognitivecomputations/dolphin-2.9.2-qwen2-72b": { - "id": "cognitivecomputations/dolphin-2.9.2-qwen2-72b", - "family": "qwen", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "input": 8192, - "output": 4096 - } - }, - "infermatic/mn-12b-inferor-v0.0": { - "id": "Infermatic/MN-12B-Inferor-v0.0", - "family": "mistral-nemo", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 8192 - } - }, - "cruciblelab/l3.3-70b-loki-v2.0": { - "id": "CrucibleLab/L3.3-70B-Loki-V2.0", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 16384 - } - }, - "soob3123/veiled-calla-12b": { - "id": "soob3123/Veiled-Calla-12B", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 8192 - } - }, - "soob3123/amoral-gemma3-27b-v2": { - "id": "soob3123/amoral-gemma3-27B-v2", - "family": "gemma", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 8192 - } - }, - "soob3123/grayline-qwen3-8b": { - "id": "soob3123/GrayLine-Qwen3-8B", - "family": "qwen", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 32768 - } - }, - "neversleep/llama-3-lumimaid-70b-v0.1": { - "id": "NeverSleep/Llama-3-Lumimaid-70B-v0.1", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 8192 - } - }, - "neversleep/lumimaid-v0.2-70b": { - "id": "NeverSleep/Lumimaid-v0.2-70B", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 8192 - } - }, - "deepseek/deepseek-v3.2:thinking": { - "id": "deepseek/deepseek-v3.2:thinking", - "family": "deepseek", + "thudm/glm-z1-9b-0414": { + "id": "THUDM/GLM-Z1-9B-0414", + "family": "glm-z", "reasoning": true, "toolCall": true, "modalities": { "input": [ - "text", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 163000, - "input": 163000, + "context": 131000, + "input": 32000, + "output": 131000 + }, + "temperature": true + }, + "thudm/glm-4-9b-0414": { + "id": "THUDM/GLM-4-9B-0414", + "family": "glm", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 33000, + "input": 32000, + "output": 33000 + }, + "temperature": true + }, + "thudm/glm-z1-rumination-32b-0414": { + "id": "THUDM/GLM-Z1-Rumination-32B-0414", + "family": "glm-z", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "input": 32000, "output": 65536 } }, - "marinaraspaghetti/nemomix-unleashed-12b": { - "id": "MarinaraSpaghetti/NemoMix-Unleashed-12B", - "family": "mistral-nemo", + "thudm/glm-4-32b-0414": { + "id": "THUDM/GLM-4-32B-0414", + "family": "glm", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 33000, + "input": 128000, + "output": 33000 + }, + "temperature": true + }, + "thudm/glm-z1-32b-0414": { + "id": "THUDM/GLM-Z1-32B-0414", + "family": "glm-z", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "input": 128000, + "output": 131000 + }, + "temperature": true + }, + "google/gemini-3-flash-preview": { + "id": "google/gemini-3-flash-preview", + "family": "gemini-flash", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "input": 1048756, + "output": 65536 + }, + "temperature": true + }, + "google/gemini-flash-1.5": { + "id": "google/gemini-flash-1.5", + "family": "gemini-flash", "reasoning": false, "toolCall": false, "modalities": { @@ -37420,16 +11095,16 @@ ] }, "limit": { - "context": 32768, - "input": 32768, + "context": 2000000, + "input": 2000000, "output": 8192 } }, - "moonshotai/kimi-k2.5:thinking": { - "id": "moonshotai/kimi-k2.5:thinking", - "family": "kimi-thinking", + "google/gemini-3-flash-preview-thinking": { + "id": "google/gemini-3-flash-preview-thinking", + "family": "gemini-flash", "reasoning": true, - "toolCall": true, + "toolCall": false, "modalities": { "input": [ "text", @@ -37440,13 +11115,55 @@ ] }, "limit": { - "context": 256000, - "input": 256000, + "context": 1048756, + "input": 1048756, "output": 65536 } }, - "moonshotai/kimi-k2-thinking-turbo-original": { - "id": "moonshotai/kimi-k2-thinking-turbo-original", + "moonshotai/kimi-k2.5": { + "id": "moonshotai/kimi-k2.5", + "family": "kimi", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "input": 256000, + "output": 262144 + }, + "temperature": false + }, + "moonshotai/kimi-k2-instruct": { + "id": "moonshotai/kimi-k2-instruct", + "family": "kimi", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 190000, + "output": 16384 + }, + "temperature": true + }, + "moonshotai/kimi-k2-thinking-original": { + "id": "moonshotai/kimi-k2-thinking-original", "family": "kimi-thinking", "reasoning": true, "toolCall": false, @@ -37503,8 +11220,8 @@ "output": 131072 } }, - "moonshotai/kimi-k2-thinking-original": { - "id": "moonshotai/kimi-k2-thinking-original", + "moonshotai/kimi-k2-thinking-turbo-original": { + "id": "moonshotai/kimi-k2-thinking-turbo-original", "family": "kimi-thinking", "reasoning": true, "toolCall": false, @@ -37522,11 +11239,11 @@ "output": 16384 } }, - "google/gemini-flash-1.5": { - "id": "google/gemini-flash-1.5", - "family": "gemini-flash", + "moonshotai/kimi-k2-instruct-0905": { + "id": "moonshotai/kimi-k2-instruct-0905", + "family": "kimi", "reasoning": false, - "toolCall": false, + "toolCall": true, "modalities": { "input": [ "text" @@ -37536,34 +11253,15 @@ ] }, "limit": { - "context": 2000000, - "input": 2000000, - "output": 8192 - } - }, - "google/gemini-3-flash-preview-thinking": { - "id": "google/gemini-3-flash-preview-thinking", - "family": "gemini-flash", - "reasoning": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] + "context": 262144, + "input": 256000, + "output": 16384 }, - "limit": { - "context": 1048756, - "input": 1048756, - "output": 65536 - } + "temperature": true }, - "z-ai/glm-4.6:thinking": { - "id": "z-ai/glm-4.6:thinking", - "family": "glm", + "moonshotai/kimi-k2-thinking": { + "id": "moonshotai/kimi-k2-thinking", + "family": "kimi-thinking", "reasoning": true, "toolCall": true, "modalities": { @@ -37575,16 +11273,17 @@ ] }, "limit": { - "context": 200000, - "input": 200000, - "output": 65535 - } + "context": 216144, + "input": 120000, + "output": 216144 + }, + "temperature": true }, - "z-ai/glm-4.5v:thinking": { - "id": "z-ai/glm-4.5v:thinking", - "family": "glmv", + "moonshotai/kimi-k2.5:thinking": { + "id": "moonshotai/kimi-k2.5:thinking", + "family": "kimi-thinking", "reasoning": true, - "toolCall": false, + "toolCall": true, "modalities": { "input": [ "text", @@ -37594,33 +11293,14 @@ "text" ] }, - "limit": { - "context": 64000, - "input": 64000, - "output": 96000 - } - }, - "stepfun-ai/step-3.5-flash:thinking": { - "id": "stepfun-ai/step-3.5-flash:thinking", - "family": "step", - "reasoning": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, "limit": { "context": 256000, "input": 256000, - "output": 256000 + "output": 65536 } }, - "deepcogito/cogito-v1-preview-qwen-32b": { - "id": "deepcogito/cogito-v1-preview-qwen-32B", + "tongyi-zhiwen/qwenlong-l1-32b": { + "id": "Tongyi-Zhiwen/QwenLong-L1-32B", "family": "qwen", "reasoning": false, "toolCall": false, @@ -37635,26 +11315,7 @@ "limit": { "context": 128000, "input": 128000, - "output": 32768 - } - }, - "inflatebot/mn-12b-mag-mell-r1": { - "id": "inflatebot/MN-12B-Mag-Mell-R1", - "family": "mistral-nemo", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 8192 + "output": 40960 } }, "nothingiisreal/l3.1-70b-celeste-v0.1-bf16": { @@ -37676,35 +11337,452 @@ "output": 16384 } }, - "x-ai/grok-4-fast:thinking": { - "id": "x-ai/grok-4-fast:thinking", - "family": "grok", + "aion-labs/aion-1.0": { + "id": "aion-labs/aion-1.0", + "family": "llama", "reasoning": true, - "toolCall": true, + "toolCall": false, "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 2000000, - "input": 2000000, - "output": 131072 - } + "context": 131072, + "input": 65536, + "output": 32768 + }, + "temperature": true }, - "x-ai/grok-4-07-09": { - "id": "x-ai/grok-4-07-09", - "family": "grok", + "aion-labs/aion-rp-llama-3.1-8b": { + "id": "aion-labs/aion-rp-llama-3.1-8b", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 32768 + }, + "temperature": true + }, + "aion-labs/aion-1.0-mini": { + "id": "aion-labs/aion-1.0-mini", + "family": "deepseek", "reasoning": true, "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "input": 131072, + "output": 32768 + }, + "temperature": true + }, + "alibaba-nlp/tongyi-deepresearch-30b-a3b": { + "id": "Alibaba-NLP/Tongyi-DeepResearch-30B-A3B", + "family": "yi", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 128000, + "output": 65536 + } + }, + "minimaxai/minimax-m1-80k": { + "id": "minimaxai/minimax-m1-80k", + "family": "minimax", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 40000 + }, + "temperature": true + }, + "anthropic/claude-opus-4.6:thinking:low": { + "id": "anthropic/claude-opus-4.6:thinking:low", + "family": "claude-opus", + "reasoning": true, + "toolCall": true, "modalities": { "input": [ "text", - "image" + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 128000 + } + }, + "anthropic/claude-opus-4.6": { + "id": "anthropic/claude-opus-4.6", + "family": "claude-opus", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 128000 + }, + "temperature": true + }, + "anthropic/claude-sonnet-4.6:thinking": { + "id": "anthropic/claude-sonnet-4.6:thinking", + "family": "claude-sonnet", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 128000 + } + }, + "anthropic/claude-opus-4.6:thinking:max": { + "id": "anthropic/claude-opus-4.6:thinking:max", + "family": "claude-opus", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 128000 + } + }, + "anthropic/claude-opus-4.6:thinking:medium": { + "id": "anthropic/claude-opus-4.6:thinking:medium", + "family": "claude-opus", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 128000 + } + }, + "anthropic/claude-sonnet-4.6": { + "id": "anthropic/claude-sonnet-4.6", + "family": "claude-sonnet", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 128000 + }, + "temperature": true + }, + "anthropic/claude-opus-4.6:thinking": { + "id": "anthropic/claude-opus-4.6:thinking", + "family": "claude-opus", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "input": 1000000, + "output": 128000 + } + }, + "abacusai/dracarys-72b-instruct": { + "id": "abacusai/Dracarys-72B-Instruct", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 8192 + } + }, + "eva-unit-01/eva-llama-3.33-70b-v0.0": { + "id": "EVA-UNIT-01/EVA-LLaMA-3.33-70B-v0.0", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 16384 + } + }, + "eva-unit-01/eva-qwen2.5-72b-v0.2": { + "id": "EVA-UNIT-01/EVA-Qwen2.5-72B-v0.2", + "family": "qwen", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 8192 + } + }, + "eva-unit-01/eva-llama-3.33-70b-v0.1": { + "id": "EVA-UNIT-01/EVA-LLaMA-3.33-70B-v0.1", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 16384 + } + }, + "eva-unit-01/eva-qwen2.5-32b-v0.2": { + "id": "EVA-UNIT-01/EVA-Qwen2.5-32B-v0.2", + "family": "qwen", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 8192 + } + }, + "huihui-ai/deepseek-r1-distill-qwen-32b-abliterated": { + "id": "huihui-ai/DeepSeek-R1-Distill-Qwen-32B-abliterated", + "family": "qwen", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 8192 + } + }, + "huihui-ai/deepseek-r1-distill-llama-70b-abliterated": { + "id": "huihui-ai/DeepSeek-R1-Distill-Llama-70B-abliterated", + "family": "deepseek", + "reasoning": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 8192 + } + }, + "huihui-ai/llama-3.3-70b-instruct-abliterated": { + "id": "huihui-ai/Llama-3.3-70B-Instruct-abliterated", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 16384 + } + }, + "huihui-ai/qwen2.5-32b-instruct-abliterated": { + "id": "huihui-ai/Qwen2.5-32B-Instruct-abliterated", + "family": "qwen", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 8192 + } + }, + "huihui-ai/llama-3.1-nemotron-70b-instruct-hf-abliterated": { + "id": "huihui-ai/Llama-3.1-Nemotron-70B-Instruct-HF-abliterated", + "family": "nemotron", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 16384 + } + }, + "xiaomi/mimo-v2-flash-thinking-original": { + "id": "xiaomi/mimo-v2-flash-thinking-original", + "family": "mimo", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" ], "output": [ "text" @@ -37713,7 +11791,65 @@ "limit": { "context": 256000, "input": 256000, - "output": 131072 + "output": 32768 + } + }, + "xiaomi/mimo-v2-flash-thinking": { + "id": "xiaomi/mimo-v2-flash-thinking", + "family": "mimo", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 32768 + } + }, + "xiaomi/mimo-v2-flash": { + "id": "xiaomi/mimo-v2-flash", + "family": "mimo", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "input": 256000, + "output": 32000 + }, + "temperature": true + }, + "xiaomi/mimo-v2-flash-original": { + "id": "xiaomi/mimo-v2-flash-original", + "family": "mimo", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 32768 } }, "tngtech/deepseek-tng-r1t2-chimera": { @@ -37755,105 +11891,9 @@ "output": 65536 } }, - "mistralai/mixtral-8x22b-instruct-v0.1": { - "id": "mistralai/mixtral-8x22b-instruct-v0.1", - "family": "mixtral", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "input": 65536, - "output": 32768 - } - }, - "mistralai/mistral-tiny": { - "id": "mistralai/mistral-tiny", - "family": "mistral", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "input": 32000, - "output": 8192 - } - }, - "mistralai/mistral-7b-instruct": { - "id": "mistralai/mistral-7b-instruct", - "family": "mistral", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 8192 - } - }, - "mistralai/mixtral-8x7b-instruct-v0.1": { - "id": "mistralai/mixtral-8x7b-instruct-v0.1", - "family": "mixtral", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 32768 - } - }, - "tongyi-zhiwen/qwenlong-l1-32b": { - "id": "Tongyi-Zhiwen/QwenLong-L1-32B", - "family": "qwen", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 40960 - } - }, - "readyart/the-omega-abomination-l-70b-v1.0": { - "id": "ReadyArt/The-Omega-Abomination-L-70B-v1.0", - "family": "llama", + "inflatebot/mn-12b-mag-mell-r1": { + "id": "inflatebot/MN-12B-Mag-Mell-R1", + "family": "mistral-nemo", "reasoning": false, "toolCall": false, "modalities": { @@ -37867,11 +11907,11 @@ "limit": { "context": 16384, "input": 16384, - "output": 16384 + "output": 8192 } }, - "readyart/ms3.2-the-omega-directive-24b-unslop-v2.0": { - "id": "ReadyArt/MS3.2-The-Omega-Directive-24B-Unslop-v2.0", + "failspy/meta-llama-3-70b-instruct-abliterated-v3.5": { + "id": "failspy/Meta-Llama-3-70B-Instruct-abliterated-v3.5", "family": "llama", "reasoning": false, "toolCall": false, @@ -37884,35 +11924,17 @@ ] }, "limit": { - "context": 16384, - "input": 16384, - "output": 32768 + "context": 8192, + "input": 8192, + "output": 8192 } }, - "openai/gpt-5.1-2025-11-13": { - "id": "openai/gpt-5.1-2025-11-13", - "family": "gpt", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "input": 1000000, - "output": 32768 - } - }, - "openai/gpt-5-chat-latest": { - "id": "openai/gpt-5-chat-latest", - "family": "gpt", + "gpt-5.1-codex-max": { + "id": "gpt-5.1-codex-max", + "family": "gpt-codex", "reasoning": true, - "toolCall": false, + "temperature": false, + "toolCall": true, "modalities": { "input": [ "text", @@ -37924,589 +11946,59 @@ }, "limit": { "context": 400000, - "input": 400000, + "input": 272000, "output": 128000 } }, - "openai/o3-mini-low": { - "id": "openai/o3-mini-low", - "family": "o-mini", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 100000 - } - }, - "openai/o3-pro-2025-06-10": { - "id": "openai/o3-pro-2025-06-10", - "family": "o-pro", - "reasoning": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 200000, - "input": 200000, - "output": 100000 - } - }, - "openai/gpt-5.1-chat-latest": { - "id": "openai/gpt-5.1-chat-latest", - "family": "gpt", - "reasoning": true, - "toolCall": false, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 400000, - "input": 400000, - "output": 16384 - } - }, - "vongolachouko/starcannon-unleashed-12b-v1.0": { - "id": "VongolaChouko/Starcannon-Unleashed-12B-v1.0", - "family": "mistral-nemo", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 8192 - } - }, - "cohere/command-r": { - "id": "cohere/command-r", - "family": "command-r", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 4096 - } - }, - "thudm/glm-z1-rumination-32b-0414": { - "id": "THUDM/GLM-Z1-Rumination-32B-0414", - "family": "glm-z", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32000, - "input": 32000, - "output": 65536 - } - }, - "chutesai/mistral-small-3.2-24b-instruct-2506": { - "id": "chutesai/Mistral-Small-3.2-24B-Instruct-2506", - "family": "chutesai", - "reasoning": false, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 128000, - "output": 131072 - }, - "temperature": true - }, - "baseten/kimi-k2-instruct-fp4": { - "id": "baseten/Kimi-K2-Instruct-FP4", + "kimi-k2.5": { + "id": "kimi-k2.5", "family": "kimi", - "reasoning": false, - "toolCall": false, + "reasoning": true, + "temperature": false, + "toolCall": true, "modalities": { "input": [ - "text" + "text", + "image", + "video" ], "output": [ "text" ] }, "limit": { - "context": 128000, - "input": 128000, - "output": 131072 + "context": 262144, + "output": 262144 } }, - "galrionsoftworks/mn-loosecannon-12b-v1": { - "id": "GalrionSoftworks/MN-LooseCannon-12B-v1", - "family": "mistral-nemo", - "reasoning": false, - "toolCall": false, + "gemini-3.1-flash-lite-preview": { + "id": "gemini-3.1-flash-lite-preview", + "family": "gemini-flash-lite", + "reasoning": true, + "temperature": true, + "toolCall": true, "modalities": { "input": [ - "text" + "text", + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "input": 16384, - "output": 8192 - } - }, - "alibaba-nlp/tongyi-deepresearch-30b-a3b": { - "id": "Alibaba-NLP/Tongyi-DeepResearch-30B-A3B", - "family": "yi", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, + "context": 1048576, "output": 65536 } }, - "steelskull/l3.3-electra-r1-70b": { - "id": "Steelskull/L3.3-Electra-R1-70b", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 16384 - } - }, - "steelskull/l3.3-ms-evalebis-70b": { - "id": "Steelskull/L3.3-MS-Evalebis-70b", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 16384 - } - }, - "steelskull/l3.3-cu-mai-r1-70b": { - "id": "Steelskull/L3.3-Cu-Mai-R1-70b", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 16384 - } - }, - "steelskull/l3.3-nevoria-r1-70b": { - "id": "Steelskull/L3.3-Nevoria-R1-70b", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 16384 - } - }, - "steelskull/l3.3-ms-nevoria-70b": { - "id": "Steelskull/L3.3-MS-Nevoria-70b", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 16384 - } - }, - "steelskull/l3.3-ms-evayale-70b": { - "id": "Steelskull/L3.3-MS-Evayale-70B", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 16384 - } - }, - "salesforce/llama-xlam-2-70b-fc-r": { - "id": "Salesforce/Llama-xLAM-2-70b-fc-r", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 16384 - } - }, - "latitudegames/wayfarer-large-70b-llama-3.3": { - "id": "LatitudeGames/Wayfarer-Large-70B-Llama-3.3", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 16384 - } - }, - "thedrummer 2/cydonia-24b-v4.3": { - "id": "TheDrummer 2/Cydonia-24B-v4.3", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 32768 - } - }, - "thedrummer 2/anubis-70b-v1": { - "id": "TheDrummer 2/Anubis-70B-v1", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 65536, - "input": 65536, - "output": 16384 - } - }, - "thedrummer 2/cydonia-24b-v4": { - "id": "TheDrummer 2/Cydonia-24B-v4", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 32768 - } - }, - "thedrummer 2/magidonia-24b-v4.3": { - "id": "TheDrummer 2/Magidonia-24B-v4.3", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 32768 - } - }, - "thedrummer 2/anubis-70b-v1.1": { - "id": "TheDrummer 2/Anubis-70B-v1.1", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "input": 131072, - "output": 16384 - } - }, - "thedrummer 2/rocinante-12b-v1.1": { - "id": "TheDrummer 2/Rocinante-12B-v1.1", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 8192 - } - }, - "thedrummer 2/cydonia-24b-v2": { - "id": "TheDrummer 2/Cydonia-24B-v2", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 32768 - } - }, - "thedrummer 2/skyfall-36b-v2": { - "id": "TheDrummer 2/skyfall-36b-v2", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 64000, - "input": 64000, - "output": 32768 - } - }, - "thedrummer 2/unslopnemo-12b-v4.1": { - "id": "TheDrummer 2/UnslopNemo-12B-v4.1", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 8192 - } - }, - "thedrummer 2/cydonia-24b-v4.1": { - "id": "TheDrummer 2/Cydonia-24B-v4.1", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 32768 - } - }, - "shisa-ai/shisa-v2.1-llama3.3-70b": { - "id": "shisa-ai/shisa-v2.1-llama3.3-70b", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 4096 - } - }, - "shisa-ai/shisa-v2-llama3.3-70b": { - "id": "shisa-ai/shisa-v2-llama3.3-70b", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "input": 128000, - "output": 16384 - } - }, - "anthropic/claude-sonnet-4.6:thinking": { - "id": "anthropic/claude-sonnet-4.6:thinking", + "claude-sonnet-4-6": { + "id": "claude-sonnet-4-6", "family": "claude-sonnet", "reasoning": true, + "temperature": true, "toolCall": true, "modalities": { "input": [ @@ -38520,19 +12012,21 @@ }, "limit": { "context": 1000000, - "input": 1000000, - "output": 128000 + "output": 64000 } }, - "anthropic/claude-opus-4.6:thinking:low": { - "id": "anthropic/claude-opus-4.6:thinking:low", - "family": "claude-opus", + "gemini-3.1-pro-preview": { + "id": "gemini-3.1-pro-preview", + "family": "gemini-pro", "reasoning": true, + "temperature": true, "toolCall": true, "modalities": { "input": [ "text", "image", + "video", + "audio", "pdf" ], "output": [ @@ -38540,57 +12034,178 @@ ] }, "limit": { - "context": 1000000, - "input": 1000000, - "output": 128000 + "context": 1048576, + "output": 65536, + "input": 128000 } }, - "anthropic/claude-opus-4.6:thinking": { - "id": "anthropic/claude-opus-4.6:thinking", - "family": "claude-opus", + "gpt-5.3-chat-latest": { + "id": "gpt-5.3-chat-latest", + "family": "gpt", "reasoning": true, + "temperature": true, "toolCall": true, "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "input": 1000000, - "output": 128000 + "context": 128000, + "output": 16384 } }, - "anthropic/claude-opus-4.6:thinking:medium": { - "id": "anthropic/claude-opus-4.6:thinking:medium", - "family": "claude-opus", + "llama-3.3-70b-versatile": { + "id": "llama-3.3-70b-versatile", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "gpt-5-nano": { + "id": "gpt-5-nano", + "family": "gpt-nano", "reasoning": true, + "temperature": false, "toolCall": true, "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, - "input": 1000000, - "output": 128000 + "context": 272000, + "output": 128000, + "input": 272000 } }, - "anthropic/claude-opus-4.6:thinking:max": { - "id": "anthropic/claude-opus-4.6:thinking:max", - "family": "claude-opus", + "gpt-5.3-codex": { + "id": "gpt-5.3-codex", + "family": "gpt-codex", "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "o3-pro": { + "id": "o3-pro", + "family": "o-pro", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 100000 + } + }, + "gpt-4o-mini": { + "id": "gpt-4o-mini", + "family": "gpt-mini", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "o4-mini": { + "id": "o4-mini", + "family": "o-mini", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 100000 + } + }, + "gpt-5.2-codex": { + "id": "gpt-5.2-codex", + "family": "gpt-codex", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "gpt-5.3-codex-xhigh": { + "id": "gpt-5.3-codex-xhigh", + "family": "gpt", + "reasoning": true, + "temperature": false, "toolCall": true, "modalities": { "input": [ @@ -38603,16 +12218,157 @@ ] }, "limit": { - "context": 1000000, - "input": 1000000, + "context": 400000, + "input": 272000, "output": 128000 } }, - "miromind-ai/mirothinker-v1.5-235b": { - "id": "miromind-ai/MiroThinker-v1.5-235B", + "grok-code-fast-1": { + "id": "grok-code-fast-1", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 10000, + "input": 128000 + } + }, + "o3-mini": { + "id": "o3-mini", + "family": "o-mini", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 100000 + } + }, + "grok-4-0709": { + "id": "grok-4-0709", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "route-llm": { + "id": "route-llm", "family": "gpt", "reasoning": false, - "toolCall": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "qwen-2.5-coder-32b": { + "id": "qwen-2.5-coder-32b", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "gpt-5-codex": { + "id": "gpt-5-codex", + "family": "gpt-codex", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "gpt-5.4": { + "id": "gpt-5.4", + "family": "gpt", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "kimi-k2-turbo-preview": { + "id": "kimi-k2-turbo-preview", + "family": "kimi", + "reasoning": false, + "temperature": true, + "toolCall": true, "modalities": { "input": [ "text" @@ -38623,54 +12379,100 @@ }, "limit": { "context": 262144, - "input": 32768, - "output": 8192 - }, - "temperature": true + "output": 262144 + } }, - "sao10k/l3.3-70b-euryale-v2.3": { - "id": "Sao10K/L3.3-70B-Euryale-v2.3", - "family": "llama", - "reasoning": false, - "toolCall": false, + "claude-opus-4-6": { + "id": "claude-opus-4-6", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, "modalities": { "input": [ - "text" + "text", + "image", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 20480, - "input": 20480, - "output": 16384 + "context": 1000000, + "output": 128000 } }, - "sao10k/l3.1-70b-euryale-v2.2": { - "id": "Sao10K/L3.1-70B-Euryale-v2.2", - "family": "llama", - "reasoning": false, - "toolCall": false, + "o3": { + "id": "o3", + "family": "o", + "reasoning": true, + "temperature": false, + "toolCall": true, "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 20480, - "input": 20480, + "context": 200000, + "output": 100000 + } + }, + "gpt-5.1-codex": { + "id": "gpt-5.1-codex", + "family": "gpt-codex", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text", + "image", + "audio" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "gpt-4o-2024-11-20": { + "id": "gpt-4o-2024-11-20", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, "output": 16384 } }, - "huihui-ai/deepseek-r1-distill-llama-70b-abliterated": { - "id": "huihui-ai/DeepSeek-R1-Distill-Llama-70B-abliterated", + "deepseek/deepseek-v3.1": { + "id": "deepseek/deepseek-v3.1", "family": "deepseek", "reasoning": true, - "toolCall": false, + "temperature": true, + "toolCall": true, "modalities": { "input": [ "text" @@ -38680,35 +12482,16 @@ ] }, "limit": { - "context": 16384, - "input": 16384, - "output": 8192 + "context": 163840, + "output": 128000 } }, - "huihui-ai/qwen2.5-32b-instruct-abliterated": { - "id": "huihui-ai/Qwen2.5-32B-Instruct-abliterated", - "family": "qwen", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "input": 32768, - "output": 8192 - } - }, - "huihui-ai/deepseek-r1-distill-qwen-32b-abliterated": { - "id": "huihui-ai/DeepSeek-R1-Distill-Qwen-32B-abliterated", + "qwen/qwq-32b": { + "id": "Qwen/QwQ-32B", "family": "qwen", "reasoning": true, - "toolCall": false, + "temperature": true, + "toolCall": true, "modalities": { "input": [ "text" @@ -38718,16 +12501,16 @@ ] }, "limit": { - "context": 16384, - "input": 16384, - "output": 8192 + "context": 131000, + "output": 131000 } }, - "huihui-ai/llama-3.3-70b-instruct-abliterated": { - "id": "huihui-ai/Llama-3.3-70B-Instruct-abliterated", - "family": "llama", + "qwen/qwen3-235b-a22b-instruct-2507": { + "id": "Qwen/Qwen3-235B-A22B-Instruct-2507", + "family": "qwen", "reasoning": false, - "toolCall": false, + "temperature": true, + "toolCall": true, "modalities": { "input": [ "text" @@ -38737,15 +12520,250 @@ ] }, "limit": { - "context": 16384, - "input": 16384, + "context": 262144, + "output": 131072 + } + }, + "qwen/qwen3-32b": { + "id": "qwen/qwen3-32b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 40960, + "input": 120000 + } + }, + "qwen/qwen3-coder-480b-a35b-instruct": { + "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + } + }, + "qwen/qwen2.5-72b-instruct": { + "id": "Qwen/Qwen2.5-72B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 33000, + "output": 4000 + } + }, + "zai-org/glm-4.5": { + "id": "zai-org/GLM-4.5", + "family": "glm", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 131000, + "input": 124000 + } + }, + "zai-org/glm-4.6": { + "id": "zai-org/GLM-4.6", + "family": "glm", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 205000, + "output": 205000 + } + }, + "meta-llama/meta-llama-3.1-405b-instruct-turbo": { + "id": "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "meta-llama/llama-4-maverick-17b-128e-instruct-fp8": { + "id": "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, "output": 16384 } }, - "huihui-ai/llama-3.1-nemotron-70b-instruct-hf-abliterated": { - "id": "huihui-ai/Llama-3.1-Nemotron-70B-Instruct-HF-abliterated", + "meta-llama/meta-llama-3.1-8b-instruct": { + "id": "meta-llama/Meta-Llama-3.1-8B-Instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 33000, + "output": 4000, + "input": 120000 + } + }, + "deepseek-ai/deepseek-r1": { + "id": "deepseek-ai/DeepSeek-R1", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163839, + "output": 163839 + } + }, + "deepseek-ai/deepseek-v3.2": { + "id": "deepseek-ai/DeepSeek-V3.2", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 164000, + "output": 164000, + "input": 160000 + } + }, + "perplexity/sonar": { + "id": "perplexity/sonar", + "family": "sonar", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 127000, + "output": 8000 + } + }, + "xai/grok-4-1-fast-non-reasoning": { + "id": "xai/grok-4-1-fast-non-reasoning", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 30000 + } + }, + "nvidia/nemotron-3-super-120b-a12b": { + "id": "nvidia/nemotron-3-super-120b-a12b", "family": "nemotron", "reasoning": false, + "temperature": true, "toolCall": false, "modalities": { "input": [ @@ -38756,16 +12774,206 @@ ] }, "limit": { - "context": 16384, - "input": 16384, - "output": 16384 + "context": 256000, + "output": 32000, + "input": 256000 } }, - "dmind/dmind-1-mini": { - "id": "dmind/dmind-1-mini", + "openai/gpt-5.4": { + "id": "openai/gpt-5.4", "family": "gpt", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1050000, + "input": 922000, + "output": 128000 + } + }, + "google/gemini-3.1-pro-preview": { + "id": "google/gemini-3.1-pro-preview", + "family": "gemini", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "google/gemini-2.5-pro": { + "id": "google/gemini-2.5-pro", + "family": "gemini-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "google/gemini-2.5-flash": { + "id": "google/gemini-2.5-flash", + "family": "gemini-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "anthropic/claude-haiku-4-5": { + "id": "anthropic/claude-haiku-4-5", + "family": "claude-haiku", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 62000 + } + }, + "anthropic/claude-sonnet-4-6": { + "id": "anthropic/claude-sonnet-4-6", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "anthropic/claude-opus-4-5": { + "id": "anthropic/claude-opus-4-5", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic/claude-opus-4-6": { + "id": "anthropic/claude-opus-4-6", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "anthropic/claude-sonnet-4-5": { + "id": "anthropic/claude-sonnet-4-5", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "kwaipilot/kat-dev": { + "id": "Kwaipilot/KAT-Dev", + "family": "kat-coder", "reasoning": false, - "toolCall": false, + "temperature": true, + "toolCall": true, "modalities": { "input": [ "text" @@ -38775,16 +12983,16 @@ ] }, "limit": { - "context": 32768, - "input": 32768, - "output": 8192 + "context": 128000, + "output": 128000 } }, - "dmind/dmind-1": { - "id": "dmind/dmind-1", - "family": "gpt", - "reasoning": false, - "toolCall": false, + "qwen/qwen3.5-35b-a3b": { + "id": "qwen/qwen3.5-35b-a3b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, "modalities": { "input": [ "text" @@ -38794,73 +13002,154 @@ ] }, "limit": { - "context": 32768, - "input": 32768, - "output": 8192 + "context": 262144, + "output": 262144 } }, - "eva-unit-01/eva-qwen2.5-72b-v0.2": { - "id": "EVA-UNIT-01/EVA-Qwen2.5-72B-v0.2", + "qwen/qwen3.5-122b-a10b": { + "id": "qwen/qwen3.5-122b-a10b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "qwen/qwen3.5-9b": { + "id": "qwen/qwen3.5-9b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "qwen/qwen3.5-27b": { + "id": "qwen/qwen3.5-27b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "qwen/qwen3.5-4b": { + "id": "Qwen/Qwen3.5-4B", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + } + }, + "qwen/qwen3-vl-8b-instruct": { + "id": "Qwen/Qwen3-VL-8B-Instruct", "family": "qwen", "reasoning": false, - "toolCall": false, + "temperature": true, + "toolCall": true, "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 16384, - "input": 16384, - "output": 8192 + "context": 262000, + "output": 262000 } }, - "eva-unit-01/eva-llama-3.33-70b-v0.0": { - "id": "EVA-UNIT-01/EVA-LLaMA-3.33-70B-v0.0", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 16384 - } - }, - "eva-unit-01/eva-llama-3.33-70b-v0.1": { - "id": "EVA-UNIT-01/EVA-LLaMA-3.33-70B-v0.1", - "family": "llama", - "reasoning": false, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 16384, - "input": 16384, - "output": 16384 - } - }, - "eva-unit-01/eva-qwen2.5-32b-v0.2": { - "id": "EVA-UNIT-01/EVA-Qwen2.5-32B-v0.2", + "qwen/qwen3-vl-32b-instruct": { + "id": "Qwen/Qwen3-VL-32B-Instruct", "family": "qwen", "reasoning": false, - "toolCall": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + } + }, + "qwen/qwen3-vl-30b-a3b-thinking": { + "id": "Qwen/Qwen3-VL-30B-A3B-Thinking", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + } + }, + "qwen/qwen2.5-14b-instruct": { + "id": "Qwen/Qwen2.5-14B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, "modalities": { "input": [ "text" @@ -38870,13 +13159,131 @@ ] }, "limit": { - "context": 16384, - "input": 16384, - "output": 8192 + "context": 33000, + "output": 4000 } }, - "qwen-3-235b-a22b-instruct-2507": { - "id": "qwen-3-235b-a22b-instruct-2507", + "qwen/qwen3-vl-235b-a22b-instruct": { + "id": "Qwen/Qwen3-VL-235B-A22B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + } + }, + "qwen/qwen3-next-80b-a3b-thinking": { + "id": "Qwen/Qwen3-Next-80B-A3B-Thinking", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000, + "input": 120000 + } + }, + "qwen/qwen2.5-vl-32b-instruct": { + "id": "Qwen/Qwen2.5-VL-32B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 131000 + } + }, + "qwen/qwen3-omni-30b-a3b-thinking": { + "id": "Qwen/Qwen3-Omni-30B-A3B-Thinking", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 66000, + "output": 66000 + } + }, + "qwen/qwen3-235b-a22b-thinking-2507": { + "id": "Qwen/Qwen3-235B-A22B-Thinking-2507", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + } + }, + "qwen/qwen2.5-32b-instruct": { + "id": "Qwen/Qwen2.5-32B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 33000, + "output": 4000 + } + }, + "qwen/qwen2.5-72b-instruct-128k": { + "id": "Qwen/Qwen2.5-72B-Instruct-128K", "family": "qwen", "reasoning": false, "temperature": true, @@ -38891,9 +13298,18207 @@ }, "limit": { "context": 131000, + "output": 4000 + } + }, + "qwen/qwen3-14b": { + "id": "Qwen/Qwen3-14B", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 131000 + } + }, + "qwen/qwen3-omni-30b-a3b-instruct": { + "id": "Qwen/Qwen3-Omni-30B-A3B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 66000, + "output": 66000 + } + }, + "qwen/qwen3-coder-30b-a3b-instruct": { + "id": "Qwen/Qwen3-Coder-30B-A3B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536, + "input": 120000 + } + }, + "qwen/qwen3-30b-a3b-instruct-2507": { + "id": "Qwen/Qwen3-30B-A3B-Instruct-2507", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 16384, + "input": 120000 + } + }, + "qwen/qwen3-8b": { + "id": "Qwen/Qwen3-8B", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 131000 + } + }, + "qwen/qwen3-next-80b-a3b-instruct": { + "id": "Qwen/Qwen3-Next-80B-A3B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + } + }, + "qwen/qwen3-vl-8b-thinking": { + "id": "Qwen/Qwen3-VL-8B-Thinking", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + } + }, + "qwen/qwen3-omni-30b-a3b-captioner": { + "id": "Qwen/Qwen3-Omni-30B-A3B-Captioner", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 66000, + "output": 66000 + } + }, + "qwen/qwen3-vl-30b-a3b-instruct": { + "id": "Qwen/Qwen3-VL-30B-A3B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + } + }, + "qwen/qwen2.5-coder-32b-instruct": { + "id": "Qwen/Qwen2.5-Coder-32B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 33000, + "output": 4000 + } + }, + "qwen/qwen2.5-7b-instruct": { + "id": "Qwen/Qwen2.5-7B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 33000, + "output": 4000 + } + }, + "qwen/qwen3-vl-235b-a22b-thinking": { + "id": "Qwen/Qwen3-VL-235B-A22B-Thinking", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + } + }, + "qwen/qwen3-30b-a3b-thinking-2507": { + "id": "Qwen/Qwen3-30B-A3B-Thinking-2507", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768, + "input": 120000 + } + }, + "qwen/qwen3-vl-32b-thinking": { + "id": "Qwen/Qwen3-VL-32B-Thinking", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + } + }, + "qwen/qwen2.5-vl-72b-instruct": { + "id": "Qwen/Qwen2.5-VL-72B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 4000, + "input": 120000 + } + }, + "zai-org/glm-4.5v": { + "id": "zai-org/GLM-4.5V", + "family": "glm", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 66000, + "output": 66000 + } + }, + "zai-org/glm-4.6v": { + "id": "zai-org/GLM-4.6V", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 131000 + } + }, + "zai-org/glm-4.5-air": { + "id": "zai-org/GLM-4.5-Air", + "family": "glm-air", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 131000, + "input": 124000 + } + }, + "inclusionai/ling-flash-2.0": { + "id": "inclusionAI/Ling-flash-2.0", + "family": "ling", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 131000 + } + }, + "inclusionai/ling-mini-2.0": { + "id": "inclusionAI/Ling-mini-2.0", + "family": "ling", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 131000 + } + }, + "inclusionai/ring-flash-2.0": { + "id": "inclusionAI/Ring-flash-2.0", + "family": "ring", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 131000 + } + }, + "ascend-tribe/pangu-pro-moe": { + "id": "ascend-tribe/pangu-pro-moe", + "family": "pangu", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "tencent/hunyuan-a13b-instruct": { + "id": "tencent/Hunyuan-A13B-Instruct", + "family": "hunyuan", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 131000 + } + }, + "pro/zai-org/glm-4.7": { + "id": "Pro/zai-org/GLM-4.7", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 205000, + "output": 205000 + } + }, + "pro/zai-org/glm-5.1": { + "id": "Pro/zai-org/GLM-5.1", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 205000, + "output": 205000 + } + }, + "pro/zai-org/glm-5": { + "id": "Pro/zai-org/GLM-5", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 205000, + "output": 205000 + } + }, + "pro/deepseek-ai/deepseek-v3": { + "id": "Pro/deepseek-ai/DeepSeek-V3", + "family": "deepseek", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 164000, + "output": 164000 + } + }, + "pro/deepseek-ai/deepseek-r1": { + "id": "Pro/deepseek-ai/DeepSeek-R1", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 164000, + "output": 164000 + } + }, + "pro/deepseek-ai/deepseek-v3.2": { + "id": "Pro/deepseek-ai/DeepSeek-V3.2", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 164000, + "output": 164000 + } + }, + "pro/deepseek-ai/deepseek-v3.1-terminus": { + "id": "Pro/deepseek-ai/DeepSeek-V3.1-Terminus", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 164000, + "output": 164000 + } + }, + "pro/moonshotai/kimi-k2-thinking": { + "id": "Pro/moonshotai/Kimi-K2-Thinking", + "family": "kimi-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + } + }, + "pro/moonshotai/kimi-k2-instruct-0905": { + "id": "Pro/moonshotai/Kimi-K2-Instruct-0905", + "family": "kimi", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + } + }, + "pro/moonshotai/kimi-k2.5": { + "id": "Pro/moonshotai/Kimi-K2.5", + "family": "kimi", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + } + }, + "pro/minimaxai/minimax-m2.5": { + "id": "Pro/MiniMaxAI/MiniMax-M2.5", + "family": "minimax", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 192000, + "output": 131000 + } + }, + "pro/minimaxai/minimax-m2.1": { + "id": "Pro/MiniMaxAI/MiniMax-M2.1", + "family": "minimax", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 197000, + "output": 131000 + } + }, + "paddlepaddle/paddleocr-vl": { + "id": "paddlepaddle/paddleocr-vl", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "output": 16384 + } + }, + "paddlepaddle/paddleocr-vl-1.5": { + "id": "PaddlePaddle/PaddleOCR-VL-1.5", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "output": 16384 + } + }, + "deepseek-ai/deepseek-ocr": { + "id": "deepseek-ai/DeepSeek-OCR", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "deepseek-ai/deepseek-r1-distill-qwen-14b": { + "id": "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 131000 + } + }, + "deepseek-ai/deepseek-r1-distill-qwen-32b": { + "id": "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 131000 + } + }, + "deepseek-ai/deepseek-v3": { + "id": "deepseek-ai/DeepSeek-V3", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "deepseek-ai/deepseek-vl2": { + "id": "deepseek-ai/deepseek-vl2", + "family": "deepseek", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4000, + "output": 4000 + } + }, + "bytedance-seed/seed-oss-36b-instruct": { + "id": "ByteDance-Seed/Seed-OSS-36B-Instruct", + "family": "seed", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + } + }, + "qwen/qwen3-coder-480b-a35b-instruct-fp8": { + "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "zai-org/glm-4.5-fp8": { + "id": "zai-org/GLM-4.5-FP8", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + } + }, + "deepseek-ai/deepseek-v3-0324": { + "id": "deepseek-ai/DeepSeek-V3-0324", + "family": "deepseek", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 163840, + "input": 120000 + } + }, + "minimax-m2.5": { + "id": "MiniMax-M2.5", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072, + "input": 196601 + } + }, + "minimax-m2.7": { + "id": "MiniMax-M2.7", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "minimax-m2.7-highspeed": { + "id": "MiniMax-M2.7-highspeed", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "minimax-m2.5-highspeed": { + "id": "MiniMax-M2.5-highspeed", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "cerebras-llama-4-maverick-17b-128e-instruct": { + "id": "cerebras-llama-4-maverick-17b-128e-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "llama-3.3-8b-instruct": { + "id": "llama-3.3-8b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "cerebras-llama-4-scout-17b-16e-instruct": { + "id": "cerebras-llama-4-scout-17b-16e-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "groq-llama-4-maverick-17b-128e-instruct": { + "id": "groq-llama-4-maverick-17b-128e-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "llama-4-scout-17b-16e-instruct-fp8": { + "id": "llama-4-scout-17b-16e-instruct-fp8", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "llama-4-maverick-17b-128e-instruct-fp8": { + "id": "llama-4-maverick-17b-128e-instruct-fp8", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "liquid/lfm-2.5-1.2b-instruct:free": { + "id": "liquid/lfm-2.5-1.2b-instruct:free", + "family": "liquid", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "liquid/lfm-2.5-1.2b-thinking:free": { + "id": "liquid/lfm-2.5-1.2b-thinking:free", + "family": "liquid", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "deepseek/deepseek-chat-v3.1": { + "id": "deepseek/deepseek-chat-v3.1", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 7168 + } + }, + "deepseek/deepseek-r1-distill-llama-70b": { + "id": "deepseek/deepseek-r1-distill-llama-70b", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "deepseek/deepseek-r1": { + "id": "deepseek/deepseek-r1", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "output": 8192 + } + }, + "deepseek/deepseek-v3.1-terminus:exacto": { + "id": "deepseek/deepseek-v3.1-terminus:exacto", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + } + }, + "deepseek/deepseek-chat-v3-0324": { + "id": "deepseek/deepseek-chat-v3-0324", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 65536 + } + }, + "deepseek/deepseek-v3.1-terminus": { + "id": "deepseek/deepseek-v3.1-terminus", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + } + }, + "openrouter/elephant-alpha": { + "id": "openrouter/elephant-alpha", + "family": "elephant", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "openrouter/free": { + "id": "openrouter/free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 200000, + "output": 32768 + } + }, + "arcee-ai/trinity-large-thinking": { + "id": "arcee-ai/trinity-large-thinking", + "family": "trinity", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262100, + "output": 80000 + } + }, + "arcee-ai/trinity-large-preview:free": { + "id": "arcee-ai/trinity-large-preview:free", + "family": "trinity", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "cognitivecomputations/dolphin-mistral-24b-venice-edition:free": { + "id": "cognitivecomputations/dolphin-mistral-24b-venice-edition:free", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "bytedance-seed/seedream-4.5": { + "id": "bytedance-seed/seedream-4.5", + "family": "seed", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 4096, + "output": 4096 + } + }, + "black-forest-labs/flux.2-max": { + "id": "black-forest-labs/flux.2-max", + "family": "flux", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 46864, + "output": 46864 + } + }, + "black-forest-labs/flux.2-flex": { + "id": "black-forest-labs/flux.2-flex", + "family": "flux", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 67344, + "output": 67344 + } + }, + "black-forest-labs/flux.2-pro": { + "id": "black-forest-labs/flux.2-pro", + "family": "flux", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 46864, + "output": 46864 + } + }, + "black-forest-labs/flux.2-klein-4b": { + "id": "black-forest-labs/flux.2-klein-4b", + "family": "flux", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 40960, + "output": 40960 + } + }, + "nousresearch/hermes-3-llama-3.1-405b:free": { + "id": "nousresearch/hermes-3-llama-3.1-405b:free", + "family": "hermes", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "nousresearch/hermes-4-405b": { + "id": "NousResearch/Hermes-4-405B", + "family": "hermes", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192, + "input": 120000 + } + }, + "nousresearch/hermes-4-70b": { + "id": "NousResearch/Hermes-4-70B", + "family": "nousresearch", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192, + "input": 120000 + } + }, + "stepfun/step-3.5-flash": { + "id": "stepfun/step-3.5-flash", + "family": "step", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "mistralai/mistral-small-3.1-24b-instruct": { + "id": "mistralai/mistral-small-3.1-24b-instruct", + "family": "mistral-small", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 131072 + } + }, + "mistralai/devstral-2512": { + "id": "mistralai/devstral-2512", + "family": "devstral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + } + }, + "mistralai/mistral-small-2603": { + "id": "mistralai/mistral-small-2603", + "family": "mistral-small", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "mistralai/mistral-small-3.2-24b-instruct": { + "id": "mistralai/mistral-small-3.2-24b-instruct", + "family": "mistral-small", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "mistralai/devstral-medium-2507": { + "id": "mistralai/devstral-medium-2507", + "family": "devstral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "mistralai/devstral-small-2507": { + "id": "mistralai/devstral-small-2507", + "family": "devstral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "meta-llama/llama-3.2-11b-vision-instruct": { + "id": "meta-llama/llama-3.2-11b-vision-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "meta-llama/llama-3.2-3b-instruct:free": { + "id": "meta-llama/llama-3.2-3b-instruct:free", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "meta-llama/llama-3.3-70b-instruct:free": { + "id": "meta-llama/llama-3.3-70b-instruct:free", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "x-ai/grok-4.20-multi-agent-beta": { + "id": "x-ai/grok-4.20-multi-agent-beta", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 30000 + } + }, + "x-ai/grok-3-beta": { + "id": "x-ai/grok-3-beta", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 26215 + } + }, + "x-ai/grok-4": { + "id": "x-ai/grok-4", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 64000 + } + }, + "x-ai/grok-3-mini": { + "id": "x-ai/grok-3-mini", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 26215 + } + }, + "x-ai/grok-4.20-beta": { + "id": "x-ai/grok-4.20-beta", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 30000 + } + }, + "x-ai/grok-3-mini-beta": { + "id": "x-ai/grok-3-mini-beta", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 26215 + } + }, + "x-ai/grok-3": { + "id": "x-ai/grok-3", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 26215 + } + }, + "prime-intellect/intellect-3": { + "id": "prime-intellect/intellect-3", + "family": "intellect", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "nvidia/nemotron-3-nano-30b-a3b:free": { + "id": "nvidia/nemotron-3-nano-30b-a3b:free", + "family": "nemotron", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "nvidia/nemotron-nano-9b-v2:free": { + "id": "nvidia/nemotron-nano-9b-v2:free", + "family": "nemotron", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "nvidia/nemotron-3-super-120b-a12b:free": { + "id": "nvidia/nemotron-3-super-120b-a12b:free", + "family": "nemotron", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "nvidia/nemotron-nano-9b-v2": { + "id": "nvidia/nemotron-nano-9b-v2", + "family": "nemotron", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "nvidia/nemotron-nano-12b-v2-vl:free": { + "id": "nvidia/nemotron-nano-12b-v2-vl:free", + "family": "nemotron", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "inception/mercury-edit-2": { + "id": "inception/mercury-edit-2", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "inception/mercury-2": { + "id": "inception/mercury-2", + "family": "mercury", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "openai/gpt-oss-120b:exacto": { + "id": "openai/gpt-oss-120b:exacto", + "family": "gpt-oss", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "openai/gpt-5-chat": { + "id": "openai/gpt-5-chat", + "family": "gpt", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 128000, + "output": 16384, + "input": 111616 + } + }, + "openai/gpt-5.3-codex": { + "id": "openai/gpt-5.3-codex", + "family": "gpt", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000, + "input": 272000 + } + }, + "openai/gpt-oss-20b:free": { + "id": "openai/gpt-oss-20b:free", + "family": "gpt-oss", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "openai/gpt-5.4-mini": { + "id": "openai/gpt-5.4-mini", + "family": "gpt", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000, + "input": 272000 + } + }, + "openai/gpt-5.4-nano": { + "id": "openai/gpt-5.4-nano", + "family": "gpt", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000, + "input": 272000 + } + }, + "openai/gpt-5-image": { + "id": "openai/gpt-5-image", + "family": "gpt", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 400000, + "output": 128000 + } + }, + "openai/gpt-5.4-pro": { + "id": "openai/gpt-5.4-pro", + "family": "gpt", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1050000, + "input": 922000, + "output": 128000 + } + }, + "openai/gpt-oss-120b:free": { + "id": "openai/gpt-oss-120b:free", + "family": "gpt-oss", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "z-ai/glm-4.7": { + "id": "z-ai/glm-4.7", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 65535 + } + }, + "z-ai/glm-4.5-air:free": { + "id": "z-ai/glm-4.5-air:free", + "family": "glm-air", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 96000 + } + }, + "z-ai/glm-5": { + "id": "z-ai/glm-5", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "z-ai/glm-5.1": { + "id": "z-ai/glm-5.1", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 131072 + } + }, + "z-ai/glm-4.5": { + "id": "z-ai/glm-4.5", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 98304 + } + }, + "z-ai/glm-4.6:exacto": { + "id": "z-ai/glm-4.6:exacto", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 128000 + } + }, + "z-ai/glm-4.5-air": { + "id": "z-ai/glm-4.5-air", + "family": "glm-air", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 98304 + } + }, + "z-ai/glm-5-turbo": { + "id": "z-ai/glm-5-turbo", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 131072 + } + }, + "z-ai/glm-4.7-flash": { + "id": "z-ai/glm-4.7-flash", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 40551 + } + }, + "sourceful/riverflow-v2-standard-preview": { + "id": "sourceful/riverflow-v2-standard-preview", + "family": "sourceful", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "sourceful/riverflow-v2-fast-preview": { + "id": "sourceful/riverflow-v2-fast-preview", + "family": "sourceful", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "sourceful/riverflow-v2-max-preview": { + "id": "sourceful/riverflow-v2-max-preview", + "family": "sourceful", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "minimax/minimax-m2": { + "id": "minimax/minimax-m2", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262114, + "output": 262114 + } + }, + "minimax/minimax-m2.5:free": { + "id": "minimax/minimax-m2.5:free", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "minimax/minimax-m1": { + "id": "minimax/minimax-m1", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 40000 + } + }, + "qwen/qwen3.5-flash-02-23": { + "id": "qwen/qwen3.5-flash-02-23", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 65536 + } + }, + "qwen/qwen3.6-plus": { + "id": "qwen/qwen3.6-plus", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 65536 + } + }, + "qwen/qwen3-max": { + "id": "qwen/qwen3-max", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "qwen/qwen3-coder:exacto": { + "id": "qwen/qwen3-coder:exacto", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "qwen/qwen3-coder-flash": { + "id": "qwen/qwen3-coder-flash", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 65536 + } + }, + "qwen/qwen-2.5-coder-32b-instruct": { + "id": "qwen/qwen-2.5-coder-32b-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + } + }, + "qwen/qwen3-coder": { + "id": "qwen/qwen3-coder", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 66536 + } + }, + "qwen/qwen3.5-plus-02-15": { + "id": "qwen/qwen3.5-plus-02-15", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 65536 + } + }, + "qwen/qwen3-235b-a22b-07-25": { + "id": "qwen/qwen3-235b-a22b-07-25", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + } + }, + "google/gemini-2.5-pro-preview-05-06": { + "id": "google/gemini-2.5-pro-preview-05-06", + "family": "gemini-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "audio", + "image", + "pdf", + "text", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65535 + } + }, + "google/gemini-3.1-pro-preview-customtools": { + "id": "google/gemini-3.1-pro-preview-customtools", + "family": "gemini-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "audio", + "image", + "pdf", + "text", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "google/gemma-3-4b-it:free": { + "id": "google/gemma-3-4b-it:free", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + } + }, + "google/gemini-2.5-flash-lite-preview-09-2025": { + "id": "google/gemini-2.5-flash-lite-preview-09-2025", + "family": "gemini-flash-lite", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "google/gemini-2.0-flash-001": { + "id": "google/gemini-2.0-flash-001", + "family": "gemini-flash", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "audio", + "image", + "pdf", + "text", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 8192 + } + }, + "google/gemma-3n-e4b-it": { + "id": "google/gemma-3n-e4b-it", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "google/gemini-3.1-flash-lite-preview": { + "id": "google/gemini-3.1-flash-lite-preview", + "family": "gemini", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 65000 + } + }, + "google/gemma-3n-e4b-it:free": { + "id": "google/gemma-3n-e4b-it:free", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 2000 + } + }, + "google/gemini-3-pro-preview": { + "id": "google/gemini-3-pro-preview", + "family": "gemini-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video", + "audio", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "google/gemma-3n-e2b-it:free": { + "id": "google/gemma-3n-e2b-it:free", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 2000 + } + }, + "google/gemma-2-9b-it": { + "id": "google/gemma-2-9b-it", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1639 + } + }, + "google/gemma-4-31b-it": { + "id": "google/gemma-4-31B-it", + "family": "gemma", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + } + }, + "google/gemini-2.5-pro-preview-06-05": { + "id": "google/gemini-2.5-pro-preview-06-05", + "family": "gemini-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "google/gemma-3-12b-it": { + "id": "google/gemma-3-12b-it", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "google/gemma-3-27b-it:free": { + "id": "google/gemma-3-27b-it:free", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "google/gemma-4-31b-it:free": { + "id": "google/gemma-4-31b-it:free", + "family": "gemma", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "google/gemma-3-12b-it:free": { + "id": "google/gemma-3-12b-it:free", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + } + }, + "google/gemma-3-4b-it": { + "id": "google/gemma-3-4b-it", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 19200 + } + }, + "google/gemini-2.5-flash-preview-09-2025": { + "id": "google/gemini-2.5-flash-preview-09-2025", + "family": "gemini-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "google/gemma-3-27b-it": { + "id": "google/gemma-3-27b-it", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 37000, + "output": 8192, + "input": 100000 + } + }, + "google/gemma-4-26b-a4b-it": { + "id": "google/gemma-4-26b-a4b-it", + "family": "gemma", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + } + }, + "google/gemma-4-26b-a4b-it:free": { + "id": "google/gemma-4-26b-a4b-it:free", + "family": "gemma", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "google/gemini-2.5-flash-lite": { + "id": "google/gemini-2.5-flash-lite", + "family": "gemini-flash-lite", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "moonshotai/kimi-k2-0905": { + "id": "moonshotai/kimi-k2-0905", + "family": "kimi", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "moonshotai/kimi-k2-0905:exacto": { + "id": "moonshotai/kimi-k2-0905:exacto", + "family": "kimi", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 16384 + } + }, + "moonshotai/kimi-k2": { + "id": "moonshotai/kimi-k2", + "family": "kimi", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "anthropic/claude-opus-4.1": { + "id": "anthropic/claude-opus-4.1", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, "output": 32000 } }, + "anthropic/claude-3.7-sonnet": { + "id": "anthropic/claude-3.7-sonnet", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic/claude-opus-4.7": { + "id": "anthropic/claude-opus-4.7", + "family": "claude-opus", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "anthropic/claude-sonnet-4": { + "id": "anthropic/claude-sonnet-4", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic/claude-sonnet-4.5": { + "id": "anthropic/claude-sonnet-4.5", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic/claude-opus-4.5": { + "id": "anthropic/claude-opus-4.5", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic/claude-opus-4": { + "id": "anthropic/claude-opus-4", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "anthropic/claude-3.5-haiku": { + "id": "anthropic/claude-3.5-haiku", + "family": "claude-haiku", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + } + }, + "anthropic/claude-haiku-4.5": { + "id": "anthropic/claude-haiku-4.5", + "family": "claude-haiku", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "xiaomi/mimo-v2-omni": { + "id": "xiaomi/mimo-v2-omni", + "family": "mimo", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "audio", + "image", + "text", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + } + }, + "xiaomi/mimo-v2-pro": { + "id": "xiaomi/mimo-v2-pro", + "family": "mimo", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "accounts/fireworks/models/glm-5p1": { + "id": "accounts/fireworks/models/glm-5p1", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202800, + "output": 131072 + } + }, + "accounts/fireworks/models/deepseek-v3p2": { + "id": "accounts/fireworks/models/deepseek-v3p2", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 160000, + "output": 160000 + } + }, + "accounts/fireworks/models/minimax-m2p5": { + "id": "accounts/fireworks/models/minimax-m2p5", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 196608, + "output": 196608 + } + }, + "accounts/fireworks/models/glm-4p5-air": { + "id": "accounts/fireworks/models/glm-4p5-air", + "family": "glm-air", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "accounts/fireworks/models/glm-5": { + "id": "accounts/fireworks/models/glm-5", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 131072 + } + }, + "accounts/fireworks/models/deepseek-v3p1": { + "id": "accounts/fireworks/models/deepseek-v3p1", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 163840 + } + }, + "accounts/fireworks/models/kimi-k2-instruct": { + "id": "accounts/fireworks/models/kimi-k2-instruct", + "family": "kimi", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "accounts/fireworks/models/qwen3p6-plus": { + "id": "accounts/fireworks/models/qwen3p6-plus", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "accounts/fireworks/models/minimax-m2p1": { + "id": "accounts/fireworks/models/minimax-m2p1", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 200000 + } + }, + "accounts/fireworks/models/minimax-m2p7": { + "id": "accounts/fireworks/models/minimax-m2p7", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 196608, + "output": 196608 + } + }, + "accounts/fireworks/models/glm-4p7": { + "id": "accounts/fireworks/models/glm-4p7", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 198000, + "output": 198000 + } + }, + "accounts/fireworks/models/glm-4p5": { + "id": "accounts/fireworks/models/glm-4p5", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "accounts/fireworks/models/kimi-k2p5": { + "id": "accounts/fireworks/models/kimi-k2p5", + "family": "kimi-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "accounts/fireworks/models/gpt-oss-20b": { + "id": "accounts/fireworks/models/gpt-oss-20b", + "family": "gpt-oss", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "accounts/fireworks/models/gpt-oss-120b": { + "id": "accounts/fireworks/models/gpt-oss-120b", + "family": "gpt-oss", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "accounts/fireworks/models/kimi-k2-thinking": { + "id": "accounts/fireworks/models/kimi-k2-thinking", + "family": "kimi-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "accounts/fireworks/routers/kimi-k2p5-turbo": { + "id": "accounts/fireworks/routers/kimi-k2p5-turbo", + "family": "kimi-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "k2p5": { + "id": "k2p5", + "family": "kimi-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "glm-5": { + "id": "glm-5", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "mimo-v2-omni": { + "id": "mimo-v2-omni", + "family": "mimo", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 128000 + } + }, + "glm-5.1": { + "id": "glm-5.1", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 131072 + } + }, + "mimo-v2-pro": { + "id": "mimo-v2-pro", + "family": "mimo", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "intel/qwen3-coder-480b-a35b-instruct-int4-mixed-ar": { + "id": "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 106000, + "output": 4096 + } + }, + "mistralai/magistral-small-2506": { + "id": "mistralai/Magistral-Small-2506", + "family": "magistral-small", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "mistralai/mistral-large-instruct-2411": { + "id": "mistralai/Mistral-Large-Instruct-2411", + "family": "mistral-large", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "qwen-plus-character": { + "id": "qwen-plus-character", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 4096 + } + }, + "qwen2-5-math-7b-instruct": { + "id": "qwen2-5-math-7b-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "output": 3072 + } + }, + "qwen-doc-turbo": { + "id": "qwen-doc-turbo", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "qwen-math-turbo": { + "id": "qwen-math-turbo", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "output": 3072 + } + }, + "qwen3.5-flash": { + "id": "qwen3.5-flash", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 65536 + } + }, + "deepseek-v3-1": { + "id": "deepseek-v3-1", + "family": "deepseek", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + } + }, + "qwen-math-plus": { + "id": "qwen-math-plus", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "output": 3072 + } + }, + "qwen2-5-coder-32b-instruct": { + "id": "qwen2-5-coder-32b-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "qwen-deep-research": { + "id": "qwen-deep-research", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 32768 + } + }, + "deepseek-r1-distill-qwen-32b": { + "id": "deepseek-r1-distill-qwen-32b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 16384 + } + }, + "deepseek-r1-distill-qwen-7b": { + "id": "deepseek-r1-distill-qwen-7b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 16384 + } + }, + "tongyi-intent-detect-v3": { + "id": "tongyi-intent-detect-v3", + "family": "yi", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1024 + } + }, + "moonshot-kimi-k2-instruct": { + "id": "moonshot-kimi-k2-instruct", + "family": "kimi", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "deepseek-v3-2-exp": { + "id": "deepseek-v3-2-exp", + "family": "deepseek", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + } + }, + "deepseek-r1-distill-qwen-1-5b": { + "id": "deepseek-r1-distill-qwen-1-5b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 16384 + } + }, + "qwen2-5-coder-7b-instruct": { + "id": "qwen2-5-coder-7b-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "qwen2-5-math-72b-instruct": { + "id": "qwen2-5-math-72b-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "output": 3072 + } + }, + "deepseek-r1-distill-qwen-14b": { + "id": "deepseek-r1-distill-qwen-14b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 16384 + } + }, + "deepseek-v3": { + "id": "deepseek-v3", + "family": "deepseek", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "deepseek-r1-0528": { + "id": "deepseek-r1-0528", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 163840 + } + }, + "deepseek-r1-distill-llama-8b": { + "id": "deepseek-r1-distill-llama-8b", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 16384 + } + }, + "kimi/kimi-k2.5": { + "id": "kimi/kimi-k2.5", + "family": "kimi", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "siliconflow/deepseek-v3-0324": { + "id": "siliconflow/deepseek-v3-0324", + "family": "deepseek", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 163840 + } + }, + "siliconflow/deepseek-v3.2": { + "id": "siliconflow/deepseek-v3.2", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 65536 + } + }, + "siliconflow/deepseek-r1-0528": { + "id": "siliconflow/deepseek-r1-0528", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 32768 + } + }, + "siliconflow/deepseek-v3.1-terminus": { + "id": "siliconflow/deepseek-v3.1-terminus", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 65536 + } + }, + "gpt-5.2-pro": { + "id": "gpt-5.2-pro", + "family": "gpt", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 272000, + "input": 272000 + } + }, + "gpt-5.1-codex-mini": { + "id": "gpt-5.1-codex-mini", + "family": "gpt-codex", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000, + "input": 272000 + } + }, + "gpt-5-chat-latest": { + "id": "gpt-5-chat-latest", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000, + "input": 272000 + } + }, + "deepseek/deepseek-v3-0324": { + "id": "deepseek/deepseek-v3-0324", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "deepseek/deepseek-r1-0528": { + "id": "deepseek/deepseek-r1-0528", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "output": 8192 + } + }, + "xiaomimimo/mimo-v2-flash": { + "id": "XiaomiMiMo/MiMo-V2-Flash", + "family": "mimo", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32000 + } + }, + "baidu/ernie-4.5-vl-424b-a47b": { + "id": "baidu/ernie-4.5-vl-424b-a47b", + "family": "ernie", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 123000, + "output": 16000 + } + }, + "baidu/ernie-4.5-300b-a47b-paddle": { + "id": "baidu/ernie-4.5-300b-a47b-paddle", + "family": "ernie", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 123000, + "output": 12000 + } + }, + "qwen/qwen3-32b-fp8": { + "id": "qwen/qwen3-32b-fp8", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 40960, + "output": 20000 + } + }, + "qwen/qwen3-30b-a3b-fp8": { + "id": "qwen/qwen3-30b-a3b-fp8", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 40960, + "output": 20000 + } + }, + "qwen/qwen3-coder-next": { + "id": "qwen/qwen3-coder-next", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + } + }, + "qwen/qwen3-235b-a22b-fp8": { + "id": "qwen/qwen3-235b-a22b-fp8", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 40960, + "output": 20000 + } + }, + "ring-1t": { + "id": "Ring-1T", + "family": "ring", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32000 + } + }, + "ling-1t": { + "id": "Ling-1T", + "family": "ling", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32000 + } + }, + "qwen3-235b-a22b-instruct": { + "id": "qwen3-235b-a22b-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 64000 + } + }, + "qwen3-235b-a22b-thinking-2507": { + "id": "qwen3-235b-a22b-thinking-2507", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 8192 + } + }, + "kimi-k2-0905": { + "id": "Kimi-K2-0905", + "family": "kimi", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "qwen3-235b": { + "id": "qwen3-235b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32000 + } + }, + "kimi-k2": { + "id": "kimi-k2", + "family": "kimi", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "qwen3-max-preview": { + "id": "qwen3-max-preview", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 64000 + } + }, + "qwen/qwen3-embedding-8b": { + "id": "Qwen/Qwen3-Embedding-8B", + "family": "text-embedding", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 40960, + "output": 40960, + "input": 32768 + } + }, + "qwen/qwen3-embedding-4b": { + "id": "qwen/qwen3-embedding-4b", + "family": "qwen", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 2048 + } + }, + "minimaxai/minimax-m2.5": { + "id": "MiniMaxAI/MiniMax-M2.5", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "minimaxai/minimax-m2.7": { + "id": "MiniMaxAI/MiniMax-M2.7", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 131072 + } + }, + "minimaxai/minimax-m2.1": { + "id": "MiniMaxAI/MiniMax-M2.1", + "family": "minimax", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 197000, + "output": 131000, + "input": 120000 + } + }, + "deepseek/deepseek-chat": { + "id": "deepseek/deepseek-chat", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 163840 + } + }, + "deepseek/deepseek-v3.2-exp": { + "id": "deepseek/deepseek-v3.2-exp", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 163840 + }, + "family": "deepseek" + }, + "inclusionai/ring-1t": { + "id": "inclusionai/ring-1t", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 64000 + } + }, + "inclusionai/ling-1t": { + "id": "inclusionai/ling-1t", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 64000 + } + }, + "stepfun/step-3.5-flash-free": { + "id": "stepfun/step-3.5-flash-free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 64000 + } + }, + "stepfun/step-3": { + "id": "stepfun/step-3", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "output": 64000 + } + }, + "kuaishou/kat-coder-pro-v2": { + "id": "kuaishou/kat-coder-pro-v2", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 80000 + } + }, + "x-ai/grok-4.1-fast-non-reasoning": { + "id": "x-ai/grok-4.1-fast-non-reasoning", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 2000000 + } + }, + "x-ai/grok-4.2-fast": { + "id": "x-ai/grok-4.2-fast", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 30000 + } + }, + "x-ai/grok-4.2-fast-non-reasoning": { + "id": "x-ai/grok-4.2-fast-non-reasoning", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 30000 + } + }, + "openai/gpt-5.3-chat": { + "id": "openai/gpt-5.3-chat", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384, + "input": 111616 + }, + "family": "gpt" + }, + "z-ai/glm-4.7-flash-free": { + "id": "z-ai/glm-4.7-flash-free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "z-ai/glm-5v-turbo": { + "id": "z-ai/glm-5v-turbo", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 131072 + } + }, + "z-ai/glm-4.7-flashx": { + "id": "z-ai/glm-4.7-flashx", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "z-ai/glm-4.6v-flash-free": { + "id": "z-ai/glm-4.6v-flash-free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "z-ai/glm-4.6v-flash": { + "id": "z-ai/glm-4.6v-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "z-ai/glm-4.6v": { + "id": "z-ai/glm-4.6v", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "volcengine/doubao-seed-2.0-code": { + "id": "volcengine/doubao-seed-2.0-code", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 32000 + } + }, + "volcengine/doubao-seed-code": { + "id": "volcengine/doubao-seed-code", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 64000 + } + }, + "volcengine/doubao-seed-2.0-mini": { + "id": "volcengine/doubao-seed-2.0-mini", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 64000 + } + }, + "volcengine/doubao-seed-2.0-lite": { + "id": "volcengine/doubao-seed-2.0-lite", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 64000 + } + }, + "volcengine/doubao-seed-1.8": { + "id": "volcengine/doubao-seed-1.8", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 64000 + } + }, + "volcengine/doubao-seed-2.0-pro": { + "id": "volcengine/doubao-seed-2.0-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 64000 + } + }, + "baidu/ernie-5.0-thinking-preview": { + "id": "baidu/ernie-5.0-thinking-preview", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 64000 + } + }, + "minimax/minimax-m2.7-highspeed": { + "id": "minimax/minimax-m2.7-highspeed", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131100 + }, + "family": "minimax" + }, + "minimax/minimax-m2.5-lightning": { + "id": "minimax/minimax-m2.5-lightning", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "qwen/qwen3-coder-plus": { + "id": "qwen/qwen3-coder-plus", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 65536 + } + }, + "qwen/qwen3.5-flash": { + "id": "qwen/qwen3.5-flash", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1020000, + "output": 1020000 + } + }, + "qwen/qwen3.5-plus": { + "id": "Qwen/Qwen3.5-Plus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 65536 + }, + "family": "qwen" + }, + "sapiens-ai/agnes-1.5-lite": { + "id": "sapiens-ai/agnes-1.5-lite", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "sapiens-ai/agnes-1.5-pro": { + "id": "sapiens-ai/agnes-1.5-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "moonshotai/kimi-k2-thinking-turbo": { + "id": "moonshotai/kimi-k2-thinking-turbo", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262114, + "output": 262114 + }, + "family": "kimi-thinking" + }, + "solar-pro2": { + "id": "solar-pro2", + "family": "solar-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "output": 8192 + } + }, + "solar-mini": { + "id": "solar-mini", + "family": "solar-mini", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 4096 + } + }, + "solar-pro3": { + "id": "solar-pro3", + "family": "solar-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "deepseek/deepseek-r1-turbo": { + "id": "deepseek/deepseek-r1-turbo", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 64000, + "output": 16000 + } + }, + "deepseek/deepseek-ocr-2": { + "id": "deepseek/deepseek-ocr-2", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "deepseek/deepseek-ocr": { + "id": "deepseek/deepseek-ocr", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "deepseek/deepseek-r1-0528-qwen3-8b": { + "id": "deepseek/deepseek-r1-0528-qwen3-8b", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32000 + } + }, + "deepseek/deepseek-v3-turbo": { + "id": "deepseek/deepseek-v3-turbo", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 64000, + "output": 16000 + } + }, + "nousresearch/hermes-2-pro-llama-3-8b": { + "id": "nousresearch/hermes-2-pro-llama-3-8b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "zai-org/autoglm-phone-9b-multilingual": { + "id": "zai-org/autoglm-phone-9b-multilingual", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "output": 65536 + } + }, + "mistralai/mistral-nemo": { + "id": "mistralai/mistral-nemo", + "family": "mistral-nemo", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "baichuan/baichuan-m2-32b": { + "id": "baichuan/baichuan-m2-32b", + "family": "baichuan", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "meta-llama/llama-4-scout-17b-16e-instruct": { + "id": "meta-llama/llama-4-scout-17b-16e-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + }, + "family": "llama" + }, + "meta-llama/llama-3-8b-instruct": { + "id": "meta-llama/llama-3-8b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 16384 + } + }, + "meta-llama/llama-3-70b-instruct": { + "id": "meta-llama/llama-3-70b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8000 + } + }, + "sao10k/l31-70b-euryale-v2.2": { + "id": "sao10k/l31-70b-euryale-v2.2", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "sao10k/l3-70b-euryale-v2.1": { + "id": "sao10k/l3-70b-euryale-v2.1", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "sao10k/l3-8b-lunaris": { + "id": "sao10k/l3-8b-lunaris", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "baidu/ernie-4.5-vl-28b-a3b-thinking": { + "id": "baidu/ernie-4.5-vl-28b-a3b-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + } + }, + "baidu/ernie-4.5-21b-a3b": { + "id": "baidu/ernie-4.5-21b-a3b", + "family": "ernie", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 120000, + "output": 8000 + } + }, + "baidu/ernie-4.5-21b-a3b-thinking": { + "id": "baidu/ernie-4.5-21b-a3b-thinking", + "family": "ernie", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + } + }, + "minimax/minimax-m2.5-highspeed": { + "id": "minimax/minimax-m2.5-highspeed", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 0, + "output": 0 + } + }, + "qwen/qwen3-4b-fp8": { + "id": "qwen/qwen3-4b-fp8", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 20000 + } + }, + "qwen/qwen-mt-plus": { + "id": "qwen/qwen-mt-plus", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "output": 8192 + } + }, + "qwen/qwen3-8b-fp8": { + "id": "qwen/qwen3-8b-fp8", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 20000 + } + }, + "qwen/qwen-2.5-72b-instruct": { + "id": "qwen/qwen-2.5-72b-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 16384 + } + }, + "kwaipilot/kat-coder-pro": { + "id": "kwaipilot/kat-coder-pro", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 128000 + } + }, + "mimo-v2-tts": { + "id": "mimo-v2-tts", + "family": "mimo", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 8000, + "output": 16000 + } + }, + "zai-org/glm-5-fp8": { + "id": "zai-org/GLM-5-FP8", + "family": "glm", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 200000 + } + }, + "meta-llama/llama-3.1-70b-instruct": { + "id": "meta-llama/llama-3.1-70b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 26215 + } + }, + "openpipe/qwen3-14b-instruct": { + "id": "OpenPipe/Qwen3-14B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "microsoft/phi-4-mini-instruct": { + "id": "microsoft/phi-4-mini-instruct", + "family": "phi", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "nvidia/nvidia-nemotron-3-super-120b-a12b-fp8": { + "id": "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-FP8", + "family": "nemotron", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "opengvlab/internvl3-78b-tee": { + "id": "OpenGVLab/InternVL3-78B-TEE", + "family": "opengvlab", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "nousresearch/deephermes-3-mistral-24b-preview": { + "id": "NousResearch/DeepHermes-3-Mistral-24B-Preview", + "family": "nousresearch", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "nousresearch/hermes-4-405b-fp8-tee": { + "id": "NousResearch/Hermes-4-405B-FP8-TEE", + "family": "nousresearch", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + } + }, + "nousresearch/hermes-4.3-36b": { + "id": "NousResearch/Hermes-4.3-36B", + "family": "nousresearch", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + } + }, + "nousresearch/hermes-4-14b": { + "id": "NousResearch/Hermes-4-14B", + "family": "nousresearch", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 40960, + "output": 40960 + } + }, + "qwen/qwen3-30b-a3b": { + "id": "qwen/qwen3-30b-a3b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 40960, + "output": 40960 + } + }, + "qwen/qwen3-235b-a22b": { + "id": "Qwen/Qwen3-235B-A22B", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 131000 + } + }, + "qwen/qwen2.5-vl-72b-instruct-tee": { + "id": "Qwen/Qwen2.5-VL-72B-Instruct-TEE", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "qwen/qwen3guard-gen-0.6b": { + "id": "Qwen/Qwen3Guard-Gen-0.6B", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + } + }, + "qwen/qwen3-235b-a22b-instruct-2507-tee": { + "id": "Qwen/Qwen3-235B-A22B-Instruct-2507-TEE", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + } + }, + "qwen/qwen3.5-397b-a17b-tee": { + "id": "Qwen/Qwen3.5-397B-A17B-TEE", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + } + }, + "qwen/qwen3-coder-480b-a35b-instruct-fp8-tee": { + "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8-TEE", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "zai-org/glm-5.1-tee": { + "id": "zai-org/GLM-5.1-TEE", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 65535 + } + }, + "zai-org/glm-4.5-tee": { + "id": "zai-org/GLM-4.5-TEE", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + } + }, + "zai-org/glm-4.6-fp8": { + "id": "zai-org/GLM-4.6-FP8", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 65535 + } + }, + "zai-org/glm-4.7-fp8": { + "id": "zai-org/GLM-4.7-FP8", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096, + "input": 124000 + } + }, + "zai-org/glm-5-tee": { + "id": "zai-org/GLM-5-TEE", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 65535 + } + }, + "zai-org/glm-4.7-tee": { + "id": "zai-org/GLM-4.7-TEE", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 65535 + } + }, + "zai-org/glm-5-turbo": { + "id": "zai-org/GLM-5-Turbo", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 65535 + } + }, + "zai-org/glm-4.6-tee": { + "id": "zai-org/GLM-4.6-TEE", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 65536 + } + }, + "mistralai/devstral-2-123b-instruct-2512-tee": { + "id": "mistralai/Devstral-2-123B-Instruct-2512-TEE", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + } + }, + "chutesai/mistral-small-3.1-24b-instruct-2503": { + "id": "chutesai/Mistral-Small-3.1-24B-Instruct-2503", + "family": "chutesai", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "nvidia/nvidia-nemotron-3-nano-30b-a3b-bf16": { + "id": "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16", + "family": "nemotron", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "deepseek-ai/deepseek-r1-tee": { + "id": "deepseek-ai/DeepSeek-R1-TEE", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 163840 + } + }, + "deepseek-ai/deepseek-r1-distill-llama-70b": { + "id": "deepseek-ai/deepseek-r1-distill-llama-70b", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "deepseek-ai/deepseek-v3.1-tee": { + "id": "deepseek-ai/DeepSeek-V3.1-TEE", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 65536 + } + }, + "deepseek-ai/deepseek-v3-0324-tee": { + "id": "deepseek-ai/DeepSeek-V3-0324-TEE", + "family": "deepseek", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 65536 + } + }, + "deepseek-ai/deepseek-v3.2-speciale-tee": { + "id": "deepseek-ai/DeepSeek-V3.2-Speciale-TEE", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 65536 + } + }, + "deepseek-ai/deepseek-v3.1-terminus-tee": { + "id": "deepseek-ai/DeepSeek-V3.1-Terminus-TEE", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 65536 + } + }, + "deepseek-ai/deepseek-r1-0528-tee": { + "id": "deepseek-ai/DeepSeek-R1-0528-TEE", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 65536 + } + }, + "deepseek-ai/deepseek-v3.2-tee": { + "id": "deepseek-ai/DeepSeek-V3.2-TEE", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + } + }, + "openai/gpt-oss-120b-tee": { + "id": "openai/gpt-oss-120b-TEE", + "family": "gpt-oss", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + } + }, + "unsloth/llama-3.2-3b-instruct": { + "id": "unsloth/Llama-3.2-3B-Instruct", + "family": "unsloth", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "output": 16384 + } + }, + "unsloth/llama-3.2-1b-instruct": { + "id": "unsloth/Llama-3.2-1B-Instruct", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + } + }, + "unsloth/mistral-nemo-instruct-2407": { + "id": "unsloth/Mistral-Nemo-Instruct-2407", + "family": "unsloth", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "unsloth/mistral-small-24b-instruct-2501": { + "id": "unsloth/Mistral-Small-24B-Instruct-2501", + "family": "unsloth", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "moonshotai/kimi-k2-thinking-tee": { + "id": "moonshotai/Kimi-K2-Thinking-TEE", + "family": "kimi-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65535 + } + }, + "moonshotai/kimi-k2.5-tee": { + "id": "moonshotai/Kimi-K2.5-TEE", + "family": "kimi", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65535 + } + }, + "minimaxai/minimax-m2.1-tee": { + "id": "MiniMaxAI/MiniMax-M2.1-TEE", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 196608, + "output": 65536 + } + }, + "minimaxai/minimax-m2.5-tee": { + "id": "MiniMaxAI/MiniMax-M2.5-TEE", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 196608, + "output": 65536 + } + }, + "rednote-hilab/dots.ocr": { + "id": "rednote-hilab/dots.ocr", + "family": "rednote", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "tngtech/tng-r1t-chimera-turbo": { + "id": "tngtech/TNG-R1T-Chimera-Turbo", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 65536 + } + }, + "tngtech/deepseek-r1t-chimera": { + "id": "tngtech/DeepSeek-R1T-Chimera", + "family": "tngtech", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 163840 + } + }, + "tngtech/tng-r1t-chimera-tee": { + "id": "tngtech/TNG-R1T-Chimera-TEE", + "family": "tngtech", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 65536 + } + }, + "gpt-5.4-mini": { + "id": "gpt-5.4-mini", + "family": "gpt-mini", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "gpt-5.4-nano": { + "id": "gpt-5.4-nano", + "family": "gpt-nano", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "qwen/qwen3-coder-480b-a35b-instruct-turbo": { + "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 66536 + } + }, + "meta-llama/llama-3.1-8b-instruct-turbo": { + "id": "meta-llama/Llama-3.1-8B-Instruct-Turbo", + "family": "llama", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "meta-llama/llama-3.3-70b-instruct-turbo": { + "id": "meta-llama/Llama-3.3-70B-Instruct-Turbo", + "family": "llama", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + }, + "temperature": true + }, + "meta-llama/llama-3.1-70b-instruct-turbo": { + "id": "meta-llama/Llama-3.1-70B-Instruct-Turbo", + "family": "llama", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "minimaxai/minimax-m2": { + "id": "MiniMaxAI/MiniMax-M2", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "anthropic/claude-3-7-sonnet-latest": { + "id": "anthropic/claude-3-7-sonnet-latest", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic/claude-4-opus": { + "id": "anthropic/claude-4-opus", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "doubao-seed-1.6-flash": { + "id": "doubao-seed-1.6-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 32000 + } + }, + "doubao-seed-2.0-code": { + "id": "doubao-seed-2.0-code", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 128000 + } + }, + "doubao-1.5-thinking-pro": { + "id": "doubao-1.5-thinking-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16000 + } + }, + "claude-3.7-sonnet": { + "id": "claude-3.7-sonnet", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + }, + "family": "claude-sonnet" + }, + "qwen-vl-max-2025-01-25": { + "id": "qwen-vl-max-2025-01-25", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "qwen2.5-vl-72b-instruct": { + "id": "qwen2.5-vl-72b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "gemini-2.0-flash": { + "id": "gemini-2.0-flash", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 8192 + }, + "family": "gemini-flash" + }, + "qwen3-vl-30b-a3b-thinking": { + "id": "qwen3-vl-30b-a3b-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + }, + "family": "qwen" + }, + "gemini-3.0-pro-image-preview": { + "id": "gemini-3.0-pro-image-preview", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + } + }, + "claude-4.5-opus": { + "id": "claude-4.5-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + }, + "family": "claude-opus" + }, + "claude-4.0-opus": { + "id": "claude-4.0-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "claude-4.5-haiku": { + "id": "claude-4.5-haiku", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + }, + "family": "claude-haiku" + }, + "gemini-3.0-flash-preview": { + "id": "gemini-3.0-flash-preview", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "claude-3.5-sonnet": { + "id": "claude-3.5-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8200 + } + }, + "claude-4.0-sonnet": { + "id": "claude-4.0-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "doubao-seed-1.6-thinking": { + "id": "doubao-seed-1.6-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 32000 + } + }, + "qwen3-30b-a3b-thinking-2507": { + "id": "qwen3-30b-a3b-thinking-2507", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 8192 + }, + "family": "qwen" + }, + "glm-4.5-air": { + "id": "glm-4.5-air", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 98304 + }, + "family": "glm-air" + }, + "deepseek-v3.1": { + "id": "deepseek-v3.1", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + }, + "family": "deepseek" + }, + "claude-4.1-opus": { + "id": "claude-4.1-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "doubao-seed-2.0-mini": { + "id": "doubao-seed-2.0-mini", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 32000 + } + }, + "doubao-seed-1.6": { + "id": "doubao-seed-1.6", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 32000 + } + }, + "qwen2.5-vl-7b-instruct": { + "id": "qwen2.5-vl-7b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "kling-v2-6": { + "id": "kling-v2-6", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 99999999, + "output": 99999999 + } + }, + "gemini-3.0-pro-preview": { + "id": "gemini-3.0-pro-preview", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video", + "pdf", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "doubao-seed-2.0-lite": { + "id": "doubao-seed-2.0-lite", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 32000 + } + }, + "claude-3.5-haiku": { + "id": "claude-3.5-haiku", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + }, + "family": "claude-haiku" + }, + "gpt-oss-20b": { + "id": "gpt-oss-20b", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32766 + }, + "family": "gpt-oss" + }, + "mimo-v2-flash": { + "id": "mimo-v2-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 64000 + }, + "family": "mimo" + }, + "doubao-1.5-vision-pro": { + "id": "doubao-1.5-vision-pro", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16000 + } + }, + "claude-4.5-sonnet": { + "id": "claude-4.5-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + }, + "family": "claude-sonnet" + }, + "qwen-max-2025-01-25": { + "id": "qwen-max-2025-01-25", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "doubao-seed-2.0-pro": { + "id": "doubao-seed-2.0-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 128000 + } + }, + "deepseek/deepseek-v3.2-exp-thinking": { + "id": "deepseek/deepseek-v3.2-exp-thinking", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32000 + } + }, + "deepseek/deepseek-v3.1-terminus-thinking": { + "id": "deepseek/deepseek-v3.1-terminus-thinking", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32000 + } + }, + "deepseek/deepseek-v3.2-251201": { + "id": "deepseek/deepseek-v3.2-251201", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32000 + } + }, + "deepseek/deepseek-math-v2": { + "id": "deepseek/deepseek-math-v2", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 160000, + "output": 160000 + } + }, + "stepfun-ai/gelab-zero-4b-preview": { + "id": "stepfun-ai/gelab-zero-4b-preview", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 4096 + } + }, + "x-ai/grok-4-fast-reasoning": { + "id": "x-ai/grok-4-fast-reasoning", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 2000000 + } + }, + "x-ai/grok-4-fast-non-reasoning": { + "id": "x-ai/grok-4-fast-non-reasoning", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 2000000 + } + }, + "z-ai/autoglm-phone-9b": { + "id": "z-ai/autoglm-phone-9b", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 12800, + "output": 4096 + } + }, + "meituan/longcat-flash-chat": { + "id": "meituan/longcat-flash-chat", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + }, + "family": "longcat" + }, + "meituan/longcat-flash-lite": { + "id": "meituan/longcat-flash-lite", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 320000 + } + }, + "rekaai/reka-edge": { + "id": "rekaai/reka-edge", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "output": 16384 + } + }, + "rekaai/reka-flash-3": { + "id": "rekaai/reka-flash-3", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "output": 65536 + } + }, + "ai21/jamba-large-1.7": { + "id": "ai21/jamba-large-1.7", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 4096 + } + }, + "alibaba/tongyi-deepresearch-30b-a3b": { + "id": "alibaba/tongyi-deepresearch-30b-a3b", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "liquid/lfm-2-24b-a2b": { + "id": "liquid/lfm-2-24b-a2b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "writer/palmyra-x5": { + "id": "writer/palmyra-x5", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1040000, + "output": 8192 + } + }, + "ibm-granite/granite-4.0-h-micro": { + "id": "ibm-granite/granite-4.0-h-micro", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 32768 + } + }, + "perplexity/sonar-pro": { + "id": "perplexity/sonar-pro", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8000 + }, + "family": "sonar-pro" + }, + "perplexity/sonar-deep-research": { + "id": "perplexity/sonar-deep-research", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 25600 + } + }, + "perplexity/sonar-pro-search": { + "id": "perplexity/sonar-pro-search", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8000 + } + }, + "perplexity/sonar-reasoning-pro": { + "id": "perplexity/sonar-reasoning-pro", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 127000, + "output": 8000 + }, + "family": "sonar-reasoning" + }, + "deepseek/deepseek-r1-distill-qwen-32b": { + "id": "deepseek/deepseek-r1-distill-qwen-32b", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "openrouter/auto": { + "id": "openrouter/auto", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "audio", + "image", + "pdf", + "text", + "video" + ], + "output": [ + "image", + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 32768 + } + }, + "openrouter/bodybuilder": { + "id": "openrouter/bodybuilder", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "arcee-ai/virtuoso-large": { + "id": "arcee-ai/virtuoso-large", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 64000 + } + }, + "arcee-ai/spotlight": { + "id": "arcee-ai/spotlight", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 65537 + } + }, + "arcee-ai/trinity-large-thinking:free": { + "id": "arcee-ai/trinity-large-thinking:free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "arcee-ai/maestro-reasoning": { + "id": "arcee-ai/maestro-reasoning", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32000 + } + }, + "arcee-ai/coder-large": { + "id": "arcee-ai/coder-large", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "upstage/solar-pro-3": { + "id": "upstage/solar-pro-3", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "bytedance-seed/dola-seed-2.0-pro:free": { + "id": "bytedance-seed/dola-seed-2.0-pro:free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 128000 + } + }, + "bytedance-seed/seed-1.6": { + "id": "bytedance-seed/seed-1.6", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "bytedance-seed/seed-2.0-lite": { + "id": "bytedance-seed/seed-2.0-lite", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + } + }, + "bytedance-seed/seed-1.6-flash": { + "id": "bytedance-seed/seed-1.6-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "bytedance-seed/seed-2.0-mini": { + "id": "bytedance-seed/seed-2.0-mini", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + } + }, + "mancer/weaver": { + "id": "mancer/weaver", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8000, + "output": 2000 + } + }, + "kilo-auto/balanced": { + "id": "kilo-auto/balanced", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "kilo-auto/frontier": { + "id": "kilo-auto/frontier", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "kilo-auto/small": { + "id": "kilo-auto/small", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000 + } + }, + "kilo-auto/free": { + "id": "kilo-auto/free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "allenai/olmo-2-0325-32b-instruct": { + "id": "allenai/olmo-2-0325-32b-instruct", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "nousresearch/hermes-3-llama-3.1-70b": { + "id": "nousresearch/hermes-3-llama-3.1-70b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "nousresearch/hermes-3-llama-3.1-405b": { + "id": "nousresearch/hermes-3-llama-3.1-405b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "morph/morph-v3-fast": { + "id": "morph/morph-v3-fast", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16000, + "output": 16000 + }, + "family": "morph" + }, + "morph/morph-v3-large": { + "id": "morph/morph-v3-large", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 32000 + }, + "family": "morph" + }, + "eleutherai/llemma_7b": { + "id": "eleutherai/llemma_7b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "output": 4096 + } + }, + "alpindale/goliath-120b": { + "id": "alpindale/goliath-120b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 6144, + "output": 1024 + } + }, + "mistralai/mistral-large-2512": { + "id": "mistralai/mistral-large-2512", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 52429 + } + }, + "mistralai/devstral-medium": { + "id": "mistralai/devstral-medium", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 26215 + } + }, + "mistralai/pixtral-large-2411": { + "id": "mistralai/pixtral-large-2411", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "mistralai/mistral-small-24b-instruct-2501": { + "id": "mistralai/mistral-small-24b-instruct-2501", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 16384 + } + }, + "mistralai/mistral-large-2411": { + "id": "mistralai/mistral-large-2411", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 26215 + } + }, + "mistralai/mixtral-8x22b-instruct": { + "id": "mistralai/mixtral-8x22b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "output": 13108 + } + }, + "mistralai/mistral-large-2407": { + "id": "mistralai/mistral-large-2407", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "mistralai/voxtral-small-24b-2507": { + "id": "mistralai/Voxtral-Small-24B-2507", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "audio", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 32000 + }, + "family": "voxtral" + }, + "mistralai/mixtral-8x7b-instruct": { + "id": "mistralai/mixtral-8x7b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 16384 + } + }, + "mistralai/devstral-small": { + "id": "mistralai/devstral-small", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 26215 + } + }, + "mistralai/mistral-7b-instruct-v0.1": { + "id": "mistralai/mistral-7b-instruct-v0.1", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2824, + "output": 565 + } + }, + "meta-llama/llama-guard-3-8b": { + "id": "meta-llama/Llama-Guard-3-8B", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1024, + "input": 8000 + } + }, + "meta-llama/llama-guard-4-12b": { + "id": "meta-llama/llama-guard-4-12b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 1024 + }, + "family": "llama" + }, + "meta-llama/llama-3.2-1b-instruct": { + "id": "meta-llama/llama-3.2-1b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 60000, + "output": 12000 + } + }, + "x-ai/grok-4.20": { + "id": "x-ai/grok-4.20", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "pdf", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 2000000 + } + }, + "x-ai/grok-code-fast-1:optimized:free": { + "id": "x-ai/grok-code-fast-1:optimized:free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 10000 + } + }, + "x-ai/grok-4.20-multi-agent": { + "id": "x-ai/grok-4.20-multi-agent", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "image", + "pdf", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 2000000 + } + }, + "sao10k/l3-euryale-70b": { + "id": "sao10k/l3-euryale-70b", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "sao10k/l3-lunaris-8b": { + "id": "sao10k/l3-lunaris-8b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "sao10k/l3.3-euryale-70b": { + "id": "sao10k/l3.3-euryale-70b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "sao10k/l3.1-euryale-70b": { + "id": "sao10k/l3.1-euryale-70b", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "microsoft/phi-4": { + "id": "microsoft/phi-4", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16000, + "output": 4096 + }, + "family": "phi" + }, + "cohere/command-r7b-12-2024": { + "id": "cohere/command-r7b-12-2024", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4000 + } + }, + "cohere/command-a": { + "id": "cohere/command-a", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 8000 + }, + "family": "command" + }, + "cohere/command-r-08-2024": { + "id": "cohere/command-r-08-2024", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4000 + } + }, + "nvidia/llama-3.3-nemotron-super-49b-v1.5": { + "id": "nvidia/llama-3.3-nemotron-super-49b-v1.5", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "nvidia/nemotron-nano-12b-v2-vl": { + "id": "nvidia/nemotron-nano-12b-v2-vl", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + }, + "family": "nemotron" + }, + "nvidia/llama-3.1-nemotron-70b-instruct": { + "id": "nvidia/llama-3.1-nemotron-70b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "openai/gpt-4o-2024-05-13": { + "id": "openai/gpt-4o-2024-05-13", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "pdf", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "openai/gpt-4-1106-preview": { + "id": "openai/gpt-4-1106-preview", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "openai/gpt-4o-audio-preview": { + "id": "openai/gpt-4o-audio-preview", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "audio", + "text" + ], + "output": [ + "audio", + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "openai/gpt-3.5-turbo-16k": { + "id": "openai/gpt-3.5-turbo-16k", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16385, + "output": 4096 + } + }, + "openai/o3-pro": { + "id": "openai/o3-pro", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 100000, + "input": 100000 + }, + "family": "o-pro" + }, + "openai/gpt-4o-mini-2024-07-18": { + "id": "openai/gpt-4o-mini-2024-07-18", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "pdf", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "openai/gpt-4": { + "id": "openai/gpt-4", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + }, + "family": "gpt" + }, + "openai/gpt-4-0314": { + "id": "openai/gpt-4-0314", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8191, + "output": 4096 + } + }, + "openai/gpt-audio": { + "id": "openai/gpt-audio", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "audio", + "text" + ], + "output": [ + "audio", + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "openai/gpt-4o:extended": { + "id": "openai/gpt-4o:extended", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "pdf", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 64000 + } + }, + "openai/gpt-audio-mini": { + "id": "openai/gpt-audio-mini", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "audio", + "text" + ], + "output": [ + "audio", + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "openai/gpt-3.5-turbo-0613": { + "id": "openai/gpt-3.5-turbo-0613", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4095, + "output": 4096 + } + }, + "openai/gpt-5-image-mini": { + "id": "openai/gpt-5-image-mini", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "pdf", + "text" + ], + "output": [ + "image", + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000 + } + }, + "openai/gpt-3.5-turbo-instruct": { + "id": "openai/gpt-3.5-turbo-instruct", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 4096, + "input": 4096 + }, + "family": "gpt" + }, + "amazon/nova-premier-v1": { + "id": "amazon/nova-premier-v1", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 32000 + } + }, + "z-ai/glm-4-32b": { + "id": "z-ai/glm-4-32b", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "relace/relace-apply-3": { + "id": "relace/relace-apply-3", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 128000 + } + }, + "relace/relace-search": { + "id": "relace/relace-search", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 128000 + } + }, + "qwen/qwen2.5-coder-7b-instruct": { + "id": "qwen/qwen2.5-coder-7b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "qwen/qwen3-235b-a22b-2507": { + "id": "qwen/qwen3-235b-a22b-2507", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 52429 + } + }, + "qwen/qwen-vl-plus": { + "id": "qwen/qwen-vl-plus", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "qwen/qwen-max": { + "id": "qwen/qwen-max", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + } + }, + "qwen/qwen-plus": { + "id": "qwen/qwen-plus", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 32768 + } + }, + "qwen/qwen-plus-2025-07-28": { + "id": "qwen/qwen-plus-2025-07-28", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 32768 + } + }, + "qwen/qwen-2.5-7b-instruct": { + "id": "qwen/qwen-2.5-7b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 6554 + } + }, + "qwen/qwen-vl-max": { + "id": "qwen/qwen-vl-max", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "qwen/qwen3-max-thinking": { + "id": "qwen/qwen3-max-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "qwen/qwen-turbo": { + "id": "qwen/qwen-turbo", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "qwen/qwen-plus-2025-07-28:thinking": { + "id": "qwen/qwen-plus-2025-07-28:thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 32768 + } + }, + "alfredpros/codellama-7b-instruct-solidity": { + "id": "alfredpros/codellama-7b-instruct-solidity", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "output": 4096 + } + }, + "kwaipilot/kat-coder-pro-v2": { + "id": "kwaipilot/kat-coder-pro-v2", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + }, + "family": "kat-coder" + }, + "google/lyria-3-clip-preview": { + "id": "google/lyria-3-clip-preview", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "audio", + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "google/lyria-3-pro-preview": { + "id": "google/lyria-3-pro-preview", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "audio", + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "google/gemini-3-pro-image-preview": { + "id": "google/gemini-3-pro-image-preview", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "image", + "text" + ] + }, + "limit": { + "context": 65536, + "output": 32768 + } + }, + "google/gemini-2.5-flash-image": { + "id": "google/gemini-2.5-flash-image", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + }, + "family": "gemini-flash" + }, + "google/gemini-3.1-flash-image-preview": { + "id": "google/gemini-3.1-flash-image-preview", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + }, + "family": "gemini" + }, + "google/gemini-2.5-pro-preview": { + "id": "google/gemini-2.5-pro-preview", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "audio", + "image", + "pdf", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "google/gemma-2-27b-it": { + "id": "google/gemma-2-27b-it", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "google/gemini-2.0-flash-lite-001": { + "id": "google/gemini-2.0-flash-lite-001", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "audio", + "image", + "pdf", + "text", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 8192 + } + }, + "aion-labs/aion-2.0": { + "id": "aion-labs/aion-2.0", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "thedrummer/unslopnemo-12b": { + "id": "thedrummer/unslopnemo-12b", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "thedrummer/cydonia-24b-v4.1": { + "id": "thedrummer/cydonia-24b-v4.1", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "thedrummer/skyfall-36b-v2": { + "id": "thedrummer/skyfall-36b-v2", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "thedrummer/rocinante-12b": { + "id": "thedrummer/rocinante-12b", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "anthropic/claude-3.7-sonnet:thinking": { + "id": "anthropic/claude-3.7-sonnet:thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "pdf", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic/claude-opus-4.6-fast": { + "id": "anthropic/claude-opus-4.6-fast", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "anthropic/claude-3-haiku": { + "id": "anthropic/claude-3-haiku", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 4096 + }, + "family": "claude-haiku" + }, + "switchpoint/router": { + "id": "switchpoint/router", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "bytedance/ui-tars-1.5-7b": { + "id": "bytedance/ui-tars-1.5-7b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "image", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 2048 + } + }, + "tngtech/deepseek-r1t2-chimera": { + "id": "tngtech/deepseek-r1t2-chimera", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 163840 + } + }, + "anthropic--claude-4.6-opus": { + "id": "anthropic--claude-4.6-opus", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "anthropic--claude-3-haiku": { + "id": "anthropic--claude-3-haiku", + "family": "claude-haiku", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 4096 + } + }, + "anthropic--claude-3-opus": { + "id": "anthropic--claude-3-opus", + "family": "claude-opus", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 4096 + } + }, + "anthropic--claude-3.7-sonnet": { + "id": "anthropic--claude-3.7-sonnet", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic--claude-4.5-sonnet": { + "id": "anthropic--claude-4.5-sonnet", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic--claude-4.6-sonnet": { + "id": "anthropic--claude-4.6-sonnet", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "anthropic--claude-4.5-opus": { + "id": "anthropic--claude-4.5-opus", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic--claude-4-opus": { + "id": "anthropic--claude-4-opus", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "anthropic--claude-3-sonnet": { + "id": "anthropic--claude-3-sonnet", + "family": "claude-sonnet", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 4096 + } + }, + "anthropic--claude-4-sonnet": { + "id": "anthropic--claude-4-sonnet", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic--claude-4.5-haiku": { + "id": "anthropic--claude-4.5-haiku", + "family": "claude-haiku", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic--claude-3.5-sonnet": { + "id": "anthropic--claude-3.5-sonnet", + "family": "claude-sonnet", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + } + }, + "auto": { + "id": "auto", + "family": "auto", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "morph-v3-fast": { + "id": "morph-v3-fast", + "family": "morph", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16000, + "output": 16000 + } + }, + "morph-v3-large": { + "id": "morph-v3-large", + "family": "morph", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 32000 + } + }, + "workers-ai/@cf/myshell-ai/melotts": { + "id": "workers-ai/@cf/myshell-ai/melotts", + "family": "melotts", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/ibm-granite/granite-4.0-h-micro": { + "id": "workers-ai/@cf/ibm-granite/granite-4.0-h-micro", + "family": "granite", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/huggingface/distilbert-sst-2-int8": { + "id": "workers-ai/@cf/huggingface/distilbert-sst-2-int8", + "family": "distilbert", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/zai-org/glm-4.7-flash": { + "id": "workers-ai/@cf/zai-org/glm-4.7-flash", + "family": "glm-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "workers-ai/@cf/pipecat-ai/smart-turn-v2": { + "id": "workers-ai/@cf/pipecat-ai/smart-turn-v2", + "family": "smart-turn", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/mistralai/mistral-small-3.1-24b-instruct": { + "id": "workers-ai/@cf/mistralai/mistral-small-3.1-24b-instruct", + "family": "mistral-small", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/facebook/bart-large-cnn": { + "id": "workers-ai/@cf/facebook/bart-large-cnn", + "family": "bart", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/aisingapore/gemma-sea-lion-v4-27b-it": { + "id": "workers-ai/@cf/aisingapore/gemma-sea-lion-v4-27b-it", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/nvidia/nemotron-3-120b-a12b": { + "id": "workers-ai/@cf/nvidia/nemotron-3-120b-a12b", + "family": "nemotron", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "workers-ai/@cf/deepseek-ai/deepseek-r1-distill-qwen-32b": { + "id": "workers-ai/@cf/deepseek-ai/deepseek-r1-distill-qwen-32b", + "family": "deepseek-thinking", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/openai/gpt-oss-20b": { + "id": "workers-ai/@cf/openai/gpt-oss-20b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/openai/gpt-oss-120b": { + "id": "workers-ai/@cf/openai/gpt-oss-120b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/mistral/mistral-7b-instruct-v0.1": { + "id": "workers-ai/@cf/mistral/mistral-7b-instruct-v0.1", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/meta/llama-4-scout-17b-16e-instruct": { + "id": "workers-ai/@cf/meta/llama-4-scout-17b-16e-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/meta/llama-3-8b-instruct-awq": { + "id": "workers-ai/@cf/meta/llama-3-8b-instruct-awq", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/meta/llama-guard-3-8b": { + "id": "workers-ai/@cf/meta/llama-guard-3-8b", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/meta/m2m100-1.2b": { + "id": "workers-ai/@cf/meta/m2m100-1.2b", + "family": "m2m", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/meta/llama-2-7b-chat-fp16": { + "id": "workers-ai/@cf/meta/llama-2-7b-chat-fp16", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/meta/llama-3.2-11b-vision-instruct": { + "id": "workers-ai/@cf/meta/llama-3.2-11b-vision-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/meta/llama-3.3-70b-instruct-fp8-fast": { + "id": "workers-ai/@cf/meta/llama-3.3-70b-instruct-fp8-fast", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/meta/llama-3.2-1b-instruct": { + "id": "workers-ai/@cf/meta/llama-3.2-1b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/meta/llama-3.1-8b-instruct-fp8": { + "id": "workers-ai/@cf/meta/llama-3.1-8b-instruct-fp8", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/meta/llama-3.2-3b-instruct": { + "id": "workers-ai/@cf/meta/llama-3.2-3b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/meta/llama-3.1-8b-instruct-awq": { + "id": "workers-ai/@cf/meta/llama-3.1-8b-instruct-awq", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/meta/llama-3-8b-instruct": { + "id": "workers-ai/@cf/meta/llama-3-8b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/meta/llama-3.1-8b-instruct": { + "id": "workers-ai/@cf/meta/llama-3.1-8b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/qwen/qwen2.5-coder-32b-instruct": { + "id": "workers-ai/@cf/qwen/qwen2.5-coder-32b-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/qwen/qwen3-embedding-0.6b": { + "id": "workers-ai/@cf/qwen/qwen3-embedding-0.6b", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/qwen/qwq-32b": { + "id": "workers-ai/@cf/qwen/qwq-32b", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/qwen/qwen3-30b-a3b-fp8": { + "id": "workers-ai/@cf/qwen/qwen3-30b-a3b-fp8", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/google/gemma-3-12b-it": { + "id": "workers-ai/@cf/google/gemma-3-12b-it", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/moonshotai/kimi-k2.5": { + "id": "workers-ai/@cf/moonshotai/kimi-k2.5", + "family": "kimi", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "workers-ai/@cf/ai4bharat/indictrans2-en-indic-1b": { + "id": "workers-ai/@cf/ai4bharat/indictrans2-en-indic-1B", + "family": "indictrans", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/pfnet/plamo-embedding-1b": { + "id": "workers-ai/@cf/pfnet/plamo-embedding-1b", + "family": "plamo", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/baai/bge-small-en-v1.5": { + "id": "workers-ai/@cf/baai/bge-small-en-v1.5", + "family": "bge", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/baai/bge-large-en-v1.5": { + "id": "workers-ai/@cf/baai/bge-large-en-v1.5", + "family": "bge", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/baai/bge-reranker-base": { + "id": "workers-ai/@cf/baai/bge-reranker-base", + "family": "bge", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/baai/bge-base-en-v1.5": { + "id": "workers-ai/@cf/baai/bge-base-en-v1.5", + "family": "bge", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/baai/bge-m3": { + "id": "workers-ai/@cf/baai/bge-m3", + "family": "bge", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/deepgram/aura-2-en": { + "id": "workers-ai/@cf/deepgram/aura-2-en", + "family": "aura", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/deepgram/aura-2-es": { + "id": "workers-ai/@cf/deepgram/aura-2-es", + "family": "aura", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "workers-ai/@cf/deepgram/nova-3": { + "id": "workers-ai/@cf/deepgram/nova-3", + "family": "nova", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "anthropic/claude-opus-4-7": { + "id": "anthropic/claude-opus-4-7", + "family": "claude-opus", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "anthropic/claude-opus-4-1": { + "id": "anthropic/claude-opus-4-1", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "anthropic/claude-3-5-haiku": { + "id": "anthropic/claude-3-5-haiku", + "family": "claude-haiku", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + } + }, + "anthropic/claude-3.5-sonnet": { + "id": "anthropic/claude-3.5-sonnet", + "family": "claude-sonnet", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + } + }, + "anthropic/claude-3-sonnet": { + "id": "anthropic/claude-3-sonnet", + "family": "claude-sonnet", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 4096 + } + }, + "anthropic/claude-3-opus": { + "id": "anthropic/claude-3-opus", + "family": "claude-opus", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 4096 + } + }, + "claude-opus-4.6": { + "id": "claude-opus-4.6", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 144000, + "input": 128000, + "output": 64000 + } + }, + "claude-sonnet-4": { + "id": "claude-sonnet-4", + "family": "claude-sonnet", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 128000, + "output": 64000 + } + }, + "claude-sonnet-4.5": { + "id": "claude-sonnet-4.5", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 144000, + "input": 128000, + "output": 32000 + } + }, + "claude-opus-41": { + "id": "claude-opus-41", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 80000, + "output": 16000 + } + }, + "claude-opus-4.5": { + "id": "claude-opus-4.5", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 160000, + "input": 128000, + "output": 32000 + } + }, + "claude-haiku-4.5": { + "id": "claude-haiku-4.5", + "family": "claude-haiku", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 144000, + "input": 128000, + "output": 32000 + } + }, + "claude-sonnet-4.6": { + "id": "claude-sonnet-4.6", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 128000, + "output": 32000 + } + }, + "glm-5v-turbo": { + "id": "glm-5v-turbo", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 131072 + } + }, + "glm-4.7-flashx": { + "id": "glm-4.7-flashx", + "family": "glm-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 131072 + } + }, + "glm-5-turbo": { + "id": "glm-5-turbo", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 131072 + } + }, + "glm-4.5-flash": { + "id": "glm-4.5-flash", + "family": "glm-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 98304 + } + }, + "glm-4.7-flash": { + "id": "glm-4.7-flash", + "family": "glm-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 131072 + } + }, + "claude-haiku-4-5": { + "id": "claude-haiku-4-5", + "family": "claude-haiku", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "glm-4.7-free": { + "id": "glm-4.7-free", + "family": "glm-free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "gemini-3.1-pro": { + "id": "gemini-3.1-pro", + "family": "gemini-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video", + "audio", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "kimi-k2.5-free": { + "id": "kimi-k2.5-free", + "family": "kimi-free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "claude-opus-4-7": { + "id": "claude-opus-4-7", + "family": "claude-opus", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "minimax-m2.5-free": { + "id": "minimax-m2.5-free", + "family": "minimax-free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "big-pickle": { + "id": "big-pickle", + "family": "big-pickle", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 128000 + } + }, + "claude-opus-4-1": { + "id": "claude-opus-4-1", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "claude-3-5-haiku": { + "id": "claude-3-5-haiku", + "family": "claude", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + } + }, + "gemini-3-flash": { + "id": "gemini-3-flash", + "family": "gemini-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video", + "audio", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "trinity-large-preview-free": { + "id": "trinity-large-preview-free", + "family": "trinity", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "gpt-5.4-pro": { + "id": "gpt-5.4-pro", + "family": "gpt-pro", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "glm-5-free": { + "id": "glm-5-free", + "family": "glm-free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "claude-opus-4-5": { + "id": "claude-opus-4-5", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "minimax-m2.1-free": { + "id": "minimax-m2.1-free", + "family": "minimax-free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "qwen3.6-plus-free": { + "id": "qwen3.6-plus-free", + "family": "qwen-free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 64000 + } + }, + "gemini-3-pro": { + "id": "gemini-3-pro", + "family": "gemini-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video", + "audio", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "grok-code": { + "id": "grok-code", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "mimo-v2-flash-free": { + "id": "mimo-v2-flash-free", + "family": "mimo-flash-free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + } + }, + "gpt-5.3-codex-spark": { + "id": "gpt-5.3-codex-spark", + "family": "gpt-codex-spark", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 100000, + "output": 32000 + } + }, + "claude-sonnet-4-5": { + "id": "claude-sonnet-4-5", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "qwen3-coder": { + "id": "qwen3-coder", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 16384 + } + }, + "mimo-v2-pro-free": { + "id": "mimo-v2-pro-free", + "family": "mimo-pro-free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 64000 + } + }, + "nemotron-3-super-free": { + "id": "nemotron-3-super-free", + "family": "nemotron-free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 128000 + } + }, + "mimo-v2-omni-free": { + "id": "mimo-v2-omni-free", + "family": "mimo-omni-free", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 64000 + } + }, + "step-3.5-flash-2603": { + "id": "step-3.5-flash-2603", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 256000 + } + }, + "step-1-32k": { + "id": "step-1-32k", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 32768 + } + }, + "step-3.5-flash": { + "id": "step-3.5-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 256000 + } + }, + "step-2-16k": { + "id": "step-2-16k", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "input": 16384, + "output": 8192 + } + }, + "black-forest-labs/flux-schnell": { + "id": "black-forest-labs/flux-schnell", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 77, + "input": 77, + "output": 0 + } + }, + "black-forest-labs/flux-dev": { + "id": "black-forest-labs/flux-dev", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 77, + "input": 77, + "output": 0 + } + }, + "qwen/qwen3-32b-fast": { + "id": "Qwen/Qwen3-32B-fast", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 120000, + "output": 8192 + } + }, + "qwen/qwen2.5-coder-7b-fast": { + "id": "Qwen/Qwen2.5-Coder-7B-fast", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 120000, + "output": 8192 + } + }, + "primeintellect/intellect-3": { + "id": "PrimeIntellect/INTELLECT-3", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 120000, + "output": 8192 + } + }, + "baai/bge-en-icl": { + "id": "BAAI/bge-en-icl", + "family": "text-embedding", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "input": 32768, + "output": 0 + } + }, + "baai/bge-multilingual-gemma2": { + "id": "BAAI/bge-multilingual-gemma2", + "family": "text-embedding", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "input": 8192, + "output": 0 + } + }, + "meta-llama/meta-llama-3.1-8b-instruct-fast": { + "id": "meta-llama/Meta-Llama-3.1-8B-Instruct-fast", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 120000, + "output": 4096 + } + }, + "meta-llama/llama-3.3-70b-instruct-fast": { + "id": "meta-llama/Llama-3.3-70B-Instruct-fast", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 120000, + "output": 8192 + } + }, + "nvidia/nemotron-nano-v2-12b": { + "id": "nvidia/Nemotron-Nano-V2-12b", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "input": 30000, + "output": 4096 + } + }, + "nvidia/llama-3_1-nemotron-ultra-253b-v1": { + "id": "nvidia/Llama-3_1-Nemotron-Ultra-253B-v1", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 120000, + "output": 4096 + } + }, + "nvidia/nvidia-nemotron-3-nano-30b-a3b": { + "id": "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "input": 30000, + "output": 4096 + } + }, + "deepseek-ai/deepseek-v3-0324-fast": { + "id": "deepseek-ai/DeepSeek-V3-0324-fast", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 120000, + "output": 8192 + } + }, + "deepseek-ai/deepseek-r1-0528-fast": { + "id": "deepseek-ai/DeepSeek-R1-0528-fast", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "google/gemma-2-2b-it": { + "id": "google/gemma-2-2b-it", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 8000, + "output": 4096 + } + }, + "google/gemma-2-9b-it-fast": { + "id": "google/gemma-2-9b-it-fast", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "input": 8000, + "output": 4096 + } + }, + "google/gemma-3-27b-it-fast": { + "id": "google/gemma-3-27b-it-fast", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 110000, + "input": 100000, + "output": 8192 + } + }, + "moonshotai/kimi-k2.5-fast": { + "id": "moonshotai/Kimi-K2.5-fast", + "family": "kimi", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "input": 256000, + "output": 8192 + } + }, + "intfloat/e5-mistral-7b-instruct": { + "id": "intfloat/e5-mistral-7b-instruct", + "family": "mistral", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "input": 32768, + "output": 4096 + } + }, + "topazlabs-co/topazlabs": { + "id": "topazlabs-co/topazlabs", + "family": "topazlabs", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 204, + "output": 0 + } + }, + "novita/kimi-k2.5": { + "id": "novita/kimi-k2.5", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 262144 + } + }, + "novita/glm-4.7": { + "id": "novita/glm-4.7", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 205000, + "output": 131072 + } + }, + "novita/glm-5": { + "id": "novita/glm-5", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 205000, + "output": 131072 + } + }, + "novita/minimax-m2.1": { + "id": "novita/minimax-m2.1", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 205000, + "output": 131072 + } + }, + "novita/glm-4.6": { + "id": "novita/glm-4.6", + "family": "glm", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 0, + "output": 0 + } + }, + "novita/glm-4.6v": { + "id": "novita/glm-4.6v", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 32768 + } + }, + "novita/deepseek-v3.2": { + "id": "novita/deepseek-v3.2", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 0 + } + }, + "novita/glm-4.7-flash": { + "id": "novita/glm-4.7-flash", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 65500 + } + }, + "novita/glm-4.7-n": { + "id": "novita/glm-4.7-n", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 205000, + "output": 131072 + } + }, + "novita/kimi-k2-thinking": { + "id": "novita/kimi-k2-thinking", + "family": "kimi", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 0 + } + }, + "fireworks-ai/kimi-k2.5-fw": { + "id": "fireworks-ai/kimi-k2.5-fw", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "input": 245760, + "output": 16384 + } + }, + "elevenlabs/elevenlabs-v2.5-turbo": { + "id": "elevenlabs/elevenlabs-v2.5-turbo", + "family": "elevenlabs", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 128000, + "output": 0 + } + }, + "elevenlabs/elevenlabs-v3": { + "id": "elevenlabs/elevenlabs-v3", + "family": "elevenlabs", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 128000, + "output": 0 + } + }, + "elevenlabs/elevenlabs-music": { + "id": "elevenlabs/elevenlabs-music", + "family": "elevenlabs", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 2000, + "output": 0 + } + }, + "cerebras/gpt-oss-120b-cs": { + "id": "cerebras/gpt-oss-120b-cs", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 0 + } + }, + "cerebras/llama-3.1-8b-cs": { + "id": "cerebras/llama-3.1-8b-cs", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 0 + } + }, + "cerebras/qwen3-32b-cs": { + "id": "cerebras/qwen3-32b-cs", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 0, + "output": 0 + } + }, + "cerebras/qwen3-235b-2507-cs": { + "id": "cerebras/qwen3-235b-2507-cs", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 0, + "output": 0 + } + }, + "cerebras/llama-3.3-70b-cs": { + "id": "cerebras/llama-3.3-70b-cs", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 0, + "output": 0 + } + }, + "stabilityai/stablediffusionxl": { + "id": "stabilityai/stablediffusionxl", + "family": "stable-diffusion", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 200, + "output": 0 + } + }, + "xai/grok-code-fast-1": { + "id": "xai/grok-code-fast-1", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 10000 + } + }, + "xai/grok-4-fast-reasoning": { + "id": "xai/grok-4-fast-reasoning", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 256000 + } + }, + "xai/grok-4.1-fast-non-reasoning": { + "id": "xai/grok-4.1-fast-non-reasoning", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 30000 + } + }, + "xai/grok-4": { + "id": "xai/grok-4", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 64000 + } + }, + "xai/grok-3-mini": { + "id": "xai/grok-3-mini", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "xai/grok-4.20-multi-agent": { + "id": "xai/grok-4.20-multi-agent", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 2000000 + }, + "family": "grok" + }, + "xai/grok-3": { + "id": "xai/grok-3", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "xai/grok-4-fast-non-reasoning": { + "id": "xai/grok-4-fast-non-reasoning", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 30000 + } + }, + "xai/grok-4.1-fast-reasoning": { + "id": "xai/grok-4.1-fast-reasoning", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 30000 + } + }, + "runwayml/runway": { + "id": "runwayml/runway", + "family": "runway", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 256, + "output": 0 + } + }, + "runwayml/runway-gen-4-turbo": { + "id": "runwayml/runway-gen-4-turbo", + "family": "runway", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 256, + "output": 0 + } + }, + "openai/sora-2-pro": { + "id": "openai/sora-2-pro", + "family": "sora", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 0, + "output": 0 + } + }, + "openai/gpt-4o-aug": { + "id": "openai/gpt-4o-aug", + "family": "gpt", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "openai/gpt-4-classic-0314": { + "id": "openai/gpt-4-classic-0314", + "family": "gpt", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 4096 + } + }, + "openai/dall-e-3": { + "id": "openai/dall-e-3", + "family": "dall-e", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 800, + "output": 0 + } + }, + "openai/gpt-image-1": { + "id": "openai/gpt-image-1", + "family": "gpt", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 128000, + "output": 0 + } + }, + "openai/gpt-image-1-mini": { + "id": "openai/gpt-image-1-mini", + "family": "gpt", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 0, + "output": 0 + } + }, + "openai/gpt-4o-search": { + "id": "openai/gpt-4o-search", + "family": "gpt", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "openai/gpt-5.3-codex-spark": { + "id": "openai/gpt-5.3-codex-spark", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "openai/gpt-3.5-turbo-raw": { + "id": "openai/gpt-3.5-turbo-raw", + "family": "gpt", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4524, + "output": 2048 + } + }, + "openai/sora-2": { + "id": "openai/sora-2", + "family": "sora", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 0, + "output": 0 + } + }, + "openai/gpt-5.2-instant": { + "id": "openai/gpt-5.2-instant", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "openai/gpt-4o-mini-search": { + "id": "openai/gpt-4o-mini-search", + "family": "gpt-mini", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "openai/gpt-image-1.5": { + "id": "openai/gpt-image-1.5", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 128000, + "output": 0 + } + }, + "openai/gpt-5.1-instant": { + "id": "openai/gpt-5.1-instant", + "family": "gpt", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 128000, + "output": 16384, + "input": 111616 + } + }, + "openai/gpt-4-classic": { + "id": "openai/gpt-4-classic", + "family": "gpt", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 4096 + } + }, + "openai/gpt-5.3-instant": { + "id": "openai/gpt-5.3-instant", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "input": 111616, + "output": 16384 + } + }, + "google/veo-3-fast": { + "id": "google/veo-3-fast", + "family": "veo", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 480, + "output": 0 + } + }, + "google/veo-3.1-fast": { + "id": "google/veo-3.1-fast", + "family": "veo", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 480, + "output": 0 + } + }, + "google/gemini-3.1-pro": { + "id": "google/gemini-3.1-pro", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "google/imagen-3-fast": { + "id": "google/imagen-3-fast", + "family": "imagen", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 480, + "output": 0 + } + }, + "google/gemini-2.0-flash": { + "id": "google/gemini-2.0-flash", + "family": "gemini-flash", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 8192 + } + }, + "google/gemini-deep-research": { + "id": "google/gemini-deep-research", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 0 + } + }, + "google/imagen-3": { + "id": "google/imagen-3", + "family": "imagen", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 480, + "output": 0 + } + }, + "google/nano-banana": { + "id": "google/nano-banana", + "family": "nano-banana", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 65536, + "output": 0 + } + }, + "google/gemini-3.1-flash-lite": { + "id": "google/gemini-3.1-flash-lite", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "google/gemini-3-flash": { + "id": "google/gemini-3-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + }, + "family": "gemini-flash" + }, + "google/veo-3.1": { + "id": "google/veo-3.1", + "family": "veo", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 480, + "output": 0 + } + }, + "google/lyria": { + "id": "google/lyria", + "family": "lyria", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 0, + "output": 0 + } + }, + "google/imagen-4-ultra": { + "id": "google/imagen-4-ultra", + "family": "imagen", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 480, + "output": 0 + } + }, + "google/nano-banana-pro": { + "id": "google/nano-banana-pro", + "family": "nano-banana", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 65536, + "output": 0 + } + }, + "google/gemini-3-pro": { + "id": "google/gemini-3-pro", + "family": "gemini-pro", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "google/imagen-4-fast": { + "id": "google/imagen-4-fast", + "family": "imagen", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 480, + "output": 0 + } + }, + "google/veo-3": { + "id": "google/veo-3", + "family": "veo", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 480, + "output": 0 + } + }, + "google/imagen-4": { + "id": "google/imagen-4", + "family": "imagen", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 480, + "output": 0 + } + }, + "google/gemma-4-31b": { + "id": "google/gemma-4-31b", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 8192 + } + }, + "google/gemini-2.0-flash-lite": { + "id": "google/gemini-2.0-flash-lite", + "family": "gemini-flash-lite", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 8192 + } + }, + "google/veo-2": { + "id": "google/veo-2", + "family": "veo", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 480, + "output": 0 + } + }, + "lumalabs/ray2": { + "id": "lumalabs/ray2", + "family": "ray", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "video" + ] + }, + "limit": { + "context": 5000, + "output": 0 + } + }, + "anthropic/claude-sonnet-3.5": { + "id": "anthropic/claude-sonnet-3.5", + "family": "claude-sonnet", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 189096, + "output": 8192 + } + }, + "anthropic/claude-haiku-3": { + "id": "anthropic/claude-haiku-3", + "family": "claude-haiku", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 189096, + "output": 8192 + } + }, + "anthropic/claude-sonnet-3.7": { + "id": "anthropic/claude-sonnet-3.7", + "family": "claude-sonnet", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 196608, + "output": 128000 + } + }, + "anthropic/claude-haiku-3.5": { + "id": "anthropic/claude-haiku-3.5", + "family": "claude-haiku", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 189096, + "output": 8192 + } + }, + "anthropic/claude-sonnet-3.5-june": { + "id": "anthropic/claude-sonnet-3.5-june", + "family": "claude-sonnet", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 189096, + "output": 8192 + } + }, + "ideogramai/ideogram": { + "id": "ideogramai/ideogram", + "family": "ideogram", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 150, + "output": 0 + } + }, + "ideogramai/ideogram-v2": { + "id": "ideogramai/ideogram-v2", + "family": "ideogram", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 150, + "output": 0 + } + }, + "ideogramai/ideogram-v2a-turbo": { + "id": "ideogramai/ideogram-v2a-turbo", + "family": "ideogram", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 150, + "output": 0 + } + }, + "ideogramai/ideogram-v2a": { + "id": "ideogramai/ideogram-v2a", + "family": "ideogram", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 150, + "output": 0 + } + }, + "trytako/tako": { + "id": "trytako/tako", + "family": "tako", + "reasoning": false, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2048, + "output": 0 + } + }, + "poetools/claude-code": { + "id": "poetools/claude-code", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 0, + "output": 0 + } + }, + "mistral-nemo": { + "id": "mistral-nemo", + "family": "mistral-nemo", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "gemma2-9b-it": { + "id": "gemma2-9b-it", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "llama-4-scout": { + "id": "llama-4-scout", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 16384 + } + }, + "claude-3.5-sonnet-v2": { + "id": "claude-3.5-sonnet-v2", + "family": "claude-sonnet", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + } + }, + "hermes-2-pro-llama-3-8b": { + "id": "hermes-2-pro-llama-3-8b", + "family": "nousresearch", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "llama-prompt-guard-2-22m": { + "id": "llama-prompt-guard-2-22m", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 512, + "output": 2 + } + }, + "o1-mini": { + "id": "o1-mini", + "family": "o-mini", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 65536 + } + }, + "gpt-4.1-mini-2025-04-14": { + "id": "gpt-4.1-mini-2025-04-14", + "family": "gpt-mini", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1047576, + "output": 32768 + } + }, + "claude-3-haiku-20240307": { + "id": "claude-3-haiku-20240307", + "family": "claude-haiku", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 4096 + } + }, + "llama-4-maverick": { + "id": "llama-4-maverick", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "mistral-large-2411": { + "id": "mistral-large-2411", + "family": "mistral-large", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "kimi-k2-0711": { + "id": "kimi-k2-0711", + "family": "kimi", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "gemma-3-12b-it": { + "id": "gemma-3-12b-it", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 8192 + } + }, + "deepseek-tng-r1t2-chimera": { + "id": "deepseek-tng-r1t2-chimera", + "family": "deepseek-thinking", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 130000, + "output": 163840 + } + }, + "o1": { + "id": "o1", + "family": "o", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 100000 + } + }, + "llama-3.1-8b-instant": { + "id": "llama-3.1-8b-instant", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "mistral-small": { + "id": "mistral-small", + "family": "mistral-small", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "codex-mini-latest": { + "id": "codex-mini-latest", + "family": "gpt-codex-mini", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 100000 + } + }, + "grok-4": { + "id": "grok-4", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 64000 + } + }, + "qwen3-235b-a22b-thinking": { + "id": "qwen3-235b-a22b-thinking", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 81920 + } + }, + "qwen2.5-coder-7b-fast": { + "id": "qwen2.5-coder-7b-fast", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 8192 + } + }, + "llama-3.1-8b-instruct-turbo": { + "id": "llama-3.1-8b-instruct-turbo", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "sonar-reasoning": { + "id": "sonar-reasoning", + "family": "sonar-reasoning", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 127000, + "output": 4096 + } + }, + "claude-opus-4": { + "id": "claude-opus-4", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "llama-prompt-guard-2-86m": { + "id": "llama-prompt-guard-2-86m", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 512, + "output": 2 + } + }, + "grok-3-mini": { + "id": "grok-3-mini", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "llama-guard-4": { + "id": "llama-guard-4", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 1024 + } + }, + "qwen3-vl-235b-a22b-instruct": { + "id": "qwen3-vl-235b-a22b-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "ernie-4.5-21b-a3b-thinking": { + "id": "ernie-4.5-21b-a3b-thinking", + "family": "ernie", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8000 + } + }, + "deepseek-v3.1-terminus": { + "id": "deepseek-v3.1-terminus", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "grok-3": { + "id": "grok-3", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "gpt-oss:20b": { + "id": "gpt-oss:20b", + "family": "gpt-oss", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "gemma4:31b": { + "id": "gemma4:31b", + "family": "gemma", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 8192 + } + }, + "gpt-oss:120b": { + "id": "gpt-oss:120b", + "family": "gpt-oss", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "qwen3.5:397b": { + "id": "qwen3.5:397b", + "family": "qwen", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 81920 + } + }, + "deepseek-v3.1:671b": { + "id": "deepseek-v3.1:671b", + "family": "deepseek", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 163840 + } + }, + "qwen3-vl:235b-instruct": { + "id": "qwen3-vl:235b-instruct", + "family": "qwen", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + } + }, + "gemma3:4b": { + "id": "gemma3:4b", + "family": "gemma", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "ministral-3:14b": { + "id": "ministral-3:14b", + "family": "ministral", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 128000 + } + }, + "qwen3-next:80b": { + "id": "qwen3-next:80b", + "family": "qwen", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "qwen3-vl:235b": { + "id": "qwen3-vl:235b", + "family": "qwen", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "rnj-1:8b": { + "id": "rnj-1:8b", + "family": "rnj", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 4096 + } + }, + "mistral-large-3:675b": { + "id": "mistral-large-3:675b", + "family": "mistral-large", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "ministral-3:8b": { + "id": "ministral-3:8b", + "family": "ministral", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 128000 + } + }, + "gemma3:12b": { + "id": "gemma3:12b", + "family": "gemma", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "qwen3-coder:480b": { + "id": "qwen3-coder:480b", + "family": "qwen", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + } + }, + "nemotron-3-nano:30b": { + "id": "nemotron-3-nano:30b", + "family": "nemotron", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 131072 + } + }, + "ministral-3:3b": { + "id": "ministral-3:3b", + "family": "ministral", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 128000 + } + }, + "gemma3:27b": { + "id": "gemma3:27b", + "family": "gemma", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "devstral-2:123b": { + "id": "devstral-2:123b", + "family": "devstral", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "cogito-2.1:671b": { + "id": "cogito-2.1:671b", + "family": "cogito", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 32000 + } + }, + "qwen3-coder-next": { + "id": "qwen3-coder-next", + "family": "qwen", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144, + "input": 262144 + }, + "temperature": true + }, + "nemotron-3-super": { + "id": "nemotron-3-super", + "family": "nemotron", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + } + }, + "devstral-small-2:24b": { + "id": "devstral-small-2:24b", + "family": "devstral", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "kimi-k2:1t": { + "id": "kimi-k2:1t", + "family": "kimi", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "anthropic.claude-opus-4-1-20250805-v1:0": { + "id": "anthropic.claude-opus-4-1-20250805-v1:0", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "anthropic.claude-3-5-sonnet-20241022-v2:0": { + "id": "anthropic.claude-3-5-sonnet-20241022-v2:0", + "family": "claude-sonnet", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + } + }, + "openai.gpt-oss-safeguard-120b": { + "id": "openai.gpt-oss-safeguard-120b", + "family": "gpt-oss", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "nvidia.nemotron-nano-3-30b": { + "id": "nvidia.nemotron-nano-3-30b", + "family": "nemotron", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "meta.llama3-2-90b-instruct-v1:0": { + "id": "meta.llama3-2-90b-instruct-v1:0", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "nvidia.nemotron-super-3-120b": { + "id": "nvidia.nemotron-super-3-120b", + "family": "nemotron", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + } + }, + "writer.palmyra-x5-v1:0": { + "id": "writer.palmyra-x5-v1:0", + "family": "palmyra", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1040000, + "output": 8192 + } + }, + "mistral.ministral-3-8b-instruct": { + "id": "mistral.ministral-3-8b-instruct", + "family": "ministral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "anthropic.claude-3-5-sonnet-20240620-v1:0": { + "id": "anthropic.claude-3-5-sonnet-20240620-v1:0", + "family": "claude-sonnet", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + } + }, + "mistral.ministral-3-3b-instruct": { + "id": "mistral.ministral-3-3b-instruct", + "family": "ministral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 8192 + } + }, + "eu.anthropic.claude-opus-4-6-v1": { + "id": "eu.anthropic.claude-opus-4-6-v1", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "amazon.nova-premier-v1:0": { + "id": "amazon.nova-premier-v1:0", + "family": "nova", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 16384 + } + }, + "eu.anthropic.claude-sonnet-4-20250514-v1:0": { + "id": "eu.anthropic.claude-sonnet-4-20250514-v1:0", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic.claude-sonnet-4-5-20250929-v1:0": { + "id": "anthropic.claude-sonnet-4-5-20250929-v1:0", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "mistral.devstral-2-123b": { + "id": "mistral.devstral-2-123b", + "family": "devstral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 8192 + } + }, + "us.anthropic.claude-opus-4-20250514-v1:0": { + "id": "us.anthropic.claude-opus-4-20250514-v1:0", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "global.anthropic.claude-opus-4-5-20251101-v1:0": { + "id": "global.anthropic.claude-opus-4-5-20251101-v1:0", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "mistral.voxtral-small-24b-2507": { + "id": "mistral.voxtral-small-24b-2507", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 8192 + } + }, + "google.gemma-3-12b-it": { + "id": "google.gemma-3-12b-it", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "amazon.nova-pro-v1:0": { + "id": "amazon.nova-pro-v1:0", + "family": "nova-pro", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 300000, + "output": 8192 + } + }, + "anthropic.claude-haiku-4-5-20251001-v1:0": { + "id": "anthropic.claude-haiku-4-5-20251001-v1:0", + "family": "claude-haiku", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "minimax.minimax-m2": { + "id": "minimax.minimax-m2", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204608, + "output": 128000 + } + }, + "global.anthropic.claude-opus-4-7": { + "id": "global.anthropic.claude-opus-4-7", + "family": "claude-opus", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "mistral.pixtral-large-2502-v1:0": { + "id": "mistral.pixtral-large-2502-v1:0", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "meta.llama4-maverick-17b-instruct-v1:0": { + "id": "meta.llama4-maverick-17b-instruct-v1:0", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 16384 + } + }, + "us.anthropic.claude-sonnet-4-5-20250929-v1:0": { + "id": "us.anthropic.claude-sonnet-4-5-20250929-v1:0", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "us.anthropic.claude-haiku-4-5-20251001-v1:0": { + "id": "us.anthropic.claude-haiku-4-5-20251001-v1:0", + "family": "claude-haiku", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "amazon.nova-micro-v1:0": { + "id": "amazon.nova-micro-v1:0", + "family": "nova-micro", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "global.anthropic.claude-sonnet-4-5-20250929-v1:0": { + "id": "global.anthropic.claude-sonnet-4-5-20250929-v1:0", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic.claude-sonnet-4-6": { + "id": "anthropic.claude-sonnet-4-6", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "openai.gpt-oss-20b-1:0": { + "id": "openai.gpt-oss-20b-1:0", + "family": "gpt-oss", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "us.anthropic.claude-sonnet-4-20250514-v1:0": { + "id": "us.anthropic.claude-sonnet-4-20250514-v1:0", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "zai.glm-5": { + "id": "zai.glm-5", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 101376 + } + }, + "qwen.qwen3-32b-v1:0": { + "id": "qwen.qwen3-32b-v1:0", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "output": 16384 + } + }, + "deepseek.v3.2": { + "id": "deepseek.v3.2", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 81920 + } + }, + "eu.anthropic.claude-haiku-4-5-20251001-v1:0": { + "id": "eu.anthropic.claude-haiku-4-5-20251001-v1:0", + "family": "claude-haiku", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "zai.glm-4.7-flash": { + "id": "zai.glm-4.7-flash", + "family": "glm-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 131072 + } + }, + "us.anthropic.claude-opus-4-7": { + "id": "us.anthropic.claude-opus-4-7", + "family": "claude-opus", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "amazon.nova-2-lite-v1:0": { + "id": "amazon.nova-2-lite-v1:0", + "family": "nova", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "anthropic.claude-opus-4-5-20251101-v1:0": { + "id": "anthropic.claude-opus-4-5-20251101-v1:0", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "qwen.qwen3-coder-480b-a35b-v1:0": { + "id": "qwen.qwen3-coder-480b-a35b-v1:0", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + } + }, + "meta.llama3-2-1b-instruct-v1:0": { + "id": "meta.llama3-2-1b-instruct-v1:0", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 4096 + } + }, + "amazon.nova-lite-v1:0": { + "id": "amazon.nova-lite-v1:0", + "family": "nova-lite", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 300000, + "output": 8192 + } + }, + "meta.llama3-1-8b-instruct-v1:0": { + "id": "meta.llama3-1-8b-instruct-v1:0", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "global.anthropic.claude-sonnet-4-6": { + "id": "global.anthropic.claude-sonnet-4-6", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "us.anthropic.claude-sonnet-4-6": { + "id": "us.anthropic.claude-sonnet-4-6", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "anthropic.claude-opus-4-7": { + "id": "anthropic.claude-opus-4-7", + "family": "claude-opus", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "global.anthropic.claude-opus-4-6-v1": { + "id": "global.anthropic.claude-opus-4-6-v1", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "google.gemma-3-27b-it": { + "id": "google.gemma-3-27b-it", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 8192 + } + }, + "global.anthropic.claude-haiku-4-5-20251001-v1:0": { + "id": "global.anthropic.claude-haiku-4-5-20251001-v1:0", + "family": "claude-haiku", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "google.gemma-3-4b-it": { + "id": "google.gemma-3-4b-it", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "us.anthropic.claude-opus-4-6-v1": { + "id": "us.anthropic.claude-opus-4-6-v1", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "meta.llama4-scout-17b-instruct-v1:0": { + "id": "meta.llama4-scout-17b-instruct-v1:0", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 3500000, + "output": 16384 + } + }, + "us.anthropic.claude-opus-4-1-20250805-v1:0": { + "id": "us.anthropic.claude-opus-4-1-20250805-v1:0", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "deepseek.v3-v1:0": { + "id": "deepseek.v3-v1:0", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 81920 + } + }, + "mistral.magistral-small-2509": { + "id": "mistral.magistral-small-2509", + "family": "magistral", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 40000 + } + }, + "qwen.qwen3-next-80b-a3b": { + "id": "qwen.qwen3-next-80b-a3b", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + } + }, + "zai.glm-4.7": { + "id": "zai.glm-4.7", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "moonshot.kimi-k2-thinking": { + "id": "moonshot.kimi-k2-thinking", + "family": "kimi-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "us.anthropic.claude-opus-4-5-20251101-v1:0": { + "id": "us.anthropic.claude-opus-4-5-20251101-v1:0", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "mistral.ministral-3-14b-instruct": { + "id": "mistral.ministral-3-14b-instruct", + "family": "ministral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "anthropic.claude-3-haiku-20240307-v1:0": { + "id": "anthropic.claude-3-haiku-20240307-v1:0", + "family": "claude-haiku", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 4096 + } + }, + "global.anthropic.claude-sonnet-4-20250514-v1:0": { + "id": "global.anthropic.claude-sonnet-4-20250514-v1:0", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "deepseek.r1-v1:0": { + "id": "deepseek.r1-v1:0", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "meta.llama3-1-405b-instruct-v1:0": { + "id": "meta.llama3-1-405b-instruct-v1:0", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "mistral.voxtral-mini-3b-2507": { + "id": "mistral.voxtral-mini-3b-2507", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "audio", + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "eu.anthropic.claude-sonnet-4-6": { + "id": "eu.anthropic.claude-sonnet-4-6", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "openai.gpt-oss-120b-1:0": { + "id": "openai.gpt-oss-120b-1:0", + "family": "gpt-oss", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "nvidia.nemotron-nano-12b-v2": { + "id": "nvidia.nemotron-nano-12b-v2", + "family": "nemotron", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "eu.anthropic.claude-opus-4-7": { + "id": "eu.anthropic.claude-opus-4-7", + "family": "claude-opus", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "minimax.minimax-m2.5": { + "id": "minimax.minimax-m2.5", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 196608, + "output": 98304 + } + }, + "meta.llama3-3-70b-instruct-v1:0": { + "id": "meta.llama3-3-70b-instruct-v1:0", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "meta.llama3-1-70b-instruct-v1:0": { + "id": "meta.llama3-1-70b-instruct-v1:0", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "meta.llama3-2-3b-instruct-v1:0": { + "id": "meta.llama3-2-3b-instruct-v1:0", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 4096 + } + }, + "eu.anthropic.claude-sonnet-4-5-20250929-v1:0": { + "id": "eu.anthropic.claude-sonnet-4-5-20250929-v1:0", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic.claude-opus-4-20250514-v1:0": { + "id": "anthropic.claude-opus-4-20250514-v1:0", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "eu.anthropic.claude-opus-4-5-20251101-v1:0": { + "id": "eu.anthropic.claude-opus-4-5-20251101-v1:0", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "anthropic.claude-sonnet-4-20250514-v1:0": { + "id": "anthropic.claude-sonnet-4-20250514-v1:0", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "meta.llama3-2-11b-instruct-v1:0": { + "id": "meta.llama3-2-11b-instruct-v1:0", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "moonshotai.kimi-k2.5": { + "id": "moonshotai.kimi-k2.5", + "family": "kimi", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "openai.gpt-oss-safeguard-20b": { + "id": "openai.gpt-oss-safeguard-20b", + "family": "gpt-oss", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "anthropic.claude-opus-4-6-v1": { + "id": "anthropic.claude-opus-4-6-v1", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "qwen.qwen3-coder-30b-a3b-v1:0": { + "id": "qwen.qwen3-coder-30b-a3b-v1:0", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + } + }, + "minimax.minimax-m2.1": { + "id": "minimax.minimax-m2.1", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "qwen.qwen3-vl-235b-a22b": { + "id": "qwen.qwen3-vl-235b-a22b", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + } + }, + "qwen.qwen3-coder-next": { + "id": "qwen.qwen3-coder-next", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + } + }, + "anthropic.claude-3-5-haiku-20241022-v1:0": { + "id": "anthropic.claude-3-5-haiku-20241022-v1:0", + "family": "claude-haiku", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + } + }, + "nvidia.nemotron-nano-9b-v2": { + "id": "nvidia.nemotron-nano-9b-v2", + "family": "nemotron", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "mistral.mistral-large-3-675b-instruct": { + "id": "mistral.mistral-large-3-675b-instruct", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 8192 + } + }, + "qwen.qwen3-235b-a22b-2507-v1:0": { + "id": "qwen.qwen3-235b-a22b-2507-v1:0", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + } + }, + "anthropic.claude-3-7-sonnet-20250219-v1:0": { + "id": "anthropic.claude-3-7-sonnet-20250219-v1:0", + "family": "claude-sonnet", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + } + }, + "writer.palmyra-x4-v1:0": { + "id": "writer.palmyra-x4-v1:0", + "family": "palmyra", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 122880, + "output": 8192 + } + }, + "text-prime": { + "id": "text-prime", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 30000 + } + }, + "text-standard": { + "id": "text-standard", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16000 + } + }, + "text-max": { + "id": "text-max", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "nvidia/nemotron-120b-a12b": { + "id": "nvidia/Nemotron-120B-A12B", + "family": "nemotron", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32678 + } + }, + "glm-4.6v-flash": { + "id": "glm-4.6v-flash", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16000 + } + }, + "openai-gpt-4o-mini-2024-07-18": { + "id": "openai-gpt-4o-mini-2024-07-18", + "family": "gpt-mini", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "qwen3-next-80b": { + "id": "qwen3-next-80b", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 16384 + } + }, + "grok-4-20-multi-agent": { + "id": "grok-4-20-multi-agent", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 128000 + } + }, + "z-ai-glm-5v-turbo": { + "id": "z-ai-glm-5v-turbo", + "family": "glmv", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32768 + } + }, + "grok-41-fast": { + "id": "grok-41-fast", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 30000 + } + }, + "nvidia-nemotron-cascade-2-30b-a3b": { + "id": "nvidia-nemotron-cascade-2-30b-a3b", + "family": "nemotron", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 32768 + } + }, + "grok-4-20": { + "id": "grok-4-20", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 128000 + } + }, + "google-gemma-4-26b-a4b-it": { + "id": "google-gemma-4-26b-a4b-it", + "family": "gemma", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 8192 + } + }, + "qwen3-coder-480b-a35b-instruct-turbo": { + "id": "qwen3-coder-480b-a35b-instruct-turbo", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 65536 + } + }, + "qwen3-5-397b-a17b": { + "id": "qwen3-5-397b-a17b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "zai-org-glm-4.7": { + "id": "zai-org-glm-4.7", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 198000, + "output": 16384 + } + }, + "openai-gpt-54": { + "id": "openai-gpt-54", + "family": "gpt", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 131072 + } + }, + "zai-org-glm-4.7-flash": { + "id": "zai-org-glm-4.7-flash", + "family": "glm-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "nvidia-nemotron-3-nano-30b-a3b": { + "id": "nvidia-nemotron-3-nano-30b-a3b", + "family": "nemotron", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "openai-gpt-53-codex": { + "id": "openai-gpt-53-codex", + "family": "gpt-codex", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000 + } + }, + "openai-gpt-52": { + "id": "openai-gpt-52", + "family": "gpt", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 65536 + } + }, + "mistral-small-3-2-24b-instruct": { + "id": "mistral-small-3-2-24b-instruct", + "family": "mistral-small", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 16384 + } + }, + "minimax-m27": { + "id": "minimax-m27", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 198000, + "output": 32768 + } + }, + "qwen3-5-35b-a3b": { + "id": "qwen3-5-35b-a3b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 65536 + } + }, + "mercury-2": { + "id": "mercury-2", + "family": "mercury", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 50000 + } + }, + "google-gemma-3-27b-it": { + "id": "google-gemma-3-27b-it", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 198000, + "output": 16384 + } + }, + "olafangensan-glm-4.7-flash-heretic": { + "id": "olafangensan-glm-4.7-flash-heretic", + "family": "glm-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 24000 + } + }, + "openai-gpt-52-codex": { + "id": "openai-gpt-52-codex", + "family": "gpt-codex", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 65536 + } + }, + "venice-uncensored-role-play": { + "id": "venice-uncensored-role-play", + "family": "venice", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "zai-org-glm-5": { + "id": "zai-org-glm-5", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 198000, + "output": 32000 + } + }, + "zai-org-glm-4.6": { + "id": "zai-org-glm-4.6", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 198000, + "output": 16384 + } + }, + "mistral-small-2603": { + "id": "mistral-small-2603", + "family": "mistral-small", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "openai-gpt-oss-120b": { + "id": "openai-gpt-oss-120b", + "family": "gpt-oss", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "qwen3-5-9b": { + "id": "qwen3-5-9b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 32768 + } + }, + "openai-gpt-54-pro": { + "id": "openai-gpt-54-pro", + "family": "gpt-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "openai-gpt-54-mini": { + "id": "openai-gpt-54-mini", + "family": "gpt-mini", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000 + } + }, + "minimax-m25": { + "id": "minimax-m25", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 198000, + "output": 32768 + } + }, + "zai-org-glm-5-1": { + "id": "zai-org-glm-5-1", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 24000 + } + }, + "qwen-3-6-plus": { + "id": "qwen-3-6-plus", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "aion-labs-aion-2-0": { + "id": "aion-labs-aion-2-0", + "family": "o", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "openai-gpt-4o-2024-11-20": { + "id": "openai-gpt-4o-2024-11-20", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "llama-3.3-70b": { + "id": "llama-3.3-70b", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "kimi-k2-5": { + "id": "kimi-k2-5", + "family": "kimi", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 65536 + } + }, + "llama-3.2-3b": { + "id": "llama-3.2-3b", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "arcee-trinity-large-thinking": { + "id": "arcee-trinity-large-thinking", + "family": "trinity", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 65536 + } + }, + "hermes-3-llama-3.1-405b": { + "id": "hermes-3-llama-3.1-405b", + "family": "hermes", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "gemini-3-1-pro-preview": { + "id": "gemini-3-1-pro-preview", + "family": "gemini-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video", + "audio", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "claude-opus-4-6-fast": { + "id": "claude-opus-4-6-fast", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "z-ai-glm-5-turbo": { + "id": "z-ai-glm-5-turbo", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32768 + } + }, + "google-gemma-4-31b-it": { + "id": "google-gemma-4-31b-it", + "family": "gemma", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 8192 + } + }, + "coding-glm-5-free": { + "id": "coding-glm-5-free", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "coding-glm-4.7": { + "id": "coding-glm-4.7", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "gemini-3-pro-preview-search": { + "id": "gemini-3-pro-preview-search", + "family": "gemini-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 65000 + } + }, + "coding-glm-5.1": { + "id": "coding-glm-5.1", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 128000 + } + }, + "coding-minimax-m2.1-free": { + "id": "coding-minimax-m2.1-free", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "deepseek-v3.2-fast": { + "id": "deepseek-v3.2-fast", + "family": "deepseek", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "deepseek-v3.2-think": { + "id": "deepseek-v3.2-think", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 64000 + } + }, + "claude-opus-4-6-think": { + "id": "claude-opus-4-6-think", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 128000 + } + }, + "coding-glm-4.7-free": { + "id": "coding-glm-4.7-free", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "claude-sonnet-4-6-think": { + "id": "claude-sonnet-4-6-think", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, "llama3.1-8b": { "id": "llama3.1-8b", "family": "llama", @@ -38913,6 +31518,25 @@ "output": 8000 } }, + "qwen-3-235b-a22b-instruct-2507": { + "id": "qwen-3-235b-a22b-instruct-2507", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 32000 + } + }, "zai-glm-4.7": { "id": "zai-glm-4.7", "reasoning": false, @@ -38931,8 +31555,1184 @@ "output": 40000 } }, - "gpt-5.3-chat": { - "id": "gpt-5.3-chat", + "gpt-5-4": { + "id": "gpt-5-4", + "family": "gpt", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 272000, + "output": 128000 + } + }, + "deepseek-v3-2": { + "id": "deepseek-v3-2", + "family": "deepseek", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "zai-glm-5-1": { + "id": "zai-glm-5-1", + "family": "glm", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 198000, + "output": 8192 + } + }, + "minimax-m2-5": { + "id": "minimax-m2-5", + "family": "minimax", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 192000, + "output": 8192 + } + }, + "gpt-5-3-codex": { + "id": "gpt-5-3-codex", + "family": "gpt", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "output": 128000 + } + }, + "qwen/qwen3-coder-30b": { + "id": "qwen/qwen3-coder-30b", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + } + }, + "qwen/qwen3-30b-a3b-2507": { + "id": "qwen/qwen3-30b-a3b-2507", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 16384 + } + }, + "lucidnova-rf1-100b": { + "id": "lucidnova-rf1-100b", + "family": "nova", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 120000, + "output": 8000 + } + }, + "lucidquery-nexus-coder": { + "id": "lucidquery-nexus-coder", + "family": "lucid", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 250000, + "output": 60000 + } + }, + "kimi-k2-0711-preview": { + "id": "kimi-k2-0711-preview", + "family": "kimi", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "mai-ds-r1": { + "id": "mai-ds-r1", + "family": "mai", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "codestral-2501": { + "id": "codestral-2501", + "family": "codestral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "gpt-3.5-turbo-instruct": { + "id": "gpt-3.5-turbo-instruct", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "output": 4096 + } + }, + "mistral-medium-2505": { + "id": "mistral-medium-2505", + "family": "mistral-medium", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "phi-4-reasoning-plus": { + "id": "phi-4-reasoning-plus", + "family": "phi", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 4096 + } + }, + "cohere-embed-v3-english": { + "id": "cohere-embed-v3-english", + "family": "cohere-embed", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 512, + "output": 1024 + } + }, + "gpt-4-32k": { + "id": "gpt-4-32k", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "phi-4": { + "id": "phi-4", + "family": "phi", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "cohere-command-r-plus-08-2024": { + "id": "cohere-command-r-plus-08-2024", + "family": "command-r", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4000 + } + }, + "gpt-3.5-turbo-0613": { + "id": "gpt-3.5-turbo-0613", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "output": 16384 + } + }, + "phi-3-medium-128k-instruct": { + "id": "phi-3-medium-128k-instruct", + "family": "phi", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "phi-3-small-128k-instruct": { + "id": "phi-3-small-128k-instruct", + "family": "phi", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "gpt-3.5-turbo-0301": { + "id": "gpt-3.5-turbo-0301", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "output": 4096 + } + }, + "phi-4-mini": { + "id": "phi-4-mini", + "family": "phi", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "meta-llama-3-8b-instruct": { + "id": "meta-llama-3-8b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 2048 + } + }, + "gpt-4": { + "id": "gpt-4", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "phi-4-mini-reasoning": { + "id": "phi-4-mini-reasoning", + "family": "phi", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "meta-llama-3.1-70b-instruct": { + "id": "meta-llama-3.1-70b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "phi-3-mini-4k-instruct": { + "id": "phi-3-mini-4k-instruct", + "family": "phi", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "output": 1024 + } + }, + "text-embedding-3-small": { + "id": "text-embedding-3-small", + "family": "text-embedding", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8191, + "output": 1536 + }, + "temperature": false + }, + "gpt-3.5-turbo-1106": { + "id": "gpt-3.5-turbo-1106", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "output": 16384 + } + }, + "model-router": { + "id": "model-router", + "family": "model-router", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "mistral-small-2503": { + "id": "mistral-small-2503", + "family": "mistral-small", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "cohere-embed-v3-multilingual": { + "id": "cohere-embed-v3-multilingual", + "family": "cohere-embed", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 512, + "output": 1024 + } + }, + "o1-preview": { + "id": "o1-preview", + "family": "o", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "gpt-3.5-turbo-0125": { + "id": "gpt-3.5-turbo-0125", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16384, + "output": 16384 + } + }, + "cohere-embed-v-4-0": { + "id": "cohere-embed-v-4-0", + "family": "cohere-embed", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 1536 + } + }, + "gpt-4-turbo-vision": { + "id": "gpt-4-turbo-vision", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "gpt-5.1-chat": { + "id": "gpt-5.1-chat", + "family": "gpt-codex", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text", + "image", + "audio" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "meta-llama-3.1-405b-instruct": { + "id": "meta-llama-3.1-405b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "llama-3.2-11b-vision-instruct": { + "id": "llama-3.2-11b-vision-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "cohere-command-a": { + "id": "cohere-command-a", + "family": "command-a", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 8000 + } + }, + "cohere-command-r-08-2024": { + "id": "cohere-command-r-08-2024", + "family": "command-r", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4000 + } + }, + "deepseek-v3.2-speciale": { + "id": "deepseek-v3.2-speciale", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "llama-3.2-90b-vision-instruct": { + "id": "llama-3.2-90b-vision-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "text-embedding-ada-002": { + "id": "text-embedding-ada-002", + "family": "text-embedding", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1536 + }, + "temperature": false + }, + "gpt-4-turbo": { + "id": "gpt-4-turbo", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "phi-3-small-8k-instruct": { + "id": "phi-3-small-8k-instruct", + "family": "phi", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 2048 + } + }, + "meta-llama-3-70b-instruct": { + "id": "meta-llama-3-70b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 2048 + } + }, + "phi-4-reasoning": { + "id": "phi-4-reasoning", + "family": "phi", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 4096 + } + }, + "phi-3-mini-128k-instruct": { + "id": "phi-3-mini-128k-instruct", + "family": "phi", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "text-embedding-3-large": { + "id": "text-embedding-3-large", + "family": "text-embedding", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8191, + "output": 3072 + }, + "temperature": false + }, + "phi-3.5-moe-instruct": { + "id": "phi-3.5-moe-instruct", + "family": "phi", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "gpt-5-chat": { + "id": "gpt-5-chat", + "family": "gpt-codex", + "reasoning": true, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "meta-llama-3.1-8b-instruct": { + "id": "meta-llama-3.1-8b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "ministral-3b": { + "id": "ministral-3b", + "family": "ministral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "phi-3-medium-4k-instruct": { + "id": "phi-3-medium-4k-instruct", + "family": "phi", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "output": 1024 + } + }, + "llama-4-scout-17b-16e-instruct": { + "id": "llama-4-scout-17b-16e-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "phi-3.5-mini-instruct": { + "id": "phi-3.5-mini-instruct", + "family": "phi", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "phi-4-multimodal": { + "id": "phi-4-multimodal", + "family": "phi", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "codex-mini": { + "id": "codex-mini", + "family": "gpt-codex-mini", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 100000 + } + }, + "gpt-5.2-chat": { + "id": "gpt-5.2-chat", "family": "gpt-codex", "reasoning": true, "temperature": false, @@ -38951,127 +32751,9 @@ "output": 16384 } }, - "kimi-k2-instruct": { - "id": "kimi-k2-instruct", - "family": "kimi", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131000, - "output": 131000 - } - }, - "claude-opus4-6": { - "id": "claude-opus4-6", - "family": "claude-opus", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 1000000 - } - }, - "claude-4-6-sonnet": { - "id": "claude-4-6-sonnet", - "family": "claude-sonnet", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image", - "pdf" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 1000000, - "output": 1000000 - } - }, - "devstral-small-2512": { - "id": "devstral-small-2512", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262000, - "output": 262000 - } - }, - "intellect-3": { - "id": "intellect-3", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 128000, - "output": 128000 - } - }, - "nova-pro-v1": { - "id": "nova-pro-v1", - "family": "nova-pro", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 300000, - "output": 5000 - } - }, - "llama-3.1-405b-instruct": { - "id": "llama-3.1-405b-instruct", - "family": "llama", + "command-r7b-12-2024": { + "id": "command-r7b-12-2024", + "family": "command-r", "reasoning": false, "temperature": true, "toolCall": true, @@ -39085,49 +32767,232 @@ }, "limit": { "context": 128000, - "output": 128000 + "output": 4000 } }, - "claude-opus4-5": { - "id": "claude-opus4-5", - "family": "claude-opus", - "reasoning": true, + "c4ai-aya-vision-8b": { + "id": "c4ai-aya-vision-8b", + "reasoning": false, "temperature": true, - "toolCall": true, + "toolCall": false, "modalities": { "input": [ "text", - "image", - "pdf" + "image" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 200000 + "context": 16000, + "output": 4000 } }, - "claude-4-5-sonnet": { - "id": "claude-4-5-sonnet", - "family": "claude-sonnet", - "reasoning": true, + "command-r-plus-08-2024": { + "id": "command-r-plus-08-2024", + "family": "command-r", + "reasoning": false, "temperature": true, "toolCall": true, "modalities": { "input": [ - "text", - "image", - "pdf" + "text" ], "output": [ "text" ] }, "limit": { - "context": 200000, - "output": 200000 + "context": 128000, + "output": 4000 + } + }, + "c4ai-aya-expanse-8b": { + "id": "c4ai-aya-expanse-8b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8000, + "output": 4000 + } + }, + "command-r7b-arabic-02-2025": { + "id": "command-r7b-arabic-02-2025", + "family": "command-r", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4000 + } + }, + "command-a-vision-07-2025": { + "id": "command-a-vision-07-2025", + "family": "command-a", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8000 + } + }, + "c4ai-aya-vision-32b": { + "id": "c4ai-aya-vision-32b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16000, + "output": 4000 + } + }, + "command-a-translate-08-2025": { + "id": "command-a-translate-08-2025", + "family": "command-a", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8000, + "output": 8000 + } + }, + "command-r-08-2024": { + "id": "command-r-08-2024", + "family": "command-r", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4000 + } + }, + "c4ai-aya-expanse-32b": { + "id": "c4ai-aya-expanse-32b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4000 + } + }, + "command-a-03-2025": { + "id": "command-a-03-2025", + "family": "command-a", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 8000 + } + }, + "speakleash/bielik-11b-v3.0-instruct": { + "id": "speakleash/Bielik-11B-v3.0-Instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 32000 + } + }, + "speakleash/bielik-11b-v2.6-instruct": { + "id": "speakleash/Bielik-11B-v2.6-Instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 32000 } }, "grok-2-1212": { @@ -39149,12 +33014,109 @@ "output": 8192 } }, - "grok-4.20-multi-agent-0309": { - "id": "grok-4.20-multi-agent-0309", + "grok-vision-beta": { + "id": "grok-vision-beta", + "family": "grok-vision", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 4096 + } + }, + "grok-3-mini-fast": { + "id": "grok-3-mini-fast", "family": "grok", "reasoning": true, "temperature": true, - "toolCall": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "grok-3-mini-latest": { + "id": "grok-3-mini-latest", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "grok-3-fast": { + "id": "grok-3-fast", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "grok-2-vision-latest": { + "id": "grok-2-vision-latest", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 4096 + } + }, + "grok-4.20-0309-reasoning": { + "id": "grok-4.20-0309-reasoning", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, "modalities": { "input": [ "text", @@ -39169,105 +33131,8 @@ "output": 30000 } }, - "grok-2": { - "id": "grok-2", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "grok-3-fast-latest": { - "id": "grok-3-fast-latest", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "grok-2-vision": { - "id": "grok-2-vision", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 4096 - } - }, - "grok-2-vision-1212": { - "id": "grok-2-vision-1212", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 4096 - } - }, - "grok-beta": { - "id": "grok-beta", - "family": "grok-beta", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 4096 - } - }, - "grok-3-mini-fast": { - "id": "grok-3-mini-fast", + "grok-3-mini-fast-latest": { + "id": "grok-3-mini-fast-latest", "family": "grok", "reasoning": true, "temperature": true, @@ -39324,10 +33189,29 @@ "output": 8192 } }, - "grok-4-1-fast": { - "id": "grok-4-1-fast", + "grok-2": { + "id": "grok-2", "family": "grok", - "reasoning": true, + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "grok-4.20-0309-non-reasoning": { + "id": "grok-4.20-0309-non-reasoning", + "family": "grok", + "reasoning": false, "temperature": true, "toolCall": true, "modalities": { @@ -39344,32 +33228,12 @@ "output": 30000 } }, - "grok-2-vision-latest": { - "id": "grok-2-vision-latest", + "grok-3-fast-latest": { + "id": "grok-3-fast-latest", "family": "grok", "reasoning": false, "temperature": true, "toolCall": true, - "modalities": { - "input": [ - "text", - "image" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 8192, - "output": 4096 - } - }, - "grok-3-mini-latest": { - "id": "grok-3-mini-latest", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, "modalities": { "input": [ "text" @@ -39383,31 +33247,12 @@ "output": 8192 } }, - "grok-3-mini-fast-latest": { - "id": "grok-3-mini-fast-latest", + "grok-4.20-multi-agent-0309": { + "id": "grok-4.20-multi-agent-0309", "family": "grok", "reasoning": true, "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "grok-4.20-0309-reasoning": { - "id": "grok-4.20-0309-reasoning", - "family": "grok", - "reasoning": true, - "temperature": true, - "toolCall": true, + "toolCall": false, "modalities": { "input": [ "text", @@ -39441,9 +33286,28 @@ "output": 8192 } }, - "grok-vision-beta": { - "id": "grok-vision-beta", - "family": "grok-vision", + "grok-beta": { + "id": "grok-beta", + "family": "grok-beta", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 4096 + } + }, + "grok-2-vision": { + "id": "grok-2-vision", + "family": "grok", "reasoning": false, "temperature": true, "toolCall": true, @@ -39461,8 +33325,5312 @@ "output": 4096 } }, - "grok-4.20-0309-non-reasoning": { - "id": "grok-4.20-0309-non-reasoning", + "grok-4-1-fast": { + "id": "grok-4-1-fast", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 30000 + } + }, + "grok-2-vision-1212": { + "id": "grok-2-vision-1212", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 4096 + } + }, + "mistralai/mistral-small-3.2-24b-instruct-2506": { + "id": "mistralai/Mistral-Small-3.2-24B-Instruct-2506", + "family": "mistral-small", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 8192 + } + }, + "claude-haiku-4-5@20251001": { + "id": "claude-haiku-4-5@20251001", + "family": "claude-haiku", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "claude-sonnet-4-6@default": { + "id": "claude-sonnet-4-6@default", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "claude-3-5-haiku@20241022": { + "id": "claude-3-5-haiku@20241022", + "family": "claude-haiku", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + } + }, + "claude-3-5-sonnet@20241022": { + "id": "claude-3-5-sonnet@20241022", + "family": "claude-sonnet", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + } + }, + "claude-opus-4-1@20250805": { + "id": "claude-opus-4-1@20250805", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "claude-sonnet-4@20250514": { + "id": "claude-sonnet-4@20250514", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "claude-3-7-sonnet@20250219": { + "id": "claude-3-7-sonnet@20250219", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "claude-opus-4@20250514": { + "id": "claude-opus-4@20250514", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "claude-opus-4-5@20251101": { + "id": "claude-opus-4-5@20251101", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "claude-sonnet-4-5@20250929": { + "id": "claude-sonnet-4-5@20250929", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "claude-opus-4-6@default": { + "id": "claude-opus-4-6@default", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "claude-opus-4-7@default": { + "id": "claude-opus-4-7@default", + "family": "claude-opus", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 128000 + } + }, + "qwen/qwen3-30b-a3b-instruct-2507-fp8": { + "id": "Qwen/Qwen3-30B-A3B-Instruct-2507-FP8", + "family": "qwen", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 64000, + "output": 64000 + } + }, + "mistralai/devstral-small-2-24b-instruct-2512": { + "id": "mistralai/devstral-small-2-24b-instruct-2512", + "family": "devstral", + "reasoning": false, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "mistralai/magistral-small-2509": { + "id": "mistralai/Magistral-Small-2509", + "family": "magistral-small", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "microsoft/phi-4-multimodal-instruct": { + "id": "microsoft/phi-4-multimodal-instruct", + "family": "phi", + "reasoning": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "temperature": true + }, + "kblab/kb-whisper-large": { + "id": "KBLab/kb-whisper-large", + "family": "whisper", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 480000, + "output": 4800 + }, + "temperature": false + }, + "nvidia/llama-3.3-70b-instruct-fp8": { + "id": "nvidia/Llama-3.3-70B-Instruct-FP8", + "family": "llama", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "openai/whisper-large-v3": { + "id": "openai/whisper-large-v3", + "family": "whisper", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 0, + "output": 4096 + }, + "temperature": false + }, + "intfloat/multilingual-e5-large-instruct": { + "id": "intfloat/multilingual-e5-large-instruct", + "family": "text-embedding", + "reasoning": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 512, + "output": 1024 + }, + "temperature": false + }, + "hf:meta-llama/llama-3.1-405b-instruct": { + "id": "hf:meta-llama/Llama-3.1-405B-Instruct", + "family": "llama", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "hf:meta-llama/llama-4-scout-17b-16e-instruct": { + "id": "hf:meta-llama/Llama-4-Scout-17B-16E-Instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 328000, + "output": 4096 + } + }, + "hf:meta-llama/llama-3.3-70b-instruct": { + "id": "hf:meta-llama/Llama-3.3-70B-Instruct", + "family": "llama", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "hf:meta-llama/llama-3.1-8b-instruct": { + "id": "hf:meta-llama/Llama-3.1-8B-Instruct", + "family": "llama", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "hf:meta-llama/llama-3.1-70b-instruct": { + "id": "hf:meta-llama/Llama-3.1-70B-Instruct", + "family": "llama", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "hf:meta-llama/llama-4-maverick-17b-128e-instruct-fp8": { + "id": "hf:meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 524000, + "output": 4096 + } + }, + "hf:minimaxai/minimax-m2": { + "id": "hf:MiniMaxAI/MiniMax-M2", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 196608, + "output": 131000 + } + }, + "hf:minimaxai/minimax-m2.5": { + "id": "hf:MiniMaxAI/MiniMax-M2.5", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 191488, + "output": 65536 + } + }, + "hf:minimaxai/minimax-m2.1": { + "id": "hf:MiniMaxAI/MiniMax-M2.1", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "hf:qwen/qwen3.5-397b-a17b": { + "id": "hf:Qwen/Qwen3.5-397B-A17B", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + } + }, + "hf:qwen/qwen2.5-coder-32b-instruct": { + "id": "hf:Qwen/Qwen2.5-Coder-32B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "hf:qwen/qwen3-235b-a22b-instruct-2507": { + "id": "hf:Qwen/Qwen3-235B-A22B-Instruct-2507", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 32000 + } + }, + "hf:qwen/qwen3-235b-a22b-thinking-2507": { + "id": "hf:Qwen/Qwen3-235B-A22B-Thinking-2507", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 32000 + } + }, + "hf:qwen/qwen3-coder-480b-a35b-instruct": { + "id": "hf:Qwen/Qwen3-Coder-480B-A35B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 32000 + } + }, + "hf:deepseek-ai/deepseek-v3.1": { + "id": "hf:deepseek-ai/DeepSeek-V3.1", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "hf:deepseek-ai/deepseek-v3-0324": { + "id": "hf:deepseek-ai/DeepSeek-V3-0324", + "family": "deepseek", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "hf:deepseek-ai/deepseek-v3": { + "id": "hf:deepseek-ai/DeepSeek-V3", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "hf:deepseek-ai/deepseek-r1": { + "id": "hf:deepseek-ai/DeepSeek-R1", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "hf:deepseek-ai/deepseek-r1-0528": { + "id": "hf:deepseek-ai/DeepSeek-R1-0528", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "hf:deepseek-ai/deepseek-v3.2": { + "id": "hf:deepseek-ai/DeepSeek-V3.2", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 162816, + "input": 162816, + "output": 8000 + } + }, + "hf:deepseek-ai/deepseek-v3.1-terminus": { + "id": "hf:deepseek-ai/DeepSeek-V3.1-Terminus", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "hf:openai/gpt-oss-120b": { + "id": "hf:openai/gpt-oss-120b", + "family": "gpt-oss", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "hf:moonshotai/kimi-k2-thinking": { + "id": "hf:moonshotai/Kimi-K2-Thinking", + "family": "kimi-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "hf:moonshotai/kimi-k2-instruct-0905": { + "id": "hf:moonshotai/Kimi-K2-Instruct-0905", + "family": "kimi", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "hf:moonshotai/kimi-k2.5": { + "id": "hf:moonshotai/Kimi-K2.5", + "family": "kimi", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + } + }, + "hf:nvidia/nvidia-nemotron-3-super-120b-a12b-nvfp4": { + "id": "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4", + "family": "nemotron", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + } + }, + "hf:nvidia/kimi-k2.5-nvfp4": { + "id": "hf:nvidia/Kimi-K2.5-NVFP4", + "family": "kimi", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + } + }, + "hf:zai-org/glm-4.7-flash": { + "id": "hf:zai-org/GLM-4.7-Flash", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 196608, + "output": 65536 + } + }, + "hf:zai-org/glm-4.7": { + "id": "hf:zai-org/GLM-4.7", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "hf:zai-org/glm-5.1": { + "id": "hf:zai-org/GLM-5.1", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 196608, + "output": 65536 + } + }, + "hf:zai-org/glm-5": { + "id": "hf:zai-org/GLM-5", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 196608, + "output": 65536 + } + }, + "hf:zai-org/glm-4.6": { + "id": "hf:zai-org/GLM-4.6", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "black-forest-labs/flux.1-dev": { + "id": "black-forest-labs/flux.1-dev", + "family": "flux", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 4096, + "output": 0 + } + }, + "mistralai/codestral-22b-instruct-v0.1": { + "id": "mistralai/codestral-22b-instruct-v0.1", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "mistralai/mistral-small-3.1-24b-instruct-2503": { + "id": "mistralai/mistral-small-3.1-24b-instruct-2503", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "mistralai/mamba-codestral-7b-v0.1": { + "id": "mistralai/mamba-codestral-7b-v0.1", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "mistralai/mistral-large-2-instruct": { + "id": "mistralai/mistral-large-2-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "microsoft/phi-3-medium-4k-instruct": { + "id": "microsoft/phi-3-medium-4k-instruct", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "output": 1024 + }, + "family": "phi" + }, + "microsoft/phi-3.5-moe-instruct": { + "id": "microsoft/phi-3.5-moe-instruct", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "family": "phi" + }, + "microsoft/phi-3-small-8k-instruct": { + "id": "microsoft/phi-3-small-8k-instruct", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 2048 + }, + "family": "phi" + }, + "microsoft/phi-3-vision-128k-instruct": { + "id": "microsoft/phi-3-vision-128k-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "microsoft/phi-3.5-vision-instruct": { + "id": "microsoft/phi-3.5-vision-instruct", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "family": "phi" + }, + "microsoft/phi-3-small-128k-instruct": { + "id": "microsoft/phi-3-small-128k-instruct", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "family": "phi" + }, + "microsoft/phi-3-medium-128k-instruct": { + "id": "microsoft/phi-3-medium-128k-instruct", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + }, + "family": "phi" + }, + "nvidia/nemotron-4-340b-instruct": { + "id": "nvidia/nemotron-4-340b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "nvidia/llama3-chatqa-1.5-70b": { + "id": "nvidia/llama3-chatqa-1.5-70b", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "nvidia/parakeet-tdt-0.6b-v2": { + "id": "nvidia/parakeet-tdt-0.6b-v2", + "family": "parakeet", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 0, + "output": 4096 + } + }, + "nvidia/cosmos-nemotron-34b": { + "id": "nvidia/cosmos-nemotron-34b", + "family": "nemotron", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "nvidia/llama-embed-nemotron-8b": { + "id": "nvidia/llama-embed-nemotron-8b", + "family": "llama", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 2048 + } + }, + "nvidia/llama-3.1-nemotron-51b-instruct": { + "id": "nvidia/llama-3.1-nemotron-51b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "nvidia/nemoretriever-ocr-v1": { + "id": "nvidia/nemoretriever-ocr-v1", + "family": "nemoretriever", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 0, + "output": 4096 + } + }, + "deepseek-ai/deepseek-coder-6.7b-instruct": { + "id": "deepseek-ai/deepseek-coder-6.7b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "z-ai/glm4.7": { + "id": "z-ai/glm4.7", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "z-ai/glm5": { + "id": "z-ai/glm5", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 131000 + } + }, + "meta/llama-4-scout-17b-16e-instruct": { + "id": "meta/llama-4-scout-17b-16e-instruct", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + }, + "family": "llama" + }, + "meta/llama-3.3-70b-instruct": { + "id": "meta/llama-3.3-70b-instruct", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + }, + "family": "llama" + }, + "meta/llama3-8b-instruct": { + "id": "meta/llama3-8b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "meta/llama3-70b-instruct": { + "id": "meta/llama3-70b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "meta/codellama-70b": { + "id": "meta/codellama-70b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "meta/llama-3.2-11b-vision-instruct": { + "id": "meta/llama-3.2-11b-vision-instruct", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + }, + "family": "llama" + }, + "meta/llama-3.1-70b-instruct": { + "id": "meta/llama-3.1-70b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "meta/llama-3.2-1b-instruct": { + "id": "meta/llama-3.2-1b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16000, + "output": 4096 + }, + "family": "llama" + }, + "meta/llama-4-maverick-17b-128e-instruct": { + "id": "meta/llama-4-maverick-17b-128e-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "meta/llama-3.1-405b-instruct": { + "id": "meta/llama-3.1-405b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "google/gemma-3-1b-it": { + "id": "google/gemma-3-1b-it", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "google/codegemma-7b": { + "id": "google/codegemma-7b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "google/codegemma-1.1-7b": { + "id": "google/codegemma-1.1-7b", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "google/gemma-3n-e2b-it": { + "id": "google/gemma-3n-e2b-it", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "mistral/mistral-nemo-12b-instruct": { + "id": "mistral/mistral-nemo-12b-instruct", + "family": "mistral-nemo", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16000, + "output": 4096 + } + }, + "meta/llama-3.2-3b-instruct": { + "id": "meta/llama-3.2-3b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16000, + "output": 4096 + } + }, + "meta/llama-3.1-8b-instruct": { + "id": "meta/llama-3.1-8b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16000, + "output": 4096 + } + }, + "qwen/qwen-2.5-7b-vision-instruct": { + "id": "qwen/qwen-2.5-7b-vision-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 125000, + "output": 4096 + } + }, + "google/gemma-3": { + "id": "google/gemma-3", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 125000, + "output": 4096 + } + }, + "osmosis/osmosis-structure-0.6b": { + "id": "osmosis/osmosis-structure-0.6b", + "family": "osmosis", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4000, + "output": 2048 + } + }, + "mercury-edit-2": { + "id": "mercury-edit-2", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "gpt-4o-2024-05-13": { + "id": "gpt-4o-2024-05-13", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "o4-mini-deep-research": { + "id": "o4-mini-deep-research", + "family": "o-mini", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 100000 + } + }, + "gpt-image-1": { + "id": "gpt-image-1", + "family": "gpt-image", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 0, + "input": 0, + "output": 0 + } + }, + "gpt-4o-2024-08-06": { + "id": "gpt-4o-2024-08-06", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "gpt-image-1-mini": { + "id": "gpt-image-1-mini", + "family": "gpt-image", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 0, + "input": 0, + "output": 0 + } + }, + "gpt-3.5-turbo": { + "id": "gpt-3.5-turbo", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 16385, + "output": 16384 + } + }, + "o3-deep-research": { + "id": "o3-deep-research", + "family": "o", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 100000 + } + }, + "o1-pro": { + "id": "o1-pro", + "family": "o-pro", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 100000 + } + }, + "chatgpt-image-latest": { + "id": "chatgpt-image-latest", + "family": "gpt-image", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 0, + "input": 0, + "output": 0 + } + }, + "gpt-image-1.5": { + "id": "gpt-image-1.5", + "family": "gpt-image", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 0, + "input": 0, + "output": 0 + } + }, + "xai/grok-4-fast": { + "id": "xai/grok-4-fast", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 64000 + } + }, + "anthropic/claude-3-7-sonnet": { + "id": "anthropic/claude-3-7-sonnet", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "glm-5-fp8": { + "id": "GLM-5-FP8", + "family": "glm", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 131072 + } + }, + "mistral-small-latest": { + "id": "mistral-small-latest", + "family": "mistral-small", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "labs-devstral-small-2512": { + "id": "labs-devstral-small-2512", + "family": "devstral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "devstral-2512": { + "id": "devstral-2512", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 16384 + } + }, + "magistral-medium-latest": { + "id": "magistral-medium-latest", + "family": "magistral-medium", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "open-mixtral-8x7b": { + "id": "open-mixtral-8x7b", + "family": "mixtral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 32000 + } + }, + "pixtral-large-latest": { + "id": "pixtral-large-latest", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "codestral-latest": { + "id": "codestral-latest", + "family": "codestral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 4096 + } + }, + "mistral-large-latest": { + "id": "mistral-large-latest", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "mistral-small-2506": { + "id": "mistral-small-2506", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "pixtral-12b": { + "id": "pixtral-12b", + "family": "pixtral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "ministral-8b-latest": { + "id": "ministral-8b-latest", + "family": "ministral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "mistral-embed": { + "id": "mistral-embed", + "family": "mistral-embed", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8000, + "output": 3072 + } + }, + "magistral-small": { + "id": "magistral-small", + "family": "magistral-small", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "ministral-3b-latest": { + "id": "ministral-3b-latest", + "family": "ministral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "open-mixtral-8x22b": { + "id": "open-mixtral-8x22b", + "family": "mixtral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 64000, + "output": 64000 + } + }, + "devstral-small-2505": { + "id": "devstral-small-2505", + "family": "devstral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "devstral-medium-2507": { + "id": "devstral-medium-2507", + "family": "devstral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "mistral-medium-latest": { + "id": "mistral-medium-latest", + "family": "mistral-medium", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "open-mistral-7b": { + "id": "open-mistral-7b", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8000, + "output": 8000 + } + }, + "devstral-medium-latest": { + "id": "devstral-medium-latest", + "family": "devstral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "devstral-small-2507": { + "id": "devstral-small-2507", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "mistral-medium-2508": { + "id": "mistral-medium-2508", + "family": "mistral-medium", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "mixtral-8x7b-instruct-v0.1": { + "id": "mixtral-8x7b-instruct-v0.1", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "qwen2.5-coder-32b-instruct": { + "id": "qwen2.5-coder-32b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "meta-llama-3_3-70b-instruct": { + "id": "meta-llama-3_3-70b-instruct", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "mistral-7b-instruct-v0.3": { + "id": "mistral-7b-instruct-v0.3", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "output": 65536 + } + }, + "nova-pro-v1": { + "id": "nova-pro-v1", + "family": "nova-pro", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 300000, + "output": 5000 + } + }, + "claude-4-5-sonnet": { + "id": "claude-4-5-sonnet", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 200000 + } + }, + "kimi-k2-instruct": { + "id": "kimi-k2-instruct", + "family": "kimi", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 131000 + } + }, + "claude-opus4-6": { + "id": "claude-opus4-6", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 1000000 + } + }, + "devstral-small-2512": { + "id": "devstral-small-2512", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 262000 + } + }, + "claude-opus4-5": { + "id": "claude-opus4-5", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 200000 + } + }, + "claude-4-6-sonnet": { + "id": "claude-4-6-sonnet", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 1000000 + } + }, + "intellect-3": { + "id": "intellect-3", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "llama-3.1-405b-instruct": { + "id": "llama-3.1-405b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "qwen/qwen2.5-vl-7b-instruct": { + "id": "Qwen/Qwen2.5-VL-7B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 33000, + "output": 4000 + } + }, + "zai-org/glm-5v-turbo": { + "id": "zai-org/GLM-5V-Turbo", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 131072 + } + }, + "alibaba/qwen3-coder-plus": { + "id": "alibaba/qwen3-coder-plus", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 1000000 + } + }, + "alibaba/qwen3-embedding-8b": { + "id": "alibaba/qwen3-embedding-8b", + "family": "qwen", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "alibaba/qwen-3-30b": { + "id": "alibaba/qwen-3-30b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 40960, + "output": 16384 + } + }, + "alibaba/qwen-3-235b": { + "id": "alibaba/qwen-3-235b", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 40960, + "output": 16384 + } + }, + "alibaba/qwen3.5-flash": { + "id": "alibaba/qwen3.5-flash", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "alibaba/qwen3.6-plus": { + "id": "alibaba/qwen3.6-plus", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "alibaba/qwen3-max": { + "id": "alibaba/qwen3-max", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "alibaba/qwen3-embedding-0.6b": { + "id": "alibaba/qwen3-embedding-0.6b", + "family": "qwen", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "alibaba/qwen-3-32b": { + "id": "alibaba/qwen-3-32b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 40960, + "output": 16384 + } + }, + "alibaba/qwen3-next-80b-a3b-thinking": { + "id": "alibaba/qwen3-next-80b-a3b-thinking", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + } + }, + "alibaba/qwen3-vl-thinking": { + "id": "alibaba/qwen3-vl-thinking", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 129024 + } + }, + "alibaba/qwen3-235b-a22b-thinking": { + "id": "alibaba/qwen3-235b-a22b-thinking", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262114, + "output": 262114 + } + }, + "alibaba/qwen3-next-80b-a3b-instruct": { + "id": "alibaba/qwen3-next-80b-a3b-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "alibaba/qwen3-coder-next": { + "id": "alibaba/qwen3-coder-next", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "alibaba/qwen3-embedding-4b": { + "id": "alibaba/qwen3-embedding-4b", + "family": "qwen", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "alibaba/qwen3-max-thinking": { + "id": "alibaba/qwen3-max-thinking", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 65536 + } + }, + "alibaba/qwen3-coder": { + "id": "alibaba/qwen3-coder", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 66536 + } + }, + "alibaba/qwen3-max-preview": { + "id": "alibaba/qwen3-max-preview", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "alibaba/qwen3.5-plus": { + "id": "alibaba/qwen3.5-plus", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "alibaba/qwen-3-14b": { + "id": "alibaba/qwen-3-14b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 40960, + "output": 16384 + } + }, + "alibaba/qwen3-vl-instruct": { + "id": "alibaba/qwen3-vl-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 129024 + } + }, + "alibaba/qwen3-coder-30b-a3b": { + "id": "alibaba/qwen3-coder-30b-a3b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 160000, + "output": 32768 + } + }, + "perplexity/sonar-reasoning": { + "id": "perplexity/sonar-reasoning", + "family": "sonar-reasoning", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 127000, + "output": 8000 + } + }, + "deepseek/deepseek-v3.2-thinking": { + "id": "deepseek/deepseek-v3.2-thinking", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 64000 + } + }, + "deepseek/deepseek-v3": { + "id": "deepseek/deepseek-v3", + "family": "deepseek", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 16384 + } + }, + "arcee-ai/trinity-large-preview": { + "id": "arcee-ai/trinity-large-preview", + "family": "trinity", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131000, + "output": 131000 + } + }, + "recraft/recraft-v3": { + "id": "recraft/recraft-v3", + "family": "recraft", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 512, + "output": 0 + } + }, + "recraft/recraft-v2": { + "id": "recraft/recraft-v2", + "family": "recraft", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 512, + "output": 0 + } + }, + "voyage/voyage-3-large": { + "id": "voyage/voyage-3-large", + "family": "voyage", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1536 + } + }, + "voyage/voyage-4-large": { + "id": "voyage/voyage-4-large", + "family": "voyage", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 0 + } + }, + "voyage/voyage-3.5-lite": { + "id": "voyage/voyage-3.5-lite", + "family": "voyage", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1536 + } + }, + "voyage/voyage-code-3": { + "id": "voyage/voyage-code-3", + "family": "voyage", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1536 + } + }, + "voyage/voyage-finance-2": { + "id": "voyage/voyage-finance-2", + "family": "voyage", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1536 + } + }, + "voyage/voyage-4-lite": { + "id": "voyage/voyage-4-lite", + "family": "voyage", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 0 + } + }, + "voyage/voyage-4": { + "id": "voyage/voyage-4", + "family": "voyage", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 0 + } + }, + "voyage/voyage-code-2": { + "id": "voyage/voyage-code-2", + "family": "voyage", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1536 + } + }, + "voyage/voyage-law-2": { + "id": "voyage/voyage-law-2", + "family": "voyage", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1536 + } + }, + "voyage/voyage-3.5": { + "id": "voyage/voyage-3.5", + "family": "voyage", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1536 + } + }, + "zai/glm-5v-turbo": { + "id": "zai/glm-5v-turbo", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 128000 + } + }, + "zai/glm-4.7": { + "id": "zai/glm-4.7", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 120000 + } + }, + "zai/glm-5": { + "id": "zai/glm-5", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202800, + "output": 131072 + } + }, + "zai/glm-4.7-flashx": { + "id": "zai/glm-4.7-flashx", + "family": "glm-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 128000 + } + }, + "zai/glm-5.1": { + "id": "zai/glm-5.1", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 202752 + } + }, + "zai/glm-4.6v-flash": { + "id": "zai/glm-4.6v-flash", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 24000 + } + }, + "zai/glm-4.5": { + "id": "zai/glm-4.5", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "zai/glm-4.5-air": { + "id": "zai/glm-4.5-air", + "family": "glm-air", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 96000 + } + }, + "zai/glm-5-turbo": { + "id": "zai/glm-5-turbo", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202800, + "output": 131100 + } + }, + "zai/glm-4.5v": { + "id": "zai/glm-4.5v", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 66000, + "output": 66000 + } + }, + "zai/glm-4.6": { + "id": "zai/glm-4.6", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 96000 + } + }, + "zai/glm-4.6v": { + "id": "zai/glm-4.6v", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 24000 + } + }, + "zai/glm-4.7-flash": { + "id": "zai/glm-4.7-flash", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 131000 + } + }, + "cohere/embed-v4.0": { + "id": "cohere/embed-v4.0", + "family": "cohere-embed", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1536 + } + }, + "xai/grok-4.20-non-reasoning": { + "id": "xai/grok-4.20-non-reasoning", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 2000000 + } + }, + "xai/grok-4.20-non-reasoning-beta": { + "id": "xai/grok-4.20-non-reasoning-beta", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 2000000 + } + }, + "xai/grok-4.20-reasoning": { + "id": "xai/grok-4.20-reasoning", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 2000000 + } + }, + "xai/grok-imagine-image": { + "id": "xai/grok-imagine-image", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 0, + "output": 0 + } + }, + "xai/grok-4.20-multi-agent-beta": { + "id": "xai/grok-4.20-multi-agent-beta", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 2000000 + } + }, + "xai/grok-imagine-image-pro": { + "id": "xai/grok-imagine-image-pro", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 0, + "output": 0 + } + }, + "xai/grok-4.20-reasoning-beta": { + "id": "xai/grok-4.20-reasoning-beta", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 2000000 + } + }, + "xai/grok-2-vision": { + "id": "xai/grok-2-vision", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 4096 + } + }, + "xai/grok-3-fast": { + "id": "xai/grok-3-fast", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "xai/grok-3-mini-fast": { + "id": "xai/grok-3-mini-fast", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "inception/mercury-coder-small": { + "id": "inception/mercury-coder-small", + "family": "mercury", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 16384 + } + }, + "openai/codex-mini": { + "id": "openai/codex-mini", + "family": "gpt-codex-mini", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "input": 100000, + "output": 100000 + } + }, + "openai/text-embedding-3-large": { + "id": "openai/text-embedding-3-large", + "family": "text-embedding", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "input": 6656, + "output": 1536 + } + }, + "openai/text-embedding-ada-002": { + "id": "openai/text-embedding-ada-002", + "family": "text-embedding", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "input": 6656, + "output": 1536 + } + }, + "openai/gpt-5.1-thinking": { + "id": "openai/gpt-5.1-thinking", + "family": "gpt", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "openai/text-embedding-3-small": { + "id": "openai/text-embedding-3-small", + "family": "text-embedding", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "input": 6656, + "output": 1536 + } + }, + "amazon/titan-embed-text-v2": { + "id": "amazon/titan-embed-text-v2", + "family": "titan-embed", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1536 + } + }, + "amazon/nova-2-lite": { + "id": "amazon/nova-2-lite", + "family": "nova", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 1000000 + } + }, + "amazon/nova-pro": { + "id": "amazon/nova-pro", + "family": "nova-pro", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 300000, + "output": 8192 + } + }, + "amazon/nova-lite": { + "id": "amazon/nova-lite", + "family": "nova-lite", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 300000, + "output": 8192 + } + }, + "amazon/nova-micro": { + "id": "amazon/nova-micro", + "family": "nova-micro", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "mistral/mistral-nemo": { + "id": "mistral/mistral-nemo", + "family": "mistral-nemo", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 60288, + "output": 16000 + } + }, + "mistral/ministral-14b": { + "id": "mistral/ministral-14b", + "family": "ministral", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "mistral/codestral-embed": { + "id": "mistral/codestral-embed", + "family": "codestral-embed", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1536 + } + }, + "mistral/mistral-medium": { + "id": "mistral/mistral-medium", + "family": "mistral-medium", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 64000 + } + }, + "mistral/mistral-embed": { + "id": "mistral/mistral-embed", + "family": "mistral-embed", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1536 + } + }, + "mistral/devstral-2": { + "id": "mistral/devstral-2", + "family": "devstral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "mistral/mistral-large-3": { + "id": "mistral/mistral-large-3", + "family": "mistral-large", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "mistral/devstral-small-2": { + "id": "mistral/devstral-small-2", + "family": "devstral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "mistral/devstral-small": { + "id": "mistral/devstral-small", + "family": "devstral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 64000 + } + }, + "mistral/ministral-8b": { + "id": "mistral/ministral-8b", + "family": "ministral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "mistral/magistral-medium": { + "id": "mistral/magistral-medium", + "family": "magistral-medium", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "mistral/mistral-small": { + "id": "mistral/mistral-small", + "family": "mistral-small", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "mistral/magistral-small": { + "id": "mistral/magistral-small", + "family": "magistral-small", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "mistral/pixtral-12b": { + "id": "mistral/pixtral-12b", + "family": "pixtral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "mistral/mixtral-8x22b-instruct": { + "id": "mistral/mixtral-8x22b-instruct", + "family": "mixtral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 64000, + "output": 64000 + } + }, + "mistral/pixtral-large": { + "id": "mistral/pixtral-large", + "family": "pixtral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "mistral/ministral-3b": { + "id": "mistral/ministral-3b", + "family": "ministral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 128000 + } + }, + "mistral/codestral": { + "id": "mistral/codestral", + "family": "codestral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 4096 + } + }, + "meta/llama-3.2-1b": { + "id": "meta/llama-3.2-1b", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "meta/llama-3.1-8b": { + "id": "meta/llama-3.1-8b", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "meta/llama-3.2-90b": { + "id": "meta/llama-3.2-90b", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "meta/llama-3.2-3b": { + "id": "meta/llama-3.2-3b", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "meta/llama-3.2-11b": { + "id": "meta/llama-3.2-11b", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "meta/llama-3.1-70b": { + "id": "meta/llama-3.1-70b", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "meta/llama-3.3-70b": { + "id": "meta/llama-3.3-70b", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "meta/llama-4-maverick": { + "id": "meta/llama-4-maverick", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "meta/llama-4-scout": { + "id": "meta/llama-4-scout", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "vercel/v0-1.5-md": { + "id": "vercel/v0-1.5-md", + "family": "v0", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32000 + } + }, + "vercel/v0-1.0-md": { + "id": "vercel/v0-1.0-md", + "family": "v0", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32000 + } + }, + "minimax/minimax-m2.1-lightning": { + "id": "minimax/minimax-m2.1-lightning", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "kwaipilot/kat-coder-pro-v1": { + "id": "kwaipilot/kat-coder-pro-v1", + "family": "kat-coder", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 32000 + } + }, + "google/gemini-3-pro-image": { + "id": "google/gemini-3-pro-image", + "family": "gemini-pro", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 65536, + "output": 32768 + } + }, + "google/imagen-4.0-ultra-generate-001": { + "id": "google/imagen-4.0-ultra-generate-001", + "family": "imagen", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 480, + "output": 0 + } + }, + "google/gemini-embedding-001": { + "id": "google/gemini-embedding-001", + "family": "gemini-embedding", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1536 + } + }, + "google/text-embedding-005": { + "id": "google/text-embedding-005", + "family": "text-embedding", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1536 + } + }, + "google/text-multilingual-embedding-002": { + "id": "google/text-multilingual-embedding-002", + "family": "text-embedding", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 1536 + } + }, + "google/imagen-4.0-generate-001": { + "id": "google/imagen-4.0-generate-001", + "family": "imagen", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 480, + "output": 0 + } + }, + "google/gemini-embedding-2": { + "id": "google/gemini-embedding-2", + "family": "gemini-embedding", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 0, + "output": 0 + } + }, + "google/imagen-4.0-fast-generate-001": { + "id": "google/imagen-4.0-fast-generate-001", + "family": "imagen", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 480, + "output": 0 + } + }, + "google/gemini-2.5-flash-image-preview": { + "id": "google/gemini-2.5-flash-image-preview", + "family": "gemini-flash", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "moonshotai/kimi-k2-turbo": { + "id": "moonshotai/kimi-k2-turbo", + "family": "kimi", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 16384 + } + }, + "anthropic/claude-3.5-sonnet-20240620": { + "id": "anthropic/claude-3.5-sonnet-20240620", + "family": "claude-sonnet", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + } + }, + "bytedance/seed-1.6": { + "id": "bytedance/seed-1.6", + "family": "seed", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 32000 + } + }, + "bytedance/seed-1.8": { + "id": "bytedance/seed-1.8", + "family": "seed", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 64000 + } + }, + "meituan/longcat-flash-thinking": { + "id": "meituan/longcat-flash-thinking", + "family": "longcat", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "meituan/longcat-flash-thinking-2601": { + "id": "meituan/longcat-flash-thinking-2601", + "family": "longcat", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "bfl/flux-pro-1.0-fill": { + "id": "bfl/flux-pro-1.0-fill", + "family": "flux", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 512, + "output": 0 + } + }, + "bfl/flux-pro-1.1": { + "id": "bfl/flux-pro-1.1", + "family": "flux", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 512, + "output": 0 + } + }, + "bfl/flux-kontext-pro": { + "id": "bfl/flux-kontext-pro", + "family": "flux", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 512, + "output": 0 + } + }, + "bfl/flux-kontext-max": { + "id": "bfl/flux-kontext-max", + "family": "flux", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 512, + "output": 0 + } + }, + "bfl/flux-pro-1.1-ultra": { + "id": "bfl/flux-pro-1.1-ultra", + "family": "flux", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "image" + ] + }, + "limit": { + "context": 512, + "output": 0 + } + }, + "gpt-4o-mini-search-preview": { + "id": "gpt-4o-mini-search-preview", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "grok-4-20-beta-0309-non-reasoning": { + "id": "grok-4-20-beta-0309-non-reasoning", "family": "grok", "reasoning": false, "temperature": true, @@ -39481,65 +38649,8 @@ "output": 30000 } }, - "grok-3-fast": { - "id": "grok-3-fast", - "family": "grok", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "qwen-math-plus": { - "id": "qwen-math-plus", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4096, - "output": 3072 - } - }, - "deepseek-v3-1": { - "id": "deepseek-v3-1", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 65536 - } - }, - "qwen2-5-coder-7b-instruct": { - "id": "qwen2-5-coder-7b-instruct", + "qwen-coder-plus": { + "id": "qwen-coder-plus", "family": "qwen", "reasoning": false, "temperature": true, @@ -39557,66 +38668,9 @@ "output": 8192 } }, - "deepseek-r1-distill-qwen-14b": { - "id": "deepseek-r1-distill-qwen-14b", - "family": "qwen", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 16384 - } - }, - "moonshot-kimi-k2-instruct": { - "id": "moonshot-kimi-k2-instruct", - "family": "kimi", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "qwen-doc-turbo": { - "id": "qwen-doc-turbo", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 8192 - } - }, - "tongyi-intent-detect-v3": { - "id": "tongyi-intent-detect-v3", - "family": "yi", + "gemma-3n-e4b-it": { + "id": "gemma-3n-e4b-it", + "family": "gemma", "reasoning": false, "temperature": true, "toolCall": false, @@ -39630,53 +38684,35 @@ }, "limit": { "context": 8192, - "output": 1024 + "output": 2000 } }, - "qwen-plus-character": { - "id": "qwen-plus-character", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 4096 - } - }, - "deepseek-v3-2-exp": { - "id": "deepseek-v3-2-exp", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 65536 - } - }, - "deepseek-r1-distill-llama-8b": { - "id": "deepseek-r1-distill-llama-8b", - "family": "deepseek-thinking", + "glm-4.6v-flashx": { + "id": "glm-4.6v-flashx", + "family": "glm", "reasoning": true, "temperature": true, "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16000 + } + }, + "gemma-2-27b-it-together": { + "id": "gemma-2-27b-it-together", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": false, "modalities": { "input": [ "text" @@ -39686,12 +38722,50 @@ ] }, "limit": { - "context": 32768, + "context": 8192, "output": 16384 } }, - "qwen3.5-flash": { - "id": "qwen3.5-flash", + "codestral-2508": { + "id": "codestral-2508", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 16384 + } + }, + "gemma-3-1b-it": { + "id": "gemma-3-1b-it", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 16384 + } + }, + "qwen35-397b-a17b": { + "id": "qwen35-397b-a17b", "family": "qwen", "reasoning": true, "temperature": true, @@ -39699,21 +38773,20 @@ "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 262144, "output": 65536 } }, - "qwen2-5-math-7b-instruct": { - "id": "qwen2-5-math-7b-instruct", - "family": "qwen", + "glm-4-32b-0414-128k": { + "id": "glm-4-32b-0414-128k", + "family": "glm", "reasoning": false, "temperature": true, "toolCall": true, @@ -39726,114 +38799,100 @@ ] }, "limit": { - "context": 4096, - "output": 3072 + "context": 128000, + "output": 16384 } }, - "deepseek-r1-distill-qwen-1-5b": { - "id": "deepseek-r1-distill-qwen-1-5b", - "family": "qwen", + "seed-1-6-flash-250715": { + "id": "seed-1-6-flash-250715", + "family": "seed", "reasoning": true, "temperature": true, "toolCall": true, "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, + "context": 256000, "output": 16384 } }, - "deepseek-r1-distill-qwen-7b": { - "id": "deepseek-r1-distill-qwen-7b", - "family": "qwen", + "seed-1-6-250615": { + "id": "seed-1-6-250615", + "family": "seed", "reasoning": true, "temperature": true, "toolCall": true, "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 32768, + "context": 256000, "output": 16384 } }, - "qwen-deep-research": { - "id": "qwen-deep-research", + "cogview-4": { + "id": "cogview-4", + "family": "glm", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 2000, + "output": 4096 + } + }, + "qwen2-5-vl-32b-instruct": { + "id": "qwen2-5-vl-32b-instruct", "family": "qwen", "reasoning": false, "temperature": true, - "toolCall": true, + "toolCall": false, "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 1000000, + "context": 131072, "output": 32768 } }, - "qwen2-5-math-72b-instruct": { - "id": "qwen2-5-math-72b-instruct", + "qwen3-vl-8b-instruct": { + "id": "qwen3-vl-8b-instruct", "family": "qwen", "reasoning": false, "temperature": true, - "toolCall": true, + "toolCall": false, "modalities": { "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4096, - "output": 3072 - } - }, - "qwen-math-turbo": { - "id": "qwen-math-turbo", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 4096, - "output": 3072 - } - }, - "qwen2-5-coder-32b-instruct": { - "id": "qwen2-5-coder-32b-instruct", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" + "text", + "image" ], "output": [ "text" @@ -39844,238 +38903,89 @@ "output": 8192 } }, - "kimi/kimi-k2.5": { - "id": "kimi/kimi-k2.5", - "family": "kimi", + "claude-3-7-sonnet": { + "id": "claude-3-7-sonnet", + "family": "claude", "reasoning": true, - "temperature": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + } + }, + "grok-4-20-beta-0309-reasoning": { + "id": "grok-4-20-beta-0309-reasoning", + "family": "grok", + "reasoning": true, + "temperature": true, "toolCall": true, "modalities": { "input": [ "text", - "image", - "video" + "image" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 262144 + "context": 2000000, + "output": 30000 } }, - "siliconflow/deepseek-r1-0528": { - "id": "siliconflow/deepseek-r1-0528", - "family": "deepseek-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 32768 - } - }, - "siliconflow/deepseek-v3-0324": { - "id": "siliconflow/deepseek-v3-0324", - "family": "deepseek", + "grok-imagine-image": { + "id": "grok-imagine-image", + "family": "grok", "reasoning": false, "temperature": true, - "toolCall": true, + "toolCall": false, "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ - "text" + "text", + "image" ] }, "limit": { - "context": 163840, - "output": 163840 + "context": 2000, + "output": 4096 } }, - "siliconflow/deepseek-v3.1-terminus": { - "id": "siliconflow/deepseek-v3.1-terminus", - "family": "deepseek", + "gemini-pro-latest": { + "id": "gemini-pro-latest", + "family": "gemini", "reasoning": true, "temperature": true, "toolCall": true, "modalities": { "input": [ - "text" + "text", + "image" ], "output": [ "text" ] }, "limit": { - "context": 163840, + "context": 1048576, "output": 65536 } }, - "siliconflow/deepseek-v3.2": { - "id": "siliconflow/deepseek-v3.2", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 65536 - } - }, - "zai-org/glm-4.7-tee": { - "id": "zai-org/GLM-4.7-TEE", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 65535 - } - }, - "zai-org/glm-4.6-tee": { - "id": "zai-org/GLM-4.6-TEE", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 65536 - } - }, - "zai-org/glm-5-tee": { - "id": "zai-org/GLM-5-TEE", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 65535 - } - }, - "zai-org/glm-4.6-fp8": { - "id": "zai-org/GLM-4.6-FP8", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 65535 - } - }, - "zai-org/glm-4.5-tee": { - "id": "zai-org/GLM-4.5-TEE", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 65536 - } - }, - "zai-org/glm-5-turbo": { - "id": "zai-org/GLM-5-Turbo", - "family": "glm", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 202752, - "output": 65535 - } - }, - "nvidia/nvidia-nemotron-3-nano-30b-a3b-bf16": { - "id": "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16", - "family": "nemotron", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "nousresearch/hermes-4.3-36b": { - "id": "NousResearch/Hermes-4.3-36B", - "family": "nousresearch", + "mixtral-8x7b-instruct-together": { + "id": "mixtral-8x7b-instruct-together", + "family": "mistral", "reasoning": false, "temperature": true, "toolCall": false, @@ -40089,15 +38999,35 @@ }, "limit": { "context": 32768, - "output": 8192 + "output": 16384 } }, - "nousresearch/deephermes-3-mistral-24b-preview": { - "id": "NousResearch/DeepHermes-3-Mistral-24B-Preview", - "family": "nousresearch", + "seedream-4-0": { + "id": "seedream-4-0", + "family": "seed", "reasoning": false, "temperature": true, - "toolCall": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 2000, + "output": 4096 + } + }, + "minimax-text-01": { + "id": "minimax-text-01", + "family": "minimax", + "reasoning": false, + "temperature": true, + "toolCall": false, "modalities": { "input": [ "text" @@ -40107,16 +39037,16 @@ ] }, "limit": { - "context": 32768, - "output": 32768 + "context": 1000000, + "output": 131072 } }, - "nousresearch/hermes-4-14b": { - "id": "NousResearch/Hermes-4-14B", - "family": "nousresearch", - "reasoning": true, + "qwen3-32b-fp8": { + "id": "qwen3-32b-fp8", + "family": "qwen", + "reasoning": false, "temperature": true, - "toolCall": true, + "toolCall": false, "modalities": { "input": [ "text" @@ -40127,202 +39057,12 @@ }, "limit": { "context": 40960, - "output": 40960 + "output": 20000 } }, - "nousresearch/hermes-4-405b-fp8-tee": { - "id": "NousResearch/Hermes-4-405B-FP8-TEE", - "family": "nousresearch", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 65536 - } - }, - "minimaxai/minimax-m2.5-tee": { - "id": "MiniMaxAI/MiniMax-M2.5-TEE", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 196608, - "output": 65536 - } - }, - "minimaxai/minimax-m2.1-tee": { - "id": "MiniMaxAI/MiniMax-M2.1-TEE", - "family": "minimax", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 196608, - "output": 65536 - } - }, - "deepseek-ai/deepseek-v3.1-terminus-tee": { - "id": "deepseek-ai/DeepSeek-V3.1-Terminus-TEE", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 65536 - } - }, - "deepseek-ai/deepseek-v3.2-tee": { - "id": "deepseek-ai/DeepSeek-V3.2-TEE", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 131072, - "output": 65536 - } - }, - "deepseek-ai/deepseek-v3-0324-tee": { - "id": "deepseek-ai/DeepSeek-V3-0324-TEE", - "family": "deepseek", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 65536 - } - }, - "deepseek-ai/deepseek-v3.2-speciale-tee": { - "id": "deepseek-ai/DeepSeek-V3.2-Speciale-TEE", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 65536 - } - }, - "deepseek-ai/deepseek-r1-tee": { - "id": "deepseek-ai/DeepSeek-R1-TEE", - "family": "deepseek-thinking", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 163840 - } - }, - "deepseek-ai/deepseek-v3.1-tee": { - "id": "deepseek-ai/DeepSeek-V3.1-TEE", - "family": "deepseek", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 65536 - } - }, - "deepseek-ai/deepseek-r1-0528-tee": { - "id": "deepseek-ai/DeepSeek-R1-0528-TEE", - "family": "deepseek-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 65536 - } - }, - "rednote-hilab/dots.ocr": { - "id": "rednote-hilab/dots.ocr", - "family": "rednote", + "llama-4-scout-17b-instruct": { + "id": "llama-4-scout-17b-instruct", + "family": "llama", "reasoning": false, "temperature": true, "toolCall": false, @@ -40336,13 +39076,51 @@ ] }, "limit": { - "context": 131072, - "output": 131072 + "context": 8192, + "output": 2048 } }, - "unsloth/mistral-nemo-instruct-2407": { - "id": "unsloth/Mistral-Nemo-Instruct-2407", - "family": "unsloth", + "qwen3-4b-fp8": { + "id": "qwen3-4b-fp8", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 20000 + } + }, + "veo-3.1-generate-preview": { + "id": "veo-3.1-generate-preview", + "family": "gemini", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 1 + } + }, + "llama-guard-4-12b": { + "id": "llama-guard-4-12b", + "family": "llama", "reasoning": false, "temperature": true, "toolCall": false, @@ -40356,12 +39134,73 @@ }, "limit": { "context": 131072, - "output": 131072 + "output": 16384 } }, - "unsloth/mistral-small-24b-instruct-2501": { - "id": "unsloth/Mistral-Small-24B-Instruct-2501", - "family": "unsloth", + "gemma-3n-e2b-it": { + "id": "gemma-3n-e2b-it", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 2000 + } + }, + "gemini-3.1-flash-image-preview": { + "id": "gemini-3.1-flash-image-preview", + "family": "gemini-flash", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "ministral-8b-2512": { + "id": "ministral-8b-2512", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 16384 + } + }, + "gemma-3-27b": { + "id": "gemma-3-27b", + "family": "gemma", "reasoning": false, "temperature": true, "toolCall": true, @@ -40374,13 +39213,627 @@ "text" ] }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "grok-imagine-image-pro": { + "id": "grok-imagine-image-pro", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 2000, + "output": 4096 + } + }, + "qwen3-vl-flash": { + "id": "qwen3-vl-flash", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 32768 + } + }, + "llama-3.1-70b-instruct": { + "id": "llama-3.1-70b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 2048 + } + }, + "seed-1-8-251228": { + "id": "seed-1-8-251228", + "family": "seed", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 16384 + } + }, + "seed-1-6-250915": { + "id": "seed-1-6-250915", + "family": "seed", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 16384 + } + }, + "glm-4.5-x": { + "id": "glm-4.5-x", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "veo-3.1-fast-generate-preview": { + "id": "veo-3.1-fast-generate-preview", + "family": "gemini", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 1 + } + }, + "gemma-3-4b-it": { + "id": "gemma-3-4b-it", + "family": "gemma", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, "limit": { "context": 32768, + "output": 8192 + } + }, + "qwen-image-max": { + "id": "qwen-image-max", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 2000, + "output": 4096 + } + }, + "claude-3-5-sonnet": { + "id": "claude-3-5-sonnet", + "family": "claude", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 16384 + } + }, + "qwen-image-max-2025-12-30": { + "id": "qwen-image-max-2025-12-30", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 2000, + "output": 4096 + } + }, + "ministral-3b-2512": { + "id": "ministral-3b-2512", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "grok-4-20-multi-agent-beta-0309": { + "id": "grok-4-20-multi-agent-beta-0309", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 2000000, + "output": 30000 + } + }, + "qwen-plus-latest": { + "id": "qwen-plus-latest", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 32000 + } + }, + "seedream-4-5": { + "id": "seedream-4-5", + "family": "seed", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 2000, + "output": 4096 + } + }, + "llama-3.1-nemotron-ultra-253b": { + "id": "llama-3.1-nemotron-ultra-253b", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "llama-4-maverick-17b-instruct": { + "id": "llama-4-maverick-17b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 2048 + } + }, + "qwen-image-edit-plus": { + "id": "qwen-image-edit-plus", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 2000, + "output": 4096 + } + }, + "qwen3-30b-a3b-fp8": { + "id": "qwen3-30b-a3b-fp8", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 40960, + "output": 20000 + } + }, + "minimax-m2.1-lightning": { + "id": "minimax-m2.1-lightning", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 196608, + "output": 131072 + } + }, + "claude-3-haiku": { + "id": "claude-3-haiku", + "family": "claude", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 4096 + } + }, + "glm-image": { + "id": "glm-image", + "family": "glm", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 2000, + "output": 4096 + } + }, + "qwen-image-edit-max": { + "id": "qwen-image-edit-max", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 2000, + "output": 4096 + } + }, + "llama-3.2-3b-instruct": { + "id": "llama-3.2-3b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32000 + } + }, + "qwen-image-plus": { + "id": "qwen-image-plus", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 2000, + "output": 4096 + } + }, + "gpt-4o-search-preview": { + "id": "gpt-4o-search-preview", + "family": "gpt", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "custom": { + "id": "custom", + "family": "auto", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "qwen3-vl-30b-a3b-instruct": { + "id": "qwen3-vl-30b-a3b-instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, "output": 32768 } }, - "unsloth/llama-3.2-1b-instruct": { - "id": "unsloth/Llama-3.2-1B-Instruct", + "qwen3-235b-a22b-fp8": { + "id": "qwen3-235b-a22b-fp8", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 40960, + "output": 20000 + } + }, + "llama-3-8b-instruct": { + "id": "llama-3-8b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "gemini-2.5-flash-image-preview": { + "id": "gemini-2.5-flash-image-preview", + "family": "gemini-flash", + "reasoning": true, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text", + "image" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "qwen25-coder-7b": { + "id": "qwen25-coder-7b", + "family": "qwen", "reasoning": false, "temperature": true, "toolCall": false, @@ -40397,9 +39850,9 @@ "output": 8192 } }, - "unsloth/llama-3.2-3b-instruct": { - "id": "unsloth/Llama-3.2-3B-Instruct", - "family": "unsloth", + "llama-3-70b-instruct": { + "id": "llama-3-70b-instruct", + "family": "llama", "reasoning": false, "temperature": true, "toolCall": false, @@ -40412,13 +39865,71 @@ ] }, "limit": { - "context": 16384, + "context": 8192, + "output": 8000 + } + }, + "glm-4.5-airx": { + "id": "glm-4.5-airx", + "family": "glm", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, "output": 16384 } }, - "moonshotai/kimi-k2.5-tee": { - "id": "moonshotai/Kimi-K2.5-TEE", - "family": "kimi", + "llama-3.2-11b-instruct": { + "id": "llama-3.2-11b-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "claude-3-opus": { + "id": "claude-3-opus", + "family": "claude", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 4096 + } + }, + "gemini-flash-lite-latest": { + "id": "gemini-flash-lite-latest", + "family": "gemini-flash-lite", "reasoning": true, "temperature": true, "toolCall": true, @@ -40426,209 +39937,165 @@ "input": [ "text", "image", - "video" + "audio", + "video", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, - "output": 65535 + "context": 1048576, + "output": 65536 } }, - "moonshotai/kimi-k2-thinking-tee": { - "id": "moonshotai/Kimi-K2-Thinking-TEE", - "family": "kimi-thinking", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65535 - } - }, - "qwen/qwen3.5-397b-a17b-tee": { - "id": "Qwen/Qwen3.5-397B-A17B-TEE", - "family": "qwen", + "gemini-3.1-pro-preview-customtools": { + "id": "gemini-3.1-pro-preview-customtools", + "family": "gemini-pro", "reasoning": true, "temperature": true, "toolCall": true, "modalities": { "input": [ "text", - "image" + "image", + "video", + "audio", + "pdf" ], "output": [ "text" ] }, "limit": { - "context": 262144, + "context": 1048576, "output": 65536 } }, - "qwen/qwen3-coder-480b-a35b-instruct-fp8-tee": { - "id": "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8-TEE", - "family": "qwen", + "gemini-embedding-001": { + "id": "gemini-embedding-001", + "family": "gemini", "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 262144 - } - }, - "qwen/qwen3-235b-a22b-instruct-2507-tee": { - "id": "Qwen/Qwen3-235B-A22B-Instruct-2507-TEE", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "qwen/qwen2.5-vl-72b-instruct-tee": { - "id": "Qwen/Qwen2.5-VL-72B-Instruct-TEE", - "family": "qwen", - "reasoning": false, - "temperature": true, + "temperature": false, "toolCall": false, "modalities": { "input": [ - "text", - "image" + "text" ], "output": [ "text" ] }, "limit": { - "context": 32768, + "context": 2048, + "output": 3072 + } + }, + "gemini-flash-latest": { + "id": "gemini-flash-latest", + "family": "gemini-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1048576, + "output": 65536 + } + }, + "zai-org/glm-5-maas": { + "id": "zai-org/glm-5-maas", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 131072 + } + }, + "zai-org/glm-4.7-maas": { + "id": "zai-org/glm-4.7-maas", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 128000 + } + }, + "deepseek-ai/deepseek-v3.2-maas": { + "id": "deepseek-ai/deepseek-v3.2-maas", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, + "output": 65536 + } + }, + "deepseek-ai/deepseek-v3.1-maas": { + "id": "deepseek-ai/deepseek-v3.1-maas", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 163840, "output": 32768 } }, - "qwen/qwen3guard-gen-0.6b": { - "id": "Qwen/Qwen3Guard-Gen-0.6B", - "family": "qwen", - "reasoning": false, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 32768, - "output": 8192 - } - }, - "tngtech/deepseek-r1t-chimera": { - "id": "tngtech/DeepSeek-R1T-Chimera", - "family": "tngtech", - "reasoning": true, - "temperature": true, - "toolCall": false, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 163840 - } - }, - "tngtech/tng-r1t-chimera-turbo": { - "id": "tngtech/TNG-R1T-Chimera-Turbo", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 65536 - } - }, - "tngtech/tng-r1t-chimera-tee": { - "id": "tngtech/TNG-R1T-Chimera-TEE", - "family": "tngtech", - "reasoning": true, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 163840, - "output": 65536 - } - }, - "mistralai/devstral-2-123b-instruct-2512-tee": { - "id": "mistralai/Devstral-2-123B-Instruct-2512-TEE", - "reasoning": false, - "temperature": true, - "toolCall": true, - "modalities": { - "input": [ - "text" - ], - "output": [ - "text" - ] - }, - "limit": { - "context": 262144, - "output": 65536 - } - }, - "openai/gpt-oss-120b-tee": { - "id": "openai/gpt-oss-120b-TEE", + "openai/gpt-oss-120b-maas": { + "id": "openai/gpt-oss-120b-maas", "family": "gpt-oss", "reasoning": true, "temperature": true, @@ -40643,12 +40110,432 @@ }, "limit": { "context": 131072, - "output": 65536 + "output": 32768 } }, - "chutesai/mistral-small-3.1-24b-instruct-2503": { - "id": "chutesai/Mistral-Small-3.1-24B-Instruct-2503", - "family": "chutesai", + "openai/gpt-oss-20b-maas": { + "id": "openai/gpt-oss-20b-maas", + "family": "gpt-oss", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32768 + } + }, + "meta/llama-3.3-70b-instruct-maas": { + "id": "meta/llama-3.3-70b-instruct-maas", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "meta/llama-4-maverick-17b-128e-instruct-maas": { + "id": "meta/llama-4-maverick-17b-128e-instruct-maas", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 524288, + "output": 8192 + } + }, + "qwen/qwen3-235b-a22b-instruct-2507-maas": { + "id": "qwen/qwen3-235b-a22b-instruct-2507-maas", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 16384 + } + }, + "moonshotai/kimi-k2-thinking-maas": { + "id": "moonshotai/kimi-k2-thinking-maas", + "family": "kimi-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "@cf/zai-org/glm-4.7-flash": { + "id": "@cf/zai-org/glm-4.7-flash", + "family": "glm-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "@cf/nvidia/nemotron-3-120b-a12b": { + "id": "@cf/nvidia/nemotron-3-120b-a12b", + "family": "nemotron", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "@cf/openai/gpt-oss-20b": { + "id": "@cf/openai/gpt-oss-20b", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "@cf/openai/gpt-oss-120b": { + "id": "@cf/openai/gpt-oss-120b", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "@cf/meta/llama-4-scout-17b-16e-instruct": { + "id": "@cf/meta/llama-4-scout-17b-16e-instruct", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "@cf/google/gemma-4-26b-a4b-it": { + "id": "@cf/google/gemma-4-26b-a4b-it", + "family": "gemma", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 16384 + } + }, + "@cf/moonshotai/kimi-k2.5": { + "id": "@cf/moonshotai/kimi-k2.5", + "family": "kimi", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 256000 + } + }, + "mistral-saba-24b": { + "id": "mistral-saba-24b", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32768, + "output": 32768 + } + }, + "llama-guard-3-8b": { + "id": "llama-guard-3-8b", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "allam-2-7b": { + "id": "allam-2-7b", + "family": "allam", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "output": 4096 + } + }, + "llama3-70b-8192": { + "id": "llama3-70b-8192", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "qwen-qwq-32b": { + "id": "qwen-qwq-32b", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "whisper-large-v3-turbo": { + "id": "whisper-large-v3-turbo", + "family": "whisper", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 448, + "output": 448 + } + }, + "llama3-8b-8192": { + "id": "llama3-8b-8192", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "canopylabs/orpheus-arabic-saudi": { + "id": "canopylabs/orpheus-arabic-saudi", + "family": "canopylabs", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 4000, + "output": 50000 + } + }, + "canopylabs/orpheus-v1-english": { + "id": "canopylabs/orpheus-v1-english", + "family": "canopylabs", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 4000, + "output": 50000 + } + }, + "meta-llama/llama-prompt-guard-2-22m": { + "id": "meta-llama/llama-prompt-guard-2-22m", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 512, + "output": 512 + } + }, + "meta-llama/llama-4-maverick-17b-128e-instruct": { + "id": "meta-llama/llama-4-maverick-17b-128e-instruct", + "family": "llama", "reasoning": false, "temperature": true, "toolCall": true, @@ -40663,12 +40550,1734 @@ }, "limit": { "context": 131072, + "output": 8192 + } + }, + "meta-llama/llama-prompt-guard-2-86m": { + "id": "meta-llama/llama-prompt-guard-2-86m", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 512, + "output": 512 + } + }, + "groq/compound": { + "id": "groq/compound", + "family": "groq", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "groq/compound-mini": { + "id": "groq/compound-mini", + "family": "groq", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "gpt-5.3-chat": { + "id": "gpt-5.3-chat", + "family": "gpt-codex", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 16384 + } + }, + "grok-4-20-non-reasoning": { + "id": "grok-4-20-non-reasoning", + "family": "grok", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 8192 + } + }, + "grok-4-20-reasoning": { + "id": "grok-4-20-reasoning", + "family": "grok", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262000, + "output": 8192 + } + }, + "qwen/qwen3-vl-embedding-8b": { + "id": "Qwen/Qwen3-VL-Embedding-8B", + "family": "qwen", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 4096 + } + }, + "qwen/qwen3-vl-235b-a22b-instruct-fp8": { + "id": "Qwen/Qwen3-VL-235B-A22B-Instruct-FP8", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 218000, + "output": 8192 + } + }, + "neuralmagic/meta-llama-3.1-8b-instruct-fp8": { + "id": "neuralmagic/Meta-Llama-3.1-8B-Instruct-FP8", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "neuralmagic/mistral-nemo-instruct-2407-fp8": { + "id": "neuralmagic/Mistral-Nemo-Instruct-2407-FP8", + "family": "mistral", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "cortecs/llama-3.3-70b-instruct-fp8-dynamic": { + "id": "cortecs/Llama-3.3-70B-Instruct-FP8-Dynamic", + "family": "llama", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "hunyuan-turbos": { + "id": "hunyuan-turbos", + "family": "hunyuan", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "hunyuan-t1": { + "id": "hunyuan-t1", + "family": "hunyuan", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "hunyuan-2.0-instruct": { + "id": "hunyuan-2.0-instruct", + "family": "hunyuan", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "tc-code-latest": { + "id": "tc-code-latest", + "family": "auto", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "hunyuan-2.0-thinking": { + "id": "hunyuan-2.0-thinking", + "family": "hunyuan", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "qwen3-embedding-4b": { + "id": "qwen3-embedding-4b", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 2560 + } + }, + "qwen3-coder-30b-a3b": { + "id": "qwen3-coder-30b-a3b", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "gemini-live-2.5-flash-preview-native-audio": { + "id": "gemini-live-2.5-flash-preview-native-audio", + "family": "gemini-flash", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "audio", + "video" + ], + "output": [ + "text", + "audio" + ] + }, + "limit": { + "context": 131072, + "output": 65536 + } + }, + "gemini-1.5-flash": { + "id": "gemini-1.5-flash", + "family": "gemini-flash", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 8192 + } + }, + "gemini-1.5-pro": { + "id": "gemini-1.5-pro", + "family": "gemini-pro", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 8192 + } + }, + "gemini-2.5-flash-preview-tts": { + "id": "gemini-2.5-flash-preview-tts", + "family": "gemini-flash", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 8000, + "output": 16000 + } + }, + "gemma-4-31b-it": { + "id": "gemma-4-31b-it", + "family": "gemma", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 8192 + } + }, + "gemini-2.5-pro-preview-tts": { + "id": "gemini-2.5-pro-preview-tts", + "family": "gemini-flash", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "audio" + ] + }, + "limit": { + "context": 8000, + "output": 16000 + } + }, + "gemma-4-26b-it": { + "id": "gemma-4-26b-it", + "family": "gemma", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 8192 + } + }, + "gemini-1.5-flash-8b": { + "id": "gemini-1.5-flash-8b", + "family": "gemini-flash", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 8192 + } + }, + "gemini-live-2.5-flash": { + "id": "gemini-live-2.5-flash", + "family": "gemini-flash", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio", + "video" + ], + "output": [ + "text", + "audio" + ] + }, + "limit": { + "context": 128000, + "output": 8000 + } + }, + "public/deepseek-r1": { + "id": "public/deepseek-r1", + "family": "deepseek-thinking", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 32000 + } + }, + "public/minimax-m25": { + "id": "public/minimax-m25", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, "output": 131072 } }, - "opengvlab/internvl3-78b-tee": { - "id": "OpenGVLab/InternVL3-78B-TEE", - "family": "opengvlab", + "public/deepseek-v3": { + "id": "public/deepseek-v3", + "family": "deepseek", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 8192 + } + }, + "baai/bge-reranker-v2-m3": { + "id": "BAAI/bge-reranker-v2-m3", + "family": "bge", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 512, + "output": 512 + } + }, + "intfloat/multilingual-e5-large": { + "id": "intfloat/multilingual-e5-large", + "family": "text-embedding", + "reasoning": false, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 512, + "output": 1024 + } + }, + "ai21-labs/ai21-jamba-1.5-mini": { + "id": "ai21-labs/ai21-jamba-1.5-mini", + "family": "jamba", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 4096 + } + }, + "ai21-labs/ai21-jamba-1.5-large": { + "id": "ai21-labs/ai21-jamba-1.5-large", + "family": "jamba", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 256000, + "output": 4096 + } + }, + "microsoft/phi-3.5-mini-instruct": { + "id": "microsoft/phi-3.5-mini-instruct", + "family": "phi", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "microsoft/phi-3-mini-128k-instruct": { + "id": "microsoft/phi-3-mini-128k-instruct", + "family": "phi", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "microsoft/phi-4-reasoning": { + "id": "microsoft/phi-4-reasoning", + "family": "phi", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "microsoft/phi-3-mini-4k-instruct": { + "id": "microsoft/phi-3-mini-4k-instruct", + "family": "phi", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 4096, + "output": 1024 + } + }, + "microsoft/phi-4-mini-reasoning": { + "id": "microsoft/phi-4-mini-reasoning", + "family": "phi", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "microsoft/mai-ds-r1": { + "id": "microsoft/mai-ds-r1", + "family": "mai", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 65536, + "output": 8192 + } + }, + "cohere/cohere-command-r-08-2024": { + "id": "cohere/cohere-command-r-08-2024", + "family": "command-r", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "cohere/cohere-command-a": { + "id": "cohere/cohere-command-a", + "family": "command-a", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "cohere/cohere-command-r-plus": { + "id": "cohere/cohere-command-r-plus", + "family": "command-r", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "cohere/cohere-command-r": { + "id": "cohere/cohere-command-r", + "family": "command-r", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "cohere/cohere-command-r-plus-08-2024": { + "id": "cohere/cohere-command-r-plus-08-2024", + "family": "command-r", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 4096 + } + }, + "openai/o1-mini": { + "id": "openai/o1-mini", + "family": "o-mini", + "reasoning": true, + "temperature": false, + "toolCall": false, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 65536 + } + }, + "meta/meta-llama-3.1-8b-instruct": { + "id": "meta/meta-llama-3.1-8b-instruct", + "family": "llama", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "meta/meta-llama-3-70b-instruct": { + "id": "meta/meta-llama-3-70b-instruct", + "family": "llama", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 2048 + } + }, + "meta/llama-3.2-90b-vision-instruct": { + "id": "meta/llama-3.2-90b-vision-instruct", + "family": "llama", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "audio" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "meta/meta-llama-3.1-405b-instruct": { + "id": "meta/meta-llama-3.1-405b-instruct", + "family": "llama", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "meta/meta-llama-3.1-70b-instruct": { + "id": "meta/meta-llama-3.1-70b-instruct", + "family": "llama", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "meta/meta-llama-3-8b-instruct": { + "id": "meta/meta-llama-3-8b-instruct", + "family": "llama", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 2048 + } + }, + "meta/llama-4-maverick-17b-128e-instruct-fp8": { + "id": "meta/llama-4-maverick-17b-128e-instruct-fp8", + "family": "llama", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "core42/jais-30b-chat": { + "id": "core42/jais-30b-chat", + "family": "jais", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 8192, + "output": 2048 + } + }, + "mistral-ai/mistral-nemo": { + "id": "mistral-ai/mistral-nemo", + "family": "mistral-nemo", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "mistral-ai/ministral-3b": { + "id": "mistral-ai/ministral-3b", + "family": "ministral", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 8192 + } + }, + "mistral-ai/mistral-large-2411": { + "id": "mistral-ai/mistral-large-2411", + "family": "mistral-large", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "mistral-ai/mistral-small-2503": { + "id": "mistral-ai/mistral-small-2503", + "family": "mistral-small", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "mistral-ai/mistral-medium-2505": { + "id": "mistral-ai/mistral-medium-2505", + "family": "mistral-medium", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 128000, + "output": 32768 + } + }, + "mistral-ai/codestral-2501": { + "id": "mistral-ai/codestral-2501", + "family": "codestral", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 32000, + "output": 8192 + } + }, + "qwen/qwen3-coder-next-fp8": { + "id": "Qwen/Qwen3-Coder-Next-FP8", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "qwen/qwen3-235b-a22b-instruct-2507-tput": { + "id": "Qwen/Qwen3-235B-A22B-Instruct-2507-tput", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "deepseek-ai/deepseek-v3-1": { + "id": "deepseek-ai/DeepSeek-V3-1", + "family": "deepseek", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "claude-3-sonnet-20240229": { + "id": "claude-3-sonnet-20240229", + "family": "claude-sonnet", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 4096 + } + }, + "claude-3-opus-20240229": { + "id": "claude-3-opus-20240229", + "family": "claude-opus", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 4096 + } + }, + "claude-opus-4-0": { + "id": "claude-opus-4-0", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 32000 + } + }, + "claude-3-5-haiku-latest": { + "id": "claude-3-5-haiku-latest", + "family": "claude-haiku", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 8192 + } + }, + "claude-sonnet-4-0": { + "id": "claude-sonnet-4-0", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "zhipuai/glm-4.5": { + "id": "ZhipuAI/GLM-4.5", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 98304 + } + }, + "zhipuai/glm-4.6": { + "id": "ZhipuAI/GLM-4.6", + "family": "glm", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 202752, + "output": 98304 + } + }, + "duo-chat-gpt-5-4-nano": { + "id": "duo-chat-gpt-5-4-nano", + "family": "gpt-nano", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "duo-chat-gpt-5-mini": { + "id": "duo-chat-gpt-5-mini", + "family": "gpt-mini", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "duo-chat-sonnet-4-6": { + "id": "duo-chat-sonnet-4-6", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "duo-chat-gpt-5-2": { + "id": "duo-chat-gpt-5-2", + "family": "gpt", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "duo-chat-gpt-5-codex": { + "id": "duo-chat-gpt-5-codex", + "family": "gpt-codex", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "duo-chat-gpt-5-1": { + "id": "duo-chat-gpt-5-1", + "family": "gpt", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "duo-chat-gpt-5-2-codex": { + "id": "duo-chat-gpt-5-2-codex", + "family": "gpt-codex", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "duo-chat-sonnet-4-5": { + "id": "duo-chat-sonnet-4-5", + "family": "claude-sonnet", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "duo-chat-gpt-5-4": { + "id": "duo-chat-gpt-5-4", + "family": "gpt", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1050000, + "input": 922000, + "output": 128000 + } + }, + "duo-chat-haiku-4-5": { + "id": "duo-chat-haiku-4-5", + "family": "claude-haiku", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "duo-chat-gpt-5-3-codex": { + "id": "duo-chat-gpt-5-3-codex", + "family": "gpt-codex", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "duo-chat-gpt-5-4-mini": { + "id": "duo-chat-gpt-5-4-mini", + "family": "gpt-mini", + "reasoning": true, + "temperature": false, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 400000, + "input": 272000, + "output": 128000 + } + }, + "duo-chat-opus-4-5": { + "id": "duo-chat-opus-4-5", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 200000, + "output": 64000 + } + }, + "duo-chat-opus-4-6": { + "id": "duo-chat-opus-4-6", + "family": "claude-opus", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "arcee_ai/afm/models/trinity-mini": { + "id": "arcee_ai/AFM/models/trinity-mini", + "family": "trinity-mini", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 131072 + } + }, + "mistralai/completion/models/ministral-3-14b-reasoning-2512": { + "id": "mistralai/completion/models/Ministral-3-14B-Reasoning-2512", + "family": "ministral", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "mistralai/completion/models/ministral-3-3b-reasoning-2512": { + "id": "mistralai/completion/models/Ministral-3-3B-Reasoning-2512", + "family": "ministral", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "deepseek-ai/deepseek-ocr/models/deepseek-ocr": { + "id": "deepseek-ai/deepseek-ocr/models/DeepSeek-OCR", + "family": "deepseek", "reasoning": false, "temperature": true, "toolCall": false, @@ -40681,9 +42290,188 @@ "text" ] }, + "limit": { + "context": 8192, + "output": 8192 + } + }, + "openai/chat-completion/models/gpt-oss-20b": { + "id": "openai/chat-completion/models/gpt-oss-20b", + "family": "gpt-oss", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "openai/chat-completion/models/gpt-oss-120b-high-throughput": { + "id": "openai/chat-completion/models/gpt-oss-120b-high-throughput", + "family": "gpt-oss", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 131072, + "output": 16384 + } + }, + "minimaxai/chat-completion/models/minimax-m2_5-high-throughput": { + "id": "minimaxai/chat-completion/models/MiniMax-M2_5-high-throughput", + "family": "minimax", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 204800, + "output": 131072 + } + }, + "qwen/qwencoder/models/qwen3-coder-30b-a3b-instruct": { + "id": "qwen/qwenCoder/models/Qwen3-Coder-30B-A3B-Instruct", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 65536 + } + }, + "qwen/qwenlm/models/qwen3-30b-a3b-thinking-2507": { + "id": "qwen/qwenLM/models/Qwen3-30B-A3B-Thinking-2507", + "family": "qwen", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 131072 + } + }, + "qwen/qwenlm/models/qwen3-30b-a3b-instruct-2507": { + "id": "qwen/qwenLM/models/Qwen3-30B-A3B-Instruct-2507", + "family": "qwen", + "reasoning": false, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 262144, + "output": 262144 + } + }, + "clarifai/main/models/mm-poly-8b": { + "id": "clarifai/main/models/mm-poly-8b", + "family": "mm-poly", + "reasoning": false, + "temperature": true, + "toolCall": false, + "modalities": { + "input": [ + "text", + "image", + "video" + ], + "output": [ + "text" + ] + }, "limit": { "context": 32768, - "output": 32768 + "output": 4096 + } + }, + "nova-2-lite-v1": { + "id": "nova-2-lite-v1", + "family": "nova-lite", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 + } + }, + "nova-2-pro-v1": { + "id": "nova-2-pro-v1", + "family": "nova-pro", + "reasoning": true, + "temperature": true, + "toolCall": true, + "modalities": { + "input": [ + "text", + "image", + "video", + "pdf" + ], + "output": [ + "text" + ] + }, + "limit": { + "context": 1000000, + "output": 64000 } } } diff --git a/src/hooks/AGENTS.md b/src/hooks/AGENTS.md index a0f9f80f9..135338424 100644 --- a/src/hooks/AGENTS.md +++ b/src/hooks/AGENTS.md @@ -1,6 +1,6 @@ # src/hooks/ — 52 Lifecycle Hooks -**Generated:** 2026-04-11 +**Generated:** 2026-04-18 ## OVERVIEW diff --git a/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts b/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts index 4c1ef6330..28dd23415 100644 --- a/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts +++ b/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts @@ -84,7 +84,7 @@ describe("executeCompact lock management", () => { let pluginConfig: ReturnType const sessionID = "test-session-123" const directory = "/test/dir" - const msg = { providerID: "anthropic", modelID: "claude-opus-4-6" } + const msg = { providerID: "anthropic", modelID: "claude-opus-4-7" } beforeEach(() => { // given: Fresh state for each test @@ -132,7 +132,7 @@ describe("executeCompact lock management", () => { expect(mockClient.session.summarize).toHaveBeenCalledWith( expect.objectContaining({ 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.objectContaining({ 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.objectContaining({ path: { id: sessionID }, - body: { providerID: "anthropic", modelID: "claude-opus-4-6", auto: true }, + body: { providerID: "anthropic", modelID: "claude-opus-4-7", auto: true }, }), ) diff --git a/src/hooks/anthropic-effort/hook.ts b/src/hooks/anthropic-effort/hook.ts index 6d4cc965c..a545768d6 100644 --- a/src/hooks/anthropic-effort/hook.ts +++ b/src/hooks/anthropic-effort/hook.ts @@ -1,4 +1,4 @@ -import { log, normalizeModelID } from "../../shared" +import { isProviderUsingOAuth, log, normalizeModelID } from "../../shared" const OPUS_PATTERN = /claude-.*opus/i const EFFORT_UNSUPPORTED_PATTERN = /claude-.*haiku/i @@ -25,6 +25,18 @@ function shouldSkipForInternalAgent(agentName: string | undefined): boolean { return INTERNAL_SKIP_AGENTS.has(agentName.trim().toLowerCase()) } +/** + * Providers that expose constrained APIs rejecting `output_config.effort: "max"` + * (supported values: low | medium | high). Includes: + * - Anthropic OAuth (Claude Pro/Max via third-party clients) + * - GitHub Copilot (proxied Anthropic, doesn't support "max") + */ +function isConstrainedProvider(providerID: string): boolean { + if (providerID === "github-copilot") return true + if (providerID === "anthropic") return isProviderUsingOAuth(providerID) + return false +} + interface ChatParamsInput { sessionID: string agent: { name?: string } @@ -49,8 +61,9 @@ const MAX_VARIANT_BY_TIER: Record = { default: "high", } -function clampVariant(variant: string, isOpus: boolean): string { +function clampVariant(variant: string, isOpus: boolean, isConstrained: boolean): string { if (variant !== "max") return variant + if (isConstrained) return MAX_VARIANT_BY_TIER.default return isOpus ? MAX_VARIANT_BY_TIER.opus : MAX_VARIANT_BY_TIER.default } @@ -65,21 +78,27 @@ export function createAnthropicEffortHook() { if (isEffortUnsupportedModel(model.modelID)) return if (message.variant !== "max") return if (!isClaudeProvider(model.providerID, model.modelID)) return - if (model.providerID === "github-copilot") return if (shouldSkipForInternalAgent(agent?.name)) return if (output.options.effort !== undefined) return const opus = isOpusModel(model.modelID) - const clamped = clampVariant(message.variant, opus) + const constrained = isConstrainedProvider(model.providerID) + const clamped = clampVariant(message.variant, opus, constrained) output.options.effort = clamped - if (!opus) { - // Override the variant so OpenCode doesn't pass "max" to the API + const shouldOverrideMessageVariant = !opus || constrained + + if (shouldOverrideMessageVariant) { + // Override the variant so OpenCode doesn't pass "max" to the API. + // Non-Opus models cap at high; Anthropic OAuth (Claude Pro/Max) also + // caps at high even on Opus because the OAuth API only accepts + // low | medium | high. ;(message as { variant?: string }).variant = clamped - log("anthropic-effort: clamped variant max→high for non-Opus model", { + log("anthropic-effort: clamped variant max→high", { sessionID: input.sessionID, provider: model.providerID, model: model.modelID, + reason: constrained ? "constrained-provider" : "non-opus", }) } else { log("anthropic-effort: injected effort=max", { diff --git a/src/hooks/anthropic-effort/index.test.ts b/src/hooks/anthropic-effort/index.test.ts index cea012eb9..129b6513e 100644 --- a/src/hooks/anthropic-effort/index.test.ts +++ b/src/hooks/anthropic-effort/index.test.ts @@ -1,4 +1,9 @@ -import { describe, expect, it } from "bun:test" +import { afterAll, afterEach, beforeAll, describe, expect, it } from "bun:test" +import { mkdirSync, rmSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import * as path from "node:path" + +import { _resetProviderAuthCacheForTesting } from "../../shared/opencode-provider-auth" import { createAnthropicEffortHook } from "./index" interface ChatParamsInput { @@ -24,7 +29,7 @@ function createMockParams(overrides: { existingOptions?: Record }): { input: ChatParamsInput; output: ChatParamsOutput } { 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 agentName = overrides.agentName ?? "sisyphus" const existingOptions = overrides.existingOptions ?? {} @@ -66,7 +71,7 @@ describe("createAnthropicEffortHook", () => { it("injects effort max for dotted opus ids", async () => { 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) @@ -148,20 +153,36 @@ describe("createAnthropicEffortHook", () => { expect(output.options.effort).toBeUndefined() }) - it("#given github-copilot + claude model #then effort NOT injected", async () => { + it("#given github-copilot + claude opus model #then effort clamped to high (constrained provider)", async () => { // given const hook = createAnthropicEffortHook() const { input, output } = createMockParams({ providerID: "github-copilot", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", + }) + + // when + await hook["chat.params"](input, output) + + // then — github-copilot is a constrained provider, clamps max→high + expect(output.options.effort).toBe("high") + expect(input.message.variant).toBe("high") + }) + + it("#given github-copilot + claude sonnet model #then effort clamped to high", async () => { + // given + const hook = createAnthropicEffortHook() + const { input, output } = createMockParams({ + providerID: "github-copilot", + modelID: "claude-sonnet-4-6", }) // when await hook["chat.params"](input, output) // then - expect(output.options.effort).toBeUndefined() - expect(input.message.variant).toBe("max") + expect(output.options.effort).toBe("high") + expect(input.message.variant).toBe("high") }) describe("#given haiku models (effort unsupported)", () => { @@ -199,4 +220,92 @@ describe("createAnthropicEffortHook", () => { expect(output.options.effort).toBe("high") }) }) + + describe("#given anthropic OAuth auth (Claude Pro/Max) — regression for #3429", () => { + let tempDataDir: string + const originalXdgDataHome = process.env.XDG_DATA_HOME + + function writeAuthFile(providerEntries: Record>): void { + const opencodeDir = path.join(tempDataDir, "opencode") + mkdirSync(opencodeDir, { recursive: true }) + writeFileSync(path.join(opencodeDir, "auth.json"), JSON.stringify(providerEntries), "utf-8") + _resetProviderAuthCacheForTesting() + } + + beforeAll(() => { + tempDataDir = path.join(tmpdir(), `anthropic-effort-oauth-${Date.now()}-${Math.random().toString(36).slice(2)}`) + mkdirSync(tempDataDir, { recursive: true }) + process.env.XDG_DATA_HOME = tempDataDir + }) + + afterAll(() => { + if (originalXdgDataHome === undefined) { + delete process.env.XDG_DATA_HOME + } else { + process.env.XDG_DATA_HOME = originalXdgDataHome + } + rmSync(tempDataDir, { recursive: true, force: true }) + _resetProviderAuthCacheForTesting() + }) + + afterEach(() => { + _resetProviderAuthCacheForTesting() + }) + + it("clamps opus-4-6 + max to high when anthropic provider uses oauth", async () => { + // given an Anthropic OAuth session and variant=max on an Opus model + writeAuthFile({ anthropic: { type: "oauth" } }) + const hook = createAnthropicEffortHook() + const { input, output } = createMockParams({ modelID: "claude-opus-4-7" }) + + // when chat.params fires + await hook["chat.params"](input, output) + + // then effort must be clamped to high so Anthropic's OAuth API accepts it + expect(output.options.effort).toBe("high") + expect(input.message.variant).toBe("high") + }) + + it("clamps dotted opus id + max to high under OAuth", async () => { + // given an Anthropic OAuth session and a dotted opus id + writeAuthFile({ anthropic: { type: "oauth" } }) + const hook = createAnthropicEffortHook() + const { input, output } = createMockParams({ modelID: "claude-opus-4.7" }) + + // when chat.params fires + await hook["chat.params"](input, output) + + // then effort must be clamped to high + expect(output.options.effort).toBe("high") + expect(input.message.variant).toBe("high") + }) + + it("still injects effort=max when anthropic auth is an API key", async () => { + // given an Anthropic API-key session (not OAuth) + writeAuthFile({ anthropic: { type: "api", key: "sk-ant-xxx" } }) + const hook = createAnthropicEffortHook() + const { input, output } = createMockParams({ modelID: "claude-opus-4-7" }) + + // when chat.params fires + await hook["chat.params"](input, output) + + // then API-key users keep the original max behaviour for Opus + expect(output.options.effort).toBe("max") + expect(input.message.variant).toBe("max") + }) + + it("does not clamp when OAuth belongs to a different provider", async () => { + // given OAuth entries for unrelated providers only + writeAuthFile({ "github-copilot": { type: "oauth" }, opencode: { type: "api", key: "sk-x" } }) + const hook = createAnthropicEffortHook() + const { input, output } = createMockParams({ modelID: "claude-opus-4-7", providerID: "anthropic" }) + + // when chat.params fires for the anthropic provider + await hook["chat.params"](input, output) + + // then max stays because anthropic itself is not OAuth + expect(output.options.effort).toBe("max") + expect(input.message.variant).toBe("max") + }) + }) }) diff --git a/src/hooks/atlas/AGENTS.md b/src/hooks/atlas/AGENTS.md index 21e4243fa..215e53861 100644 --- a/src/hooks/atlas/AGENTS.md +++ b/src/hooks/atlas/AGENTS.md @@ -1,6 +1,6 @@ # src/hooks/atlas/ — Master Boulder Orchestrator -**Generated:** 2026-04-11 +**Generated:** 2026-04-18 ## OVERVIEW diff --git a/src/hooks/atlas/compaction-agent-filter.test.ts b/src/hooks/atlas/compaction-agent-filter.test.ts index 790518e6c..b5a120f96 100644 --- a/src/hooks/atlas/compaction-agent-filter.test.ts +++ b/src/hooks/atlas/compaction-agent-filter.test.ts @@ -58,7 +58,7 @@ describe("atlas hook compaction agent filtering", () => { join(messageDir, fileName), JSON.stringify({ agent, - model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7" }, }), ) } diff --git a/src/hooks/atlas/final-wave-approval-gate-regression.test.ts b/src/hooks/atlas/final-wave-approval-gate-regression.test.ts index 180ab7cef..d0ce73c67 100644 --- a/src/hooks/atlas/final-wave-approval-gate-regression.test.ts +++ b/src/hooks/atlas/final-wave-approval-gate-regression.test.ts @@ -91,7 +91,7 @@ describe("Atlas final-wave approval gate regressions", () => { join(messageDirectory, "msg_test001.json"), JSON.stringify({ agent: "atlas", - model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7" }, }), ) } diff --git a/src/hooks/atlas/final-wave-approval-gate.test.ts b/src/hooks/atlas/final-wave-approval-gate.test.ts index 717f66016..608a53235 100644 --- a/src/hooks/atlas/final-wave-approval-gate.test.ts +++ b/src/hooks/atlas/final-wave-approval-gate.test.ts @@ -99,7 +99,7 @@ describe("Atlas final verification approval gate", () => { join(messageDirectory, "msg_test001.json"), JSON.stringify({ agent: "atlas", - model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7" }, }), ) } diff --git a/src/hooks/atlas/index.test.ts b/src/hooks/atlas/index.test.ts index 0f9e7d605..a2e80cf78 100644 --- a/src/hooks/atlas/index.test.ts +++ b/src/hooks/atlas/index.test.ts @@ -79,7 +79,7 @@ describe("atlas hook", () => { } const messageData = { 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)) } @@ -957,7 +957,8 @@ session_id: ses_untrusted_999 const updatedState = readBoulderState(TEST_DIR) expect(updatedState?.task_sessions?.["todo:1"]).toBeUndefined() expect(output.output).not.toContain('task(session_id="ses_untrusted_999"') - expect(output.output).toContain('task(session_id=""') + expect(output.output).not.toContain('task(task_id="ses_untrusted_999"') + expect(output.output).toContain('task(task_id=""') cleanupMessageStorage(sessionID) }) diff --git a/src/hooks/atlas/subagent-session-id.test.ts b/src/hooks/atlas/subagent-session-id.test.ts index b73a0c75c..3b8c68757 100644 --- a/src/hooks/atlas/subagent-session-id.test.ts +++ b/src/hooks/atlas/subagent-session-id.test.ts @@ -90,6 +90,17 @@ describe("extractSessionIdFromMetadata", () => { expect(result).toBe("ses_plugin_abc123") }) + test("extracts legacy session aliases from tool metadata object", () => { + // given + const metadata = { sessionID: "ses_plugin_alias_123" } + + // when + const result = extractSessionIdFromMetadata(metadata) + + // then + expect(result).toBe("ses_plugin_alias_123") + }) + test("returns undefined for metadata without sessionId", () => { // given const metadata = { title: "some task" } diff --git a/src/hooks/atlas/subagent-session-id.ts b/src/hooks/atlas/subagent-session-id.ts index 95aef2aee..cc52deb88 100644 --- a/src/hooks/atlas/subagent-session-id.ts +++ b/src/hooks/atlas/subagent-session-id.ts @@ -1,30 +1,20 @@ import type { PluginInput } from "@opencode-ai/plugin" +import { extractTaskLink } from "../../features/tool-metadata-store" import { log } from "../../shared/logger" import { isSessionInBoulderLineage } from "./boulder-session-lineage" import { HOOK_NAME } from "./hook-name" export function extractSessionIdFromMetadata(metadata: unknown): string | undefined { - if (metadata && typeof metadata === "object" && "sessionId" in metadata) { - const value = (metadata as Record).sessionId - if (typeof value === "string" && value.startsWith("ses_")) { - return value - } + const sessionId = extractTaskLink(metadata, "").sessionId + if (typeof sessionId === "string" && sessionId.startsWith("ses_")) { + return sessionId } + return undefined } export function extractSessionIdFromOutput(output: string): string | undefined { - const taskMetadataBlocks = [...output.matchAll(/([\s\S]*?)<\/task_metadata>/gi)] - const lastTaskMetadataBlock = taskMetadataBlocks.at(-1)?.[1] - if (lastTaskMetadataBlock) { - const taskMetadataSessionMatch = lastTaskMetadataBlock.match(/session_id:\s*(ses_[a-zA-Z0-9_-]+)/i) - if (taskMetadataSessionMatch) { - return taskMetadataSessionMatch[1] - } - } - - const explicitSessionMatches = [...output.matchAll(/Session ID:\s*(ses_[a-zA-Z0-9_-]+)/g)] - return explicitSessionMatches.at(-1)?.[1] + return extractTaskLink(undefined, output).sessionId } export async function validateSubagentSessionId(input: { diff --git a/src/hooks/atlas/verification-reminders.ts b/src/hooks/atlas/verification-reminders.ts index b00618ecd..9bde55b9d 100644 --- a/src/hooks/atlas/verification-reminders.ts +++ b/src/hooks/atlas/verification-reminders.ts @@ -29,7 +29,7 @@ Your completion will NOT be recorded until you complete ALL of the following: If anything fails while closing this out, resume the same session immediately: \`\`\`typescript -task(session_id="${sessionId}", load_skills=[], prompt="fix: checkbox not recorded correctly") +task(task_id="${sessionId}", load_skills=[], prompt="fix: checkbox not recorded correctly") \`\`\` **Your completion is NOT tracked until the checkbox is marked in the plan file.** @@ -47,7 +47,7 @@ ${VERIFICATION_REMINDER} **If ANY verification fails, use this immediately:** \`\`\` -task(session_id="${sessionId}", load_skills=[], prompt="fix: [describe the specific failure]") +task(task_id="${sessionId}", load_skills=[], prompt="fix: [describe the specific failure]") \`\`\` ${buildReuseHint(sessionId)}` diff --git a/src/hooks/auto-slash-command/executor.test.ts b/src/hooks/auto-slash-command/executor.test.ts index 246557275..0fe169cb6 100644 --- a/src/hooks/auto-slash-command/executor.test.ts +++ b/src/hooks/auto-slash-command/executor.test.ts @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test" import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs" import { tmpdir } from "node:os" import { join } from "node:path" +import { clearCommandLoaderCache } from "../../features/claude-code-command-loader" import { executeSlashCommand } from "./executor" const ENV_KEYS = [ @@ -95,6 +96,7 @@ describe("auto-slash command executor plugin dispatch", () => { let envSnapshot: EnvSnapshot beforeEach(() => { + clearCommandLoaderCache() tempDir = mkdtempSync(join(tmpdir(), "omo-executor-plugin-test-")) envSnapshot = { CLAUDE_CONFIG_DIR: process.env.CLAUDE_CONFIG_DIR, @@ -106,6 +108,7 @@ describe("auto-slash command executor plugin dispatch", () => { }) afterEach(() => { + clearCommandLoaderCache() for (const key of ENV_KEYS) { const previousValue = envSnapshot[key] if (previousValue === undefined) { diff --git a/src/hooks/auto-slash-command/index.test.ts b/src/hooks/auto-slash-command/index.test.ts index 543341b0b..cda63bf8c 100644 --- a/src/hooks/auto-slash-command/index.test.ts +++ b/src/hooks/auto-slash-command/index.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it, beforeEach, afterEach, spyOn, mock } from "bun:te import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs" import { tmpdir } from "node:os" import { join } from "node:path" +import { clearCommandLoaderCache } from "../../features/claude-code-command-loader" import type { LoadedSkill } from "../../features/opencode-skill-loader/types" import type { AutoSlashCommandHookInput, @@ -43,6 +44,7 @@ describe("createAutoSlashCommandHook", () => { let createAutoSlashCommandHook: AutoSlashCommandModule["createAutoSlashCommandHook"] beforeEach(async () => { + clearCommandLoaderCache() mock.restore() logCalls = [] spyOn(shared, "log").mockImplementation((message: string, data?: unknown) => { @@ -56,6 +58,7 @@ describe("createAutoSlashCommandHook", () => { }) afterEach(() => { + clearCommandLoaderCache() process.chdir(originalWorkingDirectory) rmSync(tempDir, { recursive: true, force: true }) mock.restore() diff --git a/src/hooks/auto-update-checker/hook.test.ts b/src/hooks/auto-update-checker/hook.test.ts new file mode 100644 index 000000000..a6cacd54d --- /dev/null +++ b/src/hooks/auto-update-checker/hook.test.ts @@ -0,0 +1,261 @@ +import type { PluginInput } from "@opencode-ai/plugin" +import { describe, expect, mock, test } from "bun:test" + +type CreateAutoUpdateCheckerHook = typeof import("./hook").createAutoUpdateCheckerHook +type HookOptions = Parameters[1] +type HookDeps = NonNullable[2]> + +let latestVersionCallCount = 0 +let scheduleDeferredStartupCheckCallCount = 0 + +const flushMicrotasks = async (count: number): Promise => { + for (let index = 0; index < count; index += 1) { + await Promise.resolve() + } +} + +const latestVersionMock = async () => { + latestVersionCallCount += 1 + return "3.0.1" +} + +const scheduleDeferredStartupCheckMock = (runCheck: () => void) => { + scheduleDeferredStartupCheckCallCount += 1 + scheduledCheck = runCheck +} + +let scheduledCheck: (() => void) | null = null + +mock.module("./checker/latest-version", () => ({ + getLatestVersion: latestVersionMock, +})) + +mock.module("./hook/deferred-startup-check", () => ({ + scheduleDeferredStartupCheck: scheduleDeferredStartupCheckMock, +})) + +const createPluginInput = (): PluginInput => ({ + client: {} as PluginInput["client"], + directory: "/tmp/project", + project: {} as PluginInput["project"], + worktree: "/tmp/project", + serverUrl: new URL("https://example.com"), + $: {} as PluginInput["$"], +} satisfies PluginInput) + +const createDeps = (overrides: Partial = {}) => { + const showConfigErrorsIfAny = mock(async () => undefined) + const updateAndShowConnectedProvidersCacheStatus = mock(async () => undefined) + const refreshModelCapabilitiesOnStartup = mock(async () => undefined) + const showModelCacheWarningIfNeeded = mock(async () => undefined) + const showLocalDevToast = mock(async () => undefined) + const showVersionToast = mock(async () => undefined) + const runBackgroundUpdateCheck = mock(async () => { + await latestVersionMock() + }) + + const deps: HookDeps = { + getCachedVersion: () => "3.0.0", + getLocalDevVersion: () => null, + showConfigErrorsIfAny, + updateAndShowConnectedProvidersCacheStatus, + refreshModelCapabilitiesOnStartup, + showModelCacheWarningIfNeeded, + showLocalDevToast, + showVersionToast, + runBackgroundUpdateCheck, + log: () => undefined, + ...overrides, + } + + return { + deps, + mocks: { + showConfigErrorsIfAny, + updateAndShowConnectedProvidersCacheStatus, + refreshModelCapabilitiesOnStartup, + showModelCacheWarningIfNeeded, + showLocalDevToast, + showVersionToast, + runBackgroundUpdateCheck, + }, + } +} + +const createHook = async ( + options: HookOptions = {}, + overrides: Partial = {}, +) => { + const module = await import("./hook") + const { deps, mocks } = createDeps(overrides) + + return { + hook: module.createAutoUpdateCheckerHook( + createPluginInput(), + { + showStartupToast: true, + autoUpdate: false, + ...options, + }, + deps, + ), + mocks, + } +} + +const resetDeferredState = (): void => { + latestVersionCallCount = 0 + scheduleDeferredStartupCheckCallCount = 0 + scheduledCheck = null +} + +const runScheduledCheck = async (): Promise => { + scheduledCheck?.() + await flushMicrotasks(8) +} + +const triggerSessionCreated = ( + hook: ReturnType, + properties?: { info?: { parentID?: string } }, +): void => { + hook.event({ event: { type: "session.created", properties } }) +} + +const triggerSessionIdle = (hook: ReturnType): void => { + hook.event({ event: { type: "session.idle" } }) +} + +describe("auto-update-checker hook", () => { + test("schedules deferred check on session.created without parentID", async () => { + // given + resetDeferredState() + const { hook, mocks } = await createHook() + + // when + triggerSessionCreated(hook) + + // then + expect(scheduleDeferredStartupCheckCallCount).toBe(1) + expect(mocks.showVersionToast).not.toHaveBeenCalled() + expect(mocks.runBackgroundUpdateCheck).not.toHaveBeenCalled() + expect(latestVersionCallCount).toBe(0) + + // when + await runScheduledCheck() + + // then + expect(mocks.showVersionToast).toHaveBeenCalledTimes(1) + expect(mocks.runBackgroundUpdateCheck).toHaveBeenCalledTimes(1) + expect(latestVersionCallCount).toBe(1) + }) + + test("does not schedule deferred check on session.created with parentID", async () => { + // given + resetDeferredState() + const { hook, mocks } = await createHook() + + // when + triggerSessionCreated(hook, { info: { parentID: "parent-123" } }) + + // then + expect(scheduleDeferredStartupCheckCallCount).toBe(0) + expect(mocks.showVersionToast).not.toHaveBeenCalled() + expect(mocks.runBackgroundUpdateCheck).not.toHaveBeenCalled() + }) + + test("does not schedule deferred check on session.idle without session.created", async () => { + // given + resetDeferredState() + const { hook, mocks } = await createHook() + + // when + triggerSessionIdle(hook) + + // then + expect(scheduleDeferredStartupCheckCallCount).toBe(0) + expect(mocks.showVersionToast).not.toHaveBeenCalled() + expect(mocks.runBackgroundUpdateCheck).not.toHaveBeenCalled() + }) + + test("runs all startup checks after deferred session.created check executes", async () => { + // given + resetDeferredState() + const { hook, mocks } = await createHook() + + // when + triggerSessionCreated(hook) + await runScheduledCheck() + + // then + expect(mocks.showConfigErrorsIfAny).toHaveBeenCalledTimes(1) + expect(mocks.updateAndShowConnectedProvidersCacheStatus).toHaveBeenCalledTimes(1) + expect(mocks.refreshModelCapabilitiesOnStartup).toHaveBeenCalledTimes(1) + expect(mocks.showModelCacheWarningIfNeeded).toHaveBeenCalledTimes(1) + expect(mocks.showVersionToast).toHaveBeenCalledTimes(1) + expect(mocks.runBackgroundUpdateCheck).toHaveBeenCalledTimes(1) + }) + + test("guards double execution across repeated session.created events", async () => { + // given + resetDeferredState() + const { hook, mocks } = await createHook() + + // when + triggerSessionCreated(hook) + triggerSessionCreated(hook) + + // then + expect(scheduleDeferredStartupCheckCallCount).toBe(1) + + // when + await runScheduledCheck() + triggerSessionCreated(hook) + + // then + expect(scheduleDeferredStartupCheckCallCount).toBe(1) + expect(mocks.showConfigErrorsIfAny).toHaveBeenCalledTimes(1) + expect(mocks.updateAndShowConnectedProvidersCacheStatus).toHaveBeenCalledTimes(1) + expect(mocks.showModelCacheWarningIfNeeded).toHaveBeenCalledTimes(1) + expect(mocks.showVersionToast).toHaveBeenCalledTimes(1) + expect(mocks.runBackgroundUpdateCheck).toHaveBeenCalledTimes(1) + }) + + test("shows localDevToast when local dev version exists", async () => { + // given + resetDeferredState() + const { hook, mocks } = await createHook({}, { + getLocalDevVersion: () => "3.0.0-dev", + }) + + // when + triggerSessionCreated(hook) + await runScheduledCheck() + + // then + expect(mocks.showConfigErrorsIfAny).toHaveBeenCalledTimes(1) + expect(mocks.updateAndShowConnectedProvidersCacheStatus).toHaveBeenCalledTimes(1) + expect(mocks.showModelCacheWarningIfNeeded).toHaveBeenCalledTimes(1) + expect(mocks.showLocalDevToast).toHaveBeenCalledTimes(1) + expect(mocks.showVersionToast).not.toHaveBeenCalled() + expect(mocks.runBackgroundUpdateCheck).not.toHaveBeenCalled() + expect(latestVersionCallCount).toBe(0) + }) + + test("passes correct toast message with sisyphus enabled", async () => { + // given + resetDeferredState() + const { hook, mocks } = await createHook({ isSisyphusEnabled: true }) + + // when + triggerSessionCreated(hook) + await runScheduledCheck() + + // then + expect(mocks.showVersionToast).toHaveBeenCalledTimes(1) + expect(mocks.showVersionToast).toHaveBeenCalledWith( + expect.anything(), + "3.0.0", + expect.stringContaining("Sisyphus"), + ) + }) +}) diff --git a/src/hooks/auto-update-checker/hook.ts b/src/hooks/auto-update-checker/hook.ts index fbe3998da..2306c03a0 100644 --- a/src/hooks/auto-update-checker/hook.ts +++ b/src/hooks/auto-update-checker/hook.ts @@ -3,6 +3,7 @@ import { log } from "../../shared/logger" import type { AutoUpdateCheckerOptions } from "./types" import { getCachedVersion, getLocalDevVersion } from "./checker" import { runBackgroundUpdateCheck } from "./hook/background-update-check" +import { scheduleDeferredStartupCheck } from "./hook/deferred-startup-check" import { showConfigErrorsIfAny } from "./hook/config-errors-toast" import { updateAndShowConnectedProvidersCacheStatus } from "./hook/connected-providers-status" import { refreshModelCapabilitiesOnStartup } from "./hook/model-capabilities-status" @@ -35,6 +36,20 @@ const defaultDeps: AutoUpdateCheckerDeps = { log, } +const isRecord = (value: unknown): value is Record => { + return typeof value === "object" && value !== null +} + +const getParentID = (properties: unknown): string | undefined => { + if (!isRecord(properties)) return undefined + + const { info } = properties + if (!isRecord(info)) return undefined + + const { parentID } = info + return typeof parentID === "string" && parentID.length > 0 ? parentID : undefined +} + export function createAutoUpdateCheckerHook( ctx: PluginInput, options: AutoUpdateCheckerOptions = {}, @@ -60,44 +75,46 @@ export function createAutoUpdateCheckerHook( } let hasChecked = false + let hasScheduled = false return { event: ({ event }: { event: { type: string; properties?: unknown } }) => { if (event.type !== "session.created") return if (isCliRunMode) return - if (hasChecked) return + if (hasChecked || hasScheduled) return + if (getParentID(event.properties)) return - const props = event.properties as { info?: { parentID?: string } } | undefined - if (props?.info?.parentID) return + hasScheduled = true + scheduleDeferredStartupCheck(() => { hasChecked = true + void (async () => { + const cachedVersion = deps.getCachedVersion() + const localDevVersion = deps.getLocalDevVersion(ctx.directory) + const displayVersion = localDevVersion ?? cachedVersion - setTimeout(async () => { - const cachedVersion = deps.getCachedVersion() - const localDevVersion = deps.getLocalDevVersion(ctx.directory) - const displayVersion = localDevVersion ?? cachedVersion + await deps.showConfigErrorsIfAny(ctx) + await deps.updateAndShowConnectedProvidersCacheStatus(ctx) + await deps.refreshModelCapabilitiesOnStartup(modelCapabilities) + await deps.showModelCacheWarningIfNeeded(ctx) - await deps.showConfigErrorsIfAny(ctx) - await deps.updateAndShowConnectedProvidersCacheStatus(ctx) - await deps.refreshModelCapabilitiesOnStartup(modelCapabilities) - await deps.showModelCacheWarningIfNeeded(ctx) - - if (localDevVersion) { - if (showStartupToast) { - deps.showLocalDevToast(ctx, displayVersion, isSisyphusEnabled).catch(() => {}) + if (localDevVersion) { + if (showStartupToast) { + deps.showLocalDevToast(ctx, displayVersion, isSisyphusEnabled).catch(() => {}) + } + deps.log("[auto-update-checker] Local development mode") + return } - deps.log("[auto-update-checker] Local development mode") - return - } - if (showStartupToast) { - deps.showVersionToast(ctx, displayVersion, getToastMessage(false)).catch(() => {}) - } + if (showStartupToast) { + deps.showVersionToast(ctx, displayVersion, getToastMessage(false)).catch(() => {}) + } - deps.runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage).catch((err) => { - deps.log("[auto-update-checker] Background update check failed:", err) - }) - }, 0) + deps.runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage).catch((err) => { + deps.log("[auto-update-checker] Background update check failed:", err) + }) + })() + }) }, } } diff --git a/src/hooks/auto-update-checker/hook/deferred-startup-check.ts b/src/hooks/auto-update-checker/hook/deferred-startup-check.ts new file mode 100644 index 000000000..2e1066424 --- /dev/null +++ b/src/hooks/auto-update-checker/hook/deferred-startup-check.ts @@ -0,0 +1,4 @@ +export function scheduleDeferredStartupCheck(runCheck: () => void): void { + const timeout = setTimeout(runCheck, 5000) + timeout.unref?.() +} diff --git a/src/hooks/claude-code-hooks/AGENTS.md b/src/hooks/claude-code-hooks/AGENTS.md index 6055c4969..10a357756 100644 --- a/src/hooks/claude-code-hooks/AGENTS.md +++ b/src/hooks/claude-code-hooks/AGENTS.md @@ -1,6 +1,6 @@ # src/hooks/claude-code-hooks/ — Claude Code Compatibility -**Generated:** 2026-04-11 +**Generated:** 2026-04-18 ## OVERVIEW diff --git a/src/hooks/comment-checker/AGENTS.md b/src/hooks/comment-checker/AGENTS.md new file mode 100644 index 000000000..cc58f96dd --- /dev/null +++ b/src/hooks/comment-checker/AGENTS.md @@ -0,0 +1,62 @@ +# src/hooks/comment-checker/ — AI Slop Comment Blocker + +**Generated:** 2026-04-18 + +## OVERVIEW + +Tool Guard tier hook. Runs after `write`/`edit` tools to detect AI-generated comment patterns in code and block them before they land. Backed by `@code-yeongyu/comment-checker` binary (trusted dependency). + +## WHAT IT BLOCKS + +AI slop comment smells: +- Restating what code literally does (`// increment counter`) +- Filler phrases (`// obviously`, `// clearly`, `// simply`) +- Decorative separators without purpose +- JSDoc on trivially-named functions +- `// TODO:` without context +- Comments contradicting surrounding code + +See `@code-yeongyu/comment-checker` for the authoritative blocklist. + +## EXECUTION FLOW + +``` +tool.execute.after (write | edit | hashline edit) + → extract changed lines from tool output + → spawn comment-checker binary with changed file path + → parse findings (line ranges + violation category) + → if findings → inject tool-level error → agent must fix +``` + +## KEY FILES + +| File | Purpose | +|------|---------| +| `hook.ts` | `createCommentCheckerHook()` — main factory, tool.execute.after handler | +| `comment-checker-runner.ts` | Spawn binary, parse JSON output | +| `changed-line-extractor.ts` | Extract which lines changed from tool result | +| `findings-formatter.ts` | Format violations as actionable error message | +| `binary-resolver.ts` | Locate `comment-checker` binary (node_modules + PATH) | + +## CONFIG + +```jsonc +// oh-my-opencode.jsonc +{ + "comment_checker": { + "enabled": true, // default: true + "severity": "error" // error blocks, warning notifies only + } +} +``` + +Disable via `"disabled_hooks": ["comment-checker"]`. + +## BYPASS FOR LEGITIMATE COMMENTS + +Prefix with `// @allow` or mark file scope with `// comment-checker-disable-file` at top. Use sparingly — defeating the purpose. + +## RELATED + +- Doctor check: `src/cli/doctor/checks/tools.ts` verifies `comment-checker` binary availability +- Postinstall: `postinstall.mjs` downloads binary if missing diff --git a/src/hooks/comment-checker/hook.lazy-init.test.ts b/src/hooks/comment-checker/hook.lazy-init.test.ts new file mode 100644 index 000000000..2598aa3c6 --- /dev/null +++ b/src/hooks/comment-checker/hook.lazy-init.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it, mock, afterAll } from "bun:test" + +const startPendingCallCleanup = mock(() => {}) +const initializeCommentCheckerCli = mock(() => {}) + +mock.module("./cli-runner", () => ({ + initializeCommentCheckerCli, + getCommentCheckerCliPathPromise: () => Promise.resolve("/tmp/fake-comment-checker"), + isCliPathUsable: () => true, + processWithCli: async () => {}, + processApplyPatchEditsWithCli: async () => {}, +})) + +mock.module("./pending-calls", () => ({ + registerPendingCall: () => {}, + startPendingCallCleanup, + stopPendingCallCleanup: () => {}, + takePendingCall: () => undefined, +})) + +afterAll(() => { + mock.restore() +}) + +const { createCommentCheckerHooks } = await import("./hook") + +describe("comment-checker lazy initialization", () => { + it("initializes CLI and cleanup on first tool hook call only", async () => { + // given + const hooks = createCommentCheckerHooks() + const beforeHook = hooks["tool.execute.before"] + const input = { tool: "write", sessionID: "ses_test", callID: "call_test" } + const output = { args: { filePath: "src/a.ts" } } + + // when + expect(startPendingCallCleanup).toHaveBeenCalledTimes(0) + expect(initializeCommentCheckerCli).toHaveBeenCalledTimes(0) + + // then + await beforeHook(input, output) + expect(startPendingCallCleanup).toHaveBeenCalledTimes(1) + expect(initializeCommentCheckerCli).toHaveBeenCalledTimes(1) + + // when + await beforeHook(input, output) + + // then + expect(startPendingCallCleanup).toHaveBeenCalledTimes(1) + expect(initializeCommentCheckerCli).toHaveBeenCalledTimes(1) + }) +}) diff --git a/src/hooks/comment-checker/hook.ts b/src/hooks/comment-checker/hook.ts index 56632b1f9..089aca2e9 100644 --- a/src/hooks/comment-checker/hook.ts +++ b/src/hooks/comment-checker/hook.ts @@ -28,6 +28,7 @@ import { stopPendingCallCleanup, takePendingCall, } from "./pending-calls" +import { ensureCommentCheckerInitialization } from "./initialization-gate" import * as fs from "fs" import { tmpdir } from "os" @@ -48,14 +49,16 @@ function debugLog(...args: unknown[]) { export function createCommentCheckerHooks(config?: CommentCheckerConfig) { debugLog("createCommentCheckerHooks called", { config }) - startPendingCallCleanup() - initializeCommentCheckerCli(debugLog) - return { "tool.execute.before": async ( input: { tool: string; sessionID: string; callID: string }, output: { args: Record }, ): Promise => { + ensureCommentCheckerInitialization(() => { + startPendingCallCleanup() + initializeCommentCheckerCli(debugLog) + }) + debugLog("tool.execute.before:", { tool: input.tool, callID: input.callID, diff --git a/src/hooks/comment-checker/initialization-gate.ts b/src/hooks/comment-checker/initialization-gate.ts new file mode 100644 index 000000000..da9759a47 --- /dev/null +++ b/src/hooks/comment-checker/initialization-gate.ts @@ -0,0 +1,7 @@ +let initialized = false + +export function ensureCommentCheckerInitialization(initializer: () => void): void { + if (initialized) return + initialized = true + initializer() +} diff --git a/src/hooks/compaction-context-injector/recovery-prompt-config.ts b/src/hooks/compaction-context-injector/recovery-prompt-config.ts index f0f8480c8..64c8dcfab 100644 --- a/src/hooks/compaction-context-injector/recovery-prompt-config.ts +++ b/src/hooks/compaction-context-injector/recovery-prompt-config.ts @@ -1,3 +1,4 @@ +import { stripInvisibleAgentCharacters } from "../../shared/agent-display-names" import type { CompactionAgentConfigCheckpoint } from "../../shared/compaction-agent-config-checkpoint" export type RecoveryPromptConfig = CompactionAgentConfigCheckpoint & { @@ -66,7 +67,7 @@ export function isPromptConfigRecovered( const agentMatches = typeof actualAgent === "string" && !isCompactionAgent(actualAgent) && - actualAgent.toLowerCase() === expectedPromptConfig.agent.toLowerCase() + stripInvisibleAgentCharacters(actualAgent).toLowerCase() === stripInvisibleAgentCharacters(expectedPromptConfig.agent).toLowerCase() return ( agentMatches && diff --git a/src/hooks/directory-agents-injector/finder.ts b/src/hooks/directory-agents-injector/finder.ts index 8ac8a1463..e04cfab74 100644 --- a/src/hooks/directory-agents-injector/finder.ts +++ b/src/hooks/directory-agents-injector/finder.ts @@ -1,4 +1,4 @@ -import { existsSync } from "node:fs"; +import { constants, promises as fsPromises } from "node:fs"; import { dirname, isAbsolute, join, resolve } from "node:path"; import { AGENTS_FILENAME } from "./constants"; @@ -9,10 +9,10 @@ export function resolveFilePath(rootDirectory: string, path: string): string | n return resolve(rootDirectory, path); } -export function findAgentsMdUp(input: { +export async function findAgentsMdUp(input: { startDir: string; rootDir: string; -}): string[] { +}): Promise { const found: string[] = []; let current = input.startDir; @@ -22,7 +22,11 @@ export function findAgentsMdUp(input: { const isRootDir = current === input.rootDir; if (!isRootDir) { const agentsPath = join(current, AGENTS_FILENAME); - if (existsSync(agentsPath)) { + const exists = await fsPromises + .access(agentsPath, constants.F_OK) + .then(() => true) + .catch(() => false); + if (exists) { found.push(agentsPath); } } diff --git a/src/hooks/directory-agents-injector/injector.test.ts b/src/hooks/directory-agents-injector/injector.test.ts index 8f5701645..822381a69 100644 --- a/src/hooks/directory-agents-injector/injector.test.ts +++ b/src/hooks/directory-agents-injector/injector.test.ts @@ -84,6 +84,23 @@ describe("processFilePathForAgentsInjection", () => { expect(output.output).toContain(srcAgentsContent) }) + it("finds AGENTS.md files while walking up directories", async () => { + // given + const { findAgentsMdUp } = await import("./finder") + + // when + const agentsPaths = await findAgentsMdUp({ + startDir: componentsDirectory, + rootDir: testRoot, + }) + + // then + expect(agentsPaths).toEqual([ + join(srcDirectory, "AGENTS.md"), + join(componentsDirectory, "AGENTS.md"), + ]) + }) + it("skips root-level AGENTS.md", async () => { // given rmSync(join(srcDirectory, "AGENTS.md"), { force: true }) diff --git a/src/hooks/directory-agents-injector/injector.ts b/src/hooks/directory-agents-injector/injector.ts index 28d0be943..3ff40784d 100644 --- a/src/hooks/directory-agents-injector/injector.ts +++ b/src/hooks/directory-agents-injector/injector.ts @@ -1,5 +1,5 @@ import type { PluginInput } from "@opencode-ai/plugin"; -import { readFileSync } from "node:fs"; +import { promises as fsPromises } from "node:fs"; import { dirname } from "node:path"; import type { createDynamicTruncator } from "../../shared/dynamic-truncator"; @@ -31,7 +31,7 @@ export async function processFilePathForAgentsInjection(input: { const dir = dirname(resolved); const cache = getSessionCache(input.sessionCaches, input.sessionID); - const agentsPaths = findAgentsMdUp({ startDir: dir, rootDir: input.ctx.directory }); + const agentsPaths = await findAgentsMdUp({ startDir: dir, rootDir: input.ctx.directory }); let dirty = false; for (const agentsPath of agentsPaths) { @@ -39,7 +39,8 @@ export async function processFilePathForAgentsInjection(input: { if (cache.has(agentsDir)) continue; try { - const content = readFileSync(agentsPath, "utf-8"); + const content = await fsPromises.readFile(agentsPath, "utf-8"); + cache.add(agentsDir); const { result, truncated } = await input.truncator.truncate( input.sessionID, content, @@ -48,7 +49,6 @@ export async function processFilePathForAgentsInjection(input: { ? `\n\n[Note: Content was truncated to save context window space. For full context, please read the file directly: ${agentsPath}]` : ""; input.output.output += `\n\n[Directory Context: ${agentsPath}]\n${result}${truncationNotice}`; - cache.add(agentsDir); dirty = true; } catch {} } diff --git a/src/hooks/directory-readme-injector/finder.ts b/src/hooks/directory-readme-injector/finder.ts index 70e0ba04d..904ef000c 100644 --- a/src/hooks/directory-readme-injector/finder.ts +++ b/src/hooks/directory-readme-injector/finder.ts @@ -1,4 +1,4 @@ -import { existsSync } from "node:fs"; +import { access } from "node:fs/promises"; import { dirname, isAbsolute, join, resolve } from "node:path"; import { README_FILENAME } from "./constants"; @@ -9,17 +9,19 @@ export function resolveFilePath(rootDirectory: string, path: string): string | n return resolve(rootDirectory, path); } -export function findReadmeMdUp(input: { +export async function findReadmeMdUp(input: { startDir: string; rootDir: string; -}): string[] { +}): Promise { const found: string[] = []; let current = input.startDir; while (true) { const readmePath = join(current, README_FILENAME); - if (existsSync(readmePath)) { + try { + await access(readmePath); found.push(readmePath); + } catch { } if (current === input.rootDir) break; diff --git a/src/hooks/directory-readme-injector/injector.test.ts b/src/hooks/directory-readme-injector/injector.test.ts index 74294fd7c..c8e704121 100644 --- a/src/hooks/directory-readme-injector/injector.test.ts +++ b/src/hooks/directory-readme-injector/injector.test.ts @@ -133,6 +133,32 @@ describe("processFilePathForReadmeInjection", () => { expect(output.output).toContain("# Components README") }) + it("returns a promise and finds README.md files from temp fixtures", async () => { + // given + const sourceDirectory = join(testRoot, "src") + const componentsDirectory = join(sourceDirectory, "components") + mkdirSync(componentsDirectory, { recursive: true }) + writeFileSync(join(testRoot, "README.md"), "# Root README") + writeFileSync(join(sourceDirectory, "README.md"), "# Src README") + writeFileSync(join(componentsDirectory, "README.md"), "# Components README") + + const { findReadmeMdUp } = await import("./finder") + + // when + const promise = findReadmeMdUp({ + startDir: componentsDirectory, + rootDir: testRoot, + }) + + // then + expect(promise).toBeInstanceOf(Promise) + await expect(promise).resolves.toEqual([ + join(testRoot, "README.md"), + join(sourceDirectory, "README.md"), + join(componentsDirectory, "README.md"), + ]) + }) + it("does not re-inject already cached directories", async () => { // given const sourceDirectory = join(testRoot, "src") diff --git a/src/hooks/directory-readme-injector/injector.ts b/src/hooks/directory-readme-injector/injector.ts index bfeae7d44..ce3ff7212 100644 --- a/src/hooks/directory-readme-injector/injector.ts +++ b/src/hooks/directory-readme-injector/injector.ts @@ -1,5 +1,5 @@ import type { PluginInput } from "@opencode-ai/plugin"; -import { readFileSync } from "node:fs"; +import { readFile } from "node:fs/promises"; import { dirname } from "node:path"; import type { createDynamicTruncator } from "../../shared/dynamic-truncator"; @@ -31,7 +31,7 @@ export async function processFilePathForReadmeInjection(input: { const dir = dirname(resolved); const cache = getSessionCache(input.sessionCaches, input.sessionID); - const readmePaths = findReadmeMdUp({ startDir: dir, rootDir: input.ctx.directory }); + const readmePaths = await findReadmeMdUp({ startDir: dir, rootDir: input.ctx.directory }); let dirty = false; for (const readmePath of readmePaths) { @@ -39,7 +39,7 @@ export async function processFilePathForReadmeInjection(input: { if (cache.has(readmeDir)) continue; try { - const content = readFileSync(readmePath, "utf-8"); + const content = await readFile(readmePath, "utf-8"); const { result, truncated } = await input.truncator.truncate( input.sessionID, content, diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 051cbd12a..8fd15af2f 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -14,7 +14,13 @@ export { createEmptyTaskResponseDetectorHook } from "./empty-task-response-detec export { createAnthropicContextWindowLimitRecoveryHook, type AnthropicContextWindowLimitRecoveryOptions } from "./anthropic-context-window-limit-recovery"; export { createThinkModeHook } from "./think-mode"; -export { createModelFallbackHook, setPendingModelFallback, clearPendingModelFallback, type ModelFallbackState } from "./model-fallback/hook"; +export { + createModelFallbackHook, + setPendingModelFallback, + clearPendingModelFallback, + type ModelFallbackHook, + type ModelFallbackState, +} from "./model-fallback/hook"; export { createClaudeCodeHooksHook } from "./claude-code-hooks"; export { createRulesInjectorHook } from "./rules-injector"; export { createBackgroundNotificationHook } from "./background-notification" diff --git a/src/hooks/keyword-detector/ultrawork/default.ts b/src/hooks/keyword-detector/ultrawork/default.ts index 639ae6b61..0c95c5f1e 100644 --- a/src/hooks/keyword-detector/ultrawork/default.ts +++ b/src/hooks/keyword-detector/ultrawork/default.ts @@ -104,7 +104,7 @@ TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST. | Architecture decision needed | MUST call plan agent | \`\`\` -task(subagent_type="plan", load_skills=[], prompt="") +task(subagent_type="plan", load_skills=[], run_in_background=false, prompt="") \`\`\` **WHY PLAN AGENT IS MANDATORY:** @@ -115,15 +115,15 @@ task(subagent_type="plan", load_skills=[], prompt=" - Prefer tools over internal knowledge for fresh or user-specific data diff --git a/src/hooks/keyword-detector/ultrawork/planner.ts b/src/hooks/keyword-detector/ultrawork/planner.ts index ee29771c3..c6cec77c6 100644 --- a/src/hooks/keyword-detector/ultrawork/planner.ts +++ b/src/hooks/keyword-detector/ultrawork/planner.ts @@ -117,7 +117,7 @@ Each TODO item MUST include: | Wave | Tasks | Dispatch Command | |------|-------|------------------| -| 1 | 1, 4 | \`task(category="...", load_skills=[...], run_in_background=false)\` × 2 | +| 1 | 1, 4 | \`task(category="...", load_skills=[...], run_in_background=true)\` × 2 | | 2 | 2, 3, 5 | \`task(...)\` × 3 after Wave 1 completes | | 3 | 6 | \`task(...)\` final integration | diff --git a/src/hooks/model-fallback/controller-accessor.ts b/src/hooks/model-fallback/controller-accessor.ts new file mode 100644 index 000000000..281ae9931 --- /dev/null +++ b/src/hooks/model-fallback/controller-accessor.ts @@ -0,0 +1,30 @@ +import type { FallbackEntry } from "../../shared/model-requirements" +import type { ModelFallbackStateController } from "./fallback-state-controller" + +export type ModelFallbackControllerAccessor = { + register: (controller: ModelFallbackStateController) => void + setSessionFallbackChain: (sessionID: string, fallbackChain: FallbackEntry[] | undefined) => void + clearSessionFallbackChain: (sessionID: string) => void +} + +export function createModelFallbackControllerAccessor(): ModelFallbackControllerAccessor { + let controller: ModelFallbackStateController | null = null + + function register(nextController: ModelFallbackStateController): void { + controller = nextController + } + + function setSessionFallbackChain(sessionID: string, fallbackChain: FallbackEntry[] | undefined): void { + controller?.setSessionFallbackChain(sessionID, fallbackChain) + } + + function clearSessionFallbackChain(sessionID: string): void { + controller?.clearSessionFallbackChain(sessionID) + } + + return { + register, + setSessionFallbackChain, + clearSessionFallbackChain, + } +} diff --git a/src/hooks/model-fallback/fallback-state-controller.ts b/src/hooks/model-fallback/fallback-state-controller.ts new file mode 100644 index 000000000..4230bfb0a --- /dev/null +++ b/src/hooks/model-fallback/fallback-state-controller.ts @@ -0,0 +1,134 @@ +import type { FallbackEntry } from "../../shared/model-requirements" +import { getAgentConfigKey } from "../../shared/agent-display-names" +import { AGENT_MODEL_REQUIREMENTS } from "../../shared/model-requirements" +import { log } from "../../shared/logger" +import { getNextReachableFallback } from "./next-fallback" + +type ModelFallbackStateLike = { + providerID: string + modelID: string + fallbackChain: FallbackEntry[] + attemptCount: number + pending: boolean +} + +export type ModelFallbackStateController = { + lastToastKey: Map + setSessionFallbackChain: (sessionID: string, fallbackChain: FallbackEntry[] | undefined) => void + clearSessionFallbackChain: (sessionID: string) => void + setPendingModelFallback: ( + sessionID: string, + agentName: string, + currentProviderID: string, + currentModelID: string, + ) => boolean + getNextFallback: (sessionID: string) => ReturnType + clearPendingModelFallback: (sessionID: string) => void + hasPendingModelFallback: (sessionID: string) => boolean + getFallbackState: (sessionID: string) => ModelFallbackStateLike | undefined + reset: () => void +} + +export function createModelFallbackStateController(input: { + pendingModelFallbacks: Map + lastToastKey: Map + sessionFallbackChains: Map +}): ModelFallbackStateController { + const { pendingModelFallbacks, lastToastKey, sessionFallbackChains } = input + + function setSessionFallbackChain(sessionID: string, fallbackChain: FallbackEntry[] | undefined): void { + if (!sessionID) return + sessionFallbackChains.set(sessionID, fallbackChain?.length ? fallbackChain : []) + } + + function clearSessionFallbackChain(sessionID: string): void { + sessionFallbackChains.delete(sessionID) + } + + function setPendingModelFallback( + sessionID: string, + agentName: string, + currentProviderID: string, + currentModelID: string, + ): boolean { + const agentKey = getAgentConfigKey(agentName) + const requirements = AGENT_MODEL_REQUIREMENTS[agentKey] + const fallbackChain = sessionFallbackChains.get(sessionID) ?? requirements?.fallbackChain + + if (!fallbackChain?.length) { + log("[model-fallback] No fallback chain for agent: " + agentName + " (key: " + agentKey + ")") + return false + } + + const existing = pendingModelFallbacks.get(sessionID) + if (!existing) { + pendingModelFallbacks.set(sessionID, { + providerID: currentProviderID, + modelID: currentModelID, + fallbackChain, + attemptCount: 0, + pending: true, + }) + log("[model-fallback] Set pending fallback for session: " + sessionID + ", agent: " + agentName) + return true + } + + if (existing.pending) { + log("[model-fallback] Pending fallback already armed for session: " + sessionID) + return false + } + + existing.providerID = currentProviderID + existing.modelID = currentModelID + existing.pending = true + if (existing.attemptCount >= existing.fallbackChain.length) { + log("[model-fallback] Fallback chain exhausted for session: " + sessionID) + return false + } + log("[model-fallback] Re-armed pending fallback for session: " + sessionID) + return true + } + + function getNextFallback(sessionID: string): ReturnType { + const state = pendingModelFallbacks.get(sessionID) + if (!state?.pending) return null + + const fallback = getNextReachableFallback(sessionID, state) + if (fallback) return fallback + + log("[model-fallback] No more fallbacks for session: " + sessionID) + pendingModelFallbacks.delete(sessionID) + return null + } + + function clearPendingModelFallback(sessionID: string): void { + pendingModelFallbacks.delete(sessionID) + lastToastKey.delete(sessionID) + } + + function hasPendingModelFallback(sessionID: string): boolean { + return pendingModelFallbacks.get(sessionID)?.pending === true + } + + function getFallbackState(sessionID: string): ModelFallbackStateLike | undefined { + return pendingModelFallbacks.get(sessionID) + } + + function reset(): void { + pendingModelFallbacks.clear() + lastToastKey.clear() + sessionFallbackChains.clear() + } + + return { + lastToastKey, + setSessionFallbackChain, + clearSessionFallbackChain, + setPendingModelFallback, + getNextFallback, + clearPendingModelFallback, + hasPendingModelFallback, + getFallbackState, + reset, + } +} diff --git a/src/hooks/model-fallback/hook.test.ts b/src/hooks/model-fallback/hook.test.ts index 637503356..de9e66fd7 100644 --- a/src/hooks/model-fallback/hook.test.ts +++ b/src/hooks/model-fallback/hook.test.ts @@ -24,7 +24,7 @@ const selectFallbackProviderMock = mock((providers: string[], preferredProviderI const transformModelForProviderMock = mock((provider: string, model: string) => { if (provider === "github-copilot") { 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-5", "claude-sonnet-4.5") .replace("claude-haiku-4-5", "claude-haiku-4.5") @@ -70,22 +70,23 @@ const { setPendingModelFallback, } = await importFreshModelFallbackHookModule() +type ModelFallbackHook = ReturnType + describe("model fallback hook", () => { + let modelFallback: ModelFallbackHook + beforeEach(() => { + modelFallback = createModelFallbackHook() readConnectedProvidersCacheMock.mockReturnValue(null) readProviderModelsCacheMock.mockReturnValue(null) readConnectedProvidersCacheMock.mockClear() readProviderModelsCacheMock.mockClear() selectFallbackProviderMock.mockClear() - - clearPendingModelFallback("ses_model_fallback_main") - clearPendingModelFallback("ses_model_fallback_ghcp") - clearPendingModelFallback("ses_model_fallback_google") }) test("applies pending fallback on chat.message by overriding model", async () => { //#given - const hook = createModelFallbackHook() as unknown as { + const hook = modelFallback as unknown as { "chat.message"?: ( input: { sessionID: string }, output: { message: Record; parts: Array<{ type: string; text?: string }> }, @@ -93,16 +94,17 @@ describe("model fallback hook", () => { } const set = setPendingModelFallback( + modelFallback, "ses_model_fallback_main", "Sisyphus - Ultraworker", "anthropic", - "claude-opus-4-6-thinking", + "claude-opus-4-7-thinking", ) expect(set).toBe(true) const output = { message: { - model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" }, variant: "max", }, parts: [{ type: "text", text: "continue" }], @@ -117,13 +119,13 @@ describe("model fallback hook", () => { //#then expect(output.message["model"]).toEqual({ providerID: "anthropic", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", }) }) test("preserves fallback progression across repeated session.error retries", async () => { //#given - const hook = createModelFallbackHook() as unknown as { + const hook = modelFallback as unknown as { "chat.message"?: ( input: { sessionID: string }, output: { message: Record; parts: Array<{ type: string; text?: string }> }, @@ -132,12 +134,12 @@ describe("model fallback hook", () => { const sessionID = "ses_model_fallback_main" expect( - setPendingModelFallback(sessionID, "Sisyphus - Ultraworker", "anthropic", "claude-opus-4-6-thinking"), + setPendingModelFallback(modelFallback, sessionID, "Sisyphus - Ultraworker", "anthropic", "claude-opus-4-7-thinking"), ).toBe(true) const firstOutput = { message: { - model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" }, variant: "max", }, parts: [{ type: "text", text: "continue" }], @@ -149,17 +151,17 @@ describe("model fallback hook", () => { //#then expect(firstOutput.message["model"]).toEqual({ providerID: "anthropic", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", }) //#when - second error re-arms fallback and should advance to next entry expect( - setPendingModelFallback(sessionID, "Sisyphus - Ultraworker", "anthropic", "claude-opus-4-6"), + setPendingModelFallback(modelFallback, sessionID, "Sisyphus - Ultraworker", "anthropic", "claude-opus-4-7"), ).toBe(true) const secondOutput = { message: { - model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7" }, }, parts: [{ type: "text", text: "continue" }], } @@ -176,57 +178,60 @@ describe("model fallback hook", () => { test("does not re-arm fallback when one is already pending", () => { //#given const sessionID = "ses_model_fallback_pending_guard" - clearPendingModelFallback(sessionID) + clearPendingModelFallback(modelFallback, sessionID) //#when const firstSet = setPendingModelFallback( + modelFallback, sessionID, "Sisyphus - Ultraworker", "anthropic", - "claude-opus-4-6-thinking", + "claude-opus-4-7-thinking", ) const secondSet = setPendingModelFallback( + modelFallback, sessionID, "Sisyphus - Ultraworker", "anthropic", - "claude-opus-4-6-thinking", + "claude-opus-4-7-thinking", ) //#then expect(firstSet).toBe(true) expect(secondSet).toBe(false) - clearPendingModelFallback(sessionID) + clearPendingModelFallback(modelFallback, sessionID) }) test("skips no-op fallback entries that resolve to same provider/model", async () => { //#given const sessionID = "ses_model_fallback_noop_skip" - clearPendingModelFallback(sessionID) + clearPendingModelFallback(modelFallback, sessionID) - const hook = createModelFallbackHook() as unknown as { + const hook = modelFallback as unknown as { "chat.message"?: ( input: { sessionID: string }, output: { message: Record; parts: Array<{ type: string; text?: string }> }, ) => Promise } - setSessionFallbackChain(sessionID, [ - { providers: ["anthropic"], model: "claude-opus-4-6" }, + setSessionFallbackChain(modelFallback, sessionID, [ + { providers: ["anthropic"], model: "claude-opus-4-7" }, { providers: ["opencode"], model: "kimi-k2.5-free" }, ]) expect( setPendingModelFallback( + modelFallback, sessionID, "Sisyphus - Ultraworker", "anthropic", - "claude-opus-4-6", + "claude-opus-4-7", ), ).toBe(true) const output = { message: { - model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7" }, }, parts: [{ type: "text", text: "continue" }], } @@ -239,38 +244,39 @@ describe("model fallback hook", () => { providerID: "opencode", modelID: "kimi-k2.5-free", }) - clearPendingModelFallback(sessionID) + clearPendingModelFallback(modelFallback, sessionID) }) test("skips no-op fallback entries even when variant differs", async () => { //#given const sessionID = "ses_model_fallback_noop_variant_skip" - clearPendingModelFallback(sessionID) + clearPendingModelFallback(modelFallback, sessionID) - const hook = createModelFallbackHook() as unknown as { + const hook = modelFallback as unknown as { "chat.message"?: ( input: { sessionID: string }, output: { message: Record; parts: Array<{ type: string; text?: string }> }, ) => Promise } - setSessionFallbackChain(sessionID, [ - { providers: ["quotio"], model: "claude-opus-4-6", variant: "max" }, + setSessionFallbackChain(modelFallback, sessionID, [ + { providers: ["quotio"], model: "claude-opus-4-7", variant: "max" }, { providers: ["quotio"], model: "gpt-5.2" }, ]) expect( setPendingModelFallback( + modelFallback, sessionID, "Sisyphus - Ultraworker", "quotio", - "claude-opus-4-6", + "claude-opus-4-7", ), ).toBe(true) const output = { message: { - model: { providerID: "quotio", modelID: "claude-opus-4-6" }, + model: { providerID: "quotio", modelID: "claude-opus-4-7" }, variant: "max", }, parts: [{ type: "text", text: "continue" }], @@ -285,28 +291,29 @@ describe("model fallback hook", () => { modelID: "gpt-5.2", }) expect(output.message["variant"]).toBeUndefined() - clearPendingModelFallback(sessionID) + clearPendingModelFallback(modelFallback, sessionID) }) test("uses connected preferred provider when fallback entry providers are disconnected", async () => { //#given const sessionID = "ses_model_fallback_preferred_provider" - clearPendingModelFallback(sessionID) + clearPendingModelFallback(modelFallback, sessionID) readConnectedProvidersCacheMock.mockReturnValue(["provider-x"]) - const hook = createModelFallbackHook() as unknown as { + const hook = modelFallback as unknown as { "chat.message"?: ( input: { sessionID: string }, output: { message: Record; parts: Array<{ type: string; text?: string }> }, ) => Promise } - setSessionFallbackChain(sessionID, [ + setSessionFallbackChain(modelFallback, sessionID, [ { providers: ["provider-y"], model: "fallback-model" }, ]) expect( setPendingModelFallback( + modelFallback, sessionID, "Sisyphus - Ultraworker", "provider-x", @@ -329,17 +336,18 @@ describe("model fallback hook", () => { providerID: "provider-x", modelID: "fallback-model", }) - clearPendingModelFallback(sessionID) + clearPendingModelFallback(modelFallback, sessionID) }) test("does not fall back to hardcoded agent chain when session explicitly stores no fallback chain [regression #2941]", () => { //#given const sessionID = "ses_model_fallback_explicit_none" - clearPendingModelFallback(sessionID) - setSessionFallbackChain(sessionID, undefined) + clearPendingModelFallback(modelFallback, sessionID) + setSessionFallbackChain(modelFallback, sessionID, undefined) //#when const set = setPendingModelFallback( + modelFallback, sessionID, "Sisyphus - Junior", "anthropic", @@ -348,7 +356,7 @@ describe("model fallback hook", () => { //#then expect(set).toBe(false) - clearPendingModelFallback(sessionID) + clearPendingModelFallback(modelFallback, sessionID) }) test("shows toast when fallback is applied", async () => { @@ -366,16 +374,17 @@ describe("model fallback hook", () => { } const set = setPendingModelFallback( + hook, "ses_model_fallback_toast", "Sisyphus - Ultraworker", "anthropic", - "claude-opus-4-6-thinking", + "claude-opus-4-7-thinking", ) expect(set).toBe(true) const output = { message: { - model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" }, variant: "max", }, parts: [{ type: "text", text: "continue" }], @@ -392,9 +401,9 @@ describe("model fallback hook", () => { test("transforms model names for github-copilot provider via fallback chain", async () => { //#given const sessionID = "ses_model_fallback_ghcp" - clearPendingModelFallback(sessionID) + clearPendingModelFallback(modelFallback, sessionID) - const hook = createModelFallbackHook() as unknown as { + const hook = modelFallback as unknown as { "chat.message"?: ( input: { sessionID: string }, output: { message: Record; parts: Array<{ type: string; text?: string }> }, @@ -402,11 +411,12 @@ describe("model fallback hook", () => { } // Set a custom fallback chain that routes through github-copilot - setSessionFallbackChain(sessionID, [ + setSessionFallbackChain(modelFallback, sessionID, [ { providers: ["github-copilot"], model: "claude-sonnet-4-6" }, ]) const set = setPendingModelFallback( + modelFallback, sessionID, "Atlas - Plan Executor", "github-copilot", @@ -430,15 +440,15 @@ describe("model fallback hook", () => { modelID: "claude-sonnet-4.6", }) - clearPendingModelFallback(sessionID) + clearPendingModelFallback(modelFallback, sessionID) }) test("preserves canonical google preview model names via fallback chain", async () => { //#given const sessionID = "ses_model_fallback_google" - clearPendingModelFallback(sessionID) + clearPendingModelFallback(modelFallback, sessionID) - const hook = createModelFallbackHook() as unknown as { + const hook = modelFallback as unknown as { "chat.message"?: ( input: { sessionID: string }, output: { message: Record; parts: Array<{ type: string; text?: string }> }, @@ -446,11 +456,12 @@ describe("model fallback hook", () => { } // Set a custom fallback chain that routes through google - setSessionFallbackChain(sessionID, [ + setSessionFallbackChain(modelFallback, sessionID, [ { providers: ["google"], model: "gemini-3.1-pro-preview" }, ]) const set = setPendingModelFallback( + modelFallback, sessionID, "Oracle", "google", @@ -474,7 +485,7 @@ describe("model fallback hook", () => { modelID: "gemini-3.1-pro-preview", }) - clearPendingModelFallback(sessionID) + clearPendingModelFallback(modelFallback, sessionID) }) }) diff --git a/src/hooks/model-fallback/hook.ts b/src/hooks/model-fallback/hook.ts index b188bd48d..fee130ed8 100644 --- a/src/hooks/model-fallback/hook.ts +++ b/src/hooks/model-fallback/hook.ts @@ -1,13 +1,11 @@ import type { FallbackEntry } from "../../shared/model-requirements" -import { getAgentConfigKey } from "../../shared/agent-display-names" -import { AGENT_MODEL_REQUIREMENTS } from "../../shared/model-requirements" -import { readConnectedProvidersCache, readProviderModelsCache } from "../../shared/connected-providers-cache" -import { selectFallbackProvider } from "../../shared/model-error-classifier" -import { transformModelForProvider } from "../../shared/provider-model-id-transform" -import { log } from "../../shared/logger" import type { ChatMessageInput, ChatMessageHandlerOutput } from "../../plugin/chat-message" import { applyFallbackToChatMessage } from "./chat-message-fallback-handler" -import { getNextReachableFallback } from "./next-fallback" +import { + createModelFallbackStateController, + type ModelFallbackStateController, +} from "./fallback-state-controller" +import type { ModelFallbackControllerAccessor } from "./controller-accessor" type FallbackToast = (input: { title: string @@ -31,30 +29,45 @@ export type ModelFallbackState = { pending: boolean } -/** - * Map of sessionID -> pending model fallback state - * When a model error occurs, we store the fallback info here. - * The next chat.message call will use this to switch to the fallback model. - */ -const pendingModelFallbacks = new Map() -const lastToastKey = new Map() -const sessionFallbackChains = new Map() +type ModelFallbackControllerWithState = Pick< + ModelFallbackStateController, + | "lastToastKey" + | "setSessionFallbackChain" + | "clearSessionFallbackChain" + | "setPendingModelFallback" + | "getNextFallback" + | "clearPendingModelFallback" + | "hasPendingModelFallback" + | "getFallbackState" + | "reset" +> -export function setSessionFallbackChain(sessionID: string, fallbackChain: FallbackEntry[] | undefined): void { - if (!sessionID) return - if (!fallbackChain) { - sessionFallbackChains.set(sessionID, []) - return - } - if (fallbackChain.length === 0) { - sessionFallbackChains.set(sessionID, []) - return - } - sessionFallbackChains.set(sessionID, fallbackChain) +export type ModelFallbackHook = ModelFallbackControllerWithState & { + "chat.message": ( + input: ChatMessageInput, + output: ChatMessageHandlerOutput, + ) => Promise } -export function clearSessionFallbackChain(sessionID: string): void { - sessionFallbackChains.delete(sessionID) +type ModelFallbackHookArgs = { + toast?: FallbackToast + onApplied?: FallbackCallback + controllerAccessor?: ModelFallbackControllerAccessor +} + +export function setSessionFallbackChain( + controller: Pick, + sessionID: string, + fallbackChain: FallbackEntry[] | undefined, +): void { + controller.setSessionFallbackChain(sessionID, fallbackChain) +} + +export function clearSessionFallbackChain( + controller: Pick, + sessionID: string, +): void { + controller.clearSessionFallbackChain(sessionID) } /** @@ -62,56 +75,18 @@ export function clearSessionFallbackChain(sessionID: string): void { * Called when a model error is detected in session.error handler. */ export function setPendingModelFallback( + controller: Pick, sessionID: string, agentName: string, currentProviderID: string, currentModelID: string, ): boolean { - const agentKey = getAgentConfigKey(agentName) - const requirements = AGENT_MODEL_REQUIREMENTS[agentKey] - const hasSessionFallback = sessionFallbackChains.has(sessionID) - const sessionFallback = sessionFallbackChains.get(sessionID) - const fallbackChain = hasSessionFallback - ? sessionFallback - : requirements?.fallbackChain - - if (!fallbackChain || fallbackChain.length === 0) { - log("[model-fallback] No fallback chain for agent: " + agentName + " (key: " + agentKey + ")") - return false - } - - const existing = pendingModelFallbacks.get(sessionID) - - if (existing) { - if (existing.pending) { - log("[model-fallback] Pending fallback already armed for session: " + sessionID) - return false - } - - // Preserve progression across repeated session.error retries in same session. - // We only mark the next turn as pending fallback application. - existing.providerID = currentProviderID - existing.modelID = currentModelID - existing.pending = true - if (existing.attemptCount >= existing.fallbackChain.length) { - log("[model-fallback] Fallback chain exhausted for session: " + sessionID) - return false - } - log("[model-fallback] Re-armed pending fallback for session: " + sessionID) - return true - } - - const state: ModelFallbackState = { - providerID: currentProviderID, - modelID: currentModelID, - fallbackChain, - attemptCount: 0, - pending: true, - } - - pendingModelFallbacks.set(sessionID, state) - log("[model-fallback] Set pending fallback for session: " + sessionID + ", agent: " + agentName) - return true + return controller.setPendingModelFallback( + sessionID, + agentName, + currentProviderID, + currentModelID, + ) } /** @@ -119,55 +94,71 @@ export function setPendingModelFallback( * Increments attemptCount each time called. */ export function getNextFallback( + controller: Pick, sessionID: string, ): { providerID: string; modelID: string; variant?: string } | null { - const state = pendingModelFallbacks.get(sessionID) - if (!state) return null - - if (!state.pending) return null - - const fallback = getNextReachableFallback(sessionID, state) - if (fallback) { - return fallback - } - - log("[model-fallback] No more fallbacks for session: " + sessionID) - pendingModelFallbacks.delete(sessionID) - return null + return controller.getNextFallback(sessionID) } /** * Clears the pending fallback for a session. * Called after fallback is successfully applied. */ -export function clearPendingModelFallback(sessionID: string): void { - pendingModelFallbacks.delete(sessionID) - lastToastKey.delete(sessionID) +export function clearPendingModelFallback( + controller: Pick, + sessionID: string, +): void { + controller.clearPendingModelFallback(sessionID) } /** * Checks if there's a pending fallback for a session. */ -export function hasPendingModelFallback(sessionID: string): boolean { - const state = pendingModelFallbacks.get(sessionID) - return state?.pending === true +export function hasPendingModelFallback( + controller: Pick, + sessionID: string, +): boolean { + return controller.hasPendingModelFallback(sessionID) } /** * Gets the current fallback state for a session (for debugging). */ -export function getFallbackState(sessionID: string): ModelFallbackState | undefined { - return pendingModelFallbacks.get(sessionID) +export function getFallbackState( + controller: Pick, + sessionID: string, +): ModelFallbackState | undefined { + return controller.getFallbackState(sessionID) } /** * Creates a chat.message hook that applies model fallbacks when pending. */ -export function createModelFallbackHook(args?: { toast?: FallbackToast; onApplied?: FallbackCallback }) { +export function createModelFallbackHook(args?: ModelFallbackHookArgs): ModelFallbackHook { + const pendingModelFallbacks = new Map() + const lastToastKey = new Map() + const sessionFallbackChains = new Map() + const controller = createModelFallbackStateController({ + pendingModelFallbacks, + lastToastKey, + sessionFallbackChains, + }) + + args?.controllerAccessor?.register(controller) + const toast = args?.toast const onApplied = args?.onApplied return { + lastToastKey: controller.lastToastKey, + setSessionFallbackChain: controller.setSessionFallbackChain, + clearSessionFallbackChain: controller.clearSessionFallbackChain, + setPendingModelFallback: controller.setPendingModelFallback, + getNextFallback: controller.getNextFallback, + clearPendingModelFallback: controller.clearPendingModelFallback, + hasPendingModelFallback: controller.hasPendingModelFallback, + getFallbackState: controller.getFallbackState, + reset: controller.reset, "chat.message": async ( input: ChatMessageInput, output: ChatMessageHandlerOutput, @@ -175,7 +166,7 @@ export function createModelFallbackHook(args?: { toast?: FallbackToast; onApplie const { sessionID } = input if (!sessionID) return - const fallback = getNextFallback(sessionID) + const fallback = getNextFallback(controller, sessionID) if (!fallback) return await applyFallbackToChatMessage({ @@ -184,18 +175,15 @@ export function createModelFallbackHook(args?: { toast?: FallbackToast; onApplie fallback, toast, onApplied, - lastToastKey, + lastToastKey: controller.lastToastKey, }) }, } } /** - * Resets all module-global state for testing. - * Clears pending fallbacks, toast keys, and session chains. + * Resets hook-owned state for testing. */ -export function _resetForTesting(): void { - pendingModelFallbacks.clear() - lastToastKey.clear() - sessionFallbackChains.clear() +export function _resetForTesting(controller?: Pick): void { + controller?.reset() } diff --git a/src/hooks/model-fallback/index.ts b/src/hooks/model-fallback/index.ts new file mode 100644 index 000000000..08e4c0dd7 --- /dev/null +++ b/src/hooks/model-fallback/index.ts @@ -0,0 +1,2 @@ +export { createModelFallbackControllerAccessor } from "./controller-accessor" +export type { ModelFallbackControllerAccessor } from "./controller-accessor" diff --git a/src/hooks/no-hephaestus-non-gpt/index.test.ts b/src/hooks/no-hephaestus-non-gpt/index.test.ts index 7686bdbf1..6ca505f3c 100644 --- a/src/hooks/no-hephaestus-non-gpt/index.test.ts +++ b/src/hooks/no-hephaestus-non-gpt/index.test.ts @@ -30,12 +30,12 @@ describe("no-hephaestus-non-gpt hook", () => { await hook["chat.message"]?.({ sessionID: "ses_1", agent: HEPHAESTUS_DISPLAY, - model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7" }, }, output1) await hook["chat.message"]?.({ sessionID: "ses_1", agent: HEPHAESTUS_DISPLAY, - model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7" }, }, output2) // then - toast is shown and agent is switched to sisyphus @@ -66,7 +66,7 @@ describe("no-hephaestus-non-gpt hook", () => { await hook["chat.message"]?.({ sessionID: "ses_opt_out", agent: HEPHAESTUS_DISPLAY, - model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7" }, }, output) // then - warning toast is shown but agent is not switched @@ -114,7 +114,7 @@ describe("no-hephaestus-non-gpt hook", () => { await hook["chat.message"]?.({ sessionID: "ses_3", agent: SISYPHUS_DISPLAY, - model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7" }, }, output) // then - no toast @@ -136,7 +136,7 @@ describe("no-hephaestus-non-gpt hook", () => { // when - chat.message runs without input.agent await hook["chat.message"]?.({ sessionID: "ses_4", - model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7" }, }, output) // then - toast shown via session-agent fallback, switched to sisyphus diff --git a/src/hooks/no-sisyphus-gpt/index.test.ts b/src/hooks/no-sisyphus-gpt/index.test.ts index 908a01351..baeb23722 100644 --- a/src/hooks/no-sisyphus-gpt/index.test.ts +++ b/src/hooks/no-sisyphus-gpt/index.test.ts @@ -83,7 +83,7 @@ describe("no-sisyphus-gpt hook", () => { await hook["chat.message"]?.({ sessionID: "ses_2", agent: SISYPHUS_DISPLAY, - model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7" }, }, output) // then - no toast diff --git a/src/hooks/non-interactive-env/index.test.ts b/src/hooks/non-interactive-env/index.test.ts index 4e3419eb4..ecf5e06a9 100644 --- a/src/hooks/non-interactive-env/index.test.ts +++ b/src/hooks/non-interactive-env/index.test.ts @@ -12,6 +12,7 @@ describe("non-interactive-env hook", () => { originalEnv = { SHELL: process.env.SHELL, PSModulePath: process.env.PSModulePath, + MSYSTEM: process.env.MSYSTEM, CI: process.env.CI, OPENCODE_NON_INTERACTIVE: process.env.OPENCODE_NON_INTERACTIVE, } @@ -252,6 +253,7 @@ describe("non-interactive-env hook", () => { test("#given Windows with PowerShell env #when bash tool git command executes #then uses powershell syntax", async () => { delete process.env.SHELL + delete process.env.MSYSTEM process.env.PSModulePath = "C:\\Program Files\\PowerShell\\Modules" Object.defineProperty(process, "platform", { value: "win32" }) @@ -276,6 +278,7 @@ describe("non-interactive-env hook", () => { test("#given Windows without SHELL env #when bash tool git command executes #then uses cmd syntax", async () => { delete process.env.PSModulePath delete process.env.SHELL + delete process.env.MSYSTEM Object.defineProperty(process, "platform", { value: "win32" }) const hook = createNonInteractiveEnvHook(mockCtx) @@ -296,8 +299,7 @@ describe("non-interactive-env hook", () => { expect(cmd).not.toContain("export ") }) - test("#given Windows Git Bash environment #when git command executes #then uses detected shell syntax", async () => { - // Git Bash sets SHELL env var — detectShellType respects this + test("#given Windows Git Bash environment with SHELL #when git command executes #then uses unix syntax", async () => { delete process.env.PSModulePath process.env.SHELL = "/usr/bin/bash" Object.defineProperty(process, "platform", { value: "win32" }) @@ -313,14 +315,37 @@ describe("non-interactive-env hook", () => { ) const cmd = output.args.command as string - // Verify env prefix is applied (exact syntax depends on detected shell) - expect(cmd).toContain("git status") - expect(cmd.length).toBeGreaterThan("git status".length) + expect(cmd).toStartWith("export ") + expect(cmd).toContain("; git status") + expect(cmd).not.toContain("$env:") + }) + + test("#given Windows Git Bash via MSYSTEM without SHELL #when git command executes #then uses unix syntax", async () => { + delete process.env.SHELL + process.env.MSYSTEM = "MINGW64" + process.env.PSModulePath = "C:\\Program Files\\PowerShell\\Modules" + Object.defineProperty(process, "platform", { value: "win32" }) + + const hook = createNonInteractiveEnvHook(mockCtx) + const output: { args: Record; message?: string } = { + args: { command: "git status" }, + } + + await hook["tool.execute.before"]( + { tool: "bash", sessionID: "test", callID: "1" }, + output + ) + + const cmd = output.args.command as string + expect(cmd).toStartWith("export ") + expect(cmd).toContain("; git status") + expect(cmd).not.toContain("$env:") }) test("#given Windows platform #when chained git commands via bash tool #then uses cmd syntax", async () => { delete process.env.PSModulePath delete process.env.SHELL + delete process.env.MSYSTEM Object.defineProperty(process, "platform", { value: "win32" }) const hook = createNonInteractiveEnvHook(mockCtx) @@ -366,6 +391,7 @@ describe("non-interactive-env hook", () => { test("#given PSModulePath set on non-Windows #when git command executes #then uses powershell syntax", async () => { // PowerShell detection via PSModulePath should work regardless of platform delete process.env.SHELL + delete process.env.MSYSTEM process.env.PSModulePath = "C:\\Program Files\\PowerShell\\Modules" Object.defineProperty(process, "platform", { value: "linux" }) @@ -389,6 +415,7 @@ describe("non-interactive-env hook", () => { // Platform fallback: win32 without env hints should use cmd delete process.env.SHELL delete process.env.PSModulePath + delete process.env.MSYSTEM Object.defineProperty(process, "platform", { value: "win32" }) const hook = createNonInteractiveEnvHook(mockCtx) diff --git a/src/hooks/preemptive-compaction-trigger.ts b/src/hooks/preemptive-compaction-trigger.ts new file mode 100644 index 000000000..bbab74f76 --- /dev/null +++ b/src/hooks/preemptive-compaction-trigger.ts @@ -0,0 +1,131 @@ +import type { OhMyOpenCodeConfig } from "../config" +import { + resolveActualContextLimit, + type ContextLimitModelCacheState, +} from "../shared/context-limit-resolver" +import { log } from "../shared/logger" + +import { resolveCompactionModel } from "./shared/compaction-model-resolver" +import type { + CachedCompactionState, + PreemptiveCompactionContext, +} from "./preemptive-compaction-types" + +const PREEMPTIVE_COMPACTION_TIMEOUT_MS = 60_000 +const PREEMPTIVE_COMPACTION_THRESHOLD = 0.78 +const PREEMPTIVE_COMPACTION_COOLDOWN_MS = 60_000 + +declare function setTimeout(handler: () => void, timeout?: number): unknown +declare function clearTimeout(timeoutID: unknown): void + +async function withTimeout( + promise: Promise, + timeoutMs: number, + errorMessage: string, +): Promise { + let timeoutID: unknown + + const timeoutPromise = new Promise((_, reject) => { + timeoutID = setTimeout(() => { + reject(new Error(errorMessage)) + }, timeoutMs) + }) + + return await Promise.race([promise, timeoutPromise]).finally(() => { + clearTimeout(timeoutID) + }) +} + +export async function runPreemptiveCompactionIfNeeded(args: { + ctx: PreemptiveCompactionContext + pluginConfig: OhMyOpenCodeConfig + modelCacheState?: ContextLimitModelCacheState + sessionID: string + tokenCache: Map + compactionInProgress: Set + compactedSessions: Set + lastCompactionTime: Map +}): Promise { + const { + ctx, + pluginConfig, + modelCacheState, + sessionID, + tokenCache, + compactionInProgress, + compactedSessions, + lastCompactionTime, + } = args + + if (compactedSessions.has(sessionID) || compactionInProgress.has(sessionID)) return + + const lastTime = lastCompactionTime.get(sessionID) + if (lastTime && Date.now() - lastTime < PREEMPTIVE_COMPACTION_COOLDOWN_MS) return + + const cached = tokenCache.get(sessionID) + if (!cached) return + + const actualLimit = resolveActualContextLimit( + cached.providerID, + cached.modelID, + modelCacheState, + ) + + if (actualLimit === null) { + log("[preemptive-compaction] Skipping preemptive compaction: unknown context limit for model", { + providerID: cached.providerID, + modelID: cached.modelID, + }) + return + } + + const totalInputTokens = (cached.tokens.input ?? 0) + (cached.tokens.cache?.read ?? 0) + const usageRatio = totalInputTokens / actualLimit + if (usageRatio < PREEMPTIVE_COMPACTION_THRESHOLD || !cached.modelID) return + + compactionInProgress.add(sessionID) + lastCompactionTime.set(sessionID, Date.now()) + + try { + const { providerID: targetProviderID, modelID: targetModelID } = resolveCompactionModel( + pluginConfig, + sessionID, + cached.providerID, + cached.modelID, + ) + + await withTimeout( + ctx.client.session.summarize({ + path: { id: sessionID }, + body: { providerID: targetProviderID, modelID: targetModelID, auto: true }, + query: { directory: ctx.directory }, + }), + PREEMPTIVE_COMPACTION_TIMEOUT_MS, + `Compaction summarize timed out after ${PREEMPTIVE_COMPACTION_TIMEOUT_MS}ms`, + ) + + compactedSessions.add(sessionID) + } catch (error) { + log("[preemptive-compaction] Compaction failed", { + sessionID, + providerID: cached.providerID, + modelID: cached.modelID, + error: String(error), + }) + ctx.client.tui.showToast({ + body: { + title: "Preemptive compaction failed", + message: `Context window is above ${Math.round(PREEMPTIVE_COMPACTION_THRESHOLD * 100)}% and auto-compaction could not run. The session may grow large. Error: ${String(error)}`, + variant: "warning", + duration: 10000, + }, + }).catch((toastError: unknown) => { + log("[preemptive-compaction] Failed to show toast", { + sessionID, + toastError: String(toastError), + }) + }) + } finally { + compactionInProgress.delete(sessionID) + } +} diff --git a/src/hooks/preemptive-compaction-types.ts b/src/hooks/preemptive-compaction-types.ts new file mode 100644 index 000000000..77efed575 --- /dev/null +++ b/src/hooks/preemptive-compaction-types.ts @@ -0,0 +1,41 @@ +export interface TokenInfo { + input: number + output: number + reasoning: number + cache: { read: number; write: number } +} + +export interface CachedCompactionState { + providerID: string + modelID: string + tokens: TokenInfo +} + +export interface PreemptiveCompactionClient { + session: { + messages: (input: { + path: { id: string } + query?: { directory: string } + }) => Promise + summarize: (input: { + path: { id: string } + body: { providerID: string; modelID: string; auto?: boolean } + query: { directory: string } + }) => Promise + } + tui: { + showToast: (input: { + body: { + title: string + message: string + variant: "warning" + duration: number + } + }) => Promise + } +} + +export interface PreemptiveCompactionContext { + client: PreemptiveCompactionClient + directory: string +} diff --git a/src/hooks/preemptive-compaction.ts b/src/hooks/preemptive-compaction.ts index ecab70676..7b4828dcb 100644 --- a/src/hooks/preemptive-compaction.ts +++ b/src/hooks/preemptive-compaction.ts @@ -1,69 +1,16 @@ -import { log } from "../shared/logger" import type { OhMyOpenCodeConfig } from "../config" -import { - resolveActualContextLimit, - type ContextLimitModelCacheState, -} from "../shared/context-limit-resolver" +import type { ContextLimitModelCacheState } from "../shared/context-limit-resolver" -import { resolveCompactionModel } from "./shared/compaction-model-resolver" import { createPostCompactionDegradationMonitor } from "./preemptive-compaction-degradation-monitor" - -const PREEMPTIVE_COMPACTION_TIMEOUT_MS = 60_000 -const PREEMPTIVE_COMPACTION_THRESHOLD = 0.78 -const PREEMPTIVE_COMPACTION_COOLDOWN_MS = 60_000 - -declare function setTimeout(handler: () => void, timeout?: number): unknown -declare function clearTimeout(timeoutID: unknown): void - -interface TokenInfo { - input: number - output: number - reasoning: number - cache: { read: number; write: number } -} - -interface CachedCompactionState { - providerID: string - modelID: string - tokens: TokenInfo -} - -async function withTimeout( - promise: Promise, - timeoutMs: number, - errorMessage: string, -): Promise { - let timeoutID: unknown - - const timeoutPromise = new Promise((_, reject) => { - timeoutID = setTimeout(() => { - reject(new Error(errorMessage)) - }, timeoutMs) - }) - - return await Promise.race([promise, timeoutPromise]).finally(() => { - clearTimeout(timeoutID) - }) -} - -type PluginInput = { - client: { - session: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - messages: (...args: any[]) => any - // eslint-disable-next-line @typescript-eslint/no-explicit-any - summarize: (...args: any[]) => any - } - tui: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - showToast: (...args: any[]) => any - } - } - directory: string -} +import { runPreemptiveCompactionIfNeeded } from "./preemptive-compaction-trigger" +import type { + CachedCompactionState, + PreemptiveCompactionContext, + TokenInfo, +} from "./preemptive-compaction-types" export function createPreemptiveCompactionHook( - ctx: PluginInput, + ctx: PreemptiveCompactionContext, pluginConfig: OhMyOpenCodeConfig, modelCacheState?: ContextLimitModelCacheState, ) { @@ -84,78 +31,16 @@ export function createPreemptiveCompactionHook( input: { tool: string; sessionID: string; callID: string }, _output: { title: string; output: string; metadata: unknown } ) => { - const { sessionID } = input - if (compactedSessions.has(sessionID) || compactionInProgress.has(sessionID)) return - - const lastTime = lastCompactionTime.get(sessionID) - if (lastTime && Date.now() - lastTime < PREEMPTIVE_COMPACTION_COOLDOWN_MS) return - - const cached = tokenCache.get(sessionID) - if (!cached) return - - const actualLimit = resolveActualContextLimit( - cached.providerID, - cached.modelID, + await runPreemptiveCompactionIfNeeded({ + ctx, + pluginConfig, modelCacheState, - ) - - if (actualLimit === null) { - log("[preemptive-compaction] Skipping preemptive compaction: unknown context limit for model", { - providerID: cached.providerID, - modelID: cached.modelID, - }) - return - } - - const totalInputTokens = (cached.tokens.input ?? 0) + (cached.tokens.cache?.read ?? 0) - const usageRatio = totalInputTokens / actualLimit - if (usageRatio < PREEMPTIVE_COMPACTION_THRESHOLD || !cached.modelID) return - - compactionInProgress.add(sessionID) - lastCompactionTime.set(sessionID, Date.now()) - - try { - const { providerID: targetProviderID, modelID: targetModelID } = resolveCompactionModel( - pluginConfig, - sessionID, - cached.providerID, - cached.modelID, - ) - - await withTimeout( - ctx.client.session.summarize({ - path: { id: sessionID }, - body: { providerID: targetProviderID, modelID: targetModelID, auto: true } as never, - query: { directory: ctx.directory }, - }), - PREEMPTIVE_COMPACTION_TIMEOUT_MS, - `Compaction summarize timed out after ${PREEMPTIVE_COMPACTION_TIMEOUT_MS}ms`, - ) - - compactedSessions.add(sessionID) - } catch (error) { - log("[preemptive-compaction] Compaction failed", { - sessionID, - providerID: cached.providerID, - modelID: cached.modelID, - error: String(error), - }) - ctx.client.tui.showToast({ - body: { - title: "Preemptive compaction failed", - message: `Context window is above ${Math.round(PREEMPTIVE_COMPACTION_THRESHOLD * 100)}% and auto-compaction could not run. The session may grow large. Error: ${String(error)}`, - variant: "warning", - duration: 10000, - }, - }).catch((toastError: unknown) => { - log("[preemptive-compaction] Failed to show toast", { - sessionID, - toastError: String(toastError), - }) - }) - } finally { - compactionInProgress.delete(sessionID) - } + sessionID: input.sessionID, + tokenCache, + compactionInProgress, + compactedSessions, + lastCompactionTime, + }) } const eventHandler = async ({ event }: { event: { type: string; properties?: unknown } }) => { diff --git a/src/hooks/ralph-loop/oracle-verification-detector.ts b/src/hooks/ralph-loop/oracle-verification-detector.ts index 304a38809..277ef1fe2 100644 --- a/src/hooks/ralph-loop/oracle-verification-detector.ts +++ b/src/hooks/ralph-loop/oracle-verification-detector.ts @@ -1,3 +1,5 @@ +import { extractTaskLink } from "../../features/tool-metadata-store" +import { stripInvisibleAgentCharacters } from "../../shared/agent-display-names" import { ULTRAWORK_VERIFICATION_PROMISE } from "./constants" export interface OracleVerificationEvidence { @@ -8,8 +10,6 @@ export interface OracleVerificationEvidence { const AGENT_LINE_PATTERN = /^Agent:[ \t]*(\S+)$/im const PROMISE_TAG_PATTERN = /[ \t]*(\S+?)[ \t]*<\/promise>/is -const TASK_METADATA_PATTERN = /[ \t]*([\s\S]*?)[ \t]*<\/task_metadata>/is -const SESSION_ID_LINE_PATTERN = /^session_id:[ \t]*(\S+)$/im export function parseOracleVerificationEvidence(text: string): OracleVerificationEvidence | undefined { const trimmedText = text.trim() @@ -35,17 +35,9 @@ export function parseOracleVerificationEvidence(text: string): OracleVerificatio return undefined } - const metadataMatch = trimmedText.match(TASK_METADATA_PATTERN) - let sessionID: string | undefined - if (metadataMatch) { - const metadataContent = metadataMatch[1] - const sessionIDMatch = metadataContent.match(SESSION_ID_LINE_PATTERN) - if (sessionIDMatch) { - sessionID = sessionIDMatch[1]?.trim() - } - } + const sessionID = extractTaskLink(undefined, trimmedText).sessionId - return { agent, promise, sessionID } + return { agent, promise, sessionID } } export function isOracleVerified(text: string): boolean { @@ -54,7 +46,7 @@ export function isOracleVerified(text: string): boolean { return false } - const isOracleAgent = evidence.agent.toLowerCase() === "oracle" + const isOracleAgent = stripInvisibleAgentCharacters(evidence.agent).toLowerCase() === "oracle" const isVerifiedPromise = evidence.promise === ULTRAWORK_VERIFICATION_PROMISE return isOracleAgent && isVerifiedPromise @@ -62,7 +54,7 @@ export function isOracleVerified(text: string): boolean { export function extractOracleSessionID(text: string): string | undefined { const evidence = parseOracleVerificationEvidence(text) - if (!evidence || evidence.agent.toLowerCase() !== "oracle") { + if (!evidence || stripInvisibleAgentCharacters(evidence.agent).toLowerCase() !== "oracle") { return undefined } diff --git a/src/hooks/ralph-loop/reset-strategy-race-condition.test.ts b/src/hooks/ralph-loop/reset-strategy-race-condition.test.ts index 5fcd35a2e..8f31f8ec2 100644 --- a/src/hooks/ralph-loop/reset-strategy-race-condition.test.ts +++ b/src/hooks/ralph-loop/reset-strategy-race-condition.test.ts @@ -21,9 +21,9 @@ function createDeferred(): { } } -async function waitUntil(condition: () => boolean): Promise { +async function waitUntil(shouldTrigger: () => boolean): Promise { for (let index = 0; index < 100; index++) { - if (condition()) { + if (shouldTrigger()) { return } diff --git a/src/hooks/rules-injector/cache.test.ts b/src/hooks/rules-injector/cache.test.ts new file mode 100644 index 000000000..39a5bb472 --- /dev/null +++ b/src/hooks/rules-injector/cache.test.ts @@ -0,0 +1,74 @@ +import { afterEach, describe, expect, it } from "bun:test"; +import { randomUUID } from "node:crypto"; +import { existsSync } from "node:fs"; +import { join } from "node:path"; +import { createSessionCacheStore } from "./cache"; +import { RULES_INJECTOR_STORAGE } from "./constants"; +import { clearInjectedRules, saveInjectedRules } from "./storage"; + +const trackedSessionIDs: string[] = []; + +function createSessionID(prefix: string): string { + const sessionID = `${prefix}-${randomUUID()}`; + trackedSessionIDs.push(sessionID); + return sessionID; +} + +function getStoragePath(sessionID: string): string { + return join(RULES_INJECTOR_STORAGE, `${sessionID}.json`); +} + +afterEach(() => { + for (const sessionID of trackedSessionIDs.splice(0)) { + clearInjectedRules(sessionID); + } +}); + +describe("createSessionCacheStore", () => { + it("keeps factory instances isolated for the same session", () => { + // given + const sessionID = createSessionID("cache-isolation"); + const firstStore = createSessionCacheStore(); + const secondStore = createSessionCacheStore(); + const firstCache = firstStore.getSessionCache(sessionID); + + // when + firstCache.contentHashes.add("hash:first"); + firstCache.realPaths.add("/tmp/first-rule.md"); + const secondCache = secondStore.getSessionCache(sessionID); + + // then + expect([...secondCache.contentHashes]).toEqual([]); + expect([...secondCache.realPaths]).toEqual([]); + }); + + it("clears only the targeted session cache and persisted state", () => { + // given + const deletedSessionID = createSessionID("deleted-session"); + const retainedSessionID = createSessionID("retained-session"); + + saveInjectedRules(deletedSessionID, { + contentHashes: new Set(["hash:deleted"]), + realPaths: new Set(["/tmp/deleted-rule.md"]), + }); + saveInjectedRules(retainedSessionID, { + contentHashes: new Set(["hash:retained"]), + realPaths: new Set(["/tmp/retained-rule.md"]), + }); + + const store = createSessionCacheStore(); + store.getSessionCache(deletedSessionID); + const retainedCache = store.getSessionCache(retainedSessionID); + + // when + store.clearSessionCache(deletedSessionID); + const reloadedRetainedCache = store.getSessionCache(retainedSessionID); + + // then + expect(existsSync(getStoragePath(deletedSessionID))).toBe(false); + expect(existsSync(getStoragePath(retainedSessionID))).toBe(true); + expect(reloadedRetainedCache).toBe(retainedCache); + expect([...reloadedRetainedCache.contentHashes]).toEqual(["hash:retained"]); + expect([...reloadedRetainedCache.realPaths]).toEqual(["/tmp/retained-rule.md"]); + }); +}); diff --git a/src/hooks/rules-injector/cache.ts b/src/hooks/rules-injector/cache.ts index b23273144..43d64565c 100644 --- a/src/hooks/rules-injector/cache.ts +++ b/src/hooks/rules-injector/cache.ts @@ -1,4 +1,6 @@ import { clearInjectedRules, loadInjectedRules } from "./storage"; +import { createRuleScanCache } from "./rule-scan-cache"; +import type { RuleScanCache } from "./rule-scan-cache"; export type SessionInjectedRulesCache = { contentHashes: Set; @@ -25,3 +27,29 @@ export function createSessionCacheStore(): { return { getSessionCache, clearSessionCache }; } + +export function createSessionRuleScanCacheStore(): { + getSessionRuleScanCache: (sessionID: string) => RuleScanCache; + clearSessionRuleScanCache: (sessionID: string) => void; +} { + const sessionCaches = new Map(); + + function getSessionRuleScanCache(sessionID: string): RuleScanCache { + const existingCache = sessionCaches.get(sessionID); + if (existingCache) { + return existingCache; + } + + const cache = createRuleScanCache(); + sessionCaches.set(sessionID, cache); + return cache; + } + + function clearSessionRuleScanCache(sessionID: string): void { + const cache = sessionCaches.get(sessionID); + cache?.clear(); + sessionCaches.delete(sessionID); + } + + return { getSessionRuleScanCache, clearSessionRuleScanCache }; +} diff --git a/src/hooks/rules-injector/hook.ts b/src/hooks/rules-injector/hook.ts index f46af4570..b3a09ca12 100644 --- a/src/hooks/rules-injector/hook.ts +++ b/src/hooks/rules-injector/hook.ts @@ -1,8 +1,9 @@ import type { PluginInput } from "@opencode-ai/plugin"; import { createDynamicTruncator } from "../../shared/dynamic-truncator"; import { getRuleInjectionFilePath } from "./output-path"; -import { createSessionCacheStore } from "./cache"; +import { createSessionCacheStore, createSessionRuleScanCacheStore } from "./cache"; import { createRuleInjectionProcessor } from "./injector"; +import { clearProjectRootCache } from "./project-root-finder"; interface ToolExecuteInput { tool: string; @@ -36,15 +37,23 @@ export function createRulesInjectorHook( ) { const truncator = createDynamicTruncator(ctx, modelCacheState); const { getSessionCache, clearSessionCache } = createSessionCacheStore(); + const { getSessionRuleScanCache, clearSessionRuleScanCache } = + createSessionRuleScanCacheStore(); const { processFilePathForInjection } = createRuleInjectionProcessor({ workspaceDirectory: ctx.directory, truncator, getSessionCache, + getSessionRuleScanCache, ruleFinderOptions: options?.skipClaudeUserRules ? { skipClaudeUserRules: true } : undefined, }); + function clearSessionState(sessionID: string): void { + clearSessionCache(sessionID); + clearSessionRuleScanCache(sessionID); + } + const toolExecuteAfter = async ( input: ToolExecuteInput, output: ToolExecuteOutput @@ -73,16 +82,18 @@ export function createRulesInjectorHook( if (event.type === "session.deleted") { const sessionInfo = props?.info as { id?: string } | undefined; if (sessionInfo?.id) { - clearSessionCache(sessionInfo.id); + clearSessionState(sessionInfo.id); } + clearProjectRootCache(); } if (event.type === "session.compacted") { const sessionID = (props?.sessionID ?? (props?.info as { id?: string } | undefined)?.id) as string | undefined; if (sessionID) { - clearSessionCache(sessionID); + clearSessionState(sessionID); } + clearProjectRootCache(); } }; diff --git a/src/hooks/rules-injector/injector.ts b/src/hooks/rules-injector/injector.ts index dc4e9fe29..0cd64be5b 100644 --- a/src/hooks/rules-injector/injector.ts +++ b/src/hooks/rules-injector/injector.ts @@ -12,6 +12,7 @@ import { import { parseRuleFrontmatter } from "./parser"; import { saveInjectedRules } from "./storage"; import type { SessionInjectedRulesCache } from "./cache"; +import type { RuleScanCache } from "./rule-scan-cache"; import type { RuleMetadata } from "./types"; type ToolExecuteOutput = { @@ -56,6 +57,7 @@ export function createRuleInjectionProcessor(deps: { workspaceDirectory: string; truncator: DynamicTruncator; getSessionCache: (sessionID: string) => SessionInjectedRulesCache; + getSessionRuleScanCache?: (sessionID: string) => RuleScanCache; ruleFinderOptions?: FindRuleFilesOptions; readFileSync?: typeof readFileSync; statSync?: typeof statSync; @@ -76,6 +78,7 @@ export function createRuleInjectionProcessor(deps: { workspaceDirectory, truncator, getSessionCache, + getSessionRuleScanCache, ruleFinderOptions, readFileSync: readRuleFileSync = readFileSync, statSync: statRuleSync = statSync, @@ -121,9 +124,16 @@ export function createRuleInjectionProcessor(deps: { const projectRoot = findProjectRoot(resolved); const cache = getSessionCache(sessionID); + const ruleScanCache = getSessionRuleScanCache?.(sessionID); const home = getHomeDir(); - const ruleFileCandidates = findRuleFiles(projectRoot, home, resolved, ruleFinderOptions); + const ruleFileCandidates = findRuleFiles( + projectRoot, + home, + resolved, + ruleFinderOptions, + ruleScanCache, + ); const toInject: RuleToInject[] = []; let dirty = false; diff --git a/src/hooks/rules-injector/project-root-finder.test.ts b/src/hooks/rules-injector/project-root-finder.test.ts new file mode 100644 index 000000000..35d442290 --- /dev/null +++ b/src/hooks/rules-injector/project-root-finder.test.ts @@ -0,0 +1,47 @@ +import { afterEach, describe, expect, it, mock } from "bun:test"; + +describe("findProjectRoot", () => { + afterEach(async () => { + const actualFileSystem = await import("node:fs"); + mock.module("node:fs", () => actualFileSystem); + }); + + it("memoizes repeated lookups for the same start path and resets on cache clear", async () => { + // given + const actualFileSystem = await import("node:fs"); + const projectRoot = "/workspace/project"; + const startPath = `${projectRoot}/src/file.ts`; + const packageJsonPath = `${projectRoot}/package.json`; + + const existsSyncSpy = mock((path: string) => path === packageJsonPath); + const statSyncSpy = mock(() => ({ isDirectory: () => false })); + + mock.module("node:fs", () => ({ + ...actualFileSystem, + existsSync: existsSyncSpy, + statSync: statSyncSpy, + })); + + const { clearProjectRootCache, findProjectRoot } = await import( + `./project-root-finder.ts?memoization=${Date.now()}` + ); + + // when + const firstResult = findProjectRoot(startPath); + const firstExistsSyncCallCount = existsSyncSpy.mock.calls.length; + + const secondResult = findProjectRoot(startPath); + const secondExistsSyncCallCount = existsSyncSpy.mock.calls.length; + + clearProjectRootCache(); + const thirdResult = findProjectRoot(startPath); + + // then + expect(firstResult).toBe(projectRoot); + expect(secondResult).toBe(projectRoot); + expect(thirdResult).toBe(projectRoot); + expect(firstExistsSyncCallCount).toBeGreaterThan(0); + expect(secondExistsSyncCallCount).toBe(firstExistsSyncCallCount); + expect(existsSyncSpy).toHaveBeenCalledTimes(firstExistsSyncCallCount * 2); + }); +}); diff --git a/src/hooks/rules-injector/project-root-finder.ts b/src/hooks/rules-injector/project-root-finder.ts index da697f0d9..ea552e0c9 100644 --- a/src/hooks/rules-injector/project-root-finder.ts +++ b/src/hooks/rules-injector/project-root-finder.ts @@ -2,6 +2,12 @@ import { existsSync, statSync } from "node:fs"; import { dirname, join } from "node:path"; import { PROJECT_MARKERS } from "./constants"; +const projectRootCache = new Map(); + +export function clearProjectRootCache(): void { + projectRootCache.clear(); +} + /** * Find project root by walking up from startPath. * Checks for PROJECT_MARKERS (.git, pyproject.toml, package.json, etc.) @@ -10,6 +16,16 @@ import { PROJECT_MARKERS } from "./constants"; * @returns Project root path or null if not found */ export function findProjectRoot(startPath: string): string | null { + if (projectRootCache.has(startPath)) { + return projectRootCache.get(startPath) ?? null; + } + + const projectRoot = findProjectRootWithoutCache(startPath); + projectRootCache.set(startPath, projectRoot); + return projectRoot; +} + +function findProjectRootWithoutCache(startPath: string): string | null { let current: string; try { diff --git a/src/hooks/rules-injector/rule-file-finder.ts b/src/hooks/rules-injector/rule-file-finder.ts index 98bd6942b..7059804d4 100644 --- a/src/hooks/rules-injector/rule-file-finder.ts +++ b/src/hooks/rules-injector/rule-file-finder.ts @@ -1,51 +1,108 @@ import { existsSync, statSync } from "node:fs"; -import { dirname, join } from "node:path"; +import { dirname, join, sep } from "node:path"; import { + OPENCODE_USER_RULE_DIRS, PROJECT_RULE_FILES, PROJECT_RULE_SUBDIRS, USER_RULE_DIR, - OPENCODE_USER_RULE_DIRS, } from "./constants"; -import type { RuleFileCandidate } from "./types"; +import type { RuleScanCache } from "./rule-scan-cache"; import { findRuleFilesRecursive, safeRealpathSync } from "./rule-file-scanner"; +import type { RuleFileCandidate } from "./types"; export interface FindRuleFilesOptions { - /** - * When true, skip loading rules from ~/.claude/rules/. - * Use when claude_code integration is disabled to prevent - * Claude Code-specific instructions from leaking into non-Claude agents. - */ skipClaudeUserRules?: boolean; } -/** - * Find all rule files for a given context. - * Searches from currentFile upward to projectRoot for rule directories, - * then user-level directory (~/.claude/rules). - * - * IMPORTANT: This searches EVERY directory from file to project root. - * Not just the project root itself. - * - * @param projectRoot - Project root path (or null if outside any project) - * @param homeDir - User home directory - * @param currentFile - Current file being edited (for distance calculation) - * @returns Array of rule file candidates sorted by distance - */ +function getUserRuleDirs(homeDir: string, skipClaudeUserRules: boolean): string[] { + const userRuleDirs = OPENCODE_USER_RULE_DIRS.map((dir) => join(homeDir, dir)); + if (!skipClaudeUserRules) { + userRuleDirs.push(join(homeDir, USER_RULE_DIR)); + } + return userRuleDirs; +} + +function createCacheKey( + projectRoot: string | null, + startDir: string, + skipClaudeUserRules: boolean, +): string { + return `${projectRoot ?? ""}|${startDir}|${skipClaudeUserRules ? "1" : "0"}`; +} + +function createCachedCandidate( + filePath: string, + projectRoot: string | null, + startDir: string, + userRuleDirs: string[], +): RuleFileCandidate | undefined { + const realPath = safeRealpathSync(filePath); + + for (const userRuleDir of userRuleDirs) { + if (filePath.startsWith(`${userRuleDir}${sep}`)) { + return { path: filePath, realPath, isGlobal: true, distance: 9999 }; + } + } + + if (projectRoot) { + for (const ruleFile of PROJECT_RULE_FILES) { + if (filePath === join(projectRoot, ruleFile)) { + return { + path: filePath, + realPath, + isGlobal: false, + distance: 0, + isSingleFile: true, + }; + } + } + } + + let currentDir = startDir; + let distance = 0; + while (true) { + for (const [parent, subdir] of PROJECT_RULE_SUBDIRS) { + const ruleDir = join(currentDir, parent, subdir); + if (filePath.startsWith(`${ruleDir}${sep}`)) { + return { path: filePath, realPath, isGlobal: false, distance }; + } + } + + if (projectRoot && currentDir === projectRoot) break; + const parentDir = dirname(currentDir); + if (parentDir === currentDir) break; + currentDir = parentDir; + distance += 1; + } + + return undefined; +} + export function findRuleFiles( projectRoot: string | null, homeDir: string, currentFile: string, options?: FindRuleFilesOptions, + cache?: RuleScanCache, ): RuleFileCandidate[] { + const startDir = dirname(currentFile); + const skipClaudeUserRules = options?.skipClaudeUserRules ?? false; + const userRuleDirs = getUserRuleDirs(homeDir, skipClaudeUserRules); + const cacheKey = createCacheKey(projectRoot, startDir, skipClaudeUserRules); + const cachedPaths = cache?.get(cacheKey); + + if (cachedPaths) { + return cachedPaths + .map((filePath) => createCachedCandidate(filePath, projectRoot, startDir, userRuleDirs)) + .filter((candidate): candidate is RuleFileCandidate => candidate !== undefined); + } + const candidates: RuleFileCandidate[] = []; const seenRealPaths = new Set(); - - // Search from current file's directory up to project root - let currentDir = dirname(currentFile); + let currentDir = startDir; let distance = 0; while (true) { - // Search rule directories in current directory for (const [parent, subdir] of PROJECT_RULE_SUBDIRS) { const ruleDir = join(currentDir, parent, subdir); const files: string[] = []; @@ -55,60 +112,41 @@ export function findRuleFiles( const realPath = safeRealpathSync(filePath); if (seenRealPaths.has(realPath)) continue; seenRealPaths.add(realPath); - - candidates.push({ - path: filePath, - realPath, - isGlobal: false, - distance, - }); + candidates.push({ path: filePath, realPath, isGlobal: false, distance }); } } - // Stop at project root or filesystem root if (projectRoot && currentDir === projectRoot) break; const parentDir = dirname(currentDir); if (parentDir === currentDir) break; currentDir = parentDir; - distance++; + distance += 1; } - // Check for single-file rules at project root (e.g., .github/copilot-instructions.md) if (projectRoot) { for (const ruleFile of PROJECT_RULE_FILES) { const filePath = join(projectRoot, ruleFile); - if (existsSync(filePath)) { - try { - const stat = statSync(filePath); - if (stat.isFile()) { - const realPath = safeRealpathSync(filePath); - if (!seenRealPaths.has(realPath)) { - seenRealPaths.add(realPath); - candidates.push({ - path: filePath, - realPath, - isGlobal: false, - distance: 0, - isSingleFile: true, - }); - } - } - } catch { - // Skip if file can't be read - } + if (!existsSync(filePath)) continue; + + try { + const stat = statSync(filePath); + if (!stat.isFile()) continue; + const realPath = safeRealpathSync(filePath); + if (seenRealPaths.has(realPath)) continue; + seenRealPaths.add(realPath); + candidates.push({ + path: filePath, + realPath, + isGlobal: false, + distance: 0, + isSingleFile: true, + }); + } catch { + continue; } } } - // Search user-level rule directories - // Always search OpenCode-native dirs (~/.sisyphus/rules, ~/.opencode/rules) - const userRuleDirs: string[] = OPENCODE_USER_RULE_DIRS.map((dir) => join(homeDir, dir)); - - // Only search ~/.claude/rules when claude_code integration is not disabled - if (!options?.skipClaudeUserRules) { - userRuleDirs.push(join(homeDir, USER_RULE_DIR)); - } - for (const userRuleDir of userRuleDirs) { const userFiles: string[] = []; findRuleFilesRecursive(userRuleDir, userFiles); @@ -117,23 +155,21 @@ export function findRuleFiles( const realPath = safeRealpathSync(filePath); if (seenRealPaths.has(realPath)) continue; seenRealPaths.add(realPath); - - candidates.push({ - path: filePath, - realPath, - isGlobal: true, - distance: 9999, // Global rules always have max distance - }); + candidates.push({ path: filePath, realPath, isGlobal: true, distance: 9999 }); } } - // Sort by distance (closest first, then global rules last) - candidates.sort((a, b) => { - if (a.isGlobal !== b.isGlobal) { - return a.isGlobal ? 1 : -1; + candidates.sort((left, right) => { + if (left.isGlobal !== right.isGlobal) { + return left.isGlobal ? 1 : -1; } - return a.distance - b.distance; + return left.distance - right.distance; }); + cache?.set( + cacheKey, + candidates.map((candidate) => candidate.path), + ); + return candidates; } diff --git a/src/hooks/rules-injector/rule-file-scanner.test.ts b/src/hooks/rules-injector/rule-file-scanner.test.ts new file mode 100644 index 000000000..cadf4c3f1 --- /dev/null +++ b/src/hooks/rules-injector/rule-file-scanner.test.ts @@ -0,0 +1,42 @@ +import { afterEach, describe, expect, test } from "bun:test"; +import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { randomUUID } from "node:crypto"; +import { findRuleFilesRecursive } from "./rule-file-scanner"; + +const createdDirectories: string[] = []; + +afterEach(() => { + for (const directory of createdDirectories.splice(0)) { + if (existsSync(directory)) { + rmSync(directory, { recursive: true, force: true }); + } + } +}); + +describe("findRuleFilesRecursive", () => { + test("returns rule files outside excluded nested directories", () => { + // given + const temporaryDirectory = join(tmpdir(), `perf-d01-${randomUUID()}`); + createdDirectories.push(temporaryDirectory); + + const rulesDirectory = join(temporaryDirectory, ".sisyphus", "rules"); + mkdirSync(join(rulesDirectory, "node_modules", "fake"), { recursive: true }); + mkdirSync(join(rulesDirectory, ".git"), { recursive: true }); + writeFileSync(join(rulesDirectory, "foo.md"), "root rule"); + writeFileSync( + join(rulesDirectory, "node_modules", "fake", "x.md"), + "ignored node_modules rule", + ); + writeFileSync(join(rulesDirectory, ".git", "x.md"), "ignored git rule"); + + const results: string[] = []; + + // when + findRuleFilesRecursive(rulesDirectory, results); + + // then + expect(results).toEqual([join(rulesDirectory, "foo.md")]); + }); +}); diff --git a/src/hooks/rules-injector/rule-file-scanner.ts b/src/hooks/rules-injector/rule-file-scanner.ts index ffd87d8a9..2cd853d07 100644 --- a/src/hooks/rules-injector/rule-file-scanner.ts +++ b/src/hooks/rules-injector/rule-file-scanner.ts @@ -1,5 +1,6 @@ import { existsSync, readdirSync, realpathSync } from "node:fs"; import { join } from "node:path"; +import { EXCLUDED_DIRS } from "../../shared"; import { GITHUB_INSTRUCTIONS_PATTERN, RULE_EXTENSIONS } from "./constants"; function isGitHubInstructionsDir(dir: string): boolean { @@ -28,6 +29,7 @@ export function findRuleFilesRecursive(dir: string, results: string[]): void { const fullPath = join(dir, entry.name); if (entry.isDirectory()) { + if (EXCLUDED_DIRS.has(entry.name)) continue; findRuleFilesRecursive(fullPath, results); } else if (entry.isFile()) { if (isValidRuleFile(entry.name, dir)) { diff --git a/src/hooks/rules-injector/rule-scan-cache.test.ts b/src/hooks/rules-injector/rule-scan-cache.test.ts new file mode 100644 index 000000000..778cf341a --- /dev/null +++ b/src/hooks/rules-injector/rule-scan-cache.test.ts @@ -0,0 +1,91 @@ +import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test"; +import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +function createImportSuffix(): string { + return `?test=${Date.now()}-${Math.random()}`; +} + +describe("createRuleScanCache", () => { + afterEach(() => { + mock.restore(); + }); + + it("returns undefined before set, returns stored value, and clears entries", async () => { + // given + const { createRuleScanCache } = await import(`./rule-scan-cache${createImportSuffix()}`); + const cache = createRuleScanCache(); + const value = ["a", "b"]; + + // when + const initialValue = cache.get("k1"); + cache.set("k1", value); + const storedValue = cache.get("k1"); + cache.clear(); + const clearedValue = cache.get("k1"); + + // then + expect(initialValue).toBeUndefined(); + expect(storedValue).toEqual(value); + expect(clearedValue).toBeUndefined(); + }); +}); + +describe("findRuleFiles with scan cache", () => { + let testRoot = ""; + let homeDir = ""; + let projectRoot = ""; + let currentFile = ""; + let expectedRuleFile = ""; + let expectedRuleDir = ""; + + beforeEach(() => { + testRoot = join(tmpdir(), `rule-scan-cache-test-${Date.now()}`); + homeDir = join(testRoot, "home"); + projectRoot = join(testRoot, "project"); + currentFile = join(projectRoot, "src", "index.ts"); + expectedRuleDir = join(projectRoot, ".github", "instructions"); + expectedRuleFile = join(expectedRuleDir, "typescript.instructions.md"); + + mkdirSync(join(projectRoot, ".git"), { recursive: true }); + mkdirSync(join(projectRoot, "src"), { recursive: true }); + mkdirSync(homeDir, { recursive: true }); + writeFileSync(currentFile, "export const value = 1;\n"); + }); + + afterEach(() => { + mock.restore(); + if (existsSync(testRoot)) { + rmSync(testRoot, { recursive: true, force: true }); + } + }); + + it("reuses cached directory scan results for identical inputs", async () => { + // given + const findRuleFilesRecursive = mock((directoryPath: string, results: string[]) => { + if (directoryPath === expectedRuleDir) { + results.push(expectedRuleFile); + } + }); + + mock.module("./rule-file-scanner", () => ({ + findRuleFilesRecursive, + safeRealpathSync: (filePath: string) => filePath, + })); + + const { createRuleScanCache } = await import(`./rule-scan-cache${createImportSuffix()}`); + const { findRuleFiles } = await import(`./rule-file-finder${createImportSuffix()}`); + const cache = createRuleScanCache(); + + // when + const firstCandidates = findRuleFiles(projectRoot, homeDir, currentFile, undefined, cache); + const firstInvocationCount = findRuleFilesRecursive.mock.calls.length; + const secondCandidates = findRuleFiles(projectRoot, homeDir, currentFile, undefined, cache); + + // then + expect(firstCandidates).toEqual(secondCandidates); + expect(firstInvocationCount).toBeGreaterThan(0); + expect(findRuleFilesRecursive).toHaveBeenCalledTimes(firstInvocationCount); + }); +}); diff --git a/src/hooks/rules-injector/rule-scan-cache.ts b/src/hooks/rules-injector/rule-scan-cache.ts new file mode 100644 index 000000000..fc8ff1a20 --- /dev/null +++ b/src/hooks/rules-injector/rule-scan-cache.ts @@ -0,0 +1,21 @@ +export type RuleScanCache = { + get: (key: string) => string[] | undefined; + set: (key: string, value: string[]) => void; + clear: () => void; +}; + +export function createRuleScanCache(): RuleScanCache { + const cache = new Map(); + + return { + get(key: string): string[] | undefined { + return cache.get(key); + }, + set(key: string, value: string[]): void { + cache.set(key, value); + }, + clear(): void { + cache.clear(); + }, + }; +} diff --git a/src/hooks/rules-injector/storage.test.ts b/src/hooks/rules-injector/storage.test.ts new file mode 100644 index 000000000..e12c4a45e --- /dev/null +++ b/src/hooks/rules-injector/storage.test.ts @@ -0,0 +1,57 @@ +import { afterEach, describe, expect, it } from "bun:test"; +import { randomUUID } from "node:crypto"; +import { existsSync } from "node:fs"; +import { join } from "node:path"; +import { RULES_INJECTOR_STORAGE } from "./constants"; +import { + clearInjectedRules, + loadInjectedRules, + saveInjectedRules, +} from "./storage"; + +const trackedSessionIDs: string[] = []; + +function createSessionID(prefix: string): string { + const sessionID = `${prefix}-${randomUUID()}`; + trackedSessionIDs.push(sessionID); + return sessionID; +} + +function getStoragePath(sessionID: string): string { + return join(RULES_INJECTOR_STORAGE, `${sessionID}.json`); +} + +afterEach(() => { + for (const sessionID of trackedSessionIDs.splice(0)) { + clearInjectedRules(sessionID); + } +}); + +describe("storage", () => { + it("reads back only the requested session data from session-scoped files", () => { + // given + const firstSessionID = createSessionID("storage-first"); + const secondSessionID = createSessionID("storage-second"); + + saveInjectedRules(firstSessionID, { + contentHashes: new Set(["hash:first"]), + realPaths: new Set(["/tmp/first-rule.md"]), + }); + saveInjectedRules(secondSessionID, { + contentHashes: new Set(["hash:second"]), + realPaths: new Set(["/tmp/second-rule.md"]), + }); + + // when + const firstLoaded = loadInjectedRules(firstSessionID); + const secondLoaded = loadInjectedRules(secondSessionID); + + // then + expect(existsSync(getStoragePath(firstSessionID))).toBe(true); + expect(existsSync(getStoragePath(secondSessionID))).toBe(true); + expect([...firstLoaded.contentHashes]).toEqual(["hash:first"]); + expect([...firstLoaded.realPaths]).toEqual(["/tmp/first-rule.md"]); + expect([...secondLoaded.contentHashes]).toEqual(["hash:second"]); + expect([...secondLoaded.realPaths]).toEqual(["/tmp/second-rule.md"]); + }); +}); diff --git a/src/hooks/runtime-fallback/dispose.test.ts b/src/hooks/runtime-fallback/dispose.test.ts index a5cb46ef7..643b7c8fb 100644 --- a/src/hooks/runtime-fallback/dispose.test.ts +++ b/src/hooks/runtime-fallback/dispose.test.ts @@ -107,9 +107,10 @@ describe("createRuntimeFallbackHook dispose", () => { globalThis.clearTimeout = originalClearTimeout }) - test("#given runtime-fallback hook created #when dispose() is called #then cleanup interval is cleared", () => { + test("#given runtime-fallback hook handles its first event #when dispose() is called #then cleanup interval is cleared", async () => { // given const hook = createRuntimeFallbackHook(createMockContext(), { pluginConfig: {} }) + await hook.event({ event: { type: "session.created", properties: {} } }) // when hook.dispose?.() @@ -125,10 +126,10 @@ describe("createRuntimeFallbackHook dispose", () => { const fallbackTimeout = setTimeout(() => {}, 60_000) capturedDeps?.sessionStates.set("session-1", { - originalModel: "anthropic/claude-opus-4-6", + originalModel: "anthropic/claude-opus-4-7", currentModel: "openai/gpt-5.4", fallbackIndex: 1, - failedModels: new Map([["anthropic/claude-opus-4-6", 1]]), + failedModels: new Map([["anthropic/claude-opus-4-7", 1]]), attemptCount: 1, }) capturedDeps?.sessionLastAccess.set("session-1", Date.now()) diff --git a/src/hooks/runtime-fallback/error-classifier.test.ts b/src/hooks/runtime-fallback/error-classifier.test.ts index 958babe5f..c9eef6e06 100644 --- a/src/hooks/runtime-fallback/error-classifier.test.ts +++ b/src/hooks/runtime-fallback/error-classifier.test.ts @@ -7,7 +7,7 @@ describe("runtime-fallback error classifier", () => { //#given const info = { 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 @@ -21,7 +21,7 @@ describe("runtime-fallback error classifier", () => { //#given const info = { 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 @@ -49,7 +49,7 @@ describe("runtime-fallback error classifier", () => { //#given const error = { 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 @@ -65,8 +65,8 @@ describe("runtime-fallback error classifier", () => { name: "ProviderModelNotFoundError", data: { providerID: "anthropic", - modelID: "claude-opus-4-6", - message: "Model not found: anthropic/claude-opus-4-6.", + modelID: "claude-opus-4-7", + message: "Model not found: anthropic/claude-opus-4-7.", }, } diff --git a/src/hooks/runtime-fallback/fallback-models.test.ts b/src/hooks/runtime-fallback/fallback-models.test.ts index 7cf3f8e32..ebfa8fbc9 100644 --- a/src/hooks/runtime-fallback/fallback-models.test.ts +++ b/src/hooks/runtime-fallback/fallback-models.test.ts @@ -15,7 +15,7 @@ describe("runtime-fallback fallback-models", () => { const pluginConfig = { categories: { 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 @@ -24,7 +24,7 @@ describe("runtime-fallback fallback-models", () => { const result = getFallbackModelsForSession(sessionID, undefined, pluginConfig) //#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", () => { @@ -32,7 +32,7 @@ describe("runtime-fallback fallback-models", () => { const pluginConfig = { agents: { 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 @@ -41,7 +41,7 @@ describe("runtime-fallback fallback-models", () => { const result = getFallbackModelsForSession("ses_runtime_fallback_agent", "oracle", pluginConfig) //#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", () => { @@ -52,7 +52,7 @@ describe("runtime-fallback fallback-models", () => { fallback_models: ["quotio/gpt-5.2", "quotio/glm-5", "quotio/kimi-k2.5"], }, 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 diff --git a/src/hooks/runtime-fallback/hook-dispose-cleanup.test.ts b/src/hooks/runtime-fallback/hook-dispose-cleanup.test.ts index 912100011..c008649bd 100644 --- a/src/hooks/runtime-fallback/hook-dispose-cleanup.test.ts +++ b/src/hooks/runtime-fallback/hook-dispose-cleanup.test.ts @@ -51,7 +51,7 @@ describe("createRuntimeFallbackHook dispose retry-key cleanup", () => { await hook.event({ event: { 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: { type: "retry", 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({ event: { 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) diff --git a/src/hooks/runtime-fallback/hook.init.test.ts b/src/hooks/runtime-fallback/hook.init.test.ts new file mode 100644 index 000000000..06a7e658c --- /dev/null +++ b/src/hooks/runtime-fallback/hook.init.test.ts @@ -0,0 +1,124 @@ +import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test" +import type { OhMyOpenCodeConfig } from "../../config" +import type { HookDeps, RuntimeFallbackInterval, RuntimeFallbackPluginInput } from "./types" + +type RuntimeFallbackModule = typeof import("./hook") + +const loadPluginConfigMock = mock(() => ({} satisfies OhMyOpenCodeConfig)) +const createAutoRetryHelpersMock = mock((_deps: HookDeps) => { + void _deps + + return { + abortSessionRequest: async () => {}, + clearSessionFallbackTimeout: () => {}, + scheduleSessionFallbackTimeout: () => {}, + autoRetryWithFallback: async () => {}, + resolveAgentForSessionFromContext: async () => undefined, + cleanupStaleSessions: () => {}, + } +}) +const createEventHandlerMock = mock(() => async () => {}) +const createMessageUpdateHandlerMock = mock(() => async () => {}) +const createChatMessageHandlerMock = mock(() => async () => {}) + +function registerModuleMocks(): void { + mock.module("../../plugin-config", () => ({ + loadPluginConfig: loadPluginConfigMock, + })) + + mock.module("./auto-retry", () => ({ + createAutoRetryHelpers: createAutoRetryHelpersMock, + })) + + mock.module("./event-handler", () => ({ + createEventHandler: createEventHandlerMock, + })) + + mock.module("./message-update-handler", () => ({ + createMessageUpdateHandler: createMessageUpdateHandlerMock, + })) + + mock.module("./chat-message-handler", () => ({ + createChatMessageHandler: createChatMessageHandlerMock, + })) +} + +function createMockContext(): RuntimeFallbackPluginInput { + return { + client: { + session: { + abort: async () => ({}), + messages: async () => ({}), + promptAsync: async () => ({}), + }, + tui: { + showToast: async () => ({}), + }, + }, + directory: "/test", + } +} + +function createMockInterval(): RuntimeFallbackInterval { + return { + unref: () => {}, + } +} + +describe("createRuntimeFallbackHook initialization", () => { + const originalSetInterval = globalThis.setInterval + let setIntervalCalls = 0 + let createRuntimeFallbackHook: RuntimeFallbackModule["createRuntimeFallbackHook"] + + beforeEach(async () => { + mock.restore() + registerModuleMocks() + loadPluginConfigMock.mockClear() + createAutoRetryHelpersMock.mockClear() + createEventHandlerMock.mockClear() + createMessageUpdateHandlerMock.mockClear() + createChatMessageHandlerMock.mockClear() + setIntervalCalls = 0 + + globalThis.setInterval = ((callback: Parameters[0], delay?: number) => { + void callback + void delay + setIntervalCalls += 1 + return createMockInterval() as ReturnType + }) as typeof globalThis.setInterval + + const cacheBuster = `${Date.now()}-${Math.random()}` + const runtimeFallbackModule: RuntimeFallbackModule = await import(`./hook?test=${cacheBuster}`) + createRuntimeFallbackHook = runtimeFallbackModule.createRuntimeFallbackHook + }) + + afterEach(() => { + globalThis.setInterval = originalSetInterval + mock.restore() + }) + + test("#given injected pluginConfig #when the hook factory runs #then loadPluginConfig is not called", () => { + // given + const pluginConfig = {} satisfies OhMyOpenCodeConfig + + // when + createRuntimeFallbackHook(createMockContext(), { pluginConfig }) + + // then + expect(loadPluginConfigMock).not.toHaveBeenCalled() + }) + + test("#given a fresh hook #when the first event arrives #then cleanup interval starts only once", async () => { + // given + const hook = createRuntimeFallbackHook(createMockContext(), { pluginConfig: {} }) + + // when + expect(setIntervalCalls).toBe(0) + await hook.event({ event: { type: "session.created", properties: {} } }) + expect(setIntervalCalls).toBe(1) + await hook.event({ event: { type: "session.error", properties: {} } }) + + // then + expect(setIntervalCalls).toBe(1) + }) +}) diff --git a/src/hooks/runtime-fallback/hook.ts b/src/hooks/runtime-fallback/hook.ts index 2a13d507e..f3509ab0a 100644 --- a/src/hooks/runtime-fallback/hook.ts +++ b/src/hooks/runtime-fallback/hook.ts @@ -1,7 +1,5 @@ import type { HookDeps, RuntimeFallbackHook, RuntimeFallbackInterval, RuntimeFallbackOptions, RuntimeFallbackPluginInput, RuntimeFallbackTimeout } from "./types" -import { DEFAULT_CONFIG, HOOK_NAME } from "./constants" -import { log } from "../../shared/logger" -import { loadPluginConfig } from "../../plugin-config" +import { DEFAULT_CONFIG } from "./constants" import { createAutoRetryHelpers } from "./auto-retry" import { createEventHandler } from "./event-handler" import { createMessageUpdateHandler } from "./message-update-handler" @@ -24,20 +22,11 @@ export function createRuntimeFallbackHook( notify_on_fallback: options?.config?.notify_on_fallback ?? DEFAULT_CONFIG.notify_on_fallback, } - let pluginConfig = options?.pluginConfig - if (!pluginConfig) { - try { - pluginConfig = loadPluginConfig(ctx.directory, ctx) - } catch { - log(`[${HOOK_NAME}] Plugin config not available`) - } - } - const deps: HookDeps = { ctx, config, options, - pluginConfig, + pluginConfig: options?.pluginConfig, sessionStates: new Map(), sessionLastAccess: new Map(), sessionRetryInFlight: new Set(), @@ -51,10 +40,23 @@ export function createRuntimeFallbackHook( const messageUpdateHandler = createMessageUpdateHandler(deps, helpers) const chatMessageHandler = createChatMessageHandler(deps) - const cleanupInterval = setInterval(helpers.cleanupStaleSessions, 5 * 60 * 1000) - cleanupInterval.unref() + let cleanupInterval: RuntimeFallbackInterval | null = null + let intervalStarted = false + + const ensureInterval = (): void => { + if (intervalStarted) return + + intervalStarted = true + cleanupInterval = setInterval(helpers.cleanupStaleSessions, 5 * 60 * 1000) + + if (typeof cleanupInterval.unref === "function") { + cleanupInterval.unref() + } + } const eventHandler = async ({ event }: { event: { type: string; properties?: unknown } }) => { + ensureInterval() + if (event.type === "message.updated") { if (!config.enabled) return const props = event.properties as Record | undefined @@ -65,7 +67,9 @@ export function createRuntimeFallbackHook( } const dispose = () => { - clearInterval(cleanupInterval) + if (cleanupInterval) { + clearInterval(cleanupInterval) + } for (const fallbackTimeout of deps.sessionFallbackTimeouts.values()) { clearTimeout(fallbackTimeout) diff --git a/src/hooks/runtime-fallback/index.test.ts b/src/hooks/runtime-fallback/index.test.ts index 8c9ad6aa4..76e29c2d3 100644 --- a/src/hooks/runtime-fallback/index.test.ts +++ b/src/hooks/runtime-fallback/index.test.ts @@ -329,7 +329,7 @@ describe("runtime-fallback", () => { const hook = createRuntimeFallbackHook(createMockPluginInput(), { config: createMockConfig({ notify_on_fallback: false }), pluginConfig: createMockPluginConfigWithCategoryFallback([ - "anthropic/claude-opus-4.6", + "anthropic/claude-opus-4.7", "openai/gpt-5.4", ]), }) @@ -365,14 +365,14 @@ describe("runtime-fallback", () => { type: "session.error", properties: { 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")) 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( (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(), { config: createMockConfig({ notify_on_fallback: false }), pluginConfig: createMockPluginConfigWithCategoryFallback([ - "anthropic/claude-opus-4.6", + "anthropic/claude-opus-4.7", "openai/gpt-5.4", ]), }) @@ -421,8 +421,8 @@ describe("runtime-fallback", () => { name: "ProviderModelNotFoundError", data: { providerID: "anthropic", - modelID: "claude-opus-4.6", - message: "Model not found: anthropic/claude-opus-4.6.", + modelID: "claude-opus-4.7", + 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")) 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 () => { @@ -500,7 +500,7 @@ describe("runtime-fallback", () => { await hook.event({ event: { 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: { sessionID, role: "assistant", - model: "github-copilot/claude-opus-4.6", + model: "github-copilot/claude-opus-4.7", status: "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")) 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 () => { const hook = createRuntimeFallbackHook(createMockPluginInput(), { 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" @@ -562,7 +562,7 @@ describe("runtime-fallback", () => { const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback")) 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 () => { @@ -577,7 +577,7 @@ describe("runtime-fallback", () => { await hook.event({ event: { 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: { sessionID, role: "assistant", - model: "quotio/claude-opus-4-6", + model: "quotio/claude-opus-4-7", }, parts: [ { @@ -605,7 +605,7 @@ describe("runtime-fallback", () => { const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback")) 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 () => { @@ -620,7 +620,7 @@ describe("runtime-fallback", () => { await hook.event({ event: { 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: { sessionID, role: "assistant", - model: "quotio/claude-opus-4-6", + model: "quotio/claude-opus-4-7", parts: [ { type: "text", @@ -648,7 +648,7 @@ describe("runtime-fallback", () => { const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback")) 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 () => { @@ -682,7 +682,7 @@ describe("runtime-fallback", () => { await hook.event({ event: { 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", next: 476, 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")) 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) }) @@ -741,7 +741,7 @@ describe("runtime-fallback", () => { await hook.event({ event: { 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", next: 476, 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", next: 475, 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 () => { const hook = createRuntimeFallbackHook(createMockPluginInput(), { 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" @@ -1161,8 +1161,8 @@ describe("runtime-fallback", () => { { config: createMockConfig({ notify_on_fallback: false }), pluginConfig: createMockPluginConfigWithCategoryFallback([ - "github-copilot/claude-opus-4.6", - "anthropic/claude-opus-4-6", + "github-copilot/claude-opus-4.7", + "anthropic/claude-opus-4-7", "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.", }, }, - 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 }), pluginConfig: createMockPluginConfigWithCategoryFallback([ - "github-copilot/claude-opus-4.6", - "anthropic/claude-opus-4-6", + "github-copilot/claude-opus-4.7", + "anthropic/claude-opus-4-7", "openai/gpt-5.4", ]), } @@ -1294,7 +1294,7 @@ describe("runtime-fallback", () => { info: { sessionID, role: "assistant", - model: "github-copilot/claude-opus-4.6", + model: "github-copilot/claude-opus-4.7", status: "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[0]).toBe("github-copilot/claude-opus-4.6") - expect(retriedModels[1]).toBe("anthropic/claude-opus-4-6") + expect(retriedModels[0]).toBe("github-copilot/claude-opus-4.7") + expect(retriedModels[1]).toBe("anthropic/claude-opus-4-7") void sessionErrorPromise }) @@ -1335,8 +1335,8 @@ describe("runtime-fallback", () => { { config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), pluginConfig: createMockPluginConfigWithCategoryFallback([ - "github-copilot/claude-opus-4.6", - "anthropic/claude-opus-4-6", + "github-copilot/claude-opus-4.7", + "anthropic/claude-opus-4-7", "openai/gpt-5.4", ]), session_timeout_ms: 20, @@ -1372,8 +1372,8 @@ describe("runtime-fallback", () => { await new Promise((resolve) => setTimeout(resolve, 50)) - expect(retriedModels).toContain("github-copilot/claude-opus-4.6") - expect(retriedModels).toContain("anthropic/claude-opus-4-6") + expect(retriedModels).toContain("github-copilot/claude-opus-4.7") + expect(retriedModels).toContain("anthropic/claude-opus-4-7") expect(abortCalls.some((call) => call.path?.id === sessionID)).toBe(true) 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 }), pluginConfig: createMockPluginConfigWithCategoryFallback([ - "github-copilot/claude-opus-4.6", - "anthropic/claude-opus-4-6", + "github-copilot/claude-opus-4.7", + "anthropic/claude-opus-4-7", "openai/gpt-5.4", ]), session_timeout_ms: 20, @@ -1443,15 +1443,15 @@ describe("runtime-fallback", () => { await hook["chat.message"]?.( { sessionID, - model: { providerID: "github-copilot", modelID: "claude-opus-4.6" }, + model: { providerID: "github-copilot", modelID: "claude-opus-4.7" }, }, output ) await new Promise((resolve) => setTimeout(resolve, 50)) - expect(retriedModels).toContain("github-copilot/claude-opus-4.6") - expect(retriedModels).toContain("anthropic/claude-opus-4-6") + expect(retriedModels).toContain("github-copilot/claude-opus-4.7") + expect(retriedModels).toContain("anthropic/claude-opus-4-7") }) 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 }), pluginConfig: createMockPluginConfigWithCategoryFallback([ - "github-copilot/claude-opus-4.6", - "anthropic/claude-opus-4-6", + "github-copilot/claude-opus-4.7", + "anthropic/claude-opus-4-7", "openai/gpt-5.4", ]), session_timeout_ms: 20, @@ -1524,8 +1524,8 @@ describe("runtime-fallback", () => { await new Promise((resolve) => setTimeout(resolve, 50)) expect(abortCalls.some((call) => call.path?.id === sessionID)).toBe(true) - expect(retriedModels).toContain("github-copilot/claude-opus-4.6") - expect(retriedModels).toContain("anthropic/claude-opus-4-6") + expect(retriedModels).toContain("github-copilot/claude-opus-4.7") + expect(retriedModels).toContain("anthropic/claude-opus-4-7") void sessionErrorPromise }) @@ -1551,8 +1551,8 @@ describe("runtime-fallback", () => { { config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), pluginConfig: createMockPluginConfigWithCategoryFallback([ - "github-copilot/claude-opus-4.6", - "anthropic/claude-opus-4-6", + "github-copilot/claude-opus-4.7", + "anthropic/claude-opus-4-7", "openai/gpt-5.4", ]), 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({ event: { @@ -1624,9 +1624,9 @@ describe("runtime-fallback", () => { { config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), pluginConfig: createMockPluginConfigWithCategoryFallback([ - "github-copilot/claude-opus-4.6", + "github-copilot/claude-opus-4.7", "openai/gpt-5.3-codex", - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", ]), 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({ event: { @@ -1695,7 +1695,7 @@ describe("runtime-fallback", () => { 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 () => { @@ -1719,9 +1719,9 @@ describe("runtime-fallback", () => { { config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), pluginConfig: createMockPluginConfigWithCategoryFallback([ - "github-copilot/claude-opus-4.6", + "github-copilot/claude-opus-4.7", "openai/gpt-5.3-codex", - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", ]), 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({ event: { @@ -1796,7 +1796,7 @@ describe("runtime-fallback", () => { config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), pluginConfig: createMockPluginConfigWithCategoryFallback([ "openai/gpt-5.3-codex", - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", ]), session_timeout_ms: 20, } @@ -1846,7 +1846,7 @@ describe("runtime-fallback", () => { 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 () => { @@ -1870,9 +1870,9 @@ describe("runtime-fallback", () => { { config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), pluginConfig: createMockPluginConfigWithCategoryFallback([ - "github-copilot/claude-opus-4.6", + "github-copilot/claude-opus-4.7", "openai/gpt-5.3-codex", - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", ]), 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({ event: { @@ -1914,7 +1914,7 @@ describe("runtime-fallback", () => { info: { sessionID, 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 }), pluginConfig: createMockPluginConfigWithCategoryFallback([ - "github-copilot/claude-opus-4.6", + "github-copilot/claude-opus-4.7", "openai/gpt-5.3-codex", - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", ]), 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({ event: { @@ -2022,9 +2022,9 @@ describe("runtime-fallback", () => { { config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), pluginConfig: createMockPluginConfigWithCategoryFallback([ - "github-copilot/claude-opus-4.6", + "github-copilot/claude-opus-4.7", "openai/gpt-5.3-codex", - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", ]), 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({ event: { @@ -2145,7 +2145,7 @@ describe("runtime-fallback", () => { }), { 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 () => { @@ -2452,7 +2452,7 @@ describe("runtime-fallback", () => { }), { 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" @@ -2462,7 +2462,7 @@ describe("runtime-fallback", () => { type: "session.error", properties: { sessionID, - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", error: { statusCode: 503, message: "Service unavailable" }, agent: "prometheus", }, @@ -2472,7 +2472,7 @@ describe("runtime-fallback", () => { expect(promptCalls.length).toBe(1) const callBody = promptCalls[0]?.body as Record 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" }) }) }) diff --git a/src/hooks/runtime-fallback/provider-matrix.test.ts b/src/hooks/runtime-fallback/provider-matrix.test.ts index d94986e78..727b2967d 100644 --- a/src/hooks/runtime-fallback/provider-matrix.test.ts +++ b/src/hooks/runtime-fallback/provider-matrix.test.ts @@ -92,7 +92,7 @@ describe("runtime-fallback provider matrix quota tests", () => { //#given const error = { 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", } diff --git a/src/hooks/runtime-fallback/retry-model-payload.ts b/src/hooks/runtime-fallback/retry-model-payload.ts index 0c9ed0c9a..d5f59b74c 100644 --- a/src/hooks/runtime-fallback/retry-model-payload.ts +++ b/src/hooks/runtime-fallback/retry-model-payload.ts @@ -1,4 +1,4 @@ -import { parseModelString } from "../../tools/delegate-task/model-string-parser" +import { parseModelString } from "../../shared/model-string-parser" export function buildRetryModelPayload( model: string, diff --git a/src/hooks/runtime-fallback/session-status-handler.test.ts b/src/hooks/runtime-fallback/session-status-handler.test.ts index 42316a6ec..6a7dee7cf 100644 --- a/src/hooks/runtime-fallback/session-status-handler.test.ts +++ b/src/hooks/runtime-fallback/session-status-handler.test.ts @@ -74,12 +74,12 @@ describe("createSessionStatusHandler", () => { const deps = createDeps() const abortCalls: 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.fallbackIndex = 0 state.attemptCount = 1 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) const handler = createSessionStatusHandler(deps, createHelpers(abortCalls, retryCalls), deps.sessionStatusRetryKeys) diff --git a/src/hooks/session-notification-event-properties.ts b/src/hooks/session-notification-event-properties.ts new file mode 100644 index 000000000..b51edf81b --- /dev/null +++ b/src/hooks/session-notification-event-properties.ts @@ -0,0 +1,51 @@ +type EventProperties = Record | undefined + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null +} + +function getEventInfo(properties: EventProperties): Record | undefined { + const info = properties?.info + return isRecord(info) ? info : undefined +} + +export function getSessionID(properties: EventProperties): string | undefined { + const sessionID = properties?.sessionID + if (typeof sessionID === "string" && sessionID.length > 0) return sessionID + + const sessionId = properties?.sessionId + if (typeof sessionId === "string" && sessionId.length > 0) return sessionId + + const info = getEventInfo(properties) + const infoSessionID = info?.sessionID + if (typeof infoSessionID === "string" && infoSessionID.length > 0) return infoSessionID + + const infoSessionId = info?.sessionId + if (typeof infoSessionId === "string" && infoSessionId.length > 0) return infoSessionId + + return undefined +} + +export function getEventToolName(properties: EventProperties): string | undefined { + const tool = properties?.tool + if (typeof tool === "string" && tool.length > 0) return tool + + const name = properties?.name + if (typeof name === "string" && name.length > 0) return name + + return undefined +} + +export function getQuestionText(properties: EventProperties): string { + const args = properties?.args + if (!isRecord(args)) return "" + + const questions = args.questions + if (!Array.isArray(questions) || questions.length === 0) return "" + + const firstQuestion = questions[0] + if (!isRecord(firstQuestion)) return "" + + const questionText = firstQuestion.question + return typeof questionText === "string" ? questionText : "" +} diff --git a/src/hooks/session-notification-init.ts b/src/hooks/session-notification-init.ts new file mode 100644 index 000000000..3dab42ea6 --- /dev/null +++ b/src/hooks/session-notification-init.ts @@ -0,0 +1,31 @@ +import type { Platform } from "./session-notification-sender" +import * as sessionNotificationSender from "./session-notification-sender" +import { startBackgroundCheck } from "./session-notification-utils" + +export function createSessionNotificationInit() { + let platform: Platform | null = null + let defaultSoundPath: string | null = null + let started = false + + function initialize(): { platform: Platform; defaultSoundPath: string } { + if (!platform) { + platform = sessionNotificationSender.detectPlatform() + } + if (!defaultSoundPath) { + defaultSoundPath = sessionNotificationSender.getDefaultSoundPath(platform) + } + if (!started) { + startBackgroundCheck(platform) + started = true + } + + return { + platform, + defaultSoundPath, + } + } + + return { + initialize, + } +} diff --git a/src/hooks/session-notification-input-needed.test.ts b/src/hooks/session-notification-input-needed.test.ts index ee1614b88..f85d9154d 100644 --- a/src/hooks/session-notification-input-needed.test.ts +++ b/src/hooks/session-notification-input-needed.test.ts @@ -93,6 +93,53 @@ describe("session-notification input-needed events", () => { expect(notificationCalls).toHaveLength(1) expect(notificationCalls[0]).toContain("Agent needs permission to continue") }) + + test("lazily detects platform and starts background checks on first idle event", async () => { + const sessionID = "main-idle" + setMainSession(sessionID) + + const detectPlatformSpy = spyOn(sender, "detectPlatform") + detectPlatformSpy.mockReturnValue("darwin") + + const getDefaultSoundPathSpy = spyOn(sender, "getDefaultSoundPath") + getDefaultSoundPathSpy.mockReturnValue("/System/Library/Sounds/Glass.aiff") + + const startBackgroundCheckSpy = spyOn(utils, "startBackgroundCheck") + startBackgroundCheckSpy.mockImplementation(() => {}) + + // given + const hook = createSessionNotification(createMockPluginInput(), { enforceMainSessionFilter: false }) + + // when + await hook({ + event: { + type: "session.idle", + properties: { + sessionID, + }, + }, + }) + + // then + expect(detectPlatformSpy).toHaveBeenCalledTimes(1) + expect(getDefaultSoundPathSpy).toHaveBeenCalledTimes(1) + expect(startBackgroundCheckSpy).toHaveBeenCalledTimes(1) + + // when + await hook({ + event: { + type: "session.idle", + properties: { + sessionID, + }, + }, + }) + + // then + expect(detectPlatformSpy).toHaveBeenCalledTimes(1) + expect(getDefaultSoundPathSpy).toHaveBeenCalledTimes(1) + expect(startBackgroundCheckSpy).toHaveBeenCalledTimes(1) + }) }) export {} diff --git a/src/hooks/session-notification.ts b/src/hooks/session-notification.ts index d54d38c61..f9a40f56d 100644 --- a/src/hooks/session-notification.ts +++ b/src/hooks/session-notification.ts @@ -1,15 +1,12 @@ import type { PluginInput } from "@opencode-ai/plugin" import { subagentSessions, getMainSessionID } from "../features/claude-code-session-state" -import { - startBackgroundCheck, -} from "./session-notification-utils" import { buildReadyNotificationContent } from "./session-notification-content" -import { - type Platform, -} from "./session-notification-sender" +import { type Platform } from "./session-notification-sender" import * as sessionNotificationSender from "./session-notification-sender" +import { getEventToolName, getQuestionText, getSessionID } from "./session-notification-event-properties" import { hasIncompleteTodos } from "./session-todo-status" import { createIdleNotificationScheduler } from "./session-notification-scheduler" +import { createSessionNotificationInit } from "./session-notification-init" interface SessionNotificationConfig { title?: string @@ -28,22 +25,15 @@ interface SessionNotificationConfig { /** Grace period in ms to ignore late-arriving activity events after scheduling (default: 100) */ activityGracePeriodMs?: number } -export function createSessionNotification( - ctx: PluginInput, - config: SessionNotificationConfig = {} -) { - const currentPlatform: Platform = sessionNotificationSender.detectPlatform() - const defaultSoundPath = sessionNotificationSender.getDefaultSoundPath(currentPlatform) - - startBackgroundCheck(currentPlatform) +export function createSessionNotification(ctx: PluginInput, config: SessionNotificationConfig = {}) { const mergedConfig = { title: "OpenCode", message: "Agent is ready for input", questionMessage: "Agent is asking a question", permissionMessage: "Agent needs permission to continue", playSound: false, - soundPath: defaultSoundPath, + soundPath: "", idleConfirmationDelay: 1500, skipIfIncompleteTodos: true, maxTrackedSessions: 100, @@ -51,22 +41,18 @@ export function createSessionNotification( ...config, } + const sessionNotificationInit = createSessionNotificationInit() + let currentPlatform: Platform | null = null + let defaultSoundPath = mergedConfig.soundPath + const scheduler = createIdleNotificationScheduler({ ctx, - platform: currentPlatform, + platform: "unsupported", config: mergedConfig, hasIncompleteTodos, send: async (hookCtx, platform, sessionID) => { - if ( - typeof hookCtx.client.session.get !== "function" - && typeof hookCtx.client.session.messages !== "function" - ) { - await sessionNotificationSender.sendSessionNotification( - hookCtx, - platform, - mergedConfig.title, - mergedConfig.message, - ) + if (typeof hookCtx.client.session.get !== "function" && typeof hookCtx.client.session.messages !== "function") { + await sessionNotificationSender.sendSessionNotification(hookCtx, platform, mergedConfig.title, mergedConfig.message) return } @@ -85,21 +71,13 @@ export function createSessionNotification( const PERMISSION_EVENTS = new Set(["permission.ask", "permission.asked", "permission.updated", "permission.requested"]) const PERMISSION_HINT_PATTERN = /\b(permission|approve|approval|allow|deny|consent)\b/i - const getSessionID = (properties: Record | undefined): string | undefined => { - const sessionID = properties?.sessionID - if (typeof sessionID === "string" && sessionID.length > 0) return sessionID + const ensureNotificationPlatform = (): Platform => { + if (currentPlatform) return currentPlatform - const sessionId = properties?.sessionId - if (typeof sessionId === "string" && sessionId.length > 0) return sessionId - - const info = properties?.info as Record | undefined - const infoSessionID = info?.sessionID - if (typeof infoSessionID === "string" && infoSessionID.length > 0) return infoSessionID - - const infoSessionId = info?.sessionId - if (typeof infoSessionId === "string" && infoSessionId.length > 0) return infoSessionId - - return undefined + const initialized = sessionNotificationInit.initialize() + currentPlatform = initialized.platform + defaultSoundPath = initialized.defaultSoundPath || mergedConfig.soundPath + return currentPlatform } const shouldNotifyForSession = (sessionID: string): boolean => { @@ -113,37 +91,13 @@ export function createSessionNotification( return true } - const getEventToolName = (properties: Record | undefined): string | undefined => { - const tool = properties?.tool - if (typeof tool === "string" && tool.length > 0) return tool - - const name = properties?.name - if (typeof name === "string" && name.length > 0) return name - - return undefined - } - - const getQuestionText = (properties: Record | undefined): string => { - const args = properties?.args as Record | undefined - const questions = args?.questions - if (!Array.isArray(questions) || questions.length === 0) return "" - - const firstQuestion = questions[0] as Record | undefined - const questionText = firstQuestion?.question - return typeof questionText === "string" ? questionText : "" - } - return async ({ event }: { event: { type: string; properties?: unknown } }) => { - if (currentPlatform === "unsupported") return - const props = event.properties as Record | undefined if (event.type === "session.created") { const info = props?.info as Record | undefined const sessionID = info?.id as string | undefined - if (sessionID) { - scheduler.markSessionActivity(sessionID) - } + if (sessionID) scheduler.markSessionActivity(sessionID) return } @@ -151,6 +105,8 @@ export function createSessionNotification( const sessionID = getSessionID(props) if (!sessionID) return + const platform = ensureNotificationPlatform() + if (platform === "unsupported") return if (!shouldNotifyForSession(sessionID)) return scheduler.scheduleIdleNotification(sessionID) @@ -160,26 +116,22 @@ export function createSessionNotification( if (event.type === "message.updated") { const info = props?.info as Record | undefined const sessionID = getSessionID({ ...props, info }) - if (sessionID) { - scheduler.markSessionActivity(sessionID) - } + if (sessionID) scheduler.markSessionActivity(sessionID) return } if (PERMISSION_EVENTS.has(event.type)) { const sessionID = getSessionID(props) if (!sessionID) return + + const platform = ensureNotificationPlatform() + if (platform === "unsupported") return if (!shouldNotifyForSession(sessionID)) return scheduler.markSessionActivity(sessionID) - await sessionNotificationSender.sendSessionNotification( - ctx, - currentPlatform, - mergedConfig.title, - mergedConfig.permissionMessage, - ) - if (mergedConfig.playSound && mergedConfig.soundPath) { - await sessionNotificationSender.playSessionNotificationSound(ctx, currentPlatform, mergedConfig.soundPath) + await sessionNotificationSender.sendSessionNotification(ctx, platform, mergedConfig.title, mergedConfig.permissionMessage) + if (mergedConfig.playSound && defaultSoundPath) { + await sessionNotificationSender.playSessionNotificationSound(ctx, platform, defaultSoundPath) } return } @@ -192,16 +144,16 @@ export function createSessionNotification( if (event.type === "tool.execute.before") { const toolName = getEventToolName(props)?.toLowerCase() if (toolName && QUESTION_TOOLS.has(toolName)) { + const platform = ensureNotificationPlatform() + if (platform === "unsupported") return if (!shouldNotifyForSession(sessionID)) return const questionText = getQuestionText(props) - const message = PERMISSION_HINT_PATTERN.test(questionText) - ? mergedConfig.permissionMessage - : mergedConfig.questionMessage + const message = PERMISSION_HINT_PATTERN.test(questionText) ? mergedConfig.permissionMessage : mergedConfig.questionMessage - await sessionNotificationSender.sendSessionNotification(ctx, currentPlatform, mergedConfig.title, message) - if (mergedConfig.playSound && mergedConfig.soundPath) { - await sessionNotificationSender.playSessionNotificationSound(ctx, currentPlatform, mergedConfig.soundPath) + await sessionNotificationSender.sendSessionNotification(ctx, platform, mergedConfig.title, message) + if (mergedConfig.playSound && defaultSoundPath) { + await sessionNotificationSender.playSessionNotificationSound(ctx, platform, defaultSoundPath) } } } @@ -211,9 +163,7 @@ export function createSessionNotification( if (event.type === "session.deleted") { const sessionInfo = props?.info as { id?: string } | undefined - if (sessionInfo?.id) { - scheduler.deleteSession(sessionInfo.id) - } + if (sessionInfo?.id) scheduler.deleteSession(sessionInfo.id) } } } diff --git a/src/hooks/task-resume-info/hook.ts b/src/hooks/task-resume-info/hook.ts index 1774aef6a..7fda3d640 100644 --- a/src/hooks/task-resume-info/hook.ts +++ b/src/hooks/task-resume-info/hook.ts @@ -1,20 +1,7 @@ +import { extractTaskLink } from "../../features/tool-metadata-store" + const TARGET_TOOLS = ["task", "Task", "task_tool", "call_omo_agent"] -const SESSION_ID_PATTERNS = [ - /Session ID: (ses_[a-zA-Z0-9_-]+)/, - /session_id: (ses_[a-zA-Z0-9_-]+)/, - /\s*session_id: (ses_[a-zA-Z0-9_-]+)/, - /sessionId: (ses_[a-zA-Z0-9_-]+)/, -] - -function extractSessionId(output: string): string | null { - for (const pattern of SESSION_ID_PATTERNS) { - const match = output.match(pattern) - if (match) return match[1] ?? null - } - return null -} - export function createTaskResumeInfoHook() { const toolExecuteAfter = async ( input: { tool: string; sessionID: string; callID: string }, @@ -25,12 +12,13 @@ export function createTaskResumeInfoHook() { if (outputText.startsWith("Error:") || outputText.startsWith("Failed")) return if (outputText.includes("\nto continue:")) return - const sessionId = extractSessionId(outputText) - if (!sessionId) return + const link = extractTaskLink(output.metadata, outputText) + const taskId = link.taskId ?? link.sessionId + if (!taskId) return output.output = outputText.trimEnd() + - `\n\nto continue: task(session_id="${sessionId}", load_skills=[], run_in_background=false, prompt="...")` + `\n\nto continue: task(task_id="${taskId}", load_skills=[], run_in_background=false, prompt="...")` } return { diff --git a/src/hooks/task-resume-info/index.test.ts b/src/hooks/task-resume-info/index.test.ts index 2d10ef757..30708380b 100644 --- a/src/hooks/task-resume-info/index.test.ts +++ b/src/hooks/task-resume-info/index.test.ts @@ -60,6 +60,7 @@ describe("createTaskResumeInfoHook", () => { await afterHook(input, output) expect(output.output).toContain("to continue:") + expect(output.output).toContain('task(task_id="ses_abc123"') expect(output.output).toContain("ses_abc123") }) @@ -74,6 +75,25 @@ describe("createTaskResumeInfoHook", () => { await afterHook(input, output) expect(output.output).toContain("run_in_background=false") + expect(output.output).toContain('task_id="ses_abc123"') + }) + }) + }) + + describe("#given target tool with session metadata object", () => { + describe("#when output text omits session ID but metadata includes it", () => { + it("#then should append resume info from metadata", async () => { + const input = createInput("task") + const output = { + title: "task", + output: "Task completed successfully", + metadata: { sessionID: "ses_meta_123" }, + } + + await afterHook(input, output) + + expect(output.output).toContain("to continue:") + expect(output.output).toContain("ses_meta_123") }) }) }) @@ -102,7 +122,7 @@ describe("createTaskResumeInfoHook", () => { const output = { title: "task", output: - 'Done.\nSession ID: ses_abc123\nto continue: task(session_id="ses_abc123", load_skills=[], prompt="...")', + 'Done.\nSession ID: ses_abc123\nto continue: task(task_id="ses_abc123", load_skills=[], run_in_background=false, prompt="...")', metadata: {}, } diff --git a/src/hooks/think-mode/index.test.ts b/src/hooks/think-mode/index.test.ts index 34c1ba55c..f85763608 100644 --- a/src/hooks/think-mode/index.test.ts +++ b/src/hooks/think-mode/index.test.ts @@ -49,7 +49,7 @@ describe("createThinkModeHook", () => { const input = createHookInput({ sessionID, providerID: "github-copilot", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", }) const output = createHookOutput("Please think deeply about this") diff --git a/src/hooks/think-mode/switcher.test.ts b/src/hooks/think-mode/switcher.test.ts index bbe01bed6..b462e76ff 100644 --- a/src/hooks/think-mode/switcher.test.ts +++ b/src/hooks/think-mode/switcher.test.ts @@ -23,26 +23,26 @@ describe("think-mode switcher", () => { describe("getHighVariant with dots vs hyphens", () => { it("should handle dots in Claude version numbers", () => { // 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 - expect(variant).toBe("claude-opus-4-6-high") + expect(variant).toBe("claude-opus-4-7-high") }) it("should handle hyphens in Claude version numbers", () => { // 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 - 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", () => { - // given a Claude Opus 4.6 model ID - const variant = getHighVariant("claude-opus-4-6") + it("should handle claude-opus-4-7 high variant", () => { + // given a Claude Opus 4.7 model ID + const variant = getHighVariant("claude-opus-4-7") // 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", () => { @@ -73,7 +73,7 @@ describe("think-mode switcher", () => { it("should return null for already-high variants", () => { // given model IDs that are already high variants - expect(getHighVariant("claude-opus-4-6-high")).toBeNull() + expect(getHighVariant("claude-opus-4-7-high")).toBeNull() expect(getHighVariant("gpt-5-4-high")).toBeNull() expect(getHighVariant("gemini-3-1-pro-high")).toBeNull() }) @@ -89,7 +89,7 @@ describe("think-mode switcher", () => { describe("isAlreadyHighVariant", () => { it("should detect -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("gemini-3.1-pro-high")).toBe(true) }) @@ -101,8 +101,8 @@ describe("think-mode switcher", () => { it("should return false for base models", () => { // given base model IDs without -high suffix - expect(isAlreadyHighVariant("claude-opus-4-6")).toBe(false) - expect(isAlreadyHighVariant("claude-opus-4.6")).toBe(false) + expect(isAlreadyHighVariant("claude-opus-4-7")).toBe(false) + expect(isAlreadyHighVariant("claude-opus-4.7")).toBe(false) expect(isAlreadyHighVariant("gpt-5.4")).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", () => { // 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 - 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", () => { @@ -167,7 +167,7 @@ describe("think-mode switcher", () => { it("should return null for already-high prefixed models", () => { // given prefixed model IDs that are already high - expect(getHighVariant("vertex_ai/claude-opus-4-6-high")).toBeNull() + expect(getHighVariant("vertex_ai/claude-opus-4-7-high")).toBeNull() expect(getHighVariant("openai/gpt-5-4-high")).toBeNull() }) }) @@ -175,14 +175,14 @@ describe("think-mode switcher", () => { describe("isAlreadyHighVariant with prefixes", () => { it("should detect -high suffix in prefixed models", () => { // given prefixed model IDs with -high suffix - expect(isAlreadyHighVariant("vertex_ai/claude-opus-4-6-high")).toBe(true) + expect(isAlreadyHighVariant("vertex_ai/claude-opus-4-7-high")).toBe(true) expect(isAlreadyHighVariant("openai/gpt-5-4-high")).toBe(true) expect(isAlreadyHighVariant("custom/gemini-3.1-pro-high")).toBe(true) }) it("should return false for prefixed base models", () => { // given prefixed base model IDs without -high suffix - expect(isAlreadyHighVariant("vertex_ai/claude-opus-4-6")).toBe(false) + expect(isAlreadyHighVariant("vertex_ai/claude-opus-4-7")).toBe(false) expect(isAlreadyHighVariant("openai/gpt-5-4")).toBe(false) }) diff --git a/src/hooks/think-mode/switcher.ts b/src/hooks/think-mode/switcher.ts index 66aa39f5a..8be017d69 100644 --- a/src/hooks/think-mode/switcher.ts +++ b/src/hooks/think-mode/switcher.ts @@ -45,7 +45,7 @@ function extractModelPrefix(modelID: string): { prefix: string; base: string } { const HIGH_VARIANT_MAP: Record = { // Claude "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-3-1-pro": "gemini-3-1-pro-high", "gemini-3-1-pro-low": "gemini-3-1-pro-high", diff --git a/src/hooks/todo-continuation-enforcer/dispose.test.ts b/src/hooks/todo-continuation-enforcer/dispose.test.ts index 5423c8068..37971bc6d 100644 --- a/src/hooks/todo-continuation-enforcer/dispose.test.ts +++ b/src/hooks/todo-continuation-enforcer/dispose.test.ts @@ -8,6 +8,7 @@ declare module "bun:test" { import { afterAll, afterEach, describe, expect, it, mock } from "bun:test" +import type { BackgroundManager } from "../../features/background-agent" import * as actualSessionStateModule from "./session-state" import type { SessionStateStore } from "./session-state" @@ -37,6 +38,12 @@ function createMockPluginInput(): PluginInput { } as PluginInput } +function createMockBackgroundManager(): BackgroundManager { + return { + getTasksByParentSession: () => [{ status: "running" }], + } as BackgroundManager +} + function getCreatedSessionStateStore(): SessionStateStore { if (!createdSessionStateStore) { throw new Error("expected session state store to be created") @@ -68,7 +75,7 @@ describe("todo-continuation-enforcer dispose", () => { enforcer.dispose() }) - it("#given enforcer with active session states #when dispose is called #then internal session state store is shut down", () => { + it("#given enforcer with active session states #when dispose is called #then internal session state store is shut down", async () => { // given const originalClearInterval = globalThis.clearInterval const clearIntervalCalls: Array[0]> = [] @@ -78,9 +85,13 @@ describe("todo-continuation-enforcer dispose", () => { }) as typeof clearInterval try { - const enforcer = createTodoContinuationEnforcer(createMockPluginInput()) + const enforcer = createTodoContinuationEnforcer(createMockPluginInput(), { + backgroundManager: createMockBackgroundManager(), + }) const sessionStateStore = getCreatedSessionStateStore() + await enforcer.handler({ event: { type: "session.idle", properties: { sessionID: "session-1" } } }) + enforcer.markRecovering("session-1") enforcer.markRecovering("session-2") diff --git a/src/hooks/todo-continuation-enforcer/handler.ts b/src/hooks/todo-continuation-enforcer/handler.ts index 3347ee666..7136dda44 100644 --- a/src/hooks/todo-continuation-enforcer/handler.ts +++ b/src/hooks/todo-continuation-enforcer/handler.ts @@ -61,6 +61,7 @@ export function createTodoContinuationHandler(args: { const sessionID = props?.sessionID as string | undefined if (!sessionID) return + sessionStateStore.startPruneInterval() await handleSessionIdle({ ctx, sessionID, diff --git a/src/hooks/todo-continuation-enforcer/session-state.ts b/src/hooks/todo-continuation-enforcer/session-state.ts index a87472b7a..dcd88629e 100644 --- a/src/hooks/todo-continuation-enforcer/session-state.ts +++ b/src/hooks/todo-continuation-enforcer/session-state.ts @@ -31,6 +31,7 @@ export interface ContinuationProgressUpdate { export interface SessionStateStore { getState: (sessionID: string) => SessionState getExistingState: (sessionID: string) => SessionState | undefined + startPruneInterval: () => void recordActivity: (sessionID: string) => void trackContinuationProgress: ( sessionID: string, @@ -76,18 +77,26 @@ export function createSessionStateStore(): SessionStateStore { // Periodic pruning of stale session states to prevent unbounded Map growth let pruneInterval: TimerHandle | undefined - pruneInterval = setInterval(() => { - const now = Date.now() - for (const [sessionID, tracked] of sessions.entries()) { - if (now - tracked.lastAccessedAt > SESSION_STATE_TTL_MS) { - cancelCountdown(sessionID) - sessions.delete(sessionID) - } + let pruneIntervalStarted = false + + function startPruneInterval(): void { + if (pruneIntervalStarted) { + return + } + + pruneIntervalStarted = true + pruneInterval = setInterval(() => { + const now = Date.now() + for (const [sessionID, tracked] of sessions.entries()) { + if (now - tracked.lastAccessedAt > SESSION_STATE_TTL_MS) { + cancelCountdown(sessionID) + sessions.delete(sessionID) + } + } + }, SESSION_STATE_PRUNE_INTERVAL_MS) + if (typeof pruneInterval === "object" && typeof pruneInterval.unref === "function") { + pruneInterval.unref() } - }, SESSION_STATE_PRUNE_INTERVAL_MS) - // Allow process to exit naturally even if interval is running - if (typeof pruneInterval === "object" && typeof pruneInterval.unref === "function") { - pruneInterval.unref() } function getTrackedSession(sessionID: string): TrackedSessionState { @@ -272,6 +281,7 @@ export function createSessionStateStore(): SessionStateStore { return { getState, getExistingState, + startPruneInterval, recordActivity, trackContinuationProgress, resetContinuationProgress, diff --git a/src/hooks/todo-continuation-enforcer/todo-continuation-enforcer.test.ts b/src/hooks/todo-continuation-enforcer/todo-continuation-enforcer.test.ts index 9c5a35f5c..5315b0842 100644 --- a/src/hooks/todo-continuation-enforcer/todo-continuation-enforcer.test.ts +++ b/src/hooks/todo-continuation-enforcer/todo-continuation-enforcer.test.ts @@ -249,6 +249,33 @@ describe("todo-continuation-enforcer", () => { _resetForTesting() }) + test("given the first idle event, starts the prune interval lazily", async () => { + // given + const originalSetInterval = globalThis.setInterval + let setIntervalCalls = 0 + globalThis.setInterval = ((callback: TimerCallback, delay?: number, ...args: any[]) => { + setIntervalCalls += 1 + return originalSetInterval(callback, delay, ...args) + }) as typeof setInterval + + try { + const sessionID = "main-lazy-prune" + setMainSession(sessionID) + const hook = createTodoContinuationEnforcer(createMockPluginInput(), { + backgroundManager: createMockBackgroundManager(true), + }) + + // when + await hook.handler({ event: { type: "session.idle", properties: { sessionID } } }) + await hook.handler({ event: { type: "session.idle", properties: { sessionID } } }) + + // then + expect(setIntervalCalls).toBe(1) + } finally { + globalThis.setInterval = originalSetInterval + } + }) + test("should inject continuation when idle with incomplete todos", async () => { fakeTimers.restore() // given - main session with incomplete todos diff --git a/src/hooks/write-existing-file-guard/hook.ts b/src/hooks/write-existing-file-guard/hook.ts index bdaf5cad8..ab7bd9aef 100644 --- a/src/hooks/write-existing-file-guard/hook.ts +++ b/src/hooks/write-existing-file-guard/hook.ts @@ -76,7 +76,15 @@ export function isOverwriteEnabled(value: boolean | string | undefined): boolean export function createWriteExistingFileGuardHook(ctx: PluginInput): Hooks { const readPermissionsBySession = new Map>() const sessionLastAccess = new Map() - const canonicalSessionRoot = toCanonicalPath(resolveInputPath(ctx, ctx.directory)) + let canonicalSessionRoot: string | undefined + + function getCanonicalSessionRoot(): string { + if (!canonicalSessionRoot) { + canonicalSessionRoot = toCanonicalPath(resolveInputPath(ctx, ctx.directory)) + } + + return canonicalSessionRoot + } return { "tool.execute.before": async (input, output) => { @@ -86,7 +94,7 @@ export function createWriteExistingFileGuardHook(ctx: PluginInput): Hooks { output, readPermissionsBySession, sessionLastAccess, - canonicalSessionRoot, + getCanonicalSessionRoot, maxTrackedSessions: MAX_TRACKED_SESSIONS, }) }, diff --git a/src/hooks/write-existing-file-guard/lazy-canonical-path-init.test.ts b/src/hooks/write-existing-file-guard/lazy-canonical-path-init.test.ts new file mode 100644 index 000000000..0f1d4bb88 --- /dev/null +++ b/src/hooks/write-existing-file-guard/lazy-canonical-path-init.test.ts @@ -0,0 +1,65 @@ +import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test" +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { join } from "node:path" + +const realFs = await import("node:fs") + +const existsSyncMock = mock(realFs.existsSync) +const realpathNativeMock = mock(realFs.realpathSync.native) + +mock.module("fs", () => ({ + ...realFs, + existsSync: existsSyncMock, + realpathSync: { + ...realFs.realpathSync, + native: realpathNativeMock, + }, +})) + +const { createWriteExistingFileGuardHook } = await import("./index") + +describe("createWriteExistingFileGuardHook", () => { + let tempDir = "" + + beforeEach(() => { + // given + tempDir = mkdtempSync(join(tmpdir(), "write-existing-file-guard-lazy-")) + mkdirSync(tempDir, { recursive: true }) + existsSyncMock.mockClear() + realpathNativeMock.mockClear() + }) + + afterEach(() => { + rmSync(tempDir, { recursive: true, force: true }) + }) + + test("#given hook factory #when created #then defers fs canonical path calls until first tool invocation", async () => { + // given + const existingFile = join(tempDir, "existing.txt") + writeFileSync(existingFile, "content") + + // when + const hook = createWriteExistingFileGuardHook({ directory: tempDir } as never) + + // then + expect(existsSyncMock).toHaveBeenCalledTimes(0) + expect(realpathNativeMock).toHaveBeenCalledTimes(0) + + // when + await expect( + hook["tool.execute.before"]?.( + { + tool: "write", + sessionID: "ses_lazy", + callID: "call_lazy", + } as never, + { args: { filePath: existingFile, content: "updated" } } as never, + ), + ).rejects.toThrow("File already exists. Use edit tool instead.") + + // then + expect(existsSyncMock).toHaveBeenCalledTimes(3) + expect(realpathNativeMock).toHaveBeenCalledTimes(2) + }) +}) diff --git a/src/hooks/write-existing-file-guard/tool-execute-before-handler.ts b/src/hooks/write-existing-file-guard/tool-execute-before-handler.ts index 25eebbda3..848238a8a 100644 --- a/src/hooks/write-existing-file-guard/tool-execute-before-handler.ts +++ b/src/hooks/write-existing-file-guard/tool-execute-before-handler.ts @@ -90,10 +90,10 @@ export async function handleWriteExistingFileGuardToolExecuteBefore(params: { output: { args?: unknown } readPermissionsBySession: Map> sessionLastAccess: Map - canonicalSessionRoot: string + getCanonicalSessionRoot: () => string maxTrackedSessions: number }): Promise { - const { ctx, input, output, readPermissionsBySession, sessionLastAccess, canonicalSessionRoot, maxTrackedSessions } = params + const { ctx, input, output, readPermissionsBySession, sessionLastAccess, getCanonicalSessionRoot, maxTrackedSessions } = params const toolName = input.tool?.toLowerCase() if (toolName !== "write" && toolName !== "read") { return @@ -107,6 +107,7 @@ export async function handleWriteExistingFileGuardToolExecuteBefore(params: { } const resolvedPath = resolveInputPath(ctx, filePath) + const canonicalSessionRoot = getCanonicalSessionRoot() const canonicalPath = toCanonicalPath(resolvedPath) if (!isPathInsideDirectory(canonicalPath, canonicalSessionRoot)) { return diff --git a/src/hooks/zauc-mocks-hook/hook.test.ts b/src/hooks/zauc-mocks-hook/hook.test.ts index 2c291b1f0..de0e4d3d2 100644 --- a/src/hooks/zauc-mocks-hook/hook.test.ts +++ b/src/hooks/zauc-mocks-hook/hook.test.ts @@ -1,5 +1,13 @@ import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test" -import { createAutoUpdateCheckerHook } from "../auto-update-checker/hook" + +let scheduledDeferredCheck: (() => void) | null = null +mock.module("../auto-update-checker/hook/deferred-startup-check", () => ({ + scheduleDeferredStartupCheck: (runCheck: () => void) => { + scheduledDeferredCheck = runCheck + }, +})) + +const { createAutoUpdateCheckerHook } = await import("../auto-update-checker/hook") const mockShowConfigErrorsIfAny = mock(async () => {}) const mockShowModelCacheWarningIfNeeded = mock(async () => {}) @@ -38,6 +46,12 @@ function runSessionCreatedEvent( }) } +function drainDeferredCheck(): void { + const run = scheduledDeferredCheck + scheduledDeferredCheck = null + run?.() +} + beforeEach(() => { mockShowConfigErrorsIfAny.mockClear() mockShowModelCacheWarningIfNeeded.mockClear() @@ -51,6 +65,8 @@ beforeEach(() => { mockGetCachedVersion.mockReturnValue("3.6.0") mockGetLocalDevVersion.mockReturnValue(null) + + scheduledDeferredCheck = null }) afterEach(() => { @@ -108,8 +124,9 @@ describe("createAutoUpdateCheckerHook", () => { log: () => {}, }) - //#when - session.created event arrives on primary session + //#when - session.created schedules work and deferred check drains it runSessionCreatedEvent(hook) + drainDeferredCheck() await flushScheduledWork() //#then - startup checks, toast, and background check run @@ -165,9 +182,10 @@ describe("createAutoUpdateCheckerHook", () => { log: () => {}, }) - //#when - session.created event is fired twice + //#when - session.created fires twice and deferred check drains once runSessionCreatedEvent(hook) runSessionCreatedEvent(hook) + drainDeferredCheck() await flushScheduledWork() //#then - side effects execute only once @@ -195,8 +213,9 @@ describe("createAutoUpdateCheckerHook", () => { log: () => {}, }) - //#when - session.created event arrives + //#when - session.created schedules and deferred check drains runSessionCreatedEvent(hook) + drainDeferredCheck() await flushScheduledWork() //#then - local dev toast is shown and background check is skipped @@ -259,8 +278,9 @@ describe("createAutoUpdateCheckerHook", () => { log: () => {}, }) - //#when - session.created event arrives + //#when - session.created schedules and deferred check drains runSessionCreatedEvent(hook) + drainDeferredCheck() await flushScheduledWork() //#then - startup toast includes sisyphus wording diff --git a/src/index.compacting.test.ts b/src/index.compacting.test.ts new file mode 100644 index 000000000..46434d8cb --- /dev/null +++ b/src/index.compacting.test.ts @@ -0,0 +1,136 @@ +import { describe, expect, it, mock } from "bun:test" + +function createCompactingHandler(hooks: { + compactionContextInjector?: { + capture: (sessionID: string) => Promise + inject: (sessionID: string) => string + } + compactionTodoPreserver?: { capture: (sessionID: string) => Promise } + claudeCodeHooks?: { + "experimental.session.compacting"?: ( + input: { sessionID: string }, + output: { context: string[] }, + ) => Promise + } +}) { + return async ( + input: { sessionID: string }, + output: { context: string[] }, + ): Promise => { + await hooks.compactionContextInjector?.capture(input.sessionID) + await hooks.compactionTodoPreserver?.capture(input.sessionID) + await hooks.claudeCodeHooks?.["experimental.session.compacting"]?.( + input, + output, + ) + if (hooks.compactionContextInjector) { + output.context.push(hooks.compactionContextInjector.inject(input.sessionID)) + } + } +} + +describe("experimental.session.compacting handler", () => { + //#given all three hooks are present + //#when compacting handler is invoked + //#then all hooks are called in order: capture → PreCompact → contextInjector + it("calls claudeCodeHooks PreCompact alongside other hooks", async () => { + const callOrder: string[] = [] + + const handler = createCompactingHandler({ + compactionContextInjector: { + capture: mock(async () => { + callOrder.push("checkpointCapture") + }), + inject: mock((sessionID: string) => { + callOrder.push("contextInjector") + return `context-for-${sessionID}` + }), + }, + compactionTodoPreserver: { + capture: mock(async () => { + callOrder.push("capture") + }), + }, + claudeCodeHooks: { + "experimental.session.compacting": mock(async () => { + callOrder.push("preCompact") + }), + }, + }) + + const output = { context: [] as string[] } + await handler({ sessionID: "ses_test" }, output) + + expect(callOrder).toEqual([ + "checkpointCapture", + "capture", + "preCompact", + "contextInjector", + ]) + expect(output.context).toEqual(["context-for-ses_test"]) + }) + + //#given claudeCodeHooks injects context during PreCompact + //#when compacting handler is invoked + //#then injected context from PreCompact is preserved in output + it("preserves context injected by PreCompact hooks", async () => { + const handler = createCompactingHandler({ + claudeCodeHooks: { + "experimental.session.compacting": async (_input, output) => { + output.context.push("precompact-injected-context") + }, + }, + }) + + const output = { context: [] as string[] } + await handler({ sessionID: "ses_test" }, output) + + expect(output.context).toContain("precompact-injected-context") + }) + + //#given claudeCodeHooks is null (no claude code hooks configured) + //#when compacting handler is invoked + //#then handler completes without error and other hooks still run + it("handles null claudeCodeHooks gracefully", async () => { + const captureMock = mock(async () => {}) + const checkpointCaptureMock = mock(async () => {}) + const contextMock = mock(() => "injected-context") + + const handler = createCompactingHandler({ + compactionContextInjector: { + capture: checkpointCaptureMock, + inject: contextMock, + }, + compactionTodoPreserver: { capture: captureMock }, + claudeCodeHooks: undefined, + }) + + const output = { context: [] as string[] } + await handler({ sessionID: "ses_test" }, output) + + expect(checkpointCaptureMock).toHaveBeenCalledWith("ses_test") + expect(captureMock).toHaveBeenCalledWith("ses_test") + expect(contextMock).toHaveBeenCalledWith("ses_test") + expect(output.context).toEqual(["injected-context"]) + }) + + //#given compactionContextInjector is null + //#when compacting handler is invoked + //#then handler does not early-return, PreCompact hooks still execute + it("does not early-return when compactionContextInjector is null", async () => { + const preCompactMock = mock(async () => {}) + + const handler = createCompactingHandler({ + claudeCodeHooks: { + "experimental.session.compacting": preCompactMock, + }, + compactionContextInjector: undefined, + }) + + const output = { context: [] as string[] } + await handler({ sessionID: "ses_test" }, output) + + expect(preCompactMock).toHaveBeenCalled() + expect(output.context).toEqual([]) + }) +}) diff --git a/src/index.compaction-model-agnostic.static.test.ts b/src/index.compaction-model-agnostic.static.test.ts index ccba44101..6dfacb6f3 100644 --- a/src/index.compaction-model-agnostic.static.test.ts +++ b/src/index.compaction-model-agnostic.static.test.ts @@ -13,7 +13,7 @@ describe("experimental.session.compacting", () => { //#then 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("providerID:")).toBe(false) expect(hookSlice.includes("modelID:")).toBe(false) diff --git a/src/index.conditional-tools.test.ts b/src/index.conditional-tools.test.ts new file mode 100644 index 000000000..96c955c4b --- /dev/null +++ b/src/index.conditional-tools.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, it } from "bun:test" + +describe("look_at tool conditional registration", () => { + describe("isMultimodalLookerEnabled logic", () => { + // given multimodal-looker is in disabled_agents + // when checking if agent is enabled + // then should return false (disabled) + it("returns false when multimodal-looker is disabled (exact case)", () => { + const disabledAgents: string[] = ["multimodal-looker"] + const isEnabled = !disabledAgents.some( + (agent) => agent.toLowerCase() === "multimodal-looker", + ) + expect(isEnabled).toBe(false) + }) + + // given multimodal-looker is in disabled_agents with different case + // when checking if agent is enabled + // then should return false (case-insensitive match) + it("returns false when multimodal-looker is disabled (case-insensitive)", () => { + const disabledAgents: string[] = ["Multimodal-Looker"] + const isEnabled = !disabledAgents.some( + (agent) => agent.toLowerCase() === "multimodal-looker", + ) + expect(isEnabled).toBe(false) + }) + + // given multimodal-looker is NOT in disabled_agents + // when checking if agent is enabled + // then should return true (enabled) + it("returns true when multimodal-looker is not disabled", () => { + const disabledAgents: string[] = ["oracle", "librarian"] + const isEnabled = !disabledAgents.some( + (agent) => agent.toLowerCase() === "multimodal-looker", + ) + expect(isEnabled).toBe(true) + }) + + // given disabled_agents is empty + // when checking if agent is enabled + // then should return true (enabled by default) + it("returns true when disabled_agents is empty", () => { + const disabledAgents: string[] = [] + const isEnabled = !disabledAgents.some( + (agent) => agent.toLowerCase() === "multimodal-looker", + ) + expect(isEnabled).toBe(true) + }) + + // given disabled_agents is undefined (simulated as empty array) + // when checking if agent is enabled + // then should return true (enabled by default) + it("returns true when disabled_agents is undefined (fallback to empty)", () => { + const disabledAgents: string[] | undefined = undefined + const list: string[] = disabledAgents ?? [] + const isEnabled = !list.some( + (agent) => agent.toLowerCase() === "multimodal-looker", + ) + expect(isEnabled).toBe(true) + }) + }) + + describe("conditional tool spread pattern", () => { + // given lookAt is not null (agent enabled) + // when spreading into tool object + // then look_at should be included + it("includes look_at when lookAt is not null", () => { + const lookAt = { execute: () => {} } + const tools = { + ...(lookAt ? { look_at: lookAt } : {}), + } + expect(tools).toHaveProperty("look_at") + }) + + // given lookAt is null (agent disabled) + // when spreading into tool object + // then look_at should NOT be included + it("excludes look_at when lookAt is null", () => { + const lookAt = null + const tools = { + ...(lookAt ? { look_at: lookAt } : {}), + } + expect(tools).not.toHaveProperty("look_at") + }) + }) +}) diff --git a/src/index.telemetry.test.ts b/src/index.telemetry.test.ts index f3026a1cb..924a7db2c 100644 --- a/src/index.telemetry.test.ts +++ b/src/index.telemetry.test.ts @@ -29,7 +29,6 @@ const mockCreateHooks = mock(() => ({ compactionTodoPreserver: undefined, claudeCodeHooks: undefined, })) -const mockCreatePluginDispose = mock(() => async () => {}) const mockCreatePluginInterface = mock(() => ({})) const mockCreatePluginPostHog = mock(() => ({ trackActive: () => { @@ -70,9 +69,6 @@ function installModuleMocks(): void { mock.module("./create-hooks", () => ({ createHooks: mockCreateHooks, })) - mock.module("./plugin-dispose", () => ({ - createPluginDispose: mockCreatePluginDispose, - })) mock.module("./plugin-interface", () => ({ createPluginInterface: mockCreatePluginInterface, })) @@ -110,7 +106,7 @@ function installModuleMocks(): void { })) } -describe("OhMyOpenCodePlugin telemetry isolation", () => { +describe("oh-my-openagent telemetry isolation", () => { beforeEach(() => { mock.restore() installModuleMocks() @@ -125,12 +121,13 @@ describe("OhMyOpenCodePlugin telemetry isolation", () => { const { default: plugin } = await import(`./index?telemetry=${Date.now()}-${Math.random()}`) // when - const result = await plugin({ + const result = await plugin.server({ directory: "/tmp/project", client: {}, - } as Parameters[0]) + } as Parameters[0]) // then - expect(result).toMatchObject({ name: "oh-my-openagent" }) + expect(typeof result).toBe("object") + expect(result).not.toBeNull() }) }) diff --git a/src/index.test.ts b/src/index.test.ts index 7101b9f09..ba7be1363 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,223 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test" -describe("experimental.session.compacting handler", () => { - function createCompactingHandler(hooks: { - compactionContextInjector?: { - capture: (sessionID: string) => Promise - inject: (sessionID: string) => string - } - compactionTodoPreserver?: { capture: (sessionID: string) => Promise } - claudeCodeHooks?: { - "experimental.session.compacting"?: ( - input: { sessionID: string }, - output: { context: string[] }, - ) => Promise - } - }) { - return async ( - _input: { sessionID: string }, - output: { context: string[] }, - ): Promise => { - await hooks.compactionContextInjector?.capture(_input.sessionID) - await hooks.compactionTodoPreserver?.capture(_input.sessionID) - await hooks.claudeCodeHooks?.["experimental.session.compacting"]?.( - _input, - output, - ) - if (hooks.compactionContextInjector) { - output.context.push(hooks.compactionContextInjector.inject(_input.sessionID)) - } - } - } - - //#given all three hooks are present - //#when compacting handler is invoked - //#then all hooks are called in order: capture → PreCompact → contextInjector - it("calls claudeCodeHooks PreCompact alongside other hooks", async () => { - const callOrder: string[] = [] - - const handler = createCompactingHandler({ - compactionContextInjector: { - capture: mock(async () => { - callOrder.push("checkpointCapture") - }), - inject: mock((sessionID: string) => { - callOrder.push("contextInjector") - return `context-for-${sessionID}` - }), - }, - compactionTodoPreserver: { - capture: mock(async () => { callOrder.push("capture") }), - }, - claudeCodeHooks: { - "experimental.session.compacting": mock(async () => { - callOrder.push("preCompact") - }), - }, - }) - - const output = { context: [] as string[] } - await handler({ sessionID: "ses_test" }, output) - - expect(callOrder).toEqual(["checkpointCapture", "capture", "preCompact", "contextInjector"]) - expect(output.context).toEqual(["context-for-ses_test"]) - }) - - //#given claudeCodeHooks injects context during PreCompact - //#when compacting handler is invoked - //#then injected context from PreCompact is preserved in output - it("preserves context injected by PreCompact hooks", async () => { - const handler = createCompactingHandler({ - claudeCodeHooks: { - "experimental.session.compacting": async (_input, output) => { - output.context.push("precompact-injected-context") - }, - }, - }) - - const output = { context: [] as string[] } - await handler({ sessionID: "ses_test" }, output) - - expect(output.context).toContain("precompact-injected-context") - }) - - //#given claudeCodeHooks is null (no claude code hooks configured) - //#when compacting handler is invoked - //#then handler completes without error and other hooks still run - it("handles null claudeCodeHooks gracefully", async () => { - const captureMock = mock(async () => {}) - const checkpointCaptureMock = mock(async () => {}) - const contextMock = mock(() => "injected-context") - - const handler = createCompactingHandler({ - compactionContextInjector: { - capture: checkpointCaptureMock, - inject: contextMock, - }, - compactionTodoPreserver: { capture: captureMock }, - claudeCodeHooks: undefined, - }) - - const output = { context: [] as string[] } - await handler({ sessionID: "ses_test" }, output) - - expect(checkpointCaptureMock).toHaveBeenCalledWith("ses_test") - expect(captureMock).toHaveBeenCalledWith("ses_test") - expect(contextMock).toHaveBeenCalledWith("ses_test") - expect(output.context).toEqual(["injected-context"]) - }) - - //#given compactionContextInjector is null - //#when compacting handler is invoked - //#then handler does not early-return, PreCompact hooks still execute - it("does not early-return when compactionContextInjector is null", async () => { - const preCompactMock = mock(async () => {}) - - const handler = createCompactingHandler({ - claudeCodeHooks: { - "experimental.session.compacting": preCompactMock, - }, - compactionContextInjector: undefined, - }) - - const output = { context: [] as string[] } - await handler({ sessionID: "ses_test" }, output) - - expect(preCompactMock).toHaveBeenCalled() - expect(output.context).toEqual([]) - }) -}) - -/** - * Tests for conditional tool registration logic in index.ts - * - * The actual plugin initialization is complex to test directly, - * so we test the underlying logic that determines tool registration. - */ -describe("look_at tool conditional registration", () => { - describe("isMultimodalLookerEnabled logic", () => { - // given multimodal-looker is in disabled_agents - // when checking if agent is enabled - // then should return false (disabled) - it("returns false when multimodal-looker is disabled (exact case)", () => { - const disabledAgents: string[] = ["multimodal-looker"] - const isEnabled = !disabledAgents.some( - (agent) => agent.toLowerCase() === "multimodal-looker" - ) - expect(isEnabled).toBe(false) - }) - - // given multimodal-looker is in disabled_agents with different case - // when checking if agent is enabled - // then should return false (case-insensitive match) - it("returns false when multimodal-looker is disabled (case-insensitive)", () => { - const disabledAgents: string[] = ["Multimodal-Looker"] - const isEnabled = !disabledAgents.some( - (agent) => agent.toLowerCase() === "multimodal-looker" - ) - expect(isEnabled).toBe(false) - }) - - // given multimodal-looker is NOT in disabled_agents - // when checking if agent is enabled - // then should return true (enabled) - it("returns true when multimodal-looker is not disabled", () => { - const disabledAgents: string[] = ["oracle", "librarian"] - const isEnabled = !disabledAgents.some( - (agent) => agent.toLowerCase() === "multimodal-looker" - ) - expect(isEnabled).toBe(true) - }) - - // given disabled_agents is empty - // when checking if agent is enabled - // then should return true (enabled by default) - it("returns true when disabled_agents is empty", () => { - const disabledAgents: string[] = [] - const isEnabled = !disabledAgents.some( - (agent) => agent.toLowerCase() === "multimodal-looker" - ) - expect(isEnabled).toBe(true) - }) - - // given disabled_agents is undefined (simulated as empty array) - // when checking if agent is enabled - // then should return true (enabled by default) - it("returns true when disabled_agents is undefined (fallback to empty)", () => { - const disabledAgents: string[] | undefined = undefined - const list: string[] = disabledAgents ?? [] - const isEnabled = !list.some( - (agent) => agent.toLowerCase() === "multimodal-looker" - ) - expect(isEnabled).toBe(true) - }) - }) - - describe("conditional tool spread pattern", () => { - // given lookAt is not null (agent enabled) - // when spreading into tool object - // then look_at should be included - it("includes look_at when lookAt is not null", () => { - const lookAt = { execute: () => {} } // mock tool - const tools = { - ...(lookAt ? { look_at: lookAt } : {}), - } - expect(tools).toHaveProperty("look_at") - }) - - // given lookAt is null (agent disabled) - // when spreading into tool object - // then look_at should NOT be included - it("excludes look_at when lookAt is null", () => { - const lookAt = null - const tools = { - ...(lookAt ? { look_at: lookAt } : {}), - } - expect(tools).not.toHaveProperty("look_at") - }) - }) -}) - const mockInitConfigContext = mock(() => {}) const mockDetectExternalSkillPlugin = mock(() => ({ detected: false, pluginName: null })) const mockGetSkillPluginConflictWarning = mock(() => "") @@ -252,12 +34,11 @@ const mockCreateHooks = mock(() => ({ compactionTodoPreserver: undefined, claudeCodeHooks: undefined, })) -const mockCreatePluginDispose = mock(() => async () => {}) const mockCreatePluginInterface = mock(() => ({})) const mockInitializeOpenClaw = mock(async () => {}) const mockStartTmuxCheck = mock(() => {}) -let OhMyOpenCodePlugin: (typeof import("./index"))["default"] +let pluginModule: (typeof import("./index"))["default"] function installIndexModuleMocks(): void { mock.module("./cli/config-manager/config-context", () => ({ @@ -297,10 +78,6 @@ function installIndexModuleMocks(): void { createHooks: mockCreateHooks, })) - mock.module("./plugin-dispose", () => ({ - createPluginDispose: mockCreatePluginDispose, - })) - mock.module("./plugin-interface", () => ({ createPluginInterface: mockCreatePluginInterface, })) @@ -333,11 +110,11 @@ async function importFreshIndexModule(): Promise { return import(`./index?test=${Date.now()}-${Math.random()}`) } -describe("OhMyOpenCodePlugin", () => { +describe("oh-my-openagent plugin module", () => { beforeEach(async () => { mock.restore() installIndexModuleMocks() - ;({ default: OhMyOpenCodePlugin } = await importFreshIndexModule()) + ;({ default: pluginModule } = await importFreshIndexModule()) mockInitConfigContext.mockClear() mockDetectExternalSkillPlugin.mockClear() mockGetSkillPluginConflictWarning.mockClear() @@ -350,7 +127,6 @@ describe("OhMyOpenCodePlugin", () => { mockCreateManagers.mockClear() mockCreateTools.mockClear() mockCreateHooks.mockClear() - mockCreatePluginDispose.mockClear() mockCreatePluginInterface.mockClear() mockInitializeOpenClaw.mockClear() mockStartTmuxCheck.mockClear() @@ -375,10 +151,10 @@ describe("OhMyOpenCodePlugin", () => { }) // when - await OhMyOpenCodePlugin({ + await pluginModule.server({ directory: "/tmp/project", client: {}, - } as Parameters[0]) + } as Parameters[0]) // then expect(mockInitializeOpenClaw).toHaveBeenCalledTimes(1) @@ -390,12 +166,21 @@ describe("OhMyOpenCodePlugin", () => { mockLoadPluginConfig.mockReturnValue({}) // when - await OhMyOpenCodePlugin({ + await pluginModule.server({ directory: "/tmp/project", client: {}, - } as Parameters[0]) + } as Parameters[0]) // then expect(mockInitializeOpenClaw).not.toHaveBeenCalled() }) + + it("exports a V1 PluginModule shape with id and server", () => { + // given the plugin module is loaded + // when inspecting the default export + // then it has the expected V1 shape + expect(typeof pluginModule).toBe("object") + expect(pluginModule.id).toBe("oh-my-openagent") + expect(typeof pluginModule.server).toBe("function") + }) }) diff --git a/src/index.ts b/src/index.ts index b36e6c4b9..6778427d0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import { initConfigContext } from "./cli/config-manager/config-context" -import type { Plugin } from "@opencode-ai/plugin" +import type { Hooks, Plugin, PluginModule } from "@opencode-ai/plugin" import type { HookName } from "./config" @@ -9,7 +9,6 @@ import { createRuntimeTmuxConfig, isTmuxIntegrationEnabled } from "./create-runt import { createTools } from "./create-tools" import { initializeOpenClaw } from "./openclaw" import { createPluginInterface } from "./plugin-interface" -import { createPluginDispose, type PluginDispose } from "./plugin-dispose" import { loadPluginConfig } from "./plugin-config" import { createModelCacheState } from "./plugin-state" @@ -17,27 +16,23 @@ import { createFirstMessageVariantGate } from "./shared/first-message-variant" import { injectServerAuthIntoClient, log, logLegacyPluginStartupWarning } from "./shared" import { detectExternalSkillPlugin, getSkillPluginConflictWarning } from "./shared/external-plugin-detector" import { startBackgroundCheck as startTmuxCheck } from "./tools/interactive-bash" -import { lspManager } from "./tools/lsp/client" import { createPluginPostHog, getPostHogDistinctId } from "./shared/posthog" -let activePluginDispose: PluginDispose | null = null - -const OhMyOpenCodePlugin: Plugin = async (ctx) => { +const serverPlugin: Plugin = async (input, _options): Promise => { initConfigContext("opencode", null) - log("[OhMyOpenCodePlugin] ENTRY - plugin loading", { - directory: ctx.directory, + log("[oh-my-openagent] ENTRY - plugin loading", { + directory: input.directory, }) logLegacyPluginStartupWarning() - const skillPluginCheck = detectExternalSkillPlugin(ctx.directory) + const skillPluginCheck = detectExternalSkillPlugin(input.directory) if (skillPluginCheck.detected && skillPluginCheck.pluginName) { console.warn(getSkillPluginConflictWarning(skillPluginCheck.pluginName)) } - injectServerAuthIntoClient(ctx.client) - await activePluginDispose?.() + injectServerAuthIntoClient(input.client) - const pluginConfig = loadPluginConfig(ctx.directory, ctx) + const pluginConfig = loadPluginConfig(input.directory, input) const posthog = createPluginPostHog() const distinctId = getPostHogDistinctId() @@ -78,7 +73,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { const modelCacheState = createModelCacheState() const managers = createManagers({ - ctx, + ctx: input, pluginConfig, tmuxConfig, modelCacheState, @@ -86,31 +81,25 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { }) const toolsResult = await createTools({ - ctx, + ctx: input, pluginConfig, managers, }) const hooks = createHooks({ - ctx, + ctx: input, pluginConfig, modelCacheState, backgroundManager: managers.backgroundManager, + modelFallbackControllerAccessor: managers.modelFallbackControllerAccessor, isHookEnabled, safeHookEnabled, mergedSkills: toolsResult.mergedSkills, availableSkills: toolsResult.availableSkills, }) - const dispose = createPluginDispose({ - backgroundManager: managers.backgroundManager, - skillMcpManager: managers.skillMcpManager, - lspManager, - disposeHooks: hooks.disposeHooks, - }) - const pluginInterface = createPluginInterface({ - ctx, + ctx: input, pluginConfig, firstMessageVariantGate, managers, @@ -118,30 +107,32 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { tools: toolsResult.filteredTools, }) - activePluginDispose = dispose - return { - name: "oh-my-openagent", ...pluginInterface, "experimental.session.compacting": async ( - _input: { sessionID: string }, + compactingInput: { sessionID: string }, output: { context: string[] }, ): Promise => { - await hooks.compactionContextInjector?.capture(_input.sessionID) - await hooks.compactionTodoPreserver?.capture(_input.sessionID) + await hooks.compactionContextInjector?.capture(compactingInput.sessionID) + await hooks.compactionTodoPreserver?.capture(compactingInput.sessionID) await hooks.claudeCodeHooks?.["experimental.session.compacting"]?.( - _input, + compactingInput, output, ) if (hooks.compactionContextInjector) { - output.context.push(hooks.compactionContextInjector.inject(_input.sessionID)) + output.context.push(hooks.compactionContextInjector.inject(compactingInput.sessionID)) } }, } } -export default OhMyOpenCodePlugin +const pluginModule: PluginModule = { + id: "oh-my-openagent", + server: serverPlugin, +} + +export default pluginModule export type { OhMyOpenCodeConfig, diff --git a/src/mcp/AGENTS.md b/src/mcp/AGENTS.md index 2518ba92f..4914d491b 100644 --- a/src/mcp/AGENTS.md +++ b/src/mcp/AGENTS.md @@ -1,6 +1,6 @@ # src/mcp/ — 3 Built-in Remote MCPs -**Generated:** 2026-04-11 +**Generated:** 2026-04-18 ## OVERVIEW diff --git a/src/openclaw/AGENTS.md b/src/openclaw/AGENTS.md index 93b32eff3..680141de8 100644 --- a/src/openclaw/AGENTS.md +++ b/src/openclaw/AGENTS.md @@ -1,6 +1,6 @@ # src/openclaw/ — Bidirectional External Integration -**Generated:** 2026-04-11 +**Generated:** 2026-04-18 ## OVERVIEW diff --git a/src/plugin-config.ts b/src/plugin-config.ts index 0b79e96d3..a5853af72 100644 --- a/src/plugin-config.ts +++ b/src/plugin-config.ts @@ -9,6 +9,7 @@ import { parseJsonc, detectPluginConfigFile, migrateConfigFile, + resolveAgentDefinitionPaths, } from "./shared"; import { migrateLegacyConfigFile } from "./shared/migrate-legacy-config-file"; import { CONFIG_BASENAME, LEGACY_CONFIG_BASENAME } from "./shared/plugin-identity"; @@ -41,6 +42,7 @@ const PARTIAL_STRING_ARRAY_KEYS = new Set([ "disabled_commands", "disabled_tools", "mcp_env_allowlist", + "agent_definitions", ]); export function parseConfigPartially( @@ -139,6 +141,12 @@ export function mergeConfigs( ...override, agents: deepMerge(base.agents, override.agents), categories: deepMerge(base.categories, override.categories), + agent_definitions: [ + ...new Set([ + ...(base.agent_definitions ?? []), + ...(override.agent_definitions ?? []), + ]), + ], disabled_agents: [ ...new Set([ ...(base.disabled_agents ?? []), @@ -250,6 +258,15 @@ export function loadPluginConfig( // Load user config first (base). Parse empty config through Zod to apply field defaults. const userConfig = loadConfigFromPath(userConfigPath, ctx) const userGitMasterOverrides = loadExplicitGitMasterOverrides(userConfigPath) + + if (userConfig?.agent_definitions) { + userConfig.agent_definitions = resolveAgentDefinitionPaths( + userConfig.agent_definitions, + configDir, + null + ) + } + let config: OhMyOpenCodeConfig = userConfig ?? OhMyOpenCodeConfigSchema.parse({}); @@ -257,6 +274,15 @@ export function loadPluginConfig( const defaultGitMaster = OhMyOpenCodeConfigSchema.parse({}).git_master const projectConfig = loadConfigFromPath(projectConfigPath, ctx); const projectGitMasterOverrides = loadExplicitGitMasterOverrides(projectConfigPath) + + if (projectConfig?.agent_definitions) { + projectConfig.agent_definitions = resolveAgentDefinitionPaths( + projectConfig.agent_definitions, + projectBasePath, + directory + ) + } + if (projectConfig) { config = mergeConfigs(config, projectConfig); } diff --git a/src/plugin-dispose.test.ts b/src/plugin-dispose.test.ts deleted file mode 100644 index d0dd0285b..000000000 --- a/src/plugin-dispose.test.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { describe, expect, spyOn, test } from "bun:test" - -import { disposeCreatedHooks } from "./create-hooks" -import { createPluginDispose } from "./plugin-dispose" - -describe("createPluginDispose", () => { - test("#given plugin with active managers and hooks #when dispose() is called #then backgroundManager.shutdown() is called", async () => { - // given - const backgroundManager = { - shutdown: async (): Promise => {}, - } - const skillMcpManager = { - disconnectAll: async (): Promise => {}, - } - const lspManager = { - stopAll: async (): Promise => {}, - } - const shutdownSpy = spyOn(backgroundManager, "shutdown") - const dispose = createPluginDispose({ - backgroundManager, - skillMcpManager, - lspManager, - disposeHooks: (): void => {}, - }) - - // when - await dispose() - - // then - expect(shutdownSpy).toHaveBeenCalledTimes(1) - }) - - test("#given plugin with active MCP connections #when dispose() is called #then skillMcpManager.disconnectAll() is called", async () => { - // given - const backgroundManager = { - shutdown: async (): Promise => {}, - } - const skillMcpManager = { - disconnectAll: async (): Promise => {}, - } - const lspManager = { - stopAll: async (): Promise => {}, - } - const disconnectAllSpy = spyOn(skillMcpManager, "disconnectAll") - const dispose = createPluginDispose({ - backgroundManager, - skillMcpManager, - lspManager, - disposeHooks: (): void => {}, - }) - - // when - await dispose() - - // then - expect(disconnectAllSpy).toHaveBeenCalledTimes(1) - }) - - test("#given plugin with hooks that have dispose #when dispose() is called #then each hook's dispose is called", async () => { - // given - const claudeCodeHooks = { - dispose: (): void => {}, - } - const commentChecker = { - dispose: (): void => {}, - } - const runtimeFallback = { - dispose: (): void => {}, - } - const todoContinuationEnforcer = { - dispose: (): void => {}, - } - const autoSlashCommand = { - dispose: (): void => {}, - } - const lspManager = { - stopAll: async (): Promise => {}, - } - const claudeCodeHooksDisposeSpy = spyOn(claudeCodeHooks, "dispose") - const commentCheckerDisposeSpy = spyOn(commentChecker, "dispose") - const runtimeFallbackDisposeSpy = spyOn(runtimeFallback, "dispose") - const todoContinuationEnforcerDisposeSpy = spyOn(todoContinuationEnforcer, "dispose") - const autoSlashCommandDisposeSpy = spyOn(autoSlashCommand, "dispose") - const dispose = createPluginDispose({ - backgroundManager: { - shutdown: async (): Promise => {}, - }, - skillMcpManager: { - disconnectAll: async (): Promise => {}, - }, - lspManager, - disposeHooks: (): void => { - disposeCreatedHooks({ - claudeCodeHooks, - commentChecker, - runtimeFallback, - todoContinuationEnforcer, - autoSlashCommand, - }) - }, - }) - - // when - await dispose() - - // then - expect(claudeCodeHooksDisposeSpy).toHaveBeenCalledTimes(1) - expect(commentCheckerDisposeSpy).toHaveBeenCalledTimes(1) - expect(runtimeFallbackDisposeSpy).toHaveBeenCalledTimes(1) - expect(todoContinuationEnforcerDisposeSpy).toHaveBeenCalledTimes(1) - expect(autoSlashCommandDisposeSpy).toHaveBeenCalledTimes(1) - }) - - test("#given dispose already called #when dispose() called again #then no errors", async () => { - // given - const backgroundManager = { - shutdown: async (): Promise => {}, - } - const skillMcpManager = { - disconnectAll: async (): Promise => {}, - } - const lspManager = { - stopAll: async (): Promise => {}, - } - const disposeHooks = { - run: (): void => {}, - } - const shutdownSpy = spyOn(backgroundManager, "shutdown") - const disconnectAllSpy = spyOn(skillMcpManager, "disconnectAll") - const stopAllSpy = spyOn(lspManager, "stopAll") - const disposeHooksSpy = spyOn(disposeHooks, "run") - const dispose = createPluginDispose({ - backgroundManager, - skillMcpManager, - lspManager, - disposeHooks: disposeHooks.run, - }) - - // when - await dispose() - await dispose() - - // then - expect(shutdownSpy).toHaveBeenCalledTimes(1) - expect(disconnectAllSpy).toHaveBeenCalledTimes(1) - expect(stopAllSpy).toHaveBeenCalledTimes(1) - expect(disposeHooksSpy).toHaveBeenCalledTimes(1) - }) - - test("#given backgroundManager.shutdown() throws #when dispose() is called #then skillMcpManager.disconnectAll() and disposeHooks() are still called", async () => { - // given - const backgroundManager = { - shutdown: async (): Promise => { - throw new Error("shutdown failed") - }, - } - const skillMcpManager = { - disconnectAll: async (): Promise => {}, - } - const lspManager = { - stopAll: async (): Promise => {}, - } - const disposeHooksCalls: number[] = [] - const disconnectAllSpy = spyOn(skillMcpManager, "disconnectAll") - const dispose = createPluginDispose({ - backgroundManager, - skillMcpManager, - lspManager, - disposeHooks: (): void => { - disposeHooksCalls.push(1) - }, - }) - - // when - await dispose() - - // then - expect(disconnectAllSpy).toHaveBeenCalledTimes(1) - expect(disposeHooksCalls).toHaveLength(1) - }) - - test("#given skillMcpManager.disconnectAll() throws #when dispose() is called #then disposeHooks() is still called", async () => { - // given - const backgroundManager = { - shutdown: async (): Promise => {}, - } - const skillMcpManager = { - disconnectAll: async (): Promise => { - throw new Error("disconnectAll failed") - }, - } - const lspManager = { - stopAll: async (): Promise => {}, - } - const disposeHooksCalls: number[] = [] - const shutdownSpy = spyOn(backgroundManager, "shutdown") - const dispose = createPluginDispose({ - backgroundManager, - skillMcpManager, - lspManager, - disposeHooks: (): void => { - disposeHooksCalls.push(1) - }, - }) - - // when - await dispose() - - // then - expect(shutdownSpy).toHaveBeenCalledTimes(1) - expect(disposeHooksCalls).toHaveLength(1) - }) - - test("#given active LSP clients #when dispose runs #then lsp manager is stopped", async () => { - // given - const lspManager = { - stopAll: async (): Promise => {}, - } - const stopAllSpy = spyOn(lspManager, "stopAll") - const dispose = createPluginDispose({ - backgroundManager: { - shutdown: async (): Promise => {}, - }, - skillMcpManager: { - disconnectAll: async (): Promise => {}, - }, - lspManager, - disposeHooks: (): void => {}, - }) - - // when - await dispose() - - // then - expect(stopAllSpy).toHaveBeenCalledTimes(1) - }) -}) diff --git a/src/plugin-dispose.ts b/src/plugin-dispose.ts deleted file mode 100644 index 998fd28eb..000000000 --- a/src/plugin-dispose.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { log } from "./shared" - -export type PluginDispose = () => Promise - -export function createPluginDispose(args: { - backgroundManager: { - shutdown: () => void | Promise - } - skillMcpManager: { - disconnectAll: () => Promise - } - lspManager: { - stopAll: () => Promise - } - disposeHooks: () => void -}): PluginDispose { - const { backgroundManager, skillMcpManager, lspManager, disposeHooks } = args - let disposePromise: Promise | null = null - - return async (): Promise => { - if (disposePromise) { - await disposePromise - return - } - - disposePromise = (async (): Promise => { - try { - await backgroundManager.shutdown() - } catch (error) { - log("[plugin-dispose] backgroundManager.shutdown() error:", error) - } - try { - await skillMcpManager.disconnectAll() - } catch (error) { - log("[plugin-dispose] skillMcpManager.disconnectAll() error:", error) - } - try { - await lspManager.stopAll() - } catch (error) { - log("[plugin-dispose] lspManager.stopAll() error:", error) - } - try { - disposeHooks() - } catch (error) { - log("[plugin-dispose] disposeHooks() error:", error) - } - })() - - await disposePromise - } -} diff --git a/src/plugin-handlers/AGENTS.md b/src/plugin-handlers/AGENTS.md index f0d9949a7..df6c8bf14 100644 --- a/src/plugin-handlers/AGENTS.md +++ b/src/plugin-handlers/AGENTS.md @@ -1,6 +1,6 @@ # src/plugin-handlers/ — 6-Phase Config Loading Pipeline -**Generated:** 2026-04-11 +**Generated:** 2026-04-18 ## CRITICAL: AGENT ORDERING diff --git a/src/plugin-handlers/agent-config-handler-agents-skills.test.ts b/src/plugin-handlers/agent-config-handler-agents-skills.test.ts index 593f22d9e..4bb94ce41 100644 --- a/src/plugin-handlers/agent-config-handler-agents-skills.test.ts +++ b/src/plugin-handlers/agent-config-handler-agents-skills.test.ts @@ -81,7 +81,7 @@ describe("applyAgentConfig .agents skills", () => { // when await applyAgentConfig({ - config: { model: "anthropic/claude-opus-4-6", agent: {} }, + config: { model: "anthropic/claude-opus-4-7", agent: {} }, pluginConfig: createPluginConfig(), ctx: { directory }, pluginComponents: createPluginComponents(), @@ -111,7 +111,7 @@ describe("applyAgentConfig .agents skills", () => { // when await applyAgentConfig({ - config: { model: "anthropic/claude-opus-4-6", agent: {} }, + config: { model: "anthropic/claude-opus-4-7", agent: {} }, pluginConfig: createPluginConfig(), ctx: { directory: "/tmp/project" }, pluginComponents: createPluginComponents(), diff --git a/src/plugin-handlers/agent-config-handler.test.ts b/src/plugin-handlers/agent-config-handler.test.ts index 8e06d7fca..afca8f62b 100644 --- a/src/plugin-handlers/agent-config-handler.test.ts +++ b/src/plugin-handlers/agent-config-handler.test.ts @@ -31,7 +31,7 @@ function createPluginComponents(): PluginComponents { function createBaseConfig(): Record { return { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } } @@ -61,6 +61,8 @@ describe("applyAgentConfig builtin override protection", () => { let discoverGlobalAgentsSkillsSpy: ReturnType let loadUserAgentsSpy: ReturnType let loadProjectAgentsSpy: ReturnType + let loadAgentDefinitionsSpy: ReturnType + let readOpencodeConfigAgentsSpy: ReturnType let migrateAgentConfigSpy: ReturnType let logSpy: ReturnType @@ -140,6 +142,11 @@ describe("applyAgentConfig builtin override protection", () => { loadUserAgentsSpy = spyOn(agentLoader, "loadUserAgents").mockReturnValue({}) loadProjectAgentsSpy = spyOn(agentLoader, "loadProjectAgents").mockReturnValue({}) + loadAgentDefinitionsSpy = spyOn(agentLoader, "loadAgentDefinitions").mockReturnValue({}) + readOpencodeConfigAgentsSpy = spyOn( + agentLoader, + "readOpencodeConfigAgents", + ).mockReturnValue({}) migrateAgentConfigSpy = spyOn(shared, "migrateAgentConfig").mockImplementation( (config: Record) => config, @@ -159,6 +166,8 @@ describe("applyAgentConfig builtin override protection", () => { discoverGlobalAgentsSkillsSpy.mockRestore() loadUserAgentsSpy.mockRestore() loadProjectAgentsSpy.mockRestore() + loadAgentDefinitionsSpy.mockRestore() + readOpencodeConfigAgentsSpy.mockRestore() migrateAgentConfigSpy.mockRestore() logSpy.mockRestore() }) @@ -441,4 +450,208 @@ describe("applyAgentConfig builtin override protection", () => { ]), ) }) + + describe("agent_definitions and opencode.json integration", () => { + test("agent_definitions agents appear in output", async () => { + // given + loadAgentDefinitionsSpy.mockReturnValue({ + "my-custom-agent": { + name: "my-custom-agent", + prompt: "test custom agent from agent_definitions", + mode: "subagent", + }, + }) + const pluginConfig = createPluginConfig() + pluginConfig.agent_definitions = ["/fake/path/agent.md"] + + // when + const result = await applyAgentConfig({ + config: createBaseConfig(), + pluginConfig, + ctx: { directory: "/tmp" }, + pluginComponents: createPluginComponents(), + }) + + // then + expect(result["my-custom-agent"]).toBeDefined() + expect(result["my-custom-agent"]?.prompt).toBe("test custom agent from agent_definitions") + }) + + test("opencode.json agents appear in output", async () => { + // given + readOpencodeConfigAgentsSpy.mockReturnValue({ + "opencode-agent": { + name: "opencode-agent", + prompt: "test opencode config agent", + mode: "subagent", + description: "(opencode-config) OC", + }, + }) + + // when + const result = await applyAgentConfig({ + config: createBaseConfig(), + pluginConfig: createPluginConfig(), + ctx: { directory: "/tmp" }, + pluginComponents: createPluginComponents(), + }) + + // then + expect(result["opencode-agent"]).toBeDefined() + expect(result["opencode-agent"]?.prompt).toBe("test opencode config agent") + expect(result["opencode-agent"]?.description).toBe("(opencode-config) OC") + }) + + test("agent_definitions agents subject to disabled_agents filtering", async () => { + // given + loadAgentDefinitionsSpy.mockReturnValue({ + "disabled-custom-agent": { + name: "disabled-custom-agent", + prompt: "this should be filtered", + mode: "subagent", + }, + }) + const pluginConfig = createPluginConfig() + pluginConfig.agent_definitions = ["/fake/path/agent.md"] + pluginConfig.disabled_agents = ["disabled-custom-agent"] + + // when + const result = await applyAgentConfig({ + config: createBaseConfig(), + pluginConfig, + ctx: { directory: "/tmp" }, + pluginComponents: createPluginComponents(), + }) + + // then + expect(result["disabled-custom-agent"]).toBeUndefined() + }) + + test("agent_definitions cannot override builtin agents", async () => { + // given + loadAgentDefinitionsSpy.mockReturnValue({ + oracle: { + name: "oracle", + prompt: "evil override prompt", + mode: "subagent", + }, + }) + const pluginConfig = createPluginConfig() + pluginConfig.agent_definitions = ["/fake/path/agent.md"] + + // when + const result = await applyAgentConfig({ + config: createBaseConfig(), + pluginConfig, + ctx: { directory: "/tmp" }, + pluginComponents: createPluginComponents(), + }) + + // then + expect(result.oracle).toBeDefined() + expect(result.oracle?.prompt).not.toBe("evil override prompt") + }) + + test("precedence: configAgents override agent_definitions", async () => { + // given + loadAgentDefinitionsSpy.mockReturnValue({ + "shared-name": { + name: "shared-name", + prompt: "from-definitions", + mode: "subagent", + }, + }) + const config = createBaseConfig() + ;(config as Record).agent = { + "shared-name": { + name: "shared-name", + prompt: "from-config", + mode: "subagent", + }, + } + const pluginConfig = createPluginConfig() + pluginConfig.agent_definitions = ["/fake/path/agent.md"] + + // when + const result = await applyAgentConfig({ + config, + pluginConfig, + ctx: { directory: "/tmp" }, + pluginComponents: createPluginComponents(), + }) + + // then + expect(result["shared-name"]).toBeDefined() + expect(result["shared-name"]?.prompt).toBe("from-config") + }) + + test("precedence: agent_definitions overrides project agents", async () => { + // given + loadProjectAgentsSpy.mockReturnValue({ + "shared-name": { + name: "shared-name", + prompt: "from-project", + mode: "subagent", + }, + }) + loadAgentDefinitionsSpy.mockReturnValue({ + "shared-name": { + name: "shared-name", + prompt: "from-definitions", + mode: "subagent", + }, + }) + const pluginConfig = createPluginConfig() + pluginConfig.agent_definitions = ["/fake/path/agent.md"] + + // when + const result = await applyAgentConfig({ + config: createBaseConfig(), + pluginConfig, + ctx: { directory: "/tmp" }, + pluginComponents: createPluginComponents(), + }) + + // then + expect(result["shared-name"]).toBeDefined() + expect(result["shared-name"]?.prompt).toBe("from-definitions") + }) + + test("both Sisyphus-enabled and disabled paths include new sources", async () => { + // given + loadAgentDefinitionsSpy.mockReturnValue({ + "definitions-agent": { + name: "definitions-agent", + prompt: "from agent_definitions", + mode: "subagent", + }, + }) + readOpencodeConfigAgentsSpy.mockReturnValue({ + "opencode-agent": { + name: "opencode-agent", + prompt: "from opencode.json", + mode: "subagent", + }, + }) + const pluginConfig = createPluginConfig() + pluginConfig.agent_definitions = ["/fake/path/agent.md"] + if (pluginConfig.sisyphus_agent) { + pluginConfig.sisyphus_agent.planner_enabled = false + } + + // when + const result = await applyAgentConfig({ + config: createBaseConfig(), + pluginConfig, + ctx: { directory: "/tmp" }, + pluginComponents: createPluginComponents(), + }) + + // then + expect(result["definitions-agent"]).toBeDefined() + expect(result["definitions-agent"]?.prompt).toBe("from agent_definitions") + expect(result["opencode-agent"]).toBeDefined() + expect(result["opencode-agent"]?.prompt).toBe("from opencode.json") + }) + }) }) diff --git a/src/plugin-handlers/agent-config-handler.ts b/src/plugin-handlers/agent-config-handler.ts index b8c7a9ee6..384871114 100644 --- a/src/plugin-handlers/agent-config-handler.ts +++ b/src/plugin-handlers/agent-config-handler.ts @@ -14,7 +14,14 @@ import { discoverProjectClaudeSkills, discoverUserClaudeSkills, } from "../features/opencode-skill-loader"; -import { loadProjectAgents, loadUserAgents } from "../features/claude-code-agent-loader"; +import { + loadProjectAgents, + loadUserAgents, + loadOpencodeGlobalAgents, + loadOpencodeProjectAgents, + loadAgentDefinitions, + readOpencodeConfigAgents, +} from "../features/claude-code-agent-loader"; import type { PluginComponents } from "./plugin-components-loader"; import { reorderAgentsByPriority } from "./agent-priority-order"; import { remapAgentKeysToDisplayNames } from "./agent-key-remapper"; @@ -33,7 +40,6 @@ type AgentConfigRecord = Record | undefined> & { function getConfiguredDefaultAgent(config: Record): string | undefined { const defaultAgent = config.default_agent; if (typeof defaultAgent !== "string") return undefined; - const trimmedDefaultAgent = defaultAgent.trim(); return trimmedDefaultAgent.length > 0 ? trimmedDefaultAgent : undefined; } @@ -96,8 +102,15 @@ export async function applyAgentConfig(params: { const includeClaudeAgents = params.pluginConfig.claude_code?.agents ?? true; const userAgents = includeClaudeAgents ? loadUserAgents() : {}; const projectAgents = includeClaudeAgents ? loadProjectAgents(params.ctx.directory) : {}; + const opencodeGlobalAgents = loadOpencodeGlobalAgents(); + const opencodeProjectAgents = loadOpencodeProjectAgents(params.ctx.directory); const rawPluginAgents = params.pluginComponents.agents; + const agentDefinitionAgents = params.pluginConfig.agent_definitions + ? loadAgentDefinitions(params.pluginConfig.agent_definitions, "definition-file") + : {}; + const opencodeConfigAgents = readOpencodeConfigAgents(params.ctx.directory); + const pluginAgents = Object.fromEntries( Object.entries(rawPluginAgents).map(([key, value]) => { if (!value) return [key, value]; @@ -113,7 +126,11 @@ export async function applyAgentConfig(params: { ...Object.entries(configAgent ?? {}), ...Object.entries(userAgents), ...Object.entries(projectAgents), + ...Object.entries(opencodeGlobalAgents), + ...Object.entries(opencodeProjectAgents), ...Object.entries(pluginAgents).filter(([, config]) => config !== undefined), + ...Object.entries(agentDefinitionAgents), + ...Object.entries(opencodeConfigAgents), ] .filter(([, config]) => config != null) .map(([name, config]) => ({ @@ -123,6 +140,20 @@ export async function applyAgentConfig(params: { : "", })); + log( + "[agent-config-handler] Agent sources loaded", + { + user: Object.keys(userAgents).length, + project: Object.keys(projectAgents).length, + opencodeGlobal: Object.keys(opencodeGlobalAgents).length, + opencodeProject: Object.keys(opencodeProjectAgents).length, + plugin: Object.keys(pluginAgents).length, + agentDefinitions: Object.keys(agentDefinitionAgents).length, + opencodeConfig: Object.keys(opencodeConfigAgents).length, + config: Object.keys(configAgent ?? {}).length, + } + ); + const builtinAgents = await createBuiltinAgents( migratedDisabledAgents, params.pluginConfig.agents, @@ -257,6 +288,22 @@ export async function applyAgentConfig(params: { pluginAgents, protectedBuiltinAgentNames, ); + const filteredOpencodeGlobalAgents = filterProtectedAgentOverrides( + opencodeGlobalAgents, + protectedBuiltinAgentNames, + ); + const filteredOpencodeProjectAgents = filterProtectedAgentOverrides( + opencodeProjectAgents, + protectedBuiltinAgentNames, + ); + const filteredAgentDefinitionAgents = filterProtectedAgentOverrides( + agentDefinitionAgents, + protectedBuiltinAgentNames, + ); + const filteredOpencodeConfigAgents = filterProtectedAgentOverrides( + opencodeConfigAgents, + protectedBuiltinAgentNames, + ); params.config.agent = { ...agentConfig, @@ -265,9 +312,14 @@ export async function applyAgentConfig(params: { ([key]) => key !== "sisyphus" && key !== "hephaestus" && key !== "atlas", ), ), - ...filterDisabledAgents(filteredUserAgents), - ...filterDisabledAgents(filteredProjectAgents), + // Precedence: later entries override earlier (project > global > user > plugin) ...filterDisabledAgents(filteredPluginAgents), + ...filterDisabledAgents(filteredUserAgents), + ...filterDisabledAgents(filteredOpencodeGlobalAgents), + ...filterDisabledAgents(filteredProjectAgents), + ...filterDisabledAgents(filteredOpencodeProjectAgents), + ...filterDisabledAgents(filteredAgentDefinitionAgents), + ...filterDisabledAgents(filteredOpencodeConfigAgents), ...filteredConfigAgents, build: { ...migratedBuild, mode: "subagent", hidden: true }, ...(planDemoteConfig ? { plan: planDemoteConfig } : {}), @@ -288,6 +340,22 @@ export async function applyAgentConfig(params: { pluginAgents, protectedBuiltinAgentNames, ); + const filteredOpencodeGlobalAgents = filterProtectedAgentOverrides( + opencodeGlobalAgents, + protectedBuiltinAgentNames, + ); + const filteredOpencodeProjectAgents = filterProtectedAgentOverrides( + opencodeProjectAgents, + protectedBuiltinAgentNames, + ); + const filteredAgentDefinitionAgents = filterProtectedAgentOverrides( + agentDefinitionAgents, + protectedBuiltinAgentNames, + ); + const filteredOpencodeConfigAgents = filterProtectedAgentOverrides( + opencodeConfigAgents, + protectedBuiltinAgentNames, + ); const defaultedConfigAgents = configAgent ? Object.fromEntries( @@ -302,9 +370,14 @@ export async function applyAgentConfig(params: { params.config.agent = { ...builtinAgents, - ...filterDisabledAgents(filteredUserAgents), - ...filterDisabledAgents(filteredProjectAgents), + // Precedence: later entries override earlier (project > global > user > plugin) ...filterDisabledAgents(filteredPluginAgents), + ...filterDisabledAgents(filteredUserAgents), + ...filterDisabledAgents(filteredOpencodeGlobalAgents), + ...filterDisabledAgents(filteredProjectAgents), + ...filterDisabledAgents(filteredOpencodeProjectAgents), + ...filterDisabledAgents(filteredAgentDefinitionAgents), + ...filterDisabledAgents(filteredOpencodeConfigAgents), ...defaultedConfigAgents, }; } diff --git a/src/plugin-handlers/agent-key-remapper.test.ts b/src/plugin-handlers/agent-key-remapper.test.ts index 7640fbbf8..2153890c7 100644 --- a/src/plugin-handlers/agent-key-remapper.test.ts +++ b/src/plugin-handlers/agent-key-remapper.test.ts @@ -108,7 +108,7 @@ describe("remapAgentKeysToDisplayNames", () => { const agents = { sisyphus: { name: "sisyphus", prompt: "test", mode: "primary" }, hephaestus: { name: "hephaestus", prompt: "test", mode: "primary" }, - prometheus: { name: "prometheus", prompt: "test", mode: "all" }, + prometheus: { name: "prometheus", prompt: "test", mode: "primary" }, atlas: { name: "atlas", prompt: "test", mode: "primary" }, oracle: { name: "oracle", prompt: "test", mode: "subagent" }, } @@ -136,7 +136,7 @@ describe("remapAgentKeysToDisplayNames", () => { expect(result[getAgentListDisplayName("prometheus")]).toEqual({ name: getAgentRuntimeName("prometheus"), prompt: "test", - mode: "all", + mode: "primary", }) expect(result[getAgentListDisplayName("atlas")]).toEqual({ name: getAgentRuntimeName("atlas"), @@ -151,7 +151,7 @@ describe("remapAgentKeysToDisplayNames", () => { const agents = { sisyphus: { prompt: "test", mode: "primary" }, hephaestus: { prompt: "test", mode: "primary" }, - prometheus: { prompt: "test", mode: "all" }, + prometheus: { prompt: "test", mode: "primary" }, atlas: { prompt: "test", mode: "primary" }, } @@ -172,7 +172,7 @@ describe("remapAgentKeysToDisplayNames", () => { expect(result[getAgentListDisplayName("prometheus")]).toEqual({ name: getAgentRuntimeName("prometheus"), prompt: "test", - mode: "all", + mode: "primary", }) expect(result[getAgentListDisplayName("atlas")]).toEqual({ name: getAgentRuntimeName("atlas"), diff --git a/src/plugin-handlers/agent-priority-order.test.ts b/src/plugin-handlers/agent-priority-order.test.ts index 10890c3b0..d1af68a61 100644 --- a/src/plugin-handlers/agent-priority-order.test.ts +++ b/src/plugin-handlers/agent-priority-order.test.ts @@ -185,7 +185,7 @@ describe("agent-priority-order", () => { const agents: Record = { [sisyphus]: { name: "sisyphus", mode: "primary" }, [hephaestus]: { name: "hephaestus", mode: "primary" }, - [prometheus]: { name: "prometheus", mode: "all" }, + [prometheus]: { name: "prometheus", mode: "primary" }, [atlas]: { name: "atlas", mode: "primary" }, } @@ -195,7 +195,7 @@ describe("agent-priority-order", () => { // then expect(result[sisyphus]).toEqual({ name: "sisyphus", mode: "primary", order: 1 }) expect(result[hephaestus]).toEqual({ name: "hephaestus", mode: "primary", order: 2 }) - expect(result[prometheus]).toEqual({ name: "prometheus", mode: "all", order: 3 }) + expect(result[prometheus]).toEqual({ name: "prometheus", mode: "primary", order: 3 }) expect(result[atlas]).toEqual({ name: "atlas", mode: "primary", order: 4 }) }) diff --git a/src/plugin-handlers/config-handler.test.ts b/src/plugin-handlers/config-handler.test.ts index e6ac62c9e..629f97796 100644 --- a/src/plugin-handlers/config-handler.test.ts +++ b/src/plugin-handlers/config-handler.test.ts @@ -69,6 +69,8 @@ beforeEach(async () => { spyOn(agentLoader, "loadUserAgents" as any).mockReturnValue({}) spyOn(agentLoader, "loadProjectAgents" as any).mockReturnValue({}) + spyOn(agentLoader, "loadOpencodeGlobalAgents" as any).mockReturnValue({}) + spyOn(agentLoader, "loadOpencodeProjectAgents" as any).mockReturnValue({}) spyOn(mcpLoader, "loadMcpConfigs" as any).mockResolvedValue({ servers: {} }) setAdditionalAllowedMcpEnvVarsSpy = spyOn(mcpLoader, "setAdditionalAllowedMcpEnvVars").mockImplementation(() => {}) @@ -86,7 +88,7 @@ beforeEach(async () => { spyOn(mcpModule, "createBuiltinMcps" as any).mockReturnValue({}) 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(configDir, "getOpenCodeConfigPaths" as any).mockReturnValue({ @@ -96,7 +98,7 @@ beforeEach(async () => { spyOn(permissionCompat, "migrateAgentConfig" as any).mockImplementation((config: Record) => 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()) }) @@ -118,6 +120,8 @@ afterEach(() => { ;(skillLoader.discoverOpencodeProjectSkills as any)?.mockRestore?.() ;(agentLoader.loadUserAgents as any)?.mockRestore?.() ;(agentLoader.loadProjectAgents as any)?.mockRestore?.() + ;(agentLoader.loadOpencodeGlobalAgents as any)?.mockRestore?.() + ;(agentLoader.loadOpencodeProjectAgents as any)?.mockRestore?.() ;(mcpLoader.loadMcpConfigs as any)?.mockRestore?.() setAdditionalAllowedMcpEnvVarsSpy?.mockRestore() ;(pluginLoader.loadAllPluginComponents as any)?.mockRestore?.() @@ -200,7 +204,7 @@ describe("MCP env allowlist initialization", () => { mcp_env_allowlist: ["CUSTOM_API_KEY", "CUSTOM_AUTH_TOKEN"], }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -242,7 +246,7 @@ describe("Plan agent demote behavior", () => { }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -288,7 +292,7 @@ describe("Plan agent demote behavior", () => { }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -332,7 +336,7 @@ describe("Plan agent demote behavior", () => { }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -381,7 +385,7 @@ describe("Plan agent demote behavior", () => { }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: { plan: { name: "plan", @@ -418,7 +422,7 @@ describe("Plan agent demote behavior", () => { }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: { plan: { name: "plan", @@ -447,7 +451,7 @@ describe("Plan agent demote behavior", () => { expect(agents.plan.prompt).toBe("original plan prompt") }) - test("prometheus should have mode 'all' to be callable via task", async () => { + test("prometheus should have mode 'primary' like the other core agents", async () => { // given const pluginConfig = createPluginConfig({ sisyphus_agent: { @@ -455,7 +459,7 @@ describe("Plan agent demote behavior", () => { }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -474,7 +478,7 @@ describe("Plan agent demote behavior", () => { const agents = config.agent as Record const prometheusKey = getAgentListDisplayName("prometheus") expect(agents[prometheusKey]).toBeDefined() - expect(agents[prometheusKey].mode).toBe("all") + expect(agents[prometheusKey].mode).toBe("primary") }) }) @@ -491,7 +495,7 @@ describe("Agent permission defaults", () => { }) const pluginConfig = createPluginConfig({}) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -519,7 +523,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => { // given const pluginConfig = createPluginConfig({}) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", default_agent: " hephaestus ", agent: {}, } @@ -543,7 +547,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => { // given const pluginConfig = createPluginConfig({}) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", default_agent: "HePhAeStUs", agent: {}, } @@ -567,7 +571,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => { // #given const pluginConfig = createPluginConfig({}) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", default_agent: "hephaestus", agent: {}, } @@ -592,7 +596,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => { const pluginConfig = createPluginConfig({}) const displayName = getAgentListDisplayName("hephaestus") const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", default_agent: displayName, agent: {}, } @@ -616,7 +620,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => { // #given const pluginConfig = createPluginConfig({}) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -639,7 +643,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => { // given const pluginConfig = createPluginConfig({}) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", default_agent: "hephaestus", agent: {}, } @@ -663,7 +667,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => { // given const pluginConfig = createPluginConfig({}) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", default_agent: " ", agent: {}, } @@ -687,7 +691,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => { // given const pluginConfig = createPluginConfig({}) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", default_agent: " Custom Agent ", agent: {}, } @@ -715,7 +719,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => { }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", default_agent: " HePhAeStUs ", agent: {}, } @@ -857,7 +861,7 @@ describe("Prometheus direct override priority over category", () => { }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -898,7 +902,7 @@ describe("Prometheus direct override priority over category", () => { }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -940,7 +944,7 @@ describe("Prometheus direct override priority over category", () => { }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -976,7 +980,7 @@ describe("Prometheus direct override priority over category", () => { }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -1003,11 +1007,11 @@ describe("Prometheus direct override priority over category", () => { describe("Plan agent model inheritance from prometheus", () => { 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({ - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", variant: "max", - mode: "all", + mode: "primary", prompt: "prometheus prompt", }) const pluginConfig = createPluginConfig({ @@ -1017,7 +1021,7 @@ describe("Plan agent model inheritance from prometheus", () => { }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: { plan: { name: "plan", @@ -1043,7 +1047,7 @@ describe("Plan agent model inheritance from prometheus", () => { const agents = config.agent as Record expect(agents.plan).toBeDefined() 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.prompt).toBeUndefined() }) @@ -1074,7 +1078,7 @@ describe("Plan agent model inheritance from prometheus", () => { }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -1106,7 +1110,7 @@ describe("Plan agent model inheritance from prometheus", () => { test("plan agent user override takes priority over prometheus inherited settings", async () => { //#given - prometheus resolves to opus, but user has plan override for gpt-5.4 spyOn(shared, "resolveModelPipeline" as any).mockReturnValue({ - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", provenance: "provider-fallback", variant: "max", }) @@ -1124,7 +1128,7 @@ describe("Plan agent model inheritance from prometheus", () => { }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -1149,7 +1153,7 @@ describe("Plan agent model inheritance from prometheus", () => { test("plan agent does NOT inherit prompt, description, or color from prometheus", async () => { //#given spyOn(shared, "resolveModelPipeline" as any).mockReturnValue({ - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", provenance: "provider-fallback", variant: "max", }) @@ -1160,7 +1164,7 @@ describe("Plan agent model inheritance from prometheus", () => { }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -1177,7 +1181,7 @@ describe("Plan agent model inheritance from prometheus", () => { //#then - plan has model settings but NOT prompt/description/color const agents = config.agent as Record> - 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.description).toBeUndefined() expect(agents.plan.color).toBeUndefined() @@ -1196,7 +1200,7 @@ describe("Deadlock prevention - fetchAvailableModels must not receive client", ( }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const mockClient = { @@ -1229,7 +1233,7 @@ describe("config-handler plugin loading error boundary (#1559)", () => { spyOn(pluginLoader, "loadAllPluginComponents" as any).mockRejectedValue(new Error("crash")) const pluginConfig = createPluginConfig({}) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const { createConfigHandler: createFreshConfigHandler } = await importFreshConfigHandlerModule() @@ -1259,7 +1263,7 @@ describe("config-handler plugin loading error boundary (#1559)", () => { experimental: { plugin_load_timeout_ms: 100 }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const { createConfigHandler: createFreshConfigHandler } = await importFreshConfigHandlerModule() @@ -1285,7 +1289,7 @@ describe("config-handler plugin loading error boundary (#1559)", () => { spyOn(pluginLoader, "loadAllPluginComponents" as any).mockRejectedValue(new Error("crash")) const pluginConfig = createPluginConfig({}) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const { createConfigHandler: createFreshConfigHandler } = await importFreshConfigHandlerModule() @@ -1322,7 +1326,7 @@ describe("config-handler plugin loading error boundary (#1559)", () => { }) const pluginConfig = createPluginConfig({}) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const { createConfigHandler: createFreshConfigHandler } = await importFreshConfigHandlerModule() @@ -1366,7 +1370,7 @@ describe("command agent routing coherence", () => { }) const pluginConfig = createPluginConfig({}) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -1416,7 +1420,7 @@ describe("per-agent todowrite/todoread deny when task_system enabled", () => { experimental: { task_system: true }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -1454,7 +1458,7 @@ describe("per-agent todowrite/todoread deny when task_system enabled", () => { experimental: { task_system: false }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -1493,7 +1497,7 @@ describe("per-agent todowrite/todoread deny when task_system enabled", () => { const pluginConfig = createPluginConfig({}) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -1534,7 +1538,7 @@ describe("disable_omo_env pass-through", () => { experimental: { disable_omo_env: true }, }) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -1571,7 +1575,7 @@ describe("disable_omo_env pass-through", () => { const pluginConfig = createPluginConfig({}) const config: Record = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", agent: {}, } const handler = createConfigHandler({ @@ -1596,3 +1600,173 @@ describe("disable_omo_env pass-through", () => { expect(disableOmoEnv).toBe(false) }) }) + +describe("Agent merge priority — project-local overrides global", () => { + test("project-local Claude agent overrides global Claude agent with same name", async () => { + // #given — same agent name in both global (user) and project scopes + ;(agentLoader.loadUserAgents as any).mockReturnValue({ + "my-custom-agent": { + description: "(user) global version", + mode: "subagent", + prompt: "I am the global agent", + }, + }) + ;(agentLoader.loadProjectAgents as any).mockReturnValue({ + "my-custom-agent": { + description: "(project) project version", + mode: "subagent", + prompt: "I am the project agent", + }, + }) + + const pluginConfig: OhMyOpenCodeConfig = {} + const config: Record = { + model: "anthropic/claude-opus-4-7", + agent: {}, + } + const handler = createConfigHandler({ + ctx: { directory: "/tmp" }, + pluginConfig, + modelCacheState: { + anthropicContext1MEnabled: false, + modelContextLimitsCache: new Map(), + }, + }) + + // #when + await handler(config) + + // #then — project version wins + const agentConfig = config.agent as Record + expect(agentConfig["my-custom-agent"]?.description).toBe("(project) project version") + expect(agentConfig["my-custom-agent"]?.prompt).toBe("I am the project agent") + }) + + test("opencode project agent overrides opencode global agent with same name", async () => { + // #given — same agent name in opencode global vs opencode project + ;(agentLoader.loadOpencodeGlobalAgents as any).mockReturnValue({ + "my-custom-agent": { + description: "(opencode) global version", + mode: "subagent", + prompt: "I am the opencode global agent", + }, + }) + ;(agentLoader.loadOpencodeProjectAgents as any).mockReturnValue({ + "my-custom-agent": { + description: "(opencode-project) project version", + mode: "subagent", + prompt: "I am the opencode project agent", + }, + }) + + const pluginConfig: OhMyOpenCodeConfig = {} + const config: Record = { + model: "anthropic/claude-opus-4-7", + agent: {}, + } + const handler = createConfigHandler({ + ctx: { directory: "/tmp" }, + pluginConfig, + modelCacheState: { + anthropicContext1MEnabled: false, + modelContextLimitsCache: new Map(), + }, + }) + + // #when + await handler(config) + + // #then — opencode project version wins over opencode global + const agentConfig = config.agent as Record + expect(agentConfig["my-custom-agent"]?.description).toBe("(opencode-project) project version") + expect(agentConfig["my-custom-agent"]?.prompt).toBe("I am the opencode project agent") + }) + + test("project Claude agent overrides opencode global agent with same name", async () => { + // #given — project-scope Claude agent vs global-scope opencode agent + ;(agentLoader.loadOpencodeGlobalAgents as any).mockReturnValue({ + "my-custom-agent": { + description: "(opencode) global version", + mode: "subagent", + prompt: "I am the opencode global agent", + }, + }) + ;(agentLoader.loadProjectAgents as any).mockReturnValue({ + "my-custom-agent": { + description: "(project) project version", + mode: "subagent", + prompt: "I am the project Claude agent", + }, + }) + + const pluginConfig: OhMyOpenCodeConfig = {} + const config: Record = { + model: "anthropic/claude-opus-4-7", + agent: {}, + } + const handler = createConfigHandler({ + ctx: { directory: "/tmp" }, + pluginConfig, + modelCacheState: { + anthropicContext1MEnabled: false, + modelContextLimitsCache: new Map(), + }, + }) + + // #when + await handler(config) + + // #then — project-scope wins over global-scope regardless of format + const agentConfig = config.agent as Record + expect(agentConfig["my-custom-agent"]?.description).toBe("(project) project version") + expect(agentConfig["my-custom-agent"]?.prompt).toBe("I am the project Claude agent") + }) + + test("plugin agents have lowest priority — overridden by all other sources", async () => { + // #given — same agent in plugin, global, and project scopes + ;(pluginLoader.loadAllPluginComponents as any).mockResolvedValue({ + commands: {}, + skills: {}, + agents: { + "my-custom-agent": { + description: "plugin version", + mode: "subagent", + prompt: "I am the plugin agent", + }, + }, + mcpServers: {}, + hooksConfigs: [], + plugins: [], + errors: [], + }) + ;(agentLoader.loadUserAgents as any).mockReturnValue({ + "my-custom-agent": { + description: "(user) global version", + mode: "subagent", + prompt: "I am the user agent", + }, + }) + + const pluginConfig: OhMyOpenCodeConfig = {} + const config: Record = { + model: "anthropic/claude-opus-4-7", + agent: {}, + } + const handler = createConfigHandler({ + ctx: { directory: "/tmp" }, + pluginConfig, + modelCacheState: { + anthropicContext1MEnabled: false, + modelContextLimitsCache: new Map(), + }, + }) + + // #when + await handler(config) + + // #then — user (global) agent overrides plugin agent + const agentConfig = config.agent as Record + expect(agentConfig["my-custom-agent"]?.description).toBe("(user) global version") + expect(agentConfig["my-custom-agent"]?.prompt).toBe("I am the user agent") + }) +}) diff --git a/src/plugin-handlers/plan-model-inheritance.test.ts b/src/plugin-handlers/plan-model-inheritance.test.ts index 0c6cc77e8..5df73a9f8 100644 --- a/src/plugin-handlers/plan-model-inheritance.test.ts +++ b/src/plugin-handlers/plan-model-inheritance.test.ts @@ -11,16 +11,16 @@ describe("buildPlanDemoteConfig", () => { const result = buildPlanDemoteConfig(prometheusConfig, planOverride) //#then - expect(result).toEqual({ mode: "subagent" }) + expect(result).toEqual({ mode: "subagent", hidden: true }) }) test("extracts all model settings from prometheus config", () => { //#given const prometheusConfig = { name: "prometheus", - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", variant: "max", - mode: "all", + mode: "primary", prompt: "You are Prometheus...", permission: { edit: "allow" }, description: "Plan agent (Prometheus)", @@ -39,7 +39,7 @@ describe("buildPlanDemoteConfig", () => { //#then - picks model settings, NOT prompt/permission/description/color/name/mode 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.temperature).toBe(0.1) expect(result.top_p).toBe(0.95) @@ -58,7 +58,7 @@ describe("buildPlanDemoteConfig", () => { test("plan override takes priority over prometheus for all model settings", () => { //#given const prometheusConfig = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", variant: "max", temperature: 0.1, reasoningEffort: "high", @@ -83,7 +83,7 @@ describe("buildPlanDemoteConfig", () => { test("falls back to prometheus when plan override has partial settings", () => { //#given const prometheusConfig = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", variant: "max", temperature: 0.1, reasoningEffort: "high", @@ -105,14 +105,14 @@ describe("buildPlanDemoteConfig", () => { test("skips undefined values from both sources", () => { //#given const prometheusConfig = { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", } //#when const result = buildPlanDemoteConfig(prometheusConfig, undefined) //#then - expect(result).toEqual({ mode: "subagent", model: "anthropic/claude-opus-4-6" }) - expect(Object.keys(result)).toEqual(["mode", "model"]) + expect(result).toEqual({ mode: "subagent", hidden: true, model: "anthropic/claude-opus-4-7" }) + expect(Object.keys(result)).toEqual(["mode", "hidden", "model"]) }) }) diff --git a/src/plugin-handlers/plan-model-inheritance.ts b/src/plugin-handlers/plan-model-inheritance.ts index bb32483c5..b5545a85f 100644 --- a/src/plugin-handlers/plan-model-inheritance.ts +++ b/src/plugin-handlers/plan-model-inheritance.ts @@ -23,5 +23,5 @@ export function buildPlanDemoteConfig( } } - return { mode: "subagent" as const, ...modelSettings } + return { mode: "subagent" as const, hidden: true, ...modelSettings } } diff --git a/src/plugin-handlers/prometheus-agent-config-builder.test.ts b/src/plugin-handlers/prometheus-agent-config-builder.test.ts index 90fe578fe..8c77f562d 100644 --- a/src/plugin-handlers/prometheus-agent-config-builder.test.ts +++ b/src/plugin-handlers/prometheus-agent-config-builder.test.ts @@ -24,7 +24,7 @@ describe("buildPrometheusAgentConfig", () => { (category) => ({ model: `${category}/default-model` } as CategoryConfig) ); resolveModelPipelineSpy = spyOn(shared, "resolveModelPipeline").mockReturnValue({ - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", provenance: "provider-fallback", }); ;({ buildPrometheusAgentConfig } = await importFreshPrometheusAgentConfigBuilderModule()) @@ -42,7 +42,7 @@ describe("buildPrometheusAgentConfig", () => { describe("#when currentModel is NOT in Prometheus fallback chain", () => { test("falls through to fallback chain instead of using currentModel as override", async () => { // 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"; // when @@ -65,14 +65,14 @@ describe("buildPrometheusAgentConfig", () => { 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", () => { - 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 - const currentModel = "anthropic/claude-opus-4-6"; + const currentModel = "anthropic/claude-opus-4-7"; // when - should not throw and should produce a valid config const result = await buildPrometheusAgentConfig({ @@ -128,7 +128,7 @@ describe("buildPrometheusAgentConfig", () => { describe("#given explicit Prometheus model configured via plugin override", () => { test("explicit config wins over currentModel and fallback chain", async () => { // given - const currentModel = "anthropic/claude-opus-4-6"; + const currentModel = "anthropic/claude-opus-4-7"; const explicitModel = "custom-provider/custom-model"; // when @@ -163,7 +163,7 @@ describe("buildPrometheusAgentConfig", () => { describe("#given category with model configured", () => { test("category model wins when no explicit override", async () => { // given - const currentModel = "anthropic/claude-opus-4-6"; + const currentModel = "anthropic/claude-opus-4-7"; const categoryModel = "category-provider/category-model"; resolveCategoryConfigSpy.mockReturnValue({ @@ -264,7 +264,22 @@ describe("buildPrometheusAgentConfig", () => { }, }) ); - expect(result.model).toBe("anthropic/claude-opus-4-6"); + expect(result.model).toBe("anthropic/claude-opus-4-7"); }); }); + + test("returns Prometheus as a primary agent", async () => { + // given + + // when + const result = await buildPrometheusAgentConfig({ + configAgentPlan: undefined, + pluginPrometheusOverride: undefined, + userCategories: undefined, + currentModel: undefined, + }); + + // then + expect(result.mode).toBe("primary"); + }); }); diff --git a/src/plugin-handlers/prometheus-agent-config-builder.ts b/src/plugin-handlers/prometheus-agent-config-builder.ts index 620e9d721..640847b94 100644 --- a/src/plugin-handlers/prometheus-agent-config-builder.ts +++ b/src/plugin-handlers/prometheus-agent-config-builder.ts @@ -97,7 +97,7 @@ export async function buildPrometheusAgentConfig(params: { const base: Record = { ...(resolvedModel ? { model: resolvedModel } : {}), ...(variantToUse ? { variant: variantToUse } : {}), - mode: "all", + mode: "primary", prompt: getPrometheusPrompt(resolvedModel, params.disabledTools), permission: PROMETHEUS_PERMISSION, description: `${(params.configAgentPlan?.description as string) ?? "Plan agent"} (Prometheus - OhMyOpenCode)`, diff --git a/src/plugin/AGENTS.md b/src/plugin/AGENTS.md index c58d32000..94732c5b9 100644 --- a/src/plugin/AGENTS.md +++ b/src/plugin/AGENTS.md @@ -1,6 +1,6 @@ # src/plugin/ — 10 OpenCode Hook Handlers + Hook Composition -**Generated:** 2026-04-11 +**Generated:** 2026-04-18 ## OVERVIEW diff --git a/src/plugin/chat-message.test.ts b/src/plugin/chat-message.test.ts index 6ecac08bb..e2e813cd8 100644 --- a/src/plugin/chat-message.test.ts +++ b/src/plugin/chat-message.test.ts @@ -709,7 +709,7 @@ describe("createChatMessageHandler - TUI variant passthrough", () => { shouldOverride: false, pluginConfig: { 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, pluginConfig: { 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 () => { //#given 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 handler = createChatMessageHandler(args) const nextModel = { providerID: "openai", modelID: "gpt-5.4" } diff --git a/src/plugin/chat-params.test.ts b/src/plugin/chat-params.test.ts index f75c1a243..5886b7204 100644 --- a/src/plugin/chat-params.test.ts +++ b/src/plugin/chat-params.test.ts @@ -48,7 +48,7 @@ describe("createChatParamsHandler", () => { const input = { sessionID: "ses_chat_params", agent: { name: "sisyphus" }, - model: { providerID: "opencode", modelID: "claude-opus-4-6" }, + model: { providerID: "opencode", modelID: "claude-opus-4-7" }, provider: { id: "opencode" }, message: {}, } diff --git a/src/plugin/event-compaction-agent.test.ts b/src/plugin/event-compaction-agent.test.ts index b247b649e..b5888d7fe 100644 --- a/src/plugin/event-compaction-agent.test.ts +++ b/src/plugin/event-compaction-agent.test.ts @@ -73,7 +73,7 @@ describe("createEventHandler compaction agent filtering", () => { role: "user", agent: "compaction", time: { created: Date.now() }, - model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7" }, }, }, }, diff --git a/src/plugin/event.model-fallback-2941.test.ts b/src/plugin/event.model-fallback-2941.test.ts index 46765a5d9..2b97d2cb7 100644 --- a/src/plugin/event.model-fallback-2941.test.ts +++ b/src/plugin/event.model-fallback-2941.test.ts @@ -65,13 +65,13 @@ function createChatMessageHandlerHooks(modelFallback: ReturnType void } | undefined let readProviderModelsCacheSpy: { mockRestore: () => void } | undefined -afterEach(() => { - readConnectedProvidersCacheSpy?.mockRestore() - readProviderModelsCacheSpy?.mockRestore() - readConnectedProvidersCacheSpy = undefined - readProviderModelsCacheSpy = undefined - _resetForTesting() -}) + afterEach(() => { + readConnectedProvidersCacheSpy?.mockRestore() + readProviderModelsCacheSpy?.mockRestore() + readConnectedProvidersCacheSpy = undefined + readProviderModelsCacheSpy = undefined + _resetForTesting() + }) describe("createEventHandler - category runtime fallback suppression", () => { test("does not arm retry fallback when category session explicitly stores no fallback chain [regression #2941]", async () => { @@ -83,11 +83,10 @@ describe("createEventHandler - category runtime fallback suppression", () => { readConnectedProvidersCacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null) readProviderModelsCacheSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue(null) - clearPendingModelFallback(sessionID) - setSessionAgent(sessionID, "sisyphus-junior") - setSessionFallbackChain(sessionID, undefined) - const modelFallback = createModelFallbackHook() + clearPendingModelFallback(modelFallback, sessionID) + setSessionAgent(sessionID, "sisyphus-junior") + setSessionFallbackChain(modelFallback, sessionID, undefined) const eventHandler = createEventHandler({ ctx: asEventHandlerContext({ directory: "/tmp", diff --git a/src/plugin/event.model-fallback.test.ts b/src/plugin/event.model-fallback.test.ts index 481c5c419..967608f09 100644 --- a/src/plugin/event.model-fallback.test.ts +++ b/src/plugin/event.model-fallback.test.ts @@ -85,12 +85,12 @@ describe("createEventHandler - model fallback", () => { name: "APIError", data: { 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, }, }, parentID: "msg_user_1", - modelID: "claude-opus-4-6-thinking", + modelID: "claude-opus-4-7-thinking", providerID: "anthropic", mode: "Sisyphus - Ultraworker", agent: "Sisyphus - Ultraworker", @@ -125,7 +125,7 @@ describe("createEventHandler - model fallback", () => { data: { error: { 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\"}}", }, }, }, @@ -142,9 +142,8 @@ describe("createEventHandler - model fallback", () => { //#given const sessionID = "ses_status_retry_fallback" setMainSession(sessionID) - clearPendingModelFallback(sessionID) - const modelFallback = createModelFallbackHook() + clearPendingModelFallback(modelFallback, sessionID) const { handler, abortCalls, promptCalls } = createHandler({ hooks: { modelFallback } }) @@ -182,7 +181,7 @@ describe("createEventHandler - model fallback", () => { role: "user", time: { created: 1 }, content: [], - modelID: "claude-opus-4-6-thinking", + modelID: "claude-opus-4-7-thinking", providerID: "anthropic", agent: "Sisyphus - Ultraworker", path: { cwd: "/tmp", root: "/tmp" }, @@ -201,7 +200,7 @@ describe("createEventHandler - model fallback", () => { type: "retry", attempt: 1, 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, }, }, @@ -213,7 +212,7 @@ describe("createEventHandler - model fallback", () => { { sessionID, agent: "sisyphus", - model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" }, }, output, ) @@ -232,8 +231,8 @@ describe("createEventHandler - model fallback", () => { //#given const sessionID = "ses_status_retry_dedup" setMainSession(sessionID) - clearPendingModelFallback(sessionID) const modelFallback = createModelFallbackHook() + clearPendingModelFallback(modelFallback, sessionID) const { handler, abortCalls, promptCalls } = createHandler({ hooks: { modelFallback } }) await handler({ @@ -244,7 +243,7 @@ describe("createEventHandler - model fallback", () => { id: "msg_user_status_dedup", sessionID, role: "user", - modelID: "claude-opus-4-6-thinking", + modelID: "claude-opus-4-7-thinking", providerID: "anthropic", agent: "Sisyphus - Ultraworker", }, @@ -262,7 +261,7 @@ describe("createEventHandler - model fallback", () => { type: "retry", attempt: 1, 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, }, }, @@ -277,7 +276,7 @@ describe("createEventHandler - model fallback", () => { type: "retry", attempt: 1, 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, }, }, @@ -293,8 +292,8 @@ describe("createEventHandler - model fallback", () => { //#given const sessionID = "ses_status_retry_runtime_enabled" setMainSession(sessionID) - clearPendingModelFallback(sessionID) const modelFallback = createModelFallbackHook() + clearPendingModelFallback(modelFallback, sessionID) const runtimeFallback = { event: async () => {}, "chat.message": async () => {}, @@ -312,7 +311,7 @@ describe("createEventHandler - model fallback", () => { id: "msg_user_status_runtime_enabled", sessionID, role: "user", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", providerID: "quotio", agent: "Sisyphus - Ultraworker", }, @@ -330,7 +329,7 @@ describe("createEventHandler - model fallback", () => { type: "retry", attempt: 1, 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, }, }, @@ -346,9 +345,8 @@ describe("createEventHandler - model fallback", () => { //#given const sessionID = "ses_status_retry_user_fallback" setMainSession(sessionID) - clearPendingModelFallback(sessionID) - const modelFallback = createModelFallbackHook() + clearPendingModelFallback(modelFallback, sessionID) const pluginConfig = { agents: { sisyphus: { @@ -393,7 +391,7 @@ describe("createEventHandler - model fallback", () => { role: "user", time: { created: 1 }, content: [], - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", providerID: "quotio", agent: "Sisyphus - Ultraworker", path: { cwd: "/tmp", root: "/tmp" }, @@ -412,7 +410,7 @@ describe("createEventHandler - model fallback", () => { type: "retry", attempt: 1, 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, }, }, @@ -424,7 +422,7 @@ describe("createEventHandler - model fallback", () => { { sessionID, agent: "sisyphus", - model: { providerID: "quotio", modelID: "claude-opus-4-6" }, + model: { providerID: "quotio", modelID: "claude-opus-4-7" }, }, output, ) @@ -446,9 +444,8 @@ describe("createEventHandler - model fallback", () => { const toastCalls: string[] = [] const sessionID = "ses_main_fallback_chain" setMainSession(sessionID) - clearPendingModelFallback(sessionID) - const modelFallback = createModelFallbackHook() + clearPendingModelFallback(modelFallback, sessionID) setupConnectedProviderCacheMocks() const eventHandler = createEventHandler({ @@ -520,13 +517,13 @@ describe("createEventHandler - model fallback", () => { properties: { sessionID, providerID: "anthropic", - modelID: "claude-opus-4-6-thinking", + modelID: "claude-opus-4-7-thinking", error: { name: "UnknownError", data: { error: { 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 +536,7 @@ describe("createEventHandler - model fallback", () => { { sessionID, agent: "sisyphus", - model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" }, }, output, ) @@ -549,7 +546,7 @@ describe("createEventHandler - model fallback", () => { //#when - first retry cycle 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({ providerID: "opencode-go", modelID: "kimi-k2.5", @@ -590,12 +587,12 @@ describe("createEventHandler - model fallback", () => { name: "APIError", data: { 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, }, }, parentID: "msg_user_disabled_1", - modelID: "claude-opus-4-6-thinking", + modelID: "claude-opus-4-7-thinking", providerID: "anthropic", agent: "Sisyphus - Ultraworker", path: { cwd: "/tmp", root: "/tmp" }, @@ -617,7 +614,7 @@ describe("createEventHandler - model fallback", () => { data: { error: { 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\"}}", }, }, }, diff --git a/src/plugin/event.test.ts b/src/plugin/event.test.ts index 85223806c..ea880c145 100644 --- a/src/plugin/event.test.ts +++ b/src/plugin/event.test.ts @@ -761,11 +761,10 @@ describe("createEventHandler - retry dedupe lifecycle", () => { //#given const sessionID = "ses_retry_recovery_rearm" setMainSession(sessionID) - clearPendingModelFallback(sessionID) - const abortCalls: string[] = [] const promptCalls: string[] = [] const modelFallback = createModelFallbackHook() + clearPendingModelFallback(modelFallback, sessionID) const eventHandler = createEventHandler({ ctx: asEventHandlerContext({ @@ -826,7 +825,7 @@ describe("createEventHandler - retry dedupe lifecycle", () => { const retryStatus = { type: "retry", 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, } as const @@ -838,7 +837,7 @@ describe("createEventHandler - retry dedupe lifecycle", () => { id: "msg_user_retry_rearm", sessionID, role: "user", - modelID: "claude-opus-4-6-thinking", + modelID: "claude-opus-4-7-thinking", providerID: "anthropic", agent: "Sisyphus - Ultraworker", }, @@ -862,7 +861,7 @@ describe("createEventHandler - retry dedupe lifecycle", () => { { sessionID, agent: "sisyphus", - model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" }, }, firstOutput, ) diff --git a/src/plugin/event.ts b/src/plugin/event.ts index 34bc730e1..5a5f177b6 100644 --- a/src/plugin/event.ts +++ b/src/plugin/event.ts @@ -15,6 +15,7 @@ import { clearSessionFallbackChain, setSessionFallbackChain, setPendingModelFallback, + type ModelFallbackHook, } from "../hooks/model-fallback/hook"; import { getRawFallbackModels } from "../hooks/runtime-fallback/fallback-models"; import { @@ -111,6 +112,7 @@ function extractProviderModelFromErrorMessage(message: string): { providerID?: s return {}; } function applyUserConfiguredFallbackChain( + modelFallback: Pick | null | undefined, sessionID: string, agentName: string, currentProviderID: string, @@ -123,7 +125,9 @@ function applyUserConfiguredFallbackChain( const fallbackChain = buildFallbackChainFromModels(rawFallbackModels, currentProviderID); if (fallbackChain && fallbackChain.length > 0) { - setSessionFallbackChain(sessionID, fallbackChain); + if (modelFallback) { + setSessionFallbackChain(modelFallback, sessionID, fallbackChain); + } } } @@ -170,6 +174,7 @@ export function createEventHandler(args: { const isModelFallbackEnabled = hooks.modelFallback !== null && hooks.modelFallback !== undefined; + const modelFallback = hooks.modelFallback; // Avoid triggering multiple abort+continue cycles for the same failing assistant message. const lastHandledModelErrorMessageID = new Map(); @@ -408,8 +413,10 @@ export function createEventHandler(args: { lastHandledModelErrorMessageID.delete(sessionInfo.id); lastHandledRetryStatusKey.delete(sessionInfo.id); lastKnownModelBySession.delete(sessionInfo.id); - clearPendingModelFallback(sessionInfo.id); - clearSessionFallbackChain(sessionInfo.id); + if (modelFallback) { + clearPendingModelFallback(modelFallback, sessionInfo.id); + clearSessionFallbackChain(modelFallback, sessionInfo.id); + } resetMessageCursor(sessionInfo.id); clearBackgroundOutputConsumptionsForParentSession(sessionInfo.id); clearBackgroundOutputConsumptionsForTaskSession(sessionInfo.id); @@ -515,11 +522,13 @@ export function createEventHandler(args: { sessionID, 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); - applyUserConfiguredFallbackChain(sessionID, agentName, currentProvider, args.pluginConfig); + applyUserConfiguredFallbackChain(modelFallback, sessionID, agentName, currentProvider, args.pluginConfig); - const setFallback = setPendingModelFallback(sessionID, agentName, currentProvider, currentModel); + const setFallback = modelFallback + ? setPendingModelFallback(modelFallback, sessionID, agentName, currentProvider, currentModel) + : false; if ( setFallback && @@ -578,11 +587,13 @@ export function createEventHandler(args: { const parsed = extractProviderModelFromErrorMessage(retryMessage); const lastKnown = lastKnownModelBySession.get(sessionID); 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); - applyUserConfiguredFallbackChain(sessionID, agentName, currentProvider, args.pluginConfig); + applyUserConfiguredFallbackChain(modelFallback, sessionID, agentName, currentProvider, args.pluginConfig); - const setFallback = setPendingModelFallback(sessionID, agentName, currentProvider, currentModel); + const setFallback = modelFallback + ? setPendingModelFallback(modelFallback, sessionID, agentName, currentProvider, currentModel) + : false; if ( setFallback && @@ -664,11 +675,13 @@ export function createEventHandler(args: { sessionID, (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); - applyUserConfiguredFallbackChain(sessionID, agentName, currentProvider, args.pluginConfig); + applyUserConfiguredFallbackChain(modelFallback, sessionID, agentName, currentProvider, args.pluginConfig); - const setFallback = setPendingModelFallback(sessionID, agentName, currentProvider, currentModel); + const setFallback = modelFallback + ? setPendingModelFallback(modelFallback, sessionID, agentName, currentProvider, currentModel) + : false; if ( setFallback && diff --git a/src/plugin/fallback.cliproxyapi-matrix.test.ts b/src/plugin/fallback.cliproxyapi-matrix.test.ts index 30535a880..3d1b5fd4c 100644 --- a/src/plugin/fallback.cliproxyapi-matrix.test.ts +++ b/src/plugin/fallback.cliproxyapi-matrix.test.ts @@ -9,7 +9,6 @@ import { createModelFallbackHook } from "../hooks/model-fallback/hook" import { createRuntimeFallbackHook } from "../hooks/runtime-fallback" import type { RuntimeFallbackPluginInput } from "../hooks/runtime-fallback/types" import { _resetForTesting } from "../features/claude-code-session-state" -import { _resetForTesting as _resetModelFallbackForTesting } from "../hooks/model-fallback/hook" import { SessionCategoryRegistry } from "../shared/session-category-registry" import * as connectedProvidersCache from "../shared/connected-providers-cache" @@ -59,7 +58,7 @@ function createChatMessageHandlerHooks( const PRIMARY_MODEL = { providerID: PROVIDER_ID, - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", } const PRIMARY_MODEL_STRING = `${PRIMARY_MODEL.providerID}/${PRIMARY_MODEL.modelID}` @@ -313,7 +312,7 @@ async function triggerSessionStatusRetry( type: "retry", attempt: 1, 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, }, }, @@ -369,7 +368,6 @@ function setupConnectedProviderCacheMocks(): void { afterEach(() => { _resetForTesting() - _resetModelFallbackForTesting() SessionCategoryRegistry.clear() }) diff --git a/src/plugin/hooks/create-core-hooks.ts b/src/plugin/hooks/create-core-hooks.ts index 4da2b5085..5a36aa026 100644 --- a/src/plugin/hooks/create-core-hooks.ts +++ b/src/plugin/hooks/create-core-hooks.ts @@ -1,4 +1,5 @@ import type { HookName, OhMyOpenCodeConfig } from "../../config" +import type { ModelFallbackControllerAccessor } from "../../hooks/model-fallback" import type { PluginContext } from "../types" import type { ModelCacheState } from "../../plugin-state" @@ -10,15 +11,17 @@ export function createCoreHooks(args: { ctx: PluginContext pluginConfig: OhMyOpenCodeConfig modelCacheState: ModelCacheState + modelFallbackControllerAccessor?: ModelFallbackControllerAccessor isHookEnabled: (hookName: HookName) => boolean safeHookEnabled: boolean }) { - const { ctx, pluginConfig, modelCacheState, isHookEnabled, safeHookEnabled } = args + const { ctx, pluginConfig, modelCacheState, modelFallbackControllerAccessor, isHookEnabled, safeHookEnabled } = args const session = createSessionHooks({ ctx, pluginConfig, modelCacheState, + modelFallbackControllerAccessor, isHookEnabled, safeHookEnabled, }) diff --git a/src/plugin/hooks/create-session-hooks.ts b/src/plugin/hooks/create-session-hooks.ts index af87bd366..9d437bc75 100644 --- a/src/plugin/hooks/create-session-hooks.ts +++ b/src/plugin/hooks/create-session-hooks.ts @@ -1,4 +1,5 @@ import type { OhMyOpenCodeConfig, HookName } from "../../config" +import type { ModelFallbackControllerAccessor } from "../../hooks/model-fallback" import type { ModelCacheState } from "../../plugin-state" import type { PluginContext } from "../types" @@ -69,10 +70,11 @@ export function createSessionHooks(args: { ctx: PluginContext pluginConfig: OhMyOpenCodeConfig modelCacheState: ModelCacheState + modelFallbackControllerAccessor?: ModelFallbackControllerAccessor isHookEnabled: (hookName: HookName) => boolean safeHookEnabled: boolean }): SessionHooks { - const { ctx, pluginConfig, modelCacheState, isHookEnabled, safeHookEnabled } = args + const { ctx, pluginConfig, modelCacheState, modelFallbackControllerAccessor, isHookEnabled, safeHookEnabled } = args const safeHook = (hookName: HookName, factory: () => T): T | null => safeCreateHook(hookName, factory, { enabled: safeHookEnabled }) @@ -171,6 +173,7 @@ export function createSessionHooks(args: { .catch(() => {}) }, onApplied: enableFallbackTitle ? updateFallbackTitle : undefined, + controllerAccessor: modelFallbackControllerAccessor, })) : null diff --git a/src/plugin/tool-execute-after.test.ts b/src/plugin/tool-execute-after.test.ts index cb70a91f8..7c8e9d87c 100644 --- a/src/plugin/tool-execute-after.test.ts +++ b/src/plugin/tool-execute-after.test.ts @@ -1,7 +1,13 @@ -import { describe, expect, it } from "bun:test" +import { beforeEach, describe, expect, it } from "bun:test" + +import { clearPendingStore, storeToolMetadata } from "../features/tool-metadata-store" import { createToolExecuteAfterHandler } from "./tool-execute-after" describe("createToolExecuteAfterHandler", () => { + beforeEach(() => { + clearPendingStore() + }) + it("#given truncator changes output #when tool.execute.after runs #then claudeCodeHooks receives truncated output", async () => { const callOrder: string[] = [] let claudeSawOutput = "" @@ -32,4 +38,58 @@ describe("createToolExecuteAfterHandler", () => { expect(callOrder).toEqual(["truncator", "claude"]) expect(claudeSawOutput).toBe("truncated output") }) + + it("#given stored metadata with legacy call id casing #when tool.execute.after runs #then it restores the stored metadata", async () => { + // given + storeToolMetadata("ses_parent", "call_legacy", { + title: "stored title", + metadata: { sessionId: "ses_child", agent: "oracle" }, + }) + + const handler = createToolExecuteAfterHandler({ + ctx: { directory: "/repo" } as never, + hooks: {} as never, + }) + + const output = { title: "result", output: "original output", metadata: { truncated: true } } + + // when + await handler( + { tool: "hashline_edit", sessionID: "ses_parent", callId: " call_legacy " }, + output + ) + + // then + expect(output.title).toBe("stored title") + expect(output.metadata).toEqual({ truncated: true, sessionId: "ses_child", agent: "oracle" }) + }) + + it("#given native session metadata #when stored metadata exists #then stored metadata does not overwrite native session linkage", async () => { + // given + storeToolMetadata("ses_parent", "call_native", { + title: "stored title", + metadata: { sessionId: "ses_stored", agent: "oracle" }, + }) + + const handler = createToolExecuteAfterHandler({ + ctx: { directory: "/repo" } as never, + hooks: {} as never, + }) + + const output = { + title: "result", + output: "original output", + metadata: { sessionId: "ses_native", agent: "hephaestus" }, + } + + // when + await handler( + { tool: "hashline_edit", sessionID: "ses_parent", callID: "call_native" }, + output + ) + + // then + expect(output.title).toBe("stored title") + expect(output.metadata).toEqual({ sessionId: "ses_native", agent: "hephaestus" }) + }) }) diff --git a/src/plugin/tool-execute-after.ts b/src/plugin/tool-execute-after.ts index fb1be3aa4..19bb724d7 100644 --- a/src/plugin/tool-execute-after.ts +++ b/src/plugin/tool-execute-after.ts @@ -1,8 +1,8 @@ -import { consumeToolMetadata } from "../features/tool-metadata-store" +import { recoverToolMetadata } from "../features/tool-metadata-store" import type { CreatedHooks } from "../create-hooks" -import { log } from "../shared" +import { log } from "../shared/logger" +import { stripInvisibleAgentCharacters } from "../shared/agent-display-names" import type { PluginContext } from "./types" -import { readState, writeState } from "../hooks/ralph-loop/storage" const VERIFICATION_ATTEMPT_PATTERN = /(.*?)<\/ulw_verification_attempt_id>/i @@ -36,20 +36,45 @@ export function createToolExecuteAfterHandler(args: { ) => Promise { const { ctx, hooks } = args + // OpenCode injects tool call ids into execute() context and after-hook input via undocumented runtime fields. + // We must treat their identity as a best-effort correlation key, not a guaranteed public contract. + return async ( - input: { tool: string; sessionID: string; callID: string }, + input: { tool: string; sessionID: string; callID?: string; callId?: string; call_id?: string }, output: { title: string; output: string; metadata: Record } | undefined, ): Promise => { if (!output) return - const stored = consumeToolMetadata(input.sessionID, input.callID) + const hookInput = { + tool: input.tool, + sessionID: input.sessionID, + callID: input.callID ?? input.callId ?? input.call_id ?? "", + } + + const nativeSessionId = getMetadataString(output.metadata, ["sessionId", "sessionID", "session_id"]) + const stored = recoverToolMetadata(input.sessionID, input) if (stored) { if (stored.title) { output.title = stored.title } if (stored.metadata) { - output.metadata = { ...output.metadata, ...stored.metadata } + if (nativeSessionId) { + log("[tool-execute-after] Native output metadata already includes session linkage; skipping stored metadata overwrite", { + tool: input.tool, + sessionID: input.sessionID, + callID: input.callID ?? input.callId ?? input.call_id, + nativeSessionId, + }) + } else { + output.metadata = { ...output.metadata, ...stored.metadata } + } } + } else if (!nativeSessionId) { + log("[tool-execute-after] Unable to recover stored metadata and no native session linkage was present", { + tool: input.tool, + sessionID: input.sessionID, + callID: input.callID ?? input.callId ?? input.call_id, + }) } if (input.tool === "task") { @@ -58,9 +83,11 @@ export function createToolExecuteAfterHandler(args: { const agent = getMetadataString(output.metadata, ["agent"]) const prompt = getMetadataString(output.metadata, ["prompt"]) const verificationAttemptId = prompt?.match(VERIFICATION_ATTEMPT_PATTERN)?.[1]?.trim() - const loopState = directory ? readState(directory) : null + const loopState = directory + ? (await import("../hooks/ralph-loop/storage")).readState(directory) + : null const isVerificationContext = - agent === "oracle" + (agent ? stripInvisibleAgentCharacters(agent) : agent) === "oracle" && !!sessionId && !!directory && loopState?.active === true @@ -82,7 +109,7 @@ export function createToolExecuteAfterHandler(args: { && verificationAttemptId && loopState.verification_attempt_id === verificationAttemptId ) { - writeState(directory, { + ;(await import("../hooks/ralph-loop/storage")).writeState(directory, { ...loopState, verification_session_id: sessionId, }) @@ -92,7 +119,7 @@ export function createToolExecuteAfterHandler(args: { verificationAttemptId, }) } else if (isVerificationContext && !verificationAttemptId) { - writeState(directory, { + ;(await import("../hooks/ralph-loop/storage")).writeState(directory, { ...loopState, verification_session_id: sessionId, }) @@ -107,26 +134,26 @@ export function createToolExecuteAfterHandler(args: { } const runToolExecuteAfterHooks = async (): Promise => { - await hooks.toolOutputTruncator?.["tool.execute.after"]?.(input, output) - await hooks.claudeCodeHooks?.["tool.execute.after"]?.(input, output) - await hooks.preemptiveCompaction?.["tool.execute.after"]?.(input, output) - await hooks.contextWindowMonitor?.["tool.execute.after"]?.(input, output) - await hooks.commentChecker?.["tool.execute.after"]?.(input, output) - await hooks.directoryAgentsInjector?.["tool.execute.after"]?.(input, output) - await hooks.directoryReadmeInjector?.["tool.execute.after"]?.(input, output) - await hooks.rulesInjector?.["tool.execute.after"]?.(input, output) - await hooks.emptyTaskResponseDetector?.["tool.execute.after"]?.(input, output) - await hooks.agentUsageReminder?.["tool.execute.after"]?.(input, output) - await hooks.categorySkillReminder?.["tool.execute.after"]?.(input, output) - await hooks.interactiveBashSession?.["tool.execute.after"]?.(input, output) - await hooks.editErrorRecovery?.["tool.execute.after"]?.(input, output) - await hooks.delegateTaskRetry?.["tool.execute.after"]?.(input, output) - await hooks.atlasHook?.["tool.execute.after"]?.(input, output) - await hooks.taskResumeInfo?.["tool.execute.after"]?.(input, output) - await hooks.readImageResizer?.["tool.execute.after"]?.(input, output) - await hooks.hashlineReadEnhancer?.["tool.execute.after"]?.(input, output) - await hooks.webfetchRedirectGuard?.["tool.execute.after"]?.(input, output) - await hooks.jsonErrorRecovery?.["tool.execute.after"]?.(input, output) + await hooks.toolOutputTruncator?.["tool.execute.after"]?.(hookInput, output) + await hooks.claudeCodeHooks?.["tool.execute.after"]?.(hookInput, output) + await hooks.preemptiveCompaction?.["tool.execute.after"]?.(hookInput, output) + await hooks.contextWindowMonitor?.["tool.execute.after"]?.(hookInput, output) + await hooks.commentChecker?.["tool.execute.after"]?.(hookInput, output) + await hooks.directoryAgentsInjector?.["tool.execute.after"]?.(hookInput, output) + await hooks.directoryReadmeInjector?.["tool.execute.after"]?.(hookInput, output) + await hooks.rulesInjector?.["tool.execute.after"]?.(hookInput, output) + await hooks.emptyTaskResponseDetector?.["tool.execute.after"]?.(hookInput, output) + await hooks.agentUsageReminder?.["tool.execute.after"]?.(hookInput, output) + await hooks.categorySkillReminder?.["tool.execute.after"]?.(hookInput, output) + await hooks.interactiveBashSession?.["tool.execute.after"]?.(hookInput, output) + await hooks.editErrorRecovery?.["tool.execute.after"]?.(hookInput, output) + await hooks.delegateTaskRetry?.["tool.execute.after"]?.(hookInput, output) + await hooks.atlasHook?.["tool.execute.after"]?.(hookInput, output) + await hooks.taskResumeInfo?.["tool.execute.after"]?.(hookInput, output) + await hooks.readImageResizer?.["tool.execute.after"]?.(hookInput, output) + await hooks.hashlineReadEnhancer?.["tool.execute.after"]?.(hookInput, output) + await hooks.webfetchRedirectGuard?.["tool.execute.after"]?.(hookInput, output) + await hooks.jsonErrorRecovery?.["tool.execute.after"]?.(hookInput, output) } if (input.tool === "extract" || input.tool === "discard") { @@ -145,7 +172,7 @@ export function createToolExecuteAfterHandler(args: { log("[tool-execute-after] Failed to process extract/discard hooks", { tool: input.tool, sessionID: input.sessionID, - callID: input.callID, + callID: input.callID ?? input.callId ?? input.call_id, error, }) } diff --git a/src/plugin/tool-execute-before.test.ts b/src/plugin/tool-execute-before.test.ts index 80daa79a8..76d11a33b 100644 --- a/src/plugin/tool-execute-before.test.ts +++ b/src/plugin/tool-execute-before.test.ts @@ -143,7 +143,7 @@ describe("createToolExecuteBeforeHandler", () => { expect(output.args.subagent_type).toBe("sisyphus-junior") }) - test("resolves subagent_type from session first message when session_id provided without subagent_type", async () => { + test("resolves subagent_type from session first message when task_id is provided without subagent_type", async () => { //#given const ctx = createCtxWithSessionMessages([ { info: { role: "user" } }, @@ -152,7 +152,7 @@ describe("createToolExecuteBeforeHandler", () => { ]) const handler = createToolExecuteBeforeHandler({ ctx, hooks: emptyHooks }) const input = { tool: "task", sessionID: "ses_123", callID: "call_1" } - const output = { args: { session_id: "ses_abc123", description: "Continue task", prompt: "fix it" } as Record } + const output = { args: { task_id: "ses_abc123", description: "Continue task", prompt: "fix it" } as Record } //#when await handler(input, output) @@ -161,6 +161,23 @@ describe("createToolExecuteBeforeHandler", () => { expect(output.args.subagent_type).toBe("explore") }) + test("normalizes task_id into the canonical resume argument", async () => { + //#given + const ctx = createCtxWithSessionMessages([ + { info: { role: "assistant", agent: "oracle" } }, + ]) + const handler = createToolExecuteBeforeHandler({ ctx, hooks: emptyHooks }) + const input = { tool: "task", sessionID: "ses_123", callID: "call_1" } + const output = { args: { task_id: "ses_resume_123", description: "Continue task", prompt: "fix it" } as Record } + + //#when + await handler(input, output) + + //#then + expect(output.args.task_id).toBe("ses_resume_123") + expect(output.args.subagent_type).toBe("oracle") + }) + test("falls back to 'continue' when session has no agent info", async () => { //#given const ctx = createCtxWithSessionMessages([ @@ -169,7 +186,7 @@ describe("createToolExecuteBeforeHandler", () => { ]) const handler = createToolExecuteBeforeHandler({ ctx, hooks: emptyHooks }) const input = { tool: "task", sessionID: "ses_123", callID: "call_1" } - const output = { args: { session_id: "ses_abc123", description: "Continue task", prompt: "fix it" } as Record } + const output = { args: { task_id: "ses_abc123", description: "Continue task", prompt: "fix it" } as Record } //#when await handler(input, output) @@ -178,12 +195,12 @@ describe("createToolExecuteBeforeHandler", () => { expect(output.args.subagent_type).toBe("continue") }) - test("preserves subagent_type when session_id is provided with explicit subagent_type", async () => { + test("preserves subagent_type when task_id is provided with explicit subagent_type", async () => { //#given const ctx = createCtxWithSessionMessages() const handler = createToolExecuteBeforeHandler({ ctx, hooks: emptyHooks }) const input = { tool: "task", sessionID: "ses_123", callID: "call_1" } - const output = { args: { session_id: "ses_abc123", subagent_type: "explore", description: "Continue explore" } as Record } + const output = { args: { task_id: "ses_abc123", subagent_type: "explore", description: "Continue explore" } as Record } //#when await handler(input, output) @@ -206,7 +223,7 @@ describe("createToolExecuteBeforeHandler", () => { expect(output.args.subagent_type).toBeUndefined() }) - test("does not set subagent_type when neither category nor session_id is provided and subagent_type is present", async () => { + test("does not set subagent_type when neither category nor task_id is provided and subagent_type is present", async () => { //#given const ctx = createCtxWithSessionMessages() const handler = createToolExecuteBeforeHandler({ ctx, hooks: emptyHooks }) diff --git a/src/plugin/tool-execute-before.ts b/src/plugin/tool-execute-before.ts index 3649720b9..5c54fba7b 100644 --- a/src/plugin/tool-execute-before.ts +++ b/src/plugin/tool-execute-before.ts @@ -4,6 +4,7 @@ import { randomUUID } from "node:crypto" import { getMainSessionID } from "../features/claude-code-session-state" import { clearBoulderState } from "../features/boulder-state" import { log } from "../shared" +import { stripInvisibleAgentCharacters } from "../shared/agent-display-names" import { resolveSessionAgent } from "./session-agent-resolver" import { parseRalphLoopArguments } from "../hooks/ralph-loop/command-arguments" import { ULTRAWORK_VERIFICATION_PROMISE } from "../hooks/ralph-loop/constants" @@ -99,17 +100,17 @@ export function createToolExecuteBeforeHandler(args: { const argsObject = output.args const category = typeof argsObject.category === "string" ? argsObject.category : undefined const subagentType = typeof argsObject.subagent_type === "string" ? argsObject.subagent_type : undefined - const sessionId = typeof argsObject.session_id === "string" ? argsObject.session_id : undefined + const taskId = typeof argsObject.task_id === "string" ? argsObject.task_id : undefined if (category) { argsObject.subagent_type = "sisyphus-junior" - } else if (!subagentType && sessionId) { - const resolvedAgent = await resolveSessionAgent(ctx.client, sessionId) + } else if (!subagentType && taskId) { + const resolvedAgent = await resolveSessionAgent(ctx.client, taskId) argsObject.subagent_type = resolvedAgent ?? "continue" } const normalizedSubagentType = - typeof argsObject.subagent_type === "string" ? argsObject.subagent_type : undefined + typeof argsObject.subagent_type === "string" ? stripInvisibleAgentCharacters(argsObject.subagent_type) : undefined const prompt = typeof argsObject.prompt === "string" ? argsObject.prompt : "" const loopState = typeof ctx.directory === "string" ? readState(ctx.directory) : null const shouldInjectOracleVerification = diff --git a/src/plugin/tool-registry.ts b/src/plugin/tool-registry.ts index 6d04e7e1c..a3e46185a 100644 --- a/src/plugin/tool-registry.ts +++ b/src/plugin/tool-registry.ts @@ -144,7 +144,7 @@ export function trimToolsToCap(filteredTools: ToolsRecord, maxTools: number): vo export function createToolRegistry(args: { ctx: PluginContext pluginConfig: OhMyOpenCodeConfig - managers: Pick + managers: Pick skillContext: SkillContext availableCategories: AvailableCategory[] interactiveBashEnabled?: boolean @@ -170,6 +170,7 @@ export function createToolRegistry(args: { pluginConfig.disabled_agents ?? [], pluginConfig.agents, pluginConfig.categories, + managers.modelFallbackControllerAccessor, ) const isMultimodalLookerEnabled = !(pluginConfig.disabled_agents ?? []).some( @@ -191,6 +192,7 @@ export function createToolRegistry(args: { availableSkills: skillContext.availableSkills, sisyphusAgentConfig: pluginConfig.sisyphus_agent, syncPollTimeoutMs: pluginConfig.background_task?.syncPollTimeoutMs, + modelFallbackControllerAccessor: managers.modelFallbackControllerAccessor, onSyncSessionCreated: async (event) => { log("[index] onSyncSessionCreated callback", { sessionID: event.sessionID, diff --git a/src/plugin/ultrawork-db-model-override.test.ts b/src/plugin/ultrawork-db-model-override.test.ts index ea5646d26..84c7ffed0 100644 --- a/src/plugin/ultrawork-db-model-override.test.ts +++ b/src/plugin/ultrawork-db-model-override.test.ts @@ -112,13 +112,13 @@ describe("scheduleDeferredModelOverride", () => { //#when scheduleDeferredModelOverride( "msg_001", - { providerID: "anthropic", modelID: "claude-opus-4-6" }, + { providerID: "anthropic", modelID: "claude-opus-4-7" }, ) await flushMicrotasks(5) //#then 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 () => { @@ -128,7 +128,7 @@ describe("scheduleDeferredModelOverride", () => { //#when scheduleDeferredModelOverride( "msg_002", - { providerID: "anthropic", modelID: "claude-opus-4-6" }, + { providerID: "anthropic", modelID: "claude-opus-4-7" }, "max", ) await flushMicrotasks(5) @@ -144,7 +144,7 @@ describe("scheduleDeferredModelOverride", () => { //#when scheduleDeferredModelOverride( "msg_nonexistent", - { providerID: "anthropic", modelID: "claude-opus-4-6" }, + { providerID: "anthropic", modelID: "claude-opus-4-7" }, ) await flushWithTimeout() @@ -162,13 +162,13 @@ describe("scheduleDeferredModelOverride", () => { //#when scheduleDeferredModelOverride( "msg_003", - { providerID: "anthropic", modelID: "claude-opus-4-6" }, + { providerID: "anthropic", modelID: "claude-opus-4-7" }, ) await flushMicrotasks(5) //#then 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", "thinking")).toBeNull() }) @@ -180,7 +180,7 @@ describe("scheduleDeferredModelOverride", () => { //#when scheduleDeferredModelOverride( "msg_004", - { providerID: "anthropic", modelID: "claude-opus-4-6" }, + { providerID: "anthropic", modelID: "claude-opus-4-7" }, ) await flushMicrotasks(5) @@ -200,7 +200,7 @@ describe("scheduleDeferredModelOverride", () => { //#when scheduleDeferredModelOverride( "msg_corrupt", - { providerID: "anthropic", modelID: "claude-opus-4-6" }, + { providerID: "anthropic", modelID: "claude-opus-4-7" }, ) await flushMicrotasks(5) diff --git a/src/plugin/ultrawork-model-override.test.ts b/src/plugin/ultrawork-model-override.test.ts index feaf369c1..b37dc1285 100644 --- a/src/plugin/ultrawork-model-override.test.ts +++ b/src/plugin/ultrawork-model-override.test.ts @@ -79,19 +79,19 @@ describe("resolveUltraworkOverride", () => { test("should resolve override when ultrawork keyword detected", () => { //#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") //#when const result = resolveUltraworkOverride(config, "sisyphus", output) //#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", () => { //#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") //#when @@ -103,7 +103,7 @@ describe("resolveUltraworkOverride", () => { test("should return null when agent name is undefined", () => { //#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") //#when @@ -115,14 +115,14 @@ describe("resolveUltraworkOverride", () => { test("should use message.agent when input agent is undefined", () => { //#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") //#when const result = resolveUltraworkOverride(config, undefined, output) //#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", () => { @@ -189,19 +189,19 @@ describe("resolveUltraworkOverride", () => { test("should resolve display name to config key", () => { //#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") //#when const result = resolveUltraworkOverride(config, "Sisyphus - Ultraworker", output) //#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", () => { //#given - const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" }) + const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" }) const output = { message: {} as Record, parts: [ @@ -215,12 +215,12 @@ describe("resolveUltraworkOverride", () => { const result = resolveUltraworkOverride(config, "sisyphus", output) //#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", () => { //#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 getSessionAgentSpy = spyOn(sessionStateModule, "getSessionAgent") getSessionAgentSpy.mockReturnValue("sisyphus") @@ -230,7 +230,7 @@ describe("resolveUltraworkOverride", () => { //#then 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() }) @@ -287,7 +287,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => { test("should schedule deferred DB override without variant when SDK unavailable", () => { //#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 tui = createMockTui() @@ -297,7 +297,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => { //#then - variant should NOT be applied without SDK validation expect(dbOverrideSpy).toHaveBeenCalledWith( "msg_123", - { providerID: "anthropic", modelID: "claude-opus-4-6" }, + { providerID: "anthropic", modelID: "claude-opus-4-7" }, undefined, ) }) @@ -305,7 +305,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => { test("should NOT override variant when SDK unavailable even if config specifies variant", () => { //#given const config = createConfig("sisyphus", { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", variant: "extended", }) const output = createOutput("ultrawork do something", { messageId: "msg_123" }) @@ -319,7 +319,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => { //#then - existing variant preserved, not overridden to "extended" expect(dbOverrideSpy).toHaveBeenCalledWith( "msg_123", - { providerID: "anthropic", modelID: "claude-opus-4-6" }, + { providerID: "anthropic", modelID: "claude-opus-4-7" }, undefined, ) expect(output.message["variant"]).toBe("max") @@ -329,7 +329,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => { test("should NOT mutate output.message.model when message ID present", () => { //#given 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", { existingModel: sonnetModel, 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", () => { //#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 tui = createMockTui() @@ -353,7 +353,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => { applyUltraworkModelOverrideOnMessage(config, "sisyphus", output, tui) //#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(dbOverrideSpy).not.toHaveBeenCalled() }) @@ -375,7 +375,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => { test("should not apply override when no keyword detected", () => { //#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 tui = createMockTui() @@ -388,7 +388,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => { test("should log the model transition with deferred DB tag", () => { //#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 output = createOutput("ultrawork do something", { existingModel, @@ -408,7 +408,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => { test("should call showToast on override", () => { //#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" }) let toastCalled = false const tui = { @@ -426,7 +426,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => { test("should resolve display name to config key with deferred path", () => { //#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 tui = createMockTui() @@ -436,16 +436,16 @@ describe("applyUltraworkModelOverrideOnMessage", () => { //#then expect(dbOverrideSpy).toHaveBeenCalledWith( "msg_123", - { providerID: "anthropic", modelID: "claude-opus-4-6" }, + { providerID: "anthropic", modelID: "claude-opus-4-7" }, undefined, ) }) test("should skip override trigger when current model already matches ultrawork model", () => { //#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", { - existingModel: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + existingModel: { providerID: "anthropic", modelID: "claude-opus-4-7" }, messageId: "msg_123", }) let toastCalled = false @@ -465,13 +465,13 @@ describe("applyUltraworkModelOverrideOnMessage", () => { test("should apply validated variant when SDK confirms model supports it", async () => { //#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 tui = createMockTui() const mockClient = { provider: { 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 expect(dbOverrideSpy).toHaveBeenCalledWith( "msg_123", - { providerID: "anthropic", modelID: "claude-opus-4-6" }, + { providerID: "anthropic", modelID: "claude-opus-4-7" }, "max", ) }) diff --git a/src/plugin/ultrawork-variant-availability.test.ts b/src/plugin/ultrawork-variant-availability.test.ts index 1fb8a0910..176603698 100644 --- a/src/plugin/ultrawork-variant-availability.test.ts +++ b/src/plugin/ultrawork-variant-availability.test.ts @@ -23,7 +23,7 @@ describe("resolveValidUltraworkVariant", () => { // given const client = createClient({ anthropic: { - "claude-opus-4-6": { + "claude-opus-4-7": { variants: { max: {}, high: {}, @@ -35,7 +35,7 @@ describe("resolveValidUltraworkVariant", () => { // when const result = await resolveValidUltraworkVariant( client, - { providerID: "anthropic", modelID: "claude-opus-4-6" }, + { providerID: "anthropic", modelID: "claude-opus-4-7" }, "max", ) @@ -47,7 +47,7 @@ describe("resolveValidUltraworkVariant", () => { // given const client = createClient({ anthropic: { - "claude-opus-4-6": { + "claude-opus-4-7": { variants: { high: {}, }, @@ -58,7 +58,7 @@ describe("resolveValidUltraworkVariant", () => { // when const result = await resolveValidUltraworkVariant( client, - { providerID: "anthropic", modelID: "claude-opus-4-6" }, + { providerID: "anthropic", modelID: "claude-opus-4-7" }, "max", ) @@ -87,7 +87,7 @@ describe("applyUltraworkModelOverrideOnMessage variant guard", () => { // given const client = createClient({ anthropic: { - "claude-opus-4-6": { + "claude-opus-4-7": { variants: { high: {}, }, @@ -100,7 +100,7 @@ describe("applyUltraworkModelOverrideOnMessage variant guard", () => { agents: { sisyphus: { ultrawork: { - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", variant: "max", }, }, @@ -130,7 +130,7 @@ describe("applyUltraworkModelOverrideOnMessage variant guard", () => { expect(output.message["thinking"]).toBeUndefined() expect(dbOverrideSpy).toHaveBeenCalledWith( "msg_123", - { providerID: "anthropic", modelID: "claude-opus-4-6" }, + { providerID: "anthropic", modelID: "claude-opus-4-7" }, undefined, ) dbOverrideSpy.mockRestore() diff --git a/src/shared/AGENTS.md b/src/shared/AGENTS.md index 46f178b5b..b6336e6f2 100644 --- a/src/shared/AGENTS.md +++ b/src/shared/AGENTS.md @@ -1,6 +1,6 @@ # src/shared/ — 100+ Utility Files -**Generated:** 2026-04-11 +**Generated:** 2026-04-18 ## OVERVIEW diff --git a/src/shared/agent-config-integration.test.ts b/src/shared/agent-config-integration.test.ts index 6e4726a36..037435b27 100644 --- a/src/shared/agent-config-integration.test.ts +++ b/src/shared/agent-config-integration.test.ts @@ -8,9 +8,9 @@ describe("Agent Config Integration", () => { test("migrates old format agent keys to lowercase", () => { // given - config with old format keys const oldConfig = { - Sisyphus: { model: "anthropic/claude-opus-4-6" }, - Atlas: { model: "anthropic/claude-opus-4-6" }, - "Prometheus - Plan Builder": { model: "anthropic/claude-opus-4-6" }, + Sisyphus: { model: "anthropic/claude-opus-4-7" }, + Atlas: { model: "anthropic/claude-opus-4-7" }, + "Prometheus - Plan Builder": { model: "anthropic/claude-opus-4-7" }, "Metis - Plan Consultant": { 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") // then - values are preserved - expect(result.migrated.sisyphus).toEqual({ model: "anthropic/claude-opus-4-6" }) - expect(result.migrated.atlas).toEqual({ model: "anthropic/claude-opus-4-6" }) - expect(result.migrated.prometheus).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-7" }) + expect(result.migrated.prometheus).toEqual({ model: "anthropic/claude-opus-4-7" }) // then - changed flag is true expect(result.changed).toBe(true) @@ -44,7 +44,7 @@ describe("Agent Config Integration", () => { test("preserves already lowercase keys", () => { // given - config with lowercase keys const config = { - sisyphus: { model: "anthropic/claude-opus-4-6" }, + sisyphus: { model: "anthropic/claude-opus-4-7" }, oracle: { model: "openai/gpt-5.4" }, librarian: { model: "opencode/big-pickle" }, } @@ -62,9 +62,9 @@ describe("Agent Config Integration", () => { test("handles mixed case config", () => { // given - config with mixed old and new format const mixedConfig = { - Sisyphus: { model: "anthropic/claude-opus-4-6" }, + Sisyphus: { model: "anthropic/claude-opus-4-7" }, 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" }, } @@ -173,8 +173,8 @@ describe("Agent Config Integration", () => { test("old config migrates and displays correctly", () => { // given - old format config const oldConfig = { - Sisyphus: { model: "anthropic/claude-opus-4-6", temperature: 0.1 }, - "Prometheus - Plan Builder": { model: "anthropic/claude-opus-4-6" }, + Sisyphus: { model: "anthropic/claude-opus-4-7", temperature: 0.1 }, + "Prometheus - Plan Builder": { model: "anthropic/claude-opus-4-7" }, } // when - config is migrated @@ -193,15 +193,15 @@ describe("Agent Config Integration", () => { expect(prometheusDisplay).toBe("Prometheus - Plan Builder") // then - config values are preserved - expect(result.migrated.sisyphus).toEqual({ model: "anthropic/claude-opus-4-6", temperature: 0.1 }) - expect(result.migrated.prometheus).toEqual({ model: "anthropic/claude-opus-4-6" }) + expect(result.migrated.sisyphus).toEqual({ model: "anthropic/claude-opus-4-7", temperature: 0.1 }) + expect(result.migrated.prometheus).toEqual({ model: "anthropic/claude-opus-4-7" }) }) test("new config works without migration", () => { // given - new format config (already lowercase) const newConfig = { - sisyphus: { model: "anthropic/claude-opus-4-6" }, - atlas: { model: "anthropic/claude-opus-4-6" }, + sisyphus: { model: "anthropic/claude-opus-4-7" }, + atlas: { model: "anthropic/claude-opus-4-7" }, } // when - migration is applied (should be no-op) diff --git a/src/shared/agent-tool-restrictions.ts b/src/shared/agent-tool-restrictions.ts index 865251d6f..bf72de6d8 100644 --- a/src/shared/agent-tool-restrictions.ts +++ b/src/shared/agent-tool-restrictions.ts @@ -1,3 +1,5 @@ +import { stripInvisibleAgentCharacters } from "./agent-display-names" + /** * Agent tool restrictions for session.prompt calls. * OpenCode SDK's session.prompt `tools` parameter expects boolean values. @@ -45,13 +47,15 @@ const AGENT_RESTRICTIONS: Record> = { } export function getAgentToolRestrictions(agentName: string): Record { - return AGENT_RESTRICTIONS[agentName] - ?? Object.entries(AGENT_RESTRICTIONS).find(([key]) => key.toLowerCase() === agentName.toLowerCase())?.[1] + // Custom/unknown agents get no restrictions (empty object), matching Claude Code's + // trust model where project-registered agents retain full tool access including bash. + const stripped = stripInvisibleAgentCharacters(agentName) + return AGENT_RESTRICTIONS[stripped] + ?? Object.entries(AGENT_RESTRICTIONS).find(([key]) => key.toLowerCase() === stripped.toLowerCase())?.[1] ?? {} } export function hasAgentToolRestrictions(agentName: string): boolean { - const restrictions = AGENT_RESTRICTIONS[agentName] - ?? Object.entries(AGENT_RESTRICTIONS).find(([key]) => key.toLowerCase() === agentName.toLowerCase())?.[1] - return restrictions !== undefined && Object.keys(restrictions).length > 0 + const restrictions = getAgentToolRestrictions(agentName) + return Object.keys(restrictions).length > 0 } diff --git a/src/shared/agent-variant.test.ts b/src/shared/agent-variant.test.ts index 00ef030fb..58bdd193b 100644 --- a/src/shared/agent-variant.test.ts +++ b/src/shared/agent-variant.test.ts @@ -84,14 +84,14 @@ describe("applyAgentVariant", () => { describe("resolveVariantForModel", () => { 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 const config = { agents: { sisyphus: { variant: "high" }, }, } as OhMyOpenCodeConfig - const model = { providerID: "anthropic", modelID: "claude-opus-4-6" } + const model = { providerID: "anthropic", modelID: "claude-opus-4-7" } // when const variant = resolveVariantForModel(config, "sisyphus", model) @@ -103,7 +103,7 @@ describe("resolveVariantForModel", () => { test("returns correct variant for anthropic provider", () => { // given const config = {} as OhMyOpenCodeConfig - const model = { providerID: "anthropic", modelID: "claude-opus-4-6" } + const model = { providerID: "anthropic", modelID: "claude-opus-4-7" } // when const variant = resolveVariantForModel(config, "sisyphus", model) @@ -151,7 +151,7 @@ describe("resolveVariantForModel", () => { test("returns undefined for unknown agent", () => { // given const config = {} as OhMyOpenCodeConfig - const model = { providerID: "anthropic", modelID: "claude-opus-4-6" } + const model = { providerID: "anthropic", modelID: "claude-opus-4-7" } // when const variant = resolveVariantForModel(config, "nonexistent-agent", model) @@ -203,7 +203,7 @@ describe("resolveVariantForModel", () => { test("returns correct variant for oracle agent with anthropic", () => { // given const config = {} as OhMyOpenCodeConfig - const model = { providerID: "anthropic", modelID: "claude-opus-4-6" } + const model = { providerID: "anthropic", modelID: "claude-opus-4-7" } // when const variant = resolveVariantForModel(config, "oracle", model) diff --git a/src/shared/agent-variant.ts b/src/shared/agent-variant.ts index 4551e3d3a..3120d07dc 100644 --- a/src/shared/agent-variant.ts +++ b/src/shared/agent-variant.ts @@ -1,4 +1,5 @@ import type { OhMyOpenCodeConfig } from "../config" +import { stripInvisibleAgentCharacters } from "./agent-display-names" import { AGENT_MODEL_REQUIREMENTS, CATEGORY_MODEL_REQUIREMENTS } from "./model-requirements" export function resolveAgentVariant( @@ -9,12 +10,13 @@ export function resolveAgentVariant( return undefined } + const stripped = stripInvisibleAgentCharacters(agentName) const agentOverrides = config.agents as | Record | undefined const agentOverride = agentOverrides - ? agentOverrides[agentName] - ?? Object.entries(agentOverrides).find(([key]) => key.toLowerCase() === agentName.toLowerCase())?.[1] + ? agentOverrides[stripped] + ?? Object.entries(agentOverrides).find(([key]) => key.toLowerCase() === stripped.toLowerCase())?.[1] : undefined if (!agentOverride) { return undefined @@ -37,18 +39,19 @@ export function resolveVariantForModel( agentName: string, currentModel: { providerID: string; modelID: string }, ): string | undefined { + const stripped = stripInvisibleAgentCharacters(agentName) const agentOverrides = config.agents as | Record | undefined const agentOverride = agentOverrides - ? agentOverrides[agentName] - ?? Object.entries(agentOverrides).find(([key]) => key.toLowerCase() === agentName.toLowerCase())?.[1] + ? agentOverrides[stripped] + ?? Object.entries(agentOverrides).find(([key]) => key.toLowerCase() === stripped.toLowerCase())?.[1] : undefined if (agentOverride?.variant) { return agentOverride.variant } - const agentRequirement = AGENT_MODEL_REQUIREMENTS[agentName] + const agentRequirement = AGENT_MODEL_REQUIREMENTS[stripped] if (agentRequirement) { return findVariantInChain(agentRequirement.fallbackChain, currentModel) } diff --git a/src/shared/connected-providers-cache.test.ts b/src/shared/connected-providers-cache.test.ts index 6572a59cc..36abdac26 100644 --- a/src/shared/connected-providers-cache.test.ts +++ b/src/shared/connected-providers-cache.test.ts @@ -61,7 +61,7 @@ describe("updateConnectedProvidersCache", () => { name: "Anthropic", env: [], 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" }, }, }, @@ -84,7 +84,7 @@ describe("updateConnectedProvidersCache", () => { { id: "gpt-5.4", name: "GPT-5.4" }, ], 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" }, ], }) diff --git a/src/shared/context-limit-resolver.test.ts b/src/shared/context-limit-resolver.test.ts index a4346a6aa..d924d8a99 100644 --- a/src/shared/context-limit-resolver.test.ts +++ b/src/shared/context-limit-resolver.test.ts @@ -28,15 +28,15 @@ describe("resolveActualContextLimit", () => { 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 delete process.env[ANTHROPIC_CONTEXT_ENV_KEY] delete process.env[VERTEX_CONTEXT_ENV_KEY] const modelContextLimitsCache = new Map() - modelContextLimitsCache.set("anthropic/claude-opus-4-6", 1_000_000) + modelContextLimitsCache.set("anthropic/claude-opus-4-7", 1_000_000) // when - const actualLimit = resolveActualContextLimit("anthropic", "claude-opus-4-6", { + const actualLimit = resolveActualContextLimit("anthropic", "claude-opus-4-7", { anthropicContext1MEnabled: false, modelContextLimitsCache, }) @@ -107,15 +107,15 @@ describe("resolveActualContextLimit", () => { 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 delete process.env[ANTHROPIC_CONTEXT_ENV_KEY] delete process.env[VERTEX_CONTEXT_ENV_KEY] const modelContextLimitsCache = new Map() - modelContextLimitsCache.set("anthropic/claude-opus-4.6", 1_000_000) + modelContextLimitsCache.set("anthropic/claude-opus-4.7", 1_000_000) // when - const actualLimit = resolveActualContextLimit("anthropic", "claude-opus-4.6", { + const actualLimit = resolveActualContextLimit("anthropic", "claude-opus-4.7", { anthropicContext1MEnabled: false, modelContextLimitsCache, }) diff --git a/src/shared/context-limit-resolver.ts b/src/shared/context-limit-resolver.ts index 2bf2a8147..2d440658b 100644 --- a/src/shared/context-limit-resolver.ts +++ b/src/shared/context-limit-resolver.ts @@ -20,7 +20,7 @@ function getAnthropicActualLimit(modelCacheState?: ContextLimitModelCacheState): } 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( diff --git a/src/shared/excluded-dirs.test.ts b/src/shared/excluded-dirs.test.ts new file mode 100644 index 000000000..21a488907 --- /dev/null +++ b/src/shared/excluded-dirs.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, test } from "bun:test" +import { EXCLUDED_DIRS } from "./excluded-dirs" +import { EXCLUDED_DIRS as EXCLUDED_DIRS_FROM_BARREL } from "." + +describe("EXCLUDED_DIRS", () => { + test("contains the well-known junk directories we never want to recurse into", () => { + // given + const expected = [ + "node_modules", + ".git", + "dist", + "build", + ".next", + ".sisyphus", + ".omx", + ".turbo", + "coverage", + "out", + ".cache", + ".vscode-test", + "target", + ".local-ignore", + ] + + // when / then + for (const name of expected) { + expect(EXCLUDED_DIRS.has(name)).toBe(true) + } + }) + + test("does not contain commonly-wanted project directories", () => { + // given + const shouldBeAllowed = ["src", "lib", "tests", "test", "docs", ".github", ".cursor", ".claude", ".opencode"] + + // when / then + for (const name of shouldBeAllowed) { + expect(EXCLUDED_DIRS.has(name)).toBe(false) + } + }) + + test("is frozen so consumers cannot mutate shared state", () => { + // given / when / then + expect(Object.isFrozen(EXCLUDED_DIRS)).toBe(true) + }) + + test("is re-exported from the shared barrel", () => { + // given / when / then + expect(EXCLUDED_DIRS_FROM_BARREL).toBe(EXCLUDED_DIRS) + }) +}) diff --git a/src/shared/excluded-dirs.ts b/src/shared/excluded-dirs.ts new file mode 100644 index 000000000..059a01406 --- /dev/null +++ b/src/shared/excluded-dirs.ts @@ -0,0 +1,18 @@ +const EXCLUDED_DIR_NAMES = [ + "node_modules", + ".git", + "dist", + "build", + ".next", + ".sisyphus", + ".omx", + ".turbo", + "coverage", + "out", + ".cache", + ".vscode-test", + "target", + ".local-ignore", +] as const + +export const EXCLUDED_DIRS: ReadonlySet = Object.freeze(new Set(EXCLUDED_DIR_NAMES)) diff --git a/src/shared/index.ts b/src/shared/index.ts index 485926bfd..e99234c33 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -17,6 +17,7 @@ export * from "./claude-config-dir" export * from "./jsonc-parser" export * from "./migration" export * from "./opencode-config-dir" +export * from "./resolve-agent-definition-paths" export type { OpenCodeBinaryType, OpenCodeConfigDirOptions, @@ -56,6 +57,7 @@ export * from "./session-utils" export * from "./tmux" export * from "./model-suggestion-retry" export * from "./opencode-server-auth" +export * from "./opencode-provider-auth" export * from "./opencode-http-api" export * from "./port-utils" export * from "./git-worktree" @@ -75,3 +77,6 @@ export { SessionCategoryRegistry } from "./session-category-registry" export * from "./plugin-identity" export * from "./log-legacy-plugin-startup-warning" export * from "./task-system-enabled" +export * from "./parse-tools-config" +export { parseModelString } from "./model-string-parser" +export { EXCLUDED_DIRS } from "./excluded-dirs" diff --git a/src/shared/jsonc-parser.memoization.test.ts b/src/shared/jsonc-parser.memoization.test.ts new file mode 100644 index 000000000..c4cd1f5b8 --- /dev/null +++ b/src/shared/jsonc-parser.memoization.test.ts @@ -0,0 +1,54 @@ +import { afterEach, describe, expect, mock, spyOn, test } from "bun:test" +import * as fs from "node:fs" +import { join } from "node:path" + +describe("detectPluginConfigFile memoization", () => { + const testDir = join(__dirname, ".test-detect-plugin-memoization") + + afterEach(() => { + mock.restore() + }) + + test("returns cached result on repeated calls for the same directory", async () => { + // given + const existsSync = spyOn(fs, "existsSync").mockImplementation((filePath: fs.PathLike) => { + return String(filePath).endsWith("oh-my-openagent.jsonc") + }) + const readdirSync = spyOn(fs, "readdirSync").mockImplementation(() => []) + spyOn(fs, "readFileSync").mockImplementation(() => "") + + const parserModule = await import(`./jsonc-parser?memoization=${Date.now()}-${Math.random()}`) + + // when + const firstResult = parserModule.detectPluginConfigFile(testDir) + const callsAfterFirstResult = existsSync.mock.calls.length + const secondResult = parserModule.detectPluginConfigFile(testDir) + + // then + expect(firstResult).toEqual(secondResult) + expect(existsSync.mock.calls.length).toBe(callsAfterFirstResult) + expect(readdirSync).toHaveBeenCalledTimes(0) + }) + + test("clears cached result when requested", async () => { + // given + const existsSync = spyOn(fs, "existsSync").mockImplementation((filePath: fs.PathLike) => { + return String(filePath).endsWith("oh-my-openagent.jsonc") + }) + const readdirSync = spyOn(fs, "readdirSync").mockImplementation(() => []) + spyOn(fs, "readFileSync").mockImplementation(() => "") + + const parserModule = await import(`./jsonc-parser?memoization=${Date.now()}-${Math.random()}`) + + parserModule.detectPluginConfigFile(testDir) + parserModule.clearPluginConfigFileDetectionCache() + const callsAfterClear = existsSync.mock.calls.length + + // when + parserModule.detectPluginConfigFile(testDir) + + // then + expect(existsSync.mock.calls.length).toBeGreaterThan(callsAfterClear) + expect(readdirSync).toHaveBeenCalledTimes(0) + }) +}) diff --git a/src/shared/jsonc-parser.test.ts b/src/shared/jsonc-parser.test.ts index 279db1fc5..c06e36353 100644 --- a/src/shared/jsonc-parser.test.ts +++ b/src/shared/jsonc-parser.test.ts @@ -1,5 +1,5 @@ -import { describe, expect, test } from "bun:test" -import { detectConfigFile, detectPluginConfigFile, parseJsonc, parseJsoncSafe, readJsoncFile } from "./jsonc-parser" +import { afterEach, beforeEach, describe, expect, test } from "bun:test" +import { clearPluginConfigFileDetectionCache, detectConfigFile, detectPluginConfigFile, parseJsonc, parseJsoncSafe, readJsoncFile } from "./jsonc-parser" import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs" import { join } from "node:path" @@ -330,6 +330,14 @@ describe("detectConfigFile", () => { describe("detectPluginConfigFile", () => { const testDir = join(__dirname, ".test-detect-plugin") + beforeEach(() => { + clearPluginConfigFileDetectionCache() + }) + + afterEach(() => { + clearPluginConfigFileDetectionCache() + }) + test("prefers oh-my-openagent over oh-my-opencode when both jsonc files exist", () => { // given if (!existsSync(testDir)) mkdirSync(testDir, { recursive: true }) diff --git a/src/shared/jsonc-parser.ts b/src/shared/jsonc-parser.ts index da1e0d98c..bb7148983 100644 --- a/src/shared/jsonc-parser.ts +++ b/src/shared/jsonc-parser.ts @@ -9,6 +9,14 @@ export interface JsoncParseResult { errors: Array<{ message: string; offset: number; length: number }> } +type DetectPluginConfigResult = { + format: "json" | "jsonc" | "none" + path: string + legacyPath?: string +} + +const pluginConfigFileDetectionCache = new Map() + function stripBom(content: string): string { return content.charCodeAt(0) === 0xfeff ? content.slice(1) : content } @@ -75,24 +83,34 @@ export function detectConfigFile(basePath: string): { return { format: "none", path: jsonPath } } -export function detectPluginConfigFile(dir: string): { - format: "json" | "jsonc" | "none" - path: string - legacyPath?: string -} { +export function clearPluginConfigFileDetectionCache(): void { + pluginConfigFileDetectionCache.clear() +} + +export function detectPluginConfigFile(dir: string): DetectPluginConfigResult { + const cachedResult = pluginConfigFileDetectionCache.get(dir) + + if (cachedResult !== undefined) { + return cachedResult + } + const canonicalResult = detectConfigFile(join(dir, CONFIG_BASENAME)) const legacyResult = detectConfigFile(join(dir, LEGACY_CONFIG_BASENAME)) + let detectionResult: DetectPluginConfigResult + if (canonicalResult.format !== "none") { - return { + detectionResult = { ...canonicalResult, legacyPath: legacyResult.format !== "none" ? legacyResult.path : undefined, } + } else if (legacyResult.format !== "none") { + detectionResult = legacyResult + } else { + detectionResult = { format: "none", path: join(dir, `${CONFIG_BASENAME}.json`) } } - if (legacyResult.format !== "none") { - return legacyResult - } + pluginConfigFileDetectionCache.set(dir, detectionResult) - return { format: "none", path: join(dir, `${CONFIG_BASENAME}.json`) } + return detectionResult } diff --git a/src/shared/load-opencode-plugins.test.ts b/src/shared/load-opencode-plugins.test.ts new file mode 100644 index 000000000..9723c1cd3 --- /dev/null +++ b/src/shared/load-opencode-plugins.test.ts @@ -0,0 +1,89 @@ +/// + +import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test" +import * as fs from "node:fs" + +type LoadOpencodePluginsModule = { + loadOpencodePlugins: (directory: string) => string[] + clearOpencodePluginsCache?: () => void +} + +const existsSyncMock = mock((_path: string) => true) +const readFileSyncMock = mock((_path: string, _encoding?: string) => `{ + "plugin": ["plugin-a", "plugin-b"] +}`) + +async function importFreshLoadOpencodePluginsModule(): Promise { + const modulePath = `${new URL("./load-opencode-plugins.ts", import.meta.url).pathname}?test=${Date.now()}-${Math.random()}` + return import(modulePath) +} + +describe("loadOpencodePlugins", () => { + beforeEach(() => { + existsSyncMock.mockReset() + existsSyncMock.mockImplementation((_path: string) => true) + readFileSyncMock.mockReset() + readFileSyncMock.mockImplementation((_path: string, _encoding?: string) => `{ + "plugin": ["plugin-a", "plugin-b"] +}`) + + mock.module("node:fs", () => ({ + ...fs, + existsSync: existsSyncMock, + readFileSync: readFileSyncMock, + })) + }) + + afterEach(() => { + mock.restore() + }) + + describe("#given the same directory is loaded twice", () => { + describe("#when loading plugins repeatedly", () => { + it("#then does not call readFileSync on the second load", async () => { + // given + const { loadOpencodePlugins } = await importFreshLoadOpencodePluginsModule() + + // when + const firstResult = loadOpencodePlugins("/some/fake/dir") + const readCountAfterFirstLoad = readFileSyncMock.mock.calls.length + const secondResult = loadOpencodePlugins("/some/fake/dir") + const readCountAfterSecondLoad = readFileSyncMock.mock.calls.length + + // then + expect(firstResult).toEqual(["plugin-a", "plugin-b"]) + expect(secondResult).toEqual(["plugin-a", "plugin-b"]) + expect(readCountAfterFirstLoad).toBeGreaterThan(0) + expect(readCountAfterSecondLoad - readCountAfterFirstLoad).toBe(0) + }) + }) + }) + + describe("#given the plugin cache was cleared", () => { + describe("#when loading the same directory again", () => { + it("#then re-reads plugin config files from disk", async () => { + // given + const { loadOpencodePlugins, clearOpencodePluginsCache } = await importFreshLoadOpencodePluginsModule() + + if (typeof clearOpencodePluginsCache !== "function") { + throw new Error("clearOpencodePluginsCache export is missing") + } + + // when + const firstResult = loadOpencodePlugins("/some/fake/dir") + const readCountAfterFirstLoad = readFileSyncMock.mock.calls.length + loadOpencodePlugins("/some/fake/dir") + const readCountAfterSecondLoad = readFileSyncMock.mock.calls.length + clearOpencodePluginsCache() + const thirdResult = loadOpencodePlugins("/some/fake/dir") + const readCountAfterThirdLoad = readFileSyncMock.mock.calls.length + + // then + expect(firstResult).toEqual(["plugin-a", "plugin-b"]) + expect(thirdResult).toEqual(["plugin-a", "plugin-b"]) + expect(readCountAfterSecondLoad - readCountAfterFirstLoad).toBe(0) + expect(readCountAfterThirdLoad - readCountAfterSecondLoad).toBeGreaterThan(0) + }) + }) + }) +}) diff --git a/src/shared/load-opencode-plugins.ts b/src/shared/load-opencode-plugins.ts index 5517c74b1..a6beffdcf 100644 --- a/src/shared/load-opencode-plugins.ts +++ b/src/shared/load-opencode-plugins.ts @@ -8,6 +8,8 @@ interface OpencodeConfig { plugin?: (string | [string, ...unknown[]])[] } +const opencodePluginsCache = new Map() + function getWindowsAppdataDir(): string | null { return process.env.APPDATA || null } @@ -33,6 +35,11 @@ function getConfigPaths(directory: string): string[] { } export function loadOpencodePlugins(directory: string): string[] { + const cachedPluginEntries = opencodePluginsCache.get(directory) + if (cachedPluginEntries) { + return cachedPluginEntries + } + const pluginEntries: string[] = [] const seenPluginEntries = new Set() @@ -56,5 +63,10 @@ export function loadOpencodePlugins(directory: string): string[] { } } + opencodePluginsCache.set(directory, pluginEntries) return pluginEntries } + +export function clearOpencodePluginsCache(): void { + opencodePluginsCache.clear() +} diff --git a/src/shared/log-legacy-plugin-startup-warning.test.ts b/src/shared/log-legacy-plugin-startup-warning.test.ts index 917f40927..76ec3541f 100644 --- a/src/shared/log-legacy-plugin-startup-warning.test.ts +++ b/src/shared/log-legacy-plugin-startup-warning.test.ts @@ -63,7 +63,7 @@ describe("logLegacyPluginStartupWarning", () => { //#then expect(mockLog).toHaveBeenCalledTimes(1) expect(mockLog).toHaveBeenCalledWith( - "[OhMyOpenCodePlugin] Legacy plugin entry detected in OpenCode config", + "[legacy-migration] Legacy plugin entry detected in OpenCode config", { legacyEntries: ["oh-my-opencode", "oh-my-opencode@3.13.1"], suggestedEntries: ["oh-my-openagent", "oh-my-openagent@3.13.1"], diff --git a/src/shared/log-legacy-plugin-startup-warning.ts b/src/shared/log-legacy-plugin-startup-warning.ts index cc8be67e2..d1151b122 100644 --- a/src/shared/log-legacy-plugin-startup-warning.ts +++ b/src/shared/log-legacy-plugin-startup-warning.ts @@ -22,7 +22,7 @@ export function logLegacyPluginStartupWarning(deps: LogLegacyPluginStartupWarnin const suggestedEntries = result.legacyEntries.map(toCanonicalEntry) - logFn("[OhMyOpenCodePlugin] Legacy plugin entry detected in OpenCode config", { + logFn("[legacy-migration] Legacy plugin entry detected in OpenCode config", { legacyEntries: result.legacyEntries, suggestedEntries, hasCanonicalEntry: result.hasCanonicalEntry, diff --git a/src/shared/merge-categories.test.ts b/src/shared/merge-categories.test.ts index c2f56aaa5..5ed82b465 100644 --- a/src/shared/merge-categories.test.ts +++ b/src/shared/merge-categories.test.ts @@ -71,7 +71,7 @@ describe("mergeCategories", () => { it("user overrides merge with defaults", () => { //#given const userCategories = { - "ultrabrain": { model: "anthropic/claude-opus-4-6" }, + "ultrabrain": { model: "anthropic/claude-opus-4-7" }, } //#when @@ -79,6 +79,6 @@ describe("mergeCategories", () => { //#then expect(result["ultrabrain"]).toBeDefined() - expect(result["ultrabrain"].model).toBe("anthropic/claude-opus-4-6") + expect(result["ultrabrain"].model).toBe("anthropic/claude-opus-4-7") }) }) diff --git a/src/shared/migration.test.ts b/src/shared/migration.test.ts index e0b5f2808..072858d7c 100644 --- a/src/shared/migration.test.ts +++ b/src/shared/migration.test.ts @@ -19,7 +19,7 @@ describe("migrateAgentNames", () => { test("migrates legacy OmO names to lowercase", () => { // given: Config with legacy OmO agent names const agents = { - omo: { model: "anthropic/claude-opus-4-6" }, + omo: { model: "anthropic/claude-opus-4-7" }, OmO: { temperature: 0.5 }, "OmO-Plan": { prompt: "custom prompt" }, } @@ -88,7 +88,7 @@ describe("migrateAgentNames", () => { test("migrates orchestrator-sisyphus to atlas", () => { // given: Config with legacy orchestrator-sisyphus agent name const agents = { - "orchestrator-sisyphus": { model: "anthropic/claude-opus-4-6" }, + "orchestrator-sisyphus": { model: "anthropic/claude-opus-4-7" }, } // when: Migrate agent names @@ -96,14 +96,14 @@ describe("migrateAgentNames", () => { // then: orchestrator-sisyphus should be migrated to atlas 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() }) test("migrates lowercase atlas to atlas", () => { // given: Config with lowercase atlas agent name const agents = { - atlas: { model: "anthropic/claude-opus-4-6" }, + atlas: { model: "anthropic/claude-opus-4-7" }, } // when: Migrate agent names @@ -111,7 +111,7 @@ describe("migrateAgentNames", () => { // then: lowercase atlas should remain atlas (no change needed) 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", () => { @@ -524,7 +524,7 @@ describe("migrateConfigFile", () => { // then: Model version should be migrated expect(needsWrite).toBe(true) const categories = rawConfig.categories as Record> - 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", () => { @@ -534,7 +534,7 @@ describe("migrateConfigFile", () => { sisyphus: { model: "openai/gpt-5.4-codex" }, }, 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() }) - 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 // 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", () => { @@ -614,7 +614,7 @@ describe("migrateModelVersions", () => { // then: Model should be updated expect(changed).toBe(true) const prometheus = migrated["prometheus"] as Record - expect(prometheus.model).toBe("anthropic/claude-opus-4-6") + expect(prometheus.model).toBe("anthropic/claude-opus-4-7") }) test("leaves unknown model strings untouched", () => { @@ -674,7 +674,7 @@ describe("migrateModelVersions", () => { // then: Only mapped models should be updated expect(changed).toBe(true) expect((migrated["sisyphus"] as Record).model).toBe("openai/gpt-5.4-codex") - expect((migrated["prometheus"] as Record).model).toBe("anthropic/claude-opus-4-6") + expect((migrated["prometheus"] as Record).model).toBe("anthropic/claude-opus-4-7") expect((migrated["oracle"] as Record).model).toBe("openai/gpt-5.4") }) @@ -736,9 +736,9 @@ describe("migrateModelVersions", () => { // then: Only prometheus should be migrated expect(changed).toBe(true) - expect(newMigrations).toEqual(["model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6"]) + expect(newMigrations).toEqual(["model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-7"]) expect((migrated["sisyphus"] as Record).model).toBe("openai/gpt-5.4-codex") - expect((migrated["prometheus"] as Record).model).toBe("anthropic/claude-opus-4-6") + expect((migrated["prometheus"] as Record).model).toBe("anthropic/claude-opus-4-7") }) test("backward compatible without appliedMigrations param", () => { @@ -820,12 +820,12 @@ describe("migrateConfigFile _migrations tracking", () => { // (legacy + new) is written to the sidecar file exactly once. expect(result).toBe(true) expect(rawConfig._migrations).toBeUndefined() - expect((rawConfig.agents as Record>).prometheus.model).toBe("anthropic/claude-opus-4-6") + expect((rawConfig.agents as Record>).prometheus.model).toBe("anthropic/claude-opus-4-7") const sidecar = JSON.parse(fs.readFileSync(`${configPath}.migrations.json`, "utf-8")) expect(new Set(sidecar.appliedMigrations)).toEqual(new Set([ "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 @@ -890,7 +890,7 @@ describe("migrateAgentConfigToCategory", () => { { model: "google/gemini-3-flash" }, { model: "openai/gpt-5.4" }, { model: "anthropic/claude-haiku-4-5" }, - { model: "anthropic/claude-opus-4-6" }, + { model: "anthropic/claude-opus-4-7" }, { model: "anthropic/claude-sonnet-4-6" }, ] @@ -970,7 +970,7 @@ describe("shouldDeleteAgentConfig", () => { // given: Config with custom model override const config = { category: "visual-engineering", - model: "anthropic/claude-opus-4-6", + model: "anthropic/claude-opus-4-7", } // when: Check if config should be deleted @@ -1245,9 +1245,9 @@ describe("migrateModelVersions with applied migrations", () => { // then: Skip sisyphus (already applied), apply oracle expect(changed).toBe(true) - expect(newMigrations).toEqual(["model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6"]) + expect(newMigrations).toEqual(["model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-7"]) expect((migrated.sisyphus as Record).model).toBe("openai/gpt-5.4-codex") - expect((migrated.oracle as Record).model).toBe("anthropic/claude-opus-4-6") + expect((migrated.oracle as Record).model).toBe("anthropic/claude-opus-4-7") }) test("backward compatible: no appliedMigrations param", () => { @@ -1334,12 +1334,12 @@ describe("migrateConfigFile with migration tracking via sidecar (#3263)", () => const needsWrite = migrateConfigFile(testConfigPath, rawConfig) expect(needsWrite).toBe(true) - expect((rawConfig.agents as Record>).oracle.model).toBe("anthropic/claude-opus-4-6") + expect((rawConfig.agents as Record>).oracle.model).toBe("anthropic/claude-opus-4-7") expect(rawConfig._migrations).toBeUndefined() const sidecar = JSON.parse(fs.readFileSync(sidecarPath(testConfigPath), "utf-8")) 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({ appliedMigrations: [ "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: { 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)) @@ -1430,7 +1430,7 @@ describe("migrateConfigFile with migration tracking via sidecar (#3263)", () => const sidecar = JSON.parse(fs.readFileSync(sidecarPath(testConfigPath), "utf-8")) 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", ]) }) @@ -1460,13 +1460,13 @@ describe("migrateConfigFile with migration tracking via sidecar (#3263)", () => // codex was reverted, must stay expect((rawConfig.agents as Record>).codex.model).toBe("openai/gpt-5.3-codex") // claude migrates - expect((rawConfig.agents as Record>).claude.model).toBe("anthropic/claude-opus-4-6") + expect((rawConfig.agents as Record>).claude.model).toBe("anthropic/claude-opus-4-7") expect(rawConfig._migrations).toBeUndefined() const sidecar = JSON.parse(fs.readFileSync(sidecarPath(testConfigPath), "utf-8")) expect(new Set(sidecar.appliedMigrations)).toEqual(new Set([ "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(migrations).toContain("model-version:openai/gpt-5.3-codex->openai/gpt-5.4") expect(migrations.length).toBeGreaterThanOrEqual(1) - expect((rawConfig.agents as Record>).oracle.model).toBe("anthropic/claude-opus-4-6") + expect((rawConfig.agents as Record>).oracle.model).toBe("anthropic/claude-opus-4-7") // Sidecar should not exist because write failed expect(fs.existsSync(sidecarPath(testConfigPath))).toBe(false) diff --git a/src/shared/migration/agent-category.ts b/src/shared/migration/agent-category.ts index 6f75682ca..f3ddf291a 100644 --- a/src/shared/migration/agent-category.ts +++ b/src/shared/migration/agent-category.ts @@ -17,6 +17,7 @@ export const MODEL_TO_CATEGORY_MAP: Record = { "openai/gpt-5.4": "ultrabrain", "anthropic/claude-haiku-4-5": "quick", "anthropic/claude-opus-4-6": "unspecified-high", + "anthropic/claude-opus-4-7": "unspecified-high", "anthropic/claude-sonnet-4-6": "unspecified-low", } diff --git a/src/shared/migration/config-migration.test.ts b/src/shared/migration/config-migration.test.ts index 88f288e5b..ff59d7ca3 100644 --- a/src/shared/migration/config-migration.test.ts +++ b/src/shared/migration/config-migration.test.ts @@ -8,7 +8,7 @@ import { migrateConfigFile } from "./config-migration" import { getSidecarPath } from "./migrations-sidecar" 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 { const workdir = mkdtempSync(join(tmpdir(), "omo-config-migration-")) @@ -46,13 +46,13 @@ describe("migrateConfigFile sidecar write ordering", () => { expect(needsWrite).toBe(true) expect(rawConfig._migrations).toBeUndefined() expect((rawConfig.agents as Record>).prometheus.model).toBe( - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", ) const persistedConfig = JSON.parse(readFileSync(configPath, "utf-8")) as Record expect(persistedConfig._migrations).toBeUndefined() expect((persistedConfig.agents as Record>).prometheus.model).toBe( - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", ) const sidecar = JSON.parse(readFileSync(getSidecarPath(configPath), "utf-8")) as { @@ -87,7 +87,7 @@ describe("migrateConfigFile sidecar write ordering", () => { expect(retriedNeedsWrite).toBe(true) expect(retriedConfig._migrations).toBeUndefined() expect((retriedConfig.agents as Record>).prometheus.model).toBe( - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", ) expect(existsSync(getSidecarPath(configPath))).toBe(true) }) @@ -108,14 +108,63 @@ describe("migrateConfigFile sidecar write ordering", () => { expect(needsWrite).toBe(true) expect(rawConfig._migrations).toEqual([MIGRATION_KEY]) expect((rawConfig.agents as Record>).prometheus.model).toBe( - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", ) const persistedConfig = JSON.parse(readFileSync(configPath, "utf-8")) as Record expect(persistedConfig._migrations).toEqual([MIGRATION_KEY]) expect((persistedConfig.agents as Record>).prometheus.model).toBe( - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", ) expect(statSync(getSidecarPath(configPath)).isDirectory()).toBe(true) }) }) + +describe("migrateConfigFile backup skipping", () => { + test("skips backup when file content is identical after migration", () => { + // given - config with legacy key that migrates to same on-disk content + const workdir = createWorkdir() + const configPath = join(workdir, "oh-my-opencode.json") + const migratedContent = { + disabled_hooks: ["comment-checker"], + } + + // Write the already-migrated content to disk + writeFileSync(configPath, JSON.stringify(migratedContent, null, 2) + "\n") + + // rawConfig still has the legacy hook that will be removed + const rawConfig: Record = { + disabled_hooks: ["gpt-permission-continuation", "comment-checker"], + } + + // when + migrateConfigFile(configPath, rawConfig) + + // then - no backup file should be created since file content is unchanged + const files = require("fs").readdirSync(workdir) as string[] + const backupFiles = files.filter((f: string) => f.includes(".bak.")) + expect(backupFiles.length).toBe(0) + }) + + test("creates backup when file content actually changes", () => { + // given - config with model that needs migration + const workdir = createWorkdir() + const configPath = join(workdir, "oh-my-opencode.json") + const rawConfig = { + agents: { + prometheus: { model: "anthropic/claude-opus-4-5" }, + }, + } + + writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n") + + // when + const needsWrite = migrateConfigFile(configPath, rawConfig as Record) + + // then - backup should be created since content changed + expect(needsWrite).toBe(true) + const files = require("fs").readdirSync(workdir) as string[] + const backupFiles = files.filter((f: string) => f.includes(".bak.")) + expect(backupFiles.length).toBe(1) + }) +}) diff --git a/src/shared/migration/config-migration.ts b/src/shared/migration/config-migration.ts index 11b34156f..792ca1083 100644 --- a/src/shared/migration/config-migration.ts +++ b/src/shared/migration/config-migration.ts @@ -143,20 +143,36 @@ export function migrateConfigFile( } if (needsWrite) { + let finalConfig = JSON.parse(JSON.stringify(copy)) as Record + const newContent = JSON.stringify(finalConfig, null, 2) + "\n" + + // Compare with existing file content to skip backup when unchanged. + // The config may still need an in-memory migration even if the file + // content is identical (e.g. removing a deleted hook from disabled_hooks + // results in content that was already written by a prior migration). + let existingContent: string | undefined + try { + existingContent = fs.readFileSync(configPath, "utf-8") + } catch { + // File may not exist yet + } + const contentChanged = existingContent !== newContent + const timestamp = new Date().toISOString().replace(/[:.]/g, "-") const backupPath = `${configPath}.bak.${timestamp}` let backupSucceeded = false - try { - fs.copyFileSync(configPath, backupPath) - backupSucceeded = true - } catch { - backupSucceeded = false + if (contentChanged) { + try { + fs.copyFileSync(configPath, backupPath) + backupSucceeded = true + } catch { + backupSucceeded = false + } } let writeSucceeded = false - let finalConfig = JSON.parse(JSON.stringify(copy)) as Record try { - writeFileAtomically(configPath, JSON.stringify(finalConfig, null, 2) + "\n") + writeFileAtomically(configPath, newContent) writeSucceeded = true } catch (err) { log(`Failed to write migrated config to ${configPath}:`, err) diff --git a/src/shared/migration/migrations-sidecar.test.ts b/src/shared/migration/migrations-sidecar.test.ts index 5809bde94..503ea9105 100644 --- a/src/shared/migration/migrations-sidecar.test.ts +++ b/src/shared/migration/migrations-sidecar.test.ts @@ -42,7 +42,7 @@ describe("migrations sidecar", () => { JSON.stringify({ appliedMigrations: [ "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.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", () => { @@ -134,7 +134,7 @@ describe("migrations sidecar", () => { const configPath = join(workdir, "oh-my-openagent.jsonc") const original = new Set([ "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) diff --git a/src/shared/migration/migrations-sidecar.ts b/src/shared/migration/migrations-sidecar.ts index cd0088922..0cbac7db1 100644 --- a/src/shared/migration/migrations-sidecar.ts +++ b/src/shared/migration/migrations-sidecar.ts @@ -22,7 +22,7 @@ import { writeFileAtomically } from "../write-file-atomically" * { * "appliedMigrations": [ * "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" * ] * } */ diff --git a/src/shared/migration/model-versions.ts b/src/shared/migration/model-versions.ts index 13731dcaa..40aee07b3 100644 --- a/src/shared/migration/model-versions.ts +++ b/src/shared/migration/model-versions.ts @@ -6,7 +6,8 @@ * Keys are full "provider/model" strings. Only openai and anthropic entries needed. */ export const MODEL_VERSION_MAP: Record = { - "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", "openai/gpt-5.3-codex": "openai/gpt-5.4", } diff --git a/src/shared/model-availability.test.ts b/src/shared/model-availability.test.ts index 7be5e363c..77ca0ca14 100644 --- a/src/shared/model-availability.test.ts +++ b/src/shared/model-availability.test.ts @@ -65,7 +65,7 @@ describe("fetchAvailableModels", () => { it("#given cache file with models #when fetchAvailableModels called with connectedProviders #then returns Set of model IDs", async () => { writeModelsCache({ openai: { id: "openai", models: { "gpt-5.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" } } }, }) @@ -76,7 +76,7 @@ describe("fetchAvailableModels", () => { expect(result).toBeInstanceOf(Set) expect(result.size).toBe(3) 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) }) @@ -145,7 +145,7 @@ describe("fetchAvailableModels", () => { it("#given cache read twice #when second call made with same providers #then reads fresh each time", async () => { writeModelsCache({ openai: { id: "openai", models: { "gpt-5.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"] }) @@ -192,7 +192,7 @@ describe("fuzzyMatchModel", () => { const available = new Set([ "openai/gpt-5.4", "openai/gpt-5.3-codex", - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", ]) const result = fuzzyMatchModel("gpt-5.4", available) expect(result).toBe("openai/gpt-5.4") @@ -239,25 +239,25 @@ describe("fuzzyMatchModel", () => { // given available models with claude variants // when searching for claude-opus // 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([ - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", "anthropic/claude-sonnet-4-6", ]) 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 // when fallback chain uses hyphen notation in requested 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([ - "github-copilot/claude-opus-4.6", + "github-copilot/claude-opus-4.7", "opencode/big-pickle", ]) - const result = fuzzyMatchModel("claude-opus-4-6", available, ["github-copilot"]) - expect(result).toBe("github-copilot/claude-opus-4.6") + const result = fuzzyMatchModel("claude-opus-4-7", available, ["github-copilot"]) + expect(result).toBe("github-copilot/claude-opus-4.7") }) // 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", () => { const available = new Set([ "openai/gpt-5.4", - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", "google/gemini-3", ]) const result = fuzzyMatchModel("gpt", available, ["openai"]) @@ -288,7 +288,7 @@ describe("fuzzyMatchModel", () => { it("should return null when provider filter excludes all matches", () => { const available = new Set([ "openai/gpt-5.4", - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", ]) const result = fuzzyMatchModel("claude", available, ["openai"]) expect(result).toBeNull() @@ -300,7 +300,7 @@ describe("fuzzyMatchModel", () => { it("should return null when no match found", () => { const available = new Set([ "openai/gpt-5.4", - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", ]) const result = fuzzyMatchModel("gemini", available) expect(result).toBeNull() @@ -312,7 +312,7 @@ describe("fuzzyMatchModel", () => { it("should match case-insensitively", () => { const available = new Set([ "openai/gpt-5.4", - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", ]) const result = fuzzyMatchModel("GPT-5.4", available) expect(result).toBe("openai/gpt-5.4") @@ -323,11 +323,11 @@ describe("fuzzyMatchModel", () => { // then return exact match first it("should prioritize exact match over longer variants", () => { const available = new Set([ - "anthropic/claude-opus-4-6", - "anthropic/claude-opus-4-6-extended", + "anthropic/claude-opus-4-7", + "anthropic/claude-opus-4-7-extended", ]) - const result = fuzzyMatchModel("claude-opus-4-6", available) - expect(result).toBe("anthropic/claude-opus-4-6") + const result = fuzzyMatchModel("claude-opus-4-7", available) + expect(result).toBe("anthropic/claude-opus-4-7") }) // 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", () => { const available = new Set([ "openai/gpt-5.4", - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", "google/gemini-3", ]) 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 () => { writeModelsCache({ 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" } } }, }) @@ -529,7 +529,7 @@ describe("fetchAvailableModels with connected providers filtering", () => { }) expect(result.size).toBe(1) - expect(result.has("anthropic/claude-opus-4-6")).toBe(true) + expect(result.has("anthropic/claude-opus-4-7")).toBe(true) expect(result.has("openai/gpt-5.4")).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 () => { writeModelsCache({ 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" } } }, }) @@ -549,7 +549,7 @@ describe("fetchAvailableModels with connected providers filtering", () => { }) expect(result.size).toBe(2) - expect(result.has("anthropic/claude-opus-4-6")).toBe(true) + expect(result.has("anthropic/claude-opus-4-7")).toBe(true) expect(result.has("google/gemini-3.1-pro")).toBe(true) 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 () => { writeModelsCache({ 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, { @@ -576,7 +576,7 @@ describe("fetchAvailableModels with connected providers filtering", () => { it("should return empty set when connectedProviders not specified", async () => { writeModelsCache({ 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() @@ -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 () => { writeModelsCache({ 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, { @@ -613,7 +613,7 @@ describe("fetchAvailableModels with connected providers filtering", () => { }) expect(result.size).toBe(1) - expect(result.has("anthropic/claude-opus-4-6")).toBe(true) + expect(result.has("anthropic/claude-opus-4-7")).toBe(true) }) // given filtered fetch @@ -622,7 +622,7 @@ describe("fetchAvailableModels with connected providers filtering", () => { it("should not cache filtered results", async () => { writeModelsCache({ 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 @@ -706,13 +706,13 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)", writeProviderModelsCache({ models: { opencode: ["big-pickle", "gpt-5-nano"], - anthropic: ["claude-opus-4-6"] + anthropic: ["claude-opus-4-7"] }, connected: ["opencode", "anthropic"] }) writeModelsCache({ 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, { @@ -722,7 +722,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)", expect(result.size).toBe(3) expect(result.has("opencode/big-pickle")).toBe(true) expect(result.has("opencode/gpt-5-nano")).toBe(true) - expect(result.has("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("anthropic/claude-sonnet-4-6")).toBe(false) }) @@ -773,7 +773,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)", writeProviderModelsCache({ models: { opencode: ["big-pickle"], - anthropic: ["claude-opus-4-6"], + anthropic: ["claude-opus-4-7"], google: ["gemini-3.1-pro"] }, connected: ["opencode", "anthropic", "google"] @@ -785,7 +785,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)", expect(result.size).toBe(1) 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) }) @@ -812,7 +812,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)", it("should handle mixed string[] and object[] formats across providers", async () => { writeProviderModelsCache({ models: { - anthropic: ["claude-opus-4-6", "claude-sonnet-4-6"], + anthropic: ["claude-opus-4-7", "claude-sonnet-4-6"], ollama: [ { id: "ministral-3:14b-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.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("ollama/ministral-3:14b-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", () => { it("returns true when model exists via fuzzy match", () => { // 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 const result = isModelAvailable("gpt-5.3-codex", available) @@ -870,7 +870,7 @@ describe("isModelAvailable", () => { it("returns false when model not found", () => { // given - const available = new Set(["anthropic/claude-opus-4-6"]) + const available = new Set(["anthropic/claude-opus-4-7"]) // when const result = isModelAvailable("gpt-5.3-codex", available) @@ -924,7 +924,7 @@ describe("fallback model availability", () => { it("returns null for completely unknown model", () => { // 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 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", () => { // given 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"]) // when @@ -950,10 +950,10 @@ describe("fallback model availability", () => { // given const fallbackChain = [ { providers: ["openai"], model: "gpt-5.4" }, - { providers: ["anthropic"], model: "claude-opus-4-6" }, + { providers: ["anthropic"], model: "claude-opus-4-7" }, ] const availableModels = new Set([ - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", "openai/gpt-5.4-preview", ]) @@ -968,7 +968,7 @@ describe("fallback model availability", () => { // given const fallbackChain = [ { 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"]) diff --git a/src/shared/model-availability.ts b/src/shared/model-availability.ts index 595962551..aaec07338 100644 --- a/src/shared/model-availability.ts +++ b/src/shared/model-availability.ts @@ -21,7 +21,7 @@ import { normalizeSDKResponse } from "./normalize-sdk-response" * If providers array is given, only models starting with "provider/" are considered. * * @example - * const available = new Set(["openai/gpt-5.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("claude", available, ["openai"]) // → null (provider filter excludes anthropic) */ diff --git a/src/shared/model-capabilities.test.ts b/src/shared/model-capabilities.test.ts index d221633ef..e79448fbf 100644 --- a/src/shared/model-capabilities.test.ts +++ b/src/shared/model-capabilities.test.ts @@ -16,8 +16,8 @@ describe("getModelCapabilities", () => { generatedAt: "2026-03-25T00:00:00.000Z", sourceUrl: "https://models.dev/api.json", models: { - "claude-opus-4-6": { - id: "claude-opus-4-6", + "claude-opus-4-7": { + id: "claude-opus-4-7", family: "claude-opus", reasoning: true, temperature: true, @@ -66,7 +66,7 @@ describe("getModelCapabilities", () => { findProviderModelMetadataSpy = spyOn(connectedProvidersCache, "findProviderModelMetadata").mockReturnValue(undefined) const result = getModelCapabilities({ providerID: "anthropic", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", runtimeModel: { variants: { low: {}, @@ -78,7 +78,7 @@ describe("getModelCapabilities", () => { }) expect(result).toMatchObject({ - canonicalModelID: "claude-opus-4-6", + canonicalModelID: "claude-opus-4-7", family: "claude-opus", variants: ["low", "medium", "high"], supportsThinking: true, @@ -173,12 +173,12 @@ describe("getModelCapabilities", () => { findProviderModelMetadataSpy = spyOn(connectedProvidersCache, "findProviderModelMetadata").mockReturnValue(undefined) const result = getModelCapabilities({ providerID: "anthropic", - modelID: "claude-opus-4-6-thinking", + modelID: "claude-opus-4-7-thinking", bundledSnapshot, }) expect(result).toMatchObject({ - canonicalModelID: "claude-opus-4-6", + canonicalModelID: "claude-opus-4-7", family: "claude-opus", supportsThinking: true, supportsTemperature: true, @@ -247,13 +247,13 @@ describe("getModelCapabilities", () => { test("canonicalizes provider-prefixed Claude thinking aliases to bare snapshot IDs", () => { const result = getModelCapabilities({ providerID: "anthropic", - modelID: "anthropic/claude-opus-4-6-thinking", + modelID: "anthropic/claude-opus-4-7-thinking", bundledSnapshot, }) expect(result).toMatchObject({ - requestedModelID: "anthropic/claude-opus-4-6-thinking", - canonicalModelID: "claude-opus-4-6", + requestedModelID: "anthropic/claude-opus-4-7-thinking", + canonicalModelID: "claude-opus-4-7", family: "claude-opus", supportsThinking: true, supportsTemperature: true, diff --git a/src/shared/model-capability-aliases.test.ts b/src/shared/model-capability-aliases.test.ts index 6d05c3abc..b6f5b6641 100644 --- a/src/shared/model-capability-aliases.test.ts +++ b/src/shared/model-capability-aliases.test.ts @@ -67,22 +67,22 @@ describe("model-capability-aliases", () => { }) 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({ - requestedModelID: "anthropic/claude-opus-4-6-thinking", - canonicalModelID: "claude-opus-4-6", + requestedModelID: "anthropic/claude-opus-4-7-thinking", + canonicalModelID: "claude-opus-4-7", source: "pattern-alias", ruleID: "claude-thinking-legacy-alias", }) }) 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({ - requestedModelID: "claude-opus-4-6-think", - canonicalModelID: "claude-opus-4-6-think", + requestedModelID: "claude-opus-4-7-think", + canonicalModelID: "claude-opus-4-7-think", source: "canonical", }) }) @@ -98,11 +98,11 @@ describe("model-capability-aliases", () => { }) 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({ - requestedModelID: "claude-opus-4-6-thinking", - canonicalModelID: "claude-opus-4-6", + requestedModelID: "claude-opus-4-7-thinking", + canonicalModelID: "claude-opus-4-7", source: "pattern-alias", ruleID: "claude-thinking-legacy-alias", }) diff --git a/src/shared/model-capability-aliases.ts b/src/shared/model-capability-aliases.ts index 4f9f1c752..fe7ef6b3b 100644 --- a/src/shared/model-capability-aliases.ts +++ b/src/shared/model-capability-aliases.ts @@ -41,9 +41,9 @@ const EXACT_ALIAS_RULES_BY_MODEL: ReadonlyMap = new Map( const PATTERN_ALIAS_RULES: ReadonlyArray = [ { ruleID: "claude-thinking-legacy-alias", - description: "Normalizes the legacy Claude Opus 4.6 thinking suffix to the canonical snapshot ID.", - match: (normalizedModelID) => /^claude-opus-4-6-thinking$/.test(normalizedModelID), - canonicalize: () => "claude-opus-4-6", + description: "Normalizes legacy Claude Opus thinking suffixes (4-6, 4-7) to the canonical snapshot ID.", + match: (normalizedModelID) => /^claude-opus-4-(?:6|7)-thinking$/.test(normalizedModelID), + canonicalize: () => "claude-opus-4-7", }, { ruleID: "gemini-3.1-pro-tier-alias", diff --git a/src/shared/model-capability-guardrails.test.ts b/src/shared/model-capability-guardrails.test.ts index a37534d9a..63ff3aab2 100644 --- a/src/shared/model-capability-guardrails.test.ts +++ b/src/shared/model-capability-guardrails.test.ts @@ -19,7 +19,7 @@ describe("model-capability-guardrails", () => { expect(modelIDs).toEqual([...modelIDs].sort()) 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("kimi-k2.5") }) diff --git a/src/shared/model-error-classifier.test.ts b/src/shared/model-error-classifier.test.ts index a1f7c5265..35c75de0a 100644 --- a/src/shared/model-error-classifier.test.ts +++ b/src/shared/model-error-classifier.test.ts @@ -31,7 +31,7 @@ describe("model-error-classifier", () => { //#given const error = { 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 diff --git a/src/shared/model-format-normalizer.test.ts b/src/shared/model-format-normalizer.test.ts index d28ab975b..4cdca42ad 100644 --- a/src/shared/model-format-normalizer.test.ts +++ b/src/shared/model-format-normalizer.test.ts @@ -9,8 +9,8 @@ describe("normalizeModelFormat", () => { }) it("handles provider with multiple slashes", () => { - const result = normalizeModelFormat("anthropic/claude-opus-4-6/max") - expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6/max" }) + const result = normalizeModelFormat("anthropic/claude-opus-4-7/max") + expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7/max" }) }) it("returns undefined for malformed string without separator", () => { diff --git a/src/shared/model-requirements.test.ts b/src/shared/model-requirements.test.ts index bb110f554..3692677f7 100644 --- a/src/shared/model-requirements.test.ts +++ b/src/shared/model-requirements.test.ts @@ -23,7 +23,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => { 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 const sisyphus = AGENT_MODEL_REQUIREMENTS["sisyphus"] @@ -35,12 +35,12 @@ describe("AGENT_MODEL_REQUIREMENTS", () => { expect(sisyphus.requiresAnyModel).toBe(true) const primary = sisyphus.fallbackChain[0] - expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode"]) - expect(primary.model).toBe("claude-opus-4-6") + expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"]) + expect(primary.model).toBe("claude-opus-4-7") expect(primary.variant).toBe("max") const second = sisyphus.fallbackChain[1] - expect(second.providers).toEqual(["opencode-go"]) + expect(second.providers).toEqual(["opencode-go", "vercel"]) expect(second.model).toBe("kimi-k2.5") const third = sisyphus.fallbackChain[2] @@ -132,56 +132,56 @@ describe("AGENT_MODEL_REQUIREMENTS", () => { expect(multimodalLooker.fallbackChain).toHaveLength(4) const primary = multimodalLooker.fallbackChain[0] - expect(primary.providers).toEqual(["openai", "opencode"]) + expect(primary.providers).toEqual(["openai", "opencode", "vercel"]) expect(primary.model).toBe("gpt-5.4") expect(primary.variant).toBe("medium") const secondary = multimodalLooker.fallbackChain[1] - expect(secondary.providers).toEqual(["opencode-go"]) + expect(secondary.providers).toEqual(["opencode-go", "vercel"]) expect(secondary.model).toBe("kimi-k2.5") const tertiary = multimodalLooker.fallbackChain[2] expect(tertiary.model).toBe("glm-4.6v") const last = multimodalLooker.fallbackChain[3] - expect(last.providers).toEqual(["openai", "github-copilot", "opencode"]) + expect(last.providers).toEqual(["openai", "github-copilot", "opencode", "vercel"]) 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 const prometheus = AGENT_MODEL_REQUIREMENTS["prometheus"] // #when - accessing Prometheus requirement - // #then - claude-opus-4-6 is first + // #then - claude-opus-4-7 is first expect(prometheus).toBeDefined() expect(prometheus.fallbackChain).toBeArray() expect(prometheus.fallbackChain.length).toBeGreaterThan(1) const primary = prometheus.fallbackChain[0] - expect(primary.model).toBe("claude-opus-4-6") - expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode"]) + expect(primary.model).toBe("claude-opus-4-7") + expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"]) 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 const metis = AGENT_MODEL_REQUIREMENTS["metis"] // #when - accessing Metis requirement - // #then - claude-opus-4-6 is first + // #then - claude-opus-4-7 is first expect(metis).toBeDefined() expect(metis.fallbackChain).toBeArray() expect(metis.fallbackChain.length).toBeGreaterThan(1) const primary = metis.fallbackChain[0] - expect(primary.model).toBe("claude-opus-4-6") - expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode"]) + expect(primary.model).toBe("claude-opus-4-7") + expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"]) expect(primary.variant).toBe("max") const openAiFallback = metis.fallbackChain.find((entry) => entry.providers.includes("openai")) expect(openAiFallback).toEqual({ - providers: ["openai", "github-copilot", "opencode"], + providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5.4", variant: "high", }) @@ -223,7 +223,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => { const tertiary = atlas.fallbackChain[2] expect(tertiary).toEqual({ - providers: ["openai", "github-copilot", "opencode"], + providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5.4", variant: "medium", }) @@ -245,7 +245,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => { // then expect(openAiFallback).toEqual({ - providers: ["openai", "github-copilot", "opencode"], + providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5.4", variant: "medium", }) @@ -261,7 +261,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => { // #when - accessing hephaestus requirement // #then - requiresProvider includes openai, github-copilot, venice, and opencode expect(hephaestus).toBeDefined() - expect(hephaestus.requiresProvider).toEqual(["openai", "github-copilot", "venice", "opencode"]) + expect(hephaestus.requiresProvider).toEqual(["openai", "github-copilot", "venice", "opencode", "vercel"]) expect(hephaestus.requiresModel).toBeUndefined() }) @@ -356,7 +356,7 @@ describe("CATEGORY_MODEL_REQUIREMENTS", () => { expect(second.model).toBe("glm-5") 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") const fourth = visualEngineering.fallbackChain[3] @@ -402,25 +402,25 @@ describe("CATEGORY_MODEL_REQUIREMENTS", () => { 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 const unspecifiedHigh = CATEGORY_MODEL_REQUIREMENTS["unspecified-high"] // #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.fallbackChain).toBeArray() expect(unspecifiedHigh.fallbackChain.length).toBeGreaterThan(1) 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.providers).toEqual(["anthropic", "github-copilot", "opencode"]) + expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"]) const secondary = unspecifiedHigh.fallbackChain[1] expect(secondary.model).toBe("gpt-5.4") expect(secondary.variant).toBe("high") - expect(secondary.providers).toEqual(["openai", "github-copilot", "opencode"]) + expect(secondary.providers).toEqual(["openai", "github-copilot", "opencode", "vercel"]) }) test("artistry has valid fallbackChain with gemini-3.1-pro as primary", () => { @@ -505,14 +505,14 @@ describe("FallbackEntry type", () => { // given - a valid FallbackEntry object const entry: FallbackEntry = { providers: ["anthropic", "github-copilot", "opencode"], - model: "claude-opus-4-6", + model: "claude-opus-4-7", variant: "high", } // when - accessing properties // then - all properties are accessible 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") }) @@ -534,7 +534,7 @@ describe("ModelRequirement type", () => { // given - a valid ModelRequirement object const requirement: ModelRequirement = { 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" }, ], } @@ -543,7 +543,7 @@ describe("ModelRequirement type", () => { // then - fallbackChain is accessible with correct structure expect(requirement.fallbackChain).toBeArray() 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") }) diff --git a/src/shared/model-requirements.ts b/src/shared/model-requirements.ts index 5a1889eae..16f64cd6a 100644 --- a/src/shared/model-requirements.ts +++ b/src/shared/model-requirements.ts @@ -21,11 +21,11 @@ export const AGENT_MODEL_REQUIREMENTS: Record = { sisyphus: { fallbackChain: [ { - providers: ["anthropic", "github-copilot", "opencode"], - model: "claude-opus-4-6", + providers: ["anthropic", "github-copilot", "opencode", "vercel"], + model: "claude-opus-4-7", variant: "max", }, - { providers: ["opencode-go"], model: "kimi-k2.5" }, + { providers: ["opencode-go", "vercel"], model: "kimi-k2.5" }, { providers: ["kimi-for-coding"], model: "k2p5" }, { providers: [ @@ -35,11 +35,12 @@ export const AGENT_MODEL_REQUIREMENTS: Record = { "firmware", "ollama-cloud", "aihubmix", + "vercel", ], model: "kimi-k2.5", }, - { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.4", variant: "medium" }, - { providers: ["zai-coding-plan", "opencode"], model: "glm-5" }, + { providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5.4", variant: "medium" }, + { providers: ["zai-coding-plan", "opencode", "vercel"], model: "glm-5" }, { providers: ["opencode"], model: "big-pickle" }, ], requiresAnyModel: true, @@ -47,73 +48,73 @@ export const AGENT_MODEL_REQUIREMENTS: Record = { hephaestus: { fallbackChain: [ { - providers: ["openai", "github-copilot", "venice", "opencode"], + providers: ["openai", "github-copilot", "venice", "opencode", "vercel"], model: "gpt-5.4", variant: "medium", }, ], - requiresProvider: ["openai", "github-copilot", "venice", "opencode"], + requiresProvider: ["openai", "github-copilot", "venice", "opencode", "vercel"], }, oracle: { fallbackChain: [ { - providers: ["openai", "github-copilot", "opencode"], + providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5.4", variant: "high", }, { - providers: ["google", "github-copilot", "opencode"], + providers: ["google", "github-copilot", "opencode", "vercel"], model: "gemini-3.1-pro", variant: "high", }, { - providers: ["anthropic", "github-copilot", "opencode"], - model: "claude-opus-4-6", + providers: ["anthropic", "github-copilot", "opencode", "vercel"], + model: "claude-opus-4-7", variant: "max", }, - { providers: ["opencode-go"], model: "glm-5" }, + { providers: ["opencode-go", "vercel"], model: "glm-5" }, ], }, librarian: { fallbackChain: [ - { providers: ["opencode-go"], model: "minimax-m2.7" }, - { providers: ["opencode"], model: "minimax-m2.7-highspeed" }, - { providers: ["anthropic", "opencode"], model: "claude-haiku-4-5" }, - { providers: ["opencode"], model: "gpt-5-nano" }, + { providers: ["opencode-go", "vercel"], model: "minimax-m2.7" }, + { providers: ["opencode", "vercel"], model: "minimax-m2.7-highspeed" }, + { providers: ["anthropic", "opencode", "vercel"], model: "claude-haiku-4-5" }, + { providers: ["opencode", "vercel"], model: "gpt-5-nano" }, ], }, explore: { fallbackChain: [ - { providers: ["github-copilot", "xai"], model: "grok-code-fast-1" }, - { providers: ["opencode-go"], model: "minimax-m2.7-highspeed" }, - { providers: ["opencode"], model: "minimax-m2.7" }, - { providers: ["anthropic", "opencode"], model: "claude-haiku-4-5" }, - { providers: ["opencode"], model: "gpt-5-nano" }, + { providers: ["github-copilot", "xai", "vercel"], model: "grok-code-fast-1" }, + { providers: ["opencode-go", "vercel"], model: "minimax-m2.7-highspeed" }, + { providers: ["opencode", "vercel"], model: "minimax-m2.7" }, + { providers: ["anthropic", "opencode", "vercel"], model: "claude-haiku-4-5" }, + { providers: ["opencode", "vercel"], model: "gpt-5-nano" }, ], }, "multimodal-looker": { fallbackChain: [ - { providers: ["openai", "opencode"], model: "gpt-5.4", variant: "medium" }, - { providers: ["opencode-go"], model: "kimi-k2.5" }, - { providers: ["zai-coding-plan"], model: "glm-4.6v" }, - { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5-nano" }, + { providers: ["openai", "opencode", "vercel"], model: "gpt-5.4", variant: "medium" }, + { providers: ["opencode-go", "vercel"], model: "kimi-k2.5" }, + { providers: ["zai-coding-plan", "vercel"], model: "glm-4.6v" }, + { providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5-nano" }, ], }, prometheus: { fallbackChain: [ { - providers: ["anthropic", "github-copilot", "opencode"], - model: "claude-opus-4-6", + providers: ["anthropic", "github-copilot", "opencode", "vercel"], + model: "claude-opus-4-7", variant: "max", }, { - providers: ["openai", "github-copilot", "opencode"], + providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5.4", variant: "high", }, - { providers: ["opencode-go"], model: "glm-5" }, + { providers: ["opencode-go", "vercel"], model: "glm-5" }, { - providers: ["google", "github-copilot", "opencode"], + providers: ["google", "github-copilot", "opencode", "vercel"], model: "gemini-3.1-pro", }, ], @@ -121,61 +122,61 @@ export const AGENT_MODEL_REQUIREMENTS: Record = { metis: { fallbackChain: [ { - providers: ["anthropic", "github-copilot", "opencode"], - model: "claude-opus-4-6", + providers: ["anthropic", "github-copilot", "opencode", "vercel"], + model: "claude-opus-4-7", variant: "max", }, { - providers: ["openai", "github-copilot", "opencode"], + providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5.4", variant: "high", }, - { providers: ["opencode-go"], model: "glm-5" }, + { providers: ["opencode-go", "vercel"], model: "glm-5" }, { providers: ["kimi-for-coding"], model: "k2p5" }, ], }, momus: { fallbackChain: [ { - providers: ["openai", "github-copilot", "opencode"], + providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5.4", variant: "xhigh", }, { - providers: ["anthropic", "github-copilot", "opencode"], - model: "claude-opus-4-6", + providers: ["anthropic", "github-copilot", "opencode", "vercel"], + model: "claude-opus-4-7", variant: "max", }, { - providers: ["google", "github-copilot", "opencode"], + providers: ["google", "github-copilot", "opencode", "vercel"], model: "gemini-3.1-pro", variant: "high", }, - { providers: ["opencode-go"], model: "glm-5" }, + { providers: ["opencode-go", "vercel"], model: "glm-5" }, ], }, atlas: { fallbackChain: [ - { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-6" }, - { providers: ["opencode-go"], model: "kimi-k2.5" }, + { providers: ["anthropic", "github-copilot", "opencode", "vercel"], model: "claude-sonnet-4-6" }, + { providers: ["opencode-go", "vercel"], model: "kimi-k2.5" }, { - providers: ["openai", "github-copilot", "opencode"], + providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5.4", variant: "medium", }, - { providers: ["opencode-go"], model: "minimax-m2.7" }, + { providers: ["opencode-go", "vercel"], model: "minimax-m2.7" }, ], }, "sisyphus-junior": { fallbackChain: [ - { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-6" }, - { providers: ["opencode-go"], model: "kimi-k2.5" }, + { providers: ["anthropic", "github-copilot", "opencode", "vercel"], model: "claude-sonnet-4-6" }, + { providers: ["opencode-go", "vercel"], model: "kimi-k2.5" }, { - providers: ["openai", "github-copilot", "opencode"], + providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5.4", variant: "medium", }, - { providers: ["opencode-go"], model: "minimax-m2.7" }, + { providers: ["opencode-go", "vercel"], model: "minimax-m2.7" }, { providers: ["opencode"], model: "big-pickle" }, ], }, @@ -185,54 +186,54 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record = { "visual-engineering": { fallbackChain: [ { - providers: ["google", "github-copilot", "opencode"], + providers: ["google", "github-copilot", "opencode", "vercel"], model: "gemini-3.1-pro", variant: "high", }, - { providers: ["zai-coding-plan", "opencode"], model: "glm-5" }, + { providers: ["zai-coding-plan", "opencode", "vercel"], model: "glm-5" }, { - providers: ["anthropic", "github-copilot", "opencode"], - model: "claude-opus-4-6", + providers: ["anthropic", "github-copilot", "opencode", "vercel"], + model: "claude-opus-4-7", variant: "max", }, - { providers: ["opencode-go"], model: "glm-5" }, + { providers: ["opencode-go", "vercel"], model: "glm-5" }, { providers: ["kimi-for-coding"], model: "k2p5" }, ], }, ultrabrain: { fallbackChain: [ { - providers: ["openai", "opencode"], + providers: ["openai", "opencode", "vercel"], model: "gpt-5.4", variant: "xhigh", }, { - providers: ["google", "github-copilot", "opencode"], + providers: ["google", "github-copilot", "opencode", "vercel"], model: "gemini-3.1-pro", variant: "high", }, { - providers: ["anthropic", "github-copilot", "opencode"], - model: "claude-opus-4-6", + providers: ["anthropic", "github-copilot", "opencode", "vercel"], + model: "claude-opus-4-7", variant: "max", }, - { providers: ["opencode-go"], model: "glm-5" }, + { providers: ["opencode-go", "vercel"], model: "glm-5" }, ], }, deep: { fallbackChain: [ { - providers: ["openai", "github-copilot", "venice", "opencode"], + providers: ["openai", "github-copilot", "venice", "opencode", "vercel"], model: "gpt-5.4", variant: "medium", }, { - providers: ["anthropic", "github-copilot", "opencode"], - model: "claude-opus-4-6", + providers: ["anthropic", "github-copilot", "opencode", "vercel"], + model: "claude-opus-4-7", variant: "max", }, { - providers: ["google", "github-copilot", "opencode"], + providers: ["google", "github-copilot", "opencode", "vercel"], model: "gemini-3.1-pro", variant: "high", }, @@ -241,72 +242,72 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record = { artistry: { fallbackChain: [ { - providers: ["google", "github-copilot", "opencode"], + providers: ["google", "github-copilot", "opencode", "vercel"], model: "gemini-3.1-pro", variant: "high", }, { - providers: ["anthropic", "github-copilot", "opencode"], - model: "claude-opus-4-6", + providers: ["anthropic", "github-copilot", "opencode", "vercel"], + model: "claude-opus-4-7", variant: "max", }, - { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.4" }, + { providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5.4" }, ], requiresModel: "gemini-3.1-pro", }, quick: { fallbackChain: [ { - providers: ["openai", "github-copilot", "opencode"], + providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5.4-mini", }, { - providers: ["anthropic", "github-copilot", "opencode"], + providers: ["anthropic", "github-copilot", "opencode", "vercel"], model: "claude-haiku-4-5", }, { - providers: ["google", "github-copilot", "opencode"], + providers: ["google", "github-copilot", "opencode", "vercel"], model: "gemini-3-flash", }, - { providers: ["opencode-go"], model: "minimax-m2.7" }, - { providers: ["opencode"], model: "gpt-5-nano" }, + { providers: ["opencode-go", "vercel"], model: "minimax-m2.7" }, + { providers: ["opencode", "vercel"], model: "gpt-5-nano" }, ], }, "unspecified-low": { fallbackChain: [ { - providers: ["anthropic", "github-copilot", "opencode"], + providers: ["anthropic", "github-copilot", "opencode", "vercel"], model: "claude-sonnet-4-6", }, { - providers: ["openai", "opencode"], + providers: ["openai", "opencode", "vercel"], model: "gpt-5.3-codex", variant: "medium", }, - { providers: ["opencode-go"], model: "kimi-k2.5" }, + { providers: ["opencode-go", "vercel"], model: "kimi-k2.5" }, { - providers: ["google", "github-copilot", "opencode"], + providers: ["google", "github-copilot", "opencode", "vercel"], model: "gemini-3-flash", }, - { providers: ["opencode-go"], model: "minimax-m2.7" }, + { providers: ["opencode-go", "vercel"], model: "minimax-m2.7" }, ], }, "unspecified-high": { fallbackChain: [ { - providers: ["anthropic", "github-copilot", "opencode"], - model: "claude-opus-4-6", + providers: ["anthropic", "github-copilot", "opencode", "vercel"], + model: "claude-opus-4-7", variant: "max", }, { - providers: ["openai", "github-copilot", "opencode"], + providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5.4", variant: "high", }, - { providers: ["zai-coding-plan", "opencode"], model: "glm-5" }, + { providers: ["zai-coding-plan", "opencode", "vercel"], model: "glm-5" }, { providers: ["kimi-for-coding"], model: "k2p5" }, - { providers: ["opencode-go"], model: "glm-5" }, - { providers: ["opencode"], model: "kimi-k2.5" }, + { providers: ["opencode-go", "vercel"], model: "glm-5" }, + { providers: ["opencode", "vercel"], model: "kimi-k2.5" }, { providers: [ "opencode", @@ -315,6 +316,7 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record = { "firmware", "ollama-cloud", "aihubmix", + "vercel", ], model: "kimi-k2.5", }, @@ -323,15 +325,15 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record = { writing: { fallbackChain: [ { - providers: ["google", "github-copilot", "opencode"], + providers: ["google", "github-copilot", "opencode", "vercel"], model: "gemini-3-flash", }, - { providers: ["opencode-go"], model: "kimi-k2.5" }, + { providers: ["opencode-go", "vercel"], model: "kimi-k2.5" }, { - providers: ["anthropic", "github-copilot", "opencode"], + providers: ["anthropic", "github-copilot", "opencode", "vercel"], model: "claude-sonnet-4-6", }, - { providers: ["opencode-go"], model: "minimax-m2.7" }, + { providers: ["opencode-go", "vercel"], model: "minimax-m2.7" }, ], }, }; diff --git a/src/shared/model-resolver.test.ts b/src/shared/model-resolver.test.ts index 292aac718..0e546c312 100644 --- a/src/shared/model-resolver.test.ts +++ b/src/shared/model-resolver.test.ts @@ -12,7 +12,7 @@ describe("resolveModel", () => { test("returns userModel when all three are set", () => { // given const input: ModelResolutionInput = { - userModel: "anthropic/claude-opus-4-6", + userModel: "anthropic/claude-opus-4-7", inheritedModel: "openai/gpt-5.4", systemDefault: "google/gemini-3.1-pro", } @@ -21,7 +21,7 @@ describe("resolveModel", () => { const result = resolveModel(input) // then - expect(result).toBe("anthropic/claude-opus-4-6") + expect(result).toBe("anthropic/claude-opus-4-7") }) test("returns inheritedModel when userModel is undefined", () => { @@ -91,7 +91,7 @@ describe("resolveModel", () => { test("same input returns same output (referential transparency)", () => { // given const input: ModelResolutionInput = { - userModel: "anthropic/claude-opus-4-6", + userModel: "anthropic/claude-opus-4-7", inheritedModel: "openai/gpt-5.4", systemDefault: "google/gemini-3.1-pro", } @@ -122,11 +122,11 @@ describe("resolveModelWithFallback", () => { // given const input: ExtendedModelResolutionInput = { uiSelectedModel: "opencode/big-pickle", - userModel: "anthropic/claude-opus-4-6", + userModel: "anthropic/claude-opus-4-7", fallbackChain: [ - { providers: ["anthropic", "github-copilot"], model: "claude-opus-4-6" }, + { providers: ["anthropic", "github-copilot"], model: "claude-opus-4-7" }, ], - availableModels: new Set(["anthropic/claude-opus-4-6", "github-copilot/claude-opus-4-6-preview"]), + availableModels: new Set(["anthropic/claude-opus-4-7", "github-copilot/claude-opus-4-7-preview"]), systemDefaultModel: "google/gemini-3.1-pro", } @@ -143,8 +143,8 @@ describe("resolveModelWithFallback", () => { // given const input: ExtendedModelResolutionInput = { uiSelectedModel: "opencode/big-pickle", - userModel: "anthropic/claude-opus-4-6", - availableModels: new Set(["anthropic/claude-opus-4-6"]), + userModel: "anthropic/claude-opus-4-7", + availableModels: new Set(["anthropic/claude-opus-4-7"]), systemDefaultModel: "google/gemini-3.1-pro", } @@ -160,8 +160,8 @@ describe("resolveModelWithFallback", () => { // given const input: ExtendedModelResolutionInput = { uiSelectedModel: " ", - userModel: "anthropic/claude-opus-4-6", - availableModels: new Set(["anthropic/claude-opus-4-6"]), + userModel: "anthropic/claude-opus-4-7", + availableModels: new Set(["anthropic/claude-opus-4-7"]), systemDefaultModel: "google/gemini-3.1-pro", } @@ -169,16 +169,16 @@ describe("resolveModelWithFallback", () => { const result = resolveModelWithFallback(input) // then - expect(result!.model).toBe("anthropic/claude-opus-4-6") - expect(logSpy).toHaveBeenCalledWith("Model resolved via config override", { model: "anthropic/claude-opus-4-6" }) + expect(result!.model).toBe("anthropic/claude-opus-4-7") + expect(logSpy).toHaveBeenCalledWith("Model resolved via config override", { model: "anthropic/claude-opus-4-7" }) }) test("empty string uiSelectedModel falls through to config override", () => { // given const input: ExtendedModelResolutionInput = { uiSelectedModel: "", - userModel: "anthropic/claude-opus-4-6", - availableModels: new Set(["anthropic/claude-opus-4-6"]), + userModel: "anthropic/claude-opus-4-7", + availableModels: new Set(["anthropic/claude-opus-4-7"]), systemDefaultModel: "google/gemini-3.1-pro", } @@ -186,7 +186,7 @@ describe("resolveModelWithFallback", () => { const result = resolveModelWithFallback(input) // then - expect(result!.model).toBe("anthropic/claude-opus-4-6") + expect(result!.model).toBe("anthropic/claude-opus-4-7") }) }) @@ -194,11 +194,11 @@ describe("resolveModelWithFallback", () => { test("returns userModel with override source when userModel is provided", () => { // given const input: ExtendedModelResolutionInput = { - userModel: "anthropic/claude-opus-4-6", + userModel: "anthropic/claude-opus-4-7", fallbackChain: [ - { providers: ["anthropic", "github-copilot"], model: "claude-opus-4-6" }, + { providers: ["anthropic", "github-copilot"], model: "claude-opus-4-7" }, ], - availableModels: new Set(["anthropic/claude-opus-4-6", "github-copilot/claude-opus-4-6-preview"]), + availableModels: new Set(["anthropic/claude-opus-4-7", "github-copilot/claude-opus-4-7-preview"]), systemDefaultModel: "google/gemini-3.1-pro", } @@ -206,9 +206,9 @@ describe("resolveModelWithFallback", () => { const result = resolveModelWithFallback(input) // then - expect(result!.model).toBe("anthropic/claude-opus-4-6") + expect(result!.model).toBe("anthropic/claude-opus-4-7") expect(result!.source).toBe("override") - expect(logSpy).toHaveBeenCalledWith("Model resolved via config override", { model: "anthropic/claude-opus-4-6" }) + expect(logSpy).toHaveBeenCalledWith("Model resolved via config override", { model: "anthropic/claude-opus-4-7" }) }) test("override takes priority even if model not in availableModels", () => { @@ -216,9 +216,9 @@ describe("resolveModelWithFallback", () => { const input: ExtendedModelResolutionInput = { userModel: "custom/my-model", fallbackChain: [ - { providers: ["anthropic"], model: "claude-opus-4-6" }, + { providers: ["anthropic"], model: "claude-opus-4-7" }, ], - availableModels: new Set(["anthropic/claude-opus-4-6"]), + availableModels: new Set(["anthropic/claude-opus-4-7"]), systemDefaultModel: "google/gemini-3.1-pro", } @@ -235,9 +235,9 @@ describe("resolveModelWithFallback", () => { const input: ExtendedModelResolutionInput = { userModel: " ", fallbackChain: [ - { providers: ["anthropic"], model: "claude-opus-4-6" }, + { providers: ["anthropic"], model: "claude-opus-4-7" }, ], - availableModels: new Set(["anthropic/claude-opus-4-6"]), + availableModels: new Set(["anthropic/claude-opus-4-7"]), systemDefaultModel: "google/gemini-3.1-pro", } @@ -253,9 +253,9 @@ describe("resolveModelWithFallback", () => { const input: ExtendedModelResolutionInput = { userModel: "", fallbackChain: [ - { providers: ["anthropic"], model: "claude-opus-4-6" }, + { providers: ["anthropic"], model: "claude-opus-4-7" }, ], - availableModels: new Set(["anthropic/claude-opus-4-6"]), + availableModels: new Set(["anthropic/claude-opus-4-7"]), systemDefaultModel: "google/gemini-3.1-pro", } @@ -272,9 +272,9 @@ describe("resolveModelWithFallback", () => { // given const input: ExtendedModelResolutionInput = { fallbackChain: [ - { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6" }, + { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-7" }, ], - availableModels: new Set(["github-copilot/claude-opus-4-6-preview", "opencode/claude-opus-4-7"]), + availableModels: new Set(["github-copilot/claude-opus-4-7-preview", "opencode/claude-opus-4-7"]), systemDefaultModel: "google/gemini-3.1-pro", } @@ -282,12 +282,12 @@ describe("resolveModelWithFallback", () => { const result = resolveModelWithFallback(input) // then - expect(result!.model).toBe("github-copilot/claude-opus-4-6-preview") + expect(result!.model).toBe("github-copilot/claude-opus-4-7-preview") expect(result!.source).toBe("provider-fallback") expect(logSpy).toHaveBeenCalledWith("Model resolved via fallback chain (availability confirmed)", { provider: "github-copilot", - model: "claude-opus-4-6", - match: "github-copilot/claude-opus-4-6-preview", + model: "claude-opus-4-7", + match: "github-copilot/claude-opus-4-7-preview", variant: undefined, }) }) @@ -298,7 +298,7 @@ describe("resolveModelWithFallback", () => { fallbackChain: [ { providers: ["openai", "anthropic", "google"], model: "gpt-5.4" }, ], - availableModels: new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-6", "google/gemini-3.1-pro"]), + availableModels: new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-7", "google/gemini-3.1-pro"]), systemDefaultModel: "google/gemini-3.1-pro", } @@ -334,7 +334,7 @@ describe("resolveModelWithFallback", () => { fallbackChain: [ { providers: ["anthropic", "github-copilot"], model: "claude-opus" }, ], - availableModels: new Set(["anthropic/claude-opus-4-6", "github-copilot/claude-opus-4-6-preview"]), + availableModels: new Set(["anthropic/claude-opus-4-7", "github-copilot/claude-opus-4-7-preview"]), systemDefaultModel: "google/gemini-3.1-pro", } @@ -342,14 +342,14 @@ describe("resolveModelWithFallback", () => { const result = resolveModelWithFallback(input) // then - expect(result!.model).toBe("anthropic/claude-opus-4-6") + expect(result!.model).toBe("anthropic/claude-opus-4-7") expect(result!.source).toBe("provider-fallback") }) test("skips fallback chain when not provided", () => { // given const input: ExtendedModelResolutionInput = { - availableModels: new Set(["anthropic/claude-opus-4-6"]), + availableModels: new Set(["anthropic/claude-opus-4-7"]), systemDefaultModel: "google/gemini-3.1-pro", } @@ -364,7 +364,7 @@ describe("resolveModelWithFallback", () => { // given const input: ExtendedModelResolutionInput = { fallbackChain: [], - availableModels: new Set(["anthropic/claude-opus-4-6"]), + availableModels: new Set(["anthropic/claude-opus-4-7"]), systemDefaultModel: "google/gemini-3.1-pro", } @@ -381,7 +381,7 @@ describe("resolveModelWithFallback", () => { fallbackChain: [ { providers: ["anthropic"], model: "CLAUDE-OPUS" }, ], - availableModels: new Set(["anthropic/claude-opus-4-6"]), + availableModels: new Set(["anthropic/claude-opus-4-7"]), systemDefaultModel: "google/gemini-3.1-pro", } @@ -389,7 +389,7 @@ describe("resolveModelWithFallback", () => { const result = resolveModelWithFallback(input) // then - expect(result!.model).toBe("anthropic/claude-opus-4-6") + expect(result!.model).toBe("anthropic/claude-opus-4-7") expect(result!.source).toBe("provider-fallback") }) @@ -480,7 +480,7 @@ describe("resolveModelWithFallback", () => { fallbackChain: [ { providers: ["anthropic"], model: "nonexistent-model" }, ], - availableModels: new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-6"]), + availableModels: new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-7"]), systemDefaultModel: "google/gemini-3.1-pro", } @@ -498,7 +498,7 @@ describe("resolveModelWithFallback", () => { const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null) const input: ExtendedModelResolutionInput = { fallbackChain: [ - { providers: ["anthropic"], model: "claude-opus-4-6" }, + { providers: ["anthropic"], model: "claude-opus-4-7" }, ], availableModels: new Set(), systemDefaultModel: undefined, // no system default configured @@ -517,7 +517,7 @@ describe("resolveModelWithFallback", () => { const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai", "google"]) const input: ExtendedModelResolutionInput = { fallbackChain: [ - { providers: ["anthropic", "openai"], model: "claude-opus-4-6" }, + { providers: ["anthropic", "openai"], model: "claude-opus-4-7" }, ], availableModels: new Set(), systemDefaultModel: "google/gemini-3.1-pro", @@ -527,7 +527,7 @@ describe("resolveModelWithFallback", () => { const result = resolveModelWithFallback(input) // then - should use connected provider (openai) from fallback chain - expect(result!.model).toBe("openai/claude-opus-4-6") + expect(result!.model).toBe("openai/claude-opus-4-7") expect(result!.source).toBe("provider-fallback") cacheSpy.mockRestore() }) @@ -561,14 +561,14 @@ describe("resolveModelWithFallback", () => { { providers: ["openai", "opencode"], model: "claude-haiku-4-5" }, ], availableModels: new Set(), - systemDefaultModel: "anthropic/claude-opus-4-6-20251101", + systemDefaultModel: "anthropic/claude-opus-4-7-20251101", } // when const result = resolveModelWithFallback(input) // then - no provider in fallback is connected, fall through to system default - expect(result!.model).toBe("anthropic/claude-opus-4-6-20251101") + expect(result!.model).toBe("anthropic/claude-opus-4-7-20251101") expect(result!.source).toBe("system-default") cacheSpy.mockRestore() }) @@ -578,7 +578,7 @@ describe("resolveModelWithFallback", () => { const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null) const input: ExtendedModelResolutionInput = { fallbackChain: [ - { providers: ["anthropic"], model: "claude-opus-4-6" }, + { providers: ["anthropic"], model: "claude-opus-4-7" }, ], availableModels: new Set(), systemDefaultModel: "google/gemini-3.1-pro", @@ -612,20 +612,20 @@ describe("resolveModelWithFallback", () => { describe("Multi-entry fallbackChain", () => { test("resolves to claude-opus when OpenAI unavailable but Anthropic available (oracle scenario)", () => { // given - const availableModels = new Set(["anthropic/claude-opus-4-6"]) + const availableModels = new Set(["anthropic/claude-opus-4-7"]) // when const result = resolveModelWithFallback({ fallbackChain: [ { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.4", variant: "high" }, - { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" }, + { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-7", variant: "max" }, ], availableModels, systemDefaultModel: "system/default", }) // then - expect(result!.model).toBe("anthropic/claude-opus-4-6") + expect(result!.model).toBe("anthropic/claude-opus-4-7") expect(result!.source).toBe("provider-fallback") }) @@ -652,14 +652,14 @@ describe("resolveModelWithFallback", () => { // given const availableModels = new Set([ "openai/gpt-5.4", - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", ]) // when const result = resolveModelWithFallback({ fallbackChain: [ { providers: ["openai"], model: "gpt-5.4" }, - { providers: ["anthropic"], model: "claude-opus-4-6" }, + { providers: ["anthropic"], model: "claude-opus-4-7" }, ], availableModels, systemDefaultModel: "system/default", @@ -678,7 +678,7 @@ describe("resolveModelWithFallback", () => { const result = resolveModelWithFallback({ fallbackChain: [ { providers: ["openai"], model: "gpt-5.4" }, - { providers: ["anthropic"], model: "claude-opus-4-6" }, + { providers: ["anthropic"], model: "claude-opus-4-7" }, { providers: ["google"], model: "gemini-3.1-pro" }, ], availableModels, @@ -695,7 +695,7 @@ describe("resolveModelWithFallback", () => { test("result has correct ModelResolutionResult shape", () => { // given const input: ExtendedModelResolutionInput = { - userModel: "anthropic/claude-opus-4-6", + userModel: "anthropic/claude-opus-4-7", availableModels: new Set(), systemDefaultModel: "google/gemini-3.1-pro", } @@ -718,7 +718,7 @@ describe("resolveModelWithFallback", () => { fallbackChain: [ { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro" }, ], - availableModels: new Set(["google/gemini-3.1-pro-preview", "anthropic/claude-opus-4-6"]), + availableModels: new Set(["google/gemini-3.1-pro-preview", "anthropic/claude-opus-4-7"]), systemDefaultModel: "anthropic/claude-sonnet-4-6", } @@ -754,9 +754,9 @@ describe("resolveModelWithFallback", () => { const input: ExtendedModelResolutionInput = { categoryDefaultModel: "google/gemini-3.1-pro", fallbackChain: [ - { providers: ["anthropic"], model: "claude-opus-4-6" }, + { providers: ["anthropic"], model: "claude-opus-4-7" }, ], - availableModels: new Set(["anthropic/claude-opus-4-6"]), + availableModels: new Set(["anthropic/claude-opus-4-7"]), systemDefaultModel: "system/default", } @@ -764,19 +764,19 @@ describe("resolveModelWithFallback", () => { const result = resolveModelWithFallback(input) // then - should fall through to fallbackChain - expect(result!.model).toBe("anthropic/claude-opus-4-6") + expect(result!.model).toBe("anthropic/claude-opus-4-7") expect(result!.source).toBe("provider-fallback") }) test("userModel takes priority over categoryDefaultModel", () => { // given - both userModel and categoryDefaultModel provided const input: ExtendedModelResolutionInput = { - userModel: "anthropic/claude-opus-4-6", + userModel: "anthropic/claude-opus-4-7", categoryDefaultModel: "google/gemini-3.1-pro", fallbackChain: [ { providers: ["google"], model: "gemini-3.1-pro" }, ], - availableModels: new Set(["google/gemini-3.1-pro-preview", "anthropic/claude-opus-4-6"]), + availableModels: new Set(["google/gemini-3.1-pro-preview", "anthropic/claude-opus-4-7"]), systemDefaultModel: "system/default", } @@ -784,7 +784,7 @@ describe("resolveModelWithFallback", () => { const result = resolveModelWithFallback(input) // then - userModel wins - expect(result!.model).toBe("anthropic/claude-opus-4-6") + expect(result!.model).toBe("anthropic/claude-opus-4-7") expect(result!.source).toBe("override") }) @@ -916,7 +916,7 @@ describe("resolveModelWithFallback", () => { test("still returns override when userModel provided even if systemDefaultModel undefined", () => { // given const input: ExtendedModelResolutionInput = { - userModel: "anthropic/claude-opus-4-6", + userModel: "anthropic/claude-opus-4-7", availableModels: new Set(), systemDefaultModel: undefined, } @@ -926,7 +926,7 @@ describe("resolveModelWithFallback", () => { // then expect(result).toBeDefined() - expect(result!.model).toBe("anthropic/claude-opus-4-6") + expect(result!.model).toBe("anthropic/claude-opus-4-7") expect(result!.source).toBe("override") }) @@ -934,9 +934,9 @@ describe("resolveModelWithFallback", () => { // given const input: ExtendedModelResolutionInput = { fallbackChain: [ - { providers: ["anthropic"], model: "claude-opus-4-6" }, + { providers: ["anthropic"], model: "claude-opus-4-7" }, ], - availableModels: new Set(["anthropic/claude-opus-4-6"]), + availableModels: new Set(["anthropic/claude-opus-4-7"]), systemDefaultModel: undefined, } @@ -945,7 +945,7 @@ describe("resolveModelWithFallback", () => { // then expect(result).toBeDefined() - expect(result!.model).toBe("anthropic/claude-opus-4-6") + expect(result!.model).toBe("anthropic/claude-opus-4-7") expect(result!.source).toBe("provider-fallback") }) }) diff --git a/src/shared/model-settings-compatibility.test.ts b/src/shared/model-settings-compatibility.test.ts index 6e7a7b590..9cf0ab172 100644 --- a/src/shared/model-settings-compatibility.test.ts +++ b/src/shared/model-settings-compatibility.test.ts @@ -6,7 +6,7 @@ describe("resolveCompatibleModelSettings", () => { test("keeps supported Claude Opus variant unchanged", () => { const result = resolveCompatibleModelSettings({ providerID: "anthropic", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", desired: { variant: "max" }, }) @@ -20,7 +20,7 @@ describe("resolveCompatibleModelSettings", () => { test("uses model metadata first for variant support", () => { const result = resolveCompatibleModelSettings({ providerID: "anthropic", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", desired: { variant: "max" }, capabilities: { variants: ["low", "medium", "high"] }, }) @@ -42,7 +42,7 @@ describe("resolveCompatibleModelSettings", () => { test("prefers metadata over family heuristics even when family would allow a higher level", () => { const result = resolveCompatibleModelSettings({ providerID: "anthropic", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", desired: { variant: "max" }, capabilities: { variants: ["low", "medium"] }, }) @@ -514,7 +514,7 @@ describe("resolveCompatibleModelSettings", () => { test("no-op when desired settings are empty", () => { const result = resolveCompatibleModelSettings({ providerID: "anthropic", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", desired: {}, }) diff --git a/src/tools/delegate-task/model-string-parser.ts b/src/shared/model-string-parser.ts similarity index 87% rename from src/tools/delegate-task/model-string-parser.ts rename to src/shared/model-string-parser.ts index 820bb3cc3..220bbd880 100644 --- a/src/tools/delegate-task/model-string-parser.ts +++ b/src/shared/model-string-parser.ts @@ -41,13 +41,13 @@ export function parseModelString( const trimmedModel = model.trim() if (!trimmedModel) return undefined - const parts = trimmedModel.split("/") - if (parts.length < 2) { + const separatorIndex = trimmedModel.indexOf("/") + if (separatorIndex === -1) { return undefined } - const providerID = parts[0]?.trim() - const rawModelID = parts.slice(1).join("/").trim() + const providerID = trimmedModel.slice(0, separatorIndex).trim() + const rawModelID = trimmedModel.slice(separatorIndex + 1).trim() if (!providerID || !rawModelID) { return undefined } diff --git a/src/shared/opencode-provider-auth.test.ts b/src/shared/opencode-provider-auth.test.ts new file mode 100644 index 000000000..56adcc9fe --- /dev/null +++ b/src/shared/opencode-provider-auth.test.ts @@ -0,0 +1,107 @@ +import { afterAll, afterEach, beforeAll, describe, expect, it } from "bun:test" +import { mkdirSync, rmSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import * as path from "node:path" + +import { + _resetProviderAuthCacheForTesting, + getProviderAuthType, + isProviderUsingOAuth, +} from "./opencode-provider-auth" + +describe("opencode-provider-auth", () => { + let tempDataDir: string + const originalXdgDataHome = process.env.XDG_DATA_HOME + + function writeAuthFile(contents: string): void { + const opencodeDir = path.join(tempDataDir, "opencode") + mkdirSync(opencodeDir, { recursive: true }) + writeFileSync(path.join(opencodeDir, "auth.json"), contents, "utf-8") + _resetProviderAuthCacheForTesting() + } + + beforeAll(() => { + tempDataDir = path.join(tmpdir(), `opencode-provider-auth-${Date.now()}-${Math.random().toString(36).slice(2)}`) + mkdirSync(tempDataDir, { recursive: true }) + process.env.XDG_DATA_HOME = tempDataDir + }) + + afterAll(() => { + if (originalXdgDataHome === undefined) { + delete process.env.XDG_DATA_HOME + } else { + process.env.XDG_DATA_HOME = originalXdgDataHome + } + rmSync(tempDataDir, { recursive: true, force: true }) + _resetProviderAuthCacheForTesting() + }) + + afterEach(() => { + _resetProviderAuthCacheForTesting() + }) + + it("#given auth.json with oauth entry #then detects OAuth for that provider", () => { + // given auth.json where anthropic is OAuth + writeAuthFile(JSON.stringify({ + anthropic: { type: "oauth", refresh: "r", access: "a", expires: 1 }, + opencode: { type: "api", key: "sk-x" }, + })) + + // when isProviderUsingOAuth queries each provider + const anthropicOauth = isProviderUsingOAuth("anthropic") + const opencodeOauth = isProviderUsingOAuth("opencode") + + // then only OAuth providers return true + expect(anthropicOauth).toBe(true) + expect(opencodeOauth).toBe(false) + }) + + it("#given api-key auth.json entry #then returns the api auth type", () => { + // given auth.json with an API key for anthropic + writeAuthFile(JSON.stringify({ anthropic: { type: "api", key: "sk-ant-xxx" } })) + + // when getProviderAuthType queries the provider + const authType = getProviderAuthType("anthropic") + + // then the api type is returned + expect(authType).toBe("api") + expect(isProviderUsingOAuth("anthropic")).toBe(false) + }) + + it("#given missing auth.json #then returns undefined with no throw", () => { + // given no auth.json exists (XDG_DATA_HOME points to an empty dir) + rmSync(path.join(tempDataDir, "opencode"), { recursive: true, force: true }) + _resetProviderAuthCacheForTesting() + + // when isProviderUsingOAuth queries a provider + const anthropicOauth = isProviderUsingOAuth("anthropic") + const anthropicType = getProviderAuthType("anthropic") + + // then callers get a safe undefined/false + expect(anthropicOauth).toBe(false) + expect(anthropicType).toBeUndefined() + }) + + it("#given malformed auth.json #then does not throw and returns undefined", () => { + // given a malformed JSON auth file + writeAuthFile("not json at all") + + // when isProviderUsingOAuth queries a provider + const anthropicOauth = isProviderUsingOAuth("anthropic") + + // then detection degrades safely + expect(anthropicOauth).toBe(false) + }) + + it("#given unknown provider #then returns undefined", () => { + // given auth.json without an entry for the queried provider + writeAuthFile(JSON.stringify({ anthropic: { type: "oauth", refresh: "r", access: "a", expires: 1 } })) + + // when querying a provider that is not present + const openai = getProviderAuthType("openai") + + // then undefined + expect(openai).toBeUndefined() + expect(isProviderUsingOAuth("openai")).toBe(false) + }) +}) diff --git a/src/shared/opencode-provider-auth.ts b/src/shared/opencode-provider-auth.ts new file mode 100644 index 000000000..235bea4c8 --- /dev/null +++ b/src/shared/opencode-provider-auth.ts @@ -0,0 +1,84 @@ +import { readFileSync, statSync } from "node:fs" +import * as path from "node:path" + +import { getDataDir } from "./data-path" +import { log } from "./logger" + +/** + * Reads OpenCode's auth.json to detect the auth type used by a provider. + * + * OpenCode stores auth credentials at `/opencode/auth.json` in the + * shape `{ [providerID]: { type: "oauth" | "api" | "wellknown", ... } }`. + * + * The file is read with mtime-based caching so we do not stat/parse it on + * every chat.params invocation. + */ + +type AuthRecord = { + type?: unknown +} + +type AuthCacheEntry = { + mtimeMs: number + map: Map +} + +let cached: AuthCacheEntry | null = null + +function getAuthFilePath(): string { + return path.join(getDataDir(), "opencode", "auth.json") +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null +} + +function loadAuthMap(): Map { + const filePath = getAuthFilePath() + + let mtimeMs: number + try { + mtimeMs = statSync(filePath).mtimeMs + } catch { + cached = null + return new Map() + } + + if (cached && cached.mtimeMs === mtimeMs) { + return cached.map + } + + try { + const raw = readFileSync(filePath, "utf-8") + const parsed: unknown = JSON.parse(raw) + const map = new Map() + if (isRecord(parsed)) { + for (const [providerID, entry] of Object.entries(parsed)) { + if (!isRecord(entry)) continue + const type = (entry as AuthRecord).type + if (typeof type === "string") { + map.set(providerID, type) + } + } + } + cached = { mtimeMs, map } + return map + } catch (error) { + log("[opencode-provider-auth] Failed to read auth.json", { + error: error instanceof Error ? error.message : String(error), + }) + return new Map() + } +} + +export function getProviderAuthType(providerID: string): string | undefined { + return loadAuthMap().get(providerID) +} + +export function isProviderUsingOAuth(providerID: string): boolean { + return getProviderAuthType(providerID) === "oauth" +} + +export function _resetProviderAuthCacheForTesting(): void { + cached = null +} diff --git a/src/shared/opencode-server-auth.ts b/src/shared/opencode-server-auth.ts index 8d4957512..02ebfa928 100644 --- a/src/shared/opencode-server-auth.ts +++ b/src/shared/opencode-server-auth.ts @@ -78,7 +78,7 @@ function tryInjectViaInterceptors(internal: UnknownRecord, auth: string): boolea return false } - use((request: Request): Request => { + use.call(requestInterceptors, (request: Request): Request => { if (!request.headers.get("Authorization")) { request.headers.set("Authorization", auth) } diff --git a/src/shared/parse-tools-config.ts b/src/shared/parse-tools-config.ts new file mode 100644 index 000000000..bc5f50169 --- /dev/null +++ b/src/shared/parse-tools-config.ts @@ -0,0 +1,25 @@ +/** + * Parses a tools configuration value into a boolean record. + * Accepts comma-separated strings, string arrays, or unknown values from config files. + * Returns undefined when input is empty or invalid. + */ +export function parseToolsConfig(toolsValue: unknown): Record | undefined { + if (!toolsValue) return undefined + + let items: string[] + if (typeof toolsValue === "string") { + items = toolsValue.split(",").map((t) => t.trim()).filter(Boolean) + } else if (Array.isArray(toolsValue)) { + items = toolsValue.filter((t) => typeof t === "string" && t.trim().length > 0).map((t) => (t as string).trim()) + } else { + return undefined + } + + if (items.length === 0) return undefined + + const result: Record = {} + for (const tool of items) { + result[tool.toLowerCase()] = true + } + return result +} diff --git a/src/shared/permission-compat.ts b/src/shared/permission-compat.ts index fd8253b77..d6df20aed 100644 --- a/src/shared/permission-compat.ts +++ b/src/shared/permission-compat.ts @@ -13,12 +13,14 @@ export interface PermissionFormat { * Creates tool restrictions that deny specified tools. */ export function createAgentToolRestrictions( - denyTools: string[] + denyTools: string[], + allowTools: string[] = [], ): PermissionFormat { return { - permission: Object.fromEntries( - denyTools.map((tool) => [tool, "deny" as const]) - ), + permission: Object.fromEntries([ + ...denyTools.map((tool) => [tool, "deny" as const]), + ...allowTools.map((tool) => [tool, "allow" as const]), + ]), } } diff --git a/src/shared/posthog.test.ts b/src/shared/posthog.test.ts new file mode 100644 index 000000000..c2f278add --- /dev/null +++ b/src/shared/posthog.test.ts @@ -0,0 +1,57 @@ +import { afterEach, describe, expect, it, mock } from "bun:test" + +async function importPostHogModule(): Promise { + return import(`./posthog?test=${Date.now()}-${Math.random()}`) +} + +describe("posthog client creation", () => { + afterEach(() => { + mock.restore() + delete process.env.OMO_DISABLE_POSTHOG + delete process.env.OMO_SEND_ANONYMOUS_TELEMETRY + delete process.env.POSTHOG_API_KEY + delete process.env.POSTHOG_HOST + }) + + it("returns a no-op client when PostHog construction throws", async () => { + // given + process.env.OMO_DISABLE_POSTHOG = "0" + process.env.OMO_SEND_ANONYMOUS_TELEMETRY = "1" + process.env.POSTHOG_API_KEY = "test-api-key" + + mock.module("posthog-node", () => ({ + PostHog: class { + constructor() { + throw new Error("posthog init failed") + } + }, + })) + + const { createCliPostHog, createPluginPostHog } = await importPostHogModule() + + // when + const cliPostHog = createCliPostHog() + const pluginPostHog = createPluginPostHog() + + // then + expect(() => + cliPostHog.capture({ + distinctId: "cli", + event: "run_started", + }), + ).not.toThrow() + expect(() => cliPostHog.captureException(new Error("cli failure"), "cli")).not.toThrow() + expect(() => cliPostHog.trackActive("cli", "run_started")).not.toThrow() + await expect(cliPostHog.shutdown()).resolves.toBeUndefined() + + expect(() => + pluginPostHog.capture({ + distinctId: "plugin", + event: "plugin_loaded", + }), + ).not.toThrow() + expect(() => pluginPostHog.captureException(new Error("plugin failure"), "plugin")).not.toThrow() + expect(() => pluginPostHog.trackActive("plugin", "plugin_loaded")).not.toThrow() + await expect(pluginPostHog.shutdown()).resolves.toBeUndefined() + }) +}) diff --git a/src/shared/posthog.ts b/src/shared/posthog.ts index 22981d8ca..6e96853d0 100644 --- a/src/shared/posthog.ts +++ b/src/shared/posthog.ts @@ -87,11 +87,17 @@ function createPostHogClient( return NO_OP_POSTHOG } - const configuredClient = new PostHog(getPostHogApiKey(), { - ...options, - host: getPostHogHost(), - disableGeoip: false, - }) + let configuredClient: PostHog + + try { + configuredClient = new PostHog(getPostHogApiKey(), { + ...options, + host: getPostHogHost(), + disableGeoip: false, + }) + } catch { + return NO_OP_POSTHOG + } const sharedProperties = getSharedProperties(source) return { @@ -149,7 +155,7 @@ export function getPostHogDistinctId(): string { export function createCliPostHog(): PostHogClient { return createPostHogClient("cli", { - enableExceptionAutocapture: true, + enableExceptionAutocapture: false, flushAt: 1, flushInterval: 0, }) @@ -157,7 +163,7 @@ export function createCliPostHog(): PostHogClient { export function createPluginPostHog(): PostHogClient { return createPostHogClient("plugin", { - enableExceptionAutocapture: true, + enableExceptionAutocapture: false, flushAt: 1, flushInterval: 0, }) diff --git a/src/shared/project-discovery-dirs.test.ts b/src/shared/project-discovery-dirs.test.ts index 39ba5dc13..d2904bc72 100644 --- a/src/shared/project-discovery-dirs.test.ts +++ b/src/shared/project-discovery-dirs.test.ts @@ -1,15 +1,10 @@ -import { afterEach, beforeEach, describe, expect, it } from "bun:test" +import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test" import { mkdirSync, realpathSync, rmSync } from "node:fs" import { tmpdir } from "node:os" import { join } from "node:path" -import { - findProjectAgentsSkillDirs, - findProjectClaudeSkillDirs, - findProjectOpencodeCommandDirs, - findProjectOpencodeSkillDirs, -} from "./project-discovery-dirs" const TEST_DIR = join(tmpdir(), `project-discovery-dirs-${Date.now()}`) +let worktreeSpawnCount = 0 function canonicalPath(path: string): string { return realpathSync(path) @@ -24,7 +19,35 @@ describe("project-discovery-dirs", () => { rmSync(TEST_DIR, { recursive: true, force: true }) }) - it("#given nested .opencode skill directories #when finding project opencode skill dirs #then returns nearest-first with aliases", () => { + it("#given repeated worktree detection #when detecting twice #then reuses the cached result", async () => { + // given + worktreeSpawnCount = 0 + + mock.module("node:child_process", () => ({ + execFileSync: () => { + worktreeSpawnCount += 1 + return TEST_DIR + }, + })) + + const { clearWorktreeCache, detectWorktreePath } = await import("./project-discovery-dirs") + + clearWorktreeCache() + + // when + const firstPath = detectWorktreePath("/some/dir") + const secondPath = detectWorktreePath("/some/dir") + clearWorktreeCache() + const thirdPath = detectWorktreePath("/some/dir") + + // then + expect(firstPath).toBe(TEST_DIR) + expect(secondPath).toBe(TEST_DIR) + expect(thirdPath).toBe(TEST_DIR) + expect(worktreeSpawnCount).toBe(2) + }) + + it("#given nested .opencode skill directories #when finding project opencode skill dirs #then returns nearest-first with aliases", async () => { // given const projectDir = join(TEST_DIR, "project") const childDir = join(projectDir, "apps", "cli") @@ -32,6 +55,8 @@ describe("project-discovery-dirs", () => { mkdirSync(join(projectDir, ".opencode", "skills"), { recursive: true }) mkdirSync(join(TEST_DIR, ".opencode", "skills"), { recursive: true }) + const { findProjectOpencodeSkillDirs } = await import("./project-discovery-dirs") + // when const directories = findProjectOpencodeSkillDirs(childDir) @@ -43,13 +68,15 @@ describe("project-discovery-dirs", () => { ]) }) - it("#given nested .opencode command directories #when finding project opencode command dirs #then returns nearest-first with aliases", () => { + it("#given nested .opencode command directories #when finding project opencode command dirs #then returns nearest-first with aliases", async () => { // given const projectDir = join(TEST_DIR, "project") const childDir = join(projectDir, "packages", "tool") mkdirSync(join(projectDir, ".opencode", "commands"), { recursive: true }) mkdirSync(join(TEST_DIR, ".opencode", "command"), { recursive: true }) + const { findProjectOpencodeCommandDirs } = await import("./project-discovery-dirs") + // when const directories = findProjectOpencodeCommandDirs(childDir) @@ -60,13 +87,15 @@ describe("project-discovery-dirs", () => { ]) }) - it("#given ancestor claude and agents skill directories #when finding project compatibility dirs #then discovers both scopes", () => { + it("#given ancestor claude and agents skill directories #when finding project compatibility dirs #then discovers both scopes", async () => { // given const projectDir = join(TEST_DIR, "project") const childDir = join(projectDir, "src", "nested") mkdirSync(join(projectDir, ".claude", "skills"), { recursive: true }) mkdirSync(join(TEST_DIR, ".agents", "skills"), { recursive: true }) + const { findProjectAgentsSkillDirs, findProjectClaudeSkillDirs } = await import("./project-discovery-dirs") + // when const claudeDirectories = findProjectClaudeSkillDirs(childDir) const agentsDirectories = findProjectAgentsSkillDirs(childDir) @@ -76,17 +105,20 @@ describe("project-discovery-dirs", () => { expect(agentsDirectories).toEqual([canonicalPath(join(TEST_DIR, ".agents", "skills"))]) }) - it("#given a stop directory #when finding ancestor dirs #then it does not scan beyond the stop boundary", () => { + it("#given a stop directory #when finding ancestor dirs #then it does not scan beyond the stop boundary", async () => { // given const projectDir = join(TEST_DIR, "project") const childDir = join(projectDir, "apps", "cli") mkdirSync(join(projectDir, ".opencode", "skills"), { recursive: true }) mkdirSync(join(TEST_DIR, ".opencode", "skills"), { recursive: true }) + const { findProjectOpencodeSkillDirs } = await import("./project-discovery-dirs") + // when const directories = findProjectOpencodeSkillDirs(childDir, projectDir) // then expect(directories).toEqual([canonicalPath(join(projectDir, ".opencode", "skills"))]) }) + }) diff --git a/src/shared/project-discovery-dirs.ts b/src/shared/project-discovery-dirs.ts index 4e22b66f6..5e243df5a 100644 --- a/src/shared/project-discovery-dirs.ts +++ b/src/shared/project-discovery-dirs.ts @@ -2,6 +2,8 @@ import { execFileSync } from "node:child_process" import { existsSync, realpathSync } from "node:fs" import { dirname, join, resolve } from "node:path" +const worktreePathCache = new Map() + function normalizePath(path: string): string { const resolvedPath = resolve(path) if (!existsSync(resolvedPath)) { @@ -49,15 +51,28 @@ function findAncestorDirectories( } } -function detectWorktreePath(directory: string): string | undefined { +export function clearWorktreeCache(): void { + worktreePathCache.clear() +} + +export function detectWorktreePath(directory: string): string | undefined { + const resolvedDirectory = resolve(directory) + if (worktreePathCache.has(resolvedDirectory)) { + return worktreePathCache.get(resolvedDirectory) + } + try { - return execFileSync("git", ["rev-parse", "--show-toplevel"], { - cwd: directory, + const worktreePath = execFileSync("git", ["rev-parse", "--show-toplevel"], { + cwd: resolvedDirectory, encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"], }).trim() + + worktreePathCache.set(resolvedDirectory, worktreePath) + return worktreePath } catch { + worktreePathCache.set(resolvedDirectory, undefined) return undefined } } diff --git a/src/shared/provider-model-id-transform.ts b/src/shared/provider-model-id-transform.ts index 38fdf869d..59904199d 100644 --- a/src/shared/provider-model-id-transform.ts +++ b/src/shared/provider-model-id-transform.ts @@ -1,24 +1,58 @@ +function inferSubProvider(model: string): string | undefined { + if (model.startsWith("claude-")) return "anthropic" + if (model.startsWith("gpt-")) return "openai" + if (model.startsWith("gemini-")) return "google" + if (model.startsWith("grok-")) return "xai" + if (model.startsWith("minimax-")) return "minimax" + if (model.startsWith("kimi-")) return "moonshotai" + if (model.startsWith("glm-")) return "zai" + return undefined +} + +const CLAUDE_VERSION_DOT = /claude-(\w+)-(\d+)-(\d+)/g +const GEMINI_31_PRO_PREVIEW = /gemini-3\.1-pro(?!-)/g +const GEMINI_3_FLASH_PREVIEW = /gemini-3-flash(?!-)/g + +function claudeVersionDot(model: string): string { + return model.replace(CLAUDE_VERSION_DOT, "claude-$1-$2.$3") +} + +function applyGatewayTransforms(model: string): string { + return claudeVersionDot(model) + .replace(GEMINI_31_PRO_PREVIEW, "gemini-3.1-pro-preview") +} + export function transformModelForProvider(provider: string, model: string): string { - if (provider === "github-copilot") { + // Vercel AI Gateway expects / (e.g. anthropic/claude-opus-4.7). + // Canonical names in model-requirements.ts may be bare (claude-opus-4-7) or + // already prefixed (anthropic/claude-opus-4-7). Both need gateway-specific transforms. + if (provider === "vercel") { + // Already prefixed — transform only the model part + const slashIndex = model.indexOf("/") + if (slashIndex !== -1) { + const subProvider = model.substring(0, slashIndex) + const subModel = model.substring(slashIndex + 1) + return `${subProvider}/${applyGatewayTransforms(subModel)}` + } + // Bare name — infer sub-provider from model prefix (claude- → anthropic, etc.) + const subProvider = inferSubProvider(model) + if (subProvider) { + return `${subProvider}/${applyGatewayTransforms(model)}` + } return model - .replace("claude-opus-4-6", "claude-opus-4.6") - .replace("claude-sonnet-4-6", "claude-sonnet-4.6") - .replace("claude-sonnet-4-5", "claude-sonnet-4.5") - .replace("claude-haiku-4-5", "claude-haiku-4.5") - .replace("claude-sonnet-4", "claude-sonnet-4") - .replace(/gemini-3\.1-pro(?!-)/g, "gemini-3.1-pro-preview") - .replace(/gemini-3-flash(?!-)/g, "gemini-3-flash-preview") + } + if (provider === "github-copilot") { + return claudeVersionDot(model) + .replace(GEMINI_31_PRO_PREVIEW, "gemini-3.1-pro-preview") + .replace(GEMINI_3_FLASH_PREVIEW, "gemini-3-flash-preview") } if (provider === "google") { return model - .replace(/gemini-3\.1-pro(?!-)/g, "gemini-3.1-pro-preview") - .replace(/gemini-3-flash(?!-)/g, "gemini-3-flash-preview") + .replace(GEMINI_31_PRO_PREVIEW, "gemini-3.1-pro-preview") + .replace(GEMINI_3_FLASH_PREVIEW, "gemini-3-flash-preview") } if (provider === "anthropic") { - return model - .replace("claude-opus-4-6", "claude-opus-4.6") - .replace("claude-sonnet-4-6", "claude-sonnet-4.6") - .replace("claude-haiku-4-5", "claude-haiku-4.5") + return claudeVersionDot(model) } return model } diff --git a/src/shared/resolve-agent-definition-paths.test.ts b/src/shared/resolve-agent-definition-paths.test.ts new file mode 100644 index 000000000..fcc14be7a --- /dev/null +++ b/src/shared/resolve-agent-definition-paths.test.ts @@ -0,0 +1,122 @@ +import { describe, test, expect, beforeEach, afterEach } from "bun:test" +import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "fs" +import { join } from "path" +import { homedir } from "os" +import { tmpdir } from "os" + +import { resolveAgentDefinitionPaths } from "./resolve-agent-definition-paths" + +describe("resolveAgentDefinitionPaths", () => { + let tempDir: string + + beforeEach(() => { + tempDir = mkdtempSync(join(tmpdir(), "resolve-agent-def-paths-")) + }) + + afterEach(() => { + rmSync(tempDir, { recursive: true, force: true }) + }) + + describe("#given relative paths", () => { + test("#then they are resolved against baseDir", () => { + const result = resolveAgentDefinitionPaths( + ["agents/my-agent.md"], + tempDir, + null, + ) + + expect(result).toEqual([join(tempDir, "agents/my-agent.md")]) + }) + }) + + describe("#given absolute paths", () => { + test("#then they are returned as-is", () => { + const absPath = join(tempDir, "absolute-agent.md") + + const result = resolveAgentDefinitionPaths( + [absPath], + "/some/other/base", + null, + ) + + expect(result).toEqual([absPath]) + }) + }) + + describe("#given tilde-prefixed paths", () => { + test("#then ~ is expanded to homedir", () => { + const result = resolveAgentDefinitionPaths( + ["~/agents/test.md"], + tempDir, + null, + ) + + expect(result).toEqual([join(homedir(), "agents/test.md")]) + }) + }) + + describe("#given containmentDir is set", () => { + test("#then paths outside the boundary are rejected", () => { + const projectDir = join(tempDir, "project") + mkdirSync(projectDir, { recursive: true }) + + const result = resolveAgentDefinitionPaths( + ["/etc/passwd"], + projectDir, + projectDir, + ) + + expect(result).toEqual([]) + }) + + test("#then paths inside the boundary are allowed", () => { + const projectDir = join(tempDir, "project") + const agentsDir = join(projectDir, "agents") + mkdirSync(agentsDir, { recursive: true }) + writeFileSync(join(agentsDir, "a.md"), "test", "utf-8") + + const result = resolveAgentDefinitionPaths( + ["agents/a.md"], + projectDir, + projectDir, + ) + + expect(result).toEqual([join(projectDir, "agents/a.md")]) + }) + }) + + describe("#given containmentDir is null", () => { + test("#then no boundary check is applied", () => { + const result = resolveAgentDefinitionPaths( + ["/some/outside/path/agent.md"], + tempDir, + null, + ) + + expect(result).toEqual(["/some/outside/path/agent.md"]) + }) + }) + + describe("#given an empty paths array", () => { + test("#then an empty array is returned", () => { + const result = resolveAgentDefinitionPaths([], tempDir, null) + + expect(result).toEqual([]) + }) + }) + + describe("#given mixed valid and invalid paths", () => { + test("#then only valid paths within the boundary are returned", () => { + const projectDir = join(tempDir, "project") + mkdirSync(projectDir, { recursive: true }) + + const result = resolveAgentDefinitionPaths( + ["./valid.md", "/outside/boundary.md"], + projectDir, + projectDir, + ) + + expect(result).toEqual([join(projectDir, "valid.md")]) + }) + }) +}) diff --git a/src/shared/resolve-agent-definition-paths.ts b/src/shared/resolve-agent-definition-paths.ts new file mode 100644 index 000000000..c7334e4b7 --- /dev/null +++ b/src/shared/resolve-agent-definition-paths.ts @@ -0,0 +1,22 @@ +import { homedir } from "os" +import { isAbsolute, resolve } from "path" +import { isWithinProject } from "./contains-path" +import { log } from "./logger" + +export function resolveAgentDefinitionPaths( + paths: string[], + baseDir: string, + containmentDir: string | null +): string[] { + return paths.flatMap((p) => { + const expanded = p.startsWith("~/") ? p.replace(/^~\//, `${homedir()}/`) : p + const resolved = isAbsolute(expanded) ? expanded : resolve(baseDir, expanded) + + if (containmentDir !== null && !isWithinProject(resolved, containmentDir)) { + log(`agent_definitions path rejected (outside project boundary): ${p} -> ${resolved}`) + return [] + } + + return [resolved] + }) +} diff --git a/src/shared/ripgrep-cli.ts b/src/shared/ripgrep-cli.ts new file mode 100644 index 000000000..5f62b3ad7 --- /dev/null +++ b/src/shared/ripgrep-cli.ts @@ -0,0 +1,114 @@ +import { spawnSync } from "node:child_process" +import { existsSync } from "node:fs" +import { dirname, join } from "node:path" +import { downloadAndInstallRipgrep, getInstalledRipgrepPath } from "../tools/grep/downloader" +import { getDataDir } from "./data-path" +import { log } from "./logger" +import { PUBLISHED_PACKAGE_NAME } from "./plugin-identity" + +export type GrepBackend = "rg" | "grep" + +export interface ResolvedCli { + path: string + backend: GrepBackend +} + +export const DEFAULT_RG_THREADS = 4 + +let cachedCli: ResolvedCli | null = null +let autoInstallAttempted = false + +function findExecutable(name: string): string | null { + const isWindows = process.platform === "win32" + const cmd = isWindows ? "where" : "which" + + try { + const result = spawnSync(cmd, [name], { encoding: "utf-8", timeout: 5000 }) + if (result.status === 0 && result.stdout.trim()) { + return result.stdout.trim().split("\n")[0] + } + } catch { + return null + } + return null +} + +function getOpenCodeBundledRg(): string | null { + const execPath = process.execPath + const execDir = dirname(execPath) + + const isWindows = process.platform === "win32" + const rgName = isWindows ? "rg.exe" : "rg" + + const candidates = [ + join(getDataDir(), "opencode", "bin", rgName), + join(execDir, rgName), + join(execDir, "bin", rgName), + join(execDir, "..", "bin", rgName), + join(execDir, "..", "libexec", rgName), + ] + + for (const candidate of candidates) { + if (existsSync(candidate)) { + return candidate + } + } + + return null +} + +export function resolveGrepCli(): ResolvedCli { + if (cachedCli) { + return cachedCli + } + + const rgPath = getOpenCodeBundledRg() ?? findExecutable("rg") ?? getInstalledRipgrepPath() + if (rgPath) { + cachedCli = { path: rgPath, backend: "rg" } + return cachedCli + } + + const grep = findExecutable("grep") + if (grep) { + cachedCli = { path: grep, backend: "grep" } + return cachedCli + } + + cachedCli = { path: "rg", backend: "rg" } + return cachedCli +} + +export async function resolveGrepCliWithAutoInstall(): Promise { + const current = resolveGrepCli() + + if (current.backend === "rg" && current.path !== "rg") { + return current + } + + if (autoInstallAttempted) { + return current + } + + autoInstallAttempted = true + + try { + const rgPath = await downloadAndInstallRipgrep() + cachedCli = { path: rgPath, backend: "rg" } + return cachedCli + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + + if (current.backend === "grep") { + log(`[${PUBLISHED_PACKAGE_NAME}] Failed to auto-install ripgrep. Falling back to GNU grep.`, { + error: message, + grep_path: current.path, + }) + } else { + log(`[${PUBLISHED_PACKAGE_NAME}] Failed to auto-install ripgrep and GNU grep was not found.`, { + error: message, + }) + } + + return current + } +} diff --git a/src/shared/shell-env.test.ts b/src/shared/shell-env.test.ts index 60a4aaba4..ef9aa52ea 100644 --- a/src/shared/shell-env.test.ts +++ b/src/shared/shell-env.test.ts @@ -10,6 +10,7 @@ describe("shell-env", () => { originalEnv = { SHELL: process.env.SHELL, PSModulePath: process.env.PSModulePath, + MSYSTEM: process.env.MSYSTEM, } }) @@ -47,6 +48,7 @@ describe("shell-env", () => { test("#given PSModulePath is set without SHELL #when detectShellType is called #then returns powershell", () => { delete process.env.SHELL + delete process.env.MSYSTEM process.env.PSModulePath = "C:\\Program Files\\PowerShell\\Modules" Object.defineProperty(process, "platform", { value: "win32" }) @@ -58,6 +60,7 @@ describe("shell-env", () => { test("#given Windows platform without PSModulePath #when detectShellType is called #then returns cmd", () => { delete process.env.PSModulePath delete process.env.SHELL + delete process.env.MSYSTEM Object.defineProperty(process, "platform", { value: "win32" }) const result = detectShellType() @@ -68,6 +71,7 @@ describe("shell-env", () => { test("#given non-Windows platform without SHELL env var #when detectShellType is called #then returns unix", () => { delete process.env.PSModulePath delete process.env.SHELL + delete process.env.MSYSTEM Object.defineProperty(process, "platform", { value: "linux" }) const result = detectShellType() @@ -94,6 +98,28 @@ describe("shell-env", () => { expect(result).toBe("unix") }) + + test("#given MSYSTEM set on Windows without SHELL #when detectShellType is called #then returns unix", () => { + delete process.env.SHELL + process.env.MSYSTEM = "MINGW64" + process.env.PSModulePath = "C:\\Program Files\\PowerShell\\Modules" + Object.defineProperty(process, "platform", { value: "win32" }) + + const result = detectShellType() + + expect(result).toBe("unix") + }) + + test("#given MSYSTEM set to MSYS without SHELL #when detectShellType is called #then returns unix", () => { + delete process.env.SHELL + process.env.MSYSTEM = "MSYS" + process.env.PSModulePath = "C:\\Program Files\\PowerShell\\Modules" + Object.defineProperty(process, "platform", { value: "win32" }) + + const result = detectShellType() + + expect(result).toBe("unix") + }) }) describe("shellEscape", () => { diff --git a/src/shared/shell-env.ts b/src/shared/shell-env.ts index 6d5a2190b..808873bbc 100644 --- a/src/shared/shell-env.ts +++ b/src/shared/shell-env.ts @@ -29,8 +29,8 @@ export function detectShellType(): ShellType { if ( process.platform === "win32" && (process.env.BASH_VERSION || - process.env.MSYSTEM || - process.env.WSL_DISTRO_NAME) + process.env.MSYSTEM || + process.env.WSL_DISTRO_NAME) ) { return "unix" } diff --git a/src/shared/tmux/tmux-utils.ts b/src/shared/tmux/tmux-utils.ts index a9aab095a..6ccdeed31 100644 --- a/src/shared/tmux/tmux-utils.ts +++ b/src/shared/tmux/tmux-utils.ts @@ -10,6 +10,8 @@ export { spawnTmuxPane } from "./tmux-utils/pane-spawn" export { closeTmuxPane } from "./tmux-utils/pane-close" export { replaceTmuxPane } from "./tmux-utils/pane-replace" export { spawnTmuxWindow } from "./tmux-utils/window-spawn" -export { spawnTmuxSession } from "./tmux-utils/session-spawn" +export { spawnTmuxSession, getIsolatedSessionName } from "./tmux-utils/session-spawn" +export { killTmuxSessionIfExists } from "./tmux-utils/session-kill" +export { sweepStaleOmoAgentSessions } from "./tmux-utils/stale-session-sweep" export { applyLayout, enforceMainPaneWidth } from "./tmux-utils/layout" diff --git a/src/shared/tmux/tmux-utils/index.ts b/src/shared/tmux/tmux-utils/index.ts new file mode 100644 index 000000000..e55436a1c --- /dev/null +++ b/src/shared/tmux/tmux-utils/index.ts @@ -0,0 +1 @@ +export { killTmuxSessionIfExists } from "./session-kill" diff --git a/src/shared/tmux/tmux-utils/pane-close.test.ts b/src/shared/tmux/tmux-utils/pane-close.test.ts new file mode 100644 index 000000000..b8d5d7887 --- /dev/null +++ b/src/shared/tmux/tmux-utils/pane-close.test.ts @@ -0,0 +1,221 @@ +import { beforeEach, describe, expect, it, mock } from "bun:test" + +type CloseTmuxPane = typeof import("./pane-close").closeTmuxPane + +type SpawnCall = { + command: string[] + options: { + stdout?: string + stderr?: string + } +} + +type FakeSubprocess = { + exited: Promise + stdout: ReadableStream + stderr: ReadableStream +} + +const TIMEOUT = Symbol("timeout") +const spawnCalls: SpawnCall[] = [] +const queuedProcesses: FakeSubprocess[] = [] + +function createClosedStream(): ReadableStream { + return new ReadableStream({ + start(controller) { + controller.close() + }, + }) +} + +type DrainSignal = { onPull: () => void } + +function createDrainSensitiveStream(byteLength: number, signal: DrainSignal): ReadableStream { + let remainingBytes = byteLength + const chunk = new TextEncoder().encode("x".repeat(16 * 1024)) + + return new ReadableStream({ + pull(controller) { + signal.onPull() + + if (remainingBytes <= 0) { + controller.close() + return + } + + const nextChunkSize = Math.min(remainingBytes, chunk.byteLength) + controller.enqueue(chunk.subarray(0, nextChunkSize)) + remainingBytes -= nextChunkSize + }, + }) +} + +function createProcess(exitCode: number): FakeSubprocess { + return { + exited: Promise.resolve(exitCode), + stdout: createClosedStream(), + stderr: createClosedStream(), + } +} + +function createStdoutSensitiveProcess(exitCode: number, stdoutBytes: number): FakeSubprocess { + let resolveDrained: () => void = () => undefined + const drained = new Promise((resolve) => { + resolveDrained = resolve + }) + const stdout = createDrainSensitiveStream(stdoutBytes, { onPull: () => resolveDrained() }) + + return { + exited: drained.then(() => exitCode), + stdout, + stderr: createClosedStream(), + } +} + +const spawnMock = mock((command: string[], options: { stdout?: string; stderr?: string } = {}): FakeSubprocess => { + spawnCalls.push({ command, options }) + + const process = queuedProcesses.shift() + if (!process) { + throw new Error(`No fake subprocess configured for ${command.join(" ")}`) + } + + return process +}) + +const isInsideTmuxMock = mock((): boolean => true) +const getTmuxPathMock = mock(async (): Promise => "tmux") +const logMock = mock(() => undefined) + +const paneCloseSpecifier = import.meta.resolve("./pane-close") +const environmentSpecifier = import.meta.resolve("./environment") +const loggerSpecifier = import.meta.resolve("../../logger") +const spawnProcessSpecifier = import.meta.resolve("./spawn-process") +const tmuxPathResolverSpecifier = import.meta.resolve("../../../tools/interactive-bash/tmux-path-resolver") + +async function loadCloseTmuxPane(): Promise { + const module = await import(`${paneCloseSpecifier}?test=${crypto.randomUUID()}`) + return module.closeTmuxPane +} + +function registerModuleMocks(): void { + mock.module(spawnProcessSpecifier, () => ({ spawn: spawnMock })) + mock.module(environmentSpecifier, () => ({ isInsideTmux: isInsideTmuxMock })) + mock.module(tmuxPathResolverSpecifier, () => ({ getTmuxPath: getTmuxPathMock })) + mock.module(loggerSpecifier, () => ({ log: logMock })) +} + +function resolveWithin(promise: Promise, milliseconds: number): Promise { + return Promise.race([ + promise, + new Promise((resolve) => { + setTimeout(() => resolve(TIMEOUT), milliseconds) + }), + ]) +} + +describe("closeTmuxPane", () => { + beforeEach(() => { + registerModuleMocks() + spawnCalls.length = 0 + queuedProcesses.length = 0 + spawnMock.mockClear() + isInsideTmuxMock.mockClear() + getTmuxPathMock.mockClear() + logMock.mockClear() + + isInsideTmuxMock.mockImplementation((): boolean => true) + getTmuxPathMock.mockImplementation(async (): Promise => "tmux") + }) + + it("#given pane exists #when closeTmuxPane called #then returns true and invokes send-keys + kill-pane in order", async () => { + // given + const closeTmuxPane = await loadCloseTmuxPane() + queuedProcesses.push(createProcess(0), createProcess(0)) + + // when + const result = await closeTmuxPane("%42") + + // then + expect(result).toBe(true) + expect(spawnCalls).toEqual([ + { command: ["tmux", "send-keys", "-t", "%42", "C-c"], options: { stdout: "ignore", stderr: "ignore" } }, + { command: ["tmux", "kill-pane", "-t", "%42"], options: { stdout: "pipe", stderr: "pipe" } }, + ]) + }) + + it("#given not inside tmux #when closeTmuxPane called #then returns false without spawn", async () => { + // given + const closeTmuxPane = await loadCloseTmuxPane() + isInsideTmuxMock.mockImplementation((): boolean => false) + + // when + const result = await closeTmuxPane("%42") + + // then + expect(result).toBe(false) + expect(spawnCalls).toHaveLength(0) + }) + + it("#given tmux not found #when closeTmuxPane called #then returns false without spawn", async () => { + // given + const closeTmuxPane = await loadCloseTmuxPane() + getTmuxPathMock.mockImplementation(async (): Promise => undefined) + + // when + const result = await closeTmuxPane("%42") + + // then + expect(result).toBe(false) + expect(spawnCalls).toHaveLength(0) + }) + + it("#given kill-pane fails with unknown error #when closeTmuxPane called #then returns false", async () => { + // given + const closeTmuxPane = await loadCloseTmuxPane() + queuedProcesses.push(createProcess(0), createProcess(1)) + + // when + const result = await closeTmuxPane("%42") + + // then + expect(result).toBe(false) + }) + + it("#given pane already closed by Ctrl+C (kill-pane reports 'can't find pane') #when closeTmuxPane called #then returns true", async () => { + // given + const closeTmuxPane = await loadCloseTmuxPane() + queuedProcesses.push( + createProcess(0), + { + exited: Promise.resolve(1), + stdout: createClosedStream(), + stderr: new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("can't find pane: %42\n")) + controller.close() + }, + }), + }, + ) + + // when + const result = await closeTmuxPane("%42") + + // then + expect(result).toBe(true) + }) + + it("#given kill-pane stdout stream waits for drain #when closeTmuxPane called #then returns true once drainer consumes stdout", async () => { + // given + const closeTmuxPane = await loadCloseTmuxPane() + queuedProcesses.push(createProcess(0), createStdoutSensitiveProcess(0, 16 * 1024)) + + // when + const result = await resolveWithin(closeTmuxPane("%42"), 2000) + + // then + expect(result).not.toBe(TIMEOUT) + expect(result).toBe(true) + }) +}) diff --git a/src/shared/tmux/tmux-utils/pane-close.ts b/src/shared/tmux/tmux-utils/pane-close.ts index cc6f4b6c4..e62e46296 100644 --- a/src/shared/tmux/tmux-utils/pane-close.ts +++ b/src/shared/tmux/tmux-utils/pane-close.ts @@ -1,13 +1,18 @@ -import { spawn } from "bun" -import { getTmuxPath } from "../../../tools/interactive-bash/tmux-path-resolver" -import { isInsideTmux } from "./environment" - function delay(milliseconds: number): Promise { return new Promise((resolve) => setTimeout(resolve, milliseconds)) } +async function readStream(stream: ReadableStream | null | undefined): Promise { + return stream ? new Response(stream).text() : "" +} + export async function closeTmuxPane(paneId: string): Promise { - const { log } = await import("../../logger") + const [{ log }, { isInsideTmux }, { getTmuxPath }, { spawn }] = await Promise.all([ + import("../../logger"), + import("./environment"), + import("../../../tools/interactive-bash/tmux-path-resolver"), + import("./spawn-process"), + ]) if (!isInsideTmux()) { log("[closeTmuxPane] SKIP: not inside tmux") @@ -22,8 +27,8 @@ export async function closeTmuxPane(paneId: string): Promise { log("[closeTmuxPane] sending Ctrl+C for graceful shutdown", { paneId }) const ctrlCProc = spawn([tmux, "send-keys", "-t", paneId, "C-c"], { - stdout: "pipe", - stderr: "pipe", + stdout: "ignore", + stderr: "ignore", }) await ctrlCProc.exited @@ -31,18 +36,29 @@ export async function closeTmuxPane(paneId: string): Promise { log("[closeTmuxPane] killing pane", { paneId }) - const proc = spawn([tmux, "kill-pane", "-t", paneId], { + const killPaneProc = spawn([tmux, "kill-pane", "-t", paneId], { stdout: "pipe", stderr: "pipe", }) - const exitCode = await proc.exited - const stderr = await new Response(proc.stderr).text() + const [, stderr, exitCode] = await Promise.all([ + readStream(killPaneProc.stdout), + readStream(killPaneProc.stderr), + killPaneProc.exited, + ]) - if (exitCode !== 0) { - log("[closeTmuxPane] FAILED", { paneId, exitCode, stderr: stderr.trim() }) - } else { - log("[closeTmuxPane] SUCCESS", { paneId }) + const trimmedStderr = stderr.trim() + const paneAlreadyGone = exitCode !== 0 && /can't find pane/i.test(trimmedStderr) + + if (paneAlreadyGone) { + log("[closeTmuxPane] SUCCESS (pane already closed by Ctrl+C)", { paneId }) + return true } - return exitCode === 0 + if (exitCode !== 0) { + log("[closeTmuxPane] FAILED", { paneId, exitCode, stderr: trimmedStderr }) + return false + } + + log("[closeTmuxPane] SUCCESS", { paneId }) + return true } diff --git a/src/shared/tmux/tmux-utils/session-kill.test.ts b/src/shared/tmux/tmux-utils/session-kill.test.ts new file mode 100644 index 000000000..ca185d980 --- /dev/null +++ b/src/shared/tmux/tmux-utils/session-kill.test.ts @@ -0,0 +1,173 @@ +import { beforeEach, describe, expect, it, mock } from "bun:test" + +type KillTmuxSessionIfExists = typeof import("./session-kill").killTmuxSessionIfExists + +type SpawnCall = { + command: string[] + options: { + stdout?: string + stderr?: string + } +} + +type FakeSubprocess = { + exited: Promise + stdout: ReadableStream + stderr: ReadableStream +} + +const spawnCalls: SpawnCall[] = [] +const queuedProcesses: FakeSubprocess[] = [] + +function createStream(chunks: string[] = []): ReadableStream { + const textEncoder = new TextEncoder() + + return new ReadableStream({ + start(controller) { + for (const chunk of chunks) { + controller.enqueue(textEncoder.encode(chunk)) + } + + controller.close() + }, + }) +} + +function createProcess(exitCode: number, output: { stdout?: string[]; stderr?: string[] } = {}): FakeSubprocess { + return { + exited: Promise.resolve(exitCode), + stdout: createStream(output.stdout), + stderr: createStream(output.stderr), + } +} + +const spawnMock = mock((command: string[], options: { stdout?: string; stderr?: string } = {}) => { + spawnCalls.push({ command, options }) + + const process = queuedProcesses.shift() + if (!process) { + throw new Error(`No fake subprocess configured for ${command.join(" ")}`) + } + + return process +}) + +const isInsideTmuxMock = mock((): boolean => true) +const getTmuxPathMock = mock(async (): Promise => "tmux") +const logMock = mock(() => undefined) + +const sessionKillSpecifier = import.meta.resolve("./session-kill") +const environmentSpecifier = import.meta.resolve("./environment") +const loggerSpecifier = import.meta.resolve("../../logger") +const spawnProcessSpecifier = import.meta.resolve("./spawn-process") +const tmuxPathResolverSpecifier = import.meta.resolve("../../../tools/interactive-bash/tmux-path-resolver") + +async function loadKillTmuxSessionIfExists(): Promise { + const module = await import(`${sessionKillSpecifier}?test=${crypto.randomUUID()}`) + return module.killTmuxSessionIfExists +} + +function registerModuleMocks(): void { + mock.module(spawnProcessSpecifier, () => ({ spawn: spawnMock })) + mock.module(environmentSpecifier, () => ({ isInsideTmux: isInsideTmuxMock })) + mock.module(tmuxPathResolverSpecifier, () => ({ getTmuxPath: getTmuxPathMock })) + mock.module(loggerSpecifier, () => ({ log: logMock })) +} + +describe("killTmuxSessionIfExists", () => { + beforeEach(() => { + registerModuleMocks() + spawnCalls.length = 0 + queuedProcesses.length = 0 + spawnMock.mockClear() + isInsideTmuxMock.mockClear() + getTmuxPathMock.mockClear() + logMock.mockClear() + + isInsideTmuxMock.mockImplementation((): boolean => true) + getTmuxPathMock.mockImplementation(async (): Promise => "tmux") + }) + + it("#given omo-agents session exists #when killTmuxSessionIfExists called #then kill-session invoked and returns true", async () => { + // given + const killTmuxSessionIfExists = await loadKillTmuxSessionIfExists() + queuedProcesses.push(createProcess(0), createProcess(0, { stdout: ["killed"], stderr: [] })) + + // when + const result = await killTmuxSessionIfExists("omo-agents") + + // then + expect(result).toBe(true) + expect(spawnCalls).toEqual([ + { + command: ["tmux", "has-session", "-t", "omo-agents"], + options: { stdout: "ignore", stderr: "ignore" }, + }, + { + command: ["tmux", "kill-session", "-t", "omo-agents"], + options: { stdout: "pipe", stderr: "pipe" }, + }, + ]) + }) + + it("#given omo-agents session does NOT exist (has-session exits non-zero) #when killTmuxSessionIfExists called #then NO kill-session invocation and returns false", async () => { + // given + const killTmuxSessionIfExists = await loadKillTmuxSessionIfExists() + queuedProcesses.push(createProcess(1)) + + // when + const result = await killTmuxSessionIfExists("omo-agents") + + // then + expect(result).toBe(false) + expect(spawnCalls).toEqual([ + { + command: ["tmux", "has-session", "-t", "omo-agents"], + options: { stdout: "ignore", stderr: "ignore" }, + }, + ]) + }) + + it("#given not inside tmux #when killTmuxSessionIfExists called #then returns false without any spawn", async () => { + // given + const killTmuxSessionIfExists = await loadKillTmuxSessionIfExists() + isInsideTmuxMock.mockReturnValue(false) + + // when + const result = await killTmuxSessionIfExists("omo-agents") + + // then + expect(result).toBe(false) + expect(spawnCalls).toHaveLength(0) + expect(getTmuxPathMock).toHaveBeenCalledTimes(0) + }) + + it("#given tmux not found #when killTmuxSessionIfExists called #then returns false without spawn", async () => { + // given + const killTmuxSessionIfExists = await loadKillTmuxSessionIfExists() + getTmuxPathMock.mockResolvedValue(undefined) + + // when + const result = await killTmuxSessionIfExists("omo-agents") + + // then + expect(result).toBe(false) + expect(spawnCalls).toHaveLength(0) + }) + + it("#given kill-session itself fails (e.g., race between has-session and kill) #when killTmuxSessionIfExists called #then returns false but does not throw", async () => { + // given + const killTmuxSessionIfExists = await loadKillTmuxSessionIfExists() + queuedProcesses.push( + createProcess(0), + createProcess(1, { stdout: [], stderr: ["no session"] }), + ) + + // when + const result = await killTmuxSessionIfExists("omo-agents") + + // then + expect(result).toBe(false) + expect(spawnCalls).toHaveLength(2) + }) +}) diff --git a/src/shared/tmux/tmux-utils/session-kill.ts b/src/shared/tmux/tmux-utils/session-kill.ts new file mode 100644 index 000000000..fc5f765df --- /dev/null +++ b/src/shared/tmux/tmux-utils/session-kill.ts @@ -0,0 +1,51 @@ +async function readStream(stream: ReadableStream | null | undefined): Promise { + return stream ? new Response(stream).text() : "" +} + +export async function killTmuxSessionIfExists(sessionName: string): Promise { + const [{ log }, { isInsideTmux }, { getTmuxPath }, { spawn }] = await Promise.all([ + import("../../logger"), + import("./environment"), + import("../../../tools/interactive-bash/tmux-path-resolver"), + import("./spawn-process"), + ]) + + if (!isInsideTmux()) { + log("[killTmuxSessionIfExists] SKIP: not inside tmux", { sessionName }) + return false + } + + const tmux = await getTmuxPath() + if (!tmux) { + log("[killTmuxSessionIfExists] SKIP: tmux not found", { sessionName }) + return false + } + + const hasSessionProcess = spawn([tmux, "has-session", "-t", sessionName], { + stdout: "ignore", + stderr: "ignore", + }) + + if ((await hasSessionProcess.exited) !== 0) { + log("[killTmuxSessionIfExists] SKIP: session not found", { sessionName }) + return false + } + + const killSessionProcess = spawn([tmux, "kill-session", "-t", sessionName], { + stdout: "pipe", + stderr: "pipe", + }) + const [, stderr, exitCode] = await Promise.all([ + readStream(killSessionProcess.stdout), + readStream(killSessionProcess.stderr), + killSessionProcess.exited, + ]) + + if (exitCode !== 0) { + log("[killTmuxSessionIfExists] FAILED", { sessionName, exitCode, stderr: stderr.trim() }) + return false + } + + log("[killTmuxSessionIfExists] SUCCESS", { sessionName }) + return true +} diff --git a/src/shared/tmux/tmux-utils/session-spawn.ts b/src/shared/tmux/tmux-utils/session-spawn.ts index db1feee29..a6fd15d2d 100644 --- a/src/shared/tmux/tmux-utils/session-spawn.ts +++ b/src/shared/tmux/tmux-utils/session-spawn.ts @@ -6,7 +6,11 @@ import { isInsideTmux } from "./environment" import { isServerRunning } from "./server-health" import { shellEscapeForDoubleQuotedCommand } from "../../shell-env" -const ISOLATED_SESSION_NAME = "omo-agents" +const ISOLATED_SESSION_NAME_PREFIX = "omo-agents" + +export function getIsolatedSessionName(pid: number = process.pid): string { + return `${ISOLATED_SESSION_NAME_PREFIX}-${pid}` +} async function getWindowDimensions( tmux: string, @@ -87,12 +91,13 @@ export async function spawnTmuxSession( } } - const sessionAlreadyExists = await sessionExists(tmux, ISOLATED_SESSION_NAME) + const isolatedSessionName = getIsolatedSessionName() + const sessionAlreadyExists = await sessionExists(tmux, isolatedSessionName) const args = sessionAlreadyExists ? [ "new-window", - "-t", ISOLATED_SESSION_NAME, + "-t", isolatedSessionName, "-P", "-F", "#{pane_id}", opencodeCmd, @@ -100,7 +105,7 @@ export async function spawnTmuxSession( : [ "new-session", "-d", - "-s", ISOLATED_SESSION_NAME, + "-s", isolatedSessionName, ...sizeArgs, "-P", "-F", "#{pane_id}", @@ -109,7 +114,7 @@ export async function spawnTmuxSession( log("[spawnTmuxSession] spawning", { mode: sessionAlreadyExists ? "new-window" : "new-session", - sessionName: ISOLATED_SESSION_NAME, + sessionName: isolatedSessionName, }) const proc = spawn([tmux, ...args], { stdout: "pipe", stderr: "pipe" }) @@ -140,6 +145,6 @@ export async function spawnTmuxSession( }) } - log("[spawnTmuxSession] SUCCESS", { paneId, sessionName: ISOLATED_SESSION_NAME }) + log("[spawnTmuxSession] SUCCESS", { paneId, sessionName: isolatedSessionName }) return { success: true, paneId } } diff --git a/src/shared/tmux/tmux-utils/spawn-process.ts b/src/shared/tmux/tmux-utils/spawn-process.ts new file mode 100644 index 000000000..c75826cab --- /dev/null +++ b/src/shared/tmux/tmux-utils/spawn-process.ts @@ -0,0 +1 @@ +export { spawn } from "bun" diff --git a/src/shared/tmux/tmux-utils/stale-session-sweep.test.ts b/src/shared/tmux/tmux-utils/stale-session-sweep.test.ts new file mode 100644 index 000000000..1acc171ed --- /dev/null +++ b/src/shared/tmux/tmux-utils/stale-session-sweep.test.ts @@ -0,0 +1,154 @@ +import { beforeEach, describe, expect, it, mock } from "bun:test" +import { sweepStaleOmoAgentSessionsWith, type SweepDeps } from "./stale-session-sweep" + +type SweepFixture = { + deps: SweepDeps + candidates: string[] + killed: string[] + killSessionMock: ReturnType + setCandidates: (sessions: string[]) => void + setAlive: (predicate: (pid: number) => boolean) => void +} + +function createFixture(): SweepFixture { + const candidates: string[] = [] + const killed: string[] = [] + let aliveCheck: (pid: number) => boolean = () => false + + const killSessionMock = mock(async (sessionName: string): Promise => { + killed.push(sessionName) + return true + }) + + const deps: SweepDeps = { + isInsideTmux: () => true, + getTmuxPath: async () => "tmux", + listCandidateSessions: async () => [...candidates], + killSession: killSessionMock, + processAlive: (pid) => aliveCheck(pid), + currentPid: 12345, + log: () => undefined, + } + + return { + deps, + candidates, + killed, + killSessionMock, + setCandidates: (sessions) => { + candidates.length = 0 + candidates.push(...sessions) + }, + setAlive: (predicate) => { + aliveCheck = predicate + }, + } +} + +describe("sweepStaleOmoAgentSessionsWith", () => { + let fixture: SweepFixture + + beforeEach(() => { + fixture = createFixture() + }) + + it("#given not inside tmux #when sweep called #then returns 0 without listing", async () => { + // given + const deps: SweepDeps = { ...fixture.deps, isInsideTmux: () => false } + + // when + const result = await sweepStaleOmoAgentSessionsWith(deps) + + // then + expect(result).toBe(0) + }) + + it("#given tmux not found #when sweep called #then returns 0 without listing", async () => { + // given + const deps: SweepDeps = { ...fixture.deps, getTmuxPath: async () => undefined } + + // when + const result = await sweepStaleOmoAgentSessionsWith(deps) + + // then + expect(result).toBe(0) + }) + + it("#given candidate list is empty #when sweep called #then returns 0 and does not kill anything", async () => { + // given + fixture.setCandidates([]) + + // when + const result = await sweepStaleOmoAgentSessionsWith(fixture.deps) + + // then + expect(result).toBe(0) + expect(fixture.killed).toEqual([]) + }) + + it("#given sessions with dead PIDs #when sweep called #then each dead session is killed once", async () => { + // given + fixture.setCandidates(["omo-agents-99991", "omo-agents-99992"]) + fixture.setAlive(() => false) + + // when + const result = await sweepStaleOmoAgentSessionsWith(fixture.deps) + + // then + expect(result).toBe(2) + expect(fixture.killed).toEqual(["omo-agents-99991", "omo-agents-99992"]) + }) + + it("#given session matches current PID #when sweep called #then it is NOT killed", async () => { + // given + fixture.setCandidates([`omo-agents-${fixture.deps.currentPid}`, "omo-agents-99999"]) + fixture.setAlive(() => false) + + // when + const result = await sweepStaleOmoAgentSessionsWith(fixture.deps) + + // then + expect(result).toBe(1) + expect(fixture.killed).toEqual(["omo-agents-99999"]) + }) + + it("#given session PID is still alive #when sweep called #then it is NOT killed", async () => { + // given + fixture.setCandidates(["omo-agents-88888"]) + fixture.setAlive((pid) => pid === 88888) + + // when + const result = await sweepStaleOmoAgentSessionsWith(fixture.deps) + + // then + expect(result).toBe(0) + expect(fixture.killed).toEqual([]) + }) + + it("#given killSession returns false #when sweep called #then session is not counted toward killedCount", async () => { + // given + fixture.setCandidates(["omo-agents-55555"]) + fixture.setAlive(() => false) + fixture.killSessionMock.mockImplementation(async () => false) + + // when + const result = await sweepStaleOmoAgentSessionsWith(fixture.deps) + + // then + expect(result).toBe(0) + expect(fixture.killSessionMock).toHaveBeenCalledTimes(1) + }) + + it("#given non-matching sessions mixed in #when sweep called #then only omo-agents- sessions are considered", async () => { + // given + fixture.setCandidates(["main", "omo-agents-99999", "other-session", "omo-agents-abc"]) + fixture.setAlive(() => false) + + // when + const result = await sweepStaleOmoAgentSessionsWith(fixture.deps) + + // then + expect(result).toBe(1) + expect(fixture.killed).toEqual(["omo-agents-99999"]) + }) +}) diff --git a/src/shared/tmux/tmux-utils/stale-session-sweep.ts b/src/shared/tmux/tmux-utils/stale-session-sweep.ts new file mode 100644 index 000000000..c8b27e938 --- /dev/null +++ b/src/shared/tmux/tmux-utils/stale-session-sweep.ts @@ -0,0 +1,99 @@ +const STALE_SESSION_PATTERN = /^omo-agents-(\d+)$/ + +function isProcessAlive(pid: number): boolean { + try { + process.kill(pid, 0) + return true + } catch (error) { + const err = error as NodeJS.ErrnoException + return err?.code === "EPERM" + } +} + +async function listOmoAgentSessionsViaTmux(tmux: string): Promise { + const { spawn } = await import("./spawn-process") + const proc = spawn([tmux, "list-sessions", "-F", "#{session_name}"], { + stdout: "pipe", + stderr: "pipe", + }) + const [stdout, , exitCode] = await Promise.all([ + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + proc.exited, + ]) + + if (exitCode !== 0) { + return [] + } + + return stdout + .split("\n") + .map((line) => line.trim()) + .filter((name) => STALE_SESSION_PATTERN.test(name)) +} + +export type SweepDeps = { + isInsideTmux: () => boolean + getTmuxPath: () => Promise + listCandidateSessions: (tmux: string) => Promise + killSession: (sessionName: string) => Promise + processAlive: (pid: number) => boolean + currentPid: number + log: (message: string, payload?: unknown) => void +} + +async function buildRuntimeDeps(): Promise { + const [{ log }, { isInsideTmux }, { getTmuxPath }, { killTmuxSessionIfExists }] = await Promise.all([ + import("../../logger"), + import("./environment"), + import("../../../tools/interactive-bash/tmux-path-resolver"), + import("./session-kill"), + ]) + + return { + isInsideTmux, + getTmuxPath, + listCandidateSessions: listOmoAgentSessionsViaTmux, + killSession: killTmuxSessionIfExists, + processAlive: isProcessAlive, + currentPid: process.pid, + log, + } +} + +export async function sweepStaleOmoAgentSessionsWith(deps: SweepDeps): Promise { + if (!deps.isInsideTmux()) { + return 0 + } + + const tmux = await deps.getTmuxPath() + if (!tmux) { + return 0 + } + + const candidateSessions = await deps.listCandidateSessions(tmux) + let killedCount = 0 + + for (const sessionName of candidateSessions) { + const pidMatch = sessionName.match(STALE_SESSION_PATTERN) + if (!pidMatch) continue + + const pid = Number.parseInt(pidMatch[1], 10) + if (!Number.isFinite(pid)) continue + if (pid === deps.currentPid) continue + if (deps.processAlive(pid)) continue + + deps.log("[sweepStaleOmoAgentSessions] killing stale session", { sessionName, deadPid: pid }) + const killed = await deps.killSession(sessionName) + if (killed) { + killedCount += 1 + } + } + + return killedCount +} + +export async function sweepStaleOmoAgentSessions(): Promise { + const deps = await buildRuntimeDeps() + return sweepStaleOmoAgentSessionsWith(deps) +} diff --git a/src/tools/AGENTS.md b/src/tools/AGENTS.md index 6c69183cf..d6e6f41ad 100644 --- a/src/tools/AGENTS.md +++ b/src/tools/AGENTS.md @@ -1,6 +1,6 @@ # src/tools/ - 26 Tools Across 16 Directories -**Generated:** 2026-04-11 +**Generated:** 2026-04-18 ## OVERVIEW @@ -97,7 +97,7 @@ | artistry | gemini-3.1-pro high | Creative approaches | | quick | gpt-5.4-mini | Trivial tasks | | unspecified-low | claude-sonnet-4-6 | Moderate effort | -| unspecified-high | claude-opus-4-6 max | High effort | +| unspecified-high | claude-opus-4-7 max | High effort | | writing | gemini-3-flash | Documentation | ## HOW TO ADD A TOOL diff --git a/src/tools/background-task/create-background-cancel.ts b/src/tools/background-task/create-background-cancel.ts index 49fc4e5bb..a61c280f8 100644 --- a/src/tools/background-task/create-background-cancel.ts +++ b/src/tools/background-task/create-background-cancel.ts @@ -59,7 +59,7 @@ export function createBackgroundCancel(manager: BackgroundManager, _client: Back To continue a cancelled task, use: \`\`\` -task(session_id="", prompt="Continue: ") +task(task_id="", prompt="Continue: ") \`\`\` Continuable sessions: diff --git a/src/tools/background-task/create-background-output.metadata.test.ts b/src/tools/background-task/create-background-output.metadata.test.ts index 5111667bb..7b031abee 100644 --- a/src/tools/background-task/create-background-output.metadata.test.ts +++ b/src/tools/background-task/create-background-output.metadata.test.ts @@ -59,7 +59,7 @@ describe("createBackgroundOutput metadata", () => { agent: "test-agent", category: undefined, description: "background task", - task_id: "task-1", + backgroundTaskId: "task-1", }, }) diff --git a/src/tools/background-task/create-background-output.ts b/src/tools/background-task/create-background-output.ts index 925db344a..56634b191 100644 --- a/src/tools/background-task/create-background-output.ts +++ b/src/tools/background-task/create-background-output.ts @@ -1,6 +1,6 @@ import { tool, type ToolDefinition } from "@opencode-ai/plugin" import type { BackgroundTask } from "../../features/background-agent" -import { storeToolMetadata } from "../../features/tool-metadata-store" +import { publishToolMetadata } from "../../features/tool-metadata-store" import type { BackgroundOutputArgs } from "./types" import type { BackgroundOutputClient, BackgroundOutputManager } from "./clients" import { BACKGROUND_OUTPUT_DESCRIPTION } from "./constants" @@ -23,13 +23,6 @@ type ToolContextWithMetadata = { call_id?: string } -function resolveToolCallID(ctx: ToolContextWithMetadata): string | undefined { - if (typeof ctx.callID === "string" && ctx.callID.trim() !== "") return ctx.callID - if (typeof ctx.callId === "string" && ctx.callId.trim() !== "") return ctx.callId - if (typeof ctx.call_id === "string" && ctx.call_id.trim() !== "") return ctx.call_id - return undefined -} - function formatResolvedTitle(task: BackgroundTask): string { const label = task.agent === SISYPHUS_JUNIOR_AGENT && task.category ? task.category : task.agent return `${label} - ${task.description}` @@ -73,19 +66,14 @@ export function createBackgroundOutput(manager: BackgroundOutputManager, client: const meta = { title: formatResolvedTitle(task), metadata: { - task_id: task.id, + backgroundTaskId: task.id, agent: task.agent, category: task.category, description: task.description, - ...(task.sessionID ? { sessionId: task.sessionID } : {}), + ...(task.sessionID ? { sessionId: task.sessionID, taskId: task.sessionID } : {}), } as Record, } - ctx.metadata?.(meta) - - const callID = resolveToolCallID(ctx) - if (callID) { - storeToolMetadata(ctx.sessionID, callID, meta) - } + await publishToolMetadata(ctx, meta) const shouldBlock = args.block === true const timeoutMs = Math.min(args.timeout ?? 60000, 600000) diff --git a/src/tools/background-task/create-background-task.ts b/src/tools/background-task/create-background-task.ts index 0d2c38f0f..cd892e6a3 100644 --- a/src/tools/background-task/create-background-task.ts +++ b/src/tools/background-task/create-background-task.ts @@ -4,7 +4,7 @@ import type { BackgroundTaskArgs } from "./types" import { BACKGROUND_TASK_DESCRIPTION } from "./constants" import { resolveMessageContext } from "../../features/hook-message-injector" import { getSessionAgent } from "../../features/claude-code-session-state" -import { storeToolMetadata } from "../../features/tool-metadata-store" +import { publishToolMetadata } from "../../features/tool-metadata-store" import { log } from "../../shared/logger" import { delay } from "./delay" import { getMessageDir } from "./message-dir" @@ -100,11 +100,7 @@ export function createBackgroundTask( ...(sessionId ? { sessionId } : {}), }, } - ctx.metadata?.(bgMeta) - - if (ctx.callID) { - storeToolMetadata(ctx.sessionID, ctx.callID, bgMeta) - } + await publishToolMetadata(ctx, bgMeta) return `Background task launched successfully. diff --git a/src/tools/call-omo-agent/agent-resolver.test.ts b/src/tools/call-omo-agent/agent-resolver.test.ts new file mode 100644 index 000000000..f2e13022b --- /dev/null +++ b/src/tools/call-omo-agent/agent-resolver.test.ts @@ -0,0 +1,240 @@ +/** + * Requirement-based tests for resolveCallableAgents(). + * + * These tests are derived from behavioral requirements in the PR description + * and feature spec, NOT from reading the implementation: + * + * R1: ALLOWED_AGENTS always present as baseline + * R2: Dynamic agents from client.app.agents() merged into the result + * R3: Primary-mode agents excluded from callable list + * R4: Falls back to ALLOWED_AGENTS alone when client.app.agents() fails + * R5: All output names are lowercase + * R6: No duplicate agent names in output + * R7: Malformed agent entries (null, missing name, non-string name, whitespace-only) are skipped gracefully + */ +const { describe, test, expect, mock, beforeEach } = require("bun:test") +const { resolveCallableAgents, clearCallableAgentsCache } = require("./agent-resolver") +const { ALLOWED_AGENTS } = require("./constants") + +function createMockClient(agents: Array>) { + return { + app: { + agents: mock(() => Promise.resolve({ data: agents })), + }, + } +} + +function createFailingClient(error: Error = new Error("API unavailable")) { + return { + app: { + agents: mock(() => Promise.reject(error)), + }, + } +} + +describe("resolveCallableAgents", () => { + beforeEach(() => { + clearCallableAgentsCache() + }) + + describe("#given the SDK returns agents successfully", () => { + describe("#when only built-in agents exist", () => { + test("#then every ALLOWED_AGENT appears in the result", async () => { + const builtinAgents = ALLOWED_AGENTS.map((name: string) => ({ + name, + mode: "subagent", + })) + const client = createMockClient(builtinAgents) + + const result = await resolveCallableAgents(client) + + for (const agent of ALLOWED_AGENTS) { + expect(result).toContain(agent) + } + }) + }) + + describe("#when dynamic custom agents are present alongside built-ins", () => { + test("#then custom agents are included in the result", async () => { + const agents = [ + ...ALLOWED_AGENTS.map((name: string) => ({ name, mode: "subagent" })), + { name: "bug-fixer", mode: "subagent" }, + { name: "code-reviewer", mode: "subagent" }, + ] + const client = createMockClient(agents) + + const result = await resolveCallableAgents(client) + + expect(result).toContain("bug-fixer") + expect(result).toContain("code-reviewer") + }) + + test("#then ALLOWED_AGENTS are still present", async () => { + const agents = [{ name: "custom-agent", mode: "subagent" }] + const client = createMockClient(agents) + + const result = await resolveCallableAgents(client) + + for (const agent of ALLOWED_AGENTS) { + expect(result).toContain(agent) + } + }) + }) + + describe("#when an agent has mode=primary", () => { + test("#then it is excluded from the callable list", async () => { + const agents = [ + { name: "sisyphus", mode: "primary" }, + { name: "explore", mode: "subagent" }, + ] + const client = createMockClient(agents) + + const result = await resolveCallableAgents(client) + + expect(result).not.toContain("sisyphus") + expect(result).toContain("explore") + }) + }) + + describe("#when agent names have mixed case", () => { + test("#then all output names are lowercase", async () => { + const agents = [ + { name: "Bug-Fixer", mode: "subagent" }, + { name: "CODE-REVIEWER", mode: "subagent" }, + ] + const client = createMockClient(agents) + + const result = await resolveCallableAgents(client) + + expect(result).toContain("bug-fixer") + expect(result).toContain("code-reviewer") + for (const name of result) { + expect(name).toBe(name.toLowerCase()) + } + }) + }) + + describe("#when duplicate agent names exist across sources", () => { + test("#then no duplicates appear in the result", async () => { + const agents = [ + { name: "explore", mode: "subagent" }, + { name: "explore", mode: "subagent" }, + { name: "Explore", mode: "subagent" }, + ] + const client = createMockClient(agents) + + const result = await resolveCallableAgents(client) + + const exploreCount = result.filter((n: string) => n === "explore").length + expect(exploreCount).toBe(1) + }) + }) + + describe("#when agent entries are malformed", () => { + test("#then entries with null name are skipped", async () => { + const agents = [ + { name: null, mode: "subagent" }, + { name: "explore", mode: "subagent" }, + ] + const client = createMockClient(agents) + + const result = await resolveCallableAgents(client) + + expect(result).toContain("explore") + expect(result.length).toBeGreaterThanOrEqual(ALLOWED_AGENTS.length) + }) + + test("#then entries with numeric name are skipped", async () => { + const agents = [ + { name: 42, mode: "subagent" }, + { name: "explore", mode: "subagent" }, + ] + const client = createMockClient(agents) + + const result = await resolveCallableAgents(client) + + expect(result).not.toContain("42") + expect(result).toContain("explore") + }) + + test("#then entries with whitespace-only name are skipped", async () => { + const agents = [ + { name: " ", mode: "subagent" }, + { name: "explore", mode: "subagent" }, + ] + const client = createMockClient(agents) + + const result = await resolveCallableAgents(client) + + expect(result).not.toContain("") + expect(result).not.toContain(" ") + expect(result).toContain("explore") + }) + + test("#then entries with missing name property are skipped", async () => { + const agents = [ + { mode: "subagent" }, + { name: "explore", mode: "subagent" }, + ] + const client = createMockClient(agents) + + const result = await resolveCallableAgents(client) + + expect(result).toContain("explore") + expect(result.length).toBeGreaterThanOrEqual(ALLOWED_AGENTS.length) + }) + + test("#then entries that are undefined/null themselves are skipped", async () => { + const agents = [ + null, + undefined, + { name: "explore", mode: "subagent" }, + ] as unknown as Array> + const client = createMockClient(agents) + + const result = await resolveCallableAgents(client) + + expect(result).toContain("explore") + }) + }) + + describe("#when SDK returns an empty list", () => { + test("#then ALLOWED_AGENTS still appear as the baseline", async () => { + const client = createMockClient([]) + + const result = await resolveCallableAgents(client) + + for (const agent of ALLOWED_AGENTS) { + expect(result).toContain(agent) + } + expect(result.length).toBe(ALLOWED_AGENTS.length) + }) + }) + }) + + describe("#given the SDK call fails", () => { + describe("#when client.app.agents() throws an error", () => { + test("#then it falls back to ALLOWED_AGENTS", async () => { + const client = createFailingClient(new Error("Network error")) + + const result = await resolveCallableAgents(client) + + expect(result.length).toBe(ALLOWED_AGENTS.length) + for (const agent of ALLOWED_AGENTS) { + expect(result).toContain(agent) + } + }) + + test("#then custom agents are NOT available in fallback mode", async () => { + const client = createFailingClient() + + const result = await resolveCallableAgents(client) + + expect(result).not.toContain("bug-fixer") + expect(result).not.toContain("custom-agent") + }) + }) + }) +}) + +export {} diff --git a/src/tools/call-omo-agent/agent-resolver.ts b/src/tools/call-omo-agent/agent-resolver.ts new file mode 100644 index 000000000..70bc4c32f --- /dev/null +++ b/src/tools/call-omo-agent/agent-resolver.ts @@ -0,0 +1,64 @@ +import type { PluginInput } from "@opencode-ai/plugin"; +import { ALLOWED_AGENTS } from "./constants"; +import { normalizeSDKResponse } from "../../shared"; +import { log } from "../../shared/logger"; + +type AgentInfo = { + name: string; + mode?: "subagent" | "primary" | "all"; +}; + +const callableAgentsCache = new Map(); +const CACHE_TTL_MS = 30_000; + +export function clearCallableAgentsCache(): void { + callableAgentsCache.clear(); +} + +/** + * Resolves the set of callable agent names at execute-time by merging the + * hardcoded `ALLOWED_AGENTS` with any additional agents discovered dynamically + * via `client.app.agents()`. Custom agents loaded from registered agent + * directories appear here alongside built-ins. + * + * Results are cached per session for 30s to avoid redundant SDK IPC calls. + * + * Falls back to `ALLOWED_AGENTS` alone if the dynamic lookup fails. + * + * @param client - The plugin client with access to the agent registry + * @param sessionId - Optional session ID for cache scoping + * @returns Array of lowercase callable agent names (excludes primary-mode agents) + */ +export async function resolveCallableAgents( + client: PluginInput["client"], + sessionId?: string, +): Promise { + const cacheKey = sessionId ?? "__default__"; + const cached = callableAgentsCache.get(cacheKey); + if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) { + return cached.agents; + } + + try { + const agentsResult = await client.app.agents(); + const agents = normalizeSDKResponse(agentsResult, [] as AgentInfo[], { + preferResponseOnMissingData: true, + }); + + const dynamicAgents = agents + .filter((a) => a && typeof a.name === "string" && a.name.trim().length > 0 && a.mode !== "primary") + .map((a) => a.name.trim().toLowerCase()); + + const merged = new Set([...ALLOWED_AGENTS, ...dynamicAgents]); + const result = [...merged]; + callableAgentsCache.set(cacheKey, { agents: result, timestamp: Date.now() }); + return result; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + log( + "[call_omo_agent] Failed to resolve dynamic agents, falling back to built-in list", + { error: message }, + ); + return [...ALLOWED_AGENTS]; + } +} diff --git a/src/tools/call-omo-agent/constants.ts b/src/tools/call-omo-agent/constants.ts index 028b0d602..823313f2f 100644 --- a/src/tools/call-omo-agent/constants.ts +++ b/src/tools/call-omo-agent/constants.ts @@ -8,8 +8,11 @@ export const ALLOWED_AGENTS = [ "multimodal-looker", ] as const -export const CALL_OMO_AGENT_DESCRIPTION = `Spawn explore/librarian agent. run_in_background REQUIRED (true=async with task_id, false=sync). +export const CALL_OMO_AGENT_DESCRIPTION = `Spawn explore/librarian agent or custom agents. run_in_background REQUIRED (true=async with task_id, false=sync). -Available: {agents} +Built-in agents: +{agents} + +Custom agents registered via user or project agent directories are also supported. Pass \`session_id=\` to continue previous agent with full context. Nested subagent depth is tracked automatically and blocked past the configured limit. Prompts MUST be in English. Use \`background_output\` for async results.` diff --git a/src/tools/call-omo-agent/sync-executor.ts b/src/tools/call-omo-agent/sync-executor.ts index 23089d8ea..56e22a80a 100644 --- a/src/tools/call-omo-agent/sync-executor.ts +++ b/src/tools/call-omo-agent/sync-executor.ts @@ -1,7 +1,6 @@ import type { CallOmoAgentArgs } from "./types" import type { PluginInput } from "@opencode-ai/plugin" import { subagentSessions, syncSubagentSessions } from "../../features/claude-code-session-state" -import { clearSessionFallbackChain, setSessionFallbackChain } from "../../hooks/model-fallback/hook" import { getAgentToolRestrictions, log } from "../../shared" import { applySessionPromptParams } from "../../shared/session-prompt-params-helpers" import type { DelegatedModelConfig } from "../../shared/model-resolution-types" @@ -19,8 +18,8 @@ type ExecuteSyncDeps = { createOrGetSession: typeof createOrGetSession waitForCompletion: typeof waitForCompletion processMessages: typeof processMessages - setSessionFallbackChain: typeof setSessionFallbackChain - clearSessionFallbackChain: typeof clearSessionFallbackChain + setSessionFallbackChain: (sessionID: string, fallbackChain: FallbackEntry[] | undefined) => void + clearSessionFallbackChain: (sessionID: string) => void } type SpawnReservation = { @@ -32,8 +31,8 @@ const defaultDeps: ExecuteSyncDeps = { createOrGetSession, waitForCompletion, processMessages, - setSessionFallbackChain, - clearSessionFallbackChain, + setSessionFallbackChain: () => {}, + clearSessionFallbackChain: () => {}, } function buildPromptGenerationParams(model: DelegatedModelConfig | undefined): Record { diff --git a/src/tools/call-omo-agent/tools-edge-cases.test.ts b/src/tools/call-omo-agent/tools-edge-cases.test.ts new file mode 100644 index 000000000..9d4c546e9 --- /dev/null +++ b/src/tools/call-omo-agent/tools-edge-cases.test.ts @@ -0,0 +1,224 @@ +/** + * Requirement-based integration tests for createCallOmoAgent edge cases + * introduced by the dev rebase and dynamic agent resolution feature. + * + * R1: Spawn reservation is rolled back when execution fails after reservation + * R2: Agent names with leading/trailing whitespace are trimmed before matching + * R3: An agent present in both ALLOWED_AGENTS and dynamic list is callable (no conflict) + * R4: session_id continuation rejects in background mode when session already exists + */ +const { describe, test, expect, mock, beforeEach } = require("bun:test") +const { createCallOmoAgent } = require("./tools") +const { clearCallableAgentsCache } = require("./agent-resolver") + +type PluginInput = { client: any; directory: string } + +function createMockCtx(agents: Array<{ name: string; mode?: string }> = []): PluginInput { + return { + client: { + app: { + agents: mock(() => Promise.resolve({ data: agents })), + }, + }, + directory: "/test", + } as unknown as PluginInput +} + +const DEFAULT_AGENTS = [ + { name: "explore", mode: "subagent" }, + { name: "librarian", mode: "subagent" }, + { name: "oracle", mode: "subagent" }, + { name: "hephaestus", mode: "subagent" }, + { name: "metis", mode: "subagent" }, + { name: "momus", mode: "subagent" }, + { name: "multimodal-looker", mode: "subagent" }, +] + +const reserveCommitMock = mock(() => 1) +const reserveRollbackMock = mock(() => {}) +const reserveSubagentSpawnMock = mock(() => Promise.resolve({ + spawnContext: { rootSessionID: "root-session", parentDepth: 0, childDepth: 1 }, + descendantCount: 1, + commit: reserveCommitMock, + rollback: reserveRollbackMock, +})) + +const toolCtx = { + sessionID: "test", + messageID: "msg", + agent: "test", + abort: new AbortController().signal, +} + +beforeEach(() => { + clearCallableAgentsCache() + reserveSubagentSpawnMock.mockClear() + reserveCommitMock.mockClear() + reserveRollbackMock.mockClear() +}) + +describe("createCallOmoAgent edge cases", () => { + describe("#given spawn reservation succeeds but sync execution fails", () => { + test("#then rollback is called to release the reservation", async () => { + const mockCtx = createMockCtx(DEFAULT_AGENTS) + reserveSubagentSpawnMock.mockResolvedValueOnce({ + spawnContext: { rootSessionID: "root-session", parentDepth: 0, childDepth: 1 }, + descendantCount: 1, + commit: reserveCommitMock, + rollback: reserveRollbackMock, + }) + const mockManager = { + assertCanSpawn: mock(() => Promise.resolve(undefined)), + reserveSubagentSpawn: reserveSubagentSpawnMock, + launch: mock(() => Promise.resolve()), + getTask: mock(() => undefined), + } + const toolDef = createCallOmoAgent(mockCtx, mockManager, []) + const executeFunc = toolDef.execute as Function + + const result = await executeFunc( + { + description: "Test", + prompt: "Test prompt", + subagent_type: "explore", + run_in_background: false, + }, + toolCtx, + ) + + expect(reserveRollbackMock).toHaveBeenCalled() + expect(result).toContain("Error:") + }) + }) + + describe("#given agent names with extra whitespace from SDK", () => { + test("#then whitespace-padded names are trimmed and matched correctly", async () => { + const agents = [ + ...DEFAULT_AGENTS, + { name: " bug-fixer ", mode: "subagent" }, + ] + const mockCtx = createMockCtx(agents) + const mockManager = { + assertCanSpawn: mock(() => Promise.resolve(undefined)), + reserveSubagentSpawn: reserveSubagentSpawnMock, + launch: mock(() => Promise.resolve({ + id: "task-id", + sessionID: "ses-1", + description: "Test", + agent: "bug-fixer", + status: "pending", + })), + getTask: mock(() => ({ status: "pending", sessionID: "ses-1" })), + } + const toolDef = createCallOmoAgent(mockCtx, mockManager, []) + const executeFunc = toolDef.execute as Function + + const result = await executeFunc( + { + description: "Test", + prompt: "Fix bug", + subagent_type: "bug-fixer", + run_in_background: true, + }, + toolCtx, + ) + + expect(result).not.toContain("Invalid agent type") + }) + }) + + describe("#given an agent exists in both ALLOWED_AGENTS and dynamic results", () => { + test("#then the agent is callable without conflict", async () => { + const agents = [ + ...DEFAULT_AGENTS, + { name: "explore", mode: "subagent" }, + ] + const mockCtx = createMockCtx(agents) + const mockManager = { + assertCanSpawn: mock(() => Promise.resolve(undefined)), + reserveSubagentSpawn: reserveSubagentSpawnMock, + launch: mock(() => Promise.resolve({ + id: "task-id", + sessionID: "ses-1", + description: "Test", + agent: "explore", + status: "pending", + })), + getTask: mock(() => ({ status: "pending", sessionID: "ses-1" })), + } + const toolDef = createCallOmoAgent(mockCtx, mockManager, []) + const executeFunc = toolDef.execute as Function + + const result = await executeFunc( + { + description: "Test", + prompt: "Search codebase", + subagent_type: "explore", + run_in_background: true, + }, + toolCtx, + ) + + expect(result).not.toContain("Invalid agent type") + }) + }) + + describe("#given a disabled custom agent from dynamic resolution", () => { + test("#then disabled_agents check takes precedence over dynamic availability", async () => { + const agents = [ + ...DEFAULT_AGENTS, + { name: "bug-fixer", mode: "subagent" }, + ] + const mockCtx = createMockCtx(agents) + const mockManager = { + assertCanSpawn: mock(() => Promise.resolve(undefined)), + reserveSubagentSpawn: reserveSubagentSpawnMock, + launch: mock(() => Promise.resolve()), + getTask: mock(() => undefined), + } + const toolDef = createCallOmoAgent(mockCtx, mockManager, ["Bug-Fixer"]) + const executeFunc = toolDef.execute as Function + + const result = await executeFunc( + { + description: "Test", + prompt: "Fix bug", + subagent_type: "bug-fixer", + run_in_background: true, + }, + toolCtx, + ) + + expect(result).toContain("disabled via disabled_agents") + }) + }) + + describe("#given session_id is provided in background mode", () => { + test("#then the request is rejected with a clear error", async () => { + const mockCtx = createMockCtx(DEFAULT_AGENTS) + const mockManager = { + assertCanSpawn: mock(() => Promise.resolve(undefined)), + reserveSubagentSpawn: reserveSubagentSpawnMock, + launch: mock(() => Promise.resolve()), + getTask: mock(() => undefined), + } + const toolDef = createCallOmoAgent(mockCtx, mockManager, []) + const executeFunc = toolDef.execute as Function + + const result = await executeFunc( + { + description: "Test", + prompt: "Continue work", + subagent_type: "explore", + run_in_background: true, + session_id: "ses-existing-123", + }, + toolCtx, + ) + + expect(result).toContain("session_id is not supported in background mode") + }) + }) +}) + +export {} diff --git a/src/tools/call-omo-agent/tools.test.ts b/src/tools/call-omo-agent/tools.test.ts index 5d499b7bb..17491cb82 100644 --- a/src/tools/call-omo-agent/tools.test.ts +++ b/src/tools/call-omo-agent/tools.test.ts @@ -1,119 +1,239 @@ const { beforeEach, describe, test, expect, mock } = require("bun:test") const { createCallOmoAgent } = require("./tools") +const { clearCallableAgentsCache } = require("./agent-resolver") + +type PluginInput = { client: any; directory: string } +type BackgroundManager = { + assertCanSpawn: Function + reserveSubagentSpawn: Function + launch: Function + getTask: Function +} + +function createMockCtx(agents: Array<{ name: string; mode?: string }> = []): PluginInput { + return { + client: { + app: { + agents: mock(() => Promise.resolve({ data: agents })), + }, + }, + directory: "/test", + } as unknown as PluginInput +} + +function createFailingMockCtx(error: Error = new Error("API unavailable")): PluginInput { + return { + client: { + app: { + agents: mock(() => Promise.reject(error)), + }, + }, + directory: "/test", + } as unknown as PluginInput +} + +const DEFAULT_AGENTS = [ + { name: "explore", mode: "subagent" }, + { name: "librarian", mode: "subagent" }, + { name: "oracle", mode: "subagent" }, + { name: "hephaestus", mode: "subagent" }, + { name: "metis", mode: "subagent" }, + { name: "momus", mode: "subagent" }, + { name: "multimodal-looker", mode: "subagent" }, +] + +const assertCanSpawnMock = mock(() => Promise.resolve(undefined)) +const reserveCommitMock = mock(() => 1) +const reserveRollbackMock = mock(() => {}) +const reserveSubagentSpawnMock = mock(() => Promise.resolve({ + spawnContext: { rootSessionID: "root-session", parentDepth: 0, childDepth: 1 }, + descendantCount: 1, + commit: reserveCommitMock, + rollback: reserveRollbackMock, +})) + +const mockBackgroundManager = { + assertCanSpawn: assertCanSpawnMock, + reserveSubagentSpawn: reserveSubagentSpawnMock, + launch: mock(() => Promise.resolve({ + id: "test-task-id", + sessionID: null, + description: "Test task", + agent: "test-agent", + status: "pending", + })), + getTask: mock(() => ({ status: "pending", sessionID: "ses-123" })), +} as unknown as BackgroundManager + +const toolCtx = { + sessionID: "test", + messageID: "msg", + agent: "test", + abort: new AbortController().signal, +} + +beforeEach(() => { + clearCallableAgentsCache() + assertCanSpawnMock.mockClear() + reserveSubagentSpawnMock.mockClear() + reserveCommitMock.mockClear() + reserveRollbackMock.mockClear() +}) describe("createCallOmoAgent", () => { - const assertCanSpawnMock = mock(() => Promise.resolve(undefined)) - const reserveCommitMock = mock(() => 1) - const reserveRollbackMock = mock(() => {}) - const reserveSubagentSpawnMock = mock(() => Promise.resolve({ - spawnContext: { rootSessionID: "root-session", parentDepth: 0, childDepth: 1 }, - descendantCount: 1, - commit: reserveCommitMock, - rollback: reserveRollbackMock, - })) - const mockCtx = { - client: {}, - directory: "/test", - } + describe("disabled_agents validation", () => { + test("should reject agent in disabled_agents list", async () => { + const mockCtx = createMockCtx(DEFAULT_AGENTS) + const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, ["explore"]) + const executeFunc = toolDef.execute as Function - const mockBackgroundManager = { - assertCanSpawn: assertCanSpawnMock, - reserveSubagentSpawn: reserveSubagentSpawnMock, - launch: mock(() => Promise.resolve({ - id: "test-task-id", - sessionID: null, - description: "Test task", - agent: "test-agent", - status: "pending", - })), - } + const result = await executeFunc( + { description: "Test", prompt: "Test prompt", subagent_type: "explore", run_in_background: true }, + toolCtx + ) - beforeEach(() => { - assertCanSpawnMock.mockClear() - reserveSubagentSpawnMock.mockClear() - reserveCommitMock.mockClear() - reserveRollbackMock.mockClear() + expect(result).toContain("disabled via disabled_agents") + }) + + test("should reject agent in disabled_agents list with case-insensitive matching", async () => { + const mockCtx = createMockCtx(DEFAULT_AGENTS) + const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, ["Explore"]) + const executeFunc = toolDef.execute as Function + + const result = await executeFunc( + { description: "Test", prompt: "Test prompt", subagent_type: "explore", run_in_background: true }, + toolCtx + ) + + expect(result).toContain("disabled via disabled_agents") + }) + + test("should allow agent not in disabled_agents list", async () => { + const mockCtx = createMockCtx(DEFAULT_AGENTS) + const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, ["librarian"]) + const executeFunc = toolDef.execute as Function + + const result = await executeFunc( + { description: "Test", prompt: "Test prompt", subagent_type: "explore", run_in_background: true }, + toolCtx + ) + + expect(result).not.toContain("disabled via disabled_agents") + }) + + test("should allow all agents when disabled_agents is empty", async () => { + const mockCtx = createMockCtx(DEFAULT_AGENTS) + const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, []) + const executeFunc = toolDef.execute as Function + + const result = await executeFunc( + { description: "Test", prompt: "Test prompt", subagent_type: "explore", run_in_background: true }, + toolCtx + ) + + expect(result).not.toContain("disabled via disabled_agents") + }) }) - test("should reject agent in disabled_agents list", async () => { - //#given - const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, ["explore"]) - const executeFunc = toolDef.execute as Function + describe("dynamic custom agent resolution", () => { + test("should accept a custom agent returned by client.app.agents()", async () => { + const agents = [...DEFAULT_AGENTS, { name: "bug-fixer", mode: "subagent" }] + const mockCtx = createMockCtx(agents) + const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, []) + const executeFunc = toolDef.execute as Function - //#when - const result = await executeFunc( - { - description: "Test", - prompt: "Test prompt", - subagent_type: "explore", - run_in_background: true, - }, - { sessionID: "test", messageID: "msg", agent: "test", abort: new AbortController().signal } - ) + const result = await executeFunc( + { description: "Test", prompt: "Fix bug", subagent_type: "bug-fixer", run_in_background: true }, + toolCtx + ) - //#then - expect(result).toContain("disabled via disabled_agents") - }) + expect(result).not.toContain("Invalid agent type") + expect(result).not.toContain("not found") + }) - test("should reject agent in disabled_agents list with case-insensitive matching", async () => { - //#given - const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, ["Explore"]) - const executeFunc = toolDef.execute as Function + test("should reject a custom agent NOT returned by client.app.agents()", async () => { + const mockCtx = createMockCtx(DEFAULT_AGENTS) + const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, []) + const executeFunc = toolDef.execute as Function - //#when - const result = await executeFunc( - { - description: "Test", - prompt: "Test prompt", - subagent_type: "explore", - run_in_background: true, - }, - { sessionID: "test", messageID: "msg", agent: "test", abort: new AbortController().signal } - ) + const result = await executeFunc( + { description: "Test", prompt: "Fix bug", subagent_type: "nonexistent-agent", run_in_background: true }, + toolCtx + ) - //#then - expect(result).toContain("disabled via disabled_agents") - }) + expect(result).toContain("Invalid agent type") + }) - test("should allow agent not in disabled_agents list", async () => { - //#given - const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, ["librarian"]) - const executeFunc = toolDef.execute as Function + test("should perform case-insensitive matching for custom agents", async () => { + const agents = [...DEFAULT_AGENTS, { name: "Bug-Fixer", mode: "subagent" }] + const mockCtx = createMockCtx(agents) + const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, []) + const executeFunc = toolDef.execute as Function - //#when - const result = await executeFunc( - { - description: "Test", - prompt: "Test prompt", - subagent_type: "explore", - run_in_background: true, - }, - { sessionID: "test", messageID: "msg", agent: "test", abort: new AbortController().signal } - ) + const result = await executeFunc( + { description: "Test", prompt: "Fix bug", subagent_type: "bug-fixer", run_in_background: true }, + toolCtx + ) - //#then - // Should not contain disabled error - may fail for other reasons but disabled check should pass - expect(result).not.toContain("disabled via disabled_agents") - }) + expect(result).not.toContain("Invalid agent type") + }) - test("should allow all agents when disabled_agents is empty", async () => { - //#given - const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, []) - const executeFunc = toolDef.execute as Function + test("should exclude primary-mode agents from callable list", async () => { + const agents = [ + ...DEFAULT_AGENTS, + { name: "sisyphus", mode: "primary" }, + ] + const mockCtx = createMockCtx(agents) + const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, []) + const executeFunc = toolDef.execute as Function - //#when - const result = await executeFunc( - { - description: "Test", - prompt: "Test prompt", - subagent_type: "explore", - run_in_background: true, - }, - { sessionID: "test", messageID: "msg", agent: "test", abort: new AbortController().signal } - ) + const result = await executeFunc( + { description: "Test", prompt: "Orchestrate", subagent_type: "sisyphus", run_in_background: true }, + toolCtx + ) - //#then - expect(result).not.toContain("disabled via disabled_agents") + expect(result).toContain("Invalid agent type") + }) + + test("should fall back to ALLOWED_AGENTS when client.app.agents() fails", async () => { + const mockCtx = createFailingMockCtx() + const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, []) + const executeFunc = toolDef.execute as Function + + const result = await executeFunc( + { description: "Test", prompt: "Explore codebase", subagent_type: "explore", run_in_background: true }, + toolCtx + ) + + expect(result).not.toContain("Invalid agent type") + }) + + test("should reject unknown agent even when client.app.agents() fails (fallback mode)", async () => { + const mockCtx = createFailingMockCtx() + const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, []) + const executeFunc = toolDef.execute as Function + + const result = await executeFunc( + { description: "Test", prompt: "Fix bug", subagent_type: "custom-agent", run_in_background: true }, + toolCtx + ) + + expect(result).toContain("Invalid agent type") + }) + + test("should still apply disabled_agents check to dynamically resolved custom agents", async () => { + const agents = [...DEFAULT_AGENTS, { name: "bug-fixer", mode: "subagent" }] + const mockCtx = createMockCtx(agents) + const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, ["bug-fixer"]) + const executeFunc = toolDef.execute as Function + + const result = await executeFunc( + { description: "Test", prompt: "Fix bug", subagent_type: "bug-fixer", run_in_background: true }, + toolCtx + ) + + expect(result).toContain("disabled via disabled_agents") + }) }) test("uses agent override fallback_models when launching background subagent", async () => { @@ -129,6 +249,7 @@ describe("createCallOmoAgent", () => { launch, getTask: mock(() => undefined), } + const mockCtx = createMockCtx(DEFAULT_AGENTS) const toolDef = createCallOmoAgent( mockCtx, managerWithLaunch, @@ -179,7 +300,7 @@ describe("createCallOmoAgent", () => { getTask: mock(() => undefined), } const toolDef = createCallOmoAgent( - mockCtx, + createMockCtx(DEFAULT_AGENTS), managerWithLaunch, [], { @@ -228,7 +349,7 @@ describe("createCallOmoAgent", () => { getTask: mock(() => undefined), } const toolDef = createCallOmoAgent( - mockCtx, + createMockCtx(DEFAULT_AGENTS), managerWithLaunch, [], { @@ -279,7 +400,7 @@ describe("createCallOmoAgent", () => { getTask: mock(() => undefined), } const toolDef = createCallOmoAgent( - mockCtx, + createMockCtx(DEFAULT_AGENTS), managerWithLaunch, [], { @@ -329,7 +450,7 @@ describe("createCallOmoAgent", () => { getTask: mock(() => undefined), } const toolDef = createCallOmoAgent( - mockCtx, + createMockCtx(DEFAULT_AGENTS), managerWithLaunch, [], { @@ -371,6 +492,7 @@ describe("createCallOmoAgent", () => { test("should return a tool error when sync spawn depth validation fails", async () => { //#given + const mockCtx = createMockCtx(DEFAULT_AGENTS) reserveSubagentSpawnMock.mockRejectedValueOnce(new Error("Subagent spawn blocked: child depth 4 exceeds background_task.maxDepth=3.")) const toolDef = createCallOmoAgent(mockCtx, mockBackgroundManager, []) const executeFunc = toolDef.execute as Function diff --git a/src/tools/call-omo-agent/tools.ts b/src/tools/call-omo-agent/tools.ts index d3e12f14d..51ea8730c 100644 --- a/src/tools/call-omo-agent/tools.ts +++ b/src/tools/call-omo-agent/tools.ts @@ -1,19 +1,38 @@ import { tool, type PluginInput, type ToolDefinition } from "@opencode-ai/plugin" import { ALLOWED_AGENTS, CALL_OMO_AGENT_DESCRIPTION } from "./constants" -import type { AllowedAgentType, CallOmoAgentArgs, ToolContextWithMetadata } from "./types" +import type { CallOmoAgentArgs, ToolContextWithMetadata } from "./types" import type { BackgroundManager } from "../../features/background-agent" +import type { ModelFallbackControllerAccessor } from "../../hooks/model-fallback" import type { CategoriesConfig, AgentOverrides } from "../../config/schema" import type { DelegatedModelConfig } from "../../shared/model-resolution-types" import type { FallbackEntry } from "../../shared/model-requirements" import { AGENT_MODEL_REQUIREMENTS } from "../../shared/model-requirements" -import { getAgentConfigKey } from "../../shared/agent-display-names" +import { getAgentConfigKey, stripInvisibleAgentCharacters } from "../../shared/agent-display-names" import { normalizeFallbackModels } from "../../shared/model-resolver" import { buildFallbackChainFromModels } from "../../shared/fallback-chain-from-models" import { log } from "../../shared" import { CONFIG_BASENAME } from "../../shared/plugin-identity" -import { parseModelString } from "../delegate-task/model-string-parser" +import { parseModelString } from "../../shared" import { executeBackground } from "./background-executor" import { executeSync } from "./sync-executor" +import { resolveCallableAgents } from "./agent-resolver" +import { createOrGetSession } from "./session-creator" +import { processMessages } from "./message-processor" +import { waitForCompletion } from "./completion-poller" + +function createSyncExecutorDeps(modelFallbackControllerAccessor?: ModelFallbackControllerAccessor) { + return { + createOrGetSession, + waitForCompletion, + processMessages, + setSessionFallbackChain: (sessionID: string, fallbackChain: FallbackEntry[] | undefined) => { + modelFallbackControllerAccessor?.setSessionFallbackChain(sessionID, fallbackChain) + }, + clearSessionFallbackChain: (sessionID: string) => { + modelFallbackControllerAccessor?.clearSessionFallbackChain(sessionID) + }, + } +} function resolveModelAndFallbackChain(args: { subagentType: string @@ -81,43 +100,63 @@ export function createCallOmoAgent( disabledAgents: string[] = [], agentOverrides?: AgentOverrides, userCategories?: CategoriesConfig, + modelFallbackControllerAccessor?: ModelFallbackControllerAccessor, ): ToolDefinition { const agentDescriptions = ALLOWED_AGENTS.map( - (name) => `- ${name}: Specialized agent for ${name} tasks` - ).join("\n") - const description = CALL_OMO_AGENT_DESCRIPTION.replace("{agents}", agentDescriptions) + (name) => `- ${name}: Specialized agent for ${name} tasks`, + ).join("\n"); + const description = CALL_OMO_AGENT_DESCRIPTION.replace( + "{agents}", + agentDescriptions, + ); return tool({ description, args: { - description: tool.schema.string().describe("A short (3-5 words) description of the task"), - prompt: tool.schema.string().describe("The task for the agent to perform"), + description: tool.schema + .string() + .describe("A short (3-5 words) description of the task"), + prompt: tool.schema + .string() + .describe("The task for the agent to perform"), subagent_type: tool.schema .string() - .describe("The type of specialized agent to use for this task (explore or librarian only)"), + .describe( + "The agent to invoke. Supports built-in agents and any custom agents registered at runtime.", + ), run_in_background: tool.schema .boolean() - .describe("REQUIRED. true: run asynchronously (use background_output to get results), false: run synchronously and wait for completion"), - session_id: tool.schema.string().describe("Existing Task session to continue").optional(), + .describe( + "REQUIRED. true: run asynchronously (use background_output to get results), false: run synchronously and wait for completion", + ), + session_id: tool.schema + .string() + .describe("Existing Task session to continue") + .optional(), }, async execute(args: CallOmoAgentArgs, toolContext) { - const toolCtx = toolContext as ToolContextWithMetadata - log(`[call_omo_agent] Starting with agent: ${args.subagent_type}, background: ${args.run_in_background}`) + const toolCtx = toolContext as ToolContextWithMetadata; + log( + `[call_omo_agent] Starting with agent: ${args.subagent_type}, background: ${args.run_in_background}`, + ); - // Case-insensitive agent validation - allows "Explore", "EXPLORE", "explore" etc. + const callableAgents = await resolveCallableAgents(ctx.client); + + // Strip ZWSP and case-insensitive agent validation - allows "Explore", "EXPLORE", "explore" etc. + const strippedAgentType = stripInvisibleAgentCharacters(args.subagent_type) if ( - !ALLOWED_AGENTS.some( - (name) => name.toLowerCase() === args.subagent_type.toLowerCase(), + !callableAgents.some( + (name) => name.toLowerCase() === strippedAgentType.toLowerCase(), ) ) { - return `Error: Invalid agent type "${args.subagent_type}". Only ${ALLOWED_AGENTS.join(", ")} are allowed.` + return `Error: Invalid agent type "${args.subagent_type}". Only ${callableAgents.join(", ")} are allowed.`; } - const normalizedAgent = args.subagent_type.toLowerCase() as AllowedAgentType - args = { ...args, subagent_type: normalizedAgent } + const normalizedAgent = strippedAgentType.toLowerCase(); + args = { ...args, subagent_type: normalizedAgent }; // Check if agent is disabled - if (disabledAgents.some((disabled) => disabled.toLowerCase() === normalizedAgent)) { + if (disabledAgents.some((disabled) => stripInvisibleAgentCharacters(disabled).toLowerCase() === normalizedAgent)) { return `Error: Agent "${normalizedAgent}" is disabled via disabled_agents configuration. Remove it from disabled_agents in your ${CONFIG_BASENAME}.json to use it.` } @@ -129,7 +168,7 @@ export function createCallOmoAgent( if (args.run_in_background) { if (args.session_id) { - return `Error: session_id is not supported in background mode. Use run_in_background=false to continue an existing session.` + return `Error: session_id is not supported in background mode. Use run_in_background=false to continue an existing session.`; } return await executeBackground(args, toolCtx, backgroundManager, ctx.client, fallbackChain, resolvedModel) } @@ -138,14 +177,30 @@ export function createCallOmoAgent( let spawnReservation: Awaited> | undefined try { spawnReservation = await backgroundManager.reserveSubagentSpawn(toolCtx.sessionID) - return await executeSync(args, toolCtx, ctx, undefined, fallbackChain, spawnReservation, resolvedModel) + return await executeSync( + args, + toolCtx, + ctx, + createSyncExecutorDeps(modelFallbackControllerAccessor), + fallbackChain, + spawnReservation, + resolvedModel, + ) } catch (error) { spawnReservation?.rollback() return `Error: ${error instanceof Error ? error.message : String(error)}` } } - return await executeSync(args, toolCtx, ctx, undefined, fallbackChain, undefined, resolvedModel) + return await executeSync( + args, + toolCtx, + ctx, + createSyncExecutorDeps(modelFallbackControllerAccessor), + fallbackChain, + undefined, + resolvedModel, + ) }, - }) + }); } diff --git a/src/tools/delegate-task/AGENTS.md b/src/tools/delegate-task/AGENTS.md index f160a7d7b..e928a37e6 100644 --- a/src/tools/delegate-task/AGENTS.md +++ b/src/tools/delegate-task/AGENTS.md @@ -32,7 +32,7 @@ sync-task.ts → sync-session-creator.ts → sync-prompt-sender.ts → sync-session-poller.ts → sync-result-fetcher.ts ``` -Each file handles one step. `sync-continuation.ts` handles session continuation (resume with session_id). +Each file handles one step. `sync-continuation.ts` handles session continuation (resume with task_id). ## BACKGROUND EXECUTION @@ -40,7 +40,7 @@ Each file handles one step. `sync-continuation.ts` handles session continuation background-task.ts → BackgroundManager.launch() → (async polling) → background-continuation.ts ``` -`background-continuation.ts` handles `session_id` resume for existing background tasks. +`background-continuation.ts` handles `task_id` resume for existing background tasks. ## CATEGORY RESOLUTION diff --git a/src/tools/delegate-task/anthropic-categories.ts b/src/tools/delegate-task/anthropic-categories.ts index e6b0894e3..cb5a88876 100644 --- a/src/tools/delegate-task/anthropic-categories.ts +++ b/src/tools/delegate-task/anthropic-categories.ts @@ -47,7 +47,7 @@ export const ANTHROPIC_CATEGORIES: BuiltinCategoryDefinition[] = [ }, { name: "unspecified-high", - config: { model: "anthropic/claude-opus-4-6", variant: "max" }, + config: { model: "anthropic/claude-opus-4-7", variant: "max" }, description: "Tasks that don't fit other categories, high effort required", promptAppend: UNSPECIFIED_HIGH_CATEGORY_PROMPT_APPEND, }, diff --git a/src/tools/delegate-task/background-continuation.test.ts b/src/tools/delegate-task/background-continuation.test.ts index f97c2d143..2b0e768c9 100644 --- a/src/tools/delegate-task/background-continuation.test.ts +++ b/src/tools/delegate-task/background-continuation.test.ts @@ -30,7 +30,7 @@ describe("executeBackgroundContinuation - subagent metadata", () => { } const args = { - session_id: "ses_resumed_123", + task_id: "ses_resumed_123", prompt: "continue working", description: "resume oracle", load_skills: [], @@ -76,7 +76,7 @@ describe("executeBackgroundContinuation - subagent metadata", () => { } const args = { - session_id: "ses_resumed_456", + task_id: "ses_resumed_456", prompt: "continue", description: "resume task", load_skills: [], diff --git a/src/tools/delegate-task/background-continuation.ts b/src/tools/delegate-task/background-continuation.ts index 0d365d964..90ea1398b 100644 --- a/src/tools/delegate-task/background-continuation.ts +++ b/src/tools/delegate-task/background-continuation.ts @@ -1,9 +1,11 @@ import type { DelegateTaskArgs, ToolContextWithMetadata } from "./types" import type { ExecutorContext, ParentContext } from "./executor-types" -import { storeToolMetadata } from "../../features/tool-metadata-store" +import { publishToolMetadata } from "../../features/tool-metadata-store" import { formatDetailedError } from "./error-formatting" import { getSessionTools } from "../../shared/session-tools-store" -import { resolveCallID } from "./resolve-call-id" +import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract" +import { resolveMetadataModel } from "./resolve-metadata-model" +import { getTaskID } from "./task-id" export async function executeBackgroundContinuation( args: DelegateTaskArgs, @@ -14,8 +16,13 @@ export async function executeBackgroundContinuation( const { manager } = executorCtx try { + const taskID = getTaskID(args) + if (!taskID) { + throw new Error("task_id is required to continue a background task") + } + const task = await manager.resume({ - sessionId: args.session_id!, + sessionId: taskID, prompt: args.prompt, parentSessionID: parentContext.sessionID, parentMessageID: parentContext.messageID, @@ -32,16 +39,14 @@ export async function executeBackgroundContinuation( load_skills: args.load_skills, description: args.description, run_in_background: args.run_in_background, + taskId: task.sessionID, + backgroundTaskId: task.id, sessionId: task.sessionID, command: args.command, - model: task.model ? { providerID: task.model.providerID, modelID: task.model.modelID } : undefined, + model: resolveMetadataModel(task.model, parentContext.model), }, } - await ctx.metadata?.(bgContMeta) - const callID = resolveCallID(ctx) - if (callID) { - storeToolMetadata(ctx.sessionID, callID, bgContMeta) - } + await publishToolMetadata(ctx, bgContMeta) return `Background task continued. @@ -55,14 +60,17 @@ System notifies on completion. Use \`background_output\` with task_id="${task.id Do NOT call background_output now. Wait for notification first. - -session_id: ${task.sessionID} -${task.agent ? `subagent: ${task.agent}\n` : ""}` +${buildTaskMetadataBlock({ + sessionId: task.sessionID, + taskId: task.sessionID, + backgroundTaskId: task.id, + agent: task.agent, + })}` } catch (error) { return formatDetailedError(error, { operation: "Continue background task", args, - sessionID: args.session_id, + sessionID: getTaskID(args), }) } } diff --git a/src/tools/delegate-task/background-task.test.ts b/src/tools/delegate-task/background-task.test.ts index 84a7bc644..3837dbb6e 100644 --- a/src/tools/delegate-task/background-task.test.ts +++ b/src/tools/delegate-task/background-task.test.ts @@ -104,11 +104,14 @@ describeFn("executeBackgroundTask output/session metadata compatibility", () => //#then - output and metadata should include canonical session linkage expectFn(result).toContain("") expectFn(result).toContain("session_id: ses_sub_123") - expectFn(result).toContain("task_id: bg_resolved") + expectFn(result).toContain("task_id: ses_sub_123") expectFn(result).toContain("background_task_id: bg_resolved") + expectFn(result).toContain("subagent: explore") expectFn(result).toContain("Background Task ID: bg_resolved") expectFn(metadataCalls).toHaveLength(1) expectFn(metadataCalls[0].metadata.sessionId).toBe("ses_sub_123") + expectFn(metadataCalls[0].metadata.taskId).toBe("ses_sub_123") + expectFn(metadataCalls[0].metadata.backgroundTaskId).toBe("bg_resolved") }) testFn("captures late-resolved session id and emits synced metadata", async () => { @@ -152,10 +155,12 @@ describeFn("executeBackgroundTask output/session metadata compatibility", () => //#then - late session id still propagates to task metadata contract expectFn(result).toContain("session_id: ses_late_123") - expectFn(result).toContain("task_id: bg_late") + expectFn(result).toContain("task_id: ses_late_123") expectFn(result).toContain("background_task_id: bg_late") expectFn(metadataCalls).toHaveLength(1) expectFn(metadataCalls[0].metadata.sessionId).toBe("ses_late_123") + expectFn(metadataCalls[0].metadata.taskId).toBe("ses_late_123") + expectFn(metadataCalls[0].metadata.backgroundTaskId).toBe("bg_late") }) testFn("passes question-deny session permission when launching delegate task", async () => { diff --git a/src/tools/delegate-task/background-task.ts b/src/tools/delegate-task/background-task.ts index 184325ec9..d5c4adf5d 100644 --- a/src/tools/delegate-task/background-task.ts +++ b/src/tools/delegate-task/background-task.ts @@ -3,14 +3,14 @@ import type { ExecutorContext, ParentContext } from "./executor-types" import type { FallbackEntry } from "../../shared/model-requirements" import { getTimingConfig } from "./timing" import { buildTaskPrompt } from "./prompt-builder" -import { storeToolMetadata } from "../../features/tool-metadata-store" -import { resolveCallID } from "./resolve-call-id" +import { publishToolMetadata } from "../../features/tool-metadata-store" import { formatDetailedError } from "./error-formatting" import { getSessionTools } from "../../shared/session-tools-store" import { SessionCategoryRegistry } from "../../shared/session-category-registry" import { QUESTION_DENIED_SESSION_PERMISSION } from "../../shared/question-denied-session-permission" -import { setSessionFallbackChain } from "../../hooks/model-fallback/hook" import { stripAgentListSortPrefix } from "../../shared/agent-display-names" +import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract" +import { resolveMetadataModel } from "./resolve-metadata-model" function continueSessionSetup(args: { taskID: string @@ -18,6 +18,7 @@ function continueSessionSetup(args: { timing: ReturnType fallbackChain?: FallbackEntry[] category?: string + modelFallbackControllerAccessor?: ExecutorContext["modelFallbackControllerAccessor"] }): void { if (!args.fallbackChain && !args.category) { return @@ -40,7 +41,7 @@ function continueSessionSetup(args: { continue } - setSessionFallbackChain(sessionId, args.fallbackChain) + args.modelFallbackControllerAccessor?.setSessionFallbackChain(sessionId, args.fallbackChain) if (args.category) { SessionCategoryRegistry.register(sessionId, args.category) } @@ -105,6 +106,7 @@ export async function executeBackgroundTask( timing, fallbackChain, category: args.category, + modelFallbackControllerAccessor: executorCtx.modelFallbackControllerAccessor, }) break } @@ -112,12 +114,13 @@ export async function executeBackgroundTask( } if (sessionId) { - setSessionFallbackChain(sessionId, fallbackChain) + executorCtx.modelFallbackControllerAccessor?.setSessionFallbackChain(sessionId, fallbackChain) } if (args.category && sessionId) { SessionCategoryRegistry.register(sessionId, args.category) } + const resolvedModel = resolveMetadataModel(categoryModel, parentContext.model) const metadata = { prompt: args.prompt, agent: task.agent, @@ -126,22 +129,26 @@ export async function executeBackgroundTask( description: args.description, run_in_background: args.run_in_background, command: args.command, + ...(sessionId ? { taskId: sessionId } : {}), + backgroundTaskId: task.id, ...(sessionId ? { sessionId } : {}), - ...(categoryModel ? { model: { providerID: categoryModel.providerID, modelID: categoryModel.modelID } } : {}), + ...(resolvedModel ? { model: resolvedModel } : {}), } const unstableMeta = { title: args.description, metadata, } - await ctx.metadata?.(unstableMeta) - const callID = resolveCallID(ctx) - if (callID) { - storeToolMetadata(ctx.sessionID, callID, unstableMeta) - } + await publishToolMetadata(ctx, unstableMeta) const taskMetadataBlock = sessionId - ? `\n\n\nsession_id: ${sessionId}\ntask_id: ${task.id}\nbackground_task_id: ${task.id}\n` + ? `\n\n${buildTaskMetadataBlock({ + sessionId, + taskId: sessionId, + backgroundTaskId: task.id, + agent: task.agent, + category: args.category, + })}` : "" return `Background task launched. diff --git a/src/tools/delegate-task/category-resolver.test.ts b/src/tools/delegate-task/category-resolver.test.ts index 4a52f4158..ffe59d705 100644 --- a/src/tools/delegate-task/category-resolver.test.ts +++ b/src/tools/delegate-task/category-resolver.test.ts @@ -99,7 +99,7 @@ describe("resolveCategoryExecution", () => { const executorCtx = createMockExecutorContext() executorCtx.userCategories = { deep: { - model: "quotio/claude-opus-4-6", + model: "quotio/claude-opus-4-7", fallback_models: ["quotio/kimi-k2.5", "openai/gpt-5.2(high)"], }, } diff --git a/src/tools/delegate-task/category-resolver.ts b/src/tools/delegate-task/category-resolver.ts index bfc4d1896..25f4e8a37 100644 --- a/src/tools/delegate-task/category-resolver.ts +++ b/src/tools/delegate-task/category-resolver.ts @@ -5,7 +5,7 @@ import type { FallbackEntry } from "../../shared/model-requirements" import { mergeCategories } from "../../shared/merge-categories" import { SISYPHUS_JUNIOR_AGENT } from "./sisyphus-junior-agent" import { resolveCategoryConfig } from "./categories" -import { parseModelString } from "./model-string-parser" +import { parseModelString } from "../../shared/model-string-parser" import { CATEGORY_MODEL_REQUIREMENTS } from "../../shared/model-requirements" import { normalizeFallbackModels, flattenToFallbackModelStrings } from "../../shared/model-resolver" import { buildFallbackChainFromModels, findMostSpecificFallbackEntry } from "../../shared/fallback-chain-from-models" diff --git a/src/tools/delegate-task/delegated-model-config.ts b/src/tools/delegate-task/delegated-model-config.ts new file mode 100644 index 000000000..dadf8321c --- /dev/null +++ b/src/tools/delegate-task/delegated-model-config.ts @@ -0,0 +1,20 @@ +import type { CategoryConfig } from "../../config/schema" +import type { DelegatedModelConfig } from "./types" + +export function applyCategoryParams( + base: DelegatedModelConfig, + config: CategoryConfig | undefined, +): DelegatedModelConfig { + if (!config) { + return base + } + + return { + ...base, + ...(config.reasoningEffort !== undefined ? { reasoningEffort: config.reasoningEffort } : {}), + ...(config.temperature !== undefined ? { temperature: config.temperature } : {}), + ...(config.top_p !== undefined ? { top_p: config.top_p } : {}), + ...(config.maxTokens !== undefined ? { maxTokens: config.maxTokens } : {}), + ...(config.thinking !== undefined ? { thinking: config.thinking } : {}), + } +} diff --git a/src/tools/delegate-task/error-formatting.ts b/src/tools/delegate-task/error-formatting.ts index f2c24abc5..9b7b98ab2 100644 --- a/src/tools/delegate-task/error-formatting.ts +++ b/src/tools/delegate-task/error-formatting.ts @@ -1,4 +1,5 @@ import type { DelegateTaskArgs } from "./types" +import { getTaskID } from "./task-id" /** * Context for error formatting. @@ -35,8 +36,9 @@ export function formatDetailedError(error: unknown, ctx: ErrorContext): string { lines.push(`- subagent_type: ${ctx.args.subagent_type ?? "(none)"}`) lines.push(`- run_in_background: ${ctx.args.run_in_background}`) lines.push(`- load_skills: [${ctx.args.load_skills?.join(", ") ?? ""}]`) - if (ctx.args.session_id) { - lines.push(`- session_id: ${ctx.args.session_id}`) + const taskID = getTaskID(ctx.args) + if (taskID) { + lines.push(`- task_id: ${taskID}`) } } diff --git a/src/tools/delegate-task/executor-types.ts b/src/tools/delegate-task/executor-types.ts index bfa7fc70b..8b430c9ce 100644 --- a/src/tools/delegate-task/executor-types.ts +++ b/src/tools/delegate-task/executor-types.ts @@ -1,5 +1,6 @@ import type { BackgroundManager } from "../../features/background-agent" import type { CategoriesConfig, GitMasterConfig, BrowserAutomationProvider, AgentOverrides, SisyphusAgentConfig } from "../../config/schema" +import type { ModelFallbackControllerAccessor } from "../../hooks/model-fallback" import type { OpencodeClient } from "./types" export interface ExecutorContext { @@ -12,6 +13,7 @@ export interface ExecutorContext { browserProvider?: BrowserAutomationProvider agentOverrides?: AgentOverrides sisyphusAgentConfig?: SisyphusAgentConfig + modelFallbackControllerAccessor?: ModelFallbackControllerAccessor onSyncSessionCreated?: (event: { sessionID: string; parentID: string; title: string }) => Promise syncPollTimeoutMs?: number } diff --git a/src/tools/delegate-task/fallback-entry-resolution.ts b/src/tools/delegate-task/fallback-entry-resolution.ts new file mode 100644 index 000000000..4603e3651 --- /dev/null +++ b/src/tools/delegate-task/fallback-entry-resolution.ts @@ -0,0 +1,27 @@ +import { findMostSpecificFallbackEntry } from "../../shared/fallback-chain-from-models" +import type { FallbackEntry } from "../../shared/model-requirements" +import type { DelegatedModelConfig } from "./types" + +export function resolveEffectiveFallbackEntry(input: { + categoryModel: DelegatedModelConfig | undefined + configuredFallbackChain: FallbackEntry[] | undefined + resolution: + | { skipped: true } + | { fallbackEntry?: FallbackEntry; matchedFallback?: boolean } + | undefined +}): FallbackEntry | undefined { + const { categoryModel, configuredFallbackChain, resolution } = input + + const resolutionSkipped = resolution && "skipped" in resolution + const resolvedFallbackEntry = resolution && !resolutionSkipped ? resolution.fallbackEntry : undefined + const matchedFallback = resolution && !resolutionSkipped ? resolution.matchedFallback === true : false + + if (!matchedFallback || !categoryModel) { + return undefined + } + + return resolvedFallbackEntry + ?? (configuredFallbackChain + ? findMostSpecificFallbackEntry(categoryModel.providerID, categoryModel.modelID, configuredFallbackChain) + : undefined) +} diff --git a/src/tools/delegate-task/fallback-entry-settings.ts b/src/tools/delegate-task/fallback-entry-settings.ts new file mode 100644 index 000000000..101d9b6e3 --- /dev/null +++ b/src/tools/delegate-task/fallback-entry-settings.ts @@ -0,0 +1,20 @@ +import type { DelegatedModelConfig } from "./types" +import type { FallbackEntry } from "../../shared/model-requirements" + +export function applyFallbackEntrySettings(input: { + categoryModel: DelegatedModelConfig + effectiveEntry: FallbackEntry + variantOverride?: string +}): DelegatedModelConfig { + const { categoryModel, effectiveEntry, variantOverride } = input + + return { + ...categoryModel, + variant: variantOverride ?? effectiveEntry.variant ?? categoryModel.variant, + reasoningEffort: effectiveEntry.reasoningEffort ?? categoryModel.reasoningEffort, + temperature: effectiveEntry.temperature ?? categoryModel.temperature, + top_p: effectiveEntry.top_p ?? categoryModel.top_p, + maxTokens: effectiveEntry.maxTokens ?? categoryModel.maxTokens, + thinking: effectiveEntry.thinking ?? categoryModel.thinking, + } +} diff --git a/src/tools/delegate-task/metadata-model-unification.test.ts b/src/tools/delegate-task/metadata-model-unification.test.ts index 2cb67a8d2..799b9537e 100644 --- a/src/tools/delegate-task/metadata-model-unification.test.ts +++ b/src/tools/delegate-task/metadata-model-unification.test.ts @@ -122,7 +122,7 @@ describe("metadata model unification", () => { const ctx = makeMockCtx() const args: DelegateTaskArgs = { description: "continue", prompt: "keep going", - load_skills: [], run_in_background: true, session_id: "ses_resumed", + load_skills: [], run_in_background: true, task_id: "ses_resumed", } await executeBackgroundContinuation(args, ctx, { @@ -144,7 +144,7 @@ describe("metadata model unification", () => { const ctx = makeMockCtx() const args: DelegateTaskArgs = { description: "continue", prompt: "keep going", - load_skills: [], run_in_background: false, session_id: "ses_cont", + load_skills: [], run_in_background: false, task_id: "ses_cont", } const deps = { @@ -161,7 +161,7 @@ describe("metadata model unification", () => { prompt: async () => ({}), }, }, - } as any, deps) + } as any, parentContext, deps) const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) expect(meta).toBeDefined() @@ -169,4 +169,179 @@ describe("metadata model unification", () => { }) }) }) + + describe("#given categoryModel is undefined but parent.model is set", () => { + describe("#when executors publish metadata", () => { + test("#then sync-task metadata falls back to parent.model", async () => { + const { executeSyncTask } = require("./sync-task") + const ctx = makeMockCtx() + const deps = { + createSyncSession: async () => ({ ok: true, sessionID: "ses_sync" }), + sendSyncPrompt: async () => null, + pollSyncSession: async () => null, + fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }), + } + const args: DelegateTaskArgs = { + description: "test", prompt: "do it", + subagent_type: "explore", load_skills: [], run_in_background: false, + } + + await executeSyncTask(args, ctx, { + client: { session: { create: async () => ({ data: { id: "ses_sync" } }) } }, + directory: "/tmp", + onSyncSessionCreated: null, + }, parentContext, "explore", undefined, undefined, undefined, undefined, deps) + + const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) + expect(meta).toBeDefined() + expect(meta.metadata.model).toEqual(MODEL) + }) + + test("#then background-task metadata falls back to parent.model", async () => { + const { executeBackgroundTask } = require("./background-task") + const ctx = makeMockCtx() + const args: DelegateTaskArgs = { + description: "test", prompt: "do it", + load_skills: [], run_in_background: true, subagent_type: "explore", + } + + await executeBackgroundTask(args, ctx, { + manager: { + launch: async () => ({ + id: "bg_1", description: "test", agent: "explore", + status: "pending", sessionID: "ses_bg", + }), + getTask: () => undefined, + }, + } as any, parentContext, "explore", undefined, undefined) + + const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) + expect(meta).toBeDefined() + expect(meta.metadata.model).toEqual(MODEL) + }) + + test("#then unstable-agent-task metadata falls back to parent.model", async () => { + const { executeUnstableAgentTask } = require("./unstable-agent-task") + const ctx = makeMockCtx() + const args: DelegateTaskArgs = { + description: "test", prompt: "do it", + category: "quick", load_skills: [], run_in_background: false, + } + + const launchedTask = { + id: "bg_unstable", description: "test", agent: "explore", + status: "completed", sessionID: "ses_unstable", + } + + await executeUnstableAgentTask( + args, ctx, + { + manager: { + launch: async () => launchedTask, + getTask: () => launchedTask, + }, + client: { + session: { + status: async () => ({ data: { ses_unstable: { type: "idle" } } }), + messages: async () => ({ + data: [{ + info: { role: "assistant", time: { created: 1 } }, + parts: [{ type: "text", text: "done" }], + }], + }), + }, + }, + syncPollTimeoutMs: 100, + } as any, + parentContext, "explore", undefined, undefined, "anthropic/claude-sonnet-4-6", + ) + + const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) + expect(meta).toBeDefined() + expect(meta.metadata.model).toEqual(MODEL) + }) + + test("#then background-continuation metadata falls back to parent.model when task.model missing", async () => { + const { executeBackgroundContinuation } = require("./background-continuation") + const ctx = makeMockCtx() + const args: DelegateTaskArgs = { + description: "continue", prompt: "keep going", + load_skills: [], run_in_background: true, task_id: "ses_resumed", + } + + await executeBackgroundContinuation(args, ctx, { + manager: { + resume: async () => ({ + id: "bg_2", description: "continue", agent: "explore", + status: "running", sessionID: "ses_resumed", + }), + }, + } as any, parentContext) + + const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) + expect(meta).toBeDefined() + expect(meta.metadata.model).toEqual(MODEL) + }) + + test("#then sync-continuation metadata falls back to parent.model when resume model missing", async () => { + const { executeSyncContinuation } = require("./sync-continuation") + const ctx = makeMockCtx() + const args: DelegateTaskArgs = { + description: "continue", prompt: "keep going", + load_skills: [], run_in_background: false, task_id: "ses_cont", + } + + const deps = { + pollSyncSession: async () => null, + fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }), + } + + await executeSyncContinuation(args, ctx, { + client: { + session: { + messages: async () => ({ data: [] }), + prompt: async () => ({}), + }, + }, + } as any, parentContext, deps) + + const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) + expect(meta).toBeDefined() + expect(meta.metadata.model).toEqual(MODEL) + }) + }) + }) + + describe("#given both categoryModel and parent.model are undefined", () => { + test("#when sync-task runs #then metadata.model is undefined without crashing", async () => { + const { executeSyncTask } = require("./sync-task") + const ctx = makeMockCtx() + const deps = { + createSyncSession: async () => ({ ok: true, sessionID: "ses_sync" }), + sendSyncPrompt: async () => null, + pollSyncSession: async () => null, + fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }), + } + const args: DelegateTaskArgs = { + description: "test", prompt: "do it", + subagent_type: "explore", load_skills: [], run_in_background: false, + } + + const parentContextWithoutModel: ParentContext = { + sessionID: "ses_parent", + messageID: "msg_parent", + agent: "sisyphus", + } + + await executeSyncTask(args, ctx, { + client: { session: { create: async () => ({ data: { id: "ses_sync" } }) } }, + directory: "/tmp", + onSyncSessionCreated: null, + }, parentContextWithoutModel, "explore", undefined, undefined, undefined, undefined, deps) + + const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) + expect(meta).toBeDefined() + expect(meta.metadata.model).toBeUndefined() + }) + }) }) diff --git a/src/tools/delegate-task/metadata-task-id-consistency.test.ts b/src/tools/delegate-task/metadata-task-id-consistency.test.ts new file mode 100644 index 000000000..c9a2a0b8c --- /dev/null +++ b/src/tools/delegate-task/metadata-task-id-consistency.test.ts @@ -0,0 +1,218 @@ +const { describe, test, expect } = require("bun:test") + +import type { DelegateTaskArgs, ToolContextWithMetadata } from "./types" +import type { ParentContext } from "./executor-types" + +const MODEL = { providerID: "anthropic", modelID: "claude-sonnet-4-6" } + +function makeMockCtx(): ToolContextWithMetadata & { captured: any[] } { + const captured: any[] = [] + return { + sessionID: "ses_parent", + messageID: "msg_parent", + agent: "sisyphus", + abort: new AbortController().signal, + callID: "call_001", + metadata: async (input: any) => { captured.push(input) }, + captured, + } +} + +const parentContext: ParentContext = { + sessionID: "ses_parent", + messageID: "msg_parent", + agent: "sisyphus", + model: MODEL, +} + +describe("taskId and backgroundTaskId metadata consistency", () => { + describe("#given sync-task runs", () => { + test("#when publishing metadata #then taskId equals sessionId", async () => { + const { executeSyncTask } = require("./sync-task") + const ctx = makeMockCtx() + const deps = { + createSyncSession: async () => ({ ok: true, sessionID: "ses_sync" }), + sendSyncPrompt: async () => null, + pollSyncSession: async () => null, + fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }), + } + const args: DelegateTaskArgs = { + description: "test", prompt: "do it", + category: "quick", load_skills: [], run_in_background: false, + } + + await executeSyncTask(args, ctx, { + client: { session: { create: async () => ({ data: { id: "ses_sync" } }) } }, + directory: "/tmp", + onSyncSessionCreated: null, + }, parentContext, "explore", MODEL, undefined, undefined, undefined, deps) + + const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) + expect(meta).toBeDefined() + expect(meta.metadata.taskId).toBe("ses_sync") + expect(meta.metadata.sessionId).toBe("ses_sync") + expect(meta.metadata.taskId).toBe(meta.metadata.sessionId) + }) + }) + + describe("#given background-task runs", () => { + test("#when publishing metadata #then taskId is sessionID and backgroundTaskId is task.id", async () => { + const { executeBackgroundTask } = require("./background-task") + const ctx = makeMockCtx() + const args: DelegateTaskArgs = { + description: "test", prompt: "do it", + load_skills: [], run_in_background: true, subagent_type: "explore", + } + + await executeBackgroundTask(args, ctx, { + manager: { + launch: async () => ({ + id: "bg_abc123", description: "test", agent: "explore", + status: "pending", sessionID: "ses_xyz789", + }), + getTask: () => undefined, + }, + } as any, parentContext, "explore", MODEL, undefined) + + const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) + expect(meta).toBeDefined() + expect(meta.metadata.taskId).toBe("ses_xyz789") + expect(meta.metadata.sessionId).toBe("ses_xyz789") + expect(meta.metadata.backgroundTaskId).toBe("bg_abc123") + }) + }) + + describe("#given unstable-agent-task runs", () => { + test("#when publishing metadata #then taskId and backgroundTaskId are both included", async () => { + const { executeUnstableAgentTask } = require("./unstable-agent-task") + const ctx = makeMockCtx() + const args: DelegateTaskArgs = { + description: "test", prompt: "do it", + category: "quick", load_skills: [], run_in_background: false, + } + + const launchedTask = { + id: "bg_unstable_abc", description: "test", agent: "explore", + status: "completed", sessionID: "ses_unstable_xyz", + } + + await executeUnstableAgentTask( + args, ctx, + { + manager: { + launch: async () => launchedTask, + getTask: () => launchedTask, + }, + client: { + session: { + status: async () => ({ data: { ses_unstable_xyz: { type: "idle" } } }), + messages: async () => ({ + data: [{ + info: { role: "assistant", time: { created: 1 } }, + parts: [{ type: "text", text: "done" }], + }], + }), + }, + }, + syncPollTimeoutMs: 100, + } as any, + parentContext, "explore", MODEL, undefined, "anthropic/claude-sonnet-4-6", + ) + + const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) + expect(meta).toBeDefined() + expect(meta.metadata.taskId).toBe("ses_unstable_xyz") + expect(meta.metadata.sessionId).toBe("ses_unstable_xyz") + expect(meta.metadata.backgroundTaskId).toBe("bg_unstable_abc") + }) + }) + + describe("#given background-continuation runs", () => { + test("#when publishing metadata #then taskId is sessionID and backgroundTaskId is bg.id", async () => { + const { executeBackgroundContinuation } = require("./background-continuation") + const ctx = makeMockCtx() + const args: DelegateTaskArgs = { + description: "continue", prompt: "keep going", + load_skills: [], run_in_background: true, task_id: "ses_resumed_x", + } + + await executeBackgroundContinuation(args, ctx, { + manager: { + resume: async () => ({ + id: "bg_resumed_y", description: "continue", agent: "explore", + status: "running", sessionID: "ses_resumed_x", model: MODEL, + }), + }, + } as any, parentContext) + + const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) + expect(meta).toBeDefined() + expect(meta.metadata.taskId).toBe("ses_resumed_x") + expect(meta.metadata.sessionId).toBe("ses_resumed_x") + expect(meta.metadata.backgroundTaskId).toBe("bg_resumed_y") + }) + }) + + describe("#given sync-continuation runs", () => { + test("#when publishing metadata #then taskId is sessionID", async () => { + const { executeSyncContinuation } = require("./sync-continuation") + const ctx = makeMockCtx() + const args: DelegateTaskArgs = { + description: "continue", prompt: "keep going", + load_skills: [], run_in_background: false, task_id: "ses_cont_abc", + } + + const deps = { + pollSyncSession: async () => null, + fetchSyncResult: async () => ({ ok: true as const, textContent: "done" }), + } + + await executeSyncContinuation(args, ctx, { + client: { + session: { + messages: async () => ({ + data: [{ info: { agent: "explore", model: MODEL } }], + }), + prompt: async () => ({}), + }, + }, + } as any, parentContext, deps) + + const meta = ctx.captured.find((m: any) => m.metadata?.sessionId) + expect(meta).toBeDefined() + expect(meta.metadata.taskId).toBe("ses_cont_abc") + expect(meta.metadata.sessionId).toBe("ses_cont_abc") + }) + }) + + describe("#given background_output runs", () => { + test("#when publishing metadata #then backgroundTaskId is task.id not task_id", async () => { + const { createBackgroundOutput } = require("../background-task/create-background-output") + const ctx = makeMockCtx() + const manager = { + getTask: (id: string) => ({ + id, + sessionID: "ses_bg_session", + agent: "explore", + category: "deep", + description: "test", + status: "completed" as const, + }), + } + const client = { + session: { + messages: async () => ({ data: [] }), + }, + } + + const bgOutput = createBackgroundOutput(manager as any, client as any) + await bgOutput.execute({ task_id: "bg_output_xyz" } as any, ctx as any) + + const meta = ctx.captured.find((m: any) => m.metadata?.backgroundTaskId) + expect(meta).toBeDefined() + expect(meta.metadata.backgroundTaskId).toBe("bg_output_xyz") + expect(meta.metadata.sessionId).toBe("ses_bg_session") + expect(meta.metadata.taskId).toBe("ses_bg_session") + }) + }) +}) diff --git a/src/tools/delegate-task/model-selection.ts b/src/tools/delegate-task/model-selection.ts index cef7df752..43fa4741b 100644 --- a/src/tools/delegate-task/model-selection.ts +++ b/src/tools/delegate-task/model-selection.ts @@ -4,7 +4,7 @@ import { fuzzyMatchModel } from "../../shared/model-availability" import { transformModelForProvider } from "../../shared/provider-model-id-transform" import { hasConnectedProvidersCache, hasProviderModelsCache, readConnectedProvidersCache } from "../../shared/connected-providers-cache" import { log } from "../../shared/logger" -import { parseModelString, parseVariantFromModelID } from "./model-string-parser" +import { parseModelString, parseVariantFromModelID } from "../../shared/model-string-parser" function isExplicitHighModel(model: string): boolean { return /(?:^|\/)[^/]+-high$/.test(model) diff --git a/src/tools/delegate-task/prompt-builder.test.ts b/src/tools/delegate-task/prompt-builder.test.ts index 9c31fdefc..b728e9d70 100644 --- a/src/tools/delegate-task/prompt-builder.test.ts +++ b/src/tools/delegate-task/prompt-builder.test.ts @@ -46,7 +46,7 @@ describe("prompt-builder", () => { test("#when agent is explore #then system content includes available_skills section", () => { // given const availableSkills: AvailableSkill[] = [ - { name: "code-review", description: "Review code quality", location: "project" }, + { name: "review-work", description: "Review code quality", location: "project" }, ] // when @@ -57,7 +57,7 @@ describe("prompt-builder", () => { // then expect(result).toBeDefined() - expect(result).toContain("code-review") + expect(result).toContain("review-work") }) test("#when availableSkills is empty #then system content does not include available_skills section", () => { diff --git a/src/tools/delegate-task/resolve-call-id.test.ts b/src/tools/delegate-task/resolve-call-id.test.ts deleted file mode 100644 index 7b4da140e..000000000 --- a/src/tools/delegate-task/resolve-call-id.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { describe, test, expect } from "bun:test" -import { resolveCallID } from "./resolve-call-id" -import type { ToolContextWithMetadata } from "./types" - -describe("resolveCallID", () => { - function makeCtx(overrides: Partial = {}): ToolContextWithMetadata { - return { - sessionID: "ses_test", - messageID: "msg_test", - agent: "sisyphus", - abort: new AbortController().signal, - ...overrides, - } - } - - test("#given callID is set #then returns callID", () => { - const ctx = makeCtx({ callID: "call_abc" }) - expect(resolveCallID(ctx)).toBe("call_abc") - }) - - test("#given only callId is set #then returns callId", () => { - const ctx = makeCtx({ callId: "call_def" }) - expect(resolveCallID(ctx)).toBe("call_def") - }) - - test("#given only call_id is set #then returns call_id", () => { - const ctx = makeCtx({ call_id: "call_ghi" }) - expect(resolveCallID(ctx)).toBe("call_ghi") - }) - - test("#given callID and callId are both set #then prefers callID", () => { - const ctx = makeCtx({ callID: "preferred", callId: "fallback" }) - expect(resolveCallID(ctx)).toBe("preferred") - }) - - test("#given no call ID variants are set #then returns undefined", () => { - const ctx = makeCtx() - expect(resolveCallID(ctx)).toBeUndefined() - }) -}) diff --git a/src/tools/delegate-task/resolve-call-id.ts b/src/tools/delegate-task/resolve-call-id.ts deleted file mode 100644 index cfa3b747e..000000000 --- a/src/tools/delegate-task/resolve-call-id.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { ToolContextWithMetadata } from "./types" - -export function resolveCallID(ctx: ToolContextWithMetadata): string | undefined { - return ctx.callID ?? ctx.callId ?? ctx.call_id -} diff --git a/src/tools/delegate-task/resolve-metadata-model.test.ts b/src/tools/delegate-task/resolve-metadata-model.test.ts new file mode 100644 index 000000000..50b29f253 --- /dev/null +++ b/src/tools/delegate-task/resolve-metadata-model.test.ts @@ -0,0 +1,50 @@ +const { describe, test, expect } = require("bun:test") + +import { resolveMetadataModel } from "./resolve-metadata-model" + +const PRIMARY = { providerID: "openai", modelID: "gpt-5.4" } +const FALLBACK = { providerID: "anthropic", modelID: "claude-sonnet-4-6" } + +describe("resolveMetadataModel", () => { + describe("#given primary and fallback are both present", () => { + test("#when resolving #then returns primary", () => { + const result = resolveMetadataModel(PRIMARY, FALLBACK) + + expect(result).toEqual(PRIMARY) + }) + }) + + describe("#given only fallback is present", () => { + test("#when resolving #then returns fallback", () => { + const result = resolveMetadataModel(undefined, FALLBACK) + + expect(result).toEqual(FALLBACK) + }) + }) + + describe("#given only primary is present", () => { + test("#when resolving #then returns primary", () => { + const result = resolveMetadataModel(PRIMARY, undefined) + + expect(result).toEqual(PRIMARY) + }) + }) + + describe("#given both are undefined", () => { + test("#when resolving #then returns undefined", () => { + const result = resolveMetadataModel(undefined, undefined) + + expect(result).toBeUndefined() + }) + }) + + describe("#given primary has extra fields", () => { + test("#when resolving #then strips to providerID and modelID only", () => { + const extended = { providerID: "openai", modelID: "gpt-5.4", variant: "high", temperature: 0.7 } as const + + const result = resolveMetadataModel(extended, undefined) + + expect(result).toEqual({ providerID: "openai", modelID: "gpt-5.4" }) + }) + }) +}) diff --git a/src/tools/delegate-task/resolve-metadata-model.ts b/src/tools/delegate-task/resolve-metadata-model.ts new file mode 100644 index 000000000..3c68ed3ad --- /dev/null +++ b/src/tools/delegate-task/resolve-metadata-model.ts @@ -0,0 +1,21 @@ +import type { DelegatedModelConfig } from "./types" + +export interface MetadataModel { + providerID: string + modelID: string +} + +type ModelLike = Pick | MetadataModel + +export function resolveMetadataModel( + primary: ModelLike | undefined, + fallback: ModelLike | undefined, +): MetadataModel | undefined { + if (primary) { + return { providerID: primary.providerID, modelID: primary.modelID } + } + if (fallback) { + return { providerID: fallback.providerID, modelID: fallback.modelID } + } + return undefined +} diff --git a/src/tools/delegate-task/subagent-discovery.ts b/src/tools/delegate-task/subagent-discovery.ts new file mode 100644 index 000000000..340ef7c22 --- /dev/null +++ b/src/tools/delegate-task/subagent-discovery.ts @@ -0,0 +1,85 @@ +import { getAgentConfigKey, getAgentDisplayName, stripAgentListSortPrefix } from "../../shared/agent-display-names" +import { loadUserAgents, loadProjectAgents } from "../../features/claude-code-agent-loader" + +export type AgentMode = "subagent" | "primary" | "all" | undefined + +export type AgentInfo = { + name: string + mode?: "subagent" | "primary" | "all" + model?: string | { providerID: string; modelID: string } +} + +export function sanitizeSubagentType(subagentType: string): string { + return subagentType.trim().replace(/^[\\\/"']+|[\\\/"']+$/g, "").trim() +} + +export function mergeWithClaudeCodeAgents( + serverAgents: AgentInfo[], + directory: string | undefined, +): AgentInfo[] { + const userAgentsRecord = loadUserAgents() + const projectAgentsRecord = loadProjectAgents(directory) + + const toAgentInfoList = (record: Record): AgentInfo[] => + Object.entries(record).map(([name, config]) => ({ + name, + mode: config.mode as AgentInfo["mode"], + model: config.model, + })) + + const mergedAgentMap = new Map() + const addIfAbsent = (agent: AgentInfo): void => { + const key = agent.name.toLowerCase() + if (!mergedAgentMap.has(key)) { + mergedAgentMap.set(key, agent) + } + } + + for (const agent of serverAgents) addIfAbsent(agent) + for (const agent of toAgentInfoList(projectAgentsRecord)) addIfAbsent(agent) + for (const agent of toAgentInfoList(userAgentsRecord)) addIfAbsent(agent) + + return Array.from(mergedAgentMap.values()) +} + +function buildComparableNames(agentName: string): Set { + return new Set([ + agentName, + getAgentDisplayName(agentName), + getAgentConfigKey(agentName), + ].map(name => stripAgentListSortPrefix(name).trim().toLowerCase())) +} + +function matchesRequestedAgent(agent: AgentInfo, requestedAgentName: string): boolean { + const comparableNames = buildComparableNames(requestedAgentName) + const listedAgentName = stripAgentListSortPrefix(agent.name).trim().toLowerCase() + const listedAgentConfigKey = getAgentConfigKey(agent.name).trim().toLowerCase() + + return comparableNames.has(listedAgentName) || comparableNames.has(listedAgentConfigKey) +} + +export function isTaskCallableAgentMode(mode: AgentMode): boolean { + return mode === "all" || mode === "subagent" +} + +export function findPrimaryAgentMatch( + agents: AgentInfo[], + requestedAgentName: string, +): AgentInfo | undefined { + return agents.find(agent => agent.mode === "primary" && matchesRequestedAgent(agent, requestedAgentName)) +} + +export function findCallableAgentMatch( + agents: AgentInfo[], + requestedAgentName: string, +): AgentInfo | undefined { + return agents.find(agent => isTaskCallableAgentMode(agent.mode) && matchesRequestedAgent(agent, requestedAgentName)) +} + +export function listCallableAgentNames(agents: AgentInfo[]): string { + return agents + .filter(agent => isTaskCallableAgentMode(agent.mode)) + .map(agent => stripAgentListSortPrefix(agent.name)) + .sort() + .join(", ") +} diff --git a/src/tools/delegate-task/subagent-resolver.ts b/src/tools/delegate-task/subagent-resolver.ts index 41eaae1f7..1d67f24b4 100644 --- a/src/tools/delegate-task/subagent-resolver.ts +++ b/src/tools/delegate-task/subagent-resolver.ts @@ -3,83 +3,28 @@ import type { ExecutorContext } from "./executor-types" import type { DelegatedModelConfig } from "./types" import { isPlanFamily } from "./constants" import { SISYPHUS_JUNIOR_AGENT } from "./sisyphus-junior-agent" +import { applyCategoryParams } from "./delegated-model-config" +import { resolveEffectiveFallbackEntry } from "./fallback-entry-resolution" +import { applyFallbackEntrySettings } from "./fallback-entry-settings" +import { + type AgentInfo, + sanitizeSubagentType, + mergeWithClaudeCodeAgents, + findPrimaryAgentMatch, + findCallableAgentMatch, + listCallableAgentNames, +} from "./subagent-discovery" import { normalizeModelFormat } from "../../shared/model-format-normalizer" import { AGENT_MODEL_REQUIREMENTS } from "../../shared/model-requirements" import { normalizeFallbackModels, flattenToFallbackModelStrings } from "../../shared/model-resolver" -import { buildFallbackChainFromModels, findMostSpecificFallbackEntry } from "../../shared/fallback-chain-from-models" -import { getAgentDisplayName, getAgentConfigKey, stripAgentListSortPrefix } from "../../shared/agent-display-names" +import { buildFallbackChainFromModels } from "../../shared/fallback-chain-from-models" +import { getAgentConfigKey, stripAgentListSortPrefix } from "../../shared/agent-display-names" import { normalizeSDKResponse } from "../../shared" import { log } from "../../shared/logger" import { getAvailableModelsForDelegateTask } from "./available-models" import type { FallbackEntry } from "../../shared/model-requirements" import { resolveModelForDelegateTask } from "./model-selection" import { fuzzyMatchModel } from "../../shared/model-availability" -import type { CategoryConfig } from "../../config/schema" -import { loadUserAgents, loadProjectAgents } from "../../features/claude-code-agent-loader" - -type AgentMode = "subagent" | "primary" | "all" | undefined - -type AgentInfo = { - name: string - mode?: "subagent" | "primary" | "all" - model?: string | { providerID: string; modelID: string } -} - -function applyCategoryParams( - base: DelegatedModelConfig, - config: CategoryConfig | undefined, -): DelegatedModelConfig { - if (!config) { - return base - } - - return { - ...base, - ...(config.reasoningEffort !== undefined ? { reasoningEffort: config.reasoningEffort } : {}), - ...(config.temperature !== undefined ? { temperature: config.temperature } : {}), - ...(config.top_p !== undefined ? { top_p: config.top_p } : {}), - ...(config.maxTokens !== undefined ? { maxTokens: config.maxTokens } : {}), - ...(config.thinking !== undefined ? { thinking: config.thinking } : {}), - } -} - -function mergeWithClaudeCodeAgents( - serverAgents: AgentInfo[], - directory: string | undefined, -): AgentInfo[] { - const userAgentsRecord = loadUserAgents() - const projectAgentsRecord = loadProjectAgents(directory) - - const toAgentInfoList = (record: Record): AgentInfo[] => - Object.entries(record).map(([name, config]) => ({ - name, - mode: config.mode as AgentInfo["mode"], - model: config.model, - })) - - const projectAgentsList = toAgentInfoList(projectAgentsRecord) - const userAgentsList = toAgentInfoList(userAgentsRecord) - - const mergedAgentMap = new Map() - const addIfAbsent = (agent: AgentInfo): void => { - const key = agent.name.toLowerCase() - if (!mergedAgentMap.has(key)) { - mergedAgentMap.set(key, agent) - } - } - - for (const agent of serverAgents) { - addIfAbsent(agent) - } - for (const agent of projectAgentsList) { - addIfAbsent(agent) - } - for (const agent of userAgentsList) { - addIfAbsent(agent) - } - - return Array.from(mergedAgentMap.values()) -} export async function resolveSubagentExecution( args: DelegateTaskArgs, @@ -93,9 +38,7 @@ export async function resolveSubagentExecution( return { agentToUse: "", categoryModel: undefined, error: `Agent name cannot be empty.` } } - // Strip wrapping characters (backslashes, quotes) that LLMs sometimes add around agent names - // e.g. \hephaestus\ -> hephaestus, "oracle" -> oracle, 'explore' -> explore - const agentName = args.subagent_type.trim().replace(/^[\\\/"']+|[\\\/"']+$/g, "").trim() + const agentName = sanitizeSubagentType(args.subagent_type) if (agentName.toLowerCase() === SISYPHUS_JUNIOR_AGENT.toLowerCase()) { return { @@ -128,26 +71,22 @@ Create the work plan directly - that's your job as the planning agent.`, }) const mergedAgents = mergeWithClaudeCodeAgents(agents, executorCtx.directory) - const callableAgents = mergedAgents.filter((agent) => isTaskCallableAgentMode(agent.mode)) + const matchedPrimaryAgent = findPrimaryAgentMatch(mergedAgents, agentToUse) - const resolvedDisplayName = stripAgentListSortPrefix(getAgentDisplayName(agentToUse)) - const normalizedAgentToUse = stripAgentListSortPrefix(agentToUse) - const matchedAgent = callableAgents.find( - (agent) => { - const normalizedListedAgentName = stripAgentListSortPrefix(agent.name) - return normalizedListedAgentName.toLowerCase() === normalizedAgentToUse.toLowerCase() - || normalizedListedAgentName.toLowerCase() === resolvedDisplayName.toLowerCase() - } - ) - if (!matchedAgent) { - const availableAgents = callableAgents - .map((a) => stripAgentListSortPrefix(a.name)) - .sort() - .join(", ") + if (matchedPrimaryAgent) { return { agentToUse: "", categoryModel: undefined, - error: `Unknown agent: "${agentToUse}". Available agents: ${availableAgents}`, + error: `Cannot delegate to primary agent "${stripAgentListSortPrefix(matchedPrimaryAgent.name)}" via task. Select that agent directly instead.`, + } + } + + const matchedAgent = findCallableAgentMatch(mergedAgents, agentToUse) + if (!matchedAgent) { + return { + agentToUse: "", + categoryModel: undefined, + error: `Unknown agent: "${agentToUse}". Available agents: ${listCallableAgentNames(mergedAgents)}`, } } @@ -216,29 +155,18 @@ Create the work plan directly - that's your job as the planning agent.`, defaultProviderID, ) fallbackChain = configuredFallbackChain ?? (resolutionSkipped ? undefined : agentRequirement?.fallbackChain) - - // Only promote fallback-only settings when resolution actually selected a fallback model. - const resolvedFallbackEntry = (resolution && !('skipped' in resolution)) ? resolution.fallbackEntry : undefined - const matchedFallback = (resolution && !('skipped' in resolution)) ? resolution.matchedFallback === true : false - const effectiveEntry = matchedFallback && categoryModel - ? ( - resolvedFallbackEntry - ?? (configuredFallbackChain - ? findMostSpecificFallbackEntry(categoryModel.providerID, categoryModel.modelID, configuredFallbackChain) - : undefined) - ) - : undefined + const effectiveEntry = resolveEffectiveFallbackEntry({ + categoryModel, + configuredFallbackChain, + resolution, + }) if (categoryModel && effectiveEntry) { - categoryModel = { - ...categoryModel, - variant: agentOverride?.variant ?? effectiveEntry.variant ?? categoryModel.variant, - reasoningEffort: effectiveEntry.reasoningEffort ?? categoryModel.reasoningEffort, - temperature: effectiveEntry.temperature ?? categoryModel.temperature, - top_p: effectiveEntry.top_p ?? categoryModel.top_p, - maxTokens: effectiveEntry.maxTokens ?? categoryModel.maxTokens, - thinking: effectiveEntry.thinking ?? categoryModel.thinking, - } + categoryModel = applyFallbackEntrySettings({ + categoryModel, + effectiveEntry, + variantOverride: agentOverride?.variant, + }) } } @@ -273,7 +201,3 @@ Create the work plan directly - that's your job as the planning agent.`, return { agentToUse, categoryModel, fallbackChain } } - -function isTaskCallableAgentMode(mode: AgentMode): boolean { - return mode === "all" || mode === "subagent" -} diff --git a/src/tools/delegate-task/sync-continuation.test.ts b/src/tools/delegate-task/sync-continuation.test.ts index fa05e93ab..37757dbeb 100644 --- a/src/tools/delegate-task/sync-continuation.test.ts +++ b/src/tools/delegate-task/sync-continuation.test.ts @@ -86,7 +86,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } const args = { - session_id: "ses_test_12345678", + task_id: "ses_test_12345678", prompt: "test prompt", description: "test task", load_skills: [], @@ -97,7 +97,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { let error: any = null let result: string | null = null try { - result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, deps) + result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, { sessionID: "parent-session", messageID: "parent-message" }, deps) } catch (e) { error = e } @@ -148,7 +148,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } const args = { - session_id: "ses_test_12345678", + task_id: "ses_test_12345678", prompt: "test prompt", description: "test task", load_skills: [], @@ -159,7 +159,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { let error: any = null let result: string | null = null try { - result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, deps) + result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, { sessionID: "parent-session", messageID: "parent-message" }, deps) } catch (e) { error = e } @@ -214,7 +214,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } const args = { - session_id: "ses_test_12345678", + task_id: "ses_test_12345678", prompt: "test prompt", description: "test task", load_skills: [], @@ -222,7 +222,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } //#when - executeSyncContinuation completes successfully - const result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, deps) + const result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, { sessionID: "parent-session", messageID: "parent-message" }, deps) //#then - toast should be removed exactly once expect(removeTaskCalls.length).toBe(1) @@ -278,7 +278,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } const args = { - session_id: "ses_test_12345678", + task_id: "ses_test_12345678", prompt: "test prompt", description: "test task", load_skills: [], @@ -286,7 +286,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } //#when - executeSyncContinuation with abort signal - const result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, deps) + const result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, { sessionID: "parent-session", messageID: "parent-message" }, deps) //#then - removeTask should be called at least once (poller and finally may both call it) expect(removeTaskCalls.length).toBeGreaterThanOrEqual(1) @@ -335,7 +335,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } const args = { - session_id: "ses_test_12345678", + task_id: "ses_test_12345678", prompt: "test prompt", description: "test task", load_skills: [], @@ -346,7 +346,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { let error: any = null let result: string | null = null try { - result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, deps) + result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, { sessionID: "parent-session", messageID: "parent-message" }, deps) } catch (e) { error = e } @@ -395,7 +395,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } const args = { - session_id: "ses_test_12345678", + task_id: "ses_test_12345678", prompt: "continue working", description: "resume oracle task", load_skills: [], @@ -403,7 +403,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } //#when - executeSyncContinuation completes with agent info in messages - const result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, deps) + const result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, { sessionID: "parent-session", messageID: "parent-message" }, deps) //#then - task_metadata should contain subagent field with the agent name expect(result).toContain("") @@ -449,7 +449,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } const args = { - session_id: "ses_test_12345678", + task_id: "ses_test_12345678", prompt: "continue working", description: "resume task", load_skills: [], @@ -457,7 +457,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } //#when - executeSyncContinuation completes without agent info - const result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, deps) + const result = await executeSyncContinuation(args, mockCtx, mockExecutorCtx, { sessionID: "parent-session", messageID: "parent-message" }, deps) //#then - task_metadata should NOT contain subagent field expect(result).toContain("") @@ -514,7 +514,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } const args = { - session_id: "ses_test_12345678", + task_id: "ses_test_12345678", prompt: "continue working", description: "resume explore task", load_skills: [], @@ -522,7 +522,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } //#when - await executeSyncContinuation(args, mockCtx, mockExecutorCtx, deps) + await executeSyncContinuation(args, mockCtx, mockExecutorCtx, { sessionID: "parent-session", messageID: "parent-message" }, deps) //#then expect(promptAsyncCalls).toHaveLength(1) @@ -584,7 +584,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } const args = { - session_id: "ses_test_12345678", + task_id: "ses_test_12345678", prompt: "continue researching", description: "resume librarian task", load_skills: [], @@ -592,7 +592,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } //#when - await executeSyncContinuation(args, mockCtx, mockExecutorCtx, deps) + await executeSyncContinuation(args, mockCtx, mockExecutorCtx, { sessionID: "parent-session", messageID: "parent-message" }, deps) //#then expect(promptAsyncCalls).toHaveLength(1) @@ -654,7 +654,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } const args = { - session_id: "ses_test_12345678", + task_id: "ses_test_12345678", prompt: "continue planning", description: "resume prometheus task", load_skills: [], @@ -662,7 +662,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => { } //#when - await executeSyncContinuation(args, mockCtx, mockExecutorCtx, deps) + await executeSyncContinuation(args, mockCtx, mockExecutorCtx, { sessionID: "parent-session", messageID: "parent-message" }, deps) //#then expect(promptAsyncCalls).toHaveLength(1) diff --git a/src/tools/delegate-task/sync-continuation.ts b/src/tools/delegate-task/sync-continuation.ts index fa6f9f022..5ec1406b0 100644 --- a/src/tools/delegate-task/sync-continuation.ts +++ b/src/tools/delegate-task/sync-continuation.ts @@ -1,8 +1,7 @@ import type { DelegateTaskArgs, ToolContextWithMetadata } from "./types" -import type { ExecutorContext, SessionMessage } from "./executor-types" +import type { ExecutorContext, ParentContext, SessionMessage } from "./executor-types" import { isPlanFamily } from "./constants" -import { storeToolMetadata } from "../../features/tool-metadata-store" -import { resolveCallID } from "./resolve-call-id" +import { publishToolMetadata } from "../../features/tool-metadata-store" import { getTaskToastManager } from "../../features/task-toast-manager" import { getAgentToolRestrictions } from "../../shared/agent-tool-restrictions" import { getMessageDir } from "../../shared" @@ -13,16 +12,24 @@ import { syncContinuationDeps, type SyncContinuationDeps } from "./sync-continua import { setSessionTools } from "../../shared/session-tools-store" import { normalizeSDKResponse } from "../../shared" import { buildTaskPrompt } from "./prompt-builder" +import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract" +import { getTaskID } from "./task-id" +import { resolveMetadataModel } from "./resolve-metadata-model" export async function executeSyncContinuation( args: DelegateTaskArgs, ctx: ToolContextWithMetadata, executorCtx: ExecutorContext, + parentContext: ParentContext, deps: SyncContinuationDeps = syncContinuationDeps ): Promise { const { client, syncPollTimeoutMs, sisyphusAgentConfig } = executorCtx const toastManager = getTaskToastManager() - const taskId = `resume_sync_${args.session_id!.slice(0, 8)}` + const continuationID = getTaskID(args) + if (!continuationID) { + throw new Error("task_id is required to continue a sync task") + } + const taskId = `resume_sync_${continuationID.slice(0, 8)}` const startTime = new Date() if (toastManager) { @@ -43,7 +50,7 @@ export async function executeSyncContinuation( try { try { - const messagesResp = await client.session.messages({ path: { id: args.session_id! } }) + const messagesResp = await client.session.messages({ path: { id: continuationID } }) const messages = normalizeSDKResponse(messagesResp, [] as SessionMessage[]) anchorMessageCount = messages.length for (let i = messages.length - 1; i >= 0; i--) { @@ -56,7 +63,7 @@ export async function executeSyncContinuation( } } } catch { - const resumeMessageDir = getMessageDir(args.session_id!) + const resumeMessageDir = getMessageDir(continuationID) const resumeMessage = resumeMessageDir ? findNearestMessageWithFields(resumeMessageDir) : null resumeAgent = resumeMessage?.agent resumeModel = resumeMessage?.model?.providerID && resumeMessage?.model?.modelID @@ -72,17 +79,14 @@ export async function executeSyncContinuation( load_skills: args.load_skills, description: args.description, run_in_background: args.run_in_background, - sessionId: args.session_id, + taskId: continuationID, + sessionId: continuationID, sync: true, command: args.command, - model: resumeModel, + model: resolveMetadataModel(resumeModel, parentContext.model), }, } - await ctx.metadata?.(syncContMeta) - const callID = resolveCallID(ctx) - if (callID) { - storeToolMetadata(ctx.sessionID, callID, syncContMeta) - } + await publishToolMetadata(ctx, syncContMeta) const allowTask = isPlanFamily(resumeAgent) const tddEnabled = sisyphusAgentConfig?.tdd @@ -93,10 +97,10 @@ export async function executeSyncContinuation( question: false, ...(resumeAgent ? getAgentToolRestrictions(resumeAgent) : {}), } - setSessionTools(args.session_id!, tools) + setSessionTools(continuationID, tools) await promptWithModelSuggestionRetry(client, { - path: { id: args.session_id! }, + path: { id: continuationID }, body: { ...(resumeAgent !== undefined ? { agent: resumeAgent } : {}), ...(resumeModel !== undefined ? { model: resumeModel } : {}), @@ -110,12 +114,12 @@ export async function executeSyncContinuation( toastManager.removeTask(taskId) } const errorMessage = promptError instanceof Error ? promptError.message : String(promptError) - return `Failed to send continuation prompt: ${errorMessage}\n\nSession ID: ${args.session_id}` + return `Failed to send continuation prompt: ${errorMessage}\n\nTask ID: ${continuationID}` } try { const pollError = await deps.pollSyncSession(ctx, client, { - sessionID: args.session_id!, + sessionID: continuationID, agentToUse: resumeAgent ?? "continue", toastManager, taskId, @@ -125,7 +129,7 @@ export async function executeSyncContinuation( return pollError } - const result = await deps.fetchSyncResult(client, args.session_id!, anchorMessageCount) + const result = await deps.fetchSyncResult(client, continuationID, anchorMessageCount) if (!result.ok) { return result.error } @@ -138,9 +142,11 @@ export async function executeSyncContinuation( ${result.textContent || "(No text output)"} - -session_id: ${args.session_id} -${resumeAgent ? `subagent: ${resumeAgent}\n` : ""}` +${buildTaskMetadataBlock({ + sessionId: continuationID, + taskId: continuationID, + agent: resumeAgent, + })}` } finally { if (toastManager) { toastManager.removeTask(taskId) diff --git a/src/tools/delegate-task/sync-prompt-sender.ts b/src/tools/delegate-task/sync-prompt-sender.ts index bd38830e5..1f8ad22a5 100644 --- a/src/tools/delegate-task/sync-prompt-sender.ts +++ b/src/tools/delegate-task/sync-prompt-sender.ts @@ -8,6 +8,7 @@ import { } from "../../shared/model-suggestion-retry" import { formatDetailedError } from "./error-formatting" import { getAgentToolRestrictions } from "../../shared/agent-tool-restrictions" +import { stripInvisibleAgentCharacters } from "../../shared/agent-display-names" import { applySessionPromptParams } from "../../shared/session-prompt-params-helpers" import { setSessionTools } from "../../shared/session-tools-store" import { createInternalAgentTextPart } from "../../shared/internal-initiator-marker" @@ -41,7 +42,7 @@ function buildPromptGenerationParams(model: DelegatedModelConfig | undefined): R } function isOracleAgent(agentToUse: string): boolean { - return agentToUse.toLowerCase() === "oracle" + return stripInvisibleAgentCharacters(agentToUse).toLowerCase() === "oracle" } function isUnexpectedEofError(error: unknown): boolean { @@ -80,7 +81,7 @@ export async function sendSyncPrompt( const promptArgs = { path: { id: input.sessionID }, body: { - agent: input.agentToUse.replace(/^\u200B+/, ""), + agent: stripInvisibleAgentCharacters(input.agentToUse), system: input.systemContent, tools, parts: [createInternalAgentTextPart(effectivePrompt)], diff --git a/src/tools/delegate-task/sync-task.test.ts b/src/tools/delegate-task/sync-task.test.ts index 16bc31521..a81fe4eb1 100644 --- a/src/tools/delegate-task/sync-task.test.ts +++ b/src/tools/delegate-task/sync-task.test.ts @@ -272,11 +272,11 @@ describe("executeSyncTask - cleanup on error paths", () => { const initialModel = { providerID: "anthropic", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", variant: "max", } const fallbackChain = [ - { providers: ["anthropic"], model: "claude-opus-4-6", variant: "max" }, + { providers: ["anthropic"], model: "claude-opus-4-7", variant: "max" }, { providers: ["opencode-go"], model: "kimi-k2.5" }, ] @@ -289,7 +289,7 @@ describe("executeSyncTask - cleanup on error paths", () => { expect(result).toContain("Task completed") expect(result).toContain("Model: opencode-go/kimi-k2.5") expect(attemptedModels).toEqual([ - { providerID: "anthropic", modelID: "claude-opus-4-6", variant: "max" }, + { providerID: "anthropic", modelID: "claude-opus-4-7", variant: "max" }, { providerID: "opencode-go", modelID: "kimi-k2.5", variant: undefined }, ]) }) @@ -339,11 +339,11 @@ describe("executeSyncTask - cleanup on error paths", () => { const initialModel = { providerID: "anthropic", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", variant: "max", } const fallbackChain = [ - { providers: ["anthropic"], model: "claude-opus-4-6", variant: "max" }, + { providers: ["anthropic"], model: "claude-opus-4-7", variant: "max" }, { providers: ["opencode-go"], model: "kimi-k2.5" }, { providers: ["openai"], model: "gpt-5.4", variant: "medium" }, ] @@ -356,7 +356,7 @@ describe("executeSyncTask - cleanup on error paths", () => { //#then expect(result).toBe("Final failure") expect(attemptedModels).toEqual([ - { providerID: "anthropic", modelID: "claude-opus-4-6", variant: "max" }, + { providerID: "anthropic", modelID: "claude-opus-4-7", variant: "max" }, { providerID: "opencode-go", modelID: "kimi-k2.5", variant: undefined }, { providerID: "openai", modelID: "gpt-5.4", variant: "medium" }, ]) diff --git a/src/tools/delegate-task/sync-task.ts b/src/tools/delegate-task/sync-task.ts index 18d99e500..034c0e199 100644 --- a/src/tools/delegate-task/sync-task.ts +++ b/src/tools/delegate-task/sync-task.ts @@ -2,16 +2,16 @@ import type { ModelFallbackInfo } from "../../features/task-toast-manager/types" import type { DelegateTaskArgs, ToolContextWithMetadata, DelegatedModelConfig } from "./types" import type { ExecutorContext, ParentContext } from "./executor-types" import { getTaskToastManager } from "../../features/task-toast-manager" -import { storeToolMetadata } from "../../features/tool-metadata-store" -import { resolveCallID } from "./resolve-call-id" +import { publishToolMetadata } from "../../features/tool-metadata-store" import { subagentSessions, syncSubagentSessions, setSessionAgent } from "../../features/claude-code-session-state" import { log } from "../../shared/logger" import { SessionCategoryRegistry } from "../../shared/session-category-registry" import { formatDuration } from "./time-formatter" import { formatDetailedError } from "./error-formatting" import { syncTaskDeps, type SyncTaskDeps } from "./sync-task-deps" -import { setSessionFallbackChain, clearSessionFallbackChain } from "../../hooks/model-fallback/hook" import { retrySyncPromptWithFallbacks } from "./sync-task-fallback" +import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract" +import { resolveMetadataModel } from "./resolve-metadata-model" export async function executeSyncTask( args: DelegateTaskArgs, @@ -38,7 +38,7 @@ export async function executeSyncTask( spawnReservation = await manager.reserveSubagentSpawn(parentContext.sessionID) } - // Depth/descendant guard. We must NOT silently fall back to childDepth: 1 + // Depth guard. We must NOT silently fall back to childDepth: 1 // when the manager is unavailable or lacks the spawn methods, because that // would let subagents recurse without bound. The only safe fallback is // when the manager genuinely cannot enforce limits (legacy SDK), in which @@ -52,7 +52,7 @@ export async function executeSyncTask( } else { log( "[task] WARNING: BackgroundManager has no spawn enforcement methods (reserveSubagentSpawn / assertCanSpawn). " + - "Depth and descendant limits cannot be enforced for this task. This indicates an old SDK or a misconfiguration.", + "Depth limits cannot be enforced for this task. This indicates an old SDK or a misconfiguration.", { parentSessionID: parentContext.sessionID } ) spawnContext = { @@ -80,7 +80,7 @@ export async function executeSyncTask( subagentSessions.add(sessionID) syncSubagentSessions.add(sessionID) setSessionAgent(sessionID, agentToUse) - setSessionFallbackChain(sessionID, fallbackChain) + executorCtx.modelFallbackControllerAccessor?.setSessionFallbackChain(sessionID, fallbackChain) if (args.category) { SessionCategoryRegistry.register(sessionID, args.category) @@ -123,18 +123,15 @@ export async function executeSyncTask( load_skills: args.load_skills, description: args.description, run_in_background: args.run_in_background, + taskId: sessionID, sessionId: sessionID, sync: true, spawnDepth: spawnContext.childDepth, command: args.command, - model: categoryModel ? { providerID: categoryModel.providerID, modelID: categoryModel.modelID } : undefined, + model: resolveMetadataModel(categoryModel, parentContext.model), }, } - await ctx.metadata?.(syncTaskMeta) - const callID = resolveCallID(ctx) - if (callID) { - storeToolMetadata(ctx.sessionID, callID, syncTaskMeta) - } + await publishToolMetadata(ctx, syncTaskMeta) let effectiveCategoryModel = categoryModel let promptError = await deps.sendSyncPrompt(client, { @@ -215,9 +212,12 @@ Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""}${mod ${result.textContent || "(No text output)"} - -session_id: ${sessionID} -` +${buildTaskMetadataBlock({ + sessionId: sessionID, + taskId: sessionID, + agent: agentToUse, + category: args.category, + })}` } finally { if (toastManager && taskId !== undefined) { toastManager.removeTask(taskId) @@ -236,7 +236,7 @@ session_id: ${sessionID} if (syncSessionID) { subagentSessions.delete(syncSessionID) syncSubagentSessions.delete(syncSessionID) - clearSessionFallbackChain(syncSessionID) + executorCtx.modelFallbackControllerAccessor?.clearSessionFallbackChain(syncSessionID) SessionCategoryRegistry.remove(syncSessionID) } } diff --git a/src/tools/delegate-task/task-id.ts b/src/tools/delegate-task/task-id.ts new file mode 100644 index 000000000..c42697898 --- /dev/null +++ b/src/tools/delegate-task/task-id.ts @@ -0,0 +1,5 @@ +import type { DelegateTaskArgs } from "./types" + +export function getTaskID(args: Pick): string | undefined { + return args.task_id +} diff --git a/src/tools/delegate-task/task-schema.test.ts b/src/tools/delegate-task/task-schema.test.ts index 958b9f0a6..c50d175bc 100644 --- a/src/tools/delegate-task/task-schema.test.ts +++ b/src/tools/delegate-task/task-schema.test.ts @@ -31,4 +31,21 @@ function createDelegateTask(...args: Parameters { + //#given + const toolDefinition = createDelegateTask({ manager: {} as never, client: {} as never, directory: "/tmp/test" }) + + //#when + const description = toolDefinition.description + + //#then + expect(description).toContain("subagent_type: Use specific agent directly") + expect(description).toContain("task_id: Existing task to continue") + expect(description).not.toContain("sisyphus") + expect(description).not.toContain("hephaestus") + expect(description).not.toContain("prometheus") + }) }) + +export {} diff --git a/src/tools/delegate-task/tool-argument-preparation.ts b/src/tools/delegate-task/tool-argument-preparation.ts new file mode 100644 index 000000000..d54b12ca6 --- /dev/null +++ b/src/tools/delegate-task/tool-argument-preparation.ts @@ -0,0 +1,80 @@ +import type { DelegateTaskArgs, ToolContextWithMetadata } from "./types" +import { SISYPHUS_JUNIOR_AGENT } from "./sisyphus-junior-agent" +import { log } from "../../shared/logger" + +export async function prepareDelegateTaskArgs(args: Record, ctx: ToolContextWithMetadata): Promise { + const category = typeof args.category === "string" ? args.category : undefined + const prompt = typeof args.prompt === "string" ? args.prompt : "" + const originalSubagentType = typeof args.subagent_type === "string" ? args.subagent_type : undefined + let subagentType = originalSubagentType + + if (category) { + if (subagentType && subagentType !== SISYPHUS_JUNIOR_AGENT) { + log("[task] category provided - overriding subagent_type to sisyphus-junior", { + category, + subagent_type: subagentType, + }) + } + subagentType = SISYPHUS_JUNIOR_AGENT + } + + let description = typeof args.description === "string" ? args.description : undefined + if (!description || description.trim() === "") { + const words = prompt.trim().split(/\s+/) + description = words.slice(0, 4).join(" ") || "Delegated task" + } + + await ctx.metadata?.({ + title: description, + }) + + const runInBackground = args.run_in_background + if (runInBackground === undefined) { + throw new Error("Invalid arguments: 'run_in_background' parameter is REQUIRED. Specify run_in_background=false for task delegation, or run_in_background=true for parallel exploration.") + } + + let loadSkills = args.load_skills + if (typeof loadSkills === "string") { + try { + const parsed = JSON.parse(loadSkills) + loadSkills = Array.isArray(parsed) ? parsed : [] + } catch { + loadSkills = [] + } + } + + if (loadSkills === undefined) { + throw new Error("Invalid arguments: 'load_skills' parameter is REQUIRED. Pass [] if no skills needed.") + } + + if (loadSkills === null) { + throw new Error("Invalid arguments: load_skills=null is not allowed. Pass [] if no skills needed.") + } + + const normalizedLoadSkills = Array.isArray(loadSkills) + ? loadSkills.filter((value): value is string => typeof value === "string") + : [] + + const taskID = typeof args.task_id === "string" ? args.task_id : undefined + const command = typeof args.command === "string" ? args.command : undefined + + args.category = category + args.subagent_type = subagentType + args.description = description + args.prompt = prompt + args.run_in_background = runInBackground + args.task_id = taskID + args.command = command + args.load_skills = normalizedLoadSkills + + return { + category, + subagent_type: subagentType, + description, + prompt, + run_in_background: runInBackground === true, + task_id: taskID, + command, + load_skills: normalizedLoadSkills, + } +} diff --git a/src/tools/delegate-task/tool-description.ts b/src/tools/delegate-task/tool-description.ts new file mode 100644 index 000000000..0b2717a82 --- /dev/null +++ b/src/tools/delegate-task/tool-description.ts @@ -0,0 +1,86 @@ +import type { AvailableCategory, AvailableSkill } from "../../agents/dynamic-agent-prompt-builder" +import { mergeCategories } from "../../shared/merge-categories" +import { CATEGORY_DESCRIPTIONS } from "./constants" +import type { DelegateTaskToolOptions } from "./types" + +export interface DelegateTaskPresentation { + availableCategories: AvailableCategory[] + availableSkills: AvailableSkill[] + categoryExamples: string + description: string +} + +export function createDelegateTaskPresentation(options: DelegateTaskToolOptions): DelegateTaskPresentation { + const { userCategories } = options + const allCategories = mergeCategories(userCategories) + const categoryEntries = Object.entries(allCategories).map(([name, categoryConfig]) => ({ + name, + categoryConfig, + description: userCategories?.[name]?.description || CATEGORY_DESCRIPTIONS[name], + })) + const categoryNames = categoryEntries.map(({ name }) => name) + const categoryExamples = categoryNames.join(", ") + + const availableCategories: AvailableCategory[] = options.availableCategories + ?? categoryEntries.map(({ name, categoryConfig, description }) => { + return { + name, + description: description || "General tasks", + model: categoryConfig.model, + } + }) + + const availableSkills: AvailableSkill[] = options.availableSkills ?? [] + + const categoryList = categoryEntries.map(({ name, description }) => { + return description ? ` - ${name}: ${description}` : ` - ${name}` + }).join("\n") + + const description = `Spawn agent task with category-based or direct agent selection. + + ⚠️ CRITICAL: You MUST provide EITHER category OR subagent_type. Omitting BOTH will FAIL. + + **COMMON MISTAKE (DO NOT DO THIS):** + \`\`\` + task(description="...", prompt="...", run_in_background=false) // ❌ FAILS - missing category AND subagent_type + \`\`\` + + **CORRECT - Using category:** + \`\`\` + task(category="quick", load_skills=[], description="Fix type error", prompt="...", run_in_background=false) + \`\`\` + + **CORRECT - Using subagent_type:** + \`\`\` + task(subagent_type="explore", load_skills=[], description="Find patterns", prompt="...", run_in_background=true) + \`\`\` + + REQUIRED: Provide ONE of: + - category: For task delegation (uses Sisyphus-Junior with category-optimized model) + - subagent_type: For direct agent invocation (explore, librarian, oracle, etc.) + + **DO NOT provide both.** If category is provided, subagent_type is ignored. + + - load_skills: ALWAYS REQUIRED. Pass [] if no skills needed, or ["skill-1", "skill-2"] for category tasks. + - category: Use predefined category → Spawns Sisyphus-Junior with category config + Available categories: + ${categoryList} + - subagent_type: Use specific agent directly (explore, librarian, oracle, metis, momus) + - run_in_background: REQUIRED. true=async (returns task_id), false=sync (waits). Use background=true ONLY for parallel exploration with 5+ independent queries. + - task_id: Existing task to continue (from previous task output). Continues the same subagent session with FULL CONTEXT PRESERVED. + - command: The command that triggered this task (optional, for slash command tracking). + + **WHEN TO USE task_id:** + - Task failed/incomplete → task_id with "fix: [specific issue]" + - Need follow-up on previous result → task_id with additional question + - Multi-turn conversation with same agent → always task_id instead of new task + + Prompts MUST be in English.` + + return { + availableCategories, + availableSkills, + categoryExamples, + description, + } +} diff --git a/src/tools/delegate-task/tools.test.ts b/src/tools/delegate-task/tools.test.ts index 3e7c242b2..2f703fa47 100644 --- a/src/tools/delegate-task/tools.test.ts +++ b/src/tools/delegate-task/tools.test.ts @@ -28,7 +28,7 @@ const SYSTEM_DEFAULT_MODEL = "anthropic/claude-sonnet-4-6" const TEST_CONNECTED_PROVIDERS = ["anthropic", "google", "openai"] const TEST_AVAILABLE_MODELS = new Set([ - "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-7", "anthropic/claude-sonnet-4-6", "anthropic/claude-haiku-4-5", "google/gemini-3.1-pro", @@ -66,7 +66,7 @@ describe("sisyphus-task", () => { cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["anthropic", "google", "openai"]) providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue({ models: { - anthropic: ["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"], + anthropic: ["claude-opus-4-7", "claude-sonnet-4-6", "claude-haiku-4-5"], google: ["gemini-3.1-pro", "gemini-3-flash"], openai: ["gpt-5.4", "gpt-5.3-codex"], }, @@ -112,13 +112,13 @@ describe("sisyphus-task", () => { expect(category.variant).toBe("medium") }) - test("unspecified-high category uses claude-opus-4-6 max as primary", () => { + test("unspecified-high category uses claude-opus-4-7 max as primary", () => { // given const category = DEFAULT_CATEGORIES["unspecified-high"] // when / #then expect(category).toBeDefined() - expect(category.model).toBe("anthropic/claude-opus-4-6") + expect(category.model).toBe("anthropic/claude-opus-4-7") expect(category.variant).toBe("max") }) }) @@ -289,6 +289,27 @@ describe("sisyphus-task", () => { expect(result).toBe(false) }) + test("returns false for 'Momus (Plan Critic)' - no substring false-positive", () => { + //#given / #when + const result = isPlanFamily("Momus (Plan Critic)") + //#then + expect(result).toBe(false) + }) + + test("returns false for 'Momus - Plan Critic' - no substring false-positive", () => { + //#given / #when + const result = isPlanFamily("Momus - Plan Critic") + //#then + expect(result).toBe(false) + }) + + test("returns false for 'Metis - Plan Consultant' - no substring false-positive", () => { + //#given / #when + const result = isPlanFamily("Metis - Plan Consultant") + //#then + expect(result).toBe(false) + }) + test("returns false for undefined", () => { //#given / #when const result = isPlanFamily(undefined) @@ -736,7 +757,7 @@ describe("sisyphus-task", () => { test("blocks requiresModel when availability is known and missing the required model", () => { // given - artistry has requiresModel: gemini-3.1-pro const categoryName = "artistry" - const availableModels = new Set(["anthropic/claude-opus-4-6"]) + const availableModels = new Set(["anthropic/claude-opus-4-7"]) // when const result = resolveCategoryConfig(categoryName, { @@ -766,9 +787,9 @@ describe("sisyphus-task", () => { test("bypasses requiresModel when explicit user config provided", () => { // #given const categoryName = "deep" - const availableModels = new Set(["anthropic/claude-opus-4-6"]) + const availableModels = new Set(["anthropic/claude-opus-4-7"]) const userCategories = { - deep: { model: "anthropic/claude-opus-4-6" }, + deep: { model: "anthropic/claude-opus-4-7" }, } // #when @@ -780,7 +801,7 @@ describe("sisyphus-task", () => { // #then expect(result).not.toBeNull() - expect(result!.config.model).toBe("anthropic/claude-opus-4-6") + expect(result!.config.model).toBe("anthropic/claude-opus-4-7") }) test("bypasses requiresModel when explicit user config provided even with empty availability", () => { @@ -788,7 +809,7 @@ describe("sisyphus-task", () => { const categoryName = "deep" const availableModels = new Set() const userCategories = { - deep: { model: "anthropic/claude-opus-4-6" }, + deep: { model: "anthropic/claude-opus-4-7" }, } // #when @@ -800,7 +821,7 @@ describe("sisyphus-task", () => { // #then expect(result).not.toBeNull() - expect(result!.config.model).toBe("anthropic/claude-opus-4-6") + expect(result!.config.model).toBe("anthropic/claude-opus-4-7") }) test("returns default model from DEFAULT_CATEGORIES for builtin category", () => { @@ -820,7 +841,7 @@ describe("sisyphus-task", () => { // given const categoryName = "visual-engineering" const userCategories = { - "visual-engineering": { model: "anthropic/claude-opus-4-6" }, + "visual-engineering": { model: "anthropic/claude-opus-4-7" }, } // when @@ -828,7 +849,7 @@ describe("sisyphus-task", () => { // then expect(result).not.toBeNull() - expect(result!.config.model).toBe("anthropic/claude-opus-4-6") + expect(result!.config.model).toBe("anthropic/claude-opus-4-7") }) test("user prompt_append is appended to default", () => { @@ -892,7 +913,7 @@ describe("sisyphus-task", () => { test("category built-in model takes precedence over inheritedModel", () => { // given - builtin category with its own model, parent model also provided const categoryName = "visual-engineering" - const inheritedModel = "cliproxy/claude-opus-4-6" + const inheritedModel = "cliproxy/claude-opus-4-7" // when const result = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL }) @@ -906,7 +927,7 @@ describe("sisyphus-task", () => { // given - custom category with no model defined const categoryName = "my-custom-no-model" const userCategories = { "my-custom-no-model": { temperature: 0.5 } } as unknown as Record - const inheritedModel = "cliproxy/claude-opus-4-6" + const inheritedModel = "cliproxy/claude-opus-4-7" // when const result = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL }) @@ -922,7 +943,7 @@ describe("sisyphus-task", () => { const userCategories = { "visual-engineering": { model: "my-provider/my-model" }, } - const inheritedModel = "cliproxy/claude-opus-4-6" + const inheritedModel = "cliproxy/claude-opus-4-7" // when const result = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL }) @@ -1033,7 +1054,7 @@ describe("sisyphus-task", () => { const mockClient = { app: { agents: async () => ({ data: [] }) }, config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) }, - model: { list: async () => [{ provider: "anthropic", id: "claude-opus-4-6" }] }, + model: { list: async () => [{ provider: "anthropic", id: "claude-opus-4-7" }] }, session: { create: async () => ({ data: { id: "test-session" } }), prompt: async () => ({ data: {} }), @@ -1057,7 +1078,7 @@ describe("sisyphus-task", () => { abort: new AbortController().signal, } - // when - unspecified-high uses claude-opus-4-6 max in DEFAULT_CATEGORIES + // when - unspecified-high uses claude-opus-4-7 max in DEFAULT_CATEGORIES await tool.execute( { description: "Test unspecified-high default variant", @@ -1069,10 +1090,10 @@ describe("sisyphus-task", () => { toolContext ) - // then - claude-opus-4-6 should be passed with max variant + // then - claude-opus-4-7 should be passed with max variant expect(launchInput.model).toEqual({ providerID: "anthropic", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", variant: "max", }) }, { timeout: 20000 }) @@ -1092,7 +1113,7 @@ describe("sisyphus-task", () => { const mockClient = { app: { agents: async () => ({ data: [] }) }, config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) }, - model: { list: async () => [{ provider: "anthropic", id: "claude-opus-4-6" }] }, + model: { list: async () => [{ provider: "anthropic", id: "claude-opus-4-7" }] }, session: { get: async () => ({ data: { directory: "/project" } }), create: async () => ({ data: { id: "ses_sync_default_variant" } }), @@ -1118,7 +1139,7 @@ describe("sisyphus-task", () => { abort: new AbortController().signal, } - // when - unspecified-high uses claude-opus-4-6 max in DEFAULT_CATEGORIES + // when - unspecified-high uses claude-opus-4-7 max in DEFAULT_CATEGORIES await tool.execute( { description: "Test unspecified-high sync variant", @@ -1130,10 +1151,10 @@ describe("sisyphus-task", () => { toolContext ) - // then - claude-opus-4-6 should be passed with max variant + // then - claude-opus-4-7 should be passed with max variant expect(promptBody.model).toEqual({ providerID: "anthropic", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", }) expect(promptBody.variant).toBe("max") }, { timeout: 20000 }) @@ -1338,7 +1359,7 @@ describe("sisyphus-task", () => { )).rejects.toThrow("Invalid arguments: 'run_in_background' parameter is REQUIRED") }) - test("#given session_id without run_in_background #when executing #then throws required parameter error", async () => { + test("#given task_id without run_in_background #when executing #then throws required parameter error", async () => { // given const { createDelegateTask } = require("./tools") const mockManager = { resume: async () => ({ id: "task-1", sessionID: "ses_1", status: "running" }) } @@ -1360,14 +1381,14 @@ describe("sisyphus-task", () => { { description: "Continue without run flag", prompt: "Continue", - session_id: "ses_existing", + task_id: "ses_existing", load_skills: [], }, { sessionID: "parent-session", messageID: "parent-message", agent: "sisyphus", abort: new AbortController().signal } )).rejects.toThrow("Invalid arguments: 'run_in_background' parameter is REQUIRED") }) - test("#given no category no subagent_type no session_id and no run_in_background #when executing #then throws required parameter error", async () => { + test("#given no category no subagent_type no task_id and no run_in_background #when executing #then throws required parameter error", async () => { // given const { createDelegateTask } = require("./tools") const mockManager = { launch: async () => ({}) } @@ -1529,7 +1550,7 @@ describe("sisyphus-task", () => { let promptCalled = false const mockManager = { launch: async () => ({}) } const mockClient = { - app: { agents: async () => ({ data: [{ name: "oracle", mode: "subagent", model: { providerID: "anthropic", modelID: "claude-opus-4-6" } }] }) }, + app: { agents: async () => ({ data: [{ name: "oracle", mode: "subagent", model: { providerID: "anthropic", modelID: "claude-opus-4-7" } }] }) }, config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) }, session: { get: async () => ({ data: { directory: "/project" } }), @@ -1698,8 +1719,8 @@ describe("sisyphus-task", () => { }, { timeout: 10000 }) }) - describe("session_id with background parameter", () => { - test("session_id with background=false should wait for result and return content", async () => { + describe("task_id with background parameter", () => { + test("task_id with background=false should wait for result and return content", async () => { // Note: This test needs extended timeout because the implementation has MIN_STABILITY_TIME_MS = 5000 // given const { createDelegateTask } = require("./tools") @@ -1787,7 +1808,7 @@ describe("sisyphus-task", () => { { description: "Continue test", prompt: "Continue the task", - session_id: "ses_continue_test", + task_id: "ses_continue_test", run_in_background: false, load_skills: ["git-master"], }, @@ -1814,7 +1835,7 @@ describe("sisyphus-task", () => { id: "msg_001", role: "user", agent: "sisyphus-junior", - model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, + model: { providerID: "anthropic", modelID: "claude-opus-4-7" }, variant: "max", time: { created: baseTime }, }, @@ -1884,7 +1905,7 @@ describe("sisyphus-task", () => { { description: "Continue with variant", prompt: "Continue the task", - session_id: "ses_var_test", + task_id: "ses_var_test", run_in_background: false, load_skills: [], }, @@ -1896,10 +1917,10 @@ describe("sisyphus-task", () => { const callArgs = promptMock.mock.calls[0][0] expect(callArgs.body.variant).toBe("max") expect(callArgs.body.agent).toBe("sisyphus-junior") - expect(callArgs.body.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" }) + expect(callArgs.body.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7" }) }, { timeout: 10000 }) - test("session_id with background=true should return immediately without waiting", async () => { + test("task_id with background=true should return immediately without waiting", async () => { // given const { createDelegateTask } = require("./tools") @@ -1943,7 +1964,7 @@ describe("sisyphus-task", () => { { description: "Continue bg test", prompt: "Continue in background", - session_id: "ses_bg_continue", + task_id: "ses_bg_continue", run_in_background: true, load_skills: ["git-master"], }, @@ -2530,7 +2551,7 @@ describe("sisyphus-task", () => { // Override provider cache to include kimi-for-coding provider providerModelsSpy.mockReturnValue({ models: { - anthropic: ["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"], + anthropic: ["claude-opus-4-7", "claude-sonnet-4-6", "claude-haiku-4-5"], google: ["gemini-3.1-pro", "gemini-3-flash"], openai: ["gpt-5.4", "gpt-5.3-codex"], "kimi-for-coding": ["k2p5"], @@ -2777,7 +2798,7 @@ describe("sisyphus-task", () => { manager: mockManager, client: mockClient, userCategories: { - "fallback-test": { model: "anthropic/claude-opus-4-6" }, + "fallback-test": { model: "anthropic/claude-opus-4-7" }, }, connectedProvidersOverride: TEST_CONNECTED_PROVIDERS, availableModelsOverride: createTestAvailableModels(), @@ -3444,7 +3465,7 @@ describe("sisyphus-task", () => { test("category built-in model takes precedence over inheritedModel for builtin category", () => { // given - builtin ultrabrain category with its own model, inherited model also provided const categoryName = "ultrabrain" - const inheritedModel = "cliproxy/claude-opus-4-6" + const inheritedModel = "cliproxy/claude-opus-4-7" // when const resolved = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL }) @@ -3459,7 +3480,7 @@ describe("sisyphus-task", () => { // given const categoryName = "ultrabrain" const userCategories = { "ultrabrain": { model: "my-provider/custom-model" } } - const inheritedModel = "cliproxy/claude-opus-4-6" + const inheritedModel = "cliproxy/claude-opus-4-7" // when const resolved = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL }) @@ -3476,7 +3497,7 @@ describe("sisyphus-task", () => { // given - This test verifies the fix for PR #770 bug // The bug was: checking `if (inheritedModel)` instead of `if (actualModel === inheritedModel)` const categoryName = "ultrabrain" - const inheritedModel = "cliproxy/claude-opus-4-6" + const inheritedModel = "cliproxy/claude-opus-4-7" const userCategories = { "ultrabrain": { model: "user/model" } } // when - user model wins @@ -3504,7 +3525,7 @@ describe("sisyphus-task", () => { // given a builtin category with its own model, and an inherited model from parent // The CORRECT chain: userConfig?.model ?? categoryBuiltIn ?? systemDefaultModel const categoryName = "ultrabrain" - const inheritedModel = "anthropic/claude-opus-4-6" + const inheritedModel = "anthropic/claude-opus-4-7" // when category has a built-in model (gpt-5.4 for ultrabrain) const resolved = resolveCategoryConfig(categoryName, { inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL }) @@ -3535,7 +3556,7 @@ describe("sisyphus-task", () => { // given userConfig.model is explicitly set const categoryName = "ultrabrain" const userCategories = { "ultrabrain": { model: "custom/user-model" } } - const inheritedModel = "anthropic/claude-opus-4-6" + const inheritedModel = "anthropic/claude-opus-4-7" const systemDefaultModel = "anthropic/claude-sonnet-4-6" // when resolveCategoryConfig is called with all sources @@ -3554,7 +3575,7 @@ describe("sisyphus-task", () => { // given userConfig.model is empty string "" for a custom category (no built-in model) const categoryName = "custom-empty-model" const userCategories = { "custom-empty-model": { model: "", temperature: 0.3 } } - const inheritedModel = "anthropic/claude-opus-4-6" + const inheritedModel = "anthropic/claude-opus-4-7" // when resolveCategoryConfig is called const resolved = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL }) @@ -3569,7 +3590,7 @@ describe("sisyphus-task", () => { const categoryName = "visual-engineering" // Using type assertion since we're testing fallback behavior for categories without model const userCategories = { "visual-engineering": { temperature: 0.2 } } as unknown as Record - const inheritedModel = "anthropic/claude-opus-4-6" + const inheritedModel = "anthropic/claude-opus-4-7" // when resolveCategoryConfig is called const resolved = resolveCategoryConfig(categoryName, { userCategories, inheritedModel, systemDefaultModel: SYSTEM_DEFAULT_MODEL }) @@ -3657,11 +3678,11 @@ describe("sisyphus-task", () => { expect(result).toContain("plan-family") }) - test("plan cannot delegate to prometheus (cross-blocking)", async () => { + test("plan cannot delegate to prometheus even when it is exposed as a primary agent", async () => { //#given const { createDelegateTask } = require("./tools") const mockClient = { - app: { agents: async () => ({ data: [{ name: "prometheus", mode: "subagent" }] }) }, + app: { agents: async () => ({ data: [{ name: "prometheus", mode: "primary" }] }) }, config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) }, session: { get: async () => ({ data: { directory: "/project" } }), create: async () => ({ data: { id: "s" } }), prompt: async () => ({ data: {} }), promptAsync: async () => ({ data: {} }), messages: async () => ({ data: [] }), status: async () => ({ data: {} }) }, } @@ -3789,7 +3810,7 @@ describe("sisyphus-task", () => { app: { agents: async () => ({ data: [ - { name: "oracle", mode: "subagent", model: { providerID: "anthropic", modelID: "claude-opus-4-6" } }, + { name: "oracle", mode: "subagent", model: { providerID: "anthropic", modelID: "claude-opus-4-7" } }, ], }), }, @@ -3833,7 +3854,7 @@ describe("sisyphus-task", () => { // then - matched agent's model should be passed to session.prompt expect(promptBody.model).toEqual({ providerID: "anthropic", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", }) }, { timeout: 20000 }) @@ -3935,7 +3956,7 @@ describe("sisyphus-task", () => { manager: mockManager, client: mockClient, agentOverrides: { - oracle: { model: "anthropic/claude-opus-4-6" }, + oracle: { model: "anthropic/claude-opus-4-7" }, }, }) @@ -3961,7 +3982,7 @@ describe("sisyphus-task", () => { // then - user-configured model should take priority over matchedAgent.model expect(promptBody.model).toEqual({ providerID: "anthropic", - modelID: "claude-opus-4-6", + modelID: "claude-opus-4-7", }) }, { timeout: 20000 }) @@ -4002,7 +4023,7 @@ describe("sisyphus-task", () => { manager: mockManager, client: mockClient, agentOverrides: { - oracle: { model: "anthropic/claude-opus-4-6", variant: "max" }, + oracle: { model: "anthropic/claude-opus-4-7", variant: "max" }, }, }) @@ -4090,7 +4111,7 @@ describe("sisyphus-task", () => { ) // then - should resolve via AGENT_MODEL_REQUIREMENTS fallback chain for oracle - // oracle fallback chain: gpt-5.4 (openai) > gemini-3.1-pro (google) > claude-opus-4-6 (anthropic) + // oracle fallback chain: gpt-5.4 (openai) > gemini-3.1-pro (google) > claude-opus-4-7 (anthropic) // Since openai is in connectedProviders, should resolve to openai/gpt-5.4 expect(promptBody.model).toBeDefined() expect(promptBody.model.providerID).toBe("openai") @@ -4154,19 +4175,17 @@ describe("sisyphus-task", () => { expect(promptBody.tools.task).toBe(true) }, { timeout: 20000 }) - test("prometheus subagent should have task permission as part of the plan family", async () => { + test("prometheus primary agent should not be callable via task", async () => { //#given const { createDelegateTask } = require("./tools") - let promptBody: any - const promptMock = async (input: any) => { promptBody = input.body; return { data: {} } } const mockClient = { - app: { agents: async () => ({ data: [{ name: "prometheus", mode: "subagent" }] }) }, + app: { agents: async () => ({ data: [{ name: "prometheus", mode: "primary" }] }) }, config: { get: async () => ({ data: { model: SYSTEM_DEFAULT_MODEL } }) }, session: { get: async () => ({ data: { directory: "/project" } }), create: async () => ({ data: { id: "ses_prometheus_task" } }), - prompt: promptMock, - promptAsync: promptMock, + prompt: async () => ({ data: {} }), + promptAsync: async () => ({ data: {} }), messages: async () => ({ data: [{ info: { role: "assistant" }, parts: [{ type: "text", text: "Plan created" }] }] }), status: async () => ({ data: { "ses_prometheus_task": { type: "idle" } } }), }, @@ -4174,13 +4193,13 @@ describe("sisyphus-task", () => { const tool = createDelegateTask({ manager: { launch: async () => ({}) }, client: mockClient }) //#when - await tool.execute( + const result = await tool.execute( { description: "Test prometheus task permission", prompt: "Create a plan", subagent_type: "prometheus", run_in_background: false, load_skills: [] }, { sessionID: "p", messageID: "m", agent: "sisyphus", abort: new AbortController().signal } ) - //#then - prometheus shares task permission with the plan family - expect(promptBody.tools.task).toBe(true) + //#then + expect(result).toContain('Cannot delegate to primary agent "prometheus" via task. Select that agent directly instead.') }, { timeout: 20000 }) test("non-plan subagent should NOT have task permission", async () => { diff --git a/src/tools/delegate-task/tools.ts b/src/tools/delegate-task/tools.ts index ab55e1789..b0820f73b 100644 --- a/src/tools/delegate-task/tools.ts +++ b/src/tools/delegate-task/tools.ts @@ -1,14 +1,7 @@ import { tool, type ToolDefinition } from "@opencode-ai/plugin" -import type { DelegateTaskArgs, DelegatedModelConfig, ToolContextWithMetadata, DelegateTaskToolOptions } from "./types" -import { CATEGORY_DESCRIPTIONS } from "./constants" -import { SISYPHUS_JUNIOR_AGENT } from "./sisyphus-junior-agent" -import { mergeCategories } from "../../shared/merge-categories" +import type { DelegatedModelConfig, ToolContextWithMetadata, DelegateTaskToolOptions } from "./types" import { log } from "../../shared/logger" import { buildSystemContent } from "./prompt-builder" -import type { - AvailableCategory, - AvailableSkill, -} from "../../agents/dynamic-agent-prompt-builder" import { resolveSkillContent, resolveParentContext, @@ -20,133 +13,37 @@ import { executeBackgroundTask, executeSyncTask, } from "./executor" +import { prepareDelegateTaskArgs } from "./tool-argument-preparation" +import { createDelegateTaskPresentation } from "./tool-description" export { resolveCategoryConfig } from "./categories" export type { SyncSessionCreatedEvent, DelegateTaskToolOptions, BuildSystemContentInput } from "./types" export { buildSystemContent, buildTaskPrompt } from "./prompt-builder" +const delegateTaskArgsSchema = { + load_skills: tool.schema.array(tool.schema.string()).describe("Skill names to inject. REQUIRED - pass [] if no skills needed."), + description: tool.schema.string().optional().describe("Short task description (3-5 words). Auto-generated from prompt if omitted."), + prompt: tool.schema.string().describe("Full detailed prompt for the agent"), + run_in_background: tool.schema.boolean().describe("REQUIRED. true=async (returns task_id), false=sync (waits). Use false for task delegation, true ONLY for parallel exploration."), + category: tool.schema.string().optional().describe("REQUIRED if subagent_type not provided. Do NOT provide both category and subagent_type."), + subagent_type: tool.schema.string().optional().describe("REQUIRED if category not provided. Do NOT provide both category and subagent_type."), + task_id: tool.schema.string().optional().describe("Existing task to continue. Canonical resume identifier."), + command: tool.schema.string().optional().describe("The command that triggered this task"), +} + export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefinition { - const { userCategories } = options - - const allCategories = mergeCategories(userCategories) - const categoryNames = Object.keys(allCategories) - const categoryExamples = categoryNames.join(", ") - - const availableCategories: AvailableCategory[] = options.availableCategories - ?? Object.entries(allCategories).map(([name, categoryConfig]) => { - const userDesc = userCategories?.[name]?.description - const builtinDesc = CATEGORY_DESCRIPTIONS[name] - const description = userDesc || builtinDesc || "General tasks" - return { - name, - description, - model: categoryConfig.model, - } - }) - - const availableSkills: AvailableSkill[] = options.availableSkills ?? [] - - const categoryList = categoryNames.map(name => { - const userDesc = userCategories?.[name]?.description - const builtinDesc = CATEGORY_DESCRIPTIONS[name] - const desc = userDesc || builtinDesc - return desc ? ` - ${name}: ${desc}` : ` - ${name}` - }).join("\n") - - const description = `Spawn agent task with category-based or direct agent selection. - - ⚠️ CRITICAL: You MUST provide EITHER category OR subagent_type. Omitting BOTH will FAIL. - - **COMMON MISTAKE (DO NOT DO THIS):** - \`\`\` - task(description="...", prompt="...", run_in_background=false) // ❌ FAILS - missing category AND subagent_type - \`\`\` - - **CORRECT - Using category:** - \`\`\` - task(category="quick", load_skills=[], description="Fix type error", prompt="...", run_in_background=false) - \`\`\` - - **CORRECT - Using subagent_type:** - \`\`\` - task(subagent_type="explore", load_skills=[], description="Find patterns", prompt="...", run_in_background=true) - \`\`\` - - REQUIRED: Provide ONE of: - - category: For task delegation (uses Sisyphus-Junior with category-optimized model) - - subagent_type: For direct agent invocation (explore, librarian, oracle, etc.) - - **DO NOT provide both.** If category is provided, subagent_type is ignored. - - - load_skills: ALWAYS REQUIRED. Pass [] if no skills needed, or ["skill-1", "skill-2"] for category tasks. - - category: Use predefined category → Spawns Sisyphus-Junior with category config - Available categories: - ${categoryList} - - subagent_type: Use specific agent directly (explore, librarian, oracle, metis, momus) - - run_in_background: REQUIRED. true=async (returns task_id), false=sync (waits). Use background=true ONLY for parallel exploration with 5+ independent queries. - - session_id: Existing Task session to continue (from previous task output). Continues agent with FULL CONTEXT PRESERVED - saves tokens, maintains continuity. - - command: The command that triggered this task (optional, for slash command tracking). - - **WHEN TO USE session_id:** - - Task failed/incomplete → session_id with "fix: [specific issue]" - - Need follow-up on previous result → session_id with additional question - - Multi-turn conversation with same agent → always session_id instead of new task - - Prompts MUST be in English.` + const { availableCategories, availableSkills, categoryExamples, description } = createDelegateTaskPresentation(options) return tool({ description, - args: { - load_skills: tool.schema.array(tool.schema.string()).describe("Skill names to inject. REQUIRED - pass [] if no skills needed."), - description: tool.schema.string().optional().describe("Short task description (3-5 words). Auto-generated from prompt if omitted."), - prompt: tool.schema.string().describe("Full detailed prompt for the agent"), - run_in_background: tool.schema.boolean().describe("REQUIRED. true=async (returns task_id), false=sync (waits). Use false for task delegation, true ONLY for parallel exploration."), - category: tool.schema.string().optional().describe(`REQUIRED if subagent_type not provided. Do NOT provide both category and subagent_type.`), - subagent_type: tool.schema.string().optional().describe("REQUIRED if category not provided. Do NOT provide both category and subagent_type."), - session_id: tool.schema.string().optional().describe("Existing Task session to continue"), - command: tool.schema.string().optional().describe("The command that triggered this task"), - }, - async execute(args: DelegateTaskArgs, toolContext) { + args: delegateTaskArgsSchema, + async execute(args, toolContext) { const ctx = toolContext as ToolContextWithMetadata + const delegateTaskArgs = await prepareDelegateTaskArgs(args, ctx) - if (args.category) { - if (args.subagent_type && args.subagent_type !== SISYPHUS_JUNIOR_AGENT) { - log("[task] category provided - overriding subagent_type to sisyphus-junior", { - category: args.category, - subagent_type: args.subagent_type, - }) - } - args.subagent_type = SISYPHUS_JUNIOR_AGENT - } - // Auto-generate description from prompt when missing or empty - if (!args.description || typeof args.description !== "string" || args.description.trim() === "") { - const words = (args.prompt || "").trim().split(/\s+/) - args.description = words.slice(0, 4).join(" ") || "Delegated task" - } - await ctx.metadata?.({ - title: args.description, - }) - if (args.run_in_background === undefined) { - throw new Error(`Invalid arguments: 'run_in_background' parameter is REQUIRED. Specify run_in_background=false for task delegation, or run_in_background=true for parallel exploration.`) - } - if (typeof args.load_skills === "string") { - try { - const parsed = JSON.parse(args.load_skills) - args.load_skills = Array.isArray(parsed) ? parsed : [] - } catch { - args.load_skills = [] - } - } - if (args.load_skills === undefined) { - throw new Error(`Invalid arguments: 'load_skills' parameter is REQUIRED. Pass [] if no skills needed.`) - } - if (args.load_skills === null) { - throw new Error(`Invalid arguments: load_skills=null is not allowed. Pass [] if no skills needed.`) - } + const runInBackground = delegateTaskArgs.run_in_background === true - const runInBackground = args.run_in_background === true - - const { content: skillContent, contents: skillContents, error: skillError } = await resolveSkillContent(args.load_skills, { + const { content: skillContent, contents: skillContents, error: skillError } = await resolveSkillContent(delegateTaskArgs.load_skills, { gitMasterConfig: options.gitMasterConfig, browserProvider: options.browserProvider, disabledSkills: options.disabledSkills, @@ -158,14 +55,14 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini const parentContext = await resolveParentContext(ctx, options.client) - if (args.session_id) { + if (delegateTaskArgs.task_id) { if (runInBackground) { - return executeBackgroundContinuation(args, ctx, options, parentContext) + return executeBackgroundContinuation(delegateTaskArgs, ctx, options, parentContext) } - return executeSyncContinuation(args, ctx, options) + return executeSyncContinuation(delegateTaskArgs, ctx, options, parentContext) } - if (!args.category && !args.subagent_type) { + if (!delegateTaskArgs.category && !delegateTaskArgs.subagent_type) { return `Invalid arguments: Must provide either category or subagent_type.` } @@ -190,8 +87,8 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini let fallbackChain: import("../../shared/model-requirements").FallbackEntry[] | undefined let maxPromptTokens: number | undefined - if (args.category) { - const resolution = await resolveCategoryExecution(args, options, inheritedModel, systemDefaultModel) + if (delegateTaskArgs.category) { + const resolution = await resolveCategoryExecution(delegateTaskArgs, options, inheritedModel, systemDefaultModel) if (resolution.error) { return resolution.error } @@ -204,14 +101,14 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini fallbackChain = resolution.fallbackChain maxPromptTokens = resolution.maxPromptTokens - const isRunInBackgroundExplicitlyFalse = args.run_in_background === false || args.run_in_background === "false" as unknown as boolean + const isRunInBackgroundExplicitlyFalse = isExplicitSyncRun(delegateTaskArgs.run_in_background) log("[task] unstable agent detection", { - category: args.category, + category: delegateTaskArgs.category, actualModel, isUnstableAgent, - run_in_background_value: args.run_in_background, - run_in_background_type: typeof args.run_in_background, + run_in_background_value: delegateTaskArgs.run_in_background, + run_in_background_type: typeof delegateTaskArgs.run_in_background, isRunInBackgroundExplicitlyFalse, willForceBackground: isUnstableAgent && isRunInBackgroundExplicitlyFalse, }) @@ -227,10 +124,10 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini availableCategories, availableSkills, }) - return executeUnstableAgentTask(args, ctx, options, parentContext, agentToUse, categoryModel, systemContent, actualModel) + return executeUnstableAgentTask(delegateTaskArgs, ctx, options, parentContext, agentToUse, categoryModel, systemContent, actualModel) } } else { - const resolution = await resolveSubagentExecution(args, options, parentContext.agent, categoryExamples) + const resolution = await resolveSubagentExecution(delegateTaskArgs, options, parentContext.agent, categoryExamples) if (resolution.error) { return resolution.error } @@ -251,10 +148,14 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini }) if (runInBackground) { - return executeBackgroundTask(args, ctx, options, parentContext, agentToUse, categoryModel, systemContent, fallbackChain) + return executeBackgroundTask(delegateTaskArgs, ctx, options, parentContext, agentToUse, categoryModel, systemContent, fallbackChain) } - return executeSyncTask(args, ctx, options, parentContext, agentToUse, categoryModel, systemContent, modelInfo, fallbackChain) + return executeSyncTask(delegateTaskArgs, ctx, options, parentContext, agentToUse, categoryModel, systemContent, modelInfo, fallbackChain) }, }) } + +function isExplicitSyncRun(runInBackground: unknown): boolean { + return runInBackground === false || runInBackground === "false" +} diff --git a/src/tools/delegate-task/types.ts b/src/tools/delegate-task/types.ts index bbb0e8e21..9eff782ce 100644 --- a/src/tools/delegate-task/types.ts +++ b/src/tools/delegate-task/types.ts @@ -1,6 +1,7 @@ import type { PluginInput } from "@opencode-ai/plugin" import type { BackgroundManager } from "../../features/background-agent" import type { CategoriesConfig, GitMasterConfig, BrowserAutomationProvider, AgentOverrides, SisyphusAgentConfig } from "../../config/schema" +import type { ModelFallbackControllerAccessor } from "../../hooks/model-fallback" import type { AvailableCategory, AvailableSkill, @@ -14,7 +15,7 @@ export interface DelegateTaskArgs { category?: string subagent_type?: string run_in_background: boolean - session_id?: string + task_id?: string command?: string load_skills: string[] execute?: { @@ -68,6 +69,7 @@ export interface DelegateTaskToolOptions { availableSkills?: AvailableSkill[] agentOverrides?: AgentOverrides sisyphusAgentConfig?: SisyphusAgentConfig + modelFallbackControllerAccessor?: ModelFallbackControllerAccessor onSyncSessionCreated?: (event: SyncSessionCreatedEvent) => Promise syncPollTimeoutMs?: number } diff --git a/src/tools/delegate-task/unstable-agent-task.ts b/src/tools/delegate-task/unstable-agent-task.ts index 7d10780cb..7afffdeee 100644 --- a/src/tools/delegate-task/unstable-agent-task.ts +++ b/src/tools/delegate-task/unstable-agent-task.ts @@ -3,13 +3,14 @@ import type { ExecutorContext, ParentContext, SessionMessage } from "./executor- import { DEFAULT_SYNC_POLL_TIMEOUT_MS, getTimingConfig } from "./timing" import { buildTaskPrompt } from "./prompt-builder" import { cancelUnstableAgentTask } from "./cancel-unstable-agent-task" -import { storeToolMetadata } from "../../features/tool-metadata-store" -import { resolveCallID } from "./resolve-call-id" +import { publishToolMetadata } from "../../features/tool-metadata-store" import { formatDuration } from "./time-formatter" import { formatDetailedError } from "./error-formatting" import { getSessionTools } from "../../shared/session-tools-store" import { normalizeSDKResponse } from "../../shared" import { QUESTION_DENIED_SESSION_PERMISSION } from "../../shared/question-denied-session-permission" +import { resolveMetadataModel } from "./resolve-metadata-model" +import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract" export async function executeUnstableAgentTask( args: DelegateTaskArgs, @@ -76,16 +77,14 @@ export async function executeUnstableAgentTask( load_skills: args.load_skills, description: args.description, run_in_background: args.run_in_background, + taskId: sessionID, + backgroundTaskId: task.id, sessionId: sessionID, command: args.command, - model: categoryModel ? { providerID: categoryModel.providerID, modelID: categoryModel.modelID } : undefined, + model: resolveMetadataModel(categoryModel, parentContext.model), }, } - await ctx.metadata?.(bgTaskMeta) - const callID = resolveCallID(ctx) - if (callID) { - storeToolMetadata(ctx.sessionID, callID, bgTaskMeta) - } + await publishToolMetadata(ctx, bgTaskMeta) const startTime = new Date() const timingCfg = getTimingConfig() @@ -152,9 +151,13 @@ Model: ${actualModel} The task session may contain partial results. - -session_id: ${sessionID} -` +${buildTaskMetadataBlock({ + sessionId: sessionID, + taskId: sessionID, + backgroundTaskId: task.id, + agent: agentToUse, + category: args.category, + })}` } if (!completedDuringMonitoring) { @@ -172,9 +175,13 @@ Model: ${actualModel} The task session may still contain partial results. - -session_id: ${sessionID} -` +${buildTaskMetadataBlock({ + sessionId: sessionID, + taskId: sessionID, + backgroundTaskId: task.id, + agent: agentToUse, + category: args.category, + })}` } const messagesResult = await client.session.messages({ path: { id: sessionID } }) @@ -222,9 +229,13 @@ RESULT: ${textContent || "(No text output)"} - -session_id: ${sessionID} -` +${buildTaskMetadataBlock({ + sessionId: sessionID, + taskId: sessionID, + backgroundTaskId: task.id, + agent: agentToUse, + category: args.category, + })}` } catch (error) { if (!cleanupReason) { cleanupReason = "exception" diff --git a/src/tools/delegate-task/zauc-mocks-subagent-resolver/subagent-resolver.test.ts b/src/tools/delegate-task/zauc-mocks-subagent-resolver/subagent-resolver.test.ts index 08286ba26..fe6ffff96 100644 --- a/src/tools/delegate-task/zauc-mocks-subagent-resolver/subagent-resolver.test.ts +++ b/src/tools/delegate-task/zauc-mocks-subagent-resolver/subagent-resolver.test.ts @@ -148,7 +148,24 @@ describe("resolveSubagentExecution", () => { //#then expect(result.agentToUse).toBe("") expect(result.categoryModel).toBeUndefined() - expect(result.error).toBe('Unknown agent: "sisyphus". Available agents: metis, oracle') + expect(result.error).toBe('Cannot delegate to primary agent "sisyphus" via task. Select that agent directly instead.') + }) + + test("returns explicit error for primary display-name agents", async () => { + //#given + const args = createBaseArgs({ subagent_type: "Prometheus - Plan Builder" }) + const executorCtx = createExecutorContext(async () => ([ + { name: "Prometheus - Plan Builder", mode: "primary" }, + { name: "oracle", mode: "subagent" }, + ])) + + //#when + const result = await resolveSubagentExecution(args, executorCtx, "sisyphus", "deep") + + //#then + expect(result.agentToUse).toBe("") + expect(result.categoryModel).toBeUndefined() + expect(result.error).toBe('Cannot delegate to primary agent "Prometheus - Plan Builder" via task. Select that agent directly instead.') }) test("requires explicit all or subagent mode for task-callable agents", async () => { @@ -823,7 +840,7 @@ describe("resolveSubagentExecution", () => { const result = await resolveSubagentExecution(args, executorCtx, "sisyphus", "deep") //#then - expect(result.error).toContain("Unknown agent") + expect(result.error).toBe('Cannot delegate to primary agent "my-primary-agent" via task. Select that agent directly instead.') expect(result.agentToUse).toBe("") }) }) diff --git a/src/tools/glob/constants.ts b/src/tools/glob/constants.ts index 05b5f85f1..8284b3681 100644 --- a/src/tools/glob/constants.ts +++ b/src/tools/glob/constants.ts @@ -1,4 +1,4 @@ -export { resolveGrepCli, resolveGrepCliWithAutoInstall, type GrepBackend, DEFAULT_RG_THREADS } from "../grep/constants" +export { resolveGrepCli, resolveGrepCliWithAutoInstall, type GrepBackend, DEFAULT_RG_THREADS } from "../../shared/ripgrep-cli" export const DEFAULT_TIMEOUT_MS = 60_000 export const DEFAULT_LIMIT = 100 diff --git a/src/tools/grep/cli.ts b/src/tools/grep/cli.ts index 1a6cd89d0..9f55b1d27 100644 --- a/src/tools/grep/cli.ts +++ b/src/tools/grep/cli.ts @@ -3,13 +3,15 @@ import { resolveGrepCli, type ResolvedCli, type GrepBackend, + DEFAULT_RG_THREADS, +} from "../../shared/ripgrep-cli" +import { DEFAULT_MAX_DEPTH, DEFAULT_MAX_FILESIZE, DEFAULT_MAX_COUNT, DEFAULT_MAX_COLUMNS, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_OUTPUT_BYTES, - DEFAULT_RG_THREADS, RG_SAFETY_FLAGS, GREP_SAFETY_FLAGS, } from "./constants" @@ -102,7 +104,8 @@ function parseOutput(output: string, filesOnly = false): GrepMatch[] { const matches: GrepMatch[] = [] const lines = output.split("\n") - for (const line of lines) { + for (let line of lines) { + line = line.replace(/\r$/, "") if (!line.trim()) continue if (filesOnly) { @@ -115,7 +118,8 @@ function parseOutput(output: string, filesOnly = false): GrepMatch[] { continue } - const match = line.match(/^(.+?):(\d+):(.*)$/) + // Handle Windows drive-letter paths (e.g. C:\path\file.ts:42:content) + const match = line.match(/^([A-Za-z]:[\\\/].*?|.+?):(\d+):(.*)$/) if (match) { matches.push({ file: match[1], @@ -134,10 +138,11 @@ function parseCountOutput(output: string): CountResult[] { const results: CountResult[] = [] const lines = output.split("\n") - for (const line of lines) { + for (let line of lines) { + line = line.replace(/\r$/, "") if (!line.trim()) continue - const match = line.match(/^(.+?):(\d+)$/) + const match = line.match(/^([A-Za-z]:[\\\/].*?|.+?):(\d+)$/) if (match) { results.push({ file: match[1], diff --git a/src/tools/grep/constants.ts b/src/tools/grep/constants.ts index 79db24c6b..f6c913303 100644 --- a/src/tools/grep/constants.ts +++ b/src/tools/grep/constants.ts @@ -1,126 +1,3 @@ -import { existsSync } from "node:fs" -import { join, dirname } from "node:path" -import { spawnSync } from "node:child_process" -import { getInstalledRipgrepPath, downloadAndInstallRipgrep } from "./downloader" -import { getDataDir } from "../../shared/data-path" -import { log } from "../../shared/logger" -import { PUBLISHED_PACKAGE_NAME } from "../../shared/plugin-identity" - -export type GrepBackend = "rg" | "grep" - -export interface ResolvedCli { - path: string - backend: GrepBackend -} - -let cachedCli: ResolvedCli | null = null -let autoInstallAttempted = false - -function findExecutable(name: string): string | null { - const isWindows = process.platform === "win32" - const cmd = isWindows ? "where" : "which" - - try { - const result = spawnSync(cmd, [name], { encoding: "utf-8", timeout: 5000 }) - if (result.status === 0 && result.stdout.trim()) { - return result.stdout.trim().split("\n")[0] - } - } catch { - // Command execution failed - } - return null -} - -function getOpenCodeBundledRg(): string | null { - const execPath = process.execPath - const execDir = dirname(execPath) - - const isWindows = process.platform === "win32" - const rgName = isWindows ? "rg.exe" : "rg" - - const candidates = [ - // OpenCode XDG data path (highest priority - where OpenCode installs rg) - join(getDataDir(), "opencode", "bin", rgName), - // Legacy paths relative to execPath - join(execDir, rgName), - join(execDir, "bin", rgName), - join(execDir, "..", "bin", rgName), - join(execDir, "..", "libexec", rgName), - ] - - for (const candidate of candidates) { - if (existsSync(candidate)) { - return candidate - } - } - - return null -} - -export function resolveGrepCli(): ResolvedCli { - if (cachedCli) return cachedCli - - const bundledRg = getOpenCodeBundledRg() - if (bundledRg) { - cachedCli = { path: bundledRg, backend: "rg" } - return cachedCli - } - - const systemRg = findExecutable("rg") - if (systemRg) { - cachedCli = { path: systemRg, backend: "rg" } - return cachedCli - } - - const installedRg = getInstalledRipgrepPath() - if (installedRg) { - cachedCli = { path: installedRg, backend: "rg" } - return cachedCli - } - - const grep = findExecutable("grep") - if (grep) { - cachedCli = { path: grep, backend: "grep" } - return cachedCli - } - - cachedCli = { path: "rg", backend: "rg" } - return cachedCli -} - -export async function resolveGrepCliWithAutoInstall(): Promise { - const current = resolveGrepCli() - - if (current.backend === "rg" && current.path !== "rg") { - return current - } - - if (autoInstallAttempted) { - return current - } - - autoInstallAttempted = true - - try { - const rgPath = await downloadAndInstallRipgrep() - cachedCli = { path: rgPath, backend: "rg" } - return cachedCli - } catch (error) { - if (current.backend === "grep") { - log(`[${PUBLISHED_PACKAGE_NAME}] Failed to auto-install ripgrep. Falling back to GNU grep.`, { - error: error instanceof Error ? error.message : String(error), - grep_path: current.path, - }) - } else { - log(`[${PUBLISHED_PACKAGE_NAME}] Failed to auto-install ripgrep and GNU grep was not found.`, { - error: error instanceof Error ? error.message : String(error), - }) - } - - return current - } -} - export const DEFAULT_MAX_DEPTH = 20 export const DEFAULT_MAX_FILESIZE = "10M" export const DEFAULT_MAX_COUNT = 500 @@ -128,7 +5,6 @@ export const DEFAULT_MAX_COLUMNS = 1000 export const DEFAULT_CONTEXT = 2 export const DEFAULT_TIMEOUT_MS = 60_000 export const DEFAULT_MAX_OUTPUT_BYTES = 256 * 1024 -export const DEFAULT_RG_THREADS = 4 export const RG_SAFETY_FLAGS = [ "--no-follow", diff --git a/src/tools/grep/tools.ts b/src/tools/grep/tools.ts index eaf8a3972..c40193c56 100644 --- a/src/tools/grep/tools.ts +++ b/src/tools/grep/tools.ts @@ -1,8 +1,8 @@ import { resolve } from "node:path" import type { PluginInput } from "@opencode-ai/plugin" import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool" +import { resolveGrepCliWithAutoInstall } from "../../shared/ripgrep-cli" import { runRg, runRgCount } from "./cli" -import { resolveGrepCliWithAutoInstall } from "./constants" import { formatGrepResult, formatCountResult } from "./result-formatter" export function createGrepTools(ctx: PluginInput): Record { diff --git a/src/tools/hashline-edit/hashline-edit-executor.ts b/src/tools/hashline-edit/hashline-edit-executor.ts index b9412d89e..54509ab6c 100644 --- a/src/tools/hashline-edit/hashline-edit-executor.ts +++ b/src/tools/hashline-edit/hashline-edit-executor.ts @@ -1,5 +1,5 @@ import type { ToolContext } from "@opencode-ai/plugin/tool" -import { storeToolMetadata } from "../../features/tool-metadata-store" +import { publishToolMetadata } from "../../features/tool-metadata-store" import { applyHashlineEditsWithReport } from "./edit-operations" import { countLineDiffs, generateUnifiedDiff } from "./diff-utils" import { canonicalizeFileText, restoreFileText } from "./file-text-canonicalization" @@ -26,13 +26,6 @@ type ToolContextWithMetadata = ToolContextWithCallID & { metadata?: (value: unknown) => void } -function resolveToolCallID(ctx: ToolContextWithCallID): string | undefined { - if (typeof ctx.callID === "string" && ctx.callID.trim() !== "") return ctx.callID - if (typeof ctx.callId === "string" && ctx.callId.trim() !== "") return ctx.callId - if (typeof ctx.call_id === "string" && ctx.call_id.trim() !== "") return ctx.call_id - return undefined -} - function canCreateFromMissingFile(edits: HashlineEdit[]): boolean { if (edits.length === 0) return false return edits.every((edit) => (edit.op === "append" || edit.op === "prepend") && !edit.pos) @@ -143,13 +136,7 @@ export async function executeHashlineEditTool(args: HashlineEditArgs, context: T applyResult.noopEdits, applyResult.deduplicatedEdits ) - if (typeof metadataContext.metadata === "function") { - metadataContext.metadata(formattedMeta) - } - const callID = resolveToolCallID(metadataContext) - if (callID) { - storeToolMetadata(context.sessionID, callID, formattedMeta) - } + await publishToolMetadata(metadataContext, formattedMeta) if (rename && rename !== filePath) { await Bun.write(rename, formattedContent) await Bun.file(filePath).delete() @@ -173,14 +160,7 @@ export async function executeHashlineEditTool(args: HashlineEditArgs, context: T applyResult.deduplicatedEdits ) - if (typeof metadataContext.metadata === "function") { - metadataContext.metadata(meta) - } - - const callID = resolveToolCallID(metadataContext) - if (callID) { - storeToolMetadata(context.sessionID, callID, meta) - } + await publishToolMetadata(metadataContext, meta) if (rename && rename !== filePath) { return `Moved ${filePath} to ${rename}` diff --git a/src/tools/look-at/look-at-input-preparer.ts b/src/tools/look-at/look-at-input-preparer.ts new file mode 100644 index 000000000..4901eb00f --- /dev/null +++ b/src/tools/look-at/look-at-input-preparer.ts @@ -0,0 +1,151 @@ +import { basename } from "node:path" +import { pathToFileURL } from "node:url" +import type { LookAtArgs } from "./types" +import { + extractBase64Data, + inferMimeTypeFromBase64, + inferMimeTypeFromFilePath, +} from "./mime-type-inference" +import { + needsConversion, + convertImageToJpeg, + convertBase64ImageToJpeg, + cleanupConvertedImage, +} from "./image-converter" +import { log } from "../../shared" + +export interface LookAtFilePart { + type: "file" + mime: string + url: string + filename: string +} + +export interface PreparedLookAtInput { + readonly filePart: LookAtFilePart + readonly isBase64Input: boolean + readonly sourceDescription: string + cleanup(): void +} + +type PrepareLookAtInputResult = + | { ok: true; value: PreparedLookAtInput } + | { ok: false; error: string } + +function getTemporaryConversionPath(error: unknown): string | null { + if (!(error instanceof Error)) { + return null + } + + const temporaryOutputPath = Reflect.get(error, "temporaryOutputPath") + if (typeof temporaryOutputPath === "string" && temporaryOutputPath.length > 0) { + return temporaryOutputPath + } + + const temporaryDirectory = Reflect.get(error, "temporaryDirectory") + if (typeof temporaryDirectory === "string" && temporaryDirectory.length > 0) { + return temporaryDirectory + } + + return null +} + +export function prepareLookAtInput(args: LookAtArgs): PrepareLookAtInputResult { + const imageData = args.image_data + const filePath = args.file_path + + if (imageData) { + const mimeType = inferMimeTypeFromBase64(imageData) + + let finalBase64Data = extractBase64Data(imageData) + let finalMimeType = mimeType + let tempFilesToCleanup: string[] = [] + + if (needsConversion(mimeType)) { + log(`[look_at] Detected unsupported Base64 format: ${mimeType}, converting to JPEG...`) + try { + const { base64, tempFiles } = convertBase64ImageToJpeg(finalBase64Data, mimeType) + finalBase64Data = base64 + finalMimeType = "image/jpeg" + tempFilesToCleanup = tempFiles + log("[look_at] Base64 conversion successful") + } catch (conversionError) { + log(`[look_at] Base64 conversion failed: ${conversionError}`) + return { + ok: false, + error: `Error: Failed to convert Base64 image format. ${conversionError}`, + } + } + } + + return { + ok: true, + value: { + isBase64Input: true, + sourceDescription: "clipboard/pasted image", + filePart: { + type: "file", + mime: finalMimeType, + url: `data:${finalMimeType};base64,${finalBase64Data}`, + filename: `clipboard-image.${finalMimeType.split("/")[1] || "png"}`, + }, + cleanup() { + for (const temporaryFile of tempFilesToCleanup) { + cleanupConvertedImage(temporaryFile) + } + }, + }, + } + } + + if (filePath) { + let mimeType = inferMimeTypeFromFilePath(filePath) + let actualFilePath = filePath + let tempConversionPath: string | null = null + + if (needsConversion(mimeType)) { + log(`[look_at] Detected unsupported format: ${mimeType}, converting to JPEG...`) + try { + const convertedFilePath = convertImageToJpeg(filePath, mimeType) + tempConversionPath = convertedFilePath + actualFilePath = convertedFilePath + mimeType = "image/jpeg" + log(`[look_at] Conversion successful: ${convertedFilePath}`) + } catch (conversionError) { + const failedConversionPath = getTemporaryConversionPath(conversionError) + if (failedConversionPath) { + tempConversionPath = failedConversionPath + } + log(`[look_at] Conversion failed: ${conversionError}`) + return { + ok: false, + error: `Error: Failed to convert image format. ${conversionError}`, + } + } + } + + return { + ok: true, + value: { + isBase64Input: false, + sourceDescription: filePath, + filePart: { + type: "file", + mime: mimeType, + url: pathToFileURL(actualFilePath).href, + filename: basename(actualFilePath), + }, + cleanup() { + if (tempConversionPath) { + cleanupConvertedImage(tempConversionPath) + } + }, + }, + } + } + + return { + ok: false, + error: "Error: Must provide either 'file_path' or 'image_data'.", + } +} diff --git a/src/tools/look-at/look-at-prompt.ts b/src/tools/look-at/look-at-prompt.ts new file mode 100644 index 000000000..585a8c38f --- /dev/null +++ b/src/tools/look-at/look-at-prompt.ts @@ -0,0 +1,18 @@ +export const READ_ENABLED = false + +export function buildLookAtPrompt(goal: string, isBase64Input: boolean): string { + const subjectNoun = isBase64Input ? "image" : "file" + const sourceClause = READ_ENABLED + ? "Use the Read tool on the provided file path to load its contents, then analyze it." + : `The ${subjectNoun} is already attached to this message. Analyze it directly from the attachment. Do NOT attempt to use the Read tool. The Read tool is disabled for this invocation and the ${subjectNoun} cannot be loaded by path.` + + return `Analyze the attached ${subjectNoun} and extract the requested information. + +${sourceClause} + +Goal: ${goal} + +Provide ONLY the extracted information that matches the goal. +Be thorough on what was requested, concise on everything else. +If the requested information is not found, clearly state what is missing.` +} diff --git a/src/tools/look-at/look-at-session-runner.ts b/src/tools/look-at/look-at-session-runner.ts new file mode 100644 index 000000000..5b87a7852 --- /dev/null +++ b/src/tools/look-at/look-at-session-runner.ts @@ -0,0 +1,107 @@ +import type { PluginInput } from "@opencode-ai/plugin" +import type { ToolContext } from "@opencode-ai/plugin/tool" +import { log, promptSyncWithModelSuggestionRetry } from "../../shared" +import { extractLatestAssistantText } from "./assistant-message-extractor" +import { MULTIMODAL_LOOKER_AGENT } from "./constants" +import { READ_ENABLED, buildLookAtPrompt } from "./look-at-prompt" +import type { LookAtFilePart } from "./look-at-input-preparer" +import { resolveMultimodalLookerAgentMetadata } from "./multimodal-agent-metadata" + +interface RunLookAtSessionInput { + ctx: PluginInput + toolContext: ToolContext + goal: string + filePart: LookAtFilePart + isBase64Input: boolean +} + +export async function runLookAtSession({ + ctx, + toolContext, + goal, + filePart, + isBase64Input, +}: RunLookAtSessionInput): Promise { + const prompt = buildLookAtPrompt(goal, isBase64Input) + const { agentModel, agentVariant } = await resolveMultimodalLookerAgentMetadata(ctx) + + log(`[look_at] Creating session with parent: ${toolContext.sessionID}`) + const parentSession = await ctx.client.session.get({ + path: { id: toolContext.sessionID }, + }).catch(() => null) + const parentDirectory = parentSession?.data?.directory ?? ctx.directory + + const createResult = await ctx.client.session.create({ + body: { + parentID: toolContext.sessionID, + title: `look_at: ${goal.substring(0, 50)}`, + }, + query: { directory: parentDirectory }, + }) + + if (createResult.error) { + log("[look_at] Session create error:", createResult.error) + const errorString = String(createResult.error) + if (errorString.toLowerCase().includes("unauthorized")) { + return `Error: Failed to create session (Unauthorized). This may be due to: +1. OAuth token restrictions (e.g., Claude Code credentials are restricted to Claude Code only) +2. Provider authentication issues +3. Session permission inheritance problems + +Try using a different provider or API key authentication. + +Original error: ${createResult.error}` + } + + return `Error: Failed to create session: ${createResult.error}` + } + + const sessionID = createResult.data.id + log(`[look_at] Created session: ${sessionID}`) + + log(`[look_at] Sending prompt with ${isBase64Input ? "base64 image" : "file"} to session ${sessionID}`) + try { + await promptSyncWithModelSuggestionRetry(ctx.client, { + path: { id: sessionID }, + body: { + agent: MULTIMODAL_LOOKER_AGENT, + tools: { + task: false, + call_omo_agent: false, + look_at: false, + read: READ_ENABLED, + }, + parts: [ + { type: "text", text: prompt }, + filePart, + ], + ...(agentModel ? { model: { providerID: agentModel.providerID, modelID: agentModel.modelID } } : {}), + ...(agentVariant ? { variant: agentVariant } : {}), + }, + }) + } catch (promptError) { + log("[look_at] Prompt error (ignored, will still fetch messages):", promptError) + } + + log(`[look_at] Fetching messages from session ${sessionID}...`) + const messagesResult = await ctx.client.session.messages({ + path: { id: sessionID }, + }) + + if (messagesResult.error) { + log("[look_at] Messages error:", messagesResult.error) + return `Error: Failed to get messages: ${messagesResult.error}` + } + + const messages = messagesResult.data + log(`[look_at] Got ${messages.length} messages`) + + const responseText = extractLatestAssistantText(messages) + if (!responseText) { + log("[look_at] No assistant message found") + return "Error: No response from multimodal-looker agent" + } + + log(`[look_at] Got response, length: ${responseText.length}`) + return responseText +} diff --git a/src/tools/look-at/tools.ts b/src/tools/look-at/tools.ts index 1296afd29..d6fbb3b01 100644 --- a/src/tools/look-at/tools.ts +++ b/src/tools/look-at/tools.ts @@ -1,43 +1,11 @@ -import { basename } from "node:path" -import { pathToFileURL } from "node:url" import { tool, type PluginInput, type ToolDefinition } from "@opencode-ai/plugin" -import { LOOK_AT_DESCRIPTION, MULTIMODAL_LOOKER_AGENT } from "./constants" +import { LOOK_AT_DESCRIPTION } from "./constants" import type { LookAtArgs } from "./types" -import { log, promptSyncWithModelSuggestionRetry } from "../../shared" -import { extractLatestAssistantText } from "./assistant-message-extractor" +import { log } from "../../shared" import type { LookAtArgsWithAlias } from "./look-at-arguments" import { normalizeArgs, validateArgs } from "./look-at-arguments" -import { - extractBase64Data, - inferMimeTypeFromBase64, - inferMimeTypeFromFilePath, -} from "./mime-type-inference" -import { resolveMultimodalLookerAgentMetadata } from "./multimodal-agent-metadata" -import { - needsConversion, - convertImageToJpeg, - convertBase64ImageToJpeg, - cleanupConvertedImage, -} from "./image-converter" - -function getTemporaryConversionPath(error: unknown): string | null { - if (!(error instanceof Error)) { - return null - } - - const temporaryOutputPath = Reflect.get(error, "temporaryOutputPath") - if (typeof temporaryOutputPath === "string" && temporaryOutputPath.length > 0) { - return temporaryOutputPath - } - - const temporaryDirectory = Reflect.get(error, "temporaryDirectory") - if (typeof temporaryDirectory === "string" && temporaryDirectory.length > 0) { - return temporaryDirectory - } - - return null -} - +import { prepareLookAtInput } from "./look-at-input-preparer" +import { runLookAtSession } from "./look-at-session-runner" export { normalizeArgs, validateArgs } from "./look-at-arguments" @@ -57,188 +25,29 @@ export function createLookAt(ctx: PluginInput): ToolDefinition { return validationError } - const isBase64Input = Boolean(args.image_data) - const sourceDescription = isBase64Input ? "clipboard/pasted image" : args.file_path + const preparedInputResult = prepareLookAtInput(args) + if (!preparedInputResult.ok) { + return preparedInputResult.error + } + + const preparedInput = preparedInputResult.value + const { isBase64Input, sourceDescription } = preparedInput log(`[look_at] Analyzing ${sourceDescription}, goal: ${args.goal}`) - const imageData = args.image_data - const filePath = args.file_path - - let mimeType: string - let filePart: { type: "file"; mime: string; url: string; filename: string } - let tempFilePath: string | null = null - let tempConversionPath: string | null = null - let tempFilesToCleanup: string[] = [] - try { - if (imageData) { - mimeType = inferMimeTypeFromBase64(imageData) - - let finalBase64Data = extractBase64Data(imageData) - let finalMimeType = mimeType - - if (needsConversion(mimeType)) { - log(`[look_at] Detected unsupported Base64 format: ${mimeType}, converting to JPEG...`) - try { - const { base64, tempFiles } = convertBase64ImageToJpeg(finalBase64Data, mimeType) - finalBase64Data = base64 - finalMimeType = "image/jpeg" - tempFilesToCleanup = tempFiles - log(`[look_at] Base64 conversion successful`) - } catch (conversionError) { - log(`[look_at] Base64 conversion failed: ${conversionError}`) - return `Error: Failed to convert Base64 image format. ${conversionError}` - } - } - - filePart = { - type: "file", - mime: finalMimeType, - url: `data:${finalMimeType};base64,${finalBase64Data}`, - filename: `clipboard-image.${finalMimeType.split("/")[1] || "png"}`, - } - } else if (filePath) { - mimeType = inferMimeTypeFromFilePath(filePath) - - let actualFilePath = filePath - if (needsConversion(mimeType)) { - log(`[look_at] Detected unsupported format: ${mimeType}, converting to JPEG...`) - try { - tempFilePath = convertImageToJpeg(filePath, mimeType) - tempConversionPath = tempFilePath - actualFilePath = tempFilePath - mimeType = "image/jpeg" - log(`[look_at] Conversion successful: ${tempFilePath}`) - } catch (conversionError) { - const failedConversionPath = getTemporaryConversionPath(conversionError) - if (failedConversionPath) { - tempConversionPath = failedConversionPath - } - log(`[look_at] Conversion failed: ${conversionError}`) - return `Error: Failed to convert image format. ${conversionError}` - } - } - - filePart = { - type: "file", - mime: mimeType, - url: pathToFileURL(actualFilePath).href, - filename: basename(actualFilePath), - } - } else { - return "Error: Must provide either 'file_path' or 'image_data'." - } - - const readEnabled = false - const subjectNoun = isBase64Input ? "image" : "file" - const sourceClause = readEnabled - ? `Use the Read tool on the provided file path to load its contents, then analyze it.` - : `The ${subjectNoun} is already attached to this message. Analyze it directly from the attachment. Do NOT attempt to use the Read tool. The Read tool is disabled for this invocation and the ${subjectNoun} cannot be loaded by path.` - - const prompt = `Analyze the attached ${subjectNoun} and extract the requested information. - -${sourceClause} - -Goal: ${args.goal} - -Provide ONLY the extracted information that matches the goal. -Be thorough on what was requested, concise on everything else. -If the requested information is not found, clearly state what is missing.` - - const { agentModel, agentVariant } = await resolveMultimodalLookerAgentMetadata(ctx) - - log(`[look_at] Creating session with parent: ${toolContext.sessionID}`) - const parentSession = await ctx.client.session.get({ - path: { id: toolContext.sessionID }, - }).catch(() => null) - const parentDirectory = parentSession?.data?.directory ?? ctx.directory - - const createResult = await ctx.client.session.create({ - body: { - parentID: toolContext.sessionID, - title: `look_at: ${args.goal.substring(0, 50)}`, - }, - query: { directory: parentDirectory }, - }) - - if (createResult.error) { - log(`[look_at] Session create error:`, createResult.error) - const errorStr = String(createResult.error) - if (errorStr.toLowerCase().includes("unauthorized")) { - return `Error: Failed to create session (Unauthorized). This may be due to: -1. OAuth token restrictions (e.g., Claude Code credentials are restricted to Claude Code only) -2. Provider authentication issues -3. Session permission inheritance problems - -Try using a different provider or API key authentication. - -Original error: ${createResult.error}` - } - return `Error: Failed to create session: ${createResult.error}` - } - - const sessionID = createResult.data.id - log(`[look_at] Created session: ${sessionID}`) - - log(`[look_at] Sending prompt with ${isBase64Input ? "base64 image" : "file"} to session ${sessionID}`) - try { - await promptSyncWithModelSuggestionRetry(ctx.client, { - path: { id: sessionID }, - body: { - agent: MULTIMODAL_LOOKER_AGENT, - tools: { - task: false, - call_omo_agent: false, - look_at: false, - read: readEnabled, - }, - parts: [ - { type: "text", text: prompt }, - filePart, - ], - ...(agentModel ? { model: { providerID: agentModel.providerID, modelID: agentModel.modelID } } : {}), - ...(agentVariant ? { variant: agentVariant } : {}), - }, + return await runLookAtSession({ + ctx, + toolContext, + goal: args.goal, + filePart: preparedInput.filePart, + isBase64Input, }) - } catch (promptError) { - log(`[look_at] Prompt error (ignored, will still fetch messages):`, promptError) - } - - log(`[look_at] Fetching messages from session ${sessionID}...`) - - const messagesResult = await ctx.client.session.messages({ - path: { id: sessionID }, - }) - - if (messagesResult.error) { - log(`[look_at] Messages error:`, messagesResult.error) - return `Error: Failed to get messages: ${messagesResult.error}` - } - - const messages = messagesResult.data - log(`[look_at] Got ${messages.length} messages`) - - const responseText = extractLatestAssistantText(messages) - if (!responseText) { - log("[look_at] No assistant message found") - return "Error: No response from multimodal-looker agent" - } - - log(`[look_at] Got response, length: ${responseText.length}`) - return responseText } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) log(`[look_at] Unexpected error analyzing ${sourceDescription}:`, error) return `Error: Failed to analyze ${sourceDescription}: ${errorMessage}` } finally { - if (tempConversionPath) { - cleanupConvertedImage(tempConversionPath) - } else if (tempFilePath) { - cleanupConvertedImage(tempFilePath) - } - tempFilesToCleanup.forEach(file => { - cleanupConvertedImage(file) - }) + preparedInput.cleanup() } }, }) diff --git a/src/tools/skill-mcp/parse-skill-mcp-arguments.ts b/src/tools/skill-mcp/parse-skill-mcp-arguments.ts new file mode 100644 index 000000000..20e0b8158 --- /dev/null +++ b/src/tools/skill-mcp/parse-skill-mcp-arguments.ts @@ -0,0 +1,25 @@ +export function parseSkillMcpArguments( + argsJson: string | Record | undefined, +): Record { + if (!argsJson) return {} + if (typeof argsJson === "object" && argsJson !== null) { + return argsJson + } + + try { + const jsonString = argsJson.startsWith("'") && argsJson.endsWith("'") ? argsJson.slice(1, -1) : argsJson + const parsed = JSON.parse(jsonString) + if (typeof parsed !== "object" || parsed === null) { + throw new Error("Arguments must be a JSON object") + } + + return parsed as Record + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + throw new Error( + `Invalid arguments JSON: ${errorMessage}\n\n` + + `Expected a valid JSON object, e.g.: '{"key": "value"}'\n` + + `Received: ${argsJson}`, + ) + } +} diff --git a/src/tools/skill-mcp/tools.ts b/src/tools/skill-mcp/tools.ts index 2e1876575..25720baf8 100644 --- a/src/tools/skill-mcp/tools.ts +++ b/src/tools/skill-mcp/tools.ts @@ -1,6 +1,7 @@ import { tool, type ToolDefinition } from "@opencode-ai/plugin" import type { ToolContext } from "@opencode-ai/plugin/tool" import { BUILTIN_MCP_TOOL_HINTS, SKILL_MCP_DESCRIPTION } from "./constants" +import { parseSkillMcpArguments } from "./parse-skill-mcp-arguments" import type { SkillMcpArgs } from "./types" import type { SkillMcpManager, SkillMcpClientInfo, SkillMcpServerContext } from "../../features/skill-mcp-manager" import type { LoadedSkill } from "../../features/opencode-skill-loader/types" @@ -82,30 +83,6 @@ function formatBuiltinMcpHint(mcpName: string): string | null { ) } -function parseArguments(argsJson: string | Record | undefined): Record { - if (!argsJson) return {} - if (typeof argsJson === "object" && argsJson !== null) { - return argsJson - } - try { - // Strip outer single quotes if present (common in LLM output) - const jsonStr = argsJson.startsWith("'") && argsJson.endsWith("'") ? argsJson.slice(1, -1) : argsJson - - const parsed = JSON.parse(jsonStr) - if (typeof parsed !== "object" || parsed === null) { - throw new Error("Arguments must be a JSON object") - } - return parsed as Record - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error) - throw new Error( - `Invalid arguments JSON: ${errorMessage}\n\n` + - `Expected a valid JSON object, e.g.: '{"key": "value"}'\n` + - `Received: ${argsJson}`, - ) - } -} - export function applyGrepFilter(output: string, pattern: string | undefined): string { if (!pattern) return output try { @@ -174,7 +151,7 @@ export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition skillName: found.skill.name, } - const parsedArgs = parseArguments(args.arguments) + const parsedArgs = parseSkillMcpArguments(args.arguments) let output: string switch (operation.type) { diff --git a/src/tools/skill/description-formatter.ts b/src/tools/skill/description-formatter.ts index fb8dd87c5..20907cda1 100644 --- a/src/tools/skill/description-formatter.ts +++ b/src/tools/skill/description-formatter.ts @@ -38,14 +38,17 @@ function formatSlashCommand(command: CommandInfo): string { return lines.join("\n") } -export function formatCombinedDescription(skills: SkillInfo[], commands: CommandInfo[]): string { - if (skills.length === 0 && commands.length === 0) { +export function formatCombinedDescription(skills?: SkillInfo[], commands?: CommandInfo[]): string { + const availableSkills = skills ?? [] + const availableCommands = commands ?? [] + + if (availableSkills.length === 0 && availableCommands.length === 0) { return TOOL_DESCRIPTION_NO_SKILLS } const availableItems = [ - ...sortByScopePriority(skills).map(formatSkillCommand), - ...sortByScopePriority(commands).map(formatSlashCommand), + ...sortByScopePriority(availableSkills).map(formatSkillCommand), + ...sortByScopePriority(availableCommands).map(formatSlashCommand), ] if (availableItems.length === 0) { diff --git a/src/tools/skill/session-skill-cache.ts b/src/tools/skill/session-skill-cache.ts new file mode 100644 index 000000000..040979ddb --- /dev/null +++ b/src/tools/skill/session-skill-cache.ts @@ -0,0 +1,10 @@ +const seenSessionIDs = new Set() + +export function shouldInvalidateSkillCacheForSession(sessionID?: string): boolean { + if (!sessionID || seenSessionIDs.has(sessionID)) { + return false + } + + seenSessionIDs.add(sessionID) + return true +} diff --git a/src/tools/skill/tools.factory.test.ts b/src/tools/skill/tools.factory.test.ts new file mode 100644 index 000000000..5b9a5ba30 --- /dev/null +++ b/src/tools/skill/tools.factory.test.ts @@ -0,0 +1,132 @@ +/// + +import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test" +import type { ToolContext } from "@opencode-ai/plugin/tool" +import type { LoadedSkill } from "../../features/opencode-skill-loader/types" +import * as skillContent from "../../features/opencode-skill-loader/skill-content" + +const discoverCommandsSync = mock(() => []) + +mock.module("../slashcommand/command-discovery", () => ({ + discoverCommandsSync, +})) + +function createMockSkill(name: string): LoadedSkill { + return { + name, + definition: { + name, + description: `Test skill ${name}`, + template: `Test skill template for ${name}`, + }, + scope: "config", + } +} + +async function flushMicrotasks(): Promise { + await Promise.resolve() + await Promise.resolve() +} + +const loadedSkill = createMockSkill("lazy-skill") +const getAllSkills = mock(async () => [loadedSkill]) +const clearSkillCache = mock(() => {}) +const mockContext: ToolContext = { + sessionID: "test-session", + messageID: "msg-1", + agent: "test-agent", + directory: "/test", + worktree: "/test", + abort: new AbortController().signal, + metadata: () => {}, + ask: async () => {}, +} + +function createMockContext(sessionID: string): ToolContext { + return { + ...mockContext, + sessionID, + } +} + +beforeEach(() => { + spyOn(skillContent, "getAllSkills").mockImplementation(getAllSkills) + spyOn(skillContent, "clearSkillCache").mockImplementation(clearSkillCache) +}) + +afterEach(async () => { + await flushMicrotasks() + mock.restore() +}) + +describe("createSkillTool", () => { + it("delays command discovery until the description getter is accessed", async () => { + // given + const baselineDiscoverCommandsSyncCalls = discoverCommandsSync.mock.calls.length + + // when + const { createSkillTool } = await import("./tools") + const skillTool = createSkillTool({}) + + // then + expect(discoverCommandsSync.mock.calls.length).toBe(baselineDiscoverCommandsSyncCalls) + + void skillTool.description + await flushMicrotasks() + + expect(discoverCommandsSync.mock.calls.length).toBe(baselineDiscoverCommandsSyncCalls + 1) + }) + + it("delays skill loading until execute is invoked", async () => { + // given + const baselineGetAllSkillsCalls = getAllSkills.mock.calls.length + + // when + const { createSkillTool } = await import("./tools") + const skillTool = createSkillTool({}) + + // then + expect(getAllSkills.mock.calls.length).toBe(baselineGetAllSkillsCalls) + + await skillTool.execute({ name: "lazy-skill" }, mockContext) + + expect(getAllSkills.mock.calls.length).toBe(baselineGetAllSkillsCalls + 1) + }) + + it("clears the shared skill cache once on first execute in a session", async () => { + // given + const baselineClearSkillCacheCalls = clearSkillCache.mock.calls.length + const sessionContext = createMockContext("session-clear-once") + + // when + const { createSkillTool } = await import("./tools") + const skillTool = createSkillTool({}) + void skillTool.description + await flushMicrotasks() + await skillTool.execute({ name: "lazy-skill" }, sessionContext) + await skillTool.execute({ name: "lazy-skill" }, sessionContext) + + // then + expect(clearSkillCache.mock.calls.length).toBe(baselineClearSkillCacheCalls + 1) + }) + + it("clears the skill discovery cache once per session", async () => { + // given + const baselineClearSkillCacheCalls = clearSkillCache.mock.calls.length + const baselineGetAllSkillsCalls = getAllSkills.mock.calls.length + const sessionAContext = createMockContext("session-a") + const sessionBContext = createMockContext("session-b") + const { createSkillTool } = await import("./tools") + const skillTool = createSkillTool({}) + + // when + await skillTool.execute({ name: "lazy-skill" }, sessionAContext) + await skillTool.execute({ name: "lazy-skill" }, sessionAContext) + await skillTool.execute({ name: "lazy-skill" }, sessionBContext) + await skillTool.execute({ name: "lazy-skill" }, sessionBContext) + + // then + expect(clearSkillCache.mock.calls.length).toBe(baselineClearSkillCacheCalls + 2) + expect(getAllSkills.mock.calls.length).toBe(baselineGetAllSkillsCalls + 4) + }) +}) diff --git a/src/tools/skill/tools.ts b/src/tools/skill/tools.ts index 0fada5607..d49936f95 100644 --- a/src/tools/skill/tools.ts +++ b/src/tools/skill/tools.ts @@ -2,9 +2,10 @@ import { dirname } from "node:path" import { tool, type ToolDefinition } from "@opencode-ai/plugin" import type { ToolContext } from "@opencode-ai/plugin/tool" import { TOOL_DESCRIPTION_PREFIX } from "./constants" +import { shouldInvalidateSkillCacheForSession } from "./session-skill-cache" import type { SkillArgs, SkillLoadOptions } from "./types" import type { LoadedSkill } from "../../features/opencode-skill-loader" -import { getAllSkills, clearSkillCache } from "../../features/opencode-skill-loader/skill-content" +import { clearSkillCache, getAllSkills } from "../../features/opencode-skill-loader/skill-content" import { injectGitMasterConfig } from "../../features/opencode-skill-loader/skill-content" import { discoverCommandsSync } from "../slashcommand/command-discovery" import type { CommandInfo } from "../slashcommand/types" @@ -27,12 +28,15 @@ import { export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition { let cachedDescription: string | null = null - const getSkills = async (): Promise => { - clearSkillCache() - const discovered = await getAllSkills({ + const getSkills = async (context?: ToolContext): Promise => { + if (shouldInvalidateSkillCacheForSession(context?.sessionID)) { + clearSkillCache() + } + + const discovered = (await getAllSkills({ disabledSkills: options?.disabledSkills, browserProvider: options?.browserProvider, - }) + })) ?? [] const allSkills = !options.skills ? discovered : [ @@ -57,7 +61,7 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition return discoverCommandsSync(undefined, { pluginsEnabled: options.pluginsEnabled, enabledPluginsOverride: options.enabledPluginsOverride, - }) + }) ?? [] } const buildDescription = async (force = false): Promise => { @@ -92,8 +96,6 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition } } else if (options.commands !== undefined) { cachedDescription = formatCombinedDescription([], options.commands) - } else { - void buildDescription() } return tool({ @@ -111,7 +113,7 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition .describe("Optional arguments or context for command invocation. Example: name='publish', user_message='patch'"), }, async execute(args: SkillArgs, ctx?: ToolContext) { - const skills = await getSkills() + const skills = await getSkills(ctx) const commands = getCommands() cachedDescription = formatCombinedDescription(skills.map(loadedSkillToInfo), commands) diff --git a/src/tools/skill/zauc-mocks-skill-tools/tools.test.ts b/src/tools/skill/zauc-mocks-skill-tools/tools.test.ts index 5dac1e7d9..32dd83bde 100644 --- a/src/tools/skill/zauc-mocks-skill-tools/tools.test.ts +++ b/src/tools/skill/zauc-mocks-skill-tools/tools.test.ts @@ -1,7 +1,14 @@ +/// + +declare const require: NodeJS.Require + import { afterAll, beforeEach, describe, expect, it, mock, spyOn } from "bun:test" import type { ToolContext } from "@opencode-ai/plugin/tool" import * as fs from "node:fs" +import { tmpdir } from "node:os" +import { join } from "node:path" import { SkillMcpManager } from "../../../features/skill-mcp-manager" +import { clearSkillCache } from "../../../features/opencode-skill-loader/skill-content" import type { LoadedSkill } from "../../../features/opencode-skill-loader/types" import type { CommandInfo } from "../../slashcommand/types" import type { Tool as McpTool } from "@modelcontextprotocol/sdk/types.js" @@ -10,7 +17,24 @@ const originalReadFileSync = fs.readFileSync.bind(fs) let createSkillTool: typeof import("../tools").createSkillTool -beforeEach(async () => { +function clearRequireCache(modulePath: string): void { + const resolvedPath = require.resolve(modulePath) + if (require.cache?.[resolvedPath]) { + delete require.cache[resolvedPath] + } +} + +function requireFresh(modulePath: string): TModule { + clearRequireCache(modulePath) + return require(modulePath) as TModule +} + +beforeEach(() => { + mock.restore() + clearRequireCache("../tools") + clearRequireCache("../../../features/opencode-skill-loader/skill-content") + clearRequireCache("../../slashcommand/command-discovery") + mock.module("node:fs", () => ({ ...fs, readFileSync: (path: string, encoding?: string) => { @@ -23,9 +47,8 @@ Test skill body content` return originalReadFileSync(path, encoding as BufferEncoding) }, })) - - const module = await import("../tools") - createSkillTool = module.createSkillTool + + createSkillTool = requireFresh("../tools").createSkillTool }) afterAll(() => { @@ -548,16 +571,43 @@ describe("skill tool - ordering and priority", () => { }) describe("skill tool - dynamic discovery", () => { - it("discovers skills from disk on every invocation instead of caching", async () => { - // given: tool created with initial skills - const initialSkills = [createMockSkill("initial-skill")] - const tool = createSkillTool({ skills: initialSkills }) + it("caches discovered skills across tool instances until the shared cache resets", async () => { + // given + clearSkillCache() + const originalDirectory = process.cwd() + const temporaryDirectory = fs.mkdtempSync(join(tmpdir(), "skill-tool-cache-")) + const initialSkillDirectory = join(temporaryDirectory, ".opencode", "skills", "initial-skill") + const secondSkillDirectory = join(temporaryDirectory, ".opencode", "skills", "second-skill") - // when: executing with the initial skill name - const result = await tool.execute({ name: "initial-skill" }, mockContext) + fs.mkdirSync(initialSkillDirectory, { recursive: true }) + fs.writeFileSync(join(initialSkillDirectory, "SKILL.md"), "---\ndescription: Initial skill\n---\nInitial skill body") + process.chdir(temporaryDirectory) - // then: initial skill found (merged from options.skills since not on disk) - expect(result).toContain("Skill: initial-skill") + try { + const firstTool = createSkillTool({}) + + // when + const initialResult = await firstTool.execute({ name: "initial-skill" }, mockContext) + + fs.mkdirSync(secondSkillDirectory, { recursive: true }) + fs.writeFileSync(join(secondSkillDirectory, "SKILL.md"), "---\ndescription: Second skill\n---\nSecond skill body") + + const cachedTool = createSkillTool({}) + + // then + expect(initialResult).toContain("Skill: initial-skill") + let cachedError: Error | undefined + try { + await cachedTool.execute({ name: "second-skill" }, mockContext) + } catch (error) { + cachedError = error instanceof Error ? error : new Error(String(error)) + } + expect(cachedError?.message).toContain('Skill or command "second-skill" not found.') + } finally { + process.chdir(originalDirectory) + clearSkillCache() + fs.rmSync(temporaryDirectory, { recursive: true, force: true }) + } }) it("merges pre-provided skills with dynamically discovered ones", async () => { @@ -586,59 +636,66 @@ describe("skill tool - dynamic discovery", () => { }) }) describe("skill tool - dynamic description cache invalidation", () => { - it("rebuilds description after execute() discovers new skills", async () => { - // given: tool created with initial skills (no pre-provided skills) - // This triggers lazy description building + it("keeps description available after execute misses a skill", async () => { + // given const tool = createSkillTool({}) - - // Get initial description - it will build from empty or disk skills + + // when const initialDescription = tool.description expect(initialDescription).toBeString() - - // when: execute() is called, which clears cache AND gets fresh skills - // Note: In real scenario, execute() would discover new skills from disk - // For testing, we verify the mechanism: execute() should invalidate cachedDescription - - // Execute any skill to trigger the cache clear + getSkills flow - // Using a non-existent skill name to trigger the error path which still goes through getSkills() + try { await tool.execute({ name: "nonexistent-skill-12345" }, mockContext) - } catch (e) { - // Expected to fail - skill doesn't exist + } catch { } - - // then: cachedDescription should be invalidated, so next description access should rebuild - // We verify by checking that the description getter triggers a rebuild - // Since we can't easily mock getAllSkills in this test, we verify the cache invalidation mechanism - - // The key assertion: after execute(), the description should be rebuildable - // If cachedDescription wasn't invalidated, it would still return old value - // We verify by checking that the tool still has valid description structure + + // then expect(tool.description).toBeDefined() expect(typeof tool.description).toBe("string") }) - it("description reflects fresh skills after execute() clears cache", async () => { - // given: tool created without pre-provided skills (will use disk discovery) - const tool = createSkillTool({}) - - // when: execute() is called with a skill that exists on disk (via mock) - // This simulates the real scenario: execute() discovers skills, cache should be invalidated - - // Execute to trigger the cache invalidation path + it("picks up new disk skills only after the shared skill cache resets", async () => { + // given + clearSkillCache() + const originalDirectory = process.cwd() + const temporaryDirectory = fs.mkdtempSync(join(tmpdir(), "skill-tool-refresh-")) + const initialSkillDirectory = join(temporaryDirectory, ".opencode", "skills", "initial-skill") + const secondSkillDirectory = join(temporaryDirectory, ".opencode", "skills", "second-skill") + + fs.mkdirSync(initialSkillDirectory, { recursive: true }) + fs.writeFileSync(join(initialSkillDirectory, "SKILL.md"), "---\ndescription: Initial skill\n---\nInitial skill body") + process.chdir(temporaryDirectory) + try { - // This will call getSkills() which clears cache - await tool.execute({ name: "nonexistent" }, mockContext) - } catch (e) { - // Expected + const initialTool = createSkillTool({}) + await initialTool.execute({ name: "initial-skill" }, mockContext) + + fs.mkdirSync(secondSkillDirectory, { recursive: true }) + fs.writeFileSync(join(secondSkillDirectory, "SKILL.md"), "---\ndescription: Second skill\n---\nSecond skill body") + + const cachedTool = createSkillTool({}) + let cachedError: Error | undefined + try { + await cachedTool.execute({ name: "second-skill" }, mockContext) + } catch (error) { + cachedError = error instanceof Error ? error : new Error(String(error)) + } + expect(cachedError?.message).toContain('Skill or command "second-skill" not found.') + + clearSkillCache() + const refreshedTool = createSkillTool({}) + + // when + const refreshedResult = await refreshedTool.execute({ name: "second-skill" }, mockContext) + + // then + expect(refreshedResult).toContain("Skill: second-skill") + expect(refreshedTool.description).toContain("second-skill") + } finally { + process.chdir(originalDirectory) + clearSkillCache() + fs.rmSync(temporaryDirectory, { recursive: true, force: true }) } - - // then: description should still work and not be stale - // The bug would cause it to return old cached value forever - const desc = tool.description - - // Verify description is a valid string (not stale/old) - expect(desc).toContain("skill") }) }) diff --git a/src/tools/slashcommand/command-discovery.test.ts b/src/tools/slashcommand/command-discovery.test.ts index e82cd7653..fc193b61f 100644 --- a/src/tools/slashcommand/command-discovery.test.ts +++ b/src/tools/slashcommand/command-discovery.test.ts @@ -326,4 +326,40 @@ describe("non-directory commands path", () => { expect(testCmd).toBeDefined() expect(testCmd?.content).toContain("Test command content.") }) + + it("#given excluded subdirectories under .claude/commands #when discoverCommandsSync runs #then prunes commands beneath them", () => { + // given + const projectDir = join(testDir, "project") + const commandsDir = join(projectDir, ".claude", "commands") + + mkdirSync(join(commandsDir, "node_modules", "fake-pkg"), { recursive: true }) + mkdirSync(join(commandsDir, ".git", "branches"), { recursive: true }) + mkdirSync(join(commandsDir, "dist"), { recursive: true }) + writeFileSync( + join(commandsDir, "real-cmd.md"), + "---\ndescription: Real command\n---\nRun real command.\n", + ) + writeFileSync( + join(commandsDir, "node_modules", "fake-pkg", "cmd.md"), + "---\ndescription: Nested command\n---\nRun nested command.\n", + ) + writeFileSync( + join(commandsDir, ".git", "branches", "cmd.md"), + "---\ndescription: Git command\n---\nRun git command.\n", + ) + writeFileSync( + join(commandsDir, "dist", "bundled-cmd.md"), + "---\ndescription: Bundled command\n---\nRun bundled command.\n", + ) + + // when + const commands = discoverCommandsSync(projectDir) + const names = commands.map((command) => command.name) + + // then + expect(names).toContain("real-cmd") + expect(names).not.toContain("node_modules/fake-pkg/cmd") + expect(names).not.toContain(".git/branches/cmd") + expect(names).not.toContain("dist/bundled-cmd") + }) }) diff --git a/src/tools/slashcommand/command-discovery.ts b/src/tools/slashcommand/command-discovery.ts index 7d220ab4f..855f6dc28 100644 --- a/src/tools/slashcommand/command-discovery.ts +++ b/src/tools/slashcommand/command-discovery.ts @@ -6,6 +6,7 @@ import { findProjectOpencodeCommandDirs, getOpenCodeCommandDirs, discoverPluginCommandDefinitions, + EXCLUDED_DIRS, } from "../../shared" import type { CommandFrontmatter } from "../../features/claude-code-command-loader/types" import { isMarkdownFile } from "../../shared/file-utils" @@ -36,6 +37,7 @@ function discoverCommandsFromDir( for (const entry of entries) { if (entry.isDirectory()) { + if (EXCLUDED_DIRS.has(entry.name)) continue if (entry.name.startsWith(".")) continue const nestedPrefix = prefix ? `${prefix}${NESTED_COMMAND_SEPARATOR}${entry.name}` diff --git a/src/tools/slashcommand/execution-compatibility.test.ts b/src/tools/slashcommand/execution-compatibility.test.ts index 6d63bd678..a33b4bcc4 100644 --- a/src/tools/slashcommand/execution-compatibility.test.ts +++ b/src/tools/slashcommand/execution-compatibility.test.ts @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test" import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs" import { tmpdir } from "node:os" import { join } from "node:path" +import { clearCommandLoaderCache } from "../../features/claude-code-command-loader" function requireFresh(modulePath: string): T { const resolvedPath = require.resolve(modulePath) @@ -25,12 +26,14 @@ describe("slashcommand discovery and execution compatibility", () => { let originalOpencodeConfigDir: string | undefined beforeEach(() => { + clearCommandLoaderCache() tempDir = mkdtempSync(join(tmpdir(), "omo-slashcommand-compat-test-")) originalWorkingDirectory = process.cwd() originalOpencodeConfigDir = process.env.OPENCODE_CONFIG_DIR }) afterEach(() => { + clearCommandLoaderCache() process.chdir(originalWorkingDirectory) if (originalOpencodeConfigDir === undefined) {