mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
Merge branch 'RooCodeInc:main' into feat/fix-bedrock
This commit is contained in:
commit
f89044e295
113 changed files with 3416 additions and 769 deletions
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
"roo-cline": patch
|
||||
"@roo-code/types": patch
|
||||
---
|
||||
|
||||
Add support for OpenAI `gpt-5.4-mini` and `gpt-5.4-nano` models.
|
||||
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -1,5 +1,20 @@
|
|||
# Roo Code Changelog
|
||||
|
||||
## 3.52.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Add Poe as an AI provider so users can access Poe models directly in Roo Code (PR #12015 by @kamilio)
|
||||
- Improve the xAI provider by migrating it to the Responses API with reusable transform utilities (#11961 by @carlesso, PR #11962 by @carlesso)
|
||||
- Fix MiniMax model listings and context window handling for more reliable configuration (#11999 by @Rexarrior, PR #12069 by @Rexarrior)
|
||||
- Add xAI Grok-4.20 models and update the default xAI model selection (#11955 by @carlesso, PR #11956 by @carlesso)
|
||||
- Add OpenAI GPT-5.4 mini and nano models to expand the available OpenAI model lineup (PR #11946 by @PeterDaveHello)
|
||||
- Chore: include the automated version bump PR from the previous release cycle for complete release accounting (PR #11892 by @app/github-actions)
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Add support for OpenAI `gpt-5.4-mini` and `gpt-5.4-nano` models.
|
||||
|
||||
## 3.51.1
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> Your AI-Powered Dev Team, Right in Your Editor
|
||||
|
||||
## What's New in v3.51.0
|
||||
## What's New in v3.52.0
|
||||
|
||||
- Add support for OpenAI GPT-5.4 and GPT-5.3 Chat Latest so you can use the newest OpenAI chat models in Roo Code.
|
||||
- Expose skills as slash commands with fallback execution to make reusable workflows faster to trigger.
|
||||
- Add Poe as an AI provider so you can access Poe models directly in Roo Code.
|
||||
- Improve the xAI provider with a Responses API migration, reusable transform utilities, and updated Grok-4.20 defaults.
|
||||
- Fix MiniMax model listings and context window handling for more reliable setup.
|
||||
|
||||
<details>
|
||||
<summary>🌐 Available languages</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](locales/zh-CN/README.md)
|
||||
- [繁體中文](locales/zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
3
apps/web-evals/CHANGELOG.md
Normal file
3
apps/web-evals/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# @roo-code/web-evals
|
||||
|
||||
## 0.0.1
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@roo-code/web-evals",
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"lint": "eslint src --ext=ts,tsx --max-warnings=0",
|
||||
|
|
|
|||
3
apps/web-roo-code/CHANGELOG.md
Normal file
3
apps/web-roo-code/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# @roo-code/web-roo-code
|
||||
|
||||
## 0.0.1
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@roo-code/web-roo-code",
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"lint": "eslint src --ext=ts,tsx --max-warnings=0",
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
"@radix-ui/react-navigation-menu": "^1.2.14",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@roo-code/evals": "workspace:^",
|
||||
"@roo-code/types": "^1.108.0",
|
||||
"@roo-code/types": "workspace:^",
|
||||
"@tanstack/react-query": "^5.90.20",
|
||||
"@vercel/og": "^0.8.6",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
|
|
|
|||
9
locales/ca/README.md
generated
9
locales/ca/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> El teu equip de desenvolupament impulsat per IA, directament al teu editor
|
||||
|
||||
## Novetats de la v3.51.0
|
||||
## Novetats de la v3.52.0
|
||||
|
||||
- S'ha afegit compatibilitat amb OpenAI GPT-5.4 i GPT-5.3 Chat Latest perquè puguis utilitzar els models de xat més nous d'OpenAI a Roo Code.
|
||||
- Les skills es poden exposar com a ordres slash amb execució de reserva perquè els fluxos de treball reutilitzables s'activin més de pressa.
|
||||
- S'ha afegit Poe com a proveïdor d'IA perquè puguis accedir directament als models de Poe a Roo Code.
|
||||
- S'ha millorat el proveïdor xAI amb una migració a la Responses API, utilitats de transformació reutilitzables i els valors per defecte actualitzats de Grok-4.20.
|
||||
- S'han corregit els llistats de models de MiniMax i la gestió de la finestra de context per oferir una configuració més fiable.
|
||||
|
||||
<details>
|
||||
<summary>🌐 Idiomes disponibles</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
9
locales/de/README.md
generated
9
locales/de/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> Dein KI-gestütztes Dev-Team – direkt in deinem Editor
|
||||
|
||||
## Neu in v3.51.0
|
||||
## Neu in v3.52.0
|
||||
|
||||
- Unterstützung für OpenAI GPT-5.4 und GPT-5.3 Chat Latest hinzugefügt, damit du die neuesten OpenAI-Chatmodelle in Roo Code nutzen kannst.
|
||||
- Skills als Slash-Commands mit Fallback-Ausführung verfügbar gemacht, damit wiederverwendbare Workflows schneller ausgelöst werden können.
|
||||
- Poe als KI-Anbieter hinzugefügt, damit du direkt in Roo Code auf Poe-Modelle zugreifen kannst.
|
||||
- Den xAI-Anbieter mit einer Migration auf die Responses API, wiederverwendbaren Transformations-Utilities und aktualisierten Grok-4.20-Standards verbessert.
|
||||
- MiniMax-Modelllisten und die Handhabung des Kontextfensters korrigiert, damit die Einrichtung zuverlässiger funktioniert.
|
||||
|
||||
<details>
|
||||
<summary>🌐 Verfügbare Sprachen</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
9
locales/es/README.md
generated
9
locales/es/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> Tu equipo de desarrollo con IA, directamente en tu editor
|
||||
|
||||
## Novedades de la v3.51.0
|
||||
## Novedades de la v3.52.0
|
||||
|
||||
- Se añadió compatibilidad con OpenAI GPT-5.4 y GPT-5.3 Chat Latest para que puedas usar los modelos de chat más recientes de OpenAI en Roo Code.
|
||||
- Las skills ahora pueden exponerse como comandos slash con ejecución de respaldo para activar más rápido los flujos de trabajo reutilizables.
|
||||
- Se añadió Poe como proveedor de IA para que puedas acceder directamente a los modelos de Poe en Roo Code.
|
||||
- Se mejoró el proveedor xAI con una migración a la Responses API, utilidades de transformación reutilizables y valores predeterminados actualizados de Grok-4.20.
|
||||
- Se corrigieron los listados de modelos de MiniMax y el manejo de la ventana de contexto para una configuración más fiable.
|
||||
|
||||
<details>
|
||||
<summary>🌐 Idiomas disponibles</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
9
locales/fr/README.md
generated
9
locales/fr/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> Ton équipe de dev propulsée par l'IA, directement dans ton éditeur
|
||||
|
||||
## Nouveautés de la v3.51.0
|
||||
## Nouveautés de la v3.52.0
|
||||
|
||||
- Ajout de la prise en charge d’OpenAI GPT-5.4 et de GPT-5.3 Chat Latest pour que tu puisses utiliser les modèles de chat OpenAI les plus récents dans Roo Code.
|
||||
- Les skills peuvent désormais être exposées comme slash commands avec une exécution de secours pour déclencher plus vite des workflows réutilisables.
|
||||
- Ajout de Poe comme fournisseur d’IA pour que tu puisses accéder directement aux modèles Poe dans Roo Code.
|
||||
- Amélioration du fournisseur xAI avec une migration vers la Responses API, des utilitaires de transformation réutilisables et des valeurs par défaut Grok-4.20 mises à jour.
|
||||
- Correction des listes de modèles MiniMax et de la gestion de la fenêtre de contexte pour une configuration plus fiable.
|
||||
|
||||
<details>
|
||||
<summary>🌐 Langues disponibles</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
9
locales/hi/README.md
generated
9
locales/hi/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> तुम्हारी AI-संचालित डेवलपमेंट टीम, सीधे तुम्हारे एडिटर में
|
||||
|
||||
## v3.51.0 में नया क्या है
|
||||
## v3.52.0 में नया क्या है
|
||||
|
||||
- OpenAI GPT-5.4 और GPT-5.3 Chat Latest के लिए सपोर्ट जोड़ा गया है, ताकि आप Roo Code में OpenAI के सबसे नए chat models इस्तेमाल कर सकें।
|
||||
- अब skills को slash commands के रूप में fallback execution के साथ expose किया जा सकता है, जिससे reusable workflows को तेज़ी से trigger किया जा सके।
|
||||
- Poe को AI provider के रूप में जोड़ा गया है, ताकि आप Roo Code में सीधे Poe models तक पहुँच सकें।
|
||||
- xAI provider को Responses API migration, reusable transform utilities, और updated Grok-4.20 defaults के साथ बेहतर बनाया गया है।
|
||||
- ज़्यादा भरोसेमंद setup के लिए MiniMax model listings और context window handling को ठीक किया गया है।
|
||||
|
||||
<details>
|
||||
<summary>🌐 उपलब्ध भाषाएँ</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
9
locales/id/README.md
generated
9
locales/id/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> Tim dev bertenaga AI-mu, langsung di editor kamu
|
||||
|
||||
## Yang Baru di v3.51.0
|
||||
## Yang Baru di v3.52.0
|
||||
|
||||
- Menambahkan dukungan untuk OpenAI GPT-5.4 dan GPT-5.3 Chat Latest agar kamu bisa memakai model chat OpenAI terbaru di Roo Code.
|
||||
- Skill kini bisa diekspos sebagai slash command dengan eksekusi fallback supaya workflow yang bisa dipakai ulang lebih cepat dijalankan.
|
||||
- Menambahkan Poe sebagai provider AI agar kamu bisa langsung mengakses model Poe di Roo Code.
|
||||
- Meningkatkan provider xAI dengan migrasi ke Responses API, utilitas transformasi yang bisa dipakai ulang, dan default Grok-4.20 yang diperbarui.
|
||||
- Memperbaiki daftar model MiniMax dan penanganan context window agar penyiapannya lebih andal.
|
||||
|
||||
<details>
|
||||
<summary>🌐 Bahasa yang tersedia</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
9
locales/it/README.md
generated
9
locales/it/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> Il tuo team di sviluppo con IA, direttamente nel tuo editor
|
||||
|
||||
## Novità in v3.51.0
|
||||
## Novità in v3.52.0
|
||||
|
||||
- Aggiunto il supporto per OpenAI GPT-5.4 e GPT-5.3 Chat Latest così puoi usare i modelli di chat OpenAI più recenti in Roo Code.
|
||||
- Le skill ora possono essere esposte come slash command con esecuzione di fallback per attivare più rapidamente i workflow riutilizzabili.
|
||||
- Aggiunto Poe come provider AI così puoi accedere direttamente ai modelli Poe in Roo Code.
|
||||
- Migliorato il provider xAI con una migrazione alla Responses API, utility di trasformazione riutilizzabili e valori predefiniti aggiornati per Grok-4.20.
|
||||
- Corretti gli elenchi dei modelli MiniMax e la gestione della finestra di contesto per una configurazione più affidabile.
|
||||
|
||||
<details>
|
||||
<summary>🌐 Lingue disponibili</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
9
locales/ja/README.md
generated
9
locales/ja/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> あなたのエディタの中に、AIで強化された開発チームを
|
||||
|
||||
## v3.51.0 の新機能
|
||||
## v3.52.0 の新機能
|
||||
|
||||
- OpenAI GPT-5.4 と GPT-5.3 Chat Latest のサポートを追加し、Roo Code で OpenAI の最新チャットモデルを使えるようにしました。
|
||||
- スキルをスラッシュコマンドとして公開し、フォールバック実行にも対応したことで、再利用可能なワークフローをより素早く呼び出せます。
|
||||
- Poe を AI プロバイダーとして追加し、Roo Code から Poe のモデルに直接アクセスできるようにしました。
|
||||
- xAI プロバイダーを Responses API への移行、再利用可能な変換ユーティリティ、更新された Grok-4.20 のデフォルト設定で改善しました。
|
||||
- より信頼性の高いセットアップのために、MiniMax のモデル一覧とコンテキストウィンドウの扱いを修正しました。
|
||||
|
||||
<details>
|
||||
<summary>🌐 利用可能な言語</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
9
locales/ko/README.md
generated
9
locales/ko/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> AI로 강화된 너의 개발 팀, 네 에디터 안에
|
||||
|
||||
## v3.51.0의 새로운 기능
|
||||
## v3.52.0의 새로운 기능
|
||||
|
||||
- OpenAI GPT-5.4와 GPT-5.3 Chat Latest 지원을 추가해 Roo Code에서 최신 OpenAI 채팅 모델을 사용할 수 있어요.
|
||||
- 이제 스킬을 슬래시 명령어로 노출하고 fallback 실행도 지원해 재사용 가능한 워크플로를 더 빠르게 트리거할 수 있어요.
|
||||
- Poe를 AI 제공업체로 추가해 Roo Code에서 Poe 모델에 직접 접근할 수 있게 했어요.
|
||||
- Responses API 마이그레이션, 재사용 가능한 변환 유틸리티, 업데이트된 Grok-4.20 기본값으로 xAI 제공업체를 개선했어요.
|
||||
- 더 안정적인 설정을 위해 MiniMax 모델 목록과 컨텍스트 윈도우 처리를 수정했어요.
|
||||
|
||||
<details>
|
||||
<summary>🌐 사용 가능한 언어</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
9
locales/nl/README.md
generated
9
locales/nl/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> Jouw AI-aangedreven dev-team, rechtstreeks in je editor
|
||||
|
||||
## Nieuw in v3.51.0
|
||||
## Nieuw in v3.52.0
|
||||
|
||||
- Ondersteuning toegevoegd voor OpenAI GPT-5.4 en GPT-5.3 Chat Latest, zodat je de nieuwste OpenAI-chatmodellen in Roo Code kunt gebruiken.
|
||||
- Skills beschikbaar gemaakt als slash-commands met fallback-uitvoering, zodat herbruikbare workflows sneller te starten zijn.
|
||||
- Poe toegevoegd als AI-provider, zodat je in Roo Code direct toegang hebt tot Poe-modellen.
|
||||
- De xAI-provider verbeterd met een migratie naar de Responses API, herbruikbare transformatiehulpmiddelen en bijgewerkte Grok-4.20-standaardinstellingen.
|
||||
- MiniMax-modellijsten en de afhandeling van het contextvenster gerepareerd voor een betrouwbaardere configuratie.
|
||||
|
||||
<details>
|
||||
<summary>🌐 Beschikbare talen</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
9
locales/pl/README.md
generated
9
locales/pl/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> Twój zespół deweloperski zasilany AI — prosto w edytorze
|
||||
|
||||
## Nowości w v3.51.0
|
||||
## Nowości w v3.52.0
|
||||
|
||||
- Dodano obsługę OpenAI GPT-5.4 i GPT-5.3 Chat Latest, żebyś mógł używać najnowszych modeli czatu OpenAI w Roo Code.
|
||||
- Skills można teraz udostępniać jako slash commandy z wykonaniem awaryjnym, żeby szybciej uruchamiać wielokrotnego użytku workflowy.
|
||||
- Dodano Poe jako dostawcę AI, żebyś mógł bezpośrednio uzyskać dostęp do modeli Poe w Roo Code.
|
||||
- Ulepszono dostawcę xAI dzięki migracji do Responses API, narzędziom transformacji wielokrotnego użytku i zaktualizowanym domyślnym ustawieniom Grok-4.20.
|
||||
- Naprawiono listy modeli MiniMax i obsługę okna kontekstu, żeby konfiguracja była bardziej niezawodna.
|
||||
|
||||
<details>
|
||||
<summary>🌐 Dostępne języki</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
9
locales/pt-BR/README.md
generated
9
locales/pt-BR/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> Seu time de desenvolvimento com IA, direto no seu editor
|
||||
|
||||
## Novidades na v3.51.0
|
||||
## Novidades na v3.52.0
|
||||
|
||||
- Adicionamos suporte ao OpenAI GPT-5.4 e ao GPT-5.3 Chat Latest para você usar os modelos de chat mais novos da OpenAI no Roo Code.
|
||||
- Agora as skills podem ser expostas como comandos slash com execução de fallback para acionar workflows reutilizáveis mais rápido.
|
||||
- Adicionamos o Poe como provedor de IA para você acessar os modelos do Poe diretamente no Roo Code.
|
||||
- Melhoramos o provedor xAI com uma migração para a Responses API, utilitários de transformação reutilizáveis e padrões atualizados do Grok-4.20.
|
||||
- Corrigimos as listagens de modelos do MiniMax e o tratamento da janela de contexto para uma configuração mais confiável.
|
||||
|
||||
<details>
|
||||
<summary>🌐 Idiomas disponíveis</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
9
locales/ru/README.md
generated
9
locales/ru/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> Твоя команда разработчиков на ИИ — прямо в редакторе
|
||||
|
||||
## Что нового в v3.51.0
|
||||
## Что нового в v3.52.0
|
||||
|
||||
- Добавлена поддержка OpenAI GPT-5.4 и GPT-5.3 Chat Latest, чтобы ты мог использовать новейшие чат-модели OpenAI в Roo Code.
|
||||
- Skills теперь можно открывать как slash-команды с резервным выполнением, чтобы быстрее запускать переиспользуемые рабочие процессы.
|
||||
- Добавлен Poe как AI-провайдер, чтобы ты мог напрямую получать доступ к моделям Poe в Roo Code.
|
||||
- Улучшен провайдер xAI: выполнена миграция на Responses API, добавлены переиспользуемые утилиты трансформации и обновлены настройки Grok-4.20 по умолчанию.
|
||||
- Исправлены списки моделей MiniMax и обработка окна контекста для более надёжной настройки.
|
||||
|
||||
<details>
|
||||
<summary>🌐 Доступные языки</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
9
locales/tr/README.md
generated
9
locales/tr/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> AI destekli dev ekibin, doğrudan editörünün içinde
|
||||
|
||||
## v3.51.0'daki Yenilikler
|
||||
## v3.52.0'daki Yenilikler
|
||||
|
||||
- Roo Code içinde en yeni OpenAI sohbet modellerini kullanabilmen için OpenAI GPT-5.4 ve GPT-5.3 Chat Latest desteği eklendi.
|
||||
- Skills artık fallback yürütmeyle slash komutları olarak sunulabiliyor, böylece yeniden kullanılabilir iş akışları daha hızlı tetiklenebiliyor.
|
||||
- Poe, Roo Code içinde Poe modellerine doğrudan erişebilmen için bir AI sağlayıcısı olarak eklendi.
|
||||
- xAI sağlayıcısı, Responses API geçişi, yeniden kullanılabilir dönüştürme yardımcıları ve güncellenmiş Grok-4.20 varsayılanlarıyla iyileştirildi.
|
||||
- Daha güvenilir bir kurulum için MiniMax model listeleri ve context window işleme düzeltildi.
|
||||
|
||||
<details>
|
||||
<summary>🌐 Mevcut diller</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
9
locales/vi/README.md
generated
9
locales/vi/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> Đội ngũ dev dùng AI của bạn, ngay trong trình chỉnh sửa
|
||||
|
||||
## Điểm mới trong v3.51.0
|
||||
## Điểm mới trong v3.52.0
|
||||
|
||||
- Đã thêm hỗ trợ cho OpenAI GPT-5.4 và GPT-5.3 Chat Latest để bạn có thể dùng các mô hình chat OpenAI mới nhất trong Roo Code.
|
||||
- Skills giờ có thể được cung cấp dưới dạng lệnh slash kèm thực thi dự phòng để kích hoạt các quy trình tái sử dụng nhanh hơn.
|
||||
- Đã thêm Poe làm nhà cung cấp AI để bạn có thể truy cập trực tiếp các model Poe trong Roo Code.
|
||||
- Đã cải thiện nhà cung cấp xAI với quá trình chuyển sang Responses API, các tiện ích chuyển đổi có thể tái sử dụng và các giá trị mặc định Grok-4.20 đã được cập nhật.
|
||||
- Đã sửa danh sách model MiniMax và cách xử lý context window để thiết lập đáng tin cậy hơn.
|
||||
|
||||
<details>
|
||||
<summary>🌐 Các ngôn ngữ có sẵn</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
9
locales/zh-CN/README.md
generated
9
locales/zh-CN/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> 你的 AI 驱动开发团队,就在你的编辑器里
|
||||
|
||||
## v3.51.0 新增内容
|
||||
## v3.52.0 新增内容
|
||||
|
||||
- 已添加对 OpenAI GPT-5.4 和 GPT-5.3 Chat Latest 的支持,让你可以在 Roo Code 中使用 OpenAI 最新的聊天模型。
|
||||
- 现在可以将 skills 公开为 slash 命令,并支持 fallback execution,让可复用工作流触发得更快。
|
||||
- 已将 Poe 添加为 AI 提供商,让你可以在 Roo Code 中直接访问 Poe 模型。
|
||||
- 已改进 xAI 提供商,完成了向 Responses API 的迁移,加入了可复用的转换工具,并更新了 Grok-4.20 默认设置。
|
||||
- 已修复 MiniMax 模型列表和上下文窗口处理方式,让配置更加可靠。
|
||||
|
||||
<details>
|
||||
<summary>🌐 可用语言</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
9
locales/zh-TW/README.md
generated
9
locales/zh-TW/README.md
generated
|
|
@ -13,10 +13,11 @@
|
|||
|
||||
> 您的 AI 驅動開發團隊,就在您的編輯器中
|
||||
|
||||
## v3.51.0 新功能
|
||||
## v3.52.0 新功能
|
||||
|
||||
- 已新增對 OpenAI GPT-5.4 和 GPT-5.3 Chat Latest 的支援,讓你可以在 Roo Code 中使用 OpenAI 最新的聊天模型。
|
||||
- 現在可以將 skills 公開為 slash 指令,並支援 fallback execution,讓可重用工作流程觸發得更快。
|
||||
- 已將 Poe 新增為 AI 供應商,讓你可以在 Roo Code 中直接存取 Poe 模型。
|
||||
- 已改善 xAI 供應商,完成 Responses API 遷移、加入可重複使用的轉換工具,並更新了 Grok-4.20 預設值。
|
||||
- 已修正 MiniMax 模型清單和上下文視窗處理方式,讓設定更加可靠。
|
||||
|
||||
<details>
|
||||
<summary>🌐 支援語言</summary>
|
||||
|
|
@ -40,7 +41,7 @@
|
|||
- [简体中文](../zh-CN/README.md)
|
||||
- [繁體中文](../zh-TW/README.md)
|
||||
- ...
|
||||
</details>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
3
packages/cloud/CHANGELOG.md
Normal file
3
packages/cloud/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# @roo-code/cloud
|
||||
|
||||
## 0.0.1
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@roo-code/cloud",
|
||||
"description": "Roo Code Cloud services.",
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"exports": "./src/index.ts",
|
||||
"scripts": {
|
||||
|
|
|
|||
3
packages/core/CHANGELOG.md
Normal file
3
packages/core/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# @roo-code/core
|
||||
|
||||
## 0.0.1
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@roo-code/core",
|
||||
"description": "Platform agnostic core functionality for Roo Code.",
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
|
|
|
|||
3
packages/evals/CHANGELOG.md
Normal file
3
packages/evals/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# @roo-code/evals
|
||||
|
||||
## 0.0.1
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@roo-code/evals",
|
||||
"description": "Roo Code evals.",
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"exports": "./src/index.ts",
|
||||
"scripts": {
|
||||
|
|
|
|||
3
packages/ipc/CHANGELOG.md
Normal file
3
packages/ipc/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# @roo-code/ipc
|
||||
|
||||
## 0.0.1
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@roo-code/ipc",
|
||||
"description": "IPC server and client for remote Roo Code access.",
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"exports": "./src/index.ts",
|
||||
"scripts": {
|
||||
|
|
|
|||
3
packages/telemetry/CHANGELOG.md
Normal file
3
packages/telemetry/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# @roo-code/telemetry
|
||||
|
||||
## 0.0.1
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@roo-code/telemetry",
|
||||
"description": "Roo Code telemetry service and clients.",
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"exports": "./src/index.ts",
|
||||
"scripts": {
|
||||
|
|
|
|||
7
packages/types/CHANGELOG.md
Normal file
7
packages/types/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# @roo-code/types
|
||||
|
||||
## 0.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Add support for OpenAI `gpt-5.4-mini` and `gpt-5.4-nano` models.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@roo-code/types",
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"exports": {
|
||||
|
|
@ -20,9 +20,11 @@
|
|||
"build": "tsup",
|
||||
"build:watch": "tsup --watch --outDir npm/dist --onSuccess 'echo ✅ Types rebuilt to npm/dist'",
|
||||
"npm:publish": "node scripts/publish-npm.cjs",
|
||||
"clean": "rimraf dist .turbo"
|
||||
"clean": "rimraf dist .turbo",
|
||||
"generate:schema": "tsx scripts/generate-roomodes-schema.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"ai-sdk-provider-poe": "2.0.18",
|
||||
"zod": "3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -31,6 +33,8 @@
|
|||
"@types/node": "^24.1.0",
|
||||
"globals": "^16.3.0",
|
||||
"tsup": "^8.4.0",
|
||||
"vitest": "^3.2.3"
|
||||
"ajv": "^8.18.0",
|
||||
"vitest": "^3.2.3",
|
||||
"zod-to-json-schema": "^3.25.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
25
packages/types/scripts/generate-roomodes-schema.ts
Normal file
25
packages/types/scripts/generate-roomodes-schema.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Generates the JSON Schema for .roomodes configuration files from the Zod
|
||||
* schemas defined in packages/types/src/mode.ts.
|
||||
*
|
||||
* This ensures the schema stays in sync with the TypeScript types. Run via:
|
||||
* pnpm --filter @roo-code/types generate:schema
|
||||
*
|
||||
* The output is written to schemas/roomodes.json at the repository root.
|
||||
*/
|
||||
|
||||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
import { generateRoomodesJsonSchema } from "../src/roomodes-schema.js"
|
||||
|
||||
const jsonSchema = generateRoomodesJsonSchema()
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const repoRoot = path.resolve(__dirname, "../../..")
|
||||
const outPath = path.join(repoRoot, "schemas", "roomodes.json")
|
||||
fs.mkdirSync(path.dirname(outPath), { recursive: true })
|
||||
fs.writeFileSync(outPath, JSON.stringify(jsonSchema, null, "\t") + "\n", "utf-8")
|
||||
|
||||
console.log(`Generated ${path.relative(repoRoot, outPath)}`)
|
||||
26
packages/types/src/__tests__/roomodes-schema-sync.spec.ts
Normal file
26
packages/types/src/__tests__/roomodes-schema-sync.spec.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { describe, it, expect } from "vitest"
|
||||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
import { generateRoomodesJsonSchema } from "../roomodes-schema.js"
|
||||
|
||||
/**
|
||||
* This test verifies that the checked-in schemas/roomodes.json matches what
|
||||
* would be generated from the current Zod schemas. If this test fails, run:
|
||||
*
|
||||
* pnpm --filter @roo-code/types generate:schema
|
||||
*
|
||||
* to regenerate the schema file.
|
||||
*/
|
||||
describe("roomodes schema sync", () => {
|
||||
it("should match the dynamically generated schema from Zod types", () => {
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const schemaPath = path.resolve(__dirname, "../../../../schemas/roomodes.json")
|
||||
const checkedIn = JSON.parse(fs.readFileSync(schemaPath, "utf-8"))
|
||||
|
||||
const generated = generateRoomodesJsonSchema()
|
||||
|
||||
expect(checkedIn).toEqual(generated)
|
||||
})
|
||||
})
|
||||
438
packages/types/src/__tests__/roomodes-schema.spec.ts
Normal file
438
packages/types/src/__tests__/roomodes-schema.spec.ts
Normal file
|
|
@ -0,0 +1,438 @@
|
|||
/**
|
||||
* Validates the generated schemas/roomodes.json against sample configurations
|
||||
* using AJV. The schema itself is dynamically generated from the Zod types in
|
||||
* packages/types/src/mode.ts -- see packages/types/scripts/generate-roomodes-schema.ts.
|
||||
*
|
||||
* A separate drift-detection test (roomodes-schema-sync.spec.ts) ensures the
|
||||
* checked-in schema stays in sync with the Zod source of truth.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll } from "vitest"
|
||||
import Ajv, { type ValidateFunction } from "ajv"
|
||||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
describe("roomodes JSON schema", () => {
|
||||
let schema: Record<string, unknown>
|
||||
let validate: ValidateFunction
|
||||
|
||||
beforeAll(() => {
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const schemaPath = path.resolve(__dirname, "../../../../schemas/roomodes.json")
|
||||
schema = JSON.parse(fs.readFileSync(schemaPath, "utf-8"))
|
||||
const ajv = new Ajv.default({ strict: false })
|
||||
validate = ajv.compile(schema)
|
||||
})
|
||||
|
||||
it("should be a valid JSON Schema", () => {
|
||||
expect(validate).toBeDefined()
|
||||
})
|
||||
|
||||
it("should accept a minimal valid .roomodes config", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "my-mode",
|
||||
name: "My Mode",
|
||||
roleDefinition: "You are a helpful assistant.",
|
||||
groups: ["read"],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(validate.errors).toBeNull()
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
|
||||
it("should accept a mode with all optional properties", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "full-mode",
|
||||
name: "Full Mode",
|
||||
roleDefinition: "A complete mode definition.",
|
||||
whenToUse: "Use when you need everything.",
|
||||
description: "A mode with all properties.",
|
||||
customInstructions: "Follow these additional rules.",
|
||||
groups: ["read", "edit", "command", "mcp"],
|
||||
source: "project",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(validate.errors).toBeNull()
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
|
||||
it("should accept the built-in architect mode with tuple-style edit group", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "architect",
|
||||
name: "Architect",
|
||||
roleDefinition: "You are an experienced technical leader.",
|
||||
whenToUse: "Use this mode when you need to plan.",
|
||||
description: "Plan and design before implementation",
|
||||
groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "mcp"],
|
||||
source: "project",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(validate.errors).toBeNull()
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
|
||||
it("should accept a tuple group entry with only fileRegex", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "restricted",
|
||||
name: "Restricted",
|
||||
roleDefinition: "Limited editor.",
|
||||
groups: [["edit", { fileRegex: "\\.ts$" }]],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(validate.errors).toBeNull()
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
|
||||
it("should accept a tuple group entry with empty options", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "empty-opts",
|
||||
name: "Empty Options",
|
||||
roleDefinition: "Mode with empty group options.",
|
||||
groups: [["edit", {}]],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(validate.errors).toBeNull()
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
|
||||
it("should accept the modes tool group", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "orchestrator",
|
||||
name: "Orchestrator",
|
||||
roleDefinition: "You orchestrate other modes.",
|
||||
groups: ["read", "modes"],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(validate.errors).toBeNull()
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
|
||||
it("should reject a config missing customModes", () => {
|
||||
const config = {}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(valid).toBe(false)
|
||||
})
|
||||
|
||||
it("should reject a mode missing required slug", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
name: "No Slug",
|
||||
roleDefinition: "Missing slug.",
|
||||
groups: ["read"],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(valid).toBe(false)
|
||||
})
|
||||
|
||||
it("should reject a mode missing required groups", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "no-groups",
|
||||
name: "No Groups",
|
||||
roleDefinition: "Missing groups.",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(valid).toBe(false)
|
||||
})
|
||||
|
||||
it("should reject a slug with invalid characters", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "invalid slug!",
|
||||
name: "Bad Slug",
|
||||
roleDefinition: "Invalid slug characters.",
|
||||
groups: ["read"],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(valid).toBe(false)
|
||||
})
|
||||
|
||||
it("should reject an invalid tool group name", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "bad-group",
|
||||
name: "Bad Group",
|
||||
roleDefinition: "Invalid group name.",
|
||||
groups: ["nonexistent"],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(valid).toBe(false)
|
||||
})
|
||||
|
||||
it("should reject additional properties on CustomMode", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "extra-props",
|
||||
name: "Extra Props",
|
||||
roleDefinition: "Has extra properties.",
|
||||
groups: ["read"],
|
||||
unknownField: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(valid).toBe(false)
|
||||
})
|
||||
|
||||
it("should reject additional properties on GroupOptions", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "bad-opts",
|
||||
name: "Bad Options",
|
||||
roleDefinition: "Extra options properties.",
|
||||
groups: [["edit", { fileRegex: "\\.md$", unknownOpt: true }]],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(valid).toBe(false)
|
||||
})
|
||||
|
||||
it("should reject a tuple with more than 2 elements", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "big-tuple",
|
||||
name: "Big Tuple",
|
||||
roleDefinition: "Too many tuple elements.",
|
||||
groups: [["edit", { fileRegex: "\\.md$" }, "extra"]],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(valid).toBe(false)
|
||||
})
|
||||
|
||||
it("should reject an invalid source value", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "bad-source",
|
||||
name: "Bad Source",
|
||||
roleDefinition: "Invalid source.",
|
||||
groups: ["read"],
|
||||
source: "unknown",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(valid).toBe(false)
|
||||
})
|
||||
|
||||
it("should accept an empty customModes array", () => {
|
||||
const config = {
|
||||
customModes: [],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(validate.errors).toBeNull()
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
|
||||
it("should accept the browser tool group (deprecated but valid)", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "browser-mode",
|
||||
name: "Browser Mode",
|
||||
roleDefinition: "A mode that uses the browser tool group.",
|
||||
groups: ["read", "browser", "command"],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(validate.errors).toBeNull()
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
|
||||
it("should accept a browser tuple group entry", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "browser-tuple",
|
||||
name: "Browser Tuple",
|
||||
roleDefinition: "A mode with browser tuple.",
|
||||
groups: [["browser", { fileRegex: "\\.html$", description: "HTML files only" }]],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(validate.errors).toBeNull()
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
|
||||
it("should accept a mode with rulesFiles", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "rules-mode",
|
||||
name: "Rules Mode",
|
||||
roleDefinition: "A mode with rules files.",
|
||||
groups: ["read"],
|
||||
rulesFiles: [
|
||||
{
|
||||
relativePath: "rule1.md",
|
||||
content: "# Rule 1\nFollow this rule.",
|
||||
},
|
||||
{
|
||||
relativePath: "subfolder/rule2.md",
|
||||
content: "# Rule 2\nFollow this other rule.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(validate.errors).toBeNull()
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
|
||||
it("should accept a mode with empty rulesFiles array", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "empty-rules",
|
||||
name: "Empty Rules",
|
||||
roleDefinition: "A mode with empty rules files.",
|
||||
groups: ["read"],
|
||||
rulesFiles: [],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(validate.errors).toBeNull()
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
|
||||
it("should accept rulesFiles entries with only relativePath (content is optional)", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "path-only-rules",
|
||||
name: "Path Only Rules",
|
||||
roleDefinition: "A mode with rules files that only have relativePath.",
|
||||
groups: ["read"],
|
||||
rulesFiles: [{ relativePath: "rule1.md" }],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(validate.errors).toBeNull()
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
|
||||
it("should reject rulesFiles entries missing required relativePath", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "bad-rules",
|
||||
name: "Bad Rules",
|
||||
roleDefinition: "A mode with invalid rules files.",
|
||||
groups: ["read"],
|
||||
rulesFiles: [{ content: "some content" }],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(valid).toBe(false)
|
||||
})
|
||||
|
||||
it("should reject rulesFiles entries with extra properties", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "extra-rules",
|
||||
name: "Extra Rules",
|
||||
roleDefinition: "A mode with extra rule properties.",
|
||||
groups: ["read"],
|
||||
rulesFiles: [{ relativePath: "rule1.md", content: "content", extra: true }],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(valid).toBe(false)
|
||||
})
|
||||
|
||||
it("should accept multiple modes", () => {
|
||||
const config = {
|
||||
customModes: [
|
||||
{
|
||||
slug: "mode-a",
|
||||
name: "Mode A",
|
||||
roleDefinition: "First mode.",
|
||||
groups: ["read"],
|
||||
},
|
||||
{
|
||||
slug: "mode-b",
|
||||
name: "Mode B",
|
||||
roleDefinition: "Second mode.",
|
||||
groups: ["read", "edit"],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const valid = validate(config)
|
||||
expect(validate.errors).toBeNull()
|
||||
expect(valid).toBe(true)
|
||||
})
|
||||
})
|
||||
|
|
@ -34,7 +34,15 @@ export const DEFAULT_CONSECUTIVE_MISTAKE_LIMIT = 3
|
|||
* Dynamic provider requires external API calls in order to get the model list.
|
||||
*/
|
||||
|
||||
export const dynamicProviders = ["openrouter", "vercel-ai-gateway", "litellm", "requesty", "roo", "unbound"] as const
|
||||
export const dynamicProviders = [
|
||||
"openrouter",
|
||||
"vercel-ai-gateway",
|
||||
"litellm",
|
||||
"requesty",
|
||||
"roo",
|
||||
"unbound",
|
||||
"poe",
|
||||
] as const
|
||||
|
||||
export type DynamicProvider = (typeof dynamicProviders)[number]
|
||||
|
||||
|
|
@ -306,6 +314,11 @@ const deepSeekSchema = apiModelIdProviderModelSchema.extend({
|
|||
deepSeekApiKey: z.string().optional(),
|
||||
})
|
||||
|
||||
const poeSchema = apiModelIdProviderModelSchema.extend({
|
||||
poeApiKey: z.string().optional(),
|
||||
poeBaseUrl: z.string().optional(),
|
||||
})
|
||||
|
||||
const moonshotSchema = apiModelIdProviderModelSchema.extend({
|
||||
moonshotBaseUrl: z
|
||||
.union([z.literal("https://api.moonshot.ai/v1"), z.literal("https://api.moonshot.cn/v1")])
|
||||
|
|
@ -400,6 +413,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
|
|||
openAiNativeSchema.merge(z.object({ apiProvider: z.literal("openai-native") })),
|
||||
mistralSchema.merge(z.object({ apiProvider: z.literal("mistral") })),
|
||||
deepSeekSchema.merge(z.object({ apiProvider: z.literal("deepseek") })),
|
||||
poeSchema.merge(z.object({ apiProvider: z.literal("poe") })),
|
||||
moonshotSchema.merge(z.object({ apiProvider: z.literal("moonshot") })),
|
||||
minimaxSchema.merge(z.object({ apiProvider: z.literal("minimax") })),
|
||||
requestySchema.merge(z.object({ apiProvider: z.literal("requesty") })),
|
||||
|
|
@ -433,6 +447,7 @@ export const providerSettingsSchema = z.object({
|
|||
...openAiNativeSchema.shape,
|
||||
...mistralSchema.shape,
|
||||
...deepSeekSchema.shape,
|
||||
...poeSchema.shape,
|
||||
...moonshotSchema.shape,
|
||||
...minimaxSchema.shape,
|
||||
...requestySchema.shape,
|
||||
|
|
@ -510,6 +525,7 @@ export const modelIdKeysByProvider: Record<TypicalProvider, ModelIdKey> = {
|
|||
moonshot: "apiModelId",
|
||||
minimax: "apiModelId",
|
||||
deepseek: "apiModelId",
|
||||
poe: "apiModelId",
|
||||
"qwen-code": "apiModelId",
|
||||
requesty: "requestyModelId",
|
||||
unbound: "unboundModelId",
|
||||
|
|
@ -632,6 +648,7 @@ export const MODELS_BY_PROVIDER: Record<
|
|||
baseten: { id: "baseten", label: "Baseten", models: Object.keys(basetenModels) },
|
||||
|
||||
// Dynamic providers; models pulled from remote APIs.
|
||||
poe: { id: "poe", label: "Poe", models: [] },
|
||||
litellm: { id: "litellm", label: "LiteLLM", models: [] },
|
||||
openrouter: { id: "openrouter", label: "OpenRouter", models: [] },
|
||||
requesty: { id: "requesty", label: "Requesty", models: [] },
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export * from "./openai.js"
|
|||
export * from "./openai-codex.js"
|
||||
export * from "./openai-codex-rate-limits.js"
|
||||
export * from "./openrouter.js"
|
||||
export * from "./poe.js"
|
||||
export * from "./qwen-code.js"
|
||||
export * from "./requesty.js"
|
||||
export * from "./roo.js"
|
||||
|
|
@ -36,6 +37,7 @@ import { mistralDefaultModelId } from "./mistral.js"
|
|||
import { moonshotDefaultModelId } from "./moonshot.js"
|
||||
import { openAiCodexDefaultModelId } from "./openai-codex.js"
|
||||
import { openRouterDefaultModelId } from "./openrouter.js"
|
||||
import { poeDefaultModelId } from "./poe.js"
|
||||
import { qwenCodeDefaultModelId } from "./qwen-code.js"
|
||||
import { requestyDefaultModelId } from "./requesty.js"
|
||||
import { rooDefaultModelId } from "./roo.js"
|
||||
|
|
@ -107,6 +109,8 @@ export function getProviderDefaultModelId(
|
|||
return rooDefaultModelId
|
||||
case "qwen-code":
|
||||
return qwenCodeDefaultModelId
|
||||
case "poe":
|
||||
return poeDefaultModelId
|
||||
case "unbound":
|
||||
return unboundDefaultModelId
|
||||
case "vercel-ai-gateway":
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import type { ModelInfo } from "../model.js"
|
||||
|
||||
// Minimax
|
||||
// https://platform.minimax.io/docs/guides/pricing
|
||||
// https://platform.minimax.io/docs/api-reference/text-openai-api
|
||||
// https://platform.minimax.io/docs/api-reference/text-anthropic-api
|
||||
// https://platform.minimax.io/docs/guides/models-intro
|
||||
// https://platform.minimax.io/docs/guides/pricing-paygo
|
||||
// https://platform.minimax.io/docs/guides/pricing-tokenplan
|
||||
export type MinimaxModelId = keyof typeof minimaxModels
|
||||
export const minimaxDefaultModelId: MinimaxModelId = "MiniMax-M2.5"
|
||||
export const minimaxDefaultModelId: MinimaxModelId = "MiniMax-M2.7"
|
||||
|
||||
export const minimaxModels = {
|
||||
"MiniMax-M2.5": {
|
||||
|
|
@ -21,11 +21,56 @@ export const minimaxModels = {
|
|||
cacheWritesPrice: 0.375,
|
||||
cacheReadsPrice: 0.03,
|
||||
description:
|
||||
"MiniMax M2.5, the latest MiniMax model with enhanced coding and agentic capabilities, building on the strengths of the M2 series.",
|
||||
"MiniMax M2.5, the latest MiniMax model with enhanced coding and agentic capabilities, building on the strengths of the M2 series. See pricing at https://platform.minimax.io/docs/guides/pricing-paygo. Note: When using TokenPlan, usage is billed per request, not per token.",
|
||||
},
|
||||
"MiniMax-M2.5-highspeed": {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 204_800,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
includedTools: ["search_and_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
preserveReasoning: true,
|
||||
inputPrice: 0.6,
|
||||
outputPrice: 2.4,
|
||||
cacheWritesPrice: 0.375,
|
||||
cacheReadsPrice: 0.03,
|
||||
description:
|
||||
"MiniMax M2.5 highspeed: same performance as M2.5 but with faster response (approximately 100 tps vs 60 tps). See pricing at https://platform.minimax.io/docs/guides/pricing-paygo. Requires TokenPlan High-Speed subscription for use with TokenPlan keys. Note: When using TokenPlan, usage is billed per request, not per token.",
|
||||
},
|
||||
"MiniMax-M2.7": {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 204_800,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
includedTools: ["search_and_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
preserveReasoning: true,
|
||||
inputPrice: 0.3,
|
||||
outputPrice: 1.2,
|
||||
cacheWritesPrice: 0.375,
|
||||
cacheReadsPrice: 0.06,
|
||||
description:
|
||||
"MiniMax M2.7, the latest MiniMax model with recursive self-improvement capabilities. See pricing at https://platform.minimax.io/docs/guides/pricing-paygo. Note: When using TokenPlan, usage is billed per request, not per token.",
|
||||
},
|
||||
"MiniMax-M2.7-highspeed": {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 204_800,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
includedTools: ["search_and_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
preserveReasoning: true,
|
||||
inputPrice: 0.6,
|
||||
outputPrice: 2.4,
|
||||
cacheWritesPrice: 0.375,
|
||||
cacheReadsPrice: 0.06,
|
||||
description:
|
||||
"MiniMax M2.7 highspeed: same performance as M2.7 but with faster response (approximately 100 tps vs 60 tps). See pricing at https://platform.minimax.io/docs/guides/pricing-paygo. Requires TokenPlan High-Speed subscription for use with TokenPlan keys. Note: When using TokenPlan, usage is billed per request, not per token.",
|
||||
},
|
||||
"MiniMax-M2": {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 192_000,
|
||||
contextWindow: 204_800,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
includedTools: ["search_and_replace"],
|
||||
|
|
@ -36,11 +81,11 @@ export const minimaxModels = {
|
|||
cacheWritesPrice: 0.375,
|
||||
cacheReadsPrice: 0.03,
|
||||
description:
|
||||
"MiniMax M2, a model born for Agents and code, featuring Top-tier Coding Capabilities, Powerful Agentic Performance, and Ultimate Cost-Effectiveness & Speed.",
|
||||
"MiniMax M2, a model born for Agents and code, featuring Top-tier Coding Capabilities, Powerful Agentic Performance, and Ultimate Cost-Effectiveness & Speed. See pricing at https://platform.minimax.io/docs/guides/pricing-paygo. Note: When using TokenPlan, usage is billed per request, not per token.",
|
||||
},
|
||||
"MiniMax-M2-Stable": {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 192_000,
|
||||
contextWindow: 204_800,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
includedTools: ["search_and_replace"],
|
||||
|
|
@ -51,11 +96,11 @@ export const minimaxModels = {
|
|||
cacheWritesPrice: 0.375,
|
||||
cacheReadsPrice: 0.03,
|
||||
description:
|
||||
"MiniMax M2 Stable (High Concurrency, Commercial Use), a model born for Agents and code, featuring Top-tier Coding Capabilities, Powerful Agentic Performance, and Ultimate Cost-Effectiveness & Speed.",
|
||||
"MiniMax M2 Stable (High Concurrency, Commercial Use), a model born for Agents and code, featuring Top-tier Coding Capabilities, Powerful Agentic Performance, and Ultimate Cost-Effectiveness & Speed. See pricing at https://platform.minimax.io/docs/guides/pricing-paygo. Note: When using TokenPlan, usage is billed per request, not per token.",
|
||||
},
|
||||
"MiniMax-M2.1": {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 192_000,
|
||||
contextWindow: 204_800,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
includedTools: ["search_and_replace"],
|
||||
|
|
@ -66,7 +111,22 @@ export const minimaxModels = {
|
|||
cacheWritesPrice: 0.375,
|
||||
cacheReadsPrice: 0.03,
|
||||
description:
|
||||
"MiniMax M2.1 builds on M2 with improved overall performance for agentic coding tasks and significantly faster response times.",
|
||||
"MiniMax M2.1 builds on M2 with improved overall performance for agentic coding tasks and significantly faster response times. See pricing at https://platform.minimax.io/docs/guides/pricing-paygo. Note: When using TokenPlan, usage is billed per request, not per token.",
|
||||
},
|
||||
"MiniMax-M2.1-highspeed": {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 204_800,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
includedTools: ["search_and_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
preserveReasoning: true,
|
||||
inputPrice: 0.6,
|
||||
outputPrice: 2.4,
|
||||
cacheWritesPrice: 0.375,
|
||||
cacheReadsPrice: 0.03,
|
||||
description:
|
||||
"MiniMax M2.1 highspeed: same performance as M2.1 but with faster response (approximately 100 tps vs 60 tps). See pricing at https://platform.minimax.io/docs/guides/pricing-paygo. Requires TokenPlan High-Speed subscription for use with TokenPlan keys. Note: When using TokenPlan, usage is billed per request, not per token.",
|
||||
},
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
||||
|
|
|
|||
2
packages/types/src/providers/poe.ts
Normal file
2
packages/types/src/providers/poe.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { poeDefaultModelId, POE_DEFAULT_BASE_URL, getPoeDefaultModelInfo } from "ai-sdk-provider-poe/code"
|
||||
export type { PoeDefaultModelInfo } from "ai-sdk-provider-poe/code"
|
||||
|
|
@ -3,10 +3,10 @@ import type { ModelInfo } from "../model.js"
|
|||
// https://docs.x.ai/docs/api-reference
|
||||
export type XAIModelId = keyof typeof xaiModels
|
||||
|
||||
export const xaiDefaultModelId: XAIModelId = "grok-4.20-beta-0309-reasoning"
|
||||
export const xaiDefaultModelId: XAIModelId = "grok-4.20"
|
||||
|
||||
export const xaiModels = {
|
||||
"grok-4.20-beta-0309-reasoning": {
|
||||
"grok-4.20": {
|
||||
maxTokens: 65_536,
|
||||
contextWindow: 2_000_000,
|
||||
supportsImages: true,
|
||||
|
|
@ -15,21 +15,7 @@ export const xaiModels = {
|
|||
outputPrice: 6.0,
|
||||
cacheWritesPrice: 0.5,
|
||||
cacheReadsPrice: 0.5,
|
||||
description:
|
||||
"xAI's Grok 4.20 reasoning model with 2M context. Reasoning is internal (not exposed via Chat Completions API).",
|
||||
includedTools: ["search_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
},
|
||||
"grok-4.20-beta-0309-non-reasoning": {
|
||||
maxTokens: 65_536,
|
||||
contextWindow: 2_000_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 2.0,
|
||||
outputPrice: 6.0,
|
||||
cacheWritesPrice: 0.5,
|
||||
cacheReadsPrice: 0.5,
|
||||
description: "xAI's Grok 4.20 non-reasoning model - faster inference with 2M context.",
|
||||
description: "xAI's flagship Grok 4.20 model with 2M context and reasoning support via Responses API.",
|
||||
includedTools: ["search_replace"],
|
||||
excludedTools: ["apply_diff"],
|
||||
},
|
||||
|
|
|
|||
58
packages/types/src/roomodes-schema.ts
Normal file
58
packages/types/src/roomodes-schema.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* Builds the Zod schema for .roomodes configuration files and converts it
|
||||
* to JSON Schema (draft-07). This module is the single source of truth for
|
||||
* both the generator script (scripts/generate-roomodes-schema.ts) and the
|
||||
* drift-detection test.
|
||||
*/
|
||||
|
||||
import { z } from "zod"
|
||||
import { zodToJsonSchema } from "zod-to-json-schema"
|
||||
|
||||
import { toolGroups, deprecatedToolGroups } from "./tool.js"
|
||||
import { groupOptionsSchema, modeConfigSchema } from "./mode.js"
|
||||
|
||||
// Build a ToolGroup enum that includes deprecated groups so existing configs
|
||||
// still validate.
|
||||
const allToolGroups = [...toolGroups, ...deprecatedToolGroups] as [string, ...string[]]
|
||||
const allToolGroupsSchema = z.enum(allToolGroups)
|
||||
|
||||
// Build a GroupEntry schema that uses the extended tool group list.
|
||||
const groupEntrySchema = z.union([allToolGroupsSchema, z.tuple([allToolGroupsSchema, groupOptionsSchema])])
|
||||
|
||||
// Build the RuleFile schema (used during import/export but not part of the
|
||||
// core Zod types).
|
||||
const ruleFileSchema = z.object({
|
||||
relativePath: z.string(),
|
||||
content: z.string().optional(),
|
||||
})
|
||||
|
||||
// Build an extended ModeConfig schema that includes rulesFiles and uses the
|
||||
// extended groups (with deprecated entries).
|
||||
const exportedModeConfigSchema = modeConfigSchema.omit({ groups: true }).extend({
|
||||
groups: z.array(groupEntrySchema),
|
||||
rulesFiles: z.array(ruleFileSchema).optional(),
|
||||
})
|
||||
|
||||
// Build the top-level .roomodes schema.
|
||||
const roomodesZodSchema = z
|
||||
.object({
|
||||
customModes: z.array(exportedModeConfigSchema),
|
||||
})
|
||||
.strict()
|
||||
|
||||
/**
|
||||
* Generates the JSON Schema object for .roomodes configuration files.
|
||||
* Includes metadata fields ($id, title, description).
|
||||
*/
|
||||
export function generateRoomodesJsonSchema(): Record<string, unknown> {
|
||||
const jsonSchema = zodToJsonSchema(roomodesZodSchema, {
|
||||
$refStrategy: "none",
|
||||
target: "jsonSchema7",
|
||||
}) as Record<string, unknown>
|
||||
|
||||
jsonSchema["$id"] = "https://github.com/RooCodeInc/Roo-Code/blob/main/schemas/roomodes.json"
|
||||
jsonSchema["title"] = "Roo Code Custom Modes"
|
||||
jsonSchema["description"] = "Schema for .roomodes configuration files used by Roo Code to define custom modes."
|
||||
|
||||
return jsonSchema
|
||||
}
|
||||
266
pnpm-lock.yaml
generated
266
pnpm-lock.yaml
generated
|
|
@ -350,8 +350,8 @@ importers:
|
|||
specifier: workspace:^
|
||||
version: link:../../packages/evals
|
||||
'@roo-code/types':
|
||||
specifier: ^1.108.0
|
||||
version: 1.108.0
|
||||
specifier: workspace:^
|
||||
version: link:../../packages/types
|
||||
'@tanstack/react-query':
|
||||
specifier: ^5.90.20
|
||||
version: 5.90.20(react@18.3.1)
|
||||
|
|
@ -569,7 +569,7 @@ importers:
|
|||
version: 7.0.5
|
||||
openai:
|
||||
specifier: ^5.12.2
|
||||
version: 5.12.2(ws@8.18.3)(zod@3.25.76)
|
||||
version: 5.12.2(ws@8.20.0)(zod@3.25.76)
|
||||
zod:
|
||||
specifier: 3.25.76
|
||||
version: 3.25.76
|
||||
|
|
@ -709,6 +709,9 @@ importers:
|
|||
|
||||
packages/types:
|
||||
dependencies:
|
||||
ai-sdk-provider-poe:
|
||||
specifier: 2.0.18
|
||||
version: 2.0.18(ai@6.0.77(zod@3.25.76))(zod@3.25.76)
|
||||
zod:
|
||||
specifier: 3.25.76
|
||||
version: 3.25.76
|
||||
|
|
@ -722,6 +725,9 @@ importers:
|
|||
'@types/node':
|
||||
specifier: ^24.1.0
|
||||
version: 24.2.1
|
||||
ajv:
|
||||
specifier: ^8.18.0
|
||||
version: 8.18.0
|
||||
globals:
|
||||
specifier: ^16.3.0
|
||||
version: 16.3.0
|
||||
|
|
@ -731,6 +737,9 @@ importers:
|
|||
vitest:
|
||||
specifier: ^3.2.3
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.2.1)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
|
||||
zod-to-json-schema:
|
||||
specifier: ^3.25.1
|
||||
version: 3.25.1(zod@3.25.76)
|
||||
|
||||
packages/vscode-shim:
|
||||
devDependencies:
|
||||
|
|
@ -818,6 +827,9 @@ importers:
|
|||
'@vscode/codicons':
|
||||
specifier: ^0.0.36
|
||||
version: 0.0.36
|
||||
ai-sdk-provider-poe:
|
||||
specifier: 2.0.18
|
||||
version: 2.0.18(ai@6.0.77(zod@3.25.76))(zod@3.25.76)
|
||||
async-mutex:
|
||||
specifier: ^0.5.0
|
||||
version: 0.5.0
|
||||
|
|
@ -907,7 +919,7 @@ importers:
|
|||
version: 0.5.17
|
||||
openai:
|
||||
specifier: ^5.12.2
|
||||
version: 5.12.2(ws@8.18.3)(zod@3.25.76)
|
||||
version: 5.12.2(ws@8.20.0)(zod@3.25.76)
|
||||
os-name:
|
||||
specifier: ^6.0.0
|
||||
version: 6.1.0
|
||||
|
|
@ -1426,6 +1438,12 @@ packages:
|
|||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/anthropic@3.0.42':
|
||||
resolution: {integrity: sha512-snoLXB9DmvAmmngbPN/Io8IGzZ9zWpC208EgIIztYf1e1JhwuMkgKCYkL30vGhSen4PrBafu2+sO4G/17wu45A==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/baseten@1.0.31':
|
||||
resolution: {integrity: sha512-tGbV96WBb5nnfyUYFrPyBxrhw53YlKSJbMC+rH3HhQlUaIs8+m/Bm4M0isrek9owIIf4MmmSDZ5VZL08zz7eFQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -1480,6 +1498,18 @@ packages:
|
|||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/openai-compatible@2.0.37':
|
||||
resolution: {integrity: sha512-+POSFVcgiu47BK64dhsI6OpcDC0/VAE2ZSaXdXGNNhpC/ava++uSRJYks0k2bpfY0wwCTgpAWZsXn/dG2Yppiw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/openai@3.0.27':
|
||||
resolution: {integrity: sha512-pLMxWOypwroXiK9dxNpn60/HGhWWWDEOJ3lo9vZLoxvpJNtKnLKojwVIvlW3yEjlD7ll1+jUO2uzsABNTaP5Yg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider-utils@3.0.5':
|
||||
resolution: {integrity: sha512-HliwB/yzufw3iwczbFVE2Fiwf1XqROB/I6ng8EKUsPM5+2wnIa8f4VbljZcDx+grhFrPV+PnRZH7zBqi8WZM7Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -1492,6 +1522,12 @@ packages:
|
|||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider-utils@4.0.21':
|
||||
resolution: {integrity: sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider@2.0.0':
|
||||
resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -1726,6 +1762,10 @@ packages:
|
|||
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/code-frame@7.29.0':
|
||||
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/compat-data@7.27.2':
|
||||
resolution: {integrity: sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
|
@ -1764,6 +1804,10 @@ packages:
|
|||
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-identifier@7.28.5':
|
||||
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-option@7.27.1':
|
||||
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
|
@ -1805,6 +1849,10 @@ packages:
|
|||
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/runtime@7.29.2':
|
||||
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/template@7.27.2':
|
||||
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
|
@ -2532,13 +2580,13 @@ packages:
|
|||
'@libsql/core@0.15.15':
|
||||
resolution: {integrity: sha512-C88Z6UKl+OyuKKPwz224riz02ih/zHYI3Ho/LAcVOgjsunIRZoBw7fjRfaH9oPMmSNeQfhGklSG2il1URoOIsA==}
|
||||
|
||||
'@libsql/darwin-arm64@0.5.22':
|
||||
resolution: {integrity: sha512-4B8ZlX3nIDPndfct7GNe0nI3Yw6ibocEicWdC4fvQbSs/jdq/RC2oCsoJxJ4NzXkvktX70C1J4FcmmoBy069UA==}
|
||||
'@libsql/darwin-arm64@0.5.29':
|
||||
resolution: {integrity: sha512-K+2RIB1OGFPYQbfay48GakLhqf3ArcbHqPFu7EZiaUcRgFcdw8RoltsMyvbj5ix2fY0HV3Q3Ioa/ByvQdaSM0A==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@libsql/darwin-x64@0.5.22':
|
||||
resolution: {integrity: sha512-ny2HYWt6lFSIdNFzUFIJ04uiW6finXfMNJ7wypkAD8Pqdm6nAByO+Fdqu8t7sD0sqJGeUCiOg480icjyQ2/8VA==}
|
||||
'@libsql/darwin-x64@0.5.29':
|
||||
resolution: {integrity: sha512-OtT+KFHsKFy1R5FVadr8FJ2Bb1mghtXTyJkxv0trocq7NuHntSki1eUbxpO5ezJesDvBlqFjnWaYYY516QNLhQ==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
|
|
@ -2552,38 +2600,38 @@ packages:
|
|||
'@libsql/isomorphic-ws@0.1.5':
|
||||
resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==}
|
||||
|
||||
'@libsql/linux-arm-gnueabihf@0.5.22':
|
||||
resolution: {integrity: sha512-3Uo3SoDPJe/zBnyZKosziRGtszXaEtv57raWrZIahtQDsjxBVjuzYQinCm9LRCJCUT5t2r5Z5nLDPJi2CwZVoA==}
|
||||
'@libsql/linux-arm-gnueabihf@0.5.29':
|
||||
resolution: {integrity: sha512-CD4n4zj7SJTHso4nf5cuMoWoMSS7asn5hHygsDuhRl8jjjCTT3yE+xdUvI4J7zsyb53VO5ISh4cwwOtf6k2UhQ==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@libsql/linux-arm-musleabihf@0.5.22':
|
||||
resolution: {integrity: sha512-LCsXh07jvSojTNJptT9CowOzwITznD+YFGGW+1XxUr7fS+7/ydUrpDfsMX7UqTqjm7xG17eq86VkWJgHJfvpNg==}
|
||||
'@libsql/linux-arm-musleabihf@0.5.29':
|
||||
resolution: {integrity: sha512-2Z9qBVpEJV7OeflzIR3+l5yAd4uTOLxklScYTwpZnkm2vDSGlC1PRlueLaufc4EFITkLKXK2MWBpexuNJfMVcg==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@libsql/linux-arm64-gnu@0.5.22':
|
||||
resolution: {integrity: sha512-KSdnOMy88c9mpOFKUEzPskSaF3VLflfSUCBwas/pn1/sV3pEhtMF6H8VUCd2rsedwoukeeCSEONqX7LLnQwRMA==}
|
||||
'@libsql/linux-arm64-gnu@0.5.29':
|
||||
resolution: {integrity: sha512-gURBqaiXIGGwFNEaUj8Ldk7Hps4STtG+31aEidCk5evMMdtsdfL3HPCpvys+ZF/tkOs2MWlRWoSq7SOuCE9k3w==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@libsql/linux-arm64-musl@0.5.22':
|
||||
resolution: {integrity: sha512-mCHSMAsDTLK5YH//lcV3eFEgiR23Ym0U9oEvgZA0667gqRZg/2px+7LshDvErEKv2XZ8ixzw3p1IrBzLQHGSsw==}
|
||||
'@libsql/linux-arm64-musl@0.5.29':
|
||||
resolution: {integrity: sha512-fwgYZ0H8mUkyVqXZHF3mT/92iIh1N94Owi/f66cPVNsk9BdGKq5gVpoKO+7UxaNzuEH1roJp2QEwsCZMvBLpqg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@libsql/linux-x64-gnu@0.5.22':
|
||||
resolution: {integrity: sha512-kNBHaIkSg78Y4BqAdgjcR2mBilZXs4HYkAmi58J+4GRwDQZh5fIUWbnQvB9f95DkWUIGVeenqLRFY2pcTmlsew==}
|
||||
'@libsql/linux-x64-gnu@0.5.29':
|
||||
resolution: {integrity: sha512-y14V0vY0nmMC6G0pHeJcEarcnGU2H6cm21ZceRkacWHvQAEhAG0latQkCtoS2njFOXiYIg+JYPfAoWKbi82rkg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@libsql/linux-x64-musl@0.5.22':
|
||||
resolution: {integrity: sha512-UZ4Xdxm4pu3pQXjvfJiyCzZop/9j/eA2JjmhMaAhe3EVLH2g11Fy4fwyUp9sT1QJYR1kpc2JLuybPM0kuXv/Tg==}
|
||||
'@libsql/linux-x64-musl@0.5.29':
|
||||
resolution: {integrity: sha512-gquqwA/39tH4pFl+J9n3SOMSymjX+6kZ3kWgY3b94nXFTwac9bnFNMffIomgvlFaC4ArVqMnOZD3nuJ3H3VO1w==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@libsql/win32-x64-msvc@0.5.22':
|
||||
resolution: {integrity: sha512-Fj0j8RnBpo43tVZUVoNK6BV/9AtDUM5S7DF3LB4qTYg1LMSZqi3yeCneUTLJD6XomQJlZzbI4mst89yspVSAnA==}
|
||||
'@libsql/win32-x64-msvc@0.5.29':
|
||||
resolution: {integrity: sha512-4/0CvEdhi6+KjMxMaVbFM2n2Z44escBRoEYpR+gZg64DdetzGnYm8mcNLcoySaDJZNaBd6wz5DNdgRmcI4hXcg==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
|
|
@ -3850,9 +3898,6 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@roo-code/types@1.108.0':
|
||||
resolution: {integrity: sha512-0Of0DOuU125i1VTI2OTdL0j47xA+JrQr6KyYinYS+CPwUsszUJt2PeWyy/AZYI1w23FYrcCvh8FqycDuwXocSA==}
|
||||
|
||||
'@sec-ant/readable-stream@0.4.1':
|
||||
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
||||
|
||||
|
|
@ -4886,6 +4931,11 @@ packages:
|
|||
resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
|
||||
ai-sdk-provider-poe@2.0.18:
|
||||
resolution: {integrity: sha512-uzFR5Zq+PD6LfrqpYAr4dt2KmfOfS9a0Wh8QJsOaGI/vOByhW02P/0mj0NtOnOWF1RIUfBkQJo647XjrgnFjjg==}
|
||||
peerDependencies:
|
||||
ai: '>=6.0.0'
|
||||
|
||||
ai@6.0.77:
|
||||
resolution: {integrity: sha512-tyyhrRpCRFVlivdNIFLK8cexSBB2jwTqO0z1qJQagk+UxZ+MW8h5V8xsvvb+xdKDY482Y8KAm0mr7TDnPKvvlw==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -4895,6 +4945,9 @@ packages:
|
|||
ajv@6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
|
||||
ajv@8.18.0:
|
||||
resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
|
||||
|
||||
ansi-colors@4.1.3:
|
||||
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
|
||||
engines: {node: '>=6'}
|
||||
|
|
@ -5130,6 +5183,7 @@ packages:
|
|||
basic-ftp@5.0.5:
|
||||
resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
deprecated: Security vulnerability fixed in 5.2.0, please upgrade
|
||||
|
||||
better-path-resolve@1.0.0:
|
||||
resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==}
|
||||
|
|
@ -6577,6 +6631,9 @@ packages:
|
|||
fast-shallow-equal@1.0.0:
|
||||
resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==}
|
||||
|
||||
fast-uri@3.1.0:
|
||||
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
|
||||
|
||||
fast-xml-parser@5.2.3:
|
||||
resolution: {integrity: sha512-OdCYfRqfpuLUFonTNjvd30rCBZUneHpSQkCqfaeWQ9qrKcl6XlWeDBNVwGb+INAIxRshuN2jF+BE0L6gbBO2mw==}
|
||||
hasBin: true
|
||||
|
|
@ -7605,6 +7662,9 @@ packages:
|
|||
json-schema-traverse@0.4.1:
|
||||
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
||||
|
||||
json-schema-traverse@1.0.0:
|
||||
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
||||
|
||||
json-schema@0.4.0:
|
||||
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
|
||||
|
||||
|
|
@ -7712,8 +7772,8 @@ packages:
|
|||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
libsql@0.5.22:
|
||||
resolution: {integrity: sha512-NscWthMQt7fpU8lqd7LXMvT9pi+KhhmTHAJWUB/Lj6MWa0MKFv0F2V4C6WKKpjCVZl0VwcDz4nOI3CyaT1DDiA==}
|
||||
libsql@0.5.29:
|
||||
resolution: {integrity: sha512-8lMP8iMgiBzzoNbAPQ59qdVcj6UaE/Vnm+fiwX4doX4Narook0a4GPKWBEv+CR8a1OwbfkgL18uBfBjWdF0Fzg==}
|
||||
cpu: [x64, arm64, wasm32, arm]
|
||||
os: [darwin, linux, win32]
|
||||
|
||||
|
|
@ -8976,6 +9036,7 @@ packages:
|
|||
prebuild-install@7.1.3:
|
||||
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
|
||||
engines: {node: '>=10'}
|
||||
deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available.
|
||||
hasBin: true
|
||||
|
||||
prelude-ls@1.2.1:
|
||||
|
|
@ -9386,6 +9447,10 @@ packages:
|
|||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
require-from-string@2.0.2:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
resize-observer-polyfill@1.5.1:
|
||||
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
|
||||
|
||||
|
|
@ -9565,6 +9630,11 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
semver@7.7.4:
|
||||
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
send@1.2.0:
|
||||
resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==}
|
||||
engines: {node: '>= 18'}
|
||||
|
|
@ -10898,6 +10968,18 @@ packages:
|
|||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
ws@8.20.0:
|
||||
resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: '>=5.0.2'
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
xml-name-validator@5.0.0:
|
||||
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -11002,6 +11084,11 @@ packages:
|
|||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
zod-to-json-schema@3.25.1:
|
||||
resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==}
|
||||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
zod-to-ts@1.2.0:
|
||||
resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==}
|
||||
peerDependencies:
|
||||
|
|
@ -11058,6 +11145,12 @@ snapshots:
|
|||
'@ai-sdk/provider-utils': 4.0.14(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/anthropic@3.0.42(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.14(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/baseten@1.0.31(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/openai-compatible': 2.0.28(zod@3.25.76)
|
||||
|
|
@ -11121,6 +11214,18 @@ snapshots:
|
|||
'@ai-sdk/provider-utils': 4.0.14(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/openai-compatible@2.0.37(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.21(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/openai@3.0.27(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.14(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider-utils@3.0.5(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 2.0.0
|
||||
|
|
@ -11136,6 +11241,13 @@ snapshots:
|
|||
eventsource-parser: 3.0.6
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider-utils@4.0.21(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@standard-schema/spec': 1.1.0
|
||||
eventsource-parser: 3.0.6
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider@2.0.0':
|
||||
dependencies:
|
||||
json-schema: 0.4.0
|
||||
|
|
@ -11767,6 +11879,12 @@ snapshots:
|
|||
js-tokens: 4.0.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
'@babel/code-frame@7.29.0':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
js-tokens: 4.0.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
'@babel/compat-data@7.27.2': {}
|
||||
|
||||
'@babel/core@7.27.1':
|
||||
|
|
@ -11827,6 +11945,8 @@ snapshots:
|
|||
|
||||
'@babel/helper-validator-identifier@7.27.1': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.28.5': {}
|
||||
|
||||
'@babel/helper-validator-option@7.27.1': {}
|
||||
|
||||
'@babel/helpers@7.27.1':
|
||||
|
|
@ -11856,6 +11976,8 @@ snapshots:
|
|||
|
||||
'@babel/runtime@7.28.4': {}
|
||||
|
||||
'@babel/runtime@7.29.2': {}
|
||||
|
||||
'@babel/template@7.27.2':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
|
|
@ -12575,7 +12697,7 @@ snapshots:
|
|||
'@libsql/core': 0.15.15
|
||||
'@libsql/hrana-client': 0.7.0
|
||||
js-base64: 3.7.8
|
||||
libsql: 0.5.22
|
||||
libsql: 0.5.29
|
||||
promise-limit: 2.7.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
|
|
@ -12587,10 +12709,10 @@ snapshots:
|
|||
js-base64: 3.7.8
|
||||
optional: true
|
||||
|
||||
'@libsql/darwin-arm64@0.5.22':
|
||||
'@libsql/darwin-arm64@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@libsql/darwin-x64@0.5.22':
|
||||
'@libsql/darwin-x64@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@libsql/hrana-client@0.7.0':
|
||||
|
|
@ -12610,31 +12732,31 @@ snapshots:
|
|||
'@libsql/isomorphic-ws@0.1.5':
|
||||
dependencies:
|
||||
'@types/ws': 8.18.1
|
||||
ws: 8.18.3
|
||||
ws: 8.20.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
optional: true
|
||||
|
||||
'@libsql/linux-arm-gnueabihf@0.5.22':
|
||||
'@libsql/linux-arm-gnueabihf@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@libsql/linux-arm-musleabihf@0.5.22':
|
||||
'@libsql/linux-arm-musleabihf@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@libsql/linux-arm64-gnu@0.5.22':
|
||||
'@libsql/linux-arm64-gnu@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@libsql/linux-arm64-musl@0.5.22':
|
||||
'@libsql/linux-arm64-musl@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@libsql/linux-x64-gnu@0.5.22':
|
||||
'@libsql/linux-x64-gnu@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@libsql/linux-x64-musl@0.5.22':
|
||||
'@libsql/linux-x64-musl@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@libsql/win32-x64-msvc@0.5.22':
|
||||
'@libsql/win32-x64-msvc@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@lmstudio/lms-isomorphic@0.4.5':
|
||||
|
|
@ -13863,10 +13985,6 @@ snapshots:
|
|||
'@rollup/rollup-win32-x64-msvc@4.40.2':
|
||||
optional: true
|
||||
|
||||
'@roo-code/types@1.108.0':
|
||||
dependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@sec-ant/readable-stream@0.4.1': {}
|
||||
|
||||
'@sevinf/maybe@0.5.0': {}
|
||||
|
|
@ -14389,8 +14507,8 @@ snapshots:
|
|||
|
||||
'@testing-library/dom@10.4.0':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/code-frame': 7.29.0
|
||||
'@babel/runtime': 7.29.2
|
||||
'@types/aria-query': 5.0.4
|
||||
aria-query: 5.3.0
|
||||
chalk: 4.1.2
|
||||
|
|
@ -15129,6 +15247,17 @@ snapshots:
|
|||
dependencies:
|
||||
humanize-ms: 1.2.1
|
||||
|
||||
ai-sdk-provider-poe@2.0.18(ai@6.0.77(zod@3.25.76))(zod@3.25.76):
|
||||
dependencies:
|
||||
'@ai-sdk/anthropic': 3.0.42(zod@3.25.76)
|
||||
'@ai-sdk/openai': 3.0.27(zod@3.25.76)
|
||||
'@ai-sdk/openai-compatible': 2.0.37(zod@3.25.76)
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.14(zod@3.25.76)
|
||||
ai: 6.0.77(zod@3.25.76)
|
||||
transitivePeerDependencies:
|
||||
- zod
|
||||
|
||||
ai@6.0.77(zod@3.25.76):
|
||||
dependencies:
|
||||
'@ai-sdk/gateway': 3.0.39(zod@3.25.76)
|
||||
|
|
@ -15144,6 +15273,13 @@ snapshots:
|
|||
json-schema-traverse: 0.4.1
|
||||
uri-js: 4.4.1
|
||||
|
||||
ajv@8.18.0:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
fast-uri: 3.1.0
|
||||
json-schema-traverse: 1.0.0
|
||||
require-from-string: 2.0.2
|
||||
|
||||
ansi-colors@4.1.3: {}
|
||||
|
||||
ansi-escapes@7.0.0:
|
||||
|
|
@ -16986,6 +17122,8 @@ snapshots:
|
|||
|
||||
fast-shallow-equal@1.0.0: {}
|
||||
|
||||
fast-uri@3.1.0: {}
|
||||
|
||||
fast-xml-parser@5.2.3:
|
||||
dependencies:
|
||||
strnum: 2.1.1
|
||||
|
|
@ -17232,7 +17370,7 @@ snapshots:
|
|||
'@petamoriken/float16': 3.9.3
|
||||
debug: 4.4.3
|
||||
env-paths: 3.0.0
|
||||
semver: 7.7.3
|
||||
semver: 7.7.4
|
||||
shell-quote: 1.8.3
|
||||
which: 4.0.0
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -18115,6 +18253,8 @@ snapshots:
|
|||
|
||||
json-schema-traverse@0.4.1: {}
|
||||
|
||||
json-schema-traverse@1.0.0: {}
|
||||
|
||||
json-schema@0.4.0: {}
|
||||
|
||||
json-stable-stringify-without-jsonify@1.0.1: {}
|
||||
|
|
@ -18251,20 +18391,20 @@ snapshots:
|
|||
prelude-ls: 1.2.1
|
||||
type-check: 0.4.0
|
||||
|
||||
libsql@0.5.22:
|
||||
libsql@0.5.29:
|
||||
dependencies:
|
||||
'@neon-rs/load': 0.0.4
|
||||
detect-libc: 2.0.2
|
||||
optionalDependencies:
|
||||
'@libsql/darwin-arm64': 0.5.22
|
||||
'@libsql/darwin-x64': 0.5.22
|
||||
'@libsql/linux-arm-gnueabihf': 0.5.22
|
||||
'@libsql/linux-arm-musleabihf': 0.5.22
|
||||
'@libsql/linux-arm64-gnu': 0.5.22
|
||||
'@libsql/linux-arm64-musl': 0.5.22
|
||||
'@libsql/linux-x64-gnu': 0.5.22
|
||||
'@libsql/linux-x64-musl': 0.5.22
|
||||
'@libsql/win32-x64-msvc': 0.5.22
|
||||
'@libsql/darwin-arm64': 0.5.29
|
||||
'@libsql/darwin-x64': 0.5.29
|
||||
'@libsql/linux-arm-gnueabihf': 0.5.29
|
||||
'@libsql/linux-arm-musleabihf': 0.5.29
|
||||
'@libsql/linux-arm64-gnu': 0.5.29
|
||||
'@libsql/linux-arm64-musl': 0.5.29
|
||||
'@libsql/linux-x64-gnu': 0.5.29
|
||||
'@libsql/linux-x64-musl': 0.5.29
|
||||
'@libsql/win32-x64-msvc': 0.5.29
|
||||
optional: true
|
||||
|
||||
lie@3.3.0:
|
||||
|
|
@ -19384,9 +19524,9 @@ snapshots:
|
|||
is-inside-container: 1.0.0
|
||||
is-wsl: 3.1.0
|
||||
|
||||
openai@5.12.2(ws@8.18.3)(zod@3.25.76):
|
||||
openai@5.12.2(ws@8.20.0)(zod@3.25.76):
|
||||
optionalDependencies:
|
||||
ws: 8.18.3
|
||||
ws: 8.20.0
|
||||
zod: 3.25.76
|
||||
|
||||
option@0.2.4: {}
|
||||
|
|
@ -20328,6 +20468,8 @@ snapshots:
|
|||
|
||||
require-directory@2.1.1: {}
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
|
||||
resize-observer-polyfill@1.5.1: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
|
@ -20538,6 +20680,9 @@ snapshots:
|
|||
|
||||
semver@7.7.3: {}
|
||||
|
||||
semver@7.7.4:
|
||||
optional: true
|
||||
|
||||
send@1.2.0:
|
||||
dependencies:
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
|
|
@ -22165,6 +22310,9 @@ snapshots:
|
|||
|
||||
ws@8.18.3: {}
|
||||
|
||||
ws@8.20.0:
|
||||
optional: true
|
||||
|
||||
xml-name-validator@5.0.0: {}
|
||||
|
||||
xml2js@0.5.0:
|
||||
|
|
@ -22267,6 +22415,10 @@ snapshots:
|
|||
dependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
zod-to-json-schema@3.25.1(zod@3.25.76):
|
||||
dependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
zod-to-ts@1.2.0(typescript@5.8.3)(zod@3.25.76):
|
||||
dependencies:
|
||||
typescript: 5.8.3
|
||||
|
|
|
|||
96
schemas/roomodes.json
Normal file
96
schemas/roomodes.json
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"customModes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9-]+$"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"roleDefinition": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"whenToUse": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"customInstructions": {
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"type": "string",
|
||||
"enum": ["global", "project"]
|
||||
},
|
||||
"groups": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["read", "edit", "command", "mcp", "modes", "browser"]
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"items": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["read", "edit", "command", "mcp", "modes", "browser"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fileRegex": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"rulesFiles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"relativePath": {
|
||||
"type": "string"
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["relativePath"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["slug", "name", "roleDefinition", "groups"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["customModes"],
|
||||
"additionalProperties": false,
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://github.com/RooCodeInc/Roo-Code/blob/main/schemas/roomodes.json",
|
||||
"title": "Roo Code Custom Modes",
|
||||
"description": "Schema for .roomodes configuration files used by Roo Code to define custom modes."
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import {
|
|||
AnthropicHandler,
|
||||
AwsBedrockHandler,
|
||||
OpenRouterHandler,
|
||||
PoeHandler,
|
||||
VertexHandler,
|
||||
AnthropicVertexHandler,
|
||||
OpenAiHandler,
|
||||
|
|
@ -176,6 +177,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
|
|||
return new MiniMaxHandler(options)
|
||||
case "baseten":
|
||||
return new BasetenHandler(options)
|
||||
case "poe":
|
||||
return new PoeHandler(options)
|
||||
default:
|
||||
return new AnthropicHandler(options)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ describe("MiniMaxHandler", () => {
|
|||
const model = handlerWithModel.getModel()
|
||||
expect(model.id).toBe(testModelId)
|
||||
expect(model.info).toEqual(minimaxModels[testModelId])
|
||||
expect(model.info.contextWindow).toBe(192_000)
|
||||
expect(model.info.contextWindow).toBe(204_800)
|
||||
expect(model.info.maxTokens).toBe(16_384)
|
||||
expect(model.info.supportsPromptCache).toBe(true)
|
||||
expect(model.info.cacheWritesPrice).toBe(0.375)
|
||||
|
|
@ -128,7 +128,7 @@ describe("MiniMaxHandler", () => {
|
|||
const model = handlerWithModel.getModel()
|
||||
expect(model.id).toBe(testModelId)
|
||||
expect(model.info).toEqual(minimaxModels[testModelId])
|
||||
expect(model.info.contextWindow).toBe(192_000)
|
||||
expect(model.info.contextWindow).toBe(204_800)
|
||||
expect(model.info.maxTokens).toBe(16_384)
|
||||
expect(model.info.supportsPromptCache).toBe(true)
|
||||
expect(model.info.cacheWritesPrice).toBe(0.375)
|
||||
|
|
@ -191,10 +191,10 @@ describe("MiniMaxHandler", () => {
|
|||
expect(model.info).toEqual(minimaxModels[minimaxDefaultModelId])
|
||||
})
|
||||
|
||||
it("should default to MiniMax-M2.5 model", () => {
|
||||
it("should default to MiniMax-M2.7 model", () => {
|
||||
const handlerDefault = new MiniMaxHandler({ minimaxApiKey: "test-minimax-api-key" })
|
||||
const model = handlerDefault.getModel()
|
||||
expect(model.id).toBe("MiniMax-M2.5")
|
||||
expect(model.id).toBe("MiniMax-M2.7")
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -399,7 +399,7 @@ describe("MiniMaxHandler", () => {
|
|||
it("should correctly configure MiniMax-M2 model properties", () => {
|
||||
const model = minimaxModels["MiniMax-M2"]
|
||||
expect(model.maxTokens).toBe(16_384)
|
||||
expect(model.contextWindow).toBe(192_000)
|
||||
expect(model.contextWindow).toBe(204_800)
|
||||
expect(model.supportsImages).toBe(false)
|
||||
expect(model.supportsPromptCache).toBe(true)
|
||||
expect(model.inputPrice).toBe(0.3)
|
||||
|
|
@ -411,7 +411,7 @@ describe("MiniMaxHandler", () => {
|
|||
it("should correctly configure MiniMax-M2-Stable model properties", () => {
|
||||
const model = minimaxModels["MiniMax-M2-Stable"]
|
||||
expect(model.maxTokens).toBe(16_384)
|
||||
expect(model.contextWindow).toBe(192_000)
|
||||
expect(model.contextWindow).toBe(204_800)
|
||||
expect(model.supportsImages).toBe(false)
|
||||
expect(model.supportsPromptCache).toBe(true)
|
||||
expect(model.inputPrice).toBe(0.3)
|
||||
|
|
@ -419,5 +419,63 @@ describe("MiniMaxHandler", () => {
|
|||
expect(model.cacheWritesPrice).toBe(0.375)
|
||||
expect(model.cacheReadsPrice).toBe(0.03)
|
||||
})
|
||||
|
||||
it("should correctly configure MiniMax-M2.7 model properties", () => {
|
||||
const model = minimaxModels["MiniMax-M2.7"]
|
||||
expect(model.maxTokens).toBe(16_384)
|
||||
expect(model.contextWindow).toBe(204_800)
|
||||
expect(model.supportsImages).toBe(false)
|
||||
expect(model.supportsPromptCache).toBe(true)
|
||||
expect(model.inputPrice).toBe(0.3)
|
||||
expect(model.outputPrice).toBe(1.2)
|
||||
expect(model.cacheWritesPrice).toBe(0.375)
|
||||
expect(model.cacheReadsPrice).toBe(0.06)
|
||||
})
|
||||
|
||||
it("should correctly configure MiniMax-M2.7-highspeed model properties", () => {
|
||||
const model = minimaxModels["MiniMax-M2.7-highspeed"]
|
||||
expect(model.maxTokens).toBe(16_384)
|
||||
expect(model.contextWindow).toBe(204_800)
|
||||
expect(model.supportsImages).toBe(false)
|
||||
expect(model.supportsPromptCache).toBe(true)
|
||||
expect(model.inputPrice).toBe(0.6)
|
||||
expect(model.outputPrice).toBe(2.4)
|
||||
expect(model.cacheWritesPrice).toBe(0.375)
|
||||
expect(model.cacheReadsPrice).toBe(0.06)
|
||||
})
|
||||
|
||||
it("should correctly configure MiniMax-M2.5-highspeed model properties", () => {
|
||||
const model = minimaxModels["MiniMax-M2.5-highspeed"]
|
||||
expect(model.maxTokens).toBe(16_384)
|
||||
expect(model.contextWindow).toBe(204_800)
|
||||
expect(model.supportsImages).toBe(false)
|
||||
expect(model.supportsPromptCache).toBe(true)
|
||||
expect(model.inputPrice).toBe(0.6)
|
||||
expect(model.outputPrice).toBe(2.4)
|
||||
expect(model.cacheWritesPrice).toBe(0.375)
|
||||
expect(model.cacheReadsPrice).toBe(0.03)
|
||||
})
|
||||
|
||||
it("should correctly configure MiniMax-M2.1-highspeed model properties", () => {
|
||||
const model = minimaxModels["MiniMax-M2.1-highspeed"]
|
||||
expect(model.maxTokens).toBe(16_384)
|
||||
expect(model.contextWindow).toBe(204_800)
|
||||
expect(model.supportsImages).toBe(false)
|
||||
expect(model.supportsPromptCache).toBe(true)
|
||||
expect(model.inputPrice).toBe(0.6)
|
||||
expect(model.outputPrice).toBe(2.4)
|
||||
expect(model.cacheWritesPrice).toBe(0.375)
|
||||
expect(model.cacheReadsPrice).toBe(0.03)
|
||||
})
|
||||
|
||||
it("should correctly configure MiniMax-M2.1 model properties with updated context window", () => {
|
||||
const model = minimaxModels["MiniMax-M2.1"]
|
||||
expect(model.contextWindow).toBe(204_800)
|
||||
})
|
||||
|
||||
it("should correctly configure MiniMax-M2 model properties with updated context window", () => {
|
||||
const model = minimaxModels["MiniMax-M2"]
|
||||
expect(model.contextWindow).toBe(204_800)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
307
src/api/providers/__tests__/poe.spec.ts
Normal file
307
src/api/providers/__tests__/poe.spec.ts
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
const mockStreamText = vitest.fn()
|
||||
const mockGenerateText = vitest.fn()
|
||||
const mockCreatePoe = vitest.fn()
|
||||
|
||||
vitest.mock("ai-sdk-provider-poe", () => ({
|
||||
createPoe: (...args: unknown[]) => mockCreatePoe(...args),
|
||||
}))
|
||||
|
||||
vitest.mock("ai-sdk-provider-poe/code", () => ({
|
||||
mapToolChoice: vitest.fn((value: unknown) => value),
|
||||
extractUsageMetrics: vitest.fn((usage: any) => ({
|
||||
inputTokens: usage?.inputTokens || 0,
|
||||
outputTokens: usage?.outputTokens || 0,
|
||||
cacheReadTokens: usage?.cacheReadTokens,
|
||||
cacheWriteTokens: usage?.cacheWriteTokens,
|
||||
reasoningTokens: usage?.reasoningTokens,
|
||||
})),
|
||||
getPoeDefaultModelInfo: vitest.fn(() => ({
|
||||
maxTokens: 8192,
|
||||
contextWindow: 200_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 3,
|
||||
outputPrice: 15,
|
||||
})),
|
||||
}))
|
||||
|
||||
vitest.mock("ai", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("ai")>()
|
||||
return {
|
||||
...actual,
|
||||
streamText: (...args: unknown[]) => mockStreamText(...args),
|
||||
generateText: (...args: unknown[]) => mockGenerateText(...args),
|
||||
}
|
||||
})
|
||||
|
||||
vitest.mock("../fetchers/modelCache", () => ({
|
||||
getModelsFromCache: vitest.fn().mockReturnValue({
|
||||
"anthropic/claude-sonnet-4": {
|
||||
maxTokens: 10_000,
|
||||
contextWindow: 200_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningBudget: true,
|
||||
inputPrice: 3,
|
||||
outputPrice: 15,
|
||||
},
|
||||
"openai/gpt-4o": {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 128_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 2.5,
|
||||
outputPrice: 10,
|
||||
},
|
||||
"openai/o3": {
|
||||
maxTokens: 100_000,
|
||||
contextWindow: 200_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
supportsReasoningEffort: ["low", "medium", "high"],
|
||||
inputPrice: 10,
|
||||
outputPrice: 40,
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
import { poeDefaultModelId } from "@roo-code/types"
|
||||
import { PoeHandler } from "../poe"
|
||||
|
||||
describe("PoeHandler", () => {
|
||||
const mockLanguageModel = { modelId: "test-model" }
|
||||
const mockPoeProvider = vitest.fn().mockReturnValue(mockLanguageModel)
|
||||
|
||||
beforeEach(() => {
|
||||
vitest.clearAllMocks()
|
||||
mockCreatePoe.mockReturnValue(mockPoeProvider)
|
||||
})
|
||||
|
||||
describe("constructor", () => {
|
||||
it("creates poe provider with api key and default base URL", () => {
|
||||
new PoeHandler({ poeApiKey: "test-key" })
|
||||
|
||||
expect(mockCreatePoe).toHaveBeenCalledWith({
|
||||
apiKey: "test-key",
|
||||
baseURL: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it("creates poe provider with custom base URL", () => {
|
||||
new PoeHandler({ poeApiKey: "key", poeBaseUrl: "https://custom.poe.com/v1" })
|
||||
|
||||
expect(mockCreatePoe).toHaveBeenCalledWith({
|
||||
apiKey: "key",
|
||||
baseURL: "https://custom.poe.com/v1",
|
||||
})
|
||||
})
|
||||
|
||||
it("uses fallback api key when not provided", () => {
|
||||
new PoeHandler({})
|
||||
|
||||
expect(mockCreatePoe).toHaveBeenCalledWith({
|
||||
apiKey: "not-provided",
|
||||
baseURL: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("getModel", () => {
|
||||
it("returns model info from cache", () => {
|
||||
const handler = new PoeHandler({ poeApiKey: "key", apiModelId: "anthropic/claude-sonnet-4" })
|
||||
const result = handler.getModel()
|
||||
|
||||
expect(result.id).toBe("anthropic/claude-sonnet-4")
|
||||
expect(result.info.contextWindow).toBe(200_000)
|
||||
expect(result.info.maxTokens).toBe(10_000)
|
||||
})
|
||||
|
||||
it("returns default model when no model ID specified", () => {
|
||||
const handler = new PoeHandler({ poeApiKey: "key" })
|
||||
const result = handler.getModel()
|
||||
|
||||
expect(result.id).toBe(poeDefaultModelId)
|
||||
})
|
||||
|
||||
it("falls back to default model info when model not in cache", () => {
|
||||
const handler = new PoeHandler({ poeApiKey: "key", apiModelId: "unknown/model" })
|
||||
const result = handler.getModel()
|
||||
|
||||
expect(result.id).toBe("unknown/model")
|
||||
expect(result.info.contextWindow).toBeGreaterThan(0)
|
||||
expect(result.info.maxTokens).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("createMessage", () => {
|
||||
it("streams text chunks", async () => {
|
||||
const handler = new PoeHandler({ poeApiKey: "key", apiModelId: "anthropic/claude-sonnet-4" })
|
||||
|
||||
const fullStream = (async function* () {
|
||||
yield { type: "text-delta", text: "Hello " }
|
||||
yield { type: "text-delta", text: "world!" }
|
||||
})()
|
||||
|
||||
mockStreamText.mockReturnValue({
|
||||
fullStream,
|
||||
usage: Promise.resolve({ inputTokens: 10, outputTokens: 5 }),
|
||||
})
|
||||
|
||||
const chunks = []
|
||||
for await (const chunk of handler.createMessage("system prompt", [
|
||||
{ role: "user" as const, content: "Hi" },
|
||||
])) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
expect(chunks).toContainEqual({ type: "text", text: "Hello " })
|
||||
expect(chunks).toContainEqual({ type: "text", text: "world!" })
|
||||
expect(chunks).toContainEqual(expect.objectContaining({ type: "usage", inputTokens: 10, outputTokens: 5 }))
|
||||
})
|
||||
})
|
||||
|
||||
describe("reasoning", () => {
|
||||
it("passes anthropic thinking config for budget models", async () => {
|
||||
const handler = new PoeHandler({
|
||||
poeApiKey: "key",
|
||||
apiModelId: "anthropic/claude-sonnet-4",
|
||||
enableReasoningEffort: true,
|
||||
modelMaxThinkingTokens: 4096,
|
||||
})
|
||||
|
||||
const fullStream = (async function* () {
|
||||
yield { type: "reasoning", text: "Let me think..." }
|
||||
yield { type: "text-delta", text: "Answer" }
|
||||
})()
|
||||
|
||||
mockStreamText.mockReturnValue({
|
||||
fullStream,
|
||||
usage: Promise.resolve({ inputTokens: 10, outputTokens: 5 }),
|
||||
})
|
||||
|
||||
const chunks = []
|
||||
const modelMaxTokens = handler.getModel().info.maxTokens ?? 0
|
||||
for await (const chunk of handler.createMessage("system", [
|
||||
{ role: "user" as const, content: "think about this" },
|
||||
])) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
const callArgs = mockStreamText.mock.calls[0][0]
|
||||
expect(callArgs.temperature).toBe(1.0)
|
||||
expect(callArgs.providerOptions).toEqual({
|
||||
poe: {
|
||||
reasoningBudgetTokens: 4096,
|
||||
},
|
||||
})
|
||||
expect(callArgs.maxOutputTokens).toBe(modelMaxTokens - 4096)
|
||||
|
||||
expect(chunks).toContainEqual({ type: "reasoning", text: "Let me think..." })
|
||||
expect(chunks).toContainEqual({ type: "text", text: "Answer" })
|
||||
})
|
||||
|
||||
it("passes openai reasoning effort for effort models", async () => {
|
||||
const handler = new PoeHandler({
|
||||
poeApiKey: "key",
|
||||
apiModelId: "openai/o3",
|
||||
enableReasoningEffort: true,
|
||||
reasoningEffort: "high",
|
||||
})
|
||||
|
||||
const fullStream = (async function* () {
|
||||
yield { type: "text-delta", text: "Answer" }
|
||||
})()
|
||||
|
||||
mockStreamText.mockReturnValue({
|
||||
fullStream,
|
||||
usage: Promise.resolve({ inputTokens: 10, outputTokens: 5 }),
|
||||
})
|
||||
|
||||
for await (const _ of handler.createMessage("system", [{ role: "user" as const, content: "reason" }])) {
|
||||
/* drain */
|
||||
}
|
||||
|
||||
expect(mockStreamText).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
providerOptions: {
|
||||
poe: {
|
||||
reasoningEffort: "high",
|
||||
reasoningSummary: "auto",
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("does not pass providerOptions when reasoning is disabled", async () => {
|
||||
const handler = new PoeHandler({
|
||||
poeApiKey: "key",
|
||||
apiModelId: "anthropic/claude-sonnet-4",
|
||||
enableReasoningEffort: false,
|
||||
})
|
||||
|
||||
const fullStream = (async function* () {
|
||||
yield { type: "text-delta", text: "Answer" }
|
||||
})()
|
||||
|
||||
mockStreamText.mockReturnValue({
|
||||
fullStream,
|
||||
usage: Promise.resolve({ inputTokens: 1, outputTokens: 1 }),
|
||||
})
|
||||
|
||||
for await (const _ of handler.createMessage("system", [{ role: "user" as const, content: "hi" }])) {
|
||||
/* drain */
|
||||
}
|
||||
|
||||
const callArgs = mockStreamText.mock.calls[0][0]
|
||||
expect(callArgs.providerOptions).toBeUndefined()
|
||||
expect(callArgs.temperature).toBeUndefined()
|
||||
})
|
||||
|
||||
it("uses default thinking budget when not specified", async () => {
|
||||
const handler = new PoeHandler({
|
||||
poeApiKey: "key",
|
||||
apiModelId: "anthropic/claude-sonnet-4",
|
||||
enableReasoningEffort: true,
|
||||
})
|
||||
|
||||
const fullStream = (async function* () {})()
|
||||
mockStreamText.mockReturnValue({
|
||||
fullStream,
|
||||
usage: Promise.resolve({ inputTokens: 0, outputTokens: 0 }),
|
||||
})
|
||||
|
||||
for await (const _ of handler.createMessage("system", [{ role: "user" as const, content: "hi" }])) {
|
||||
/* drain */
|
||||
}
|
||||
|
||||
const callArgs = mockStreamText.mock.calls[0][0]
|
||||
expect(callArgs.providerOptions).toEqual({
|
||||
poe: {
|
||||
reasoningBudgetTokens: expect.any(Number),
|
||||
},
|
||||
})
|
||||
expect(callArgs.providerOptions.poe.reasoningBudgetTokens + callArgs.maxOutputTokens).toBe(
|
||||
handler.getModel().info.maxTokens,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("completePrompt", () => {
|
||||
it("returns generated text", async () => {
|
||||
const handler = new PoeHandler({ poeApiKey: "key", apiModelId: "openai/gpt-4o" })
|
||||
|
||||
mockGenerateText.mockResolvedValue({ text: "generated response" })
|
||||
|
||||
const result = await handler.completePrompt("complete this")
|
||||
|
||||
expect(result).toBe("generated response")
|
||||
expect(mockGenerateText).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: mockLanguageModel,
|
||||
prompt: "complete this",
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -10,14 +10,16 @@ vitest.mock("@roo-code/telemetry", () => ({
|
|||
},
|
||||
}))
|
||||
|
||||
const mockCreate = vitest.fn()
|
||||
const mockResponsesCreate = vitest.fn()
|
||||
|
||||
vitest.mock("openai", () => {
|
||||
const mockConstructor = vitest.fn()
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
default: mockConstructor.mockImplementation(() => ({ chat: { completions: { create: mockCreate } } })),
|
||||
default: mockConstructor.mockImplementation(() => ({
|
||||
responses: { create: mockResponsesCreate },
|
||||
})),
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -28,16 +30,30 @@ import { xaiDefaultModelId, xaiModels } from "@roo-code/types"
|
|||
|
||||
import { XAIHandler } from "../xai"
|
||||
|
||||
// Helper to create an async iterable from events
|
||||
function mockStream(events: any[]) {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => {
|
||||
let index = 0
|
||||
return {
|
||||
async next() {
|
||||
if (index < events.length) {
|
||||
return { done: false, value: events[index++] }
|
||||
}
|
||||
return { done: true, value: undefined }
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe("XAIHandler", () => {
|
||||
let handler: XAIHandler
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset all mocks
|
||||
vi.clearAllMocks()
|
||||
mockCreate.mockClear()
|
||||
mockResponsesCreate.mockClear()
|
||||
mockCaptureException.mockClear()
|
||||
|
||||
// Create handler with mock
|
||||
handler = new XAIHandler({})
|
||||
})
|
||||
|
||||
|
|
@ -50,14 +66,9 @@ describe("XAIHandler", () => {
|
|||
})
|
||||
|
||||
it("should use the provided API key", () => {
|
||||
// Clear mocks before this specific test
|
||||
vi.clearAllMocks()
|
||||
|
||||
// Create a handler with our API key
|
||||
const xaiApiKey = "test-api-key"
|
||||
new XAIHandler({ xaiApiKey })
|
||||
|
||||
// Verify the OpenAI constructor was called with our API key
|
||||
expect(OpenAI).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
apiKey: xaiApiKey,
|
||||
|
|
@ -71,111 +82,41 @@ describe("XAIHandler", () => {
|
|||
expect(model.info).toEqual(xaiModels[xaiDefaultModelId])
|
||||
})
|
||||
|
||||
test("should return specified model when valid model is provided", () => {
|
||||
it("should return specified model when valid model is provided", () => {
|
||||
const testModelId = "grok-3"
|
||||
const handlerWithModel = new XAIHandler({ apiModelId: testModelId })
|
||||
const model = handlerWithModel.getModel()
|
||||
|
||||
expect(model.id).toBe(testModelId)
|
||||
expect(model.info).toEqual(xaiModels[testModelId])
|
||||
})
|
||||
|
||||
it("should include reasoning_effort parameter for mini models", async () => {
|
||||
const miniModelHandler = new XAIHandler({
|
||||
apiModelId: "grok-3-mini",
|
||||
reasoningEffort: "high",
|
||||
})
|
||||
it("should use Responses API (client.responses.create)", async () => {
|
||||
mockResponsesCreate.mockResolvedValueOnce(mockStream([]))
|
||||
|
||||
// Setup mock for streaming response
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
async next() {
|
||||
return { done: true }
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
const stream = handler.createMessage("test prompt", [])
|
||||
await stream.next()
|
||||
|
||||
// Start generating a message
|
||||
const messageGenerator = miniModelHandler.createMessage("test prompt", [])
|
||||
await messageGenerator.next() // Start the generator
|
||||
|
||||
// Check that reasoning_effort was included
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect(mockResponsesCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reasoning_effort: "high",
|
||||
model: xaiDefaultModelId,
|
||||
instructions: "test prompt",
|
||||
stream: true,
|
||||
store: false,
|
||||
include: ["reasoning.encrypted_content"],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should not include reasoning_effort parameter for non-mini models", async () => {
|
||||
const regularModelHandler = new XAIHandler({
|
||||
apiModelId: "grok-3",
|
||||
reasoningEffort: "high",
|
||||
})
|
||||
|
||||
// Setup mock for streaming response
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
async next() {
|
||||
return { done: true }
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
// Start generating a message
|
||||
const messageGenerator = regularModelHandler.createMessage("test prompt", [])
|
||||
await messageGenerator.next() // Start the generator
|
||||
|
||||
// Check call args for reasoning_effort
|
||||
const calls = mockCreate.mock.calls
|
||||
const lastCall = calls[calls.length - 1][0]
|
||||
expect(lastCall).not.toHaveProperty("reasoning_effort")
|
||||
})
|
||||
|
||||
it("completePrompt method should return text from OpenAI API", async () => {
|
||||
const expectedResponse = "This is a test response"
|
||||
mockCreate.mockResolvedValueOnce({ choices: [{ message: { content: expectedResponse } }] })
|
||||
|
||||
const result = await handler.completePrompt("test prompt")
|
||||
expect(result).toBe(expectedResponse)
|
||||
})
|
||||
|
||||
it("should handle errors in completePrompt", async () => {
|
||||
const errorMessage = "API error"
|
||||
mockCreate.mockRejectedValueOnce(new Error(errorMessage))
|
||||
|
||||
await expect(handler.completePrompt("test prompt")).rejects.toThrow(`xAI completion error: ${errorMessage}`)
|
||||
})
|
||||
|
||||
it("createMessage should yield text content from stream", async () => {
|
||||
const testContent = "This is test content"
|
||||
|
||||
// Setup mock for streaming response
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
next: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
done: false,
|
||||
value: {
|
||||
choices: [{ delta: { content: testContent } }],
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({ done: true }),
|
||||
}),
|
||||
}
|
||||
})
|
||||
mockResponsesCreate.mockResolvedValueOnce(
|
||||
mockStream([{ type: "response.output_text.delta", delta: testContent }]),
|
||||
)
|
||||
|
||||
// Create and consume the stream
|
||||
const stream = handler.createMessage("system prompt", [])
|
||||
const firstChunk = await stream.next()
|
||||
|
||||
// Verify the content
|
||||
expect(firstChunk.done).toBe(false)
|
||||
expect(firstChunk.value).toEqual({
|
||||
type: "text",
|
||||
|
|
@ -186,28 +127,13 @@ describe("XAIHandler", () => {
|
|||
it("createMessage should yield reasoning content from stream", async () => {
|
||||
const testReasoning = "Test reasoning content"
|
||||
|
||||
// Setup mock for streaming response
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
next: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
done: false,
|
||||
value: {
|
||||
choices: [{ delta: { reasoning_content: testReasoning } }],
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({ done: true }),
|
||||
}),
|
||||
}
|
||||
})
|
||||
mockResponsesCreate.mockResolvedValueOnce(
|
||||
mockStream([{ type: "response.reasoning_text.delta", delta: testReasoning }]),
|
||||
)
|
||||
|
||||
// Create and consume the stream
|
||||
const stream = handler.createMessage("system prompt", [])
|
||||
const firstChunk = await stream.next()
|
||||
|
||||
// Verify the reasoning content
|
||||
expect(firstChunk.done).toBe(false)
|
||||
expect(firstChunk.value).toEqual({
|
||||
type: "reasoning",
|
||||
|
|
@ -215,373 +141,158 @@ describe("XAIHandler", () => {
|
|||
})
|
||||
})
|
||||
|
||||
it("createMessage should yield usage data from stream", async () => {
|
||||
// Setup mock for streaming response that includes usage data
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
next: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
done: false,
|
||||
value: {
|
||||
choices: [{ delta: {} }], // Needs to have choices array to avoid error
|
||||
usage: {
|
||||
prompt_tokens: 10,
|
||||
completion_tokens: 20,
|
||||
cache_read_input_tokens: 5,
|
||||
cache_creation_input_tokens: 15,
|
||||
},
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({ done: true }),
|
||||
}),
|
||||
}
|
||||
})
|
||||
it("createMessage should yield usage data from response.completed", async () => {
|
||||
mockResponsesCreate.mockResolvedValueOnce(
|
||||
mockStream([
|
||||
{
|
||||
type: "response.completed",
|
||||
response: {
|
||||
usage: {
|
||||
input_tokens: 10,
|
||||
output_tokens: 20,
|
||||
input_tokens_details: { cached_tokens: 5 },
|
||||
output_tokens_details: { reasoning_tokens: 8 },
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
)
|
||||
|
||||
// Create and consume the stream
|
||||
const stream = handler.createMessage("system prompt", [])
|
||||
const firstChunk = await stream.next()
|
||||
|
||||
// Verify the usage data
|
||||
expect(firstChunk.done).toBe(false)
|
||||
expect(firstChunk.value).toEqual({
|
||||
type: "usage",
|
||||
inputTokens: 10,
|
||||
outputTokens: 20,
|
||||
cacheReadTokens: 5,
|
||||
cacheWriteTokens: 15,
|
||||
})
|
||||
})
|
||||
|
||||
it("createMessage should pass correct parameters to OpenAI client", async () => {
|
||||
// Setup a handler with specific model
|
||||
const modelId = "grok-3"
|
||||
const modelInfo = xaiModels[modelId]
|
||||
const handlerWithModel = new XAIHandler({ apiModelId: modelId })
|
||||
|
||||
// Setup mock for streaming response
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
async next() {
|
||||
return { done: true }
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
// System prompt and messages
|
||||
const systemPrompt = "Test system prompt"
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test message" }]
|
||||
|
||||
// Start generating a message
|
||||
const messageGenerator = handlerWithModel.createMessage(systemPrompt, messages)
|
||||
await messageGenerator.next() // Start the generator
|
||||
|
||||
// Check that all parameters were passed correctly
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect(firstChunk.value).toEqual(
|
||||
expect.objectContaining({
|
||||
model: modelId,
|
||||
max_tokens: modelInfo.maxTokens,
|
||||
temperature: 0,
|
||||
messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]),
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
type: "usage",
|
||||
inputTokens: 10,
|
||||
outputTokens: 20,
|
||||
cacheReadTokens: 5,
|
||||
reasoningTokens: 8,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe("Native Tool Calling", () => {
|
||||
it("createMessage should yield tool_call from output_item.done", async () => {
|
||||
mockResponsesCreate.mockResolvedValueOnce(
|
||||
mockStream([
|
||||
{
|
||||
type: "response.output_item.done",
|
||||
item: {
|
||||
type: "function_call",
|
||||
call_id: "call_123",
|
||||
name: "test_tool",
|
||||
arguments: '{"arg1":"value"}',
|
||||
},
|
||||
},
|
||||
]),
|
||||
)
|
||||
|
||||
const stream = handler.createMessage("system prompt", [])
|
||||
const firstChunk = await stream.next()
|
||||
|
||||
expect(firstChunk.done).toBe(false)
|
||||
expect(firstChunk.value).toEqual({
|
||||
type: "tool_call",
|
||||
id: "call_123",
|
||||
name: "test_tool",
|
||||
arguments: '{"arg1":"value"}',
|
||||
})
|
||||
})
|
||||
|
||||
it("should include tools in Responses API format", async () => {
|
||||
const testTools = [
|
||||
{
|
||||
type: "function" as const,
|
||||
function: {
|
||||
name: "test_tool",
|
||||
description: "A test tool",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
arg1: { type: "string", description: "First argument" },
|
||||
},
|
||||
required: ["arg1"],
|
||||
},
|
||||
parameters: { type: "object", properties: { arg1: { type: "string" } }, required: ["arg1"] },
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
it("should include tools in request when model supports native tools and tools are provided (native is default)", async () => {
|
||||
const handlerWithTools = new XAIHandler({ apiModelId: "grok-3" })
|
||||
mockResponsesCreate.mockResolvedValueOnce(mockStream([]))
|
||||
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
async next() {
|
||||
return { done: true }
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
const messageGenerator = handlerWithTools.createMessage("test prompt", [], {
|
||||
taskId: "test-task-id",
|
||||
tools: testTools,
|
||||
})
|
||||
await messageGenerator.next()
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
tools: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: "function",
|
||||
function: expect.objectContaining({
|
||||
name: "test_tool",
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
parallel_tool_calls: true,
|
||||
}),
|
||||
)
|
||||
const stream = handler.createMessage("test prompt", [], {
|
||||
taskId: "test-task-id",
|
||||
tools: testTools,
|
||||
})
|
||||
await stream.next()
|
||||
|
||||
it("should include tool_choice when provided", async () => {
|
||||
const handlerWithTools = new XAIHandler({ apiModelId: "grok-3" })
|
||||
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
async next() {
|
||||
return { done: true }
|
||||
},
|
||||
expect(mockResponsesCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
tools: [
|
||||
expect.objectContaining({
|
||||
type: "function",
|
||||
name: "test_tool",
|
||||
description: "A test tool",
|
||||
strict: true,
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
const messageGenerator = handlerWithTools.createMessage("test prompt", [], {
|
||||
taskId: "test-task-id",
|
||||
tools: testTools,
|
||||
],
|
||||
tool_choice: "auto",
|
||||
})
|
||||
await messageGenerator.next()
|
||||
parallel_tool_calls: true,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
tool_choice: "auto",
|
||||
it("completePrompt should return text from Responses API", async () => {
|
||||
const expectedResponse = "This is a test response"
|
||||
mockResponsesCreate.mockResolvedValueOnce({
|
||||
output_text: expectedResponse,
|
||||
})
|
||||
|
||||
const result = await handler.completePrompt("test prompt")
|
||||
expect(result).toBe(expectedResponse)
|
||||
})
|
||||
|
||||
it("should handle errors in completePrompt", async () => {
|
||||
const errorMessage = "API error"
|
||||
mockResponsesCreate.mockRejectedValueOnce(new Error(errorMessage))
|
||||
|
||||
await expect(handler.completePrompt("test prompt")).rejects.toThrow(`xAI completion error: ${errorMessage}`)
|
||||
})
|
||||
|
||||
it("should include reasoning_effort for mini models", async () => {
|
||||
const miniModelHandler = new XAIHandler({
|
||||
apiModelId: "grok-3-mini",
|
||||
reasoningEffort: "high",
|
||||
})
|
||||
|
||||
mockResponsesCreate.mockResolvedValueOnce(mockStream([]))
|
||||
|
||||
const stream = miniModelHandler.createMessage("test prompt", [])
|
||||
await stream.next()
|
||||
|
||||
expect(mockResponsesCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reasoning: expect.objectContaining({
|
||||
reasoning_effort: "high",
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should not include reasoning for non-mini models", async () => {
|
||||
const regularHandler = new XAIHandler({
|
||||
apiModelId: "grok-3",
|
||||
reasoningEffort: "high",
|
||||
})
|
||||
|
||||
it("should always include tools and tool_choice (tools are guaranteed to be present after ALWAYS_AVAILABLE_TOOLS)", async () => {
|
||||
const handlerWithTools = new XAIHandler({ apiModelId: "grok-3" })
|
||||
mockResponsesCreate.mockResolvedValueOnce(mockStream([]))
|
||||
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
async next() {
|
||||
return { done: true }
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
const stream = regularHandler.createMessage("test prompt", [])
|
||||
await stream.next()
|
||||
|
||||
const messageGenerator = handlerWithTools.createMessage("test prompt", [], {
|
||||
taskId: "test-task-id",
|
||||
})
|
||||
await messageGenerator.next()
|
||||
const callArgs = mockResponsesCreate.mock.calls[mockResponsesCreate.mock.calls.length - 1][0]
|
||||
expect(callArgs).not.toHaveProperty("reasoning")
|
||||
})
|
||||
|
||||
// Tools are now always present (minimum 6 from ALWAYS_AVAILABLE_TOOLS)
|
||||
const callArgs = mockCreate.mock.calls[mockCreate.mock.calls.length - 1][0]
|
||||
expect(callArgs).toHaveProperty("tools")
|
||||
expect(callArgs).toHaveProperty("tool_choice")
|
||||
expect(callArgs).toHaveProperty("parallel_tool_calls", true)
|
||||
})
|
||||
it("should handle errors in createMessage", async () => {
|
||||
const errorMessage = "Stream error"
|
||||
mockResponsesCreate.mockRejectedValueOnce(new Error(errorMessage))
|
||||
|
||||
it("should yield tool_call_partial chunks during streaming", async () => {
|
||||
const handlerWithTools = new XAIHandler({ apiModelId: "grok-3" })
|
||||
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
next: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
done: false,
|
||||
value: {
|
||||
choices: [
|
||||
{
|
||||
delta: {
|
||||
tool_calls: [
|
||||
{
|
||||
index: 0,
|
||||
id: "call_123",
|
||||
function: {
|
||||
name: "test_tool",
|
||||
arguments: '{"arg1":',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
done: false,
|
||||
value: {
|
||||
choices: [
|
||||
{
|
||||
delta: {
|
||||
tool_calls: [
|
||||
{
|
||||
index: 0,
|
||||
function: {
|
||||
arguments: '"value"}',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({ done: true }),
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
const stream = handlerWithTools.createMessage("test prompt", [], {
|
||||
taskId: "test-task-id",
|
||||
tools: testTools,
|
||||
})
|
||||
|
||||
const chunks = []
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
expect(chunks).toContainEqual({
|
||||
type: "tool_call_partial",
|
||||
index: 0,
|
||||
id: "call_123",
|
||||
name: "test_tool",
|
||||
arguments: '{"arg1":',
|
||||
})
|
||||
|
||||
expect(chunks).toContainEqual({
|
||||
type: "tool_call_partial",
|
||||
index: 0,
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
arguments: '"value"}',
|
||||
})
|
||||
})
|
||||
|
||||
it("should set parallel_tool_calls based on metadata", async () => {
|
||||
const handlerWithTools = new XAIHandler({ apiModelId: "grok-3" })
|
||||
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
async next() {
|
||||
return { done: true }
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
const messageGenerator = handlerWithTools.createMessage("test prompt", [], {
|
||||
taskId: "test-task-id",
|
||||
tools: testTools,
|
||||
parallelToolCalls: true,
|
||||
})
|
||||
await messageGenerator.next()
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
parallel_tool_calls: true,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should yield tool_call_end events when finish_reason is tool_calls", async () => {
|
||||
// Import NativeToolCallParser to set up state
|
||||
const { NativeToolCallParser } = await import("../../../core/assistant-message/NativeToolCallParser")
|
||||
|
||||
// Clear any previous state
|
||||
NativeToolCallParser.clearRawChunkState()
|
||||
|
||||
const handlerWithTools = new XAIHandler({ apiModelId: "grok-3" })
|
||||
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
next: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
done: false,
|
||||
value: {
|
||||
choices: [
|
||||
{
|
||||
delta: {
|
||||
tool_calls: [
|
||||
{
|
||||
index: 0,
|
||||
id: "call_xai_test",
|
||||
function: {
|
||||
name: "test_tool",
|
||||
arguments: '{"arg1":"value"}',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
done: false,
|
||||
value: {
|
||||
choices: [
|
||||
{
|
||||
delta: {},
|
||||
finish_reason: "tool_calls",
|
||||
},
|
||||
],
|
||||
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({ done: true }),
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
const stream = handlerWithTools.createMessage("test prompt", [], {
|
||||
taskId: "test-task-id",
|
||||
tools: testTools,
|
||||
})
|
||||
|
||||
const chunks = []
|
||||
for await (const chunk of stream) {
|
||||
// Simulate what Task.ts does: when we receive tool_call_partial,
|
||||
// process it through NativeToolCallParser to populate rawChunkTracker
|
||||
if (chunk.type === "tool_call_partial") {
|
||||
NativeToolCallParser.processRawChunk({
|
||||
index: chunk.index,
|
||||
id: chunk.id,
|
||||
name: chunk.name,
|
||||
arguments: chunk.arguments,
|
||||
})
|
||||
}
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
// Should have tool_call_partial and tool_call_end
|
||||
const partialChunks = chunks.filter((chunk) => chunk.type === "tool_call_partial")
|
||||
const endChunks = chunks.filter((chunk) => chunk.type === "tool_call_end")
|
||||
|
||||
expect(partialChunks).toHaveLength(1)
|
||||
expect(endChunks).toHaveLength(1)
|
||||
expect(endChunks[0].id).toBe("call_xai_test")
|
||||
})
|
||||
const stream = handler.createMessage("test prompt", [])
|
||||
await expect(stream.next()).rejects.toThrow(`xAI completion error: ${errorMessage}`)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
131
src/api/providers/fetchers/__tests__/poe.spec.ts
Normal file
131
src/api/providers/fetchers/__tests__/poe.spec.ts
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import { getPoeModels } from "../poe"
|
||||
|
||||
const mockFetchPoeModels = vitest.fn()
|
||||
const mockGetModels = vitest.fn()
|
||||
|
||||
vitest.mock("ai-sdk-provider-poe/code", () => ({
|
||||
fetchPoeModels: (...args: unknown[]) => mockFetchPoeModels(...args),
|
||||
getModels: () => mockGetModels(),
|
||||
}))
|
||||
|
||||
describe("getPoeModels", () => {
|
||||
beforeEach(() => vitest.clearAllMocks())
|
||||
|
||||
it("converts PoeModelInfo to ModelRecord", async () => {
|
||||
mockFetchPoeModels.mockResolvedValue([])
|
||||
mockGetModels.mockReturnValue([
|
||||
{
|
||||
id: "anthropic/claude-sonnet-4",
|
||||
rawId: "claude-sonnet-4",
|
||||
contextWindow: 200_000,
|
||||
maxOutputTokens: 8192,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningBudget: true,
|
||||
pricing: {
|
||||
inputPerMillion: 3,
|
||||
outputPerMillion: 15,
|
||||
cacheReadPerMillion: 0.3,
|
||||
cacheWritePerMillion: 3.75,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "openai/gpt-4o",
|
||||
rawId: "gpt-4o",
|
||||
contextWindow: 128_000,
|
||||
maxOutputTokens: 16_384,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
pricing: {
|
||||
inputPerMillion: 2.5,
|
||||
outputPerMillion: 10,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const models = await getPoeModels("test-key")
|
||||
|
||||
expect(mockFetchPoeModels).toHaveBeenCalledWith({ apiKey: "test-key", baseURL: undefined })
|
||||
|
||||
expect(models["anthropic/claude-sonnet-4"]).toEqual({
|
||||
contextWindow: 200_000,
|
||||
maxTokens: 8192,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningBudget: true,
|
||||
inputPrice: 3,
|
||||
outputPrice: 15,
|
||||
cacheReadsPrice: 0.3,
|
||||
cacheWritesPrice: 3.75,
|
||||
})
|
||||
|
||||
expect(models["openai/gpt-4o"]).toEqual({
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 16_384,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 2.5,
|
||||
outputPrice: 10,
|
||||
})
|
||||
})
|
||||
|
||||
it("passes baseURL when provided", async () => {
|
||||
mockFetchPoeModels.mockResolvedValue([])
|
||||
mockGetModels.mockReturnValue([])
|
||||
|
||||
await getPoeModels("key", "https://custom.api.com/v1")
|
||||
|
||||
expect(mockFetchPoeModels).toHaveBeenCalledWith({ apiKey: "key", baseURL: "https://custom.api.com/v1" })
|
||||
})
|
||||
|
||||
it("returns empty record on empty response", async () => {
|
||||
mockFetchPoeModels.mockResolvedValue([])
|
||||
mockGetModels.mockReturnValue([])
|
||||
|
||||
const models = await getPoeModels("key")
|
||||
|
||||
expect(models).toEqual({})
|
||||
})
|
||||
|
||||
it("handles models with missing optional fields", async () => {
|
||||
mockFetchPoeModels.mockResolvedValue([])
|
||||
mockGetModels.mockReturnValue([
|
||||
{
|
||||
id: "some-model",
|
||||
rawId: "some-model",
|
||||
contextWindow: 4096,
|
||||
maxOutputTokens: 1024,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
},
|
||||
])
|
||||
|
||||
const models = await getPoeModels("key")
|
||||
|
||||
expect(models["some-model"]).toEqual({
|
||||
contextWindow: 4096,
|
||||
maxTokens: 1024,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
})
|
||||
})
|
||||
|
||||
it("maps supportsReasoningEffort when present", async () => {
|
||||
mockFetchPoeModels.mockResolvedValue([])
|
||||
mockGetModels.mockReturnValue([
|
||||
{
|
||||
id: "openai/o3",
|
||||
rawId: "o3",
|
||||
contextWindow: 200_000,
|
||||
maxOutputTokens: 100_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
supportsReasoningEffort: ["low", "medium", "high"],
|
||||
},
|
||||
])
|
||||
|
||||
const models = await getPoeModels("key")
|
||||
|
||||
expect(models["openai/o3"].supportsReasoningEffort).toEqual(["low", "medium", "high"])
|
||||
})
|
||||
})
|
||||
|
|
@ -24,6 +24,7 @@ import { getLiteLLMModels } from "./litellm"
|
|||
import { GetModelsOptions } from "../../../shared/api"
|
||||
import { getOllamaModels } from "./ollama"
|
||||
import { getLMStudioModels } from "./lmstudio"
|
||||
import { getPoeModels } from "./poe"
|
||||
import { getRooModels } from "./roo"
|
||||
|
||||
const memoryCache = new NodeCache({ stdTTL: 5 * 60, checkperiod: 5 * 60 })
|
||||
|
|
@ -91,6 +92,9 @@ async function fetchModelsFromProvider(options: GetModelsOptions): Promise<Model
|
|||
models = await getRooModels(rooBaseUrl, options.apiKey)
|
||||
break
|
||||
}
|
||||
case "poe":
|
||||
models = await getPoeModels(options.apiKey, options.baseUrl)
|
||||
break
|
||||
default: {
|
||||
// Ensures router is exhaustively checked if RouterName is a strict union.
|
||||
const exhaustiveCheck: never = provider
|
||||
|
|
|
|||
44
src/api/providers/fetchers/poe.ts
Normal file
44
src/api/providers/fetchers/poe.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import type { ModelInfo, ModelRecord } from "@roo-code/types"
|
||||
import { fetchPoeModels, getModels } from "ai-sdk-provider-poe/code"
|
||||
|
||||
export async function getPoeModels(apiKey?: string, baseURL?: string): Promise<ModelRecord> {
|
||||
try {
|
||||
// fetchPoeModels populates the internal model store, then getModels()
|
||||
// returns only code-capable models with camelCase fields.
|
||||
await fetchPoeModels({ apiKey, baseURL })
|
||||
const poeModels = getModels()
|
||||
const models: ModelRecord = {}
|
||||
|
||||
for (const m of poeModels) {
|
||||
// The library's applyReasoningFallbacks workaround sets
|
||||
// supportsReasoningEffort to boolean `true` for any model that
|
||||
// supports /v1/responses, even when the model has no actual
|
||||
// reasoning capability (e.g. Haiku 3/3.5). Only trust the value
|
||||
// when it is an explicit array of effort levels.
|
||||
const effort = Array.isArray(m.supportsReasoningEffort) ? m.supportsReasoningEffort : undefined
|
||||
const info: ModelInfo = {
|
||||
contextWindow: m.contextWindow,
|
||||
maxTokens: m.maxOutputTokens,
|
||||
supportsImages: m.supportsImages,
|
||||
supportsPromptCache: m.supportsPromptCache,
|
||||
...(m.supportsReasoningBudget && { supportsReasoningBudget: m.supportsReasoningBudget }),
|
||||
...(effort && {
|
||||
supportsReasoningEffort: effort as ModelInfo["supportsReasoningEffort"],
|
||||
}),
|
||||
...(m.pricing?.inputPerMillion != null && { inputPrice: m.pricing.inputPerMillion }),
|
||||
...(m.pricing?.outputPerMillion != null && { outputPrice: m.pricing.outputPerMillion }),
|
||||
...(m.pricing?.cacheReadPerMillion != null && { cacheReadsPrice: m.pricing.cacheReadPerMillion }),
|
||||
...(m.pricing?.cacheWritePerMillion != null && { cacheWritesPrice: m.pricing.cacheWritePerMillion }),
|
||||
}
|
||||
|
||||
models[m.id] = info
|
||||
}
|
||||
|
||||
return models
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[Poe] Error fetching models: ${JSON.stringify(error, Object.getOwnPropertyNames(error as object), 2)}`,
|
||||
)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ export { OpenAiHandler } from "./openai"
|
|||
export { OpenAICompatibleHandler } from "./openai-compatible"
|
||||
export type { OpenAICompatibleConfig } from "./openai-compatible"
|
||||
export { OpenRouterHandler } from "./openrouter"
|
||||
export { PoeHandler } from "./poe"
|
||||
export { QwenCodeHandler } from "./qwen-code"
|
||||
export { RequestyHandler } from "./requesty"
|
||||
export { SambaNovaHandler } from "./sambanova"
|
||||
|
|
|
|||
151
src/api/providers/poe.ts
Normal file
151
src/api/providers/poe.ts
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { createPoe, type PoeProvider, type PoeScopedProviderOptions } from "ai-sdk-provider-poe"
|
||||
import { extractUsageMetrics, mapToolChoice } from "ai-sdk-provider-poe/code"
|
||||
import { streamText, generateText, type ToolSet } from "ai"
|
||||
|
||||
import {
|
||||
poeDefaultModelId,
|
||||
getPoeDefaultModelInfo,
|
||||
type ModelInfo,
|
||||
type ReasoningEffortExtended,
|
||||
ApiProviderError,
|
||||
} from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
||||
import { shouldUseReasoningBudget, shouldUseReasoningEffort, type ApiHandlerOptions } from "../../shared/api"
|
||||
|
||||
import { convertToAiSdkMessages, convertToolsForAiSdk, processAiSdkStreamPart } from "../transform/ai-sdk"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
import { BaseProvider } from "./base-provider"
|
||||
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
||||
import { getModelsFromCache } from "./fetchers/modelCache"
|
||||
|
||||
const DEFAULT_THINKING_BUDGET = 8192
|
||||
|
||||
export class PoeHandler extends BaseProvider implements SingleCompletionHandler {
|
||||
protected options: ApiHandlerOptions
|
||||
private poe: PoeProvider
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
super()
|
||||
this.options = options
|
||||
this.poe = createPoe({
|
||||
apiKey: options.poeApiKey ?? "not-provided",
|
||||
baseURL: options.poeBaseUrl || undefined,
|
||||
})
|
||||
}
|
||||
|
||||
override getModel() {
|
||||
const id = this.options.apiModelId ?? poeDefaultModelId
|
||||
const cached = getModelsFromCache("poe")
|
||||
const info: ModelInfo = cached?.[id] ?? getPoeDefaultModelInfo()
|
||||
return { id, info }
|
||||
}
|
||||
|
||||
override async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
metadata?: ApiHandlerCreateMessageMetadata,
|
||||
): ApiStream {
|
||||
const { id, info } = this.getModel()
|
||||
const languageModel = this.poe(id)
|
||||
|
||||
const aiSdkMessages = convertToAiSdkMessages(messages)
|
||||
const openAiTools = this.convertToolsForOpenAI(metadata?.tools)
|
||||
const aiSdkTools = convertToolsForAiSdk(openAiTools) as ToolSet | undefined
|
||||
|
||||
const useBudget = shouldUseReasoningBudget({ model: info, settings: this.options })
|
||||
const useEffort = !useBudget && shouldUseReasoningEffort({ model: info, settings: this.options })
|
||||
|
||||
// Only pass temperature when the user explicitly configured it.
|
||||
let temperature: number | undefined = this.options.modelTemperature ?? undefined
|
||||
let maxOutputTokens: number | undefined
|
||||
const providerOptions: NonNullable<Parameters<typeof streamText>[0]["providerOptions"]> & {
|
||||
poe?: PoeScopedProviderOptions
|
||||
} = {}
|
||||
|
||||
if (useBudget) {
|
||||
const requestedBudget = this.options.modelMaxThinkingTokens ?? DEFAULT_THINKING_BUDGET
|
||||
// maxOutputTokens is the text-only budget; reasoningBudgetTokens is
|
||||
// separate, so total output = maxOutputTokens + reasoningBudgetTokens.
|
||||
maxOutputTokens = this.options.modelMaxTokens ?? Math.max(0, (info.maxTokens ?? 0) - requestedBudget)
|
||||
providerOptions.poe = {
|
||||
reasoningBudgetTokens: requestedBudget,
|
||||
}
|
||||
temperature = 1.0
|
||||
} else if (useEffort) {
|
||||
let effort = (this.options.reasoningEffort ?? info.reasoningEffort ?? "medium") as ReasoningEffortExtended
|
||||
// Validate that the effort level is actually supported by the current model
|
||||
const supportedEfforts = info.supportsReasoningEffort
|
||||
if (Array.isArray(supportedEfforts) && !supportedEfforts.includes(effort as any)) {
|
||||
effort = (info.reasoningEffort as ReasoningEffortExtended) ?? "medium"
|
||||
}
|
||||
providerOptions.poe = {
|
||||
reasoningEffort: effort,
|
||||
reasoningSummary: "auto",
|
||||
}
|
||||
if (this.options.modelMaxTokens) {
|
||||
maxOutputTokens = this.options.modelMaxTokens
|
||||
}
|
||||
}
|
||||
|
||||
let result
|
||||
try {
|
||||
result = streamText({
|
||||
model: languageModel,
|
||||
system: systemPrompt,
|
||||
messages: aiSdkMessages,
|
||||
temperature,
|
||||
maxOutputTokens,
|
||||
tools: aiSdkTools,
|
||||
toolChoice: mapToolChoice(metadata?.tool_choice as any),
|
||||
...(Object.keys(providerOptions).length > 0 && { providerOptions }),
|
||||
})
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
TelemetryService.instance.captureException(new ApiProviderError(errorMessage, "poe", id, "createMessage"))
|
||||
throw new Error(`Poe completion error: ${errorMessage}`)
|
||||
}
|
||||
|
||||
try {
|
||||
for await (const part of result.fullStream) {
|
||||
for (const chunk of processAiSdkStreamPart(part)) {
|
||||
yield chunk
|
||||
}
|
||||
}
|
||||
|
||||
const usage = await result.usage
|
||||
if (usage) {
|
||||
const metrics = extractUsageMetrics(usage as any)
|
||||
yield {
|
||||
type: "usage" as const,
|
||||
inputTokens: metrics.inputTokens,
|
||||
outputTokens: metrics.outputTokens,
|
||||
cacheReadTokens: metrics.cacheReadTokens,
|
||||
cacheWriteTokens: metrics.cacheWriteTokens,
|
||||
reasoningTokens: metrics.reasoningTokens,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
TelemetryService.instance.captureException(new ApiProviderError(errorMessage, "poe", id, "createMessage"))
|
||||
throw new Error(`Poe streaming error: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
|
||||
async completePrompt(prompt: string): Promise<string> {
|
||||
const { id } = this.getModel()
|
||||
try {
|
||||
const { text } = await generateText({
|
||||
model: this.poe(id),
|
||||
prompt,
|
||||
})
|
||||
return text
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
TelemetryService.instance.captureException(new ApiProviderError(errorMessage, "poe", id, "completePrompt"))
|
||||
throw new Error(`Poe completion error: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -69,6 +69,12 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan
|
|||
this.client = new OpenAI({
|
||||
apiKey: "dummy-key-will-be-replaced",
|
||||
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
defaultHeaders: {
|
||||
"User-Agent": `QwenCode/1.0.0 (${os.platform()}; ${os.arch()})`,
|
||||
"X-DashScope-CacheControl": "enable",
|
||||
"X-DashScope-UserAgent": `QwenCode/1.0.0 (${os.platform()}; ${os.arch()})`,
|
||||
"X-DashScope-AuthType": "qwen-oauth",
|
||||
},
|
||||
})
|
||||
}
|
||||
return this.client
|
||||
|
|
|
|||
|
|
@ -4,17 +4,18 @@ import OpenAI from "openai"
|
|||
import { type XAIModelId, xaiDefaultModelId, xaiModels, ApiProviderError } from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
||||
import { NativeToolCallParser } from "../../core/assistant-message/NativeToolCallParser"
|
||||
import type { ApiHandlerOptions } from "../../shared/api"
|
||||
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { convertToResponsesApiInput } from "../transform/responses-api-input"
|
||||
import { processResponsesApiStream, createUsageNormalizer } from "../transform/responses-api-stream"
|
||||
import { getModelParams } from "../transform/model-params"
|
||||
|
||||
import { DEFAULT_HEADERS } from "./constants"
|
||||
import { BaseProvider } from "./base-provider"
|
||||
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
||||
import { handleOpenAIError } from "./utils/openai-error-handler"
|
||||
import { isMcpTool } from "../../utils/mcp-name"
|
||||
|
||||
const XAI_DEFAULT_TEMPERATURE = 0
|
||||
|
||||
|
|
@ -53,118 +54,108 @@ export class XAIHandler extends BaseProvider implements SingleCompletionHandler
|
|||
return { id, info, ...params }
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert tools from OpenAI Chat Completions format to Responses API format.
|
||||
* Chat Completions: { type: "function", function: { name, description, parameters } }
|
||||
* Responses API: { type: "function", name, description, parameters }
|
||||
*
|
||||
* Uses base provider's convertToolSchemaForOpenAI() for schema hardening
|
||||
* (additionalProperties: false, ensureAllRequired) and handles MCP tools.
|
||||
*/
|
||||
private mapResponseTools(tools?: any[]): any[] | undefined {
|
||||
const converted = this.convertToolsForOpenAI(tools)
|
||||
if (!converted?.length) {
|
||||
return undefined
|
||||
}
|
||||
return converted
|
||||
.filter((tool) => tool?.type === "function")
|
||||
.map((tool) => {
|
||||
const isMcp = isMcpTool(tool.function.name)
|
||||
return {
|
||||
type: "function",
|
||||
name: tool.function.name,
|
||||
description: tool.function.description,
|
||||
parameters: isMcp
|
||||
? tool.function.parameters
|
||||
: this.convertToolSchemaForOpenAI(tool.function.parameters),
|
||||
strict: !isMcp,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
metadata?: ApiHandlerCreateMessageMetadata,
|
||||
): ApiStream {
|
||||
const { id: modelId, info: modelInfo, reasoning } = this.getModel()
|
||||
const model = this.getModel()
|
||||
|
||||
// Use the OpenAI-compatible API.
|
||||
const requestOptions = {
|
||||
model: modelId,
|
||||
max_tokens: modelInfo.maxTokens,
|
||||
temperature: this.options.modelTemperature ?? XAI_DEFAULT_TEMPERATURE,
|
||||
messages: [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOpenAiMessages(messages),
|
||||
] as OpenAI.Chat.ChatCompletionMessageParam[],
|
||||
stream: true as const,
|
||||
stream_options: { include_usage: true },
|
||||
...(reasoning && reasoning),
|
||||
tools: this.convertToolsForOpenAI(metadata?.tools),
|
||||
tool_choice: metadata?.tool_choice,
|
||||
parallel_tool_calls: metadata?.parallelToolCalls ?? true,
|
||||
// Convert directly from Anthropic format to Responses API input format
|
||||
const input = convertToResponsesApiInput(messages)
|
||||
const responseTools = this.mapResponseTools(metadata?.tools)
|
||||
|
||||
// Build request options
|
||||
const requestBody: Record<string, any> = {
|
||||
model: model.id,
|
||||
instructions: systemPrompt,
|
||||
input: input,
|
||||
stream: true,
|
||||
store: false, // Don't store responses server-side for privacy
|
||||
include: ["reasoning.encrypted_content"],
|
||||
}
|
||||
|
||||
let stream
|
||||
if (model.maxTokens) {
|
||||
requestBody.max_output_tokens = model.maxTokens
|
||||
}
|
||||
|
||||
if (model.temperature !== undefined) {
|
||||
requestBody.temperature = model.temperature
|
||||
}
|
||||
|
||||
if (responseTools) {
|
||||
requestBody.tools = responseTools
|
||||
// Cast tool_choice since metadata uses Chat Completions types but Responses API has its own type
|
||||
requestBody.tool_choice = (metadata?.tool_choice ?? "auto") as any
|
||||
requestBody.parallel_tool_calls = metadata?.parallelToolCalls ?? true
|
||||
}
|
||||
|
||||
// Pass reasoning effort for models that support it (e.g., mini models)
|
||||
if (model.reasoning) {
|
||||
requestBody.reasoning = model.reasoning
|
||||
}
|
||||
|
||||
let stream: AsyncIterable<any>
|
||||
try {
|
||||
stream = await this.client.chat.completions.create(requestOptions)
|
||||
stream = (await this.client.responses.create({
|
||||
...requestBody,
|
||||
stream: true,
|
||||
} as any)) as unknown as AsyncIterable<any>
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const apiError = new ApiProviderError(errorMessage, this.providerName, modelId, "createMessage")
|
||||
const apiError = new ApiProviderError(errorMessage, this.providerName, model.id, "createMessage")
|
||||
TelemetryService.instance.captureException(apiError)
|
||||
throw handleOpenAIError(error, this.providerName)
|
||||
}
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
const finishReason = chunk.choices[0]?.finish_reason
|
||||
|
||||
if (delta?.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
|
||||
if (delta && "reasoning_content" in delta && delta.reasoning_content) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
text: delta.reasoning_content as string,
|
||||
}
|
||||
}
|
||||
|
||||
// Handle tool calls in stream - emit partial chunks for NativeToolCallParser
|
||||
if (delta?.tool_calls) {
|
||||
for (const toolCall of delta.tool_calls) {
|
||||
yield {
|
||||
type: "tool_call_partial",
|
||||
index: toolCall.index,
|
||||
id: toolCall.id,
|
||||
name: toolCall.function?.name,
|
||||
arguments: toolCall.function?.arguments,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process finish_reason to emit tool_call_end events
|
||||
// This ensures tool calls are finalized even if the stream doesn't properly close
|
||||
if (finishReason) {
|
||||
const endEvents = NativeToolCallParser.processFinishReason(finishReason)
|
||||
for (const event of endEvents) {
|
||||
yield event
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
// Extract detailed token information if available
|
||||
// First check for prompt_tokens_details structure (real API response)
|
||||
const promptDetails = "prompt_tokens_details" in chunk.usage ? chunk.usage.prompt_tokens_details : null
|
||||
const cachedTokens = promptDetails && "cached_tokens" in promptDetails ? promptDetails.cached_tokens : 0
|
||||
|
||||
// Fall back to direct fields in usage (used in test mocks)
|
||||
const readTokens =
|
||||
cachedTokens ||
|
||||
("cache_read_input_tokens" in chunk.usage ? (chunk.usage as any).cache_read_input_tokens : 0)
|
||||
const writeTokens =
|
||||
"cache_creation_input_tokens" in chunk.usage ? (chunk.usage as any).cache_creation_input_tokens : 0
|
||||
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: chunk.usage.prompt_tokens || 0,
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
cacheReadTokens: readTokens,
|
||||
cacheWriteTokens: writeTokens,
|
||||
}
|
||||
}
|
||||
}
|
||||
const normalizeUsage = createUsageNormalizer()
|
||||
yield* processResponsesApiStream(stream, normalizeUsage)
|
||||
}
|
||||
|
||||
async completePrompt(prompt: string): Promise<string> {
|
||||
const { id: modelId, reasoning } = this.getModel()
|
||||
const model = this.getModel()
|
||||
|
||||
try {
|
||||
const response = await this.client.chat.completions.create({
|
||||
model: modelId,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
...(reasoning && reasoning),
|
||||
const response = await this.client.responses.create({
|
||||
model: model.id,
|
||||
input: [{ role: "user", content: [{ type: "input_text", text: prompt }] }],
|
||||
store: false,
|
||||
})
|
||||
|
||||
return response.choices[0]?.message.content || ""
|
||||
// output_text is a convenience field on the Responses API response
|
||||
return response.output_text || ""
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const apiError = new ApiProviderError(errorMessage, this.providerName, modelId, "completePrompt")
|
||||
const apiError = new ApiProviderError(errorMessage, this.providerName, model.id, "completePrompt")
|
||||
TelemetryService.instance.captureException(apiError)
|
||||
throw handleOpenAIError(error, this.providerName)
|
||||
}
|
||||
|
|
|
|||
338
src/api/transform/__tests__/responses-api-input.spec.ts
Normal file
338
src/api/transform/__tests__/responses-api-input.spec.ts
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
import type { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { convertToResponsesApiInput } from "../responses-api-input"
|
||||
|
||||
describe("convertToResponsesApiInput", () => {
|
||||
it("should return empty array for empty messages", () => {
|
||||
expect(convertToResponsesApiInput([])).toEqual([])
|
||||
})
|
||||
|
||||
describe("string content messages", () => {
|
||||
it("should convert string content to input_text", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello" }]
|
||||
|
||||
const result = convertToResponsesApiInput(messages)
|
||||
|
||||
expect(result).toEqual([{ role: "user", content: [{ type: "input_text", text: "Hello" }] }])
|
||||
})
|
||||
|
||||
it("should convert assistant string content to output_text message format", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "assistant", content: "Hi there" }]
|
||||
|
||||
const result = convertToResponsesApiInput(messages)
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: "Hi there" }],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("user messages with content blocks", () => {
|
||||
it("should convert text blocks to input_text", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "What is this?" }],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToResponsesApiInput(messages)
|
||||
|
||||
expect(result).toEqual([{ role: "user", content: [{ type: "input_text", text: "What is this?" }] }])
|
||||
})
|
||||
|
||||
it("should convert image blocks to input_image", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "image",
|
||||
source: { type: "base64", media_type: "image/png", data: "abc123" },
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToResponsesApiInput(messages)
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "input_image",
|
||||
detail: "auto",
|
||||
image_url: "data:image/png;base64,abc123",
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("should convert tool_result to function_call_output", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "tool_123",
|
||||
content: "Result text",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToResponsesApiInput(messages)
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
type: "function_call_output",
|
||||
call_id: "tool_123",
|
||||
output: "Result text",
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("should use (empty) for empty tool_result content", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "tool_123",
|
||||
content: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToResponsesApiInput(messages)
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
type: "function_call_output",
|
||||
call_id: "tool_123",
|
||||
output: "(empty)",
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("should extract text from array tool_result content", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "tool_123",
|
||||
content: [
|
||||
{ type: "text", text: "Line 1" },
|
||||
{ type: "text", text: "Line 2" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToResponsesApiInput(messages)
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
type: "function_call_output",
|
||||
call_id: "tool_123",
|
||||
output: "Line 1\nLine 2",
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("should flush pending user content before tool_result", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Here is context" },
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "tool_123",
|
||||
content: "Done",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToResponsesApiInput(messages)
|
||||
|
||||
expect(result).toEqual([
|
||||
{ role: "user", content: [{ type: "input_text", text: "Here is context" }] },
|
||||
{ type: "function_call_output", call_id: "tool_123", output: "Done" },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("assistant messages with content blocks", () => {
|
||||
it("should convert text blocks to output_text messages", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Here is my response" }],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToResponsesApiInput(messages)
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: "Here is my response" }],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("should convert tool_use to function_call", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "call_abc",
|
||||
name: "read_file",
|
||||
input: { path: "/tmp/test.txt" },
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToResponsesApiInput(messages)
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
type: "function_call",
|
||||
call_id: "call_abc",
|
||||
name: "read_file",
|
||||
arguments: '{"path":"/tmp/test.txt"}',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("should handle tool_use with string input", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "call_abc",
|
||||
name: "run_command",
|
||||
input: '{"cmd":"ls"}' as any,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToResponsesApiInput(messages)
|
||||
|
||||
expect(result[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: "function_call",
|
||||
arguments: '{"cmd":"ls"}',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle mixed text and tool_use in assistant message", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "text", text: "Let me read that file" },
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "call_abc",
|
||||
name: "read_file",
|
||||
input: { path: "/tmp/test.txt" },
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToResponsesApiInput(messages)
|
||||
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: "Let me read that file" }],
|
||||
}),
|
||||
)
|
||||
expect(result[1]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: "function_call",
|
||||
name: "read_file",
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("multi-turn conversations", () => {
|
||||
it("should handle a complete tool use cycle", () => {
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{ role: "user", content: "Read /tmp/test.txt" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool_use",
|
||||
id: "call_1",
|
||||
name: "read_file",
|
||||
input: { path: "/tmp/test.txt" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: "call_1",
|
||||
content: "file contents here",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "The file contains: file contents here" }],
|
||||
},
|
||||
]
|
||||
|
||||
const result = convertToResponsesApiInput(messages)
|
||||
|
||||
expect(result).toHaveLength(4)
|
||||
expect(result[0]).toEqual({ role: "user", content: [{ type: "input_text", text: "Read /tmp/test.txt" }] })
|
||||
expect(result[1]).toEqual(
|
||||
expect.objectContaining({ type: "function_call", call_id: "call_1", name: "read_file" }),
|
||||
)
|
||||
expect(result[2]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: "function_call_output",
|
||||
call_id: "call_1",
|
||||
output: "file contents here",
|
||||
}),
|
||||
)
|
||||
expect(result[3]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
392
src/api/transform/__tests__/responses-api-stream.spec.ts
Normal file
392
src/api/transform/__tests__/responses-api-stream.spec.ts
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
import { processResponsesApiStream, createUsageNormalizer } from "../responses-api-stream"
|
||||
|
||||
// Helper to create an async iterable from events
|
||||
async function* mockStream(events: any[]) {
|
||||
for (const event of events) {
|
||||
yield event
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to collect all chunks from a stream
|
||||
async function collectChunks(stream: AsyncGenerator<any>) {
|
||||
const chunks = []
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
return chunks
|
||||
}
|
||||
|
||||
const noopUsage = () => undefined
|
||||
|
||||
describe("processResponsesApiStream", () => {
|
||||
describe("text deltas", () => {
|
||||
it("should yield text chunk for response.output_text.delta", async () => {
|
||||
const stream = mockStream([{ type: "response.output_text.delta", delta: "Hello world" }])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, noopUsage))
|
||||
|
||||
expect(chunks).toEqual([{ type: "text", text: "Hello world" }])
|
||||
})
|
||||
|
||||
it("should yield text chunk for response.text.delta", async () => {
|
||||
const stream = mockStream([{ type: "response.text.delta", delta: "Hello" }])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, noopUsage))
|
||||
|
||||
expect(chunks).toEqual([{ type: "text", text: "Hello" }])
|
||||
})
|
||||
|
||||
it("should skip text delta with empty delta", async () => {
|
||||
const stream = mockStream([{ type: "response.output_text.delta", delta: "" }])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, noopUsage))
|
||||
|
||||
expect(chunks).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe("reasoning deltas", () => {
|
||||
it("should yield reasoning chunk for response.reasoning_text.delta", async () => {
|
||||
const stream = mockStream([{ type: "response.reasoning_text.delta", delta: "Let me think..." }])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, noopUsage))
|
||||
|
||||
expect(chunks).toEqual([{ type: "reasoning", text: "Let me think..." }])
|
||||
})
|
||||
|
||||
it("should yield reasoning chunk for response.reasoning.delta", async () => {
|
||||
const stream = mockStream([{ type: "response.reasoning.delta", delta: "Step 1" }])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, noopUsage))
|
||||
|
||||
expect(chunks).toEqual([{ type: "reasoning", text: "Step 1" }])
|
||||
})
|
||||
|
||||
it("should yield reasoning chunk for response.reasoning_summary_text.delta", async () => {
|
||||
const stream = mockStream([{ type: "response.reasoning_summary_text.delta", delta: "Summary" }])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, noopUsage))
|
||||
|
||||
expect(chunks).toEqual([{ type: "reasoning", text: "Summary" }])
|
||||
})
|
||||
|
||||
it("should yield reasoning chunk for response.reasoning_summary.delta", async () => {
|
||||
const stream = mockStream([{ type: "response.reasoning_summary.delta", delta: "Summary" }])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, noopUsage))
|
||||
|
||||
expect(chunks).toEqual([{ type: "reasoning", text: "Summary" }])
|
||||
})
|
||||
})
|
||||
|
||||
describe("tool calls", () => {
|
||||
it("should yield tool_call for function_call in output_item.done", async () => {
|
||||
const stream = mockStream([
|
||||
{
|
||||
type: "response.output_item.done",
|
||||
item: {
|
||||
type: "function_call",
|
||||
call_id: "call_123",
|
||||
name: "read_file",
|
||||
arguments: '{"path":"/tmp/test.txt"}',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, noopUsage))
|
||||
|
||||
expect(chunks).toEqual([
|
||||
{
|
||||
type: "tool_call",
|
||||
id: "call_123",
|
||||
name: "read_file",
|
||||
arguments: '{"path":"/tmp/test.txt"}',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("should yield tool_call for tool_call type in output_item.done", async () => {
|
||||
const stream = mockStream([
|
||||
{
|
||||
type: "response.output_item.done",
|
||||
item: {
|
||||
type: "tool_call",
|
||||
tool_call_id: "call_456",
|
||||
name: "write_file",
|
||||
arguments: '{"path":"/tmp/out.txt"}',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, noopUsage))
|
||||
|
||||
expect(chunks).toEqual([
|
||||
{
|
||||
type: "tool_call",
|
||||
id: "call_456",
|
||||
name: "write_file",
|
||||
arguments: '{"path":"/tmp/out.txt"}',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("should handle object arguments by JSON.stringifying", async () => {
|
||||
const stream = mockStream([
|
||||
{
|
||||
type: "response.output_item.done",
|
||||
item: {
|
||||
type: "function_call",
|
||||
call_id: "call_789",
|
||||
name: "test",
|
||||
input: { key: "value" },
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, noopUsage))
|
||||
|
||||
expect(chunks[0].arguments).toBe('{"key":"value"}')
|
||||
})
|
||||
|
||||
it("should skip tool_call with missing call_id or name", async () => {
|
||||
const stream = mockStream([
|
||||
{
|
||||
type: "response.output_item.done",
|
||||
item: { type: "function_call", call_id: "", name: "test", arguments: "{}" },
|
||||
},
|
||||
{
|
||||
type: "response.output_item.done",
|
||||
item: { type: "function_call", call_id: "call_1", name: "", arguments: "{}" },
|
||||
},
|
||||
])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, noopUsage))
|
||||
|
||||
expect(chunks).toEqual([])
|
||||
})
|
||||
|
||||
it("should yield tool_call_partial for function_call_arguments.delta", async () => {
|
||||
const stream = mockStream([
|
||||
{
|
||||
type: "response.function_call_arguments.delta",
|
||||
call_id: "call_123",
|
||||
name: "read_file",
|
||||
delta: '{"path":',
|
||||
index: 0,
|
||||
},
|
||||
])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, noopUsage))
|
||||
|
||||
expect(chunks).toEqual([
|
||||
{
|
||||
type: "tool_call_partial",
|
||||
index: 0,
|
||||
id: "call_123",
|
||||
name: "read_file",
|
||||
arguments: '{"path":',
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("completion and usage", () => {
|
||||
it("should yield usage from response.completed", async () => {
|
||||
const mockNormalize = (usage: any) => ({
|
||||
type: "usage" as const,
|
||||
inputTokens: usage.input_tokens,
|
||||
outputTokens: usage.output_tokens,
|
||||
})
|
||||
|
||||
const stream = mockStream([
|
||||
{
|
||||
type: "response.completed",
|
||||
response: { usage: { input_tokens: 100, output_tokens: 50 } },
|
||||
},
|
||||
])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, mockNormalize))
|
||||
|
||||
expect(chunks).toEqual([{ type: "usage", inputTokens: 100, outputTokens: 50 }])
|
||||
})
|
||||
|
||||
it("should yield usage from response.done", async () => {
|
||||
const mockNormalize = (usage: any) => ({
|
||||
type: "usage" as const,
|
||||
inputTokens: usage.input_tokens,
|
||||
outputTokens: usage.output_tokens,
|
||||
})
|
||||
|
||||
const stream = mockStream([
|
||||
{
|
||||
type: "response.done",
|
||||
response: { usage: { input_tokens: 200, output_tokens: 100 } },
|
||||
},
|
||||
])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, mockNormalize))
|
||||
|
||||
expect(chunks).toEqual([{ type: "usage", inputTokens: 200, outputTokens: 100 }])
|
||||
})
|
||||
|
||||
it("should not yield usage when normalizer returns undefined", async () => {
|
||||
const stream = mockStream([
|
||||
{
|
||||
type: "response.completed",
|
||||
response: { usage: null },
|
||||
},
|
||||
])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, noopUsage))
|
||||
|
||||
expect(chunks).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe("unknown events", () => {
|
||||
it("should silently ignore unknown event types", async () => {
|
||||
const stream = mockStream([
|
||||
{ type: "response.created" },
|
||||
{ type: "response.in_progress" },
|
||||
{ type: "response.output_item.added", item: { type: "message" } },
|
||||
{ type: "response.content_part.added" },
|
||||
])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, noopUsage))
|
||||
|
||||
expect(chunks).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe("full conversation stream", () => {
|
||||
it("should handle a complete stream with reasoning, text, and usage", async () => {
|
||||
const mockNormalize = (usage: any) => ({
|
||||
type: "usage" as const,
|
||||
inputTokens: usage.input_tokens,
|
||||
outputTokens: usage.output_tokens,
|
||||
})
|
||||
|
||||
const stream = mockStream([
|
||||
{ type: "response.reasoning_text.delta", delta: "Thinking..." },
|
||||
{ type: "response.reasoning_text.delta", delta: " done." },
|
||||
{ type: "response.output_text.delta", delta: "The answer is " },
|
||||
{ type: "response.output_text.delta", delta: "42." },
|
||||
{
|
||||
type: "response.completed",
|
||||
response: { usage: { input_tokens: 50, output_tokens: 30 } },
|
||||
},
|
||||
])
|
||||
|
||||
const chunks = await collectChunks(processResponsesApiStream(stream, mockNormalize))
|
||||
|
||||
expect(chunks).toEqual([
|
||||
{ type: "reasoning", text: "Thinking..." },
|
||||
{ type: "reasoning", text: " done." },
|
||||
{ type: "text", text: "The answer is " },
|
||||
{ type: "text", text: "42." },
|
||||
{ type: "usage", inputTokens: 50, outputTokens: 30 },
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("createUsageNormalizer", () => {
|
||||
it("should return undefined for null/undefined usage", () => {
|
||||
const normalize = createUsageNormalizer()
|
||||
expect(normalize(null)).toBeUndefined()
|
||||
expect(normalize(undefined)).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should extract input and output tokens", () => {
|
||||
const normalize = createUsageNormalizer()
|
||||
|
||||
const result = normalize({ input_tokens: 100, output_tokens: 50 })
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
type: "usage",
|
||||
inputTokens: 100,
|
||||
outputTokens: 50,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should extract cached tokens from input_tokens_details", () => {
|
||||
const normalize = createUsageNormalizer()
|
||||
|
||||
const result = normalize({
|
||||
input_tokens: 100,
|
||||
output_tokens: 50,
|
||||
input_tokens_details: { cached_tokens: 30 },
|
||||
})
|
||||
|
||||
expect(result?.cacheReadTokens).toBe(30)
|
||||
})
|
||||
|
||||
it("should extract cache write tokens", () => {
|
||||
const normalize = createUsageNormalizer()
|
||||
|
||||
const result = normalize({
|
||||
input_tokens: 100,
|
||||
output_tokens: 50,
|
||||
cache_creation_input_tokens: 15,
|
||||
})
|
||||
|
||||
expect(result?.cacheWriteTokens).toBe(15)
|
||||
})
|
||||
|
||||
it("should extract reasoning tokens from output_tokens_details", () => {
|
||||
const normalize = createUsageNormalizer()
|
||||
|
||||
const result = normalize({
|
||||
input_tokens: 100,
|
||||
output_tokens: 50,
|
||||
output_tokens_details: { reasoning_tokens: 20 },
|
||||
})
|
||||
|
||||
expect(result?.reasoningTokens).toBe(20)
|
||||
})
|
||||
|
||||
it("should not include reasoningTokens when not present", () => {
|
||||
const normalize = createUsageNormalizer()
|
||||
|
||||
const result = normalize({ input_tokens: 100, output_tokens: 50 })
|
||||
|
||||
expect(result).not.toHaveProperty("reasoningTokens")
|
||||
})
|
||||
|
||||
it("should compute totalCost when calculateCost is provided", () => {
|
||||
const calculateCost = (input: number, output: number, cached: number) => 0.42
|
||||
const normalize = createUsageNormalizer(calculateCost)
|
||||
|
||||
const result = normalize({ input_tokens: 100, output_tokens: 50 })
|
||||
|
||||
expect(result?.totalCost).toBe(0.42)
|
||||
})
|
||||
|
||||
it("should not include totalCost when calculateCost is not provided", () => {
|
||||
const normalize = createUsageNormalizer()
|
||||
|
||||
const result = normalize({ input_tokens: 100, output_tokens: 50 })
|
||||
|
||||
expect(result).not.toHaveProperty("totalCost")
|
||||
})
|
||||
|
||||
it("should handle Chat Completions style field names as fallback", () => {
|
||||
const normalize = createUsageNormalizer()
|
||||
|
||||
const result = normalize({
|
||||
prompt_tokens: 100,
|
||||
completion_tokens: 50,
|
||||
prompt_tokens_details: { cached_tokens: 10 },
|
||||
})
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
inputTokens: 100,
|
||||
outputTokens: 50,
|
||||
cacheReadTokens: 10,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
118
src/api/transform/responses-api-input.ts
Normal file
118
src/api/transform/responses-api-input.ts
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
|
||||
/**
|
||||
* Converts Anthropic-format messages to the OpenAI Responses API input format.
|
||||
*
|
||||
* Key differences from Chat Completions format:
|
||||
* - Content parts use { type: "input_text" } instead of { type: "text" }
|
||||
* - Images use { type: "input_image" } instead of { type: "image_url" }
|
||||
* - Tool results use { type: "function_call_output", call_id } instead of { role: "tool", tool_call_id }
|
||||
* - Tool uses become { type: "function_call", call_id, name, arguments } items
|
||||
* - System prompt goes via the `instructions` parameter, not as a message
|
||||
*
|
||||
* @param messages - Array of Anthropic MessageParam objects
|
||||
* @returns Array of Responses API input items
|
||||
*/
|
||||
export function convertToResponsesApiInput(messages: Anthropic.Messages.MessageParam[]): any[] {
|
||||
const input: any[] = []
|
||||
|
||||
for (const message of messages) {
|
||||
if (typeof message.content === "string") {
|
||||
if (message.role === "assistant") {
|
||||
// Assistant messages use output_text in the Responses API format
|
||||
input.push({
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: message.content }],
|
||||
})
|
||||
} else {
|
||||
input.push({
|
||||
role: message.role,
|
||||
content: [{ type: "input_text", text: message.content }],
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (message.role === "assistant") {
|
||||
for (const part of message.content) {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
input.push({
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: part.text }],
|
||||
})
|
||||
break
|
||||
case "tool_use":
|
||||
input.push({
|
||||
type: "function_call",
|
||||
call_id: part.id,
|
||||
name: part.name,
|
||||
arguments: typeof part.input === "string" ? part.input : JSON.stringify(part.input ?? {}),
|
||||
})
|
||||
break
|
||||
case "thinking":
|
||||
// Include reasoning if it has content
|
||||
if ((part as any).thinking && (part as any).thinking.trim().length > 0) {
|
||||
input.push({
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [{ type: "output_text", text: `[Thinking] ${(part as any).thinking}` }],
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// User messages
|
||||
const contentParts: any[] = []
|
||||
for (const part of message.content) {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
contentParts.push({ type: "input_text", text: part.text })
|
||||
break
|
||||
case "image":
|
||||
contentParts.push({
|
||||
type: "input_image",
|
||||
detail: "auto",
|
||||
image_url: `data:${part.source.media_type};base64,${part.source.data}`,
|
||||
})
|
||||
break
|
||||
case "tool_result": {
|
||||
// Flush any pending user content before the tool result
|
||||
if (contentParts.length > 0) {
|
||||
input.push({ role: "user", content: [...contentParts] })
|
||||
contentParts.length = 0
|
||||
}
|
||||
// Convert tool result content
|
||||
let output: string
|
||||
if (typeof part.content === "string") {
|
||||
output = part.content || "(empty)"
|
||||
} else if (Array.isArray(part.content)) {
|
||||
output =
|
||||
part.content
|
||||
.filter((c): c is Anthropic.TextBlockParam => c.type === "text")
|
||||
.map((c) => c.text)
|
||||
.join("\n") || "(empty)"
|
||||
} else {
|
||||
output = "(empty)"
|
||||
}
|
||||
input.push({
|
||||
type: "function_call_output",
|
||||
call_id: part.tool_use_id,
|
||||
output,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// Flush remaining user content
|
||||
if (contentParts.length > 0) {
|
||||
input.push({ role: "user", content: contentParts })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
140
src/api/transform/responses-api-stream.ts
Normal file
140
src/api/transform/responses-api-stream.ts
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import type { ApiStream, ApiStreamUsageChunk } from "./stream"
|
||||
|
||||
/**
|
||||
* Processes Responses API stream events and yields ApiStreamChunks.
|
||||
*
|
||||
* This is a shared utility for providers that use OpenAI's Responses API
|
||||
* (POST /v1/responses with stream: true). It handles the core event types:
|
||||
*
|
||||
* - Text deltas (response.output_text.delta)
|
||||
* - Reasoning deltas (response.reasoning_text.delta, response.reasoning_summary_text.delta)
|
||||
* - Tool/function calls (response.output_item.done with function_call type)
|
||||
* - Usage data (response.completed)
|
||||
*
|
||||
* Provider-specific concerns (WebSocket mode, SSE fallback, duplicate detection,
|
||||
* pending tool tracking) are intentionally left to individual providers.
|
||||
*
|
||||
* @param stream - AsyncIterable of Responses API stream events
|
||||
* @param normalizeUsage - Provider-specific function to normalize usage data into ApiStreamUsageChunk
|
||||
*/
|
||||
export async function* processResponsesApiStream(
|
||||
stream: AsyncIterable<any>,
|
||||
normalizeUsage: (usage: any) => ApiStreamUsageChunk | undefined,
|
||||
): ApiStream {
|
||||
for await (const event of stream) {
|
||||
// Text content deltas
|
||||
if (event?.type === "response.output_text.delta" || event?.type === "response.text.delta") {
|
||||
if (event?.delta) {
|
||||
yield { type: "text", text: event.delta }
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Reasoning deltas
|
||||
if (
|
||||
event?.type === "response.reasoning_text.delta" ||
|
||||
event?.type === "response.reasoning.delta" ||
|
||||
event?.type === "response.reasoning_summary_text.delta" ||
|
||||
event?.type === "response.reasoning_summary.delta"
|
||||
) {
|
||||
if (event?.delta) {
|
||||
yield { type: "reasoning", text: event.delta }
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Output item events — handle completed function calls and fallback text
|
||||
if (event?.type === "response.output_item.done") {
|
||||
const item = event?.item
|
||||
if (item?.type === "function_call" || item?.type === "tool_call") {
|
||||
const callId = item.call_id || item.tool_call_id || item.id
|
||||
const name = item.name || item.function?.name
|
||||
const argsRaw = item.arguments || item.function?.arguments || item.input
|
||||
const args =
|
||||
typeof argsRaw === "string"
|
||||
? argsRaw
|
||||
: argsRaw && typeof argsRaw === "object"
|
||||
? JSON.stringify(argsRaw)
|
||||
: ""
|
||||
|
||||
if (typeof callId === "string" && callId.length > 0 && typeof name === "string" && name.length > 0) {
|
||||
yield {
|
||||
type: "tool_call",
|
||||
id: callId,
|
||||
name,
|
||||
arguments: args,
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Function call argument deltas (for streaming tool calls)
|
||||
if (
|
||||
event?.type === "response.function_call_arguments.delta" ||
|
||||
event?.type === "response.tool_call_arguments.delta"
|
||||
) {
|
||||
const callId = event.call_id || event.tool_call_id || event.id || event.item_id
|
||||
const name = event.name || event.function_name
|
||||
if (typeof callId === "string" && callId.length > 0) {
|
||||
yield {
|
||||
type: "tool_call_partial",
|
||||
index: event.index ?? 0,
|
||||
id: callId,
|
||||
name,
|
||||
arguments: typeof event.delta === "string" ? event.delta : "",
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Completion events — extract usage
|
||||
if (event?.type === "response.completed" || event?.type === "response.done") {
|
||||
const usage = event?.response?.usage || event?.usage
|
||||
const usageData = normalizeUsage(usage)
|
||||
if (usageData) {
|
||||
yield usageData
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a standard usage normalizer for providers with per-token pricing.
|
||||
* Extracts input/output tokens, cache tokens, reasoning tokens, and computes cost.
|
||||
*
|
||||
* @param calculateCost - Optional function to compute total cost from token counts
|
||||
*/
|
||||
export function createUsageNormalizer(
|
||||
calculateCost?: (inputTokens: number, outputTokens: number, cacheReadTokens: number) => number,
|
||||
): (usage: any) => ApiStreamUsageChunk | undefined {
|
||||
return (usage: any): ApiStreamUsageChunk | undefined => {
|
||||
if (!usage) return undefined
|
||||
|
||||
const inputDetails = usage.input_tokens_details ?? usage.prompt_tokens_details
|
||||
const cachedTokens = inputDetails?.cached_tokens ?? 0
|
||||
|
||||
const inputTokens = usage.input_tokens ?? usage.prompt_tokens ?? 0
|
||||
const outputTokens = usage.output_tokens ?? usage.completion_tokens ?? 0
|
||||
const cacheReadTokens = usage.cache_read_input_tokens ?? cachedTokens ?? 0
|
||||
const cacheWriteTokens = usage.cache_creation_input_tokens ?? usage.cache_write_tokens ?? 0
|
||||
|
||||
const reasoningTokens =
|
||||
typeof usage.output_tokens_details?.reasoning_tokens === "number"
|
||||
? usage.output_tokens_details.reasoning_tokens
|
||||
: undefined
|
||||
|
||||
const totalCost = calculateCost ? calculateCost(inputTokens, outputTokens, cacheReadTokens) : undefined
|
||||
|
||||
return {
|
||||
type: "usage",
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
cacheWriteTokens,
|
||||
cacheReadTokens,
|
||||
...(typeof reasoningTokens === "number" ? { reasoningTokens } : {}),
|
||||
...(typeof totalCost === "number" ? { totalCost } : {}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -169,7 +169,7 @@ export class ClineProvider
|
|||
|
||||
public isViewLaunched = false
|
||||
public settingsImportedAt?: number
|
||||
public readonly latestAnnouncementId = "mar-2026-v3.51.0-gpt-54-slash-skills" // v3.51.0 OpenAI GPT-5.4 support and slash command skills
|
||||
public readonly latestAnnouncementId = "apr-2026-v3.52.0-poe-xai-minimax" // v3.52.0 Poe provider, xAI improvements, and MiniMax fixes
|
||||
public readonly providerSettingsManager: ProviderSettingsManager
|
||||
public readonly customModesManager: CustomModesManager
|
||||
|
||||
|
|
|
|||
|
|
@ -2494,6 +2494,7 @@ describe("ClineProvider - Router Models", () => {
|
|||
ollama: {},
|
||||
lmstudio: {},
|
||||
"vercel-ai-gateway": mockModels,
|
||||
poe: {},
|
||||
},
|
||||
values: undefined,
|
||||
})
|
||||
|
|
@ -2540,6 +2541,7 @@ describe("ClineProvider - Router Models", () => {
|
|||
lmstudio: {},
|
||||
litellm: {},
|
||||
"vercel-ai-gateway": mockModels,
|
||||
poe: {},
|
||||
},
|
||||
values: undefined,
|
||||
})
|
||||
|
|
@ -2634,6 +2636,7 @@ describe("ClineProvider - Router Models", () => {
|
|||
ollama: {},
|
||||
lmstudio: {},
|
||||
"vercel-ai-gateway": mockModels,
|
||||
poe: {},
|
||||
},
|
||||
values: undefined,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -346,6 +346,7 @@ describe("webviewMessageHandler - requestRouterModels", () => {
|
|||
ollama: {},
|
||||
lmstudio: {},
|
||||
"vercel-ai-gateway": mockModels,
|
||||
poe: {},
|
||||
},
|
||||
values: undefined,
|
||||
})
|
||||
|
|
@ -431,6 +432,7 @@ describe("webviewMessageHandler - requestRouterModels", () => {
|
|||
ollama: {},
|
||||
lmstudio: {},
|
||||
"vercel-ai-gateway": mockModels,
|
||||
poe: {},
|
||||
},
|
||||
values: undefined,
|
||||
})
|
||||
|
|
@ -486,6 +488,7 @@ describe("webviewMessageHandler - requestRouterModels", () => {
|
|||
ollama: {},
|
||||
lmstudio: {},
|
||||
"vercel-ai-gateway": mockModels,
|
||||
poe: {},
|
||||
},
|
||||
values: undefined,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -955,6 +955,7 @@ export const webviewMessageHandler = async (
|
|||
ollama: {},
|
||||
lmstudio: {},
|
||||
roo: {},
|
||||
poe: {},
|
||||
}
|
||||
|
||||
const safeGetModels = async (options: GetModelsOptions): Promise<ModelRecord> => {
|
||||
|
|
@ -1018,6 +1019,21 @@ export const webviewMessageHandler = async (
|
|||
})
|
||||
}
|
||||
|
||||
// Poe is conditional on apiKey
|
||||
const poeApiKey = apiConfiguration.poeApiKey || message?.values?.poeApiKey
|
||||
const poeBaseUrl = apiConfiguration.poeBaseUrl || message?.values?.poeBaseUrl
|
||||
|
||||
if (poeApiKey) {
|
||||
if (message?.values?.poeApiKey || message?.values?.poeBaseUrl) {
|
||||
await flushModels({ provider: "poe", apiKey: poeApiKey, baseUrl: poeBaseUrl }, true)
|
||||
}
|
||||
|
||||
candidates.push({
|
||||
key: "poe",
|
||||
options: { provider: "poe", apiKey: poeApiKey, baseUrl: poeBaseUrl },
|
||||
})
|
||||
}
|
||||
|
||||
// Apply single provider filter if specified
|
||||
const modelFetchPromises = providerFilter
|
||||
? candidates.filter(({ key }) => key === providerFilter)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"displayName": "%extension.displayName%",
|
||||
"description": "%extension.description%",
|
||||
"publisher": "RooVeterinaryInc",
|
||||
"version": "3.51.1",
|
||||
"version": "3.52.0",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"galleryBanner": {
|
||||
"color": "#617A91",
|
||||
|
|
@ -471,6 +471,7 @@
|
|||
"@roo-code/telemetry": "workspace:^",
|
||||
"@roo-code/types": "workspace:^",
|
||||
"@vscode/codicons": "^0.0.36",
|
||||
"ai-sdk-provider-poe": "2.0.18",
|
||||
"async-mutex": "^0.5.0",
|
||||
"axios": "^1.12.0",
|
||||
"cheerio": "^1.0.0",
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ const dynamicProviderExtras = {
|
|||
ollama: {} as {}, // eslint-disable-line @typescript-eslint/no-empty-object-type
|
||||
lmstudio: {} as {}, // eslint-disable-line @typescript-eslint/no-empty-object-type
|
||||
roo: {} as { apiKey?: string; baseUrl?: string },
|
||||
poe: {} as { apiKey?: string; baseUrl?: string },
|
||||
} as const satisfies Record<RouterName, object>
|
||||
|
||||
// Build the dynamic options union from the map, intersected with CommonFetchParams
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ vi.mock("@src/utils/vscode", () => ({
|
|||
|
||||
vi.mock("@roo/package", () => ({
|
||||
Package: {
|
||||
version: "3.51.0",
|
||||
version: "3.52.0",
|
||||
},
|
||||
}))
|
||||
|
||||
|
|
@ -34,9 +34,9 @@ vi.mock("@src/i18n/TranslationContext", () => ({
|
|||
const translations: Record<string, string> = {
|
||||
"chat:announcement.release.heading": "What's New:",
|
||||
"chat:announcement.release.gpt54":
|
||||
"OpenAI GPT-5.4 Support: Added OpenAI GPT-5.4 and GPT-5.3 Chat Latest so you can use the newest OpenAI chat models in Roo Code.",
|
||||
"Poe Provider: Added Poe as an AI provider so you can access Poe models directly in Roo Code.",
|
||||
"chat:announcement.release.slashSkills":
|
||||
"Slash Command Skills: Skills can now be exposed as slash commands with fallback execution for faster workflows.",
|
||||
"xAI and MiniMax Improvements: Migrated the xAI provider to the Responses API, added Grok-4.20 defaults, and fixed MiniMax model listings and context window handling for a more reliable setup.",
|
||||
}
|
||||
|
||||
if (key === "chat:announcement.title") {
|
||||
|
|
@ -49,18 +49,18 @@ vi.mock("@src/i18n/TranslationContext", () => ({
|
|||
}))
|
||||
|
||||
describe("Announcement", () => {
|
||||
it("renders the v3.51.0 announcement title and highlights", () => {
|
||||
it("renders the v3.52.0 announcement title and highlights", () => {
|
||||
render(<Announcement hideAnnouncement={vi.fn()} />)
|
||||
|
||||
expect(screen.getByText("Roo Code 3.51.0 Released")).toBeInTheDocument()
|
||||
expect(screen.getByText("Roo Code 3.52.0 Released")).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText(
|
||||
"OpenAI GPT-5.4 Support: Added OpenAI GPT-5.4 and GPT-5.3 Chat Latest so you can use the newest OpenAI chat models in Roo Code.",
|
||||
"Poe Provider: Added Poe as an AI provider so you can access Poe models directly in Roo Code.",
|
||||
),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText(
|
||||
"Slash Command Skills: Skills can now be exposed as slash commands with fallback execution for faster workflows.",
|
||||
"xAI and MiniMax Improvements: Migrated the xAI provider to the Responses API, added Grok-4.20 defaults, and fixed MiniMax model listings and context window handling for a more reliable setup.",
|
||||
),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { memo, useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { convertHeadersToObject } from "./utils/headers"
|
||||
import { useDebounce } from "react-use"
|
||||
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { ExternalLinkIcon } from "@radix-ui/react-icons"
|
||||
|
||||
import {
|
||||
|
|
@ -10,6 +10,7 @@ import {
|
|||
isRetiredProvider,
|
||||
DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
|
||||
openRouterDefaultModelId,
|
||||
poeDefaultModelId,
|
||||
requestyDefaultModelId,
|
||||
litellmDefaultModelId,
|
||||
openAiNativeDefaultModelId,
|
||||
|
|
@ -80,6 +81,7 @@ import {
|
|||
OpenAICompatible,
|
||||
OpenAICodex,
|
||||
OpenRouter,
|
||||
Poe,
|
||||
QwenCode,
|
||||
Requesty,
|
||||
Roo,
|
||||
|
|
@ -238,7 +240,7 @@ const ApiOptions = ({
|
|||
vscode.postMessage({ type: "requestLmStudioModels" })
|
||||
} else if (selectedProvider === "vscode-lm") {
|
||||
vscode.postMessage({ type: "requestVsCodeLmModels" })
|
||||
} else if (selectedProvider === "litellm" || selectedProvider === "roo") {
|
||||
} else if (selectedProvider === "litellm" || selectedProvider === "roo" || selectedProvider === "poe") {
|
||||
vscode.postMessage({ type: "requestRouterModels" })
|
||||
}
|
||||
},
|
||||
|
|
@ -252,6 +254,8 @@ const ApiOptions = ({
|
|||
apiConfiguration?.lmStudioBaseUrl,
|
||||
apiConfiguration?.litellmBaseUrl,
|
||||
apiConfiguration?.litellmApiKey,
|
||||
apiConfiguration?.poeApiKey,
|
||||
apiConfiguration?.poeBaseUrl,
|
||||
customHeaders,
|
||||
],
|
||||
)
|
||||
|
|
@ -356,6 +360,7 @@ const ApiOptions = ({
|
|||
: internationalZAiDefaultModelId,
|
||||
},
|
||||
fireworks: { field: "apiModelId", default: fireworksDefaultModelId },
|
||||
poe: { field: "apiModelId", default: poeDefaultModelId },
|
||||
roo: { field: "apiModelId", default: rooDefaultModelId },
|
||||
"vercel-ai-gateway": { field: "vercelAiGatewayModelId", default: vercelAiGatewayDefaultModelId },
|
||||
openai: { field: "openAiModelId" },
|
||||
|
|
@ -703,6 +708,16 @@ const ApiOptions = ({
|
|||
/>
|
||||
)}
|
||||
|
||||
{selectedProvider === "poe" && (
|
||||
<Poe
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfigurationField={setApiConfigurationField}
|
||||
organizationAllowList={organizationAllowList}
|
||||
modelValidationError={modelValidationError}
|
||||
simplifySettings={fromWelcomeView}
|
||||
/>
|
||||
)}
|
||||
|
||||
{selectedProvider === "roo" && (
|
||||
<Roo
|
||||
apiConfiguration={apiConfiguration}
|
||||
|
|
@ -800,6 +815,17 @@ const ApiOptions = ({
|
|||
}
|
||||
onChange={(value) => setApiConfigurationField("consecutiveMistakeLimit", value)}
|
||||
/>
|
||||
{selectedProvider === "poe" && (
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.poeBaseUrl || ""}
|
||||
onInput={handleInputChange("poeBaseUrl")}
|
||||
placeholder="https://api.poe.com/v1"
|
||||
className="w-full">
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:providers.poeBaseUrl")}
|
||||
</label>
|
||||
</VSCodeTextField>
|
||||
)}
|
||||
{selectedProvider === "openrouter" &&
|
||||
openRouterModelProviders &&
|
||||
Object.keys(openRouterModelProviders).length > 0 && (
|
||||
|
|
|
|||
|
|
@ -65,4 +65,5 @@ export const PROVIDERS = [
|
|||
{ value: "minimax", label: "MiniMax", proxy: false },
|
||||
{ value: "baseten", label: "Baseten", proxy: false },
|
||||
{ value: "unbound", label: "Unbound", proxy: false },
|
||||
{ value: "poe", label: "Poe", proxy: false },
|
||||
].sort((a, b) => a.label.localeCompare(b.label))
|
||||
|
|
|
|||
166
webview-ui/src/components/settings/providers/Poe.tsx
Normal file
166
webview-ui/src/components/settings/providers/Poe.tsx
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
import { useCallback, useState, useEffect, useRef } from "react"
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
|
||||
import {
|
||||
type ProviderSettings,
|
||||
type OrganizationAllowList,
|
||||
type ExtensionMessage,
|
||||
poeDefaultModelId,
|
||||
type ProviderName,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import { RouterName } from "@roo/api"
|
||||
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { Button } from "@src/components/ui"
|
||||
|
||||
import { inputEventTransform } from "../transforms"
|
||||
import { ModelPicker } from "../ModelPicker"
|
||||
import { handleModelChangeSideEffects } from "../utils/providerModelConfig"
|
||||
|
||||
type PoeProps = {
|
||||
apiConfiguration: ProviderSettings
|
||||
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
|
||||
organizationAllowList: OrganizationAllowList
|
||||
modelValidationError?: string
|
||||
simplifySettings?: boolean
|
||||
}
|
||||
|
||||
export const Poe = ({
|
||||
apiConfiguration,
|
||||
setApiConfigurationField,
|
||||
organizationAllowList,
|
||||
modelValidationError,
|
||||
simplifySettings,
|
||||
}: PoeProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
const { routerModels } = useExtensionState()
|
||||
const [refreshStatus, setRefreshStatus] = useState<"idle" | "loading" | "success" | "error">("idle")
|
||||
const [refreshError, setRefreshError] = useState<string | undefined>()
|
||||
const poeErrorJustReceived = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent<ExtensionMessage>) => {
|
||||
const message = event.data
|
||||
if (message.type === "singleRouterModelFetchResponse" && !message.success) {
|
||||
const providerName = message.values?.provider as RouterName
|
||||
if (providerName === "poe") {
|
||||
poeErrorJustReceived.current = true
|
||||
setRefreshStatus("error")
|
||||
setRefreshError(message.error)
|
||||
}
|
||||
} else if (message.type === "routerModels") {
|
||||
if (refreshStatus === "loading") {
|
||||
if (!poeErrorJustReceived.current) {
|
||||
setRefreshStatus("success")
|
||||
// Invalidate the react-query router models cache so
|
||||
// validation in ApiOptions picks up the refreshed list.
|
||||
queryClient.invalidateQueries({ queryKey: ["routerModels"] })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", handleMessage)
|
||||
return () => {
|
||||
window.removeEventListener("message", handleMessage)
|
||||
}
|
||||
}, [refreshStatus, queryClient])
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
<K extends keyof ProviderSettings, E>(
|
||||
field: K,
|
||||
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
|
||||
) =>
|
||||
(event: E | Event) => {
|
||||
setApiConfigurationField(field, transform(event as E))
|
||||
},
|
||||
[setApiConfigurationField],
|
||||
)
|
||||
|
||||
const handleRefreshModels = useCallback(() => {
|
||||
poeErrorJustReceived.current = false
|
||||
setRefreshStatus("loading")
|
||||
setRefreshError(undefined)
|
||||
|
||||
const key = apiConfiguration.poeApiKey
|
||||
|
||||
if (!key) {
|
||||
setRefreshStatus("error")
|
||||
setRefreshError(t("settings:providers.refreshModels.missingConfig"))
|
||||
return
|
||||
}
|
||||
|
||||
vscode.postMessage({
|
||||
type: "requestRouterModels",
|
||||
values: { poeApiKey: key, poeBaseUrl: apiConfiguration.poeBaseUrl },
|
||||
})
|
||||
}, [apiConfiguration, t])
|
||||
|
||||
return (
|
||||
<>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.poeApiKey || ""}
|
||||
type="password"
|
||||
onInput={handleInputChange("poeApiKey")}
|
||||
placeholder={t("settings:placeholders.apiKey")}
|
||||
className="w-full">
|
||||
<label className="block font-medium mb-1">{t("settings:providers.poeApiKey")}</label>
|
||||
</VSCodeTextField>
|
||||
<div className="text-sm text-vscode-descriptionForeground -mt-2">
|
||||
{t("settings:providers.apiKeyStorageNotice")}
|
||||
</div>
|
||||
{!apiConfiguration?.poeApiKey && (
|
||||
<VSCodeButtonLink href="https://poe.com/api_key" appearance="secondary">
|
||||
{t("settings:providers.getPoeApiKey")}
|
||||
</VSCodeButtonLink>
|
||||
)}
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleRefreshModels}
|
||||
disabled={refreshStatus === "loading" || !apiConfiguration.poeApiKey}>
|
||||
<div className="flex items-center gap-2">
|
||||
{refreshStatus === "loading" ? (
|
||||
<span className="codicon codicon-loading codicon-modifier-spin" />
|
||||
) : (
|
||||
<span className="codicon codicon-refresh" />
|
||||
)}
|
||||
{t("settings:providers.refreshModels.label")}
|
||||
</div>
|
||||
</Button>
|
||||
{refreshStatus === "loading" && (
|
||||
<div className="text-sm text-vscode-descriptionForeground">
|
||||
{t("settings:providers.refreshModels.loading")}
|
||||
</div>
|
||||
)}
|
||||
{refreshStatus === "success" && (
|
||||
<div className="text-sm text-vscode-foreground">{t("settings:providers.refreshModels.success")}</div>
|
||||
)}
|
||||
{refreshStatus === "error" && (
|
||||
<div className="text-sm text-vscode-errorForeground">
|
||||
{refreshError || t("settings:providers.refreshModels.error")}
|
||||
</div>
|
||||
)}
|
||||
<ModelPicker
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfigurationField={setApiConfigurationField}
|
||||
defaultModelId={poeDefaultModelId}
|
||||
models={routerModels?.poe ?? {}}
|
||||
modelIdKey="apiModelId"
|
||||
serviceName="Poe"
|
||||
serviceUrl="https://poe.com"
|
||||
organizationAllowList={organizationAllowList}
|
||||
errorMessage={modelValidationError}
|
||||
simplifySettings={simplifySettings}
|
||||
onModelChange={(modelId) =>
|
||||
handleModelChangeSideEffects("poe" as ProviderName, modelId, setApiConfigurationField)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ export { OpenAI } from "./OpenAI"
|
|||
export { OpenAICodex } from "./OpenAICodex"
|
||||
export { OpenAICompatible } from "./OpenAICompatible"
|
||||
export { OpenRouter } from "./OpenRouter"
|
||||
export { Poe } from "./Poe"
|
||||
export { QwenCode } from "./QwenCode"
|
||||
export { Roo } from "./Roo"
|
||||
export { Requesty } from "./Requesty"
|
||||
|
|
|
|||
|
|
@ -150,8 +150,10 @@ export const handleModelChangeSideEffects = <K extends keyof ProviderSettings>(
|
|||
setApiConfigurationField("awsCustomArn" as K, "" as ProviderSettings[K])
|
||||
}
|
||||
|
||||
// All providers: Clear reasoning effort when switching models to allow
|
||||
// the new model's default to take effect. Different models within the
|
||||
// same provider can have different reasoning effort defaults/options.
|
||||
// All providers: Clear reasoning settings when switching models to allow
|
||||
// the new model's defaults to take effect. Different models within the
|
||||
// same provider can have different reasoning defaults/options.
|
||||
setApiConfigurationField("reasoningEffort" as K, undefined as ProviderSettings[K])
|
||||
setApiConfigurationField("modelMaxTokens" as K, undefined as ProviderSettings[K])
|
||||
setApiConfigurationField("modelMaxThinkingTokens" as K, undefined as ProviderSettings[K])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import {
|
|||
BEDROCK_1M_CONTEXT_MODEL_IDS,
|
||||
litellmDefaultModelInfo,
|
||||
openAiModelInfoSaneDefaults,
|
||||
minimaxDefaultModelId,
|
||||
minimaxModels,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import { useSelectedModel } from "../useSelectedModel"
|
||||
|
|
@ -725,4 +727,51 @@ describe("useSelectedModel", () => {
|
|||
expect(result.current.info).toEqual(customModelInfo)
|
||||
})
|
||||
})
|
||||
|
||||
describe("minimax provider", () => {
|
||||
beforeEach(() => {
|
||||
mockUseRouterModels.mockReturnValue({
|
||||
data: {
|
||||
openrouter: {},
|
||||
requesty: {},
|
||||
litellm: {},
|
||||
},
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
} as any)
|
||||
|
||||
mockUseOpenRouterModelProviders.mockReturnValue({
|
||||
data: {},
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
} as any)
|
||||
})
|
||||
|
||||
it("should return default minimax model when no custom model is specified", () => {
|
||||
const apiConfiguration: ProviderSettings = {
|
||||
apiProvider: "minimax",
|
||||
}
|
||||
|
||||
const wrapper = createWrapper()
|
||||
const { result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper })
|
||||
|
||||
expect(result.current.provider).toBe("minimax")
|
||||
expect(result.current.id).toBe(minimaxDefaultModelId)
|
||||
expect(result.current.info).toEqual(minimaxModels[minimaxDefaultModelId])
|
||||
})
|
||||
|
||||
it("should use custom model ID and info when model exists in minimaxModels", () => {
|
||||
const apiConfiguration: ProviderSettings = {
|
||||
apiProvider: "minimax",
|
||||
apiModelId: "MiniMax-M2.7",
|
||||
}
|
||||
|
||||
const wrapper = createWrapper()
|
||||
const { result } = renderHook(() => useSelectedModel(apiConfiguration), { wrapper })
|
||||
|
||||
expect(result.current.provider).toBe("minimax")
|
||||
expect(result.current.id).toBe("MiniMax-M2.7")
|
||||
expect(result.current.info).toEqual(minimaxModels["MiniMax-M2.7"])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -317,6 +317,11 @@ function getSelectedModel({
|
|||
const info = routerModels.roo?.[id]
|
||||
return { id, info }
|
||||
}
|
||||
case "poe": {
|
||||
const id = apiConfiguration.apiModelId ?? defaultModelId
|
||||
const info = routerModels.poe?.[id]
|
||||
return { id, info }
|
||||
}
|
||||
case "qwen-code": {
|
||||
const id = apiConfiguration.apiModelId ?? defaultModelId
|
||||
const info = qwenCodeModels[id as keyof typeof qwenCodeModels]
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/ca/chat.json
generated
4
webview-ui/src/i18n/locales/ca/chat.json
generated
|
|
@ -348,8 +348,8 @@
|
|||
},
|
||||
"release": {
|
||||
"heading": "Què hi ha de nou:",
|
||||
"gpt54": "Compatibilitat amb OpenAI GPT-5.4: Hem afegit OpenAI GPT-5.4 i GPT-5.3 Chat Latest perquè puguis fer servir els models de xat més nous d’OpenAI a Roo Code.",
|
||||
"slashSkills": "Skills com a ordres slash: Ara les skills es poden exposar com a ordres slash amb execució de reserva per accelerar els fluxos de treball.",
|
||||
"gpt54": "Proveïdor Poe: S'ha afegit Poe com a proveïdor d’IA perquè puguis accedir directament als models de Poe a Roo Code.",
|
||||
"slashSkills": "Millores a xAI i MiniMax: S'ha migrat el proveïdor xAI a la Responses API, s'hi han afegit utilitats de transformació reutilitzables, s'han actualitzat els valors per defecte de Grok-4.20 i s'han corregit els llistats de models de MiniMax i la gestió de la finestra de context per oferir una configuració més fiable.",
|
||||
"geminiPro": "Suport per Gemini 3.1 Pro: S'ha afegit el suport per al model Gemini 3.1 Pro i s'ha establert com a model Gemini per defecte per a un millor rendiment.",
|
||||
"cliNdjson": "Protocol NDJSON del CLI: Nou protocol NDJSON via stdin, subcomandes de llistat i comanda d'execució modularitzada per a fluxos de treball CLI més flexibles.",
|
||||
"cliRelease": "CLI v0.1.0: El CLI de Roo Code arriba a la seva primera versió oficial amb una interfície de comandes estable."
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/ca/settings.json
generated
3
webview-ui/src/i18n/locales/ca/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Amplia la finestra de context a 1 milió de tokens per a Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Clau API de Baseten",
|
||||
"getBasetenApiKey": "Obtenir clau API de Baseten",
|
||||
"poeApiKey": "Clau API de Poe",
|
||||
"getPoeApiKey": "Obtenir clau API de Poe",
|
||||
"poeBaseUrl": "URL base de Poe",
|
||||
"fireworksApiKey": "Clau API de Fireworks",
|
||||
"getFireworksApiKey": "Obtenir clau API de Fireworks",
|
||||
"deepSeekApiKey": "Clau API de DeepSeek",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/de/chat.json
generated
4
webview-ui/src/i18n/locales/de/chat.json
generated
|
|
@ -348,8 +348,8 @@
|
|||
},
|
||||
"release": {
|
||||
"heading": "Was ist neu:",
|
||||
"gpt54": "OpenAI GPT-5.4-Unterstützung: OpenAI GPT-5.4 und GPT-5.3 Chat Latest wurden hinzugefügt, damit du die neuesten OpenAI-Chatmodelle in Roo Code nutzen kannst.",
|
||||
"slashSkills": "Slash-Command-Skills: Skills können jetzt als Slash-Commands mit Fallback-Ausführung verfügbar gemacht werden, damit Workflows schneller ablaufen.",
|
||||
"gpt54": "Poe-Anbieter: Poe als KI-Anbieter hinzugefügt, damit du direkt in Roo Code auf Poe-Modelle zugreifen kannst.",
|
||||
"slashSkills": "xAI- und MiniMax-Verbesserungen: Den xAI-Anbieter auf die Responses API migriert, wiederverwendbare Transformations-Utilities hinzugefügt, Grok-4.20-Standards aktualisiert und MiniMax-Modelllisten sowie die Handhabung des Kontextfensters für eine zuverlässigere Einrichtung korrigiert.",
|
||||
"geminiPro": "Gemini 3.1 Pro Unterstützung: Gemini 3.1 Pro wurde als Modell hinzugefügt und als Standard-Gemini-Modell für bessere Leistung festgelegt.",
|
||||
"cliNdjson": "CLI NDJSON-Protokoll: Neues NDJSON-stdin-Protokoll, List-Unterbefehle und modularisierter Run-Befehl für flexiblere CLI-Workflows.",
|
||||
"cliRelease": "CLI v0.1.0: Das Roo Code CLI erreicht seine erste offizielle Veröffentlichung mit einer stabilen Befehlsoberfläche."
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/de/settings.json
generated
3
webview-ui/src/i18n/locales/de/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Erweitert das Kontextfenster für Claude Sonnet 4.x / Claude Opus 4.6 auf 1 Million Token",
|
||||
"basetenApiKey": "Baseten API-Schlüssel",
|
||||
"getBasetenApiKey": "Baseten API-Schlüssel erhalten",
|
||||
"poeApiKey": "Poe API-Schlüssel",
|
||||
"getPoeApiKey": "Poe API-Schlüssel erhalten",
|
||||
"poeBaseUrl": "Poe Basis-URL",
|
||||
"fireworksApiKey": "Fireworks API-Schlüssel",
|
||||
"getFireworksApiKey": "Fireworks API-Schlüssel erhalten",
|
||||
"deepSeekApiKey": "DeepSeek API-Schlüssel",
|
||||
|
|
|
|||
|
|
@ -367,8 +367,8 @@
|
|||
},
|
||||
"release": {
|
||||
"heading": "What's New:",
|
||||
"gpt54": "OpenAI GPT-5.4 Support: Added OpenAI GPT-5.4 and GPT-5.3 Chat Latest so you can use the newest OpenAI chat models in Roo Code.",
|
||||
"slashSkills": "Slash Command Skills: Skills can now be exposed as slash commands with fallback execution for faster workflows."
|
||||
"gpt54": "Poe Provider: Added Poe as an AI provider so you can access Poe models directly in Roo Code.",
|
||||
"slashSkills": "xAI and MiniMax Improvements: Migrated the xAI provider to the Responses API, added Grok-4.20 defaults, and fixed MiniMax model listings and context window handling for a more reliable setup."
|
||||
},
|
||||
"cloudAgents": {
|
||||
"heading": "New in the Cloud:",
|
||||
|
|
|
|||
|
|
@ -443,6 +443,9 @@
|
|||
"vertex1MContextBetaDescription": "Extends context window to 1 million tokens for Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Baseten API Key",
|
||||
"getBasetenApiKey": "Get Baseten API Key",
|
||||
"poeApiKey": "Poe API Key",
|
||||
"getPoeApiKey": "Get Poe API Key",
|
||||
"poeBaseUrl": "Poe Base URL",
|
||||
"fireworksApiKey": "Fireworks API Key",
|
||||
"getFireworksApiKey": "Get Fireworks API Key",
|
||||
"deepSeekApiKey": "DeepSeek API Key",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/es/chat.json
generated
4
webview-ui/src/i18n/locales/es/chat.json
generated
|
|
@ -348,8 +348,8 @@
|
|||
},
|
||||
"release": {
|
||||
"heading": "Qué hay de nuevo:",
|
||||
"gpt54": "Compatibilidad con OpenAI GPT-5.4: Se añadieron OpenAI GPT-5.4 y GPT-5.3 Chat Latest para que puedas usar los modelos de chat más recientes de OpenAI en Roo Code.",
|
||||
"slashSkills": "Skills como comandos slash: Ahora las skills pueden exponerse como comandos slash con ejecución de respaldo para activar flujos de trabajo más rápido.",
|
||||
"gpt54": "Proveedor Poe: Se añadió Poe como proveedor de IA para que puedas acceder directamente a los modelos de Poe en Roo Code.",
|
||||
"slashSkills": "Mejoras en xAI y MiniMax: Se migró el proveedor xAI a la Responses API, se añadieron utilidades de transformación reutilizables, se actualizaron los valores predeterminados de Grok-4.20 y se corrigieron los listados de modelos de MiniMax y el manejo de la ventana de contexto para una configuración más fiable.",
|
||||
"geminiPro": "Soporte para Gemini 3.1 Pro: Se añadió soporte para el modelo Gemini 3.1 Pro y se estableció como modelo Gemini predeterminado para un mejor rendimiento.",
|
||||
"cliNdjson": "Protocolo NDJSON del CLI: Nuevo protocolo NDJSON por stdin, subcomandos de listado y comando de ejecución modularizado para flujos de trabajo CLI más flexibles.",
|
||||
"cliRelease": "CLI v0.1.0: El CLI de Roo Code alcanza su primera versión oficial con una interfaz de comandos estable."
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/es/settings.json
generated
3
webview-ui/src/i18n/locales/es/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Amplía la ventana de contexto a 1 millón de tokens para Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Clave API de Baseten",
|
||||
"getBasetenApiKey": "Obtener clave API de Baseten",
|
||||
"poeApiKey": "Clave API de Poe",
|
||||
"getPoeApiKey": "Obtener clave API de Poe",
|
||||
"poeBaseUrl": "URL base de Poe",
|
||||
"fireworksApiKey": "Clave API de Fireworks",
|
||||
"getFireworksApiKey": "Obtener clave API de Fireworks",
|
||||
"deepSeekApiKey": "Clave API de DeepSeek",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/fr/chat.json
generated
4
webview-ui/src/i18n/locales/fr/chat.json
generated
|
|
@ -348,8 +348,8 @@
|
|||
},
|
||||
"release": {
|
||||
"heading": "Quoi de neuf :",
|
||||
"gpt54": "Prise en charge d’OpenAI GPT-5.4 : OpenAI GPT-5.4 et GPT-5.3 Chat Latest ont été ajoutés pour que tu puisses utiliser les modèles de chat OpenAI les plus récents dans Roo Code.",
|
||||
"slashSkills": "Skills en slash commands : Les skills peuvent maintenant être exposées comme slash commands avec une exécution de secours pour accélérer les workflows.",
|
||||
"gpt54": "Fournisseur Poe : Poe a été ajouté comme fournisseur d’IA pour que tu puisses accéder directement aux modèles Poe dans Roo Code.",
|
||||
"slashSkills": "Améliorations xAI et MiniMax : Le fournisseur xAI a été migré vers la Responses API, des utilitaires de transformation réutilisables ont été ajoutés, les valeurs par défaut de Grok-4.20 ont été mises à jour et les listes de modèles MiniMax ainsi que la gestion de la fenêtre de contexte ont été corrigées pour une configuration plus fiable.",
|
||||
"geminiPro": "Support de Gemini 3.1 Pro : Le modèle Gemini 3.1 Pro a été ajouté et défini comme modèle Gemini par défaut pour de meilleures performances.",
|
||||
"cliNdjson": "Protocole NDJSON du CLI : Nouveau protocole NDJSON via stdin, sous-commandes de liste et commande d'exécution modularisée pour des workflows CLI plus flexibles.",
|
||||
"cliRelease": "CLI v0.1.0 : Le CLI de Roo Code atteint sa première version officielle avec une interface de commandes stable."
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/fr/settings.json
generated
3
webview-ui/src/i18n/locales/fr/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Étend la fenêtre de contexte à 1 million de tokens pour Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Clé API Baseten",
|
||||
"getBasetenApiKey": "Obtenir la clé API Baseten",
|
||||
"poeApiKey": "Clé API Poe",
|
||||
"getPoeApiKey": "Obtenir la clé API Poe",
|
||||
"poeBaseUrl": "URL de base Poe",
|
||||
"fireworksApiKey": "Clé API Fireworks",
|
||||
"getFireworksApiKey": "Obtenir la clé API Fireworks",
|
||||
"deepSeekApiKey": "Clé API DeepSeek",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/hi/chat.json
generated
4
webview-ui/src/i18n/locales/hi/chat.json
generated
|
|
@ -348,8 +348,8 @@
|
|||
},
|
||||
"release": {
|
||||
"heading": "नया क्या है:",
|
||||
"gpt54": "OpenAI GPT-5.4 सपोर्ट: OpenAI GPT-5.4 और GPT-5.3 Chat Latest जोड़े गए हैं, ताकि आप Roo Code में OpenAI के सबसे नए chat models इस्तेमाल कर सकें।",
|
||||
"slashSkills": "Slash command skills: अब skills को slash commands के रूप में fallback execution के साथ expose किया जा सकता है, जिससे workflows तेज़ हो जाते हैं।",
|
||||
"gpt54": "Poe provider: Poe को AI provider के रूप में जोड़ा गया है, ताकि आप Roo Code में सीधे Poe models तक पहुँच सकें।",
|
||||
"slashSkills": "xAI और MiniMax में सुधार: xAI provider को Responses API पर migrate किया गया है, reusable transform utilities जोड़ी गई हैं, Grok-4.20 defaults अपडेट किए गए हैं, और ज़्यादा भरोसेमंद setup के लिए MiniMax model listings व context window handling को ठीक किया गया है।",
|
||||
"geminiPro": "Gemini 3.1 Pro सपोर्ट: Gemini 3.1 Pro मॉडल का सपोर्ट जोड़ा गया और बेहतर प्रदर्शन के लिए इसे डिफ़ॉल्ट Gemini मॉडल के रूप में सेट किया गया।",
|
||||
"cliNdjson": "CLI NDJSON प्रोटोकॉल: नया NDJSON stdin प्रोटोकॉल, list सबकमांड, और अधिक लचीले CLI वर्कफ़्लो के लिए मॉड्यूलर run कमांड।",
|
||||
"cliRelease": "CLI v0.1.0: Roo Code CLI एक स्थिर कमांड इंटरफ़ेस के साथ अपनी पहली आधिकारिक रिलीज़ तक पहुँचा।"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/hi/settings.json
generated
3
webview-ui/src/i18n/locales/hi/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Claude Sonnet 4.x / Claude Opus 4.6 के लिए संदर्भ विंडो को 1 मिलियन टोकन तक बढ़ाता है",
|
||||
"basetenApiKey": "Baseten API कुंजी",
|
||||
"getBasetenApiKey": "Baseten API कुंजी प्राप्त करें",
|
||||
"poeApiKey": "Poe API कुंजी",
|
||||
"getPoeApiKey": "Poe API कुंजी प्राप्त करें",
|
||||
"poeBaseUrl": "Poe बेस URL",
|
||||
"fireworksApiKey": "Fireworks API कुंजी",
|
||||
"getFireworksApiKey": "Fireworks API कुंजी प्राप्त करें",
|
||||
"deepSeekApiKey": "DeepSeek API कुंजी",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/id/chat.json
generated
4
webview-ui/src/i18n/locales/id/chat.json
generated
|
|
@ -377,8 +377,8 @@
|
|||
},
|
||||
"release": {
|
||||
"heading": "Yang Baru:",
|
||||
"gpt54": "Dukungan OpenAI GPT-5.4: OpenAI GPT-5.4 dan GPT-5.3 Chat Latest kini ditambahkan agar kamu bisa memakai model chat OpenAI terbaru di Roo Code.",
|
||||
"slashSkills": "Skill sebagai slash command: Skill sekarang bisa diekspos sebagai slash command dengan eksekusi fallback supaya workflow bisa berjalan lebih cepat.",
|
||||
"gpt54": "Provider Poe: Poe ditambahkan sebagai provider AI agar kamu bisa langsung mengakses model Poe di Roo Code.",
|
||||
"slashSkills": "Peningkatan xAI dan MiniMax: Provider xAI dimigrasikan ke Responses API, utilitas transformasi yang bisa dipakai ulang ditambahkan, default Grok-4.20 diperbarui, dan daftar model MiniMax serta penanganan context window diperbaiki agar setup lebih andal.",
|
||||
"geminiPro": "Dukungan Gemini 3.1 Pro: Dukungan model Gemini 3.1 Pro telah ditambahkan dan ditetapkan sebagai model Gemini default untuk performa yang lebih baik.",
|
||||
"cliNdjson": "Protokol NDJSON CLI: Protokol NDJSON stdin baru, subperintah list, dan perintah run yang dimodularisasi untuk alur kerja CLI yang lebih fleksibel.",
|
||||
"cliRelease": "CLI v0.1.0: CLI Roo Code mencapai rilis resmi pertamanya dengan antarmuka perintah yang stabil."
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/id/settings.json
generated
3
webview-ui/src/i18n/locales/id/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Memperluas jendela konteks menjadi 1 juta token untuk Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Baseten API Key",
|
||||
"getBasetenApiKey": "Dapatkan Baseten API Key",
|
||||
"poeApiKey": "Poe API Key",
|
||||
"getPoeApiKey": "Dapatkan Poe API Key",
|
||||
"poeBaseUrl": "Poe Base URL",
|
||||
"fireworksApiKey": "Fireworks API Key",
|
||||
"getFireworksApiKey": "Dapatkan Fireworks API Key",
|
||||
"deepSeekApiKey": "DeepSeek API Key",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/it/chat.json
generated
4
webview-ui/src/i18n/locales/it/chat.json
generated
|
|
@ -348,8 +348,8 @@
|
|||
},
|
||||
"release": {
|
||||
"heading": "Novità:",
|
||||
"gpt54": "Supporto per OpenAI GPT-5.4: abbiamo aggiunto OpenAI GPT-5.4 e GPT-5.3 Chat Latest così puoi usare i modelli di chat OpenAI più recenti in Roo Code.",
|
||||
"slashSkills": "Skill come slash command: ora le skill possono essere esposte come slash command con esecuzione di fallback per attivare i workflow più rapidamente.",
|
||||
"gpt54": "Provider Poe: abbiamo aggiunto Poe come provider AI così puoi accedere direttamente ai modelli Poe in Roo Code.",
|
||||
"slashSkills": "Miglioramenti per xAI e MiniMax: il provider xAI è stato migrato alla Responses API, sono state aggiunte utility di trasformazione riutilizzabili, sono stati aggiornati i valori predefiniti di Grok-4.20 e sono stati corretti gli elenchi dei modelli MiniMax e la gestione della finestra di contesto per una configurazione più affidabile.",
|
||||
"geminiPro": "Supporto Gemini 3.1 Pro: Aggiunto il supporto per il modello Gemini 3.1 Pro e impostato come modello Gemini predefinito per prestazioni migliori.",
|
||||
"cliNdjson": "Protocollo NDJSON del CLI: Nuovo protocollo NDJSON via stdin, sottocomandi di elenco e comando di esecuzione modularizzato per flussi di lavoro CLI più flessibili.",
|
||||
"cliRelease": "CLI v0.1.0: Il CLI di Roo Code raggiunge la sua prima release ufficiale con un'interfaccia di comandi stabile."
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/it/settings.json
generated
3
webview-ui/src/i18n/locales/it/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Estende la finestra di contesto a 1 milione di token per Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Chiave API Baseten",
|
||||
"getBasetenApiKey": "Ottieni chiave API Baseten",
|
||||
"poeApiKey": "Chiave API Poe",
|
||||
"getPoeApiKey": "Ottieni chiave API Poe",
|
||||
"poeBaseUrl": "URL base Poe",
|
||||
"fireworksApiKey": "Chiave API Fireworks",
|
||||
"getFireworksApiKey": "Ottieni chiave API Fireworks",
|
||||
"deepSeekApiKey": "Chiave API DeepSeek",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/ja/chat.json
generated
4
webview-ui/src/i18n/locales/ja/chat.json
generated
|
|
@ -348,8 +348,8 @@
|
|||
},
|
||||
"release": {
|
||||
"heading": "新機能:",
|
||||
"gpt54": "OpenAI GPT-5.4 サポート: OpenAI GPT-5.4 と GPT-5.3 Chat Latest を追加し、Roo Code で OpenAI の最新チャットモデルを使えるようにしました。",
|
||||
"slashSkills": "スラッシュコマンド対応スキル: スキルをスラッシュコマンドとして公開し、フォールバック実行にも対応したことで、ワークフローをより素早く実行できます。",
|
||||
"gpt54": "Poe プロバイダー: Poe を AI プロバイダーとして追加し、Roo Code から Poe のモデルに直接アクセスできるようにしました。",
|
||||
"slashSkills": "xAI と MiniMax の改善: xAI プロバイダーを Responses API へ移行し、再利用可能な変換ユーティリティを追加し、Grok-4.20 のデフォルト設定を更新し、MiniMax のモデル一覧とコンテキストウィンドウの扱いを修正して、より信頼性の高いセットアップにしました。",
|
||||
"geminiPro": "Gemini 3.1 Pro サポート: Gemini 3.1 Pro モデルのサポートを追加し、パフォーマンス向上のためデフォルトの Gemini モデルに設定しました。",
|
||||
"cliNdjson": "CLI NDJSON プロトコル: 新しい NDJSON stdin プロトコル、list サブコマンド、より柔軟な CLI ワークフローのためのモジュール化された run コマンド。",
|
||||
"cliRelease": "CLI v0.1.0: Roo Code CLI が安定したコマンドインターフェースで初の正式リリースに到達しました。"
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/ja/settings.json
generated
3
webview-ui/src/i18n/locales/ja/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Claude Sonnet 4.x / Claude Opus 4.6のコンテキストウィンドウを100万トークンに拡張します",
|
||||
"basetenApiKey": "Baseten APIキー",
|
||||
"getBasetenApiKey": "Baseten APIキーを取得",
|
||||
"poeApiKey": "Poe APIキー",
|
||||
"getPoeApiKey": "Poe APIキーを取得",
|
||||
"poeBaseUrl": "Poe ベースURL",
|
||||
"fireworksApiKey": "Fireworks APIキー",
|
||||
"getFireworksApiKey": "Fireworks APIキーを取得",
|
||||
"deepSeekApiKey": "DeepSeek APIキー",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/ko/chat.json
generated
4
webview-ui/src/i18n/locales/ko/chat.json
generated
|
|
@ -348,8 +348,8 @@
|
|||
},
|
||||
"release": {
|
||||
"heading": "새로운 기능:",
|
||||
"gpt54": "OpenAI GPT-5.4 지원: OpenAI GPT-5.4와 GPT-5.3 Chat Latest를 추가해 Roo Code에서 최신 OpenAI 채팅 모델을 사용할 수 있게 했어요.",
|
||||
"slashSkills": "슬래시 명령어 스킬: 이제 스킬을 슬래시 명령어로 노출하고 fallback 실행도 지원해 워크플로를 더 빠르게 실행할 수 있어요.",
|
||||
"gpt54": "Poe 제공업체: Poe를 AI 제공업체로 추가해 Roo Code에서 Poe 모델에 직접 접근할 수 있게 했어요.",
|
||||
"slashSkills": "xAI 및 MiniMax 개선: xAI 제공업체를 Responses API로 마이그레이션하고, 재사용 가능한 변환 유틸리티를 추가하고, Grok-4.20 기본값을 업데이트하고, MiniMax 모델 목록과 컨텍스트 윈도우 처리를 수정해 더 안정적인 설정을 제공해요.",
|
||||
"geminiPro": "Gemini 3.1 Pro 지원: Gemini 3.1 Pro 모델 지원이 추가되었으며, 향상된 성능을 위해 기본 Gemini 모델로 설정되었습니다.",
|
||||
"cliNdjson": "CLI NDJSON 프로토콜: 새로운 NDJSON stdin 프로토콜, list 하위 명령어, 더 유연한 CLI 워크플로를 위한 모듈화된 run 명령어.",
|
||||
"cliRelease": "CLI v0.1.0: Roo Code CLI가 안정적인 명령어 인터페이스로 첫 공식 릴리스에 도달했습니다."
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/ko/settings.json
generated
3
webview-ui/src/i18n/locales/ko/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Claude Sonnet 4.x / Claude Opus 4.6의 컨텍스트 창을 100만 토큰으로 확장",
|
||||
"basetenApiKey": "Baseten API 키",
|
||||
"getBasetenApiKey": "Baseten API 키 가져오기",
|
||||
"poeApiKey": "Poe API 키",
|
||||
"getPoeApiKey": "Poe API 키 가져오기",
|
||||
"poeBaseUrl": "Poe 기본 URL",
|
||||
"fireworksApiKey": "Fireworks API 키",
|
||||
"getFireworksApiKey": "Fireworks API 키 받기",
|
||||
"deepSeekApiKey": "DeepSeek API 키",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/nl/chat.json
generated
4
webview-ui/src/i18n/locales/nl/chat.json
generated
|
|
@ -321,8 +321,8 @@
|
|||
},
|
||||
"release": {
|
||||
"heading": "Wat is er nieuw:",
|
||||
"gpt54": "Ondersteuning voor OpenAI GPT-5.4: OpenAI GPT-5.4 en GPT-5.3 Chat Latest zijn toegevoegd, zodat je de nieuwste OpenAI-chatmodellen in Roo Code kunt gebruiken.",
|
||||
"slashSkills": "Skills als slash-commands: Skills kunnen nu als slash-commands worden aangeboden met fallback-uitvoering voor snellere workflows.",
|
||||
"gpt54": "Poe-provider: Poe toegevoegd als AI-provider, zodat je in Roo Code direct toegang hebt tot Poe-modellen.",
|
||||
"slashSkills": "xAI- en MiniMax-verbeteringen: De xAI-provider gemigreerd naar de Responses API, herbruikbare transformatiehulpmiddelen toegevoegd, Grok-4.20-standaardinstellingen bijgewerkt en MiniMax-modellijsten plus de afhandeling van het contextvenster gerepareerd voor een betrouwbaardere configuratie.",
|
||||
"geminiPro": "Gemini 3.1 Pro ondersteuning: Ondersteuning voor het Gemini 3.1 Pro model is toegevoegd en ingesteld als standaard Gemini-model voor betere prestaties.",
|
||||
"cliNdjson": "CLI NDJSON-protocol: Nieuw NDJSON stdin-protocol, list-subopdrachten en gemodulariseerd run-commando voor flexibelere CLI-workflows.",
|
||||
"cliRelease": "CLI v0.1.0: De Roo Code CLI bereikt zijn eerste officiële release met een stabiele opdrachtinterface."
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/nl/settings.json
generated
3
webview-ui/src/i18n/locales/nl/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Breidt het contextvenster uit tot 1 miljoen tokens voor Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Baseten API-sleutel",
|
||||
"getBasetenApiKey": "Baseten API-sleutel verkrijgen",
|
||||
"poeApiKey": "Poe API-sleutel",
|
||||
"getPoeApiKey": "Poe API-sleutel verkrijgen",
|
||||
"poeBaseUrl": "Poe Basis-URL",
|
||||
"fireworksApiKey": "Fireworks API-sleutel",
|
||||
"getFireworksApiKey": "Fireworks API-sleutel ophalen",
|
||||
"deepSeekApiKey": "DeepSeek API-sleutel",
|
||||
|
|
|
|||
4
webview-ui/src/i18n/locales/pl/chat.json
generated
4
webview-ui/src/i18n/locales/pl/chat.json
generated
|
|
@ -348,8 +348,8 @@
|
|||
},
|
||||
"release": {
|
||||
"heading": "Co nowego:",
|
||||
"gpt54": "Obsługa OpenAI GPT-5.4: Dodaliśmy OpenAI GPT-5.4 i GPT-5.3 Chat Latest, żebyś mógł używać najnowszych modeli czatu OpenAI w Roo Code.",
|
||||
"slashSkills": "Umiejętności jako slash commandy: Skills można teraz udostępniać jako slash commandy z wykonaniem awaryjnym, żeby szybciej uruchamiać workflowy.",
|
||||
"gpt54": "Dostawca Poe: Dodano Poe jako dostawcę AI, żebyś mógł bezpośrednio uzyskać dostęp do modeli Poe w Roo Code.",
|
||||
"slashSkills": "Usprawnienia xAI i MiniMax: Przeniesiono dostawcę xAI do Responses API, dodano narzędzia transformacji wielokrotnego użytku, zaktualizowano domyślne ustawienia Grok-4.20 oraz naprawiono listy modeli MiniMax i obsługę okna kontekstu, żeby konfiguracja była bardziej niezawodna.",
|
||||
"geminiPro": "Wsparcie dla Gemini 3.1 Pro: Dodano wsparcie dla modelu Gemini 3.1 Pro i ustawiono go jako domyślny model Gemini dla lepszej wydajności.",
|
||||
"cliNdjson": "Protokół NDJSON w CLI: Nowy protokół NDJSON przez stdin, podkomendy list oraz zmodularyzowana komenda run dla bardziej elastycznych przepływów pracy CLI.",
|
||||
"cliRelease": "CLI v0.1.0: CLI Roo Code osiąga swoją pierwszą oficjalną wersję ze stabilnym interfejsem poleceń."
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/pl/settings.json
generated
3
webview-ui/src/i18n/locales/pl/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Rozszerza okno kontekstowe do 1 miliona tokenów dla Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Klucz API Baseten",
|
||||
"getBasetenApiKey": "Uzyskaj klucz API Baseten",
|
||||
"poeApiKey": "Klucz API Poe",
|
||||
"getPoeApiKey": "Uzyskaj klucz API Poe",
|
||||
"poeBaseUrl": "Bazowy URL Poe",
|
||||
"fireworksApiKey": "Klucz API Fireworks",
|
||||
"getFireworksApiKey": "Uzyskaj klucz API Fireworks",
|
||||
"deepSeekApiKey": "Klucz API DeepSeek",
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue