Compare commits

...
Author SHA1 Message Date
Elephant Lumps 2a3dd66ca9 anthropic support 2025-05-22 09:51:09 -07:00
EvanandElephant Lumps 6835870dee Model 4 UI prep (#3721)
* ui copy changes sonnet 4

* changeset

---------

Co-authored-by: Elephant Lumps <celestial_vault@Elephants-MacBook-Pro.local>
2025-05-22 20:40:03 +04:00
Daniel Trugman a48c37ee82 Fix requesty list models and usage (#3630)
* Fix requesty list models

* Fix requesty usage
2025-05-22 08:25:02 -07:00
canvrno 579b1f1968 [PROTOBUS] Move togglePlanActMode to protobus (fixed) (#3686)
* togglePlanActMode protobus migration

* metadata on request

* cleanup

* snake case in protos

* merge conflcits + removed metadata for consistency
2025-05-22 02:11:12 -07:00
Adil RiazudeenandAdil Riazudeen 331da0f802 fix: revert ServerConfigSchema to union. (#3716)
This type was changed from a union to a discriminated union based on
transportType. It seems like this is an internal implementation detail
that end users shouldn't really care about. This change reverts that
type back to a union.

Co-authored-by: Adil Riazudeen <adiriazu@amazon.com>
2025-05-22 01:25:32 -07:00
canvrno 782ed7ff21 [PROTOBUS] Move autoApprovalSettings to protobus (#3423)
* autoApprovalSettings protobus migration

* fixed merge conflict mistake
2025-05-21 23:56:38 -07:00
pashpashpashandCline Evaluation c511b91a09 New settings page, inspired by Yellow Bat and Roo <3 (#3720)
* new settings view tabs

* making sure things scroll

* tidying up

* changeset

* py

* bringing back mcp settings button

---------

Co-authored-by: Cline Evaluation <cline@example.com>
2025-05-21 23:46:24 -07:00
AraandCline Evaluation efb6ae1529 Changing Title wording for cline extension (#3712)
Co-authored-by: Cline Evaluation <cline@example.com>
2025-05-22 10:37:16 +04:00
Toshii 58f80d4d16 NO MORE PROTO (#3715)
* delete ui proto

* changeset
2025-05-21 20:24:50 -07:00
AraandCline Evaluation c3b556ddce Remove Advanced settings banner from MCP (#3713)
* Remove MCP Settings banner

* Remove Advnaced Settings banner

---------

Co-authored-by: Cline Evaluation <cline@example.com>
2025-05-21 19:38:16 -07:00
wangyijing130andwangyj20 f16120fbfc fix the display from filename if type is windows (#3554)
Co-authored-by: wangyj20 <wangyj20@asiainfo.com>
2025-05-21 19:13:01 -07:00
pashpashpashandCline Evaluation 02002860ca when the assistant says to act mode we render a custom highlight with hotkey suggestion (#3708)
* when the assistant says to act mode we render a custom highlight with hotkey suggestion

* changeset

* console

---------

Co-authored-by: Cline Evaluation <cline@example.com>
2025-05-21 18:11:17 -07:00
b97e57fd4e Integration with Nebius AI Studio (#2789)
* Integration with Nebius AI Studio added

* changeset added

* tests fixed

* Nebius naming changed

* bugs fixed

* styling fixed

* minor bug fixed

* Remove obsolete ClineProvider.ts file that was causing build errors

* redundant 'Model' section removed for Nebius

* feat: add Nebius AI Studio to the list of inference providers (#2789)

- replace `nebiusModelId` to `apiModelId`
- add our latest models
- fix `getModel` method
- fix spaces and the link to Nebius AI Studio api keys
- delete extra code
- add a couple of tests

---------

Co-authored-by: Akim Tsvigun <aktsvigun@nebius.com>
Co-authored-by: Albert Abdulmanov <albertworks@nebius.com>
2025-05-22 05:10:44 +05:30
62 changed files with 1365 additions and 421 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
when the assistant says to act mode we render a custom highlight with hotkey suggestion
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
New settings page split into tabs
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
no longer requires `transportType` property to be set in MCP Server configuration.
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
autoApprovalSettings protobus migration
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Remove advanced settings banner
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Sonnet 4 UI copy updates
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
integration with nebius ai studio
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
remove proto file
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Fix model listing for Requesty
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Change the extension title name wording
+2 -2
View File
@@ -32,7 +32,7 @@
التقى Cline، مساعد الذكاء الاصطناعي الذي يمكنه استخدام **سطر الأوامر** و **محرر النصوص** الخاص بك.
بفضل [قدرات Claude 3.7 Sonnet على التعليمات البرمجية الوكيلة](https://www.anthropic.com/claude/sonnet)، يمكن لـ Cline التعامل مع مهام تطوير البرامج المعقدة خطوة بخطوة. مع الأدوات التي تسمح له بإنشاء وتعديل الملفات، واستكشاف المشاريع الكبيرة، واستخدام المتصفح، وتنفيذ أوامر الطرفية (بعد منحك الإذن)، يمكنه مساعدتك بطرق تتجاوز إكمال الكود أو الدعم الفني. يمكن لـ Cline أيضًا استخدام بروتوكول سياق النموذج (MCP) لإنشاء أدوات جديدة وتوسيع قدراته الخاصة. في حين تعمل النصوص البرمجية الآلية المستقلة تقليديًا في بيئات محاصرة، توفر هذه الإضافة واجهة رسومية لموافقة المستخدم على كل تغيير في الملف وأمر طرفية، مما يوفر طريقة آمنة وسهلة الاستخدام لاستكشاف إمكانات الذكاء الاصطناعي الوكيل.
بفضل [قدرات Claude 4 Sonnet على التعليمات البرمجية الوكيلة](https://www.anthropic.com/claude/sonnet)، يمكن لـ Cline التعامل مع مهام تطوير البرامج المعقدة خطوة بخطوة. مع الأدوات التي تسمح له بإنشاء وتعديل الملفات، واستكشاف المشاريع الكبيرة، واستخدام المتصفح، وتنفيذ أوامر الطرفية (بعد منحك الإذن)، يمكنه مساعدتك بطرق تتجاوز إكمال الكود أو الدعم الفني. يمكن لـ Cline أيضًا استخدام بروتوكول سياق النموذج (MCP) لإنشاء أدوات جديدة وتوسيع قدراته الخاصة. في حين تعمل النصوص البرمجية الآلية المستقلة تقليديًا في بيئات محاصرة، توفر هذه الإضافة واجهة رسومية لموافقة المستخدم على كل تغيير في الملف وأمر طرفية، مما يوفر طريقة آمنة وسهلة الاستخدام لاستكشاف إمكانات الذكاء الاصطناعي الوكيل.
1. أدخل مهمتك وأضف الصور لتحويل المحاكاة إلى تطبيقات وظيفية أو إصلاح الأخطاء مع لقطات الشاشة.
2. يبدأ Cline بتحليل هيكل الملفات الخاصة بك وشجرة التعريف المصدرية، وإجراء عمليات بحث regex، وقراءة الملفات ذات الصلة للاطلاع على المشاريع الحالية. من خلال إدارة المعلومات التي يتم إضافتها إلى السياق بعناية، يمكن لـ Cline تقديم مساعدة قيمة حتى للمشاريع الكبيرة والمعقدة دون إرهاق نافذة السياق.
@@ -87,7 +87,7 @@
### استخدم المتصفح
مع قدرة [استخدام الكمبيوتر](https://www.anthropic.com/news/3-5-models-and-computer-use) الجديدة لـ Claude 3.5 Sonnet، يمكن لـ Cline إطلاق متصفح، والنقر على العناصر، وكتابة النص، والتمرير، والتقاط لقطات الشاشة وسجلات وحدة التحكم في كل خطوة. يسمح له هذا بالتصحيح التفاعلي، واختبار نهاية إلى نهاية، وحتى الاستخدام العام للويب! يمنحه هذا الاستقلالية لإصلاح الأخطاء البصرية وأخطاء وقت التشغيل دون الحاجة إلى نسخ ولصق سجلات الأخطاء بنفسك.
مع قدرة [استخدام الكمبيوتر](https://www.anthropic.com/news/3-5-models-and-computer-use) الجديدة لـ Claude 4 Sonnet، يمكن لـ Cline إطلاق متصفح، والنقر على العناصر، وكتابة النص، والتمرير، والتقاط لقطات الشاشة وسجلات وحدة التحكم في كل خطوة. يسمح له هذا بالتصحيح التفاعلي، واختبار نهاية إلى نهاية، وحتى الاستخدام العام للويب! يمنحه هذا الاستقلالية لإصلاح الأخطاء البصرية وأخطاء وقت التشغيل دون الحاجة إلى نسخ ولصق سجلات الأخطاء بنفسك.
حاول طلب من Cline "اختبار التطبيق"، وشاهده يشغل أمرًا مثل `npm run dev`، ويطلق خادم التطوير المحلي في متصفح، ويجري سلسلة من الاختبارات للتأكد من أن كل شيء يعمل. [شاهد عرضًا توضيحيًا هنا.](https://x.com/sdrzn/status/1850880547825823989)
+2 -2
View File
@@ -28,7 +28,7 @@
Lernen Sie Cline kennen, einen KI-Assistenten, der Ihre **CLI** u**N**d **E**ditor nutzen kann.
Dank der [agentischen Codierungsfähigkeiten von Claude 3.7 Sonnet](https://www.anthropic.com/claude/sonnet) kann Cline komplexe Softwareentwicklungsaufgaben Schritt für Schritt bewältigen. Mit Werkzeugen, die ihm das Erstellen und Bearbeiten von Dateien, das Erkunden großer Projekte, die Nutzung des Browsers und das Ausführen von Terminalbefehlen (nach Ihrer Genehmigung) ermöglichen, kann er Ihnen auf eine Weise helfen, die über die Codevervollständigung oder technischen Support hinausgeht. Cline kann sogar das Model Context Protocol (MCP) verwenden, um neue Werkzeuge zu erstellen und seine eigenen Fähigkeiten zu erweitern. Während autonome KI-Skripte traditionell in sandboxed Umgebungen laufen, bietet diese Erweiterung eine Mensch-in-der-Schleife-GUI, um jede Dateiänderung und jeden Terminalbefehl zu genehmigen, was eine sichere und zugängliche Möglichkeit bietet, das Potenzial agentischer KI zu erkunden.
Dank der [agentischen Codierungsfähigkeiten von Claude 4 Sonnet](https://www.anthropic.com/claude/sonnet) kann Cline komplexe Softwareentwicklungsaufgaben Schritt für Schritt bewältigen. Mit Werkzeugen, die ihm das Erstellen und Bearbeiten von Dateien, das Erkunden großer Projekte, die Nutzung des Browsers und das Ausführen von Terminalbefehlen (nach Ihrer Genehmigung) ermöglichen, kann er Ihnen auf eine Weise helfen, die über die Codevervollständigung oder technischen Support hinausgeht. Cline kann sogar das Model Context Protocol (MCP) verwenden, um neue Werkzeuge zu erstellen und seine eigenen Fähigkeiten zu erweitern. Während autonome KI-Skripte traditionell in sandboxed Umgebungen laufen, bietet diese Erweiterung eine Mensch-in-der-Schleife-GUI, um jede Dateiänderung und jeden Terminalbefehl zu genehmigen, was eine sichere und zugängliche Möglichkeit bietet, das Potenzial agentischer KI zu erkunden.
1. Geben Sie Ihre Aufgabe ein und fügen Sie Bilder hinzu, um Mockups in funktionale Apps zu konvertieren oder Fehler mit Screenshots zu beheben.
2. Cline beginnt mit der Analyse Ihrer Dateistruktur und Quellcode-ASTs, führt Regex-Suchen durch und liest relevante Dateien, um sich in bestehenden Projekten zurechtzufinden. Durch sorgfältiges Management der hinzugefügten Informationen kann Cline wertvolle Unterstützung auch bei großen, komplexen Projekten bieten, ohne das Kontextfenster zu überladen.
@@ -83,7 +83,7 @@ Alle von Cline vorgenommenen Änderungen werden in der Timeline Ihrer Datei aufg
### Den Browser verwenden
Mit der neuen [Computer Use](https://www.anthropic.com/news/3-5-models-and-computer-use) Fähigkeit von Claude 3.5 Sonnet kann Cline einen Browser starten, Elemente anklicken, Text eingeben und scrollen, dabei Screenshots und Konsolenprotokolle bei jedem Schritt erfassen. Dies ermöglicht interaktives Debugging, End-to-End-Tests und sogar allgemeine Webnutzung! Dies gibt ihm die Autonomie, visuelle Fehler und Laufzeitprobleme zu beheben, ohne dass Sie selbst Fehlerprotokolle kopieren und einfügen müssen.
Mit der neuen [Computer Use](https://www.anthropic.com/news/3-5-models-and-computer-use) Fähigkeit von Claude 4 Sonnet kann Cline einen Browser starten, Elemente anklicken, Text eingeben und scrollen, dabei Screenshots und Konsolenprotokolle bei jedem Schritt erfassen. Dies ermöglicht interaktives Debugging, End-to-End-Tests und sogar allgemeine Webnutzung! Dies gibt ihm die Autonomie, visuelle Fehler und Laufzeitprobleme zu beheben, ohne dass Sie selbst Fehlerprotokolle kopieren und einfügen müssen.
Versuchen Sie, Cline zu bitten, "die App zu testen", und sehen Sie zu, wie er einen Befehl wie `npm run dev` ausführt, Ihren lokal laufenden Dev-Server in einem Browser startet und eine Reihe von Tests durchführt, um zu bestätigen, dass alles funktioniert. [Sehen Sie sich hier eine Demo an.](https://x.com/sdrzn/status/1850880547825823989)
+2 -2
View File
@@ -28,7 +28,7 @@
Conozca a Cline, un asistente de IA que puede usar su **CLI** y **E**ditor.
Gracias a las [habilidades de codificación agencial de Claude 3.7 Sonnet](https://www.anthropic.com/claude/sonnet), Cline puede abordar tareas complejas de desarrollo de software paso a paso. Con herramientas que le permiten crear y editar archivos, explorar grandes proyectos, usar el navegador y ejecutar comandos de terminal (con su aprobación), puede ayudarle de una manera que va más allá de la autocompletación de código o el soporte técnico. Cline incluso puede usar el Model Context Protocol (MCP) para crear nuevas herramientas y expandir sus propias capacidades. Mientras que los scripts de IA autónomos tradicionalmente se ejecutan en entornos aislados, esta extensión ofrece una GUI con un humano en el bucle para aprobar cada cambio de archivo y comando de terminal, proporcionando una forma segura y accesible de explorar el potencial de la IA agencial.
Gracias a las [habilidades de codificación agencial de Claude 4 Sonnet](https://www.anthropic.com/claude/sonnet), Cline puede abordar tareas complejas de desarrollo de software paso a paso. Con herramientas que le permiten crear y editar archivos, explorar grandes proyectos, usar el navegador y ejecutar comandos de terminal (con su aprobación), puede ayudarle de una manera que va más allá de la autocompletación de código o el soporte técnico. Cline incluso puede usar el Model Context Protocol (MCP) para crear nuevas herramientas y expandir sus propias capacidades. Mientras que los scripts de IA autónomos tradicionalmente se ejecutan en entornos aislados, esta extensión ofrece una GUI con un humano en el bucle para aprobar cada cambio de archivo y comando de terminal, proporcionando una forma segura y accesible de explorar el potencial de la IA agencial.
1. Ingrese su tarea y agregue imágenes para convertir maquetas en aplicaciones funcionales o solucionar errores con capturas de pantalla.
2. Cline comenzará analizando su estructura de archivos y ASTs de código fuente, realizando búsquedas Regex y leyendo archivos relevantes para orientarse en proyectos existentes. Al gestionar cuidadosamente la información agregada, Cline puede proporcionar asistencia valiosa incluso en proyectos grandes y complejos sin sobrecargar la ventana de contexto.
@@ -83,7 +83,7 @@ Todos los cambios realizados por Cline se registran en la línea de tiempo de su
### Usar el navegador
Con la nueva [habilidad de uso de computadora](https://www.anthropic.com/news/3-5-models-and-computer-use) de Claude 3.5 Sonnet, Cline puede iniciar un navegador, hacer clic en elementos, escribir texto y desplazarse, capturando capturas de pantalla y registros de consola. Esto permite la depuración interactiva, pruebas de extremo a extremo e incluso el uso general de la web. Esto le da la autonomía para solucionar errores visuales y problemas de tiempo de ejecución sin que tenga que copiar y pegar registros de errores.
Con la nueva [habilidad de uso de computadora](https://www.anthropic.com/news/3-5-models-and-computer-use) de Claude 4 Sonnet, Cline puede iniciar un navegador, hacer clic en elementos, escribir texto y desplazarse, capturando capturas de pantalla y registros de consola. Esto permite la depuración interactiva, pruebas de extremo a extremo e incluso el uso general de la web. Esto le da la autonomía para solucionar errores visuales y problemas de tiempo de ejecución sin que tenga que copiar y pegar registros de errores.
Intente pedirle a Cline que "pruebe la aplicación" y observe cómo ejecuta un comando como `npm run dev`, inicia su servidor de desarrollo local en un navegador y realiza una serie de pruebas para confirmar que todo funciona. [Vea una demostración aquí.](https://x.com/sdrzn/status/1850880547825823989)
+2 -2
View File
@@ -28,7 +28,7 @@
Clineは、**CLI**と**エディター**を使用できるAIアシスタントです。
[Claude 3.7 Sonnetのエージェント的コーディング機能](https://www.anthropic.com/claude/sonnet)のおかげで、Clineは複雑なソフトウェア開発タスクをステップバイステップで処理できます。ファイルの作成と編集、大規模プロジェクトの探索、ブラウザの使用、ターミナルコマンドの実行(許可後)などのツールを使用して、コード補完や技術サポートを超えた支援を提供します。Clineは、Model Context Protocol (MCP)を使用して新しいツールを作成し、自身の機能を拡張することもできます。自律的なAIスクリプトは通常サンドボックス環境で実行されますが、この拡張機能はファイル変更やターミナルコマンドを承認するための人間インターフェースを提供し、エージェント的AIの可能性を安全かつアクセスしやすい方法で探求できます。
[Claude 4 Sonnetのエージェント的コーディング機能](https://www.anthropic.com/claude/sonnet)のおかげで、Clineは複雑なソフトウェア開発タスクをステップバイステップで処理できます。ファイルの作成と編集、大規模プロジェクトの探索、ブラウザの使用、ターミナルコマンドの実行(許可後)などのツールを使用して、コード補完や技術サポートを超えた支援を提供します。Clineは、Model Context Protocol (MCP)を使用して新しいツールを作成し、自身の機能を拡張することもできます。自律的なAIスクリプトは通常サンドボックス環境で実行されますが、この拡張機能はファイル変更やターミナルコマンドを承認するための人間インターフェースを提供し、エージェント的AIの可能性を安全かつアクセスしやすい方法で探求できます。
1. タスクを入力し、モックアップを機能するアプリに変換したり、スクリーンショットでバグを修正したりします。
2. Clineは、ファイル構造とソースコードASTの分析、正規表現検索の実行、関連ファイルの読み取りから始め、既存プロジェクトに精通します。コンテキストに追加される情報を慎重に管理することで、大規模で複雑なプロジェクトでもコンテキストウィンドウを圧倒することなく貴重な支援を提供できます。
@@ -83,7 +83,7 @@ Clineによるすべての変更はファイルのタイムラインに記録さ
### ブラウザの使用
Claude 3.5 Sonnetの新しい[コンピュータ使用](https://www.anthropic.com/news/3-5-models-and-computer-use)機能により、Clineはブラウザを起動し、要素をクリック、テキストを入力、スクロールし、各ステップでスクリーンショットとコンソールログをキャプチャできます。これにより、インタラクティブなデバッグ、エンドツーエンドテスト、さらには一般的なウェブ使用が可能になります。これにより、エラーログを手動でコピー&ペーストすることなく、視覚的なバグやランタイムの問題を自律的に修正できます。
Claude 4 Sonnetの新しい[コンピュータ使用](https://www.anthropic.com/news/3-5-models-and-computer-use)機能により、Clineはブラウザを起動し、要素をクリック、テキストを入力、スクロールし、各ステップでスクリーンショットとコンソールログをキャプチャできます。これにより、インタラクティブなデバッグ、エンドツーエンドテスト、さらには一般的なウェブ使用が可能になります。これにより、エラーログを手動でコピー&ペーストすることなく、視覚的なバグやランタイムの問題を自律的に修正できます。
Clineに「アプリをテストして」と頼んでみてください。彼は`npm run dev`のようなコマンドを実行し、ローカルで実行中の開発サーバーをブラウザで起動し、一連のテストを実行してすべてが正常に動作することを確認します。[デモはこちら。](https://x.com/sdrzn/status/1850880547825823989)
+2 -2
View File
@@ -28,7 +28,7 @@
Cline을 만나보세요, **CLI** 및 **에디터**를 활용할 수 있는 AI 어시스턴트입니다.
[Claude 3.7 Sonnet의 에이전트형 코딩 기능](https://www.anthropic.com/claude/sonnet) 덕분에, Cline은 복잡한 소프트웨어 개발 작업을 단계별로 처리할 수 있습니다. 파일 생성과 편집, 대규모 프로젝트 탐색, 브라우저 사용, 터미널 명령 실행(권한 허가 필요) 등의 도구를 사용하여 단순 코드 완성이나 기술 지원을 넘어서는 도움을 제공합니다. Cline은 Model Context Protocol(MCP)를 사용하여 새로운 도구를 만들고 자신의 기능을 확장할 수도 있습니다. 자율적인 AI 스크립트는 일반적으로 샌드박스 환경에서 실행되지만, 이 확장 프로그램은 모든 파일 변경 및 터미널 명령을 승인할 수 있는 사람이 개입가능한 GUI를 제공하여, 에이전트형 AI의 잠재력을 보다 안전하고 쉽게 탐색할 수 있도록 합니다.
[Claude 4 Sonnet의 에이전트형 코딩 기능](https://www.anthropic.com/claude/sonnet) 덕분에, Cline은 복잡한 소프트웨어 개발 작업을 단계별로 처리할 수 있습니다. 파일 생성과 편집, 대규모 프로젝트 탐색, 브라우저 사용, 터미널 명령 실행(권한 허가 필요) 등의 도구를 사용하여 단순 코드 완성이나 기술 지원을 넘어서는 도움을 제공합니다. Cline은 Model Context Protocol(MCP)를 사용하여 새로운 도구를 만들고 자신의 기능을 확장할 수도 있습니다. 자율적인 AI 스크립트는 일반적으로 샌드박스 환경에서 실행되지만, 이 확장 프로그램은 모든 파일 변경 및 터미널 명령을 승인할 수 있는 사람이 개입가능한 GUI를 제공하여, 에이전트형 AI의 잠재력을 보다 안전하고 쉽게 탐색할 수 있도록 합니다.
1. 작업을 입력하고, 목업을 기능하는 앱으로 변환하거나 스크린샷으로 버그를 수정합니다.
2. Cline은 파일 구조와 소스코드 AST의 분석, 정규식 검색 실행, 관련 파일 읽기부터 시작하여 기존 프로젝트를 파악합니다. 또한, 어떤 정보를 컨텍스트에 추가할지를 신중하게 관리하여, 대규모 복잡한 프로젝트에서도 컨텍스트 윈도우를 과부하시키지 않으면서도 효과적인 지원을 제공합니다.
@@ -78,7 +78,7 @@ Cline에 의한 모든 변경은 파일의 타임라인에 기록되어 필요
### 브라우저 사용
Claude 3.5 Sonnet의 새로운 [컴퓨터 사용](https://www.anthropic.com/news/3-5-models-and-computer-use) 기능으로 인해, Cline은 브라우저를 실행하고 요소를 클릭하고 텍스트를 입력하고 스크롤하며 각 단계에서 스크린샷과 콘솔 로그를 캡처할 수 있습니다. 이를 통해 인터랙티브한 디버깅, 엔드투엔드 테스트, 심지어 일반적인 웹 탐색까지 가능해집니다. 이로 인해 오류 로그를 수동으로 복사 & 붙여넣기 할 필요 없이 시각적 버그나 런타임 문제를 자율적으로 수정할 수 있습니다.
Claude 4 Sonnet의 새로운 [컴퓨터 사용](https://www.anthropic.com/news/3-5-models-and-computer-use) 기능으로 인해, Cline은 브라우저를 실행하고 요소를 클릭하고 텍스트를 입력하고 스크롤하며 각 단계에서 스크린샷과 콘솔 로그를 캡처할 수 있습니다. 이를 통해 인터랙티브한 디버깅, 엔드투엔드 테스트, 심지어 일반적인 웹 탐색까지 가능해집니다. 이로 인해 오류 로그를 수동으로 복사 & 붙여넣기 할 필요 없이 시각적 버그나 런타임 문제를 자율적으로 수정할 수 있습니다.
Cline에게 "앱을 테스트해줘"라고 요청하면, `npm run dev`와 같은 명령을 실행하고 로컬에서 실행 중인 개발 서버를 브라우저에서 실행하여 일련의 테스트를 수행하고 모든 것이 정상적으로 작동하는지 확인합니다. [데모는 여기를 참조하세요.](https://x.com/sdrzn/status/1850880547825823989)
+2 -2
View File
@@ -28,7 +28,7 @@
Conheça o Cline: um assistente de IA que pode usar seu **CLI** e **Editor**.
Graças às [habilidades avançadas do Claude 3.7 Sonnet](https://www.anthropic.com/claude/sonnet), o Cline pode lidar com tarefas complexas de desenvolvimento de software passo a passo. Com ferramentas que permitem criar e editar arquivos, explorar grandes projetos, usar o navegador e executar comandos no terminal (com sua aprovação), ele pode ajudar você de maneiras que vão além da inclusão de código ou suporte técnico. O Cline pode é capaz inclusive de usar o Model Context Protocol (MCP) para criar novas ferramentas e expandir seus próprios recursos. Embora os scripts de IA autônomas tradicionalmente sejam executados em ambientes isolados, esta extensão oferece uma GUI com um humano no circuito para aprovar cada alteração de arquivo e comando de terminal, fornecendo uma maneira segura e acessível de explorar todo o potencial da IA.
Graças às [habilidades avançadas do Claude 4 Sonnet](https://www.anthropic.com/claude/sonnet), o Cline pode lidar com tarefas complexas de desenvolvimento de software passo a passo. Com ferramentas que permitem criar e editar arquivos, explorar grandes projetos, usar o navegador e executar comandos no terminal (com sua aprovação), ele pode ajudar você de maneiras que vão além da inclusão de código ou suporte técnico. O Cline pode é capaz inclusive de usar o Model Context Protocol (MCP) para criar novas ferramentas e expandir seus próprios recursos. Embora os scripts de IA autônomas tradicionalmente sejam executados em ambientes isolados, esta extensão oferece uma GUI com um humano no circuito para aprovar cada alteração de arquivo e comando de terminal, fornecendo uma maneira segura e acessível de explorar todo o potencial da IA.
1. Insira sua tarefa e adicione imagens para transformar mockups em aplicativos funcionais ou corrigir erros através de capturas de tela.
@@ -83,7 +83,7 @@ Todas as alterações feitas pelo Cline são registradas na Linha do tempo do ar
### Uso do navegador
Com a nova habilidade de [uso de computador](https://www.anthropic.com/news/3-5-models-and-computer-use) do Claude Sonnet 3.5, Cline pode abrir um navegador, clicar em elementos, digitar texto e rolar, capturando a tela e logs de console. Isso permite depurar de maneira interativa, testes end-to-end e até mesmo uso geral da web. Isso lhe dá autonomia para solucionar erros visuais e problemas em tempo de execução sem precisar copiar e colar logs dos erros.
Com a nova habilidade de [uso de computador](https://www.anthropic.com/news/3-5-models-and-computer-use) do Claude Sonnet 4, Cline pode abrir um navegador, clicar em elementos, digitar texto e rolar, capturando a tela e logs de console. Isso permite depurar de maneira interativa, testes end-to-end e até mesmo uso geral da web. Isso lhe dá autonomia para solucionar erros visuais e problemas em tempo de execução sem precisar copiar e colar logs dos erros.
Tente pedir a Cline para "testar o aplicativo" e observe enquanto o Cline executa um comando como `npm run dev`, inicia seu servidor de desenvolvimento local em um navegador e executa uma série de testes para confirmar se tudo funciona. [Veja uma demonstração aqui.](https://x.com/sdrzn/status/1850880547825823989)
+2 -2
View File
@@ -28,7 +28,7 @@
认识 Cline —— 一个可以使用你的 **终端****编辑器** 的 AI 助手。
得益于 [Claude 3.7 Sonnet 的代理式编码能力](https://www.anthropic.com/claude/sonnet),Cline 能够逐步处理复杂的软件开发任务。借助于一系列工具,他可以创建和编辑文件、浏览大型项目、使用浏览器,并在你授权后执行终端命令,从而在代码补全或技术支持之外提供更深入的帮助。Cline 甚至还能使用 Model Context ProtocolMCP)来创建新工具,并扩展自身的能力。虽然传统的自动化 AI 脚本通常运行在沙盒环境中,但这个扩展提供了一个人类参与审核的图形界面(GUI),用于审批每一次文件变更和终端命令,从而为探索代理式 AI 的潜力提供了一种安全且易于使用的方式。
得益于 [Claude 4 Sonnet 的代理式编码能力](https://www.anthropic.com/claude/sonnet),Cline 能够逐步处理复杂的软件开发任务。借助于一系列工具,他可以创建和编辑文件、浏览大型项目、使用浏览器,并在你授权后执行终端命令,从而在代码补全或技术支持之外提供更深入的帮助。Cline 甚至还能使用 Model Context ProtocolMCP)来创建新工具,并扩展自身的能力。虽然传统的自动化 AI 脚本通常运行在沙盒环境中,但这个扩展提供了一个人类参与审核的图形界面(GUI),用于审批每一次文件变更和终端命令,从而为探索代理式 AI 的潜力提供了一种安全且易于使用的方式。
1. 输入你的任务,并添加图片,以将界面原型(mockup)转换为功能应用,或通过截图修复 bug。
2. Cline 会从分析你的文件结构和源代码的抽象语法树(AST)开始,同时执行正则搜索并读取相关文件,以便尽快熟悉项目上下文。通过精细地管理上下文中引入的信息,即使面对大型复杂项目,Cline 也能在不超出上下文窗口限制的前提下提供有效协助。
@@ -83,7 +83,7 @@ Cline 所做的所有更改都会记录在你的文件时间轴中,提供了
### 使用浏览器
借助 Claude 3.5 Sonnet 的新 [计算机使用](https://www.anthropic.com/news/3-5-models-and-computer-use) 功能,Cline 可以启动浏览器,点击元素,输入文本和滚动,在每一步捕获截图和控制台日志。这允许进行交互式调试、端到端测试,甚至是一般的网页使用!这使他能够自主修复视觉错误和运行时问题,而无需你亲自操作和复制粘贴错误日志。
借助 Claude 4 Sonnet 的新 [计算机使用](https://www.anthropic.com/news/3-5-models-and-computer-use) 功能,Cline 可以启动浏览器,点击元素,输入文本和滚动,在每一步捕获截图和控制台日志。这允许进行交互式调试、端到端测试,甚至是一般的网页使用!这使他能够自主修复视觉错误和运行时问题,而无需你亲自操作和复制粘贴错误日志。
试试让 Cline “测试应用程序”,看看他如何运行 `npm run dev` 命令,在浏览器中启动你本地运行的开发服务器,并执行一系列测试以确认一切正常。[在这里查看演示。](https://x.com/sdrzn/status/1850880547825823989)
+2 -2
View File
@@ -32,7 +32,7 @@
認識 Cline,一個可以使用您的**命令列介面** (CLI) 和**程式編輯器** (Editor) 的 AI 助理。
感謝 [Claude 3.7 Sonnet 的代理式程式設計能力](https://www.anthropic.com/claude/sonnet),Cline 能夠逐步處理複雜的軟體開發任務。透過能讓他建立和編輯檔案、探索大型專案、使用瀏覽器,以及執行終端機指令(在您授權後)的工具,從而在程式碼補全或技術支援之外提供更深入的協助。Cline 甚至能使用模型上下文協定(Model Context ProtocolMCP)來建立新工具並擴展自己的功能。雖然自主 AI 腳本傳統上會在沙箱環境中執行,但這個擴充套件提供了人機互動的圖形介面,讓您可以核准每個檔案變更和終端機指令,提供一個安全且容易使用的方式來探索代理式 AI 的潛力。
感謝 [Claude 4 Sonnet 的代理式程式設計能力](https://www.anthropic.com/claude/sonnet),Cline 能夠逐步處理複雜的軟體開發任務。透過能讓他建立和編輯檔案、探索大型專案、使用瀏覽器,以及執行終端機指令(在您授權後)的工具,從而在程式碼補全或技術支援之外提供更深入的協助。Cline 甚至能使用模型上下文協定(Model Context ProtocolMCP)來建立新工具並擴展自己的功能。雖然自主 AI 腳本傳統上會在沙箱環境中執行,但這個擴充套件提供了人機互動的圖形介面,讓您可以核准每個檔案變更和終端機指令,提供一個安全且容易使用的方式來探索代理式 AI 的潛力。
1. 輸入您的任務,並可以加入圖片來將設計稿轉換成功能性應用程式,或使用截圖來修正錯誤。
2. Cline 會先分析您的檔案結構和程式碼 AST、執行正規表達式搜尋,並讀取相關檔案,以便在現有專案中快速掌握狀況。透過仔細管理加入上下文的資訊,Cline 可以在不超過上下文視窗的情況下,為大型且複雜的專案提供有價值的協助。
@@ -84,7 +84,7 @@ Cline 可以直接在您的編輯器中建立和編輯檔案,並顯示變更
### 使用瀏覽器
透過 Claude 3.5 Sonnet 的新[電腦使用](https://www.anthropic.com/news/3-5-models-and-computer-use)功能,Cline 可以啟動瀏覽器、點選元素、輸入文字和捲動,在每個步驟擷取螢幕截圖和主控台記錄。這讓互動式除錯、端對端測試,甚至一般網頁使用成為可能!這讓他能獨立修正視覺問題和執行時錯誤,而不需要您手動複製錯誤記錄。
透過 Claude 4 Sonnet 的新[電腦使用](https://www.anthropic.com/news/3-5-models-and-computer-use)功能,Cline 可以啟動瀏覽器、點選元素、輸入文字和捲動,在每個步驟擷取螢幕截圖和主控台記錄。這讓互動式除錯、端對端測試,甚至一般網頁使用成為可能!這讓他能獨立修正視覺問題和執行時錯誤,而不需要您手動複製錯誤記錄。
試著請 Cline 「測試應用程式」,觀察他如何執行 `npm run dev`、在瀏覽器中啟動您的本機開發伺服器,並執行一系列測試來確認一切正常運作。[點此觀看示範](https://x.com/sdrzn/status/1850880547825823989)。
+15 -2
View File
@@ -50,8 +50,21 @@
"activitybar": [
{
"id": "claude-dev-ActivityBar",
"title": "Cline",
"icon": "assets/icons/icon.svg"
"title": "Cline (⌘+')",
"icon": "assets/icons/icon.svg",
"when": "isMac"
},
{
"id": "claude-dev-ActivityBar",
"title": "Cline (Ctrl+')",
"icon": "assets/icons/icon.svg",
"when": "isWindows"
},
{
"id": "claude-dev-ActivityBar",
"title": "Cline (Ctrl+')",
"icon": "assets/icons/icon.svg",
"when": "isLinux || !isMac && !isWindows"
}
]
},
+24
View File
@@ -10,6 +10,7 @@ service StateService {
rpc resetState(EmptyRequest) returns (Empty);
rpc togglePlanActMode(TogglePlanActModeRequest) returns (Empty);
rpc updateTerminalConnectionTimeout(Int64Request) returns (Int64);
rpc updateAutoApprovalSettings(AutoApprovalSettingsRequest) returns (Empty);
}
message State {
@@ -37,3 +38,26 @@ message ChatContent {
optional string message = 1;
repeated string images = 2;
}
// Message for auto approval settings
message AutoApprovalSettingsRequest {
Metadata metadata = 1;
message Actions {
bool read_files = 1;
bool read_files_externally = 2;
bool edit_files = 3;
bool edit_files_externally = 4;
bool execute_safe_commands = 5;
bool execute_all_commands = 6;
bool use_browser = 7;
bool use_mcp = 8;
}
int32 version = 2;
bool enabled = 3;
Actions actions = 4;
int32 max_requests = 5;
bool enable_notifications = 6;
repeated string favorites = 7;
}
+3
View File
@@ -13,6 +13,7 @@ import { ApiStream, ApiStreamUsageChunk } from "./transform/stream"
import { DeepSeekHandler } from "./providers/deepseek"
import { RequestyHandler } from "./providers/requesty"
import { TogetherHandler } from "./providers/together"
import { NebiusHandler } from "./providers/nebius"
import { QwenHandler } from "./providers/qwen"
import { MistralHandler } from "./providers/mistral"
import { DoubaoHandler } from "./providers/doubao"
@@ -75,6 +76,8 @@ export function buildApiHandler(configuration: ApiConfiguration): ApiHandler {
return new ClineHandler(options)
case "litellm":
return new LiteLlmHandler(options)
case "nebius":
return new NebiusHandler(options)
case "asksage":
return new AskSageHandler(options)
case "xai":
+5 -1
View File
@@ -24,13 +24,15 @@ export class AnthropicHandler implements ApiHandler {
const modelId = model.id
const budget_tokens = this.options.thinkingBudgetTokens || 0
const reasoningOn = modelId.includes("3-7") && budget_tokens !== 0 ? true : false
const reasoningOn = (modelId.includes("3-7") || modelId.includes("4-")) && budget_tokens !== 0 ? true : false
switch (modelId) {
// 'latest' alias does not support cache_control
case "claude-sonnet-4-20250514":
case "claude-3-7-sonnet-20250219":
case "claude-3-5-sonnet-20241022":
case "claude-3-5-haiku-20241022":
case "claude-opus-4-20250514":
case "claude-3-opus-20240229":
case "claude-3-haiku-20240307": {
/*
@@ -96,6 +98,8 @@ export class AnthropicHandler implements ApiHandler {
// https://github.com/anthropics/anthropic-sdk-typescript?tab=readme-ov-file#default-headers
// https://github.com/anthropics/anthropic-sdk-typescript/commit/c920b77fc67bd839bfeb6716ceab9d7c9bbe7393
switch (modelId) {
case "claude-sonnet-4-20250514":
case "claude-opus-4-20250514":
case "claude-3-7-sonnet-20250219":
case "claude-3-5-sonnet-20241022":
case "claude-3-5-haiku-20241022":
+69
View File
@@ -0,0 +1,69 @@
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import { withRetry } from "../retry"
import { ApiHandler } from "../index"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { ApiStream } from "../transform/stream"
import { convertToR1Format } from "../transform/r1-format"
import { nebiusDefaultModelId, nebiusModels, type ModelInfo, type ApiHandlerOptions, type NebiusModelId } from "../../shared/api"
export class NebiusHandler implements ApiHandler {
private client: OpenAI
constructor(private readonly options: ApiHandlerOptions) {
this.client = new OpenAI({
baseURL: "https://api.studio.nebius.ai/v1",
apiKey: this.options.nebiusApiKey,
})
}
@withRetry()
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
const model = this.getModel()
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = model.id.includes("DeepSeek-R1")
? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)]
const stream = await this.client.chat.completions.create({
model: model.id,
messages: openAiMessages,
temperature: 0,
stream: true,
stream_options: { include_usage: true },
})
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta
if (delta?.content) {
yield {
type: "text",
text: delta.content,
}
}
if (delta && "reasoning_content" in delta && delta.reasoning_content) {
yield {
type: "reasoning",
reasoning: (delta.reasoning_content as string | undefined) || "",
}
}
if (chunk.usage) {
yield {
type: "usage",
inputTokens: chunk.usage.prompt_tokens || 0,
outputTokens: chunk.usage.completion_tokens || 0,
}
}
}
}
getModel(): { id: string; info: ModelInfo } {
const modelId = this.options.apiModelId
if (modelId !== undefined && modelId in nebiusModels) {
return { id: modelId, info: nebiusModels[modelId as NebiusModelId] }
}
return { id: nebiusDefaultModelId, info: nebiusModels[nebiusDefaultModelId] }
}
}
+30 -24
View File
@@ -7,6 +7,16 @@ import { convertToOpenAiMessages } from "@api/transform/openai-format"
import { calculateApiCostOpenAI } from "@utils/cost"
import { ApiStream } from "@api/transform/stream"
// Requesty usage includes an extra field for Anthropic use cases.
// Safely cast the prompt token details section to the appropriate structure.
interface RequestyUsage extends OpenAI.CompletionUsage {
prompt_tokens_details?: {
caching_tokens?: number
cached_tokens?: number
}
total_cost?: number
}
export class RequestyHandler implements ApiHandler {
private options: ApiHandlerOptions
private client: OpenAI
@@ -55,6 +65,8 @@ export class RequestyHandler implements ApiHandler {
...thinkingArgs,
})
let lastUsage: any = undefined
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta
if (delta?.content) {
@@ -71,32 +83,26 @@ export class RequestyHandler implements ApiHandler {
}
}
// Requesty usage includes an extra field for Anthropic use cases.
// Safely cast the prompt token details section to the appropriate structure.
interface RequestyUsage extends OpenAI.CompletionUsage {
prompt_tokens_details?: {
caching_tokens?: number
cached_tokens?: number
}
total_cost?: number
}
if (chunk.usage) {
const usage = chunk.usage as RequestyUsage
const inputTokens = usage.prompt_tokens || 0
const outputTokens = usage.completion_tokens || 0
const cacheWriteTokens = usage.prompt_tokens_details?.caching_tokens || undefined
const cacheReadTokens = usage.prompt_tokens_details?.cached_tokens || undefined
const totalCost = calculateApiCostOpenAI(model.info, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens)
lastUsage = chunk.usage
}
}
yield {
type: "usage",
inputTokens: inputTokens,
outputTokens: outputTokens,
cacheWriteTokens: cacheWriteTokens,
cacheReadTokens: cacheReadTokens,
totalCost: totalCost,
}
if (lastUsage) {
const usage = lastUsage as RequestyUsage
const inputTokens = usage.prompt_tokens || 0
const outputTokens = usage.completion_tokens || 0
const cacheWriteTokens = usage.prompt_tokens_details?.caching_tokens || undefined
const cacheReadTokens = usage.prompt_tokens_details?.cached_tokens || undefined
const totalCost = calculateApiCostOpenAI(model.info, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens)
yield {
type: "usage",
inputTokens: inputTokens,
outputTokens: outputTokens,
cacheWriteTokens: cacheWriteTokens,
cacheReadTokens: cacheReadTokens,
totalCost: totalCost,
}
}
}
-19
View File
@@ -291,25 +291,6 @@ export class Controller {
}
await this.postStateToWebview()
break
case "autoApprovalSettings":
if (message.autoApprovalSettings) {
const currentSettings = (await getAllExtensionState(this.context)).autoApprovalSettings
const incomingVersion = message.autoApprovalSettings.version ?? 1
const currentVersion = currentSettings?.version ?? 1
if (incomingVersion > currentVersion) {
await updateGlobalState(this.context, "autoApprovalSettings", message.autoApprovalSettings)
if (this.task) {
this.task.autoApprovalSettings = message.autoApprovalSettings
}
await this.postStateToWebview()
}
}
break
case "togglePlanActMode":
if (message.chatSettings) {
await this.togglePlanActModeWithChatSettings(message.chatSettings, message.chatContent)
}
break
case "optionsResponse":
await this.postMessageToWebview({
type: "invoke",
@@ -44,6 +44,11 @@ export async function refreshRequestyModels(
models[model.id] = modelInfo
}
console.log("Requesty models fetched", models)
controller.postMessageToWebview({
type: "requestyModels",
requestyModels: models,
})
} else {
console.error("Invalid response from Requesty API")
}
@@ -0,0 +1,32 @@
import { Controller } from ".."
import { AutoApprovalSettingsRequest } from "../../../shared/proto/state"
import { Empty } from "../../../shared/proto/common"
import { convertProtoToAutoApprovalSettings } from "../../../shared/proto-conversions/models/auto-approval-settings-conversion"
import { updateGlobalState } from "../../../core/storage/state"
/**
* Updates the auto approval settings
* @param controller The controller instance
* @param request The auto approval settings request
* @returns Empty response
*/
export async function updateAutoApprovalSettings(controller: Controller, request: AutoApprovalSettingsRequest): Promise<Empty> {
const currentSettings = (await controller.getStateToPostToWebview()).autoApprovalSettings
const incomingVersion = request.version
const currentVersion = currentSettings?.version ?? 1
// Only update if incoming version is higher
if (incomingVersion > currentVersion) {
const settings = convertProtoToAutoApprovalSettings(request)
await updateGlobalState(controller.context, "autoApprovalSettings", settings)
if (controller.task) {
controller.task.autoApprovalSettings = settings
}
await controller.postStateToWebview()
}
return Empty.create()
}
+1 -1
View File
@@ -19,4 +19,4 @@ export const handleUiServiceStreamingRequest = uiService.handleStreamingRequest
export const isStreamingMethod = uiService.isStreamingMethod
// Register all ui methods
registerAllMethods()
registerAllMethods()
+1 -1
View File
@@ -9,4 +9,4 @@ import { scrollToSettings } from "./scrollToSettings"
export function registerAllMethods(): void {
// Register each method with the registry
registerMethod("scrollToSettings", scrollToSettings)
}
}
+1
View File
@@ -19,6 +19,7 @@ export type SecretKey =
| "authNonce"
| "asksageApiKey"
| "xaiApiKey"
| "nebiusApiKey"
| "sambanovaApiKey"
export type GlobalStateKey =
+6
View File
@@ -155,6 +155,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
thinkingBudgetTokens,
reasoningEffort,
sambanovaApiKey,
nebiusApiKey,
planActSeparateModelsSettingRaw,
favoritedModelIds,
globalClineRulesToggles,
@@ -242,6 +243,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
getGlobalState(context, "thinkingBudgetTokens") as Promise<number | undefined>,
getGlobalState(context, "reasoningEffort") as Promise<string | undefined>,
getSecret(context, "sambanovaApiKey") as Promise<string | undefined>,
getSecret(context, "nebiusApiKey") as Promise<string | undefined>,
getGlobalState(context, "planActSeparateModelsSetting") as Promise<boolean | undefined>,
getGlobalState(context, "favoritedModelIds") as Promise<string[] | undefined>,
getGlobalState(context, "globalClineRulesToggles") as Promise<ClineRulesToggles | undefined>,
@@ -353,6 +355,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
asksageApiUrl,
xaiApiKey,
sambanovaApiKey,
nebiusApiKey,
favoritedModelIds,
requestTimeoutMs,
},
@@ -445,6 +448,7 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
reasoningEffort,
clineApiKey,
sambanovaApiKey,
nebiusApiKey,
favoritedModelIds,
} = apiConfiguration
await updateGlobalState(context, "apiProvider", apiProvider)
@@ -505,6 +509,7 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
await updateGlobalState(context, "reasoningEffort", reasoningEffort)
await storeSecret(context, "clineApiKey", clineApiKey)
await storeSecret(context, "sambanovaApiKey", sambanovaApiKey)
await storeSecret(context, "nebiusApiKey", nebiusApiKey)
await updateGlobalState(context, "favoritedModelIds", favoritedModelIds)
await updateGlobalState(context, "requestTimeoutMs", apiConfiguration.requestTimeoutMs)
}
@@ -534,6 +539,7 @@ export async function resetExtensionState(context: vscode.ExtensionContext) {
"asksageApiKey",
"xaiApiKey",
"sambanovaApiKey",
"nebiusApiKey",
]
for (const key of secretKeys) {
await storeSecret(context, key, undefined)
+1 -21
View File
@@ -150,27 +150,7 @@ export class WebviewProvider implements vscode.WebviewViewProvider {
this.outputChannel.appendLine("Webview view resolved")
// Set the title to include the keyboard shortcut
if (this.view && "title" in this.view) {
// Check if the view object has a title property
const isMac = process.platform === "darwin"
const shortcutDisplay = isMac ? " (⌘+')" : " (Ctrl+')"
const baseTitle = this.context.extension.packageJSON.displayName || "Cline"
const newTitleWithShortcut = `${baseTitle}${shortcutDisplay}`
const currentTitle = this.view.title
if (typeof currentTitle === "string") {
if (!currentTitle.includes(shortcutDisplay)) {
// Title exists and is a string, but doesn't have the shortcut
this.view.title = newTitleWithShortcut
}
// If it includes shortcutDisplay, do nothing
} else {
// Title is undefined or not a string (e.g., for sidebar initially)
this.view.title = newTitleWithShortcut
}
}
// Title setting logic removed to allow VSCode to use the container title primarily.
}
}
+13 -6
View File
@@ -55,23 +55,30 @@ const BaseConfigSchema = z.object({
})
const SseConfigSchema = BaseConfigSchema.extend({
transportType: z.literal("sse"),
url: z.string().url(),
})
}).transform((config) => ({
...config,
transportType: "sse" as const,
}))
const StdioConfigSchema = BaseConfigSchema.extend({
transportType: z.literal("stdio"),
command: z.string(),
args: z.array(z.string()).optional(),
env: z.record(z.string()).optional(),
})
}).transform((config) => ({
...config,
transportType: "stdio" as const,
}))
const StreamableHTTPConfigSchema = BaseConfigSchema.extend({
transportType: z.literal("http"),
url: z.string().url(),
})
}).transform((config) => ({
...config,
transportType: "http" as const,
}))
const ServerConfigSchema = z.discriminatedUnion("transportType", [StdioConfigSchema, SseConfigSchema, StreamableHTTPConfigSchema])
const ServerConfigSchema = z.union([StdioConfigSchema, SseConfigSchema, StreamableHTTPConfigSchema])
const McpSettingsSchema = z.object({
mcpServers: z.record(ServerConfigSchema),
-4
View File
@@ -1,5 +1,4 @@
import { ApiConfiguration } from "./api"
import { AutoApprovalSettings } from "./AutoApprovalSettings"
import { BrowserSettings } from "./BrowserSettings"
import { ChatSettings } from "./ChatSettings"
import { UserInfo } from "./UserInfo"
@@ -18,8 +17,6 @@ export interface WebviewMessage {
| "openInBrowser"
| "showChatView"
| "openMcpSettings"
| "autoApprovalSettings"
| "togglePlanActMode"
| "openExtensionSettings"
| "requestVsCodeLmModels"
| "showAccountViewClicked"
@@ -46,7 +43,6 @@ export interface WebviewMessage {
images?: string[]
bool?: boolean
number?: number
autoApprovalSettings?: AutoApprovalSettings
browserSettings?: BrowserSettings
chatSettings?: ChatSettings
chatContent?: ChatContent
+111 -1
View File
@@ -19,6 +19,7 @@ export type ApiProvider =
| "vscode-lm"
| "cline"
| "litellm"
| "nebius"
| "fireworks"
| "asksage"
| "xai"
@@ -81,6 +82,7 @@ export interface ApiHandlerOptions {
azureApiVersion?: string
vsCodeLmModelSelector?: LanguageModelChatSelector
qwenApiLine?: string
nebiusApiKey?: string
asksageApiUrl?: string
asksageApiKey?: string
xaiApiKey?: string
@@ -136,8 +138,29 @@ export interface OpenAiCompatibleModelInfo extends ModelInfo {
// Anthropic
// https://docs.anthropic.com/en/docs/about-claude/models // prices updated 2025-01-02
export type AnthropicModelId = keyof typeof anthropicModels
export const anthropicDefaultModelId: AnthropicModelId = "claude-3-7-sonnet-20250219"
export const anthropicDefaultModelId: AnthropicModelId = "claude-sonnet-4-20250514"
export const anthropicModels = {
"claude-sonnet-4-20250514": {
maxTokens: 8192,
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 3.0,
outputPrice: 15.0,
cacheWritesPrice: 3.75,
cacheReadsPrice: 0.3,
},
"claude-opus-4-20250514": {
maxTokens: 4096,
contextWindow: 200_000,
supportsImages: true,
supportsPromptCache: true,
inputPrice: 15.0,
outputPrice: 75.0,
cacheWritesPrice: 18.75,
cacheReadsPrice: 1.5,
},
"claude-3-7-sonnet-20250219": {
maxTokens: 8192,
contextWindow: 200_000,
@@ -1560,6 +1583,93 @@ export const askSageModels = {
},
}
// Nebius AI Studio
// https://docs.nebius.com/studio/inference/models
export const nebiusModels = {
"deepseek-ai/DeepSeek-V3": {
maxTokens: 32_000,
contextWindow: 96_000,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.5,
outputPrice: 1.5,
},
"deepseek-ai/DeepSeek-V3-0324-fast": {
maxTokens: 128_000,
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 2,
outputPrice: 6,
},
"deepseek-ai/DeepSeek-R1": {
maxTokens: 32_000,
contextWindow: 96_000,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.8,
outputPrice: 2.4,
},
"deepseek-ai/DeepSeek-R1-fast": {
maxTokens: 32_000,
contextWindow: 96_000,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 2,
outputPrice: 6,
},
"meta-llama/Llama-3.3-70B-Instruct-fast": {
maxTokens: 32_000,
contextWindow: 96_000,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.25,
outputPrice: 0.75,
},
"Qwen/Qwen2.5-32B-Instruct-fast": {
maxTokens: 8_192,
contextWindow: 32_768,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.13,
outputPrice: 0.4,
},
"Qwen/Qwen2.5-Coder-32B-Instruct-fast": {
maxTokens: 128_000,
contextWindow: 128_000,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.1,
outputPrice: 0.3,
},
"Qwen/Qwen3-4B-fast": {
maxTokens: 32_000,
contextWindow: 41_000,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.08,
outputPrice: 0.24,
},
"Qwen/Qwen3-30B-A3B-fast": {
maxTokens: 32_000,
contextWindow: 41_000,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.3,
outputPrice: 0.9,
},
"Qwen/Qwen3-235B-A22B": {
maxTokens: 32_000,
contextWindow: 41_000,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.2,
outputPrice: 0.6,
},
} as const satisfies Record<string, ModelInfo>
export type NebiusModelId = keyof typeof nebiusModels
export const nebiusDefaultModelId = "Qwen/Qwen2.5-32B-Instruct-fast" satisfies NebiusModelId
// X AI
// https://docs.x.ai/docs/api-reference
export type XAIModelId = keyof typeof xaiModels
@@ -0,0 +1,45 @@
import { AutoApprovalSettings } from "../../AutoApprovalSettings"
import { AutoApprovalSettingsRequest } from "../../proto/state"
// Converts domain AutoApprovalSettings to proto AutoApprovalSettingsRequest
export function convertAutoApprovalSettingsToProto(settings: AutoApprovalSettings): AutoApprovalSettingsRequest {
return {
metadata: {},
version: settings.version,
enabled: settings.enabled,
actions: {
readFiles: settings.actions.readFiles || false,
readFilesExternally: settings.actions.readFilesExternally || false,
editFiles: settings.actions.editFiles || false,
editFilesExternally: settings.actions.editFilesExternally || false,
executeSafeCommands: settings.actions.executeSafeCommands || false,
executeAllCommands: settings.actions.executeAllCommands || false,
useBrowser: settings.actions.useBrowser || false,
useMcp: settings.actions.useMcp || false,
},
maxRequests: settings.maxRequests || 20,
enableNotifications: settings.enableNotifications || false,
favorites: settings.favorites || [],
}
}
// Converts proto AutoApprovalSettingsRequest to domain AutoApprovalSettings
export function convertProtoToAutoApprovalSettings(protoSettings: AutoApprovalSettingsRequest): AutoApprovalSettings {
return {
version: protoSettings.version,
enabled: protoSettings.enabled,
actions: {
readFiles: protoSettings.actions?.readFiles || false,
readFilesExternally: protoSettings.actions?.readFilesExternally || false,
editFiles: protoSettings.actions?.editFiles || false,
editFilesExternally: protoSettings.actions?.editFilesExternally || false,
executeSafeCommands: protoSettings.actions?.executeSafeCommands || false,
executeAllCommands: protoSettings.actions?.executeAllCommands || false,
useBrowser: protoSettings.actions?.useBrowser || false,
useMcp: protoSettings.actions?.useMcp || false,
},
maxRequests: protoSettings.maxRequests || 20,
enableNotifications: protoSettings.enableNotifications || false,
favorites: protoSettings.favorites || [],
}
}
-28
View File
@@ -1,28 +0,0 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.7.0
// protoc v3.19.1
// source: ui.proto
/* eslint-disable */
import { Empty, StringRequest } from "./common"
export const protobufPackage = "cline"
/** UiService provides methods for managing UI interactions */
export type UiServiceDefinition = typeof UiServiceDefinition
export const UiServiceDefinition = {
name: "UiService",
fullName: "cline.UiService",
methods: {
/** Scrolls to a specific settings section in the settings view */
scrollToSettings: {
name: "scrollToSettings",
requestType: StringRequest,
requestStream: false,
responseType: Empty,
responseStream: false,
options: {},
},
},
} as const
+51 -62
View File
@@ -11,6 +11,7 @@
"@floating-ui/react": "^0.27.4",
"@heroui/react": "^2.8.0-beta.2",
"@vscode/webview-ui-toolkit": "^1.4.0",
"clsx": "^2.1.1",
"debounce": "^2.1.1",
"dompurify": "^3.2.4",
"fast-deep-equal": "^3.1.3",
@@ -19,6 +20,7 @@
"fuse.js": "^7.0.0",
"fzf": "^0.5.2",
"katex": "^0.16.22",
"lucide-react": "^0.511.0",
"mermaid": "^11.4.1",
"package.json": "^2.0.1",
"posthog-js": "^1.224.0",
@@ -37,6 +39,7 @@
"remark-math": "^6.0.0",
"remark-stringify": "^11.0.0",
"styled-components": "^6.1.15",
"tailwind-merge": "^3.3.0",
"unified": "^11.0.5",
"uuid": "^9.0.1"
},
@@ -2513,6 +2516,14 @@
"react": ">=18 || >=19.0.0-rc.0"
}
},
"node_modules/@heroui/system-rsc/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/@heroui/table": {
"version": "2.2.17-beta.2",
"resolved": "https://registry.npmjs.org/@heroui/table/-/table-2.2.17-beta.2.tgz",
@@ -2590,6 +2601,23 @@
"tailwindcss": ">=4.0.0"
}
},
"node_modules/@heroui/theme/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/@heroui/theme/node_modules/tailwind-merge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz",
"integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
}
},
"node_modules/@heroui/toast": {
"version": "2.0.8-beta.2",
"resolved": "https://registry.npmjs.org/@heroui/toast/-/toast-2.0.8-beta.2.tgz",
@@ -3615,15 +3643,6 @@
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/focus/node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/@react-aria/form": {
"version": "3.0.14",
"resolved": "https://registry.npmjs.org/@react-aria/form/-/form-3.0.14.tgz",
@@ -3839,15 +3858,6 @@
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/grid/node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/@react-aria/i18n": {
"version": "3.12.7",
"resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.7.tgz",
@@ -3970,15 +3980,6 @@
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/landmark/node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/@react-aria/link": {
"version": "3.7.10",
"resolved": "https://registry.npmjs.org/@react-aria/link/-/link-3.7.10.tgz",
@@ -4290,15 +4291,6 @@
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/spinbutton/node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/@react-aria/ssr": {
"version": "3.9.7",
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz",
@@ -4535,15 +4527,6 @@
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/toggle/node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/@react-aria/toolbar": {
"version": "3.0.0-beta.14",
"resolved": "https://registry.npmjs.org/@react-aria/toolbar/-/toolbar-3.0.0-beta.14.tgz",
@@ -4597,15 +4580,6 @@
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
"node_modules/@react-aria/utils/node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/@react-aria/visually-hidden": {
"version": "3.8.21",
"resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.21.tgz",
@@ -8278,10 +8252,9 @@
}
},
"node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"license": "MIT",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"engines": {
"node": ">=6"
}
@@ -12487,6 +12460,14 @@
"dev": true,
"license": "ISC"
},
"node_modules/lucide-react": {
"version": "0.511.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.511.0.tgz",
"integrity": "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/lz-string": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
@@ -15865,10 +15846,9 @@
"license": "MIT"
},
"node_modules/tailwind-merge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz",
"integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==",
"license": "MIT",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.0.tgz",
"integrity": "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
@@ -15890,6 +15870,15 @@
"tailwindcss": "*"
}
},
"node_modules/tailwind-variants/node_modules/tailwind-merge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz",
"integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
}
},
"node_modules/tailwindcss": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.5.tgz",
+3
View File
@@ -18,6 +18,7 @@
"@floating-ui/react": "^0.27.4",
"@heroui/react": "^2.8.0-beta.2",
"@vscode/webview-ui-toolkit": "^1.4.0",
"clsx": "^2.1.1",
"debounce": "^2.1.1",
"dompurify": "^3.2.4",
"fast-deep-equal": "^3.1.3",
@@ -26,6 +27,7 @@
"fuse.js": "^7.0.0",
"fzf": "^0.5.2",
"katex": "^0.16.22",
"lucide-react": "^0.511.0",
"mermaid": "^11.4.1",
"package.json": "^2.0.1",
"posthog-js": "^1.224.0",
@@ -44,6 +46,7 @@
"remark-math": "^6.0.0",
"remark-stringify": "^11.0.0",
"styled-components": "^6.1.15",
"tailwind-merge": "^3.3.0",
"unified": "^11.0.5",
"uuid": "^9.0.1"
},
@@ -71,7 +71,7 @@ export const BrowserSettingsMenu = () => {
// After a short delay, send a message to scroll to browser settings
setTimeout(async () => {
try {
await UiServiceClient.scrollToSettings({ value: "browser-settings-section" })
await UiServiceClient.scrollToSettings({ value: "browser" })
} catch (error) {
console.error("Error scrolling to browser settings:", error)
}
@@ -39,6 +39,7 @@ import SlashCommandMenu from "@/components/chat/SlashCommandMenu"
import { ChatSettings } from "@shared/ChatSettings"
import ServersToggleModal from "./ServersToggleModal"
import ClineRulesToggleModal from "../cline-rules/ClineRulesToggleModal"
import { PlanActMode } from "@shared/proto/state"
const getImageDimensions = (dataUrl: string): Promise<{ width: number; height: number }> => {
return new Promise((resolve, reject) => {
@@ -965,9 +966,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
changeModeDelay = 250 // necessary to let the api config update (we send message and wait for it to be saved) FIXME: this is a hack and we ideally should check for api config changes, then wait for it to be saved, before switching modes
}
setTimeout(() => {
const newMode = chatSettings.mode === "plan" ? "act" : "plan"
vscode.postMessage({
type: "togglePlanActMode",
const newMode = chatSettings.mode === "plan" ? PlanActMode.ACT : PlanActMode.PLAN
StateServiceClient.togglePlanActMode({
chatSettings: {
mode: newMode,
preferredLanguage: chatSettings.preferredLanguage,
@@ -1071,6 +1071,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
case "litellm":
return `${selectedProvider}:${apiConfiguration.liteLlmModelId}`
case "requesty":
return `${selectedProvider}:${apiConfiguration.requestyModelId}`
case "anthropic":
case "openrouter":
default:
@@ -8,8 +8,8 @@ interface AutoApproveMenuItemProps {
action: ActionMetadata
isChecked: (action: ActionMetadata) => boolean
isFavorited?: (action: ActionMetadata) => boolean
onToggle: (action: ActionMetadata, checked: boolean) => void
onToggleFavorite?: (actionId: string) => void
onToggle: (action: ActionMetadata, checked: boolean) => Promise<void>
onToggleFavorite?: (actionId: string) => Promise<void>
condensed?: boolean
showIcon?: boolean
}
@@ -83,9 +83,9 @@ const AutoApproveMenuItem = ({
const checked = isChecked(action)
const favorited = isFavorited?.(action)
const onChange = (e: Event) => {
const onChange = async (e: Event) => {
e.stopPropagation()
onToggle(action, !checked)
await onToggle(action, !checked)
}
const content = (
@@ -107,9 +107,10 @@ const AutoApproveMenuItem = ({
style={{
cursor: "pointer",
}}
onClick={(e) => {
onClick={async (e) => {
e.stopPropagation()
onToggleFavorite?.(action.id)
if (action.id === "enableAll") return
await onToggleFavorite?.(action.id)
}}
/>
</HeroTooltip>
@@ -170,13 +170,13 @@ const AutoApproveModal: React.FC<AutoApproveModalProps> = ({
<VSCodeTextField
className="flex-1 w-full pr-[35px] ml-4"
value={autoApprovalSettings.maxRequests.toString()}
onInput={(e) => {
onInput={async (e) => {
const input = e.target as HTMLInputElement
// Remove any non-numeric characters
input.value = input.value.replace(/[^0-9]/g, "")
const value = parseInt(input.value)
if (!isNaN(value) && value > 0) {
updateMaxRequests(value)
await updateMaxRequests(value)
}
}}
onKeyDown={(e) => {
@@ -0,0 +1,18 @@
import { StateServiceClient } from "@/services/grpc-client"
import { AutoApprovalSettings } from "@shared/AutoApprovalSettings"
import { convertAutoApprovalSettingsToProto } from "@shared/proto-conversions/models/auto-approval-settings-conversion"
/**
* Updates auto approval settings using the gRPC/Protobus client
* @param settings The auto approval settings to update
* @throws Error if the update fails
*/
export async function updateAutoApproveSettings(settings: AutoApprovalSettings) {
try {
const protoSettings = convertAutoApprovalSettingsToProto(settings)
await StateServiceClient.updateAutoApprovalSettings(protoSettings)
} catch (error) {
console.error("Failed to update auto approval settings:", error)
throw error
}
}
@@ -9,8 +9,10 @@ const RuleRow: React.FC<{
ruleType: string
toggleRule: (rulePath: string, enabled: boolean) => void
}> = ({ rulePath, enabled, isGlobal, toggleRule, ruleType }) => {
// Check if the path type is Windows
const win32Path = /^[a-zA-Z]:\\/.test(rulePath)
// Get the filename from the path for display
const displayName = rulePath.split("/").pop() || rulePath
const displayName = rulePath.split(win32Path ? "\\" : "/").pop() || rulePath
const getRuleTypeIcon = () => {
switch (ruleType) {
@@ -10,6 +10,27 @@ import type { Node } from "unist"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
import MermaidBlock from "@/components/common/MermaidBlock"
import { vscode } from "@/utils/vscode"
// Styled component for Act Mode text with more specific styling
const ActModeHighlight: React.FC = () => (
<span
onClick={() => {
vscode.postMessage({
type: "togglePlanActMode",
chatSettings: {
mode: "act",
},
})
}}
title="Click to toggle to Act Mode"
className="text-[var(--vscode-textLink-foreground)] hover:opacity-90 cursor-pointer inline-flex items-center gap-1">
<div className="p-1 rounded-[12px] bg-[var(--vscode-editor-background)] flex items-center justify-end w-4 border-[1px] border-[var(--vscode-input-border)]">
<div className="rounded-full bg-[var(--vscode-textLink-foreground)] w-2 h-2" />
</div>
Act Mode (A)
</span>
)
interface MarkdownBlockProps {
markdown?: string
@@ -55,6 +76,68 @@ const remarkUrlToLink = () => {
}
}
/**
* Custom remark plugin that highlights "to Act Mode" mentions and adds keyboard shortcut hint
*/
const remarkHighlightActMode = () => {
return (tree: Node) => {
visit(tree, "text", (node: any, index, parent) => {
// Case-insensitive regex to match "to Act Mode" in various capitalizations
// Using word boundaries to avoid matching within words
// Added negative lookahead to avoid matching if already followed by the shortcut
const actModeRegex = /\bto\s+Act\s+Mode\b(?!\s*\(⌘⇧A\))/i
if (!node.value.match(actModeRegex)) return
// Split the text by the matches
const parts = node.value.split(actModeRegex)
const matches = node.value.match(actModeRegex)
if (!matches || parts.length <= 1) return
const children: any[] = []
parts.forEach((part: string, i: number) => {
// Add the text before the match
if (part) children.push({ type: "text", value: part })
// Add the match, but only make "Act Mode" bold (not the "to" part)
if (matches[i]) {
// Extract "to" and "Act Mode" parts
const matchText = matches[i]
const toIndex = matchText.toLowerCase().indexOf("to")
const actModeIndex = matchText.toLowerCase().indexOf("act mode", toIndex + 2)
if (toIndex !== -1 && actModeIndex !== -1) {
// Add "to" as regular text
const toPart = matchText.substring(toIndex, actModeIndex).trim()
children.push({ type: "text", value: toPart + " " })
// Add "Act Mode" as bold with keyboard shortcut
const actModePart = matchText.substring(actModeIndex)
children.push({
type: "strong",
children: [{ type: "text", value: `${actModePart} (⌘⇧A)` }],
})
} else {
// Fallback if we can't parse it correctly
children.push({ type: "text", value: matchText + " " })
children.push({
type: "strong",
children: [{ type: "text", value: `(⌘⇧A)` }],
})
}
}
})
// Replace the original text node with our new nodes
if (parent) {
parent.children.splice(index, 1, ...children)
}
})
}
}
/**
* Custom remark plugin that prevents filenames with extensions from being parsed as bold text
* For example: __init__.py should not be rendered as bold "init" followed by ".py"
@@ -286,6 +369,7 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
remarkPlugins: [
remarkPreventBoldFilenames,
remarkUrlToLink,
remarkHighlightActMode,
remarkMath,
() => {
return (tree) => {
@@ -329,6 +413,27 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
}
return <code {...props} />
},
strong: (props: ComponentProps<"strong">) => {
// Check if this is an "Act Mode" strong element by looking for the keyboard shortcut
// Handle both string children and array of children cases
const childrenText = React.Children.toArray(props.children)
.map((child) => {
if (typeof child === "string") return child
if (typeof child === "object" && "props" in child && child.props.children)
return String(child.props.children)
return ""
})
.join("")
// Case-insensitive check for "Act Mode (⌘⇧A)" pattern
// This ensures we only style the exact "Act Mode" mentions with keyboard shortcut
// Using case-insensitive flag to catch all capitalization variations
if (/^act mode\s*\(⌘⇧A\)$/i.test(childrenText)) {
return <ActModeHighlight />
}
return <strong {...props} />
},
},
},
})
+93
View File
@@ -0,0 +1,93 @@
import React, { HTMLAttributes, useCallback, forwardRef } from "react"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { cn } from "@/utils/cn"
type TabProps = HTMLAttributes<HTMLDivElement>
export const Tab = ({ className, children, ...props }: TabProps) => (
<div className={cn("fixed inset-0 flex flex-col", className)} {...props}>
{children}
</div>
)
export const TabHeader = ({ className, children, ...props }: TabProps) => (
<div className={cn("px-5 py-2.5 border-b border-[var(--vscode-panel-border)]", className)} {...props}>
{children}
</div>
)
export const TabContent = ({ className, children, ...props }: TabProps) => {
const onWheel = useCallback((e: React.WheelEvent<HTMLDivElement>) => {
const target = e.target as HTMLElement
// Prevent scrolling if the target is a listbox or option
// (e.g. selects, dropdowns, etc).
if (target.role === "listbox" || target.role === "option") {
return
}
e.currentTarget.scrollTop += e.deltaY
}, [])
return (
<div className={cn("flex-1 overflow-auto", className)} onWheel={onWheel} {...props}>
{children}
</div>
)
}
export const TabList = forwardRef<
HTMLDivElement,
HTMLAttributes<HTMLDivElement> & {
value: string
onValueChange: (value: string) => void
}
>(({ children, className, value, onValueChange, ...props }, ref) => {
const handleTabSelect = useCallback(
(tabValue: string) => {
console.log("Tab selected:", tabValue)
onValueChange(tabValue)
},
[onValueChange],
)
return (
<div ref={ref} role="tablist" className={cn("flex", className)} {...props}>
{React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
// Make sure we're passing the correct props to the TabTrigger
return React.cloneElement(child as React.ReactElement<any>, {
isSelected: child.props.value === value,
onSelect: () => handleTabSelect(child.props.value),
})
}
return child
})}
</div>
)
})
export const TabTrigger = forwardRef<
HTMLButtonElement,
React.ButtonHTMLAttributes<HTMLButtonElement> & {
value: string
isSelected?: boolean
onSelect?: () => void
}
>(({ children, className, value, isSelected, onSelect, ...props }, ref) => {
// Ensure we're using the value prop correctly
return (
<button
ref={ref}
role="tab"
aria-selected={isSelected}
tabIndex={isSelected ? 0 : -1}
className={cn("focus:outline-none", className)}
onClick={onSelect}
data-value={value} // Add data-value attribute for debugging
{...props}>
{children}
</button>
)
})
@@ -1,9 +1,12 @@
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { vscode } from "@/utils/vscode"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { UiServiceClient } from "@/services/grpc-client"
import ServersToggleList from "./ServersToggleList"
const InstalledServersView = () => {
const { mcpServers: servers } = useExtensionState()
const { mcpServers: servers, navigateToSettings } = useExtensionState()
return (
<div style={{ padding: "16px 20px" }}>
@@ -46,10 +49,17 @@ const InstalledServersView = () => {
<div style={{ textAlign: "center" }}>
<VSCodeLink
onClick={() => {
vscode.postMessage({
type: "openExtensionSettings",
text: "cline.mcp",
})
// First open the settings panel using direct navigation
navigateToSettings()
// After a short delay, send a message to scroll to browser settings
setTimeout(async () => {
try {
await UiServiceClient.scrollToSettings({ value: "features" })
} catch (error) {
console.error("Error scrolling to mcp settings:", error)
}
}, 300)
}}
style={{ fontSize: "12px" }}>
Advanced MCP Settings
@@ -47,6 +47,8 @@ import {
askSageDefaultURL,
xaiDefaultModelId,
xaiModels,
nebiusModels,
nebiusDefaultModelId,
sambanovaModels,
sambanovaDefaultModelId,
doubaoModels,
@@ -324,6 +326,7 @@ const ApiOptions = ({
<VSCodeOption value="lmstudio">LM Studio</VSCodeOption>
<VSCodeOption value="ollama">Ollama</VSCodeOption>
<VSCodeOption value="litellm">LiteLLM</VSCodeOption>
<VSCodeOption value="nebius">Nebius AI Studio</VSCodeOption>
<VSCodeOption value="asksage">AskSage</VSCodeOption>
<VSCodeOption value="xai">xAI</VSCodeOption>
<VSCodeOption value="sambanova">SambaNova</VSCodeOption>
@@ -1884,6 +1887,52 @@ const ApiOptions = ({
</div>
)}
{selectedProvider === "nebius" && (
<div>
<VSCodeTextField
value={apiConfiguration?.nebiusApiKey || ""}
style={{ width: "100%" }}
type="password"
onInput={handleInputChange("nebiusApiKey")}
placeholder="Enter API Key...">
<span style={{ fontWeight: 500 }}>Nebius API Key</span>
</VSCodeTextField>
<p
style={{
fontSize: "12px",
marginTop: 3,
color: "var(--vscode-descriptionForeground)",
}}>
This key is stored locally and only used to make API requests from this extension.{" "}
{!apiConfiguration?.nebiusApiKey && (
<VSCodeLink
href="https://studio.nebius.com/settings/api-keys"
style={{
display: "inline",
fontSize: "inherit",
}}>
You can get a Nebius API key by signing up here.{" "}
</VSCodeLink>
)}
<span style={{ color: "var(--vscode-errorForeground)" }}>
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex prompts and works best with Claude
models. Less capable models may not work as expected.)
</span>
</p>
</div>
)}
{apiErrorMessage && (
<p
style={{
margin: "-10px 0 4px 0",
fontSize: 12,
color: "var(--vscode-errorForeground)",
}}>
{apiErrorMessage}
</p>
)}
{selectedProvider === "xai" && (
<div>
<VSCodeTextField
@@ -1900,6 +1949,10 @@ const ApiOptions = ({
marginTop: 3,
color: "var(--vscode-descriptionForeground)",
}}>
<span style={{ color: "var(--vscode-errorForeground)" }}>
(<span style={{ fontWeight: 500 }}>Note:</span> Cline uses complex prompts and works best with Claude
models. Less capable models may not work as expected.)
</span>
This key is stored locally and only used to make API requests from this extension.
{!apiConfiguration?.xaiApiKey && (
<VSCodeLink href="https://x.ai" style={{ display: "inline", fontSize: "inherit" }}>
@@ -2072,9 +2125,13 @@ const ApiOptions = ({
{selectedProvider === "asksage" && createDropdown(askSageModels)}
{selectedProvider === "xai" && createDropdown(xaiModels)}
{selectedProvider === "sambanova" && createDropdown(sambanovaModels)}
{selectedProvider === "nebius" && createDropdown(nebiusModels)}
</DropdownContainer>
{((selectedProvider === "anthropic" && selectedModelId === "claude-3-7-sonnet-20250219") ||
{((selectedProvider === "anthropic" &&
(selectedModelId === "claude-3-7-sonnet-20250219" ||
selectedModelId === "claude-sonnet-4-20250514" ||
selectedModelId === "claude-opus-4-20250514")) ||
(selectedProvider === "vertex" && selectedModelId === "claude-3-7-sonnet@20250219")) && (
<ThinkingBudgetSlider apiConfiguration={apiConfiguration} setApiConfiguration={setApiConfiguration} />
)}
@@ -2488,6 +2545,8 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
}
case "xai":
return getProviderData(xaiModels, xaiDefaultModelId)
case "nebius":
return getProviderData(nebiusModels, nebiusDefaultModelId)
case "sambanova":
return getProviderData(sambanovaModels, sambanovaDefaultModelId)
default:
@@ -366,11 +366,7 @@ export const BrowserSettingsSection: React.FC = () => {
const isSubSettingsOpen = !(browserSettings.disableToolUse || false)
return (
<div
id="browser-settings-section"
style={{ marginBottom: 20, borderTop: "1px solid var(--vscode-panel-border)", paddingTop: 15 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 10px 0", fontSize: "14px" }}>Browser Settings</h3>
<div id="browser-settings-section" style={{ marginBottom: 20 }}>
{/* Master Toggle */}
<div style={{ marginBottom: isSubSettingsOpen ? 0 : 10 }}>
<VSCodeCheckbox
@@ -14,8 +14,7 @@ const FeatureSettingsSection = () => {
} = useExtensionState()
return (
<div style={{ marginBottom: 20, borderTop: "1px solid var(--vscode-panel-border)", paddingTop: 15 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 10px 0", fontSize: "14px" }}>Feature Settings</h3>
<div style={{ marginBottom: 20 }}>
<div>
<VSCodeCheckbox
checked={enableCheckpointsSetting}
@@ -9,7 +9,7 @@ interface PreferredLanguageSettingProps {
const PreferredLanguageSetting: React.FC<PreferredLanguageSettingProps> = ({ chatSettings, setChatSettings }) => {
return (
<div style={{ marginTop: 10, marginBottom: 10 }}>
<div style={{}}>
<label htmlFor="preferred-language-dropdown" className="block mb-1 text-sm font-medium">
Preferred Language
</label>
@@ -0,0 +1,10 @@
import { HTMLAttributes } from "react"
import { cn } from "@/utils/cn"
type SectionProps = HTMLAttributes<HTMLDivElement>
export const Section = ({ className, ...props }: SectionProps) => (
<div className={cn("flex flex-col gap-3 p-5 py-2", className)} {...props} />
)
export default Section
@@ -0,0 +1,25 @@
import { HTMLAttributes } from "react"
import { cn } from "@/utils/cn"
import { OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker"
type SectionHeaderProps = HTMLAttributes<HTMLDivElement> & {
children: React.ReactNode
description?: string
}
export const SectionHeader = ({ description, children, className, ...props }: SectionHeaderProps) => {
return (
<div
className={cn(
`sticky top-0 z-${OPENROUTER_MODEL_PICKER_Z_INDEX + 20} text-[var(--vscode-foreground)] bg-[var(--vscode-panel-background)] px-5 py-3`,
className,
)}
{...props}>
<h4 className="m-0">{children}</h4>
{description && <p className="text-[var(--vscode-descriptionForeground)] text-sm mt-2 mb-0">{description}</p>}
</div>
)
}
export default SectionHeader
@@ -6,7 +6,24 @@ import {
VSCodeOption,
VSCodeTextArea,
} from "@vscode/webview-ui-toolkit/react"
import { memo, useCallback, useEffect, useState } from "react"
import { memo, useCallback, useEffect, useState, useRef } from "react"
import {
Settings,
Webhook,
CheckCheck,
SquareMousePointer,
GitBranch,
Bell,
Database,
SquareTerminal,
FlaskConical,
Globe,
Info,
LucideIcon,
} from "lucide-react"
import HeroTooltip from "@/components/common/HeroTooltip"
import SectionHeader from "./SectionHeader"
import Section from "./Section"
import PreferredLanguageSetting from "./PreferredLanguageSetting" // Added import
import { OpenAIReasoningEffort } from "@shared/ChatSettings"
import { useExtensionState } from "@/context/ExtensionStateContext"
@@ -22,13 +39,92 @@ import FeatureSettingsSection from "./FeatureSettingsSection"
import BrowserSettingsSection from "./BrowserSettingsSection"
import TerminalSettingsSection from "./TerminalSettingsSection"
import { FEATURE_FLAGS } from "@shared/services/feature-flags/feature-flags"
import { Tab, TabContent, TabHeader, TabList, TabTrigger } from "../common/Tab"
import { cn } from "@/utils/cn"
import { PlanActMode } from "@shared/proto/state"
const { IS_DEV } = process.env
// Styles for the tab system
const settingsTabsContainer = "flex flex-1 overflow-hidden [&.narrow_.tab-label]:hidden"
const settingsTabList =
"w-48 data-[compact=true]:w-12 flex-shrink-0 flex flex-col overflow-y-auto overflow-x-hidden border-r border-[var(--vscode-sideBar-background)]"
const settingsTabTrigger =
"whitespace-nowrap overflow-hidden min-w-0 h-12 px-4 py-3 box-border flex items-center border-l-2 border-transparent text-[var(--vscode-foreground)] opacity-70 bg-transparent hover:bg-[var(--vscode-list-hoverBackground)] data-[compact=true]:w-12 data-[compact=true]:p-4 cursor-pointer"
const settingsTabTriggerActive =
"opacity-100 border-l-2 border-l-[var(--vscode-focusBorder)] border-t-0 border-r-0 border-b-0 bg-[var(--vscode-list-activeSelectionBackground)]"
// Tab definitions
interface SettingsTab {
id: string
name: string
tooltipText: string
headerText: string
icon: LucideIcon
}
export const SETTINGS_TABS: SettingsTab[] = [
{
id: "api-config",
name: "API Configuration",
tooltipText: "API Configuration",
headerText: "API Configuration",
icon: Webhook,
},
{
id: "general",
name: "General",
tooltipText: "General Settings",
headerText: "General Settings",
icon: Settings,
},
{
id: "features",
name: "Features",
tooltipText: "Feature Settings",
headerText: "Feature Settings",
icon: CheckCheck,
},
{
id: "browser",
name: "Browser",
tooltipText: "Browser Settings",
headerText: "Browser Settings",
icon: SquareMousePointer,
},
{
id: "terminal",
name: "Terminal",
tooltipText: "Terminal Settings",
headerText: "Terminal Settings",
icon: SquareTerminal,
},
// Only show in dev mode
...(IS_DEV
? [
{
id: "debug",
name: "Debug",
tooltipText: "Debug Tools",
headerText: "Debug",
icon: FlaskConical,
},
]
: []),
{
id: "about",
name: "About",
tooltipText: "About Cline",
headerText: "About",
icon: Info,
},
]
type SettingsViewProps = {
onDone: () => void
targetSection?: string
}
const SettingsView = ({ onDone }: SettingsViewProps) => {
const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
const {
apiConfiguration,
version,
@@ -115,10 +211,9 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
switch (message.type) {
case "didUpdateSettings":
if (pendingTabChange) {
vscode.postMessage({
type: "togglePlanActMode",
StateServiceClient.togglePlanActMode({
chatSettings: {
mode: pendingTabChange,
mode: pendingTabChange === "plan" ? PlanActMode.PLAN : PlanActMode.ACT,
preferredLanguage: chatSettings.preferredLanguage,
openAIReasoningEffort: chatSettings.openAIReasoningEffort,
},
@@ -126,24 +221,35 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
setPendingTabChange(null)
}
break
// Handle tab navigation through targetSection prop instead
case "grpc_response":
if (message.grpc_response?.message?.action === "scrollToSettings") {
setTimeout(() => {
const elementId = message.grpc_response?.message?.value
if (elementId) {
const element = document.getElementById(elementId)
if (element) {
element.scrollIntoView({ behavior: "smooth" })
const tabId = message.grpc_response?.message?.value
if (tabId) {
console.log("Opening settings tab from GRPC response:", tabId)
// Check if the value corresponds to a valid tab ID
const isValidTabId = SETTINGS_TABS.some((tab) => tab.id === tabId)
element.style.transition = "background-color 0.5s ease"
element.style.backgroundColor = "var(--vscode-textPreformat-background)"
if (isValidTabId) {
// Set the active tab directly
setActiveTab(tabId)
} else {
// Fall back to the old behavior of scrolling to an element
setTimeout(() => {
const element = document.getElementById(tabId)
if (element) {
element.scrollIntoView({ behavior: "smooth" })
setTimeout(() => {
element.style.backgroundColor = "transparent"
}, 1200)
}
element.style.transition = "background-color 0.5s ease"
element.style.backgroundColor = "var(--vscode-textPreformat-background)"
setTimeout(() => {
element.style.backgroundColor = "transparent"
}, 1200)
}
}, 300)
}
}, 300)
}
}
break
}
@@ -161,7 +267,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
}
}
const handleTabChange = (tab: "plan" | "act") => {
const handlePlanActModeChange = (tab: "plan" | "act") => {
if (tab === chatSettings.mode) {
return
}
@@ -169,136 +275,325 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
handleSubmit(true)
}
// Track active tab
const [activeTab, setActiveTab] = useState<string>(targetSection || SETTINGS_TABS[0].id)
// Update active tab when targetSection changes
useEffect(() => {
if (targetSection) {
setActiveTab(targetSection)
}
}, [targetSection])
// Enhanced tab change handler with debugging
const handleTabChange = useCallback(
(tabId: string) => {
console.log("Tab change requested:", tabId, "Current:", activeTab)
setActiveTab(tabId)
},
[activeTab],
)
// Debug tab changes
useEffect(() => {
console.log("Active tab changed to:", activeTab)
}, [activeTab])
// Track whether we're in compact mode
const [isCompactMode, setIsCompactMode] = useState(false)
const containerRef = useRef<HTMLDivElement>(null)
// Setup resize observer to detect when we should switch to compact mode
useEffect(() => {
if (!containerRef.current) return
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
// If container width is less than 500px, switch to compact mode
setIsCompactMode(entry.contentRect.width < 500)
}
})
observer.observe(containerRef.current)
return () => {
observer?.disconnect()
}
}, [])
return (
<div className="fixed top-0 left-0 right-0 bottom-0 pt-[10px] pr-0 pb-0 pl-5 flex flex-col overflow-hidden">
<div className="flex justify-between items-center mb-[13px] pr-[17px]">
<h3 className="text-[var(--vscode-foreground)] m-0">Settings</h3>
<VSCodeButton onClick={() => handleSubmit(false)}>Save</VSCodeButton>
<Tab>
<TabHeader className="flex justify-between items-center gap-2">
<div className="flex items-center gap-1">
<h3 className="text-[var(--vscode-foreground)] m-0">Settings</h3>
</div>
<div className="flex gap-2">
<VSCodeButton onClick={() => handleSubmit(false)}>Save</VSCodeButton>
</div>
</TabHeader>
{/* Vertical tabs layout */}
<div ref={containerRef} className={cn(settingsTabsContainer, isCompactMode && "narrow")}>
{/* Tab sidebar */}
<TabList
value={activeTab}
onValueChange={handleTabChange}
className={cn(settingsTabList)}
data-compact={isCompactMode}>
{SETTINGS_TABS.map((tab) =>
isCompactMode ? (
<HeroTooltip key={tab.id} content={tab.tooltipText} placement="right">
<div
className={cn(
activeTab === tab.id
? `${settingsTabTrigger} ${settingsTabTriggerActive}`
: settingsTabTrigger,
"focus:ring-0",
)}
data-compact={isCompactMode}
data-testid={`tab-${tab.id}`}
data-value={tab.id}
onClick={() => {
console.log("Compact tab clicked:", tab.id)
handleTabChange(tab.id)
}}>
<div className={cn("flex items-center gap-2", isCompactMode && "justify-center")}>
<tab.icon className="w-4 h-4" />
<span className="tab-label">{tab.name}</span>
</div>
</div>
</HeroTooltip>
) : (
<TabTrigger
key={tab.id}
value={tab.id}
className={cn(
activeTab === tab.id
? `${settingsTabTrigger} ${settingsTabTriggerActive}`
: settingsTabTrigger,
"focus:ring-0",
)}
data-compact={isCompactMode}
data-testid={`tab-${tab.id}`}>
<div className={cn("flex items-center gap-2", isCompactMode && "justify-center")}>
<tab.icon className="w-4 h-4" />
<span className="tab-label">{tab.name}</span>
</div>
</TabTrigger>
),
)}
</TabList>
{/* Helper function to render section header */}
{(() => {
const renderSectionHeader = (tabId: string) => {
const tab = SETTINGS_TABS.find((t) => t.id === tabId)
if (!tab) return null
return (
<SectionHeader>
<div className="flex items-center gap-2">
{(() => {
const Icon = tab.icon
return <Icon className="w-4" />
})()}
<div>{tab.headerText}</div>
</div>
</SectionHeader>
)
}
return (
<TabContent className="flex-1 overflow-auto">
{/* API Configuration Tab */}
{activeTab === "api-config" && (
<div>
{renderSectionHeader("api-config")}
<Section>
{/* Tabs container */}
{planActSeparateModelsSetting ? (
<div className="rounded-md mb-5 bg-[var(--vscode-panel-background)]">
<div className="flex gap-[1px] mb-[10px] -mt-2 border-0 border-b border-solid border-[var(--vscode-panel-border)]">
<TabButton
isActive={chatSettings.mode === "plan"}
onClick={() => handlePlanActModeChange("plan")}>
Plan Mode
</TabButton>
<TabButton
isActive={chatSettings.mode === "act"}
onClick={() => handlePlanActModeChange("act")}>
Act Mode
</TabButton>
</div>
{/* Content container */}
<div className="-mb-3">
<ApiOptions
key={chatSettings.mode}
showModelOptions={true}
apiErrorMessage={apiErrorMessage}
modelIdErrorMessage={modelIdErrorMessage}
/>
</div>
</div>
) : (
<ApiOptions
key={"single"}
showModelOptions={true}
apiErrorMessage={apiErrorMessage}
modelIdErrorMessage={modelIdErrorMessage}
/>
)}
<div className="mb-[5px]">
<VSCodeCheckbox
className="mb-[5px]"
checked={planActSeparateModelsSetting}
onChange={(e: any) => {
const checked = e.target.checked === true
setPlanActSeparateModelsSetting(checked)
}}>
Use different models for Plan and Act modes
</VSCodeCheckbox>
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
Switching between Plan and Act mode will persist the API and model used in the
previous mode. This may be helpful e.g. when using a strong reasoning model to
architect a plan for a cheaper coding model to act on.
</p>
</div>
<div className="mb-[5px]">
<VSCodeTextArea
value={customInstructions ?? ""}
className="w-full"
resize="vertical"
rows={4}
placeholder={
'e.g. "Run unit tests at the end", "Use TypeScript with async/await", "Speak in Spanish"'
}
onInput={(e: any) => setCustomInstructions(e.target?.value ?? "")}>
<span className="font-medium">Custom Instructions</span>
</VSCodeTextArea>
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
These instructions are added to the end of the system prompt sent with every
request.
</p>
</div>
</Section>
</div>
)}
{/* General Settings Tab */}
{activeTab === "general" && (
<div>
{renderSectionHeader("general")}
<Section>
{chatSettings && (
<PreferredLanguageSetting
chatSettings={chatSettings}
setChatSettings={setChatSettings}
/>
)}
<div className="mb-[5px]">
<VSCodeCheckbox
className="mb-[5px]"
checked={telemetrySetting === "enabled"}
onChange={(e: any) => {
const checked = e.target.checked === true
setTelemetrySetting(checked ? "enabled" : "disabled")
}}>
Allow anonymous error and usage reporting
</VSCodeCheckbox>
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
Help improve Cline by sending anonymous usage data and error reports. No code,
prompts, or personal information are ever sent. See our{" "}
<VSCodeLink
href="https://docs.cline.bot/more-info/telemetry"
className="text-inherit">
telemetry overview
</VSCodeLink>{" "}
and{" "}
<VSCodeLink href="https://cline.bot/privacy" className="text-inherit">
privacy policy
</VSCodeLink>{" "}
for more details.
</p>
</div>
</Section>
</div>
)}
{/* Feature Settings Tab */}
{activeTab === "features" && (
<div>
{renderSectionHeader("features")}
<Section>
<FeatureSettingsSection />
</Section>
</div>
)}
{/* Browser Settings Tab */}
{activeTab === "browser" && (
<div>
{renderSectionHeader("browser")}
<Section>
<BrowserSettingsSection />
</Section>
</div>
)}
{/* Terminal Settings Tab */}
{activeTab === "terminal" && (
<div>
{renderSectionHeader("terminal")}
<Section>
<TerminalSettingsSection />
</Section>
</div>
)}
{/* Debug Tab (only in dev mode) */}
{IS_DEV && activeTab === "debug" && (
<div>
{renderSectionHeader("debug")}
<Section>
<VSCodeButton
onClick={handleResetState}
className="mt-[5px] w-auto"
style={{ backgroundColor: "var(--vscode-errorForeground)", color: "black" }}>
Reset State
</VSCodeButton>
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
This will reset all global state and secret storage in the extension.
</p>
</Section>
</div>
)}
{/* About Tab */}
{activeTab === "about" && (
<div>
{renderSectionHeader("about")}
<Section>
<div className="text-center text-[var(--vscode-descriptionForeground)] text-xs leading-[1.2] px-0 py-0 pr-2 pb-[15px] mt-auto">
<p className="break-words m-0 p-0">
If you have any questions or feedback, feel free to open an issue at{" "}
<VSCodeLink href="https://github.com/cline/cline" className="inline">
https://github.com/cline/cline
</VSCodeLink>
</p>
<p className="italic mt-[10px] mb-0 p-0">v{version}</p>
</div>
</Section>
</div>
)}
</TabContent>
)
})()}
</div>
<div className="grow overflow-y-scroll pr-2 flex flex-col">
{/* Tabs container */}
{planActSeparateModelsSetting ? (
<div className="border border-solid border-[var(--vscode-panel-border)] rounded-md p-[10px] mb-5 bg-[var(--vscode-panel-background)]">
<div className="flex gap-[1px] mb-[10px] -mt-2 border-0 border-b border-solid border-[var(--vscode-panel-border)]">
<TabButton isActive={chatSettings.mode === "plan"} onClick={() => handleTabChange("plan")}>
Plan Mode
</TabButton>
<TabButton isActive={chatSettings.mode === "act"} onClick={() => handleTabChange("act")}>
Act Mode
</TabButton>
</div>
{/* Content container */}
<div className="-mb-3">
<ApiOptions
key={chatSettings.mode}
showModelOptions={true}
apiErrorMessage={apiErrorMessage}
modelIdErrorMessage={modelIdErrorMessage}
/>
</div>
</div>
) : (
<ApiOptions
key={"single"}
showModelOptions={true}
apiErrorMessage={apiErrorMessage}
modelIdErrorMessage={modelIdErrorMessage}
/>
)}
<div className="mb-[5px]">
<VSCodeTextArea
value={customInstructions ?? ""}
className="w-full"
resize="vertical"
rows={4}
placeholder={'e.g. "Run unit tests at the end", "Use TypeScript with async/await", "Speak in Spanish"'}
onInput={(e: any) => setCustomInstructions(e.target?.value ?? "")}>
<span className="font-medium">Custom Instructions</span>
</VSCodeTextArea>
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
These instructions are added to the end of the system prompt sent with every request.
</p>
</div>
{chatSettings && <PreferredLanguageSetting chatSettings={chatSettings} setChatSettings={setChatSettings} />}
<div className="mb-[5px]">
<VSCodeCheckbox
className="mb-[5px]"
checked={planActSeparateModelsSetting}
onChange={(e: any) => {
const checked = e.target.checked === true
setPlanActSeparateModelsSetting(checked)
}}>
Use different models for Plan and Act modes
</VSCodeCheckbox>
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
Switching between Plan and Act mode will persist the API and model used in the previous mode. This may be
helpful e.g. when using a strong reasoning model to architect a plan for a cheaper coding model to act on.
</p>
</div>
<div className="mb-[5px]">
<VSCodeCheckbox
className="mb-[5px]"
checked={telemetrySetting === "enabled"}
onChange={(e: any) => {
const checked = e.target.checked === true
setTelemetrySetting(checked ? "enabled" : "disabled")
}}>
Allow anonymous error and usage reporting
</VSCodeCheckbox>
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
Help improve Cline by sending anonymous usage data and error reports. No code, prompts, or personal
information are ever sent. See our{" "}
<VSCodeLink href="https://docs.cline.bot/more-info/telemetry" className="text-inherit">
telemetry overview
</VSCodeLink>{" "}
and{" "}
<VSCodeLink href="https://cline.bot/privacy" className="text-inherit">
privacy policy
</VSCodeLink>{" "}
for more details.
</p>
</div>
{/* Feature Settings Section */}
<FeatureSettingsSection />
{/* Browser Settings Section */}
<BrowserSettingsSection />
{/* Terminal Settings Section */}
<TerminalSettingsSection />
{IS_DEV && (
<>
<div className="mt-[10px] mb-1">Debug</div>
<VSCodeButton
onClick={handleResetState}
className="mt-[5px] w-auto"
style={{ backgroundColor: "var(--vscode-errorForeground)", color: "black" }}>
Reset State
</VSCodeButton>
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
This will reset all global state and secret storage in the extension.
</p>
</>
)}
<div className="text-center text-[var(--vscode-descriptionForeground)] text-xs leading-[1.2] px-0 py-0 pr-2 pb-[15px] mt-auto">
<p className="break-words m-0 p-0">
If you have any questions or feedback, feel free to open an issue at{" "}
<VSCodeLink href="https://github.com/cline/cline" className="inline">
https://github.com/cline/cline
</VSCodeLink>
</p>
<p className="italic mt-[10px] mb-0 p-0">v{version}</p>
</div>
</div>
</div>
</Tab>
)
}
@@ -49,10 +49,7 @@ export const TerminalSettingsSection: React.FC = () => {
}
return (
<div
id="terminal-settings-section"
style={{ marginBottom: 20, borderTop: "1px solid var(--vscode-panel-border)", paddingTop: 15 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 10px 0", fontSize: "14px" }}>Terminal Settings</h3>
<div id="terminal-settings-section" style={{ marginBottom: 20 }}>
<div style={{ marginBottom: 15 }}>
<div style={{ marginBottom: 8 }}>
<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>
@@ -200,3 +200,39 @@ describe("OpenApiInfoOptions", () => {
expect(modelInput).toBeInTheDocument()
})
})
describe("ApiOptions Component", () => {
vi.clearAllMocks()
const mockPostMessage = vi.fn()
beforeEach(() => {
//@ts-expect-error - vscode is not defined in the global namespace in test environment
global.vscode = { postMessage: mockPostMessage }
mockExtensionState({
apiProvider: "nebius",
nebiusApiKey: "",
})
})
it("renders Nebius API Key input", () => {
render(
<ExtensionStateContextProvider>
<ApiOptions showModelOptions={true} />
</ExtensionStateContextProvider>,
)
const apiKeyInput = screen.getByPlaceholderText("Enter API Key...")
expect(apiKeyInput).toBeInTheDocument()
})
it("renders Nebius Model ID select with a default model", () => {
render(
<ExtensionStateContextProvider>
<ApiOptions showModelOptions={true} />
</ExtensionStateContextProvider>,
)
const modelIdSelect = screen.getByLabelText("Model")
expect(modelIdSelect).toBeInTheDocument()
expect(modelIdSelect).toHaveValue("Qwen/Qwen2.5-32B-Instruct-fast")
})
})
@@ -39,7 +39,7 @@ const WelcomeView = memo(() => {
<p>
I can do all kinds of tasks thanks to breakthroughs in{" "}
<VSCodeLink href="https://www.anthropic.com/claude/sonnet" className="inline">
Claude 3.7 Sonnet's
Claude 4 Sonnet's
</VSCodeLink>
agentic coding capabilities and access to tools that let me create & edit files, explore complex projects, use
a browser, and execute terminal commands <i>(with your permission, of course)</i>. I can even use MCP to
@@ -47,8 +47,8 @@ const WelcomeView = memo(() => {
</p>
<p className="text-[var(--vscode-descriptionForeground)]">
Sign up for an account to get started for free, or use an API key that provides access to models like Claude
3.7 Sonnet.
Sign up for an account to get started for free, or use an API key that provides access to models like Claude 4
Sonnet.
</p>
<VSCodeButton appearance="primary" onClick={handleLogin} className="w-full mt-1">
@@ -246,6 +246,7 @@ export const ExtensionStateContextProvider: React.FC<{
config.asksageApiKey,
config.xaiApiKey,
config.sambanovaApiKey,
config.nebiusApiKey,
].some((key) => key !== undefined)
: false
+36 -54
View File
@@ -1,7 +1,7 @@
import { useCallback } from "react"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { vscode } from "@/utils/vscode"
import { AutoApprovalSettings } from "@shared/AutoApprovalSettings"
import { updateAutoApproveSettings } from "@/components/chat/auto-approve-menu/AutoApproveSettingsAPI"
import { ActionMetadata } from "@/components/chat/auto-approve-menu/types"
export function useAutoApproveActions() {
@@ -35,7 +35,7 @@ export function useAutoApproveActions() {
// Toggle favorite status
const toggleFavorite = useCallback(
(actionId: string) => {
async (actionId: string) => {
const currentFavorites = autoApprovalSettings.favorites || []
let newFavorites: string[]
@@ -45,13 +45,10 @@ export function useAutoApproveActions() {
newFavorites = [...currentFavorites, actionId]
}
vscode.postMessage({
type: "autoApprovalSettings",
autoApprovalSettings: {
...autoApprovalSettings,
version: (autoApprovalSettings.version ?? 1) + 1,
favorites: newFavorites,
},
await updateAutoApproveSettings({
...autoApprovalSettings,
version: (autoApprovalSettings.version ?? 1) + 1,
favorites: newFavorites,
})
},
[autoApprovalSettings],
@@ -59,22 +56,22 @@ export function useAutoApproveActions() {
// Update action state
const updateAction = useCallback(
(action: ActionMetadata, value: boolean) => {
async (action: ActionMetadata, value: boolean) => {
const actionId = action.id
const subActionId = action.subAction?.id
if (actionId === "enableAutoApprove") {
updateAutoApproveEnabled(value)
await updateAutoApproveEnabled(value)
return
}
if (actionId === "enableAll" || subActionId === "enableAll") {
toggleAll(action, value)
await toggleAll(action, value)
return
}
if (actionId === "enableNotifications" || subActionId === "enableNotifications") {
updateNotifications(action, value)
await updateNotifications(action, value)
return
}
@@ -95,14 +92,11 @@ export function useAutoApproveActions() {
// Check if this will result in any enabled actions
const willHaveEnabledActions = Object.values(newActions).some(Boolean)
vscode.postMessage({
type: "autoApprovalSettings",
autoApprovalSettings: {
...autoApprovalSettings,
version: (autoApprovalSettings.version ?? 1) + 1,
actions: newActions,
enabled: willHaveEnabledActions,
},
await updateAutoApproveSettings({
...autoApprovalSettings,
version: (autoApprovalSettings.version ?? 1) + 1,
actions: newActions,
enabled: willHaveEnabledActions,
})
},
[autoApprovalSettings],
@@ -110,14 +104,11 @@ export function useAutoApproveActions() {
// Update max requests
const updateMaxRequests = useCallback(
(maxRequests: number) => {
vscode.postMessage({
type: "autoApprovalSettings",
autoApprovalSettings: {
...autoApprovalSettings,
version: (autoApprovalSettings.version ?? 1) + 1,
maxRequests,
},
async (maxRequests: number) => {
await updateAutoApproveSettings({
...autoApprovalSettings,
version: (autoApprovalSettings.version ?? 1) + 1,
maxRequests,
})
},
[autoApprovalSettings],
@@ -125,14 +116,11 @@ export function useAutoApproveActions() {
// Update auto-approve enabled state
const updateAutoApproveEnabled = useCallback(
(checked: boolean) => {
vscode.postMessage({
type: "autoApprovalSettings",
autoApprovalSettings: {
...autoApprovalSettings,
version: (autoApprovalSettings.version ?? 1) + 1,
enabled: checked,
},
async (checked: boolean) => {
await updateAutoApproveSettings({
...autoApprovalSettings,
version: (autoApprovalSettings.version ?? 1) + 1,
enabled: checked,
})
},
[autoApprovalSettings],
@@ -140,21 +128,18 @@ export function useAutoApproveActions() {
// Toggle all actions
const toggleAll = useCallback(
(action: ActionMetadata, checked: boolean) => {
async (action: ActionMetadata, checked: boolean) => {
let actions = { ...autoApprovalSettings.actions }
for (const action of Object.keys(actions)) {
actions[action as keyof AutoApprovalSettings["actions"]] = checked
}
vscode.postMessage({
type: "autoApprovalSettings",
autoApprovalSettings: {
...autoApprovalSettings,
version: (autoApprovalSettings.version ?? 1) + 1,
actions,
enabled: checked,
},
await updateAutoApproveSettings({
...autoApprovalSettings,
version: (autoApprovalSettings.version ?? 1) + 1,
actions,
enabled: checked,
})
},
[autoApprovalSettings],
@@ -162,15 +147,12 @@ export function useAutoApproveActions() {
// Update notifications setting
const updateNotifications = useCallback(
(action: ActionMetadata, checked: boolean) => {
async (action: ActionMetadata, checked: boolean) => {
if (action.id === "enableNotifications") {
vscode.postMessage({
type: "autoApprovalSettings",
autoApprovalSettings: {
...autoApprovalSettings,
version: (autoApprovalSettings.version ?? 1) + 1,
enableNotifications: checked,
},
await updateAutoApproveSettings({
...autoApprovalSettings,
version: (autoApprovalSettings.version ?? 1) + 1,
enableNotifications: checked,
})
}
},
+13
View File
@@ -0,0 +1,13 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
/**
* A utility function that combines clsx and tailwind-merge to handle class name merging
* with proper Tailwind CSS conflict resolution.
*
* @param inputs - Class values to be merged
* @returns A string of merged class names
*/
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
+5
View File
@@ -98,6 +98,11 @@ export function validateApiConfiguration(apiConfiguration?: ApiConfiguration): s
return "You must provide a valid model selector."
}
break
case "nebius":
if (!apiConfiguration.nebiusApiKey) {
return "You must provide a valid API key or choose a different provider."
}
break
case "asksage":
if (!apiConfiguration.asksageApiKey) {
return "You must provide a valid API key or choose a different provider."