Compare commits

...
Author SHA1 Message Date
0xtoshii 53fdd5c7de adding new tool handling and tool spec for webfetch 2025-11-16 13:22:28 -08:00
canvrno 535b653228 Added stronger prompting around the use of act_mode_respond (#7448) 2025-11-14 12:12:34 -08:00
Igor Tceglevskii 1335fa5452 Retire firebase (#7362) 2025-11-14 09:29:47 -08:00
yuvalman b2a4395f71 feat: upgrade sap ai-sdk-js packages major version (#6547)
* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version

* feat: upgrade sap-ai-sdk major version
2025-11-14 09:11:03 -08:00
Toshii ae34a3a8c5 adding state variable for clineWebToolsEnabled (noop) (#7455)
* adding state variable for clineWebToolsEnabled

* removing console log
2025-11-14 08:20:01 -08:00
0fb4a6c7e9 v3.37.1 Release Notes (#7451)
* changeset version bump

* Updating CHANGELOG.md format

* Update changelog for version 3.37.1

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2025-11-13 17:42:42 -08:00
Saoud Rizwan 855db7d8d8 feat(models): Add free minimax/mimax-m2 model to the model picker (#7453)
* feat(models): Add free minimax/mimax-m2 model to the model picker

* Add free minimax/mimax-m2 model to model picker
2025-11-13 17:41:16 -08:00
CandiedUniverse bb375b78ca fix(hooks): Prevent PreToolUse hook from running before attempt_completion tool (#7450) 2025-11-13 16:14:13 -08:00
CandiedUniverse 31af254f0a fix(hooks): Run PreToolUse only after approval (#7446)
* fix(hooks): Run PreToolUse only after approval; get it working for read_file first

* fix(hooks): Run PreToolUse only after approval; get it working for six more tools now

* fix(hooks): Run PreToolUse only after approval; get it working for the remaining six tools now

* fix(hooks): Implement HookExecution type to avoid using 'any'
2025-11-13 15:41:57 -08:00
github-actions[bot]andArafatkatze 2745cdd54b Changeset version bump (#7358)
* v3.37.0 Release Notes

- GPT-5.1 with model-specific prompting: tailored system prompts, tool usage, focus chain, and deep-planning optimizations
- Nous Research provider with Hermes 4 model family and custom system prompts
- Model-family breakouts for deep-planning prompting, laying groundwork for enhanced slash commands
- Expanded HTTP proxy support throughout the codebase
- Improved focus chain prompting for frontier models (Anthropic, OpenAI, Gemini, xAI)

- Duplicate tool results prevention through existence checking
- XML entity escaping in model content processor
- Commit message generation in command palette
- OpenAI Compatible provider temperature parameter type conversion

- Added missing proto generation step in CONTRIBUTING.md
- New `npm run dev` script for streamlined terminal workflow (fixes #7335)

* Adding image optimizations

* Adding image optimizations

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Arafatkatze <arafat.da.khan@gmail.com>
2025-11-13 11:47:42 -08:00
celestial-vault c94c6fd8f1 Fix CLI auth state persistence with explicit flush mechanism (#7445)
Fixes issue where 'cline auth' command would lose all configuration due to process termination race condition.

Root cause: StateManager uses 500ms debounced persistence, but CLI process terminated before setTimeout callback could fire.

Solution: Implemented explicit flushPendingState() mechanism:
- Added StateManager.flushPendingState() method for immediate persistence
- Refactored to extract shared persistence logic (DRY)
- Added flushPendingState gRPC endpoint
- CLI now calls flush instead of using 5s sleep workaround

Results:
- Deterministic persistence (no race condition)
- Faster auth flow (~2s vs 7s)
- Cleaner, more maintainable code
2025-11-13 11:34:17 -08:00
Ara a87664318e Adding Gpt-5.1 to model picker and onboarding list (#7444)
* feat(settings): add GPT-5.1 to Model picker featured models

* adding

* adding

* adding
2025-11-13 11:20:08 -08:00
AraandJuan Pablo ba70718c83 Remove Qwen3 coder from Cerebras providers (#7404)
* Remove Qwen3 coder from Cerebras providers

* Update Cerebras Docs to include new model `zai-glm-4.6` and remove `qwen-3-coder-480b-free` and `qwen-3-coder-480b` entries.

---------

Co-authored-by: Juan Pablo <juan@cline.bot>
2025-11-13 11:13:25 -08:00
canvrno cf8dd1c150 Feat:Enhanced support for OpenAI GPT 5.1 (#7443)
* Comprehensive changes to better support GPT 5.1 - System prompt, tools, deep-planning, focus chain, etc.

* Fix typos

* Fix more typos
2025-11-13 11:06:23 -08:00
Bee e714c74ec4 fix(anthropic): sanitize messages to SDK MessageParam shape (#7441)
- Use Anthropic SDK types and return Array<Messages.MessageParam>
- Replace removeReasoningDetails with removeUnknownParams
- Strip unsupported fields (e.g., reasoning_details) and normalize role
- Keep string content unchanged; map array content to known blocks
- Continue adding ephemeral cache_control to targeted user messages

This ensures only Anthropic-supported fields are sent, improving type safety and preventing API errors.
2025-11-13 10:28:40 -08:00
Sarah Fortune a6f8b33895 Change the label in the webview to say Enterprise Rules/Workflows instead of Remote Rules/Workflows (#7440) 2025-11-13 10:15:27 -08:00
Saoud RizwanandCopilot 02abbcf045 feat: add AGENTS.md support (#7437)
* feat: add AGENTS.md support

* Update webview-ui/src/components/cline-rules/RuleRow.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update font size for documentation link in ClineRulesToggleModal component

* docs: add support for AGENTS.md standard in Cline rules documentation

* fix: delete agents.md

* Add AGENTS.md support

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 09:42:24 -08:00
Bee 8ca2706cca chore: cleanup leftover code from anthropic provider (#7434)
Follow up from https://github.com/cline/cline/pull/7399

- Only add the redacted reasoning block when a reasoningSignature is present,
  instead of checking message length. This aligns with Claude extended thinking
  requirements and prevents unsignatured reasoning content from being sent.
- Remove leftover thinkingDeltaAccumulator from the Anthropic provider.
2025-11-12 18:33:13 -08:00
Bee e747d211e6 refactor: standardize reasoning yield types across providers [ENG-1226] (#7399)
* refactor(api): standardize reasoning yield types across providers

- Unify reasoning output format in Anthropic, Cline, and Minimax handlers
- Change "ant_thinking" and "reasoning_details" to "reasoning" type
- Add signature and redacted_data properties for consistency
- Wrap message_start cases in braces for scoping
- Consolidate yields to reduce redundancy and improve maintainability

* VercelAIGatewayHandler

* feat: add model information tracking to tasks and messages

Add modelId field to TaskResponse and TaskItem proto messages, and introduce ClineModelInfo message type to track provider and model IDs throughout the system. Update API transform functions to use ClineStorageMessage types and refactor message handling to support model information tracking.

This enables better tracking and auditing of which AI models are used for specific tasks and messages, improving observability and allowing for model-specific analytics.

* clean up protos

* remove console log

* minimax

* use new interface

* clean up
2025-11-12 15:33:48 -08:00
Bee 19aa81a1cc fix(dev): add back CLINE_ENVIRONMENT_OVERRIDE support (#7427)
CLINE_ENVIRONMENT_OVERRIDE was removed in https://github.com/cline/cline/pull/6621 accidentally. This PR adds it back:
Prioritize CLINE_ENVIRONMENT_OVERRIDE over CLINE_ENVIRONMENT when
setting the runtime environment at module load.
This enables temporary/test-specific environment selection without
modifying the base env var. Existing behavior is unchanged when the
override is not set. Updated inline comment for clarity.
2025-11-12 14:58:53 -08:00
Beeandellipsis-dev[bot] f5f3654966 refactor: Extract model info into method in WriteToFileToolHandler (#7420)
* refactor: Extract provider and model info into method in WriteToFileToolHandler

- Introduce private getModelInfo method to centralize extraction of providerId and modelId
- Replace duplicated telemetry extraction logic across multiple function calls
- Update telemetry service invocations to use the new method's return values for consistency and reduced code duplication/script>

* Update src/core/task/tools/handlers/WriteToFileToolHandler.ts

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-11-12 14:40:51 -08:00
Bee 344b99988b feat: add native tool calls tracking to task events (#7429)
Add `isNativeToolCall` parameter to telemetry service to track when
assistant turns and provider API errors occur with native tool calls
enabled. This enables better analytics and debugging of native tool
call behavior across conversation turns.
2025-11-12 13:03:41 -08:00
Bee 7d3c39bcdb feat: make baseUrl dynamic by removing static field (#7428)
Remove the static _baseUrl field and replace all references with direct calls to ClineEnv.config().apiBaseUrl. This ensures the base URL updates correctly when configuration changes, fixing an issue where the URL remained static even after configuration updates.
2025-11-12 12:38:13 -08:00
celestial-vaultandSarah Fortune 33c1692f8a add remotely configured rules and workflows (#7411)
* add remotely configured rules and workflows

* fix toggles state not being passes to webview on updates; minor format updates

---------

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
2025-11-12 10:33:19 -08:00
Bee 3be3ffae4c fix: prevent duplicate tool results by adding existence check (#7419)
* fix: prevent duplicate tool results by adding existence check

Add a check in ToolResultUtils to skip adding a tool result if one already exists for the same tool_use_id, logging a warning to avoid redundant entries in userMessageContent and maintain message integrity. Introduces Logger import for warning output.

* add changeset
2025-11-11 21:37:11 -08:00
87c6708fbe fix: restore commit msg generation functionality to command palette (#7417)
Co-authored-by: Kevin Murphy <murkvin@amazon.com>
Co-authored-by: CandiedUniverse <132302818+candieduniverse@users.noreply.github.com>
2025-11-11 17:36:30 -08:00
Juan Pablo FloresandCandiedUniverse b296706bc0 docs: add guidance for handling strings with quotes in JSON payloads for hooks (#7396)
* docs: add guidance for handling strings with quotes in JSON payloads in hooks documentation

- Introduced a new section on using jq's --arg flag for proper escaping of unescaped quote characters in JSON output.
- Provided a code example demonstrating how to handle complex strings or nested JSON structures safely.

This update enhances the documentation by clarifying how to manage JSON payloads effectively within hooks.

* docs: clarify context modification behavior in hooks documentation

- Updated the explanation regarding context modifications and their effect on AI decisions.
- Emphasized the need to return `cancel: true` in PreToolUse hooks to block actions effectively.

This change enhances the clarity of the documentation, ensuring users understand how to implement immediate effects in their hooks.

---------

Co-authored-by: CandiedUniverse <132302818+candieduniverse@users.noreply.github.com>
2025-11-11 15:24:52 -08:00
canvrnoandAra 7fdc82ae23 Renamed Nous proto config strings (#7416)
* Renamed Nous proto config strings

* Revert "feat: migrate terminal execution mode default to background" (#7412)

This reverts commit 0b7393f50e.

---------

Co-authored-by: Ara <arafat.da.khan@gmail.com>
2025-11-11 14:42:13 -08:00
Bee 246ed87350 fix: ensure temperature parameter is explicitly converted to number (#7397)
* fix(openai): ensure temperature parameter is explicitly converted to number

Changed temperature assignment to explicitly check for undefined and convert
the value to Number type before use. This prevents potential type issues when
the temperature configuration is provided as a string or other non-number type,
ensuring consistent numeric handling for the OpenAI API.

* set 0 tempature to undefined

* add changeset
2025-11-11 14:10:21 -08:00
Ara a28465151f Revert "feat: migrate terminal execution mode default to background" (#7412)
This reverts commit 0b7393f50e.
2025-11-11 12:53:18 -08:00
Saoud Rizwan f1967bc3a1 feat: Add OAuth 2.1 authentication support for remote MCP servers (#7376) 2025-11-11 12:35:11 -08:00
Bee ccc83d6728 feat: add feature flag to toggle between new and old onboarding view [ENG-1224] (#7410)
* feat: add feature flag to toggle between new and old onboarding view

Add a new feature flag `show_onboarding_flow` that controls whether users see the new onboarding experience or the legacy welcome view. This enables A/B testing and gradual rollout of the new onboarding flow, and gives me better control when model list has changed and the onboarding model list is outdated.

Changes:
- Add ONBOARDING feature flag (enabled by default)
- Add showOnboardingFlow to ExtensionState and proto definitions
- Add getOnboardingEnabled() method to FeatureFlagsService
- Update App component to conditionally render OnboardingView or WelcomeView based on flag
- Extend ExtensionStateContext with showOnboardingFlow state management

This allows for controlled rollout and easy rollback if issues arise with the new onboarding experience.

* default true

* default to false unless e2e test

* showOnboardingFlow default to off
2025-11-11 11:33:42 -08:00
Szymon Stasik c151d719c8 fix: Handle tool_use blocks in Claude Code backward compatibility path (#7394)
- Transform tool_use content blocks to tool_calls format
- Map Anthropic tool_use structure to ApiStreamToolCallsChunk
- Fixes 'tool_use is not supported yet' error
- Resolves 'Current ask promise was ignored' downstream errors
2025-11-11 11:13:22 -08:00
canvrno cc25833963 Add Nous Research provider (#7141)
* Added Nous Research provider

* Fix casing on import
2025-11-11 10:43:12 -08:00
canvrnoandellipsis-dev[bot] 2ef3b7cf0f Nous Hermes-4 family system prompt (#7142)
* Hermes-4 model family system prompt

* Update src/core/prompts/system-prompt/variants/hermes/overrides.ts

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-11-11 10:14:24 -08:00
Mike Mikula 19d7bd5198 fix: Fix XML entity escaping in model content processor (#7385)
* refactor(task): replace HTML escaping logic with model-specific content fixes

Updated ExecuteCommandToolHandler and WriteToFileToolHandler to utilize a new utility function, applyModelContentFixes, for handling model-specific quirks. This change centralizes the logic for fixing escaped characters and invalid characters, improving maintainability and consistency across command execution and file writing processes. Additionally, introduced ModelContentProcessor to encapsulate the new content processing logic.

* test(ModelContentProcessor): add comprehensive unit tests for applyModelContentFixes

Introduced a new test suite for the ModelContentProcessor, covering various scenarios for the applyModelContentFixes function. Tests include model ID detection, file type handling, orchestration logic, and integration with real-world use cases, ensuring robust validation of content processing for different model types and file formats.

* fix: add changeset for XML escaping bug fix

* refactor(ModelContentProcessor): make modelId parameter optional in applyModelContentFixes

Updated the applyModelContentFixes function to accept an optional modelId parameter, allowing for fixes to be applied even when the model ID is not provided. Adjusted related logic in ExecuteCommandToolHandler and added a test case to verify this new behavior.
2025-11-11 07:26:27 -08:00
Ara 121066f938 feat: migrate terminal execution mode default to background (#7368)
- Add one-time migration to set all users to backgroundExec mode
- Change default terminal execution mode from vscodeTerminal to backgroundExec
- Update UI labels to clarify background mode is recommended
- Add migration sentinel flag to prevent re-running

This migration improves the default user experience by using background
terminal execution, which provides a cleaner UI and better performance
compared to the interactive VSCode terminal mode. Existing users will be
automatically migrated once, while new users will get the improved default.
2025-11-10 14:06:32 -08:00
Juan Pablo FloresandCopilot f9b88318ea docs(dictation): add notice on Windows support (#7059)
* docs(dictation): remove Windows support from system requirements

- Add note indicating dictation is currently unavailable on Windows
- Remove Windows FFmpeg installation instructions from the list
- Clarify that Windows support is planned for a future release

This update reflects the current state of the feature and sets proper user expectations about platform availability.

* Update docs/features/dictation.mdx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-10 13:26:10 -08:00
Ara 1d64f64f43 Enable voice mode for linux also (#7369) 2025-11-10 12:19:41 -08:00
canvrno 766e2e6a25 Model-family specific variants for deep-planning slash command (#7337) 2025-11-10 11:23:40 -08:00
Ara 8bd7260350 Fix Standalone Terminal Background Transition Issues (#7390) 2025-11-10 11:11:16 -08:00
canvrno a1f4e8b9d4 Adjusted focus chain prompting - more guidance for models using native tool calling, consolidated prompting for other models (#7340) 2025-11-10 11:05:51 -08:00
Johnny Rice aa3e0860cd docs: add missing proto generation step in CONTRIBUTING.md (#7336)
- Add step 4 in Local Development Instructions to run npm run protos
- Add proto generation step in Extension section
- Create new npm run dev script that runs protos + watch
- Update Terminal Workflow documentation to mention npm run dev

Fixes #7335
2025-11-10 10:20:44 -08:00
Dominic Cooney 67bc9932e5 Add proxy configuration to more network fetches. (#7328) 2025-11-10 09:54:54 -08:00
Ara ba98b44504 feat(task): cancel active background command during task abort (#7363)
Add cleanup logic to cancel any active background command when aborting a task. This ensures background processes are properly terminated before the task abort completes, preventing orphaned background commands.
2025-11-07 19:13:31 -08:00
celestial-vault fdeb639f75 make field mask type a string array so that the kotlin/go protobuf libraries don't convert it to snake case (#7364) 2025-11-07 18:52:38 -08:00
Ara 76410b42b0 Removing deprecated Fireworks Models (#7268) 2025-11-07 14:34:29 -08:00
Tomás Barreiro e4e347a8a4 Add Remote Server MCPs to the remote config (#7357) 2025-11-07 23:33:07 +01:00
canvrno 59e7c2b7b9 Fix CLI quick auth issue in docker envs (#7256) 2025-11-07 14:26:39 -08:00
Ara 5371377b21 Removing Minimax M2 from free models list (#7359) 2025-11-07 14:13:19 -08:00
canvrno 5a444dc30a Hide context window usage from env details until usage reaches elevated state (ng models only) (#7345) 2025-11-07 13:46:36 -08:00
Bee 34c48264c0 fix(models): ensure thinking config has default maxBudget value (#7319)
* fix(models): ensure thinking config has default maxBudget value

- Add ANTHROPIC_MAX_THINKING_BUDGET import and use as fallback
- Check for both "include_reasoning" and "reasoning" parameters
- Set default maxBudget when thinking is supported but value not provided
- Ensures thinking models always have valid budget configuration

This prevents issues when OpenRouter API doesn't return thinking_config.maxBudget, ensuring all thinking-enabled models have a proper budget value set as it's used to determine if a model support thinking in some part of our code.

* update thinkingConfig placeholder
2025-11-07 12:52:14 -08:00
Tomás Barreiro daf14ef181 Add LiteLLM to the remote config (#7307)
* Add Google Vertex and LiteLLM to the remote config

* Add the LiteLLM models and export types
2025-11-07 20:24:27 +01:00
Bee f1bf9b3f90 feat: new onboarding flow [ENG-1128] (#7088)
* feat(auth): new onboarding UI

- Add optional `strict` parameter to `createAuthRequest()` to prevent opening new auth windows when already authenticated
- Update onboarding flow with new UI text and button labels ("Login to Cline", "I have my own key", "Ready")
- Add new onboarding data models and step configuration for improved user experience
- Update all e2e tests to reflect new button labels and authentication flow
- Remove obsolete `closeBanners` utility function
- Refactor authentication logic to support strict mode for better control over auth window behavior

This change improves the authentication UX by preventing duplicate auth windows and provides a more streamlined onboarding experience with clearer call-to-action buttons.

* Debug buttons

* Update model list

* Add search box

* improve model selection with names and improved search

- Add name field to ModelInfo interface for better model identification
- Populate model name from OpenRouter API response
- Improve model search filtering to exclude embedding models
- Add case-insensitive search for better UX
- Enhance UI with badges for model capabilities and pricing
- Update styling for better visual hierarchy and selected state
- Display model names in search results for clarity

This improves the onboarding experience by making model selection more informative and user-friendly with better search capabilities and visual feedback.

* clean up

* async onboarding

* Update langauge

* spacing

* search result info text

* update e2e test

* update badge

* style(onboarding): adjust max-width constraint placement

Move max-width constraint from parent container to content wrapper div to improve responsive layout behavior. This ensures the full width is utilized at the top level while constraining only the scrollable content area.

Changes:
- Added w-full to root container for proper width handling
- Removed max-w-lg from middle container
- Applied max-w-lg to content wrapper instead

* Update search box placeholder text

* remove unused description field

* clean up

* Fix search box reset state

* feat: add loading screen to onboarding flow

- Add loading state (step 2) during authentication process
- Simplify welcomeViewCompleted logic by removing auth service check
- Move welcomeViewCompleted state update to auth success handler
- Display loading spinner during sign-in to improve UX
- Ensure auth status update occurs in finally block for reliability

The loading screen provides visual feedback while authentication completes, preventing users from seeing incomplete UI states during the sign-in process.

* update AuthServiceMock

* capture onboarding events

* smaller badge radius

* radius-xs = 4px & add speed label

* update to MiniMax M2
2025-11-07 10:30:50 -08:00
Bee 6e20047c3e refactor(ui): simplify flexible SettingsView (#7352)
* refactor(ui): simplify SettingsView tab system

- Remove ResizeObserver-based compact mode detection and related state
- Replace dynamic CSS class constants with inline Tailwind classes using cn utility
- Remove debounce dependency and handleTabChange callback
- Simplify tab rendering logic by removing conditional compact/full mode rendering
- Clean up unused imports (debounce, useRef) and state management
- Streamline component architecture for better maintainability
- simplify ModelDescriptionMarkdown

This refactoring reduces complexity while maintaining the same visual functionality, making the code easier to understand and maintain.

* clean up
2025-11-07 10:01:14 -08:00
celestial-vault 1d4fe88bec simplify updateApiConfiguration calls (#7299)
* simplify calls to updateApiConfiguration by moving the logic around which mode to update to the RPC; change proto field name; use new calls in MoonshotProvider, and update calls in LiteLlmProvider

* remove console logs
2025-11-07 08:12:20 -08:00
Sarah Fortune 74dbbb9d6b Use staging endpoint for MCP marketplace (#7348) 2025-11-07 00:03:02 -08:00
3073cf40be v3.36.1 Release Notes (#7317)
* changeset version bump

* Updating CHANGELOG.md format

* Update changelog format for version 3.36.1

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2025-11-06 18:54:30 -08:00
canvrnoandellipsis-dev[bot] 50056768d6 Add MCP tool usage to GLM prompt (#7347)
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-11-06 18:03:23 -08:00
Bee e7cd32b830 fix: function declarations tool converter for native tool calling (#7342)
* fix:  function declarations tool converter for native tool calling

- Replace crypto-based ID generation with nanoid in McpHub that is causing invalid function name issue with Gemini models
- Refactor Google tool parameter schema mapping with typed constant
- Improve parameter properties handling in toolSpecFunctionDeclarations
  - Add support for nested properties and enum values
  - Skip $schema property during parameter processing

This change fix type safety for Google Gemini function calling parameter definitions.

* disable native tool calling by default

* Always starts with a letter

* whoops

* reuse id

* fix run time package issue
2025-11-06 17:06:17 -08:00
Bee d983b451e1 fix: Remove Gemini and XAI from next-gen model providers (#7341)
Removes "gemini" and "xai" from the list of providers considered "next-gen" in `isNextGenModelProvider`.
This change prevents Cline from attempting to use native tool callings with these providers until existing issues are resolved.
2025-11-06 14:18:55 -08:00
Bee bb32aac8e7 fix: exclude grok-code from Grok-4 model family check (#7338)
Remove "grok-code" from isGrok4ModelFamily function to exclude it from the next-gen model list, disabling native tool calling for grok-code as it does not work well with the model.
2025-11-06 12:59:58 -08:00
chenxue e6d5f55fb2 fix bug (#7324) 2025-11-06 11:42:14 -08:00
Tomás Barreiro 93f60ebe5b Share provider information (#7331)
* Share provider information

* Move files

* Move files
2025-11-06 20:40:32 +01:00
Bee a3e000d5dd fix: refactor APPLY_PATCH tool [ENG-1174] (#7189)
* feat: refactor APPLY_PATCH tool

Replace separate file creation and editing tools with unified APPLY_PATCH tool for the native-gpt-5 model variant. This consolidation simplifies file operations through a single patch-based interface.

Changes:
- Replace FILE_NEW and FILE_EDIT tools with APPLY_PATCH in config for gpt-5 with native tool calling
- Disable EDITING_FILES system prompt section (no longer needed)
- Remove EDITING_FILES section from base template
- Refactor ApplyPatchHandler with improved architecture:
  - Extract patch parsing logic into PatchParser utility class
  - Extract file operations into FileProviderOperations utility
  - Extract path resolution into PathResolver utility
  - Add comprehensive error handling with DiffError types
  - Improve type safety with shared Patch types
- Add extensive test coverage for PatchParser including:
  - Edge cases (empty files, large files, unicode)
  - Error conditions (malformed patches, invalid operations)
  - Complex scenarios (multiple chunks, context matching)
- Export PATCH_MARKERS and BASH_WRAPPERS for reusability

This refactoring improves maintainability, testability, and provides a more robust patch application system for the GPT-5 model variant when native tool calling is enabled.

* Add diagnostic to result

* Update unit tests

* add feedback

* typo

* captureToolUsage

* fix test

* fix test

* Update ClineMessage

* update diff editor on stream

* revert content partial stream

* Disable Apply patch and add native gpt snap shot

* Remove delete action and make preserveEscaping conditional

* Add delete function to diff provider

* Update UI for file deletion
2025-11-05 20:10:26 -08:00
Bee 0076be531d fix: removes reasoning_details field from Anthropic and Gemini providers (#7282)
* fix: sanitize Anthropic message and removes reasoning_details field

- Extract duplicate Anthropic message formatting code into sanitizeAnthropicMessages function
- Remove code duplication between AnthropicHandler and VertexHandler
- Add support for claude-sonnet-4-5-20250929:1m model
- Remove unused MessageParam import from anthropic provider

* add changeset

* Create thinking content block for gemini
2025-11-05 20:10:09 -08:00
github-actions[bot] 9de4fc2a2f v3.36.0 Release Notes (#7271)
- Add: Hooks allow you to inject custom logic into Cline's workflow
- Add: new provider AIhubmix
- Add: Use http_proxy, https_proxy and no_proxy in JetBrains
- Fix: Oca Token Refresh logic
- Fix: issues where assistant message with empty content is added to conversation history
- Fix: bug where the checkbox shows in the model selector dropdown
- Fix: Switch from defaultUserAgentProvider to customUserAgent for Bedrock
- Fix: support for `<think>` tags for better compatibility with open-source models, and refinements to the GLM-4.6 system prompt

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-05 18:10:09 -08:00
CandiedUniverse 8a4b87a133 feat(hooks): Improve examples in .clinerules/hooks/ directory (#7316) 2025-11-05 16:52:26 -08:00
571480e3b6 Hooks docs (#7173)
* docs(features): add comprehensive hooks documentation

Add detailed documentation for the Hooks feature, including:
- Overview of hooks functionality and use cases
- Getting started guide with step-by-step setup instructions
- Hook types and lifecycle explanations
- JSON communication format and examples
- Validation and blocking capabilities
- Best practices and implementation patterns

Update docs navigation to include the new hooks documentation page
in the features section.

* docs(features): reorganize and clarify Hooks documentation

- add frontmatter description and insert demo GIF in Getting Started
- reorganize Hook Types into Task Lifecycle and Communication categories
- convert tables into dedicated sections per hook with explicit "Input Fields" examples
- add JSON input/output examples and clarify execution timing, limits, and context behavior
- remove embedded bash examples and Advanced Topics clutter; streamline troubleshooting and security warning

* Update docs/features/hooks.mdx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* docs: reorganize hooks documentation into four categories

Restructure the hooks documentation to improve clarity and organization:

- Change from two categories to four logical categories: Tool Execution,
  User Interaction, Task Lifecycle, and API Response
- Reorder hooks by category based on trigger points and use cases
- Add complete JSON examples with all base fields for each hook type
- Enhance descriptions with clearer use case explanations
- Improve consistency across hook documentation format

This reorganization makes it easier for developers to find the right hook
for their specific needs and understand the complete data structure they
will receive.

* docs: enhance TaskStart hook example with JSON payload handling

Replace basic "Hello World" example with practical demonstration of reading JSON input from stdin and using jq to inspect payload structure and field types. Added explanatory text to help developers understand hook input/output mechanics before building complex logic.

* Update docs/features/hooks.mdx

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* comments out preCompact and taskComplete

* fix: clarify description of hook types in documentation

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: CandiedUniverse <132302818+candieduniverse@users.noreply.github.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-11-05 16:50:22 -08:00
5c2c264a1d initial CLI samples (#7040)
* initial CLI samples

* Add GitHub integration and root cause analysis samples to CLI documentation

* docs(cline-cli): restructure samples into nested documentation group

Convert the single samples page into a grouped navigation structure with three separate sample pages:
- Overview (main samples page)
- GitHub Issue RCA
- GitHub Integration

Add redirect from the old `/cline-cli/samples` path to the new `/cline-cli/samples/overview` location to maintain backward compatibility with existing links.

* docs: improve GitHub issue RCA sample docs with better structure and links

Reorganize documentation with source code links, move demo video to prominent position,
simplify explanations, and add tutorial references for better discoverability.

* docs: improve github-issue-rca documentation and add download instructions

- Remove redundant "Demo Video" heading from mdx documentation
- Add "Getting the Script" section to README with two options:
  - Option 1: Clone/view repository with direct link to script
  - Option 2: Direct download using curl with example commands

These changes make it easier for users to quickly access and download
the analyze-issue.sh script without needing to navigate the repository
structure, improving the overall user experience.

* docs: update GitHub Root Cause Analysis sample links and enhance README with installation instructions

* docs(samples): improve GitHub integration setup guide and add prerequisites

Enhanced the GitHub integration sample documentation with:
- Added prerequisites section listing required knowledge and resources
- Improved setup instructions with direct curl commands for downloading files
- Added prominent warning callout for editing GITORG and GITREPO variables
- Included beginner-friendly note recommending simpler sample first
- Updated documentation link to point directly to GitHub repository
- Provided two options for adding analysis script (download or copy)
- Enhanced formatting and clarity throughout the guide

These changes make the sample more accessible to new users and reduce
setup friction by providing copy-paste commands and clearer guidance.

* docs(samples): consolidate CLI samples documentation and enhance GitHub integration README with improved image formatting

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* removes bookmarks and creates padding for images

* Update image source in README for CLI sample

* Add GitHub CLI samples and update docs navigation

- Add GitHub Issue RCA sample (docs/cline-cli/samples/github-issue-rca.mdx)
- Add GitHub Actions integration sample (docs/cline-cli/samples/github-integration.mdx)
- Add samples overview page (docs/cline-cli/samples/overview.mdx)
- Update docs.json to include new CLI samples navigation entries

* docs: simplify "Getting the Script" section in GitHub Issue RCA sample

Reorder options so curl download is Option 1 and the full script view is Option 2.
Remove the clone/view-repo entry, move the chmod +x instruction into a Note
after the script accordion, and clarify the Basic Usage intro text.

* docs: tidy GitHub Integration sample — remove redundant workflow placement note, simplify script copy options, fix GitHub Action capitalization and related-samples link

* docs: remove GitHub Integration and GitHub Issue RCA sample READMEs

---------

Co-authored-by: Juan Pablo <juan@cline.bot>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-05 16:49:57 -08:00
CandiedUniverse 04549cf1e3 feat(hooks): Trim characters around JSON in hook output (#7314) 2025-11-05 15:59:56 -08:00
Bee f2a93b6201 fix(account): correct tooltip visibility logic for remote config lock (#7313)
Invert the boolean condition for TooltipContent's hidden prop to properly
show the tooltip when organization has remote configuration enabled.
Previously, the tooltip was hidden when it should be visible and vice versa.
2025-11-05 15:57:47 -08:00
CandiedUniverse 2f09b994dd feat(hooks): Fix contextModification injection into API Request (#7311) 2025-11-05 15:28:34 -08:00
Bee 8aac67bf75 feat: enable native tool call by default (#7304)
Change the default value of `nativeToolCallEnabled` from `false` to `true` in the global state initialization. This enables native tool calling functionality by default when no previous state exists, improving the out-of-box experience for users.
2025-11-05 15:23:02 -08:00
CandiedUniverse 10422dd3fc feat(hooks): Remove hooks feature flag (step 1), keeping hooks setting toggle (#7309) 2025-11-05 14:51:11 -08:00
CandiedUniverse 6191c681ce feat(hooks): Fix the missing Start New Task button after task completion (#7303) 2025-11-05 14:49:42 -08:00
CandiedUniverse 9ab7b41c24 feat(hooks): Remove debug logging to the console (#7306) 2025-11-05 14:10:19 -08:00
311 changed files with 13574 additions and 3255 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Use http_proxy, https_proxy and no_proxy in JetBrains and CLI mode
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fixing the logic for Oca Token Refresh
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": minor
---
add new provider AIhubmix
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fix issues where assistant message with empty content is added to conversation history
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fixes a bug where the checkbox shows in the model selector dropdown.
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
upgrade sap ai-sdk-js packages major version
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
switch from defaultUserAgentProvider to customUserAgent for Bedrock
+9 -5
View File
@@ -1,15 +1,19 @@
#!/usr/bin/env bash
for i in {1..100}; do
echo "PostToolUse running inside local cline/.clinerules/hooks/ directory"
input=$(cat)
echo $input | jq .
for i in {1..5}; do
sleep 1
echo "$i"
done
sleep 3
cat <<EOF
{
"cancel": false,
"contextModification": "WORKSPACE_RULES: This is from the local bar/ workspace.",
"errorMessage": "PostToolUse hook custom errorMessage: foo"
"contextModification": "PostToolUse response from the local cline/.clinerules/hooks/ directory.",
"errorMessage": "PostToolUse hook custom errorMessage"
}
EOF
+9 -5
View File
@@ -1,15 +1,19 @@
#!/usr/bin/env bash
for i in {1..100}; do
echo "PreToolUse running inside local cline/.clinerules/hooks/ directory"
input=$(cat)
echo $input | jq .
for i in {1..5}; do
sleep 1
echo "$i"
done
sleep 3
cat <<EOF
{
"cancel": false,
"contextModification": "Make sure that all variables in python code in this repo always start with the string 'EVIEDEBUG_'. This is important.",
"errorMessage": "PreToolUse hook custom errorMessage: foo"
"contextModification": "PreToolUse response from the local cline/.clinerules/hooks/ directory.",
"errorMessage": "PreToolUse hook custom errorMessage"
}
EOF
+26 -59
View File
@@ -4,7 +4,7 @@
Cline hooks allow you to execute custom scripts at specific points in the agentic workflow. Hooks can be placed in either:
- **Global hooks directory**: `~/Documents/Cline/Hooks/` (applies to all workspaces)
- **Workspace hooks directory**: `.clinerules/hooks/` (applies to specific workspace)
- **Workspace hooks directory**: `.clinerules/hooks/` (applies to the workspace the repo is part of)
Hooks run automatically when enabled.
@@ -20,51 +20,51 @@ Hooks run automatically when enabled.
### TaskStart Hook
- **When**: Runs when a NEW task is started (not when resuming)
- **Purpose**: Initialize task context, validate task requirements, set up environment
- **Global Location**: `~/Documents/Cline/Hooks/TaskStart` (all platforms)
- **Workspace Location**: `.clinerules/hooks/TaskStart` (all platforms)
- **Global Location**: `~/Documents/Cline/Hooks/TaskStart`
- **Workspace Location**: `.clinerules/hooks/TaskStart`
### TaskResume Hook
- **When**: Runs when an EXISTING task is resumed (after user clicks resume button)
- **Purpose**: Validate resumed task state, restore context, check for changes since last run
- **Global Location**: `~/Documents/Cline/Hooks/TaskResume` (all platforms)
- **Workspace Location**: `.clinerules/hooks/TaskResume` (all platforms)
- **Global Location**: `~/Documents/Cline/Hooks/TaskResume`
- **Workspace Location**: `.clinerules/hooks/TaskResume`
### TaskCancel Hook
- **When**: Runs when a task is cancelled by the user (only if there's actual active work or work was started)
- **When**: Runs when a task is cancelled or a hook is aborted by the user (only if there's actual active work or work was started)
- **Purpose**: Clean up resources, log cancellation, save state
- **Global Location**: `~/Documents/Cline/Hooks/TaskCancel` (all platforms)
- **Workspace Location**: `.clinerules/hooks/TaskCancel` (all platforms)
- **Note**: This hook is NOT cancellable and will complete even if the task is being aborted
- **Global Location**: `~/Documents/Cline/Hooks/TaskCancel`
- **Workspace Location**: `.clinerules/hooks/TaskCancel`
- **Note**: This hook is NOT cancellable
### TaskComplete Hook
### TaskComplete Hook (coming soon!)
- **When**: Runs when a task is marked as complete
- **Purpose**: Log completion status, perform final cleanup, generate reports
- **Global Location**: `~/Documents/Cline/Hooks/TaskComplete` (all platforms)
- **Workspace Location**: `.clinerules/hooks/TaskComplete` (all platforms)
- **Global Location**: `~/Documents/Cline/Hooks/TaskComplete`
- **Workspace Location**: `.clinerules/hooks/TaskComplete`
### UserPromptSubmit Hook
- **When**: Runs when the user submits a prompt/message (initial task, resume, or feedback)
- **Purpose**: Validate user input, preprocess prompts, add context to user messages
- **Global Location**: `~/Documents/Cline/Hooks/UserPromptSubmit` (all platforms)
- **Workspace Location**: `.clinerules/hooks/UserPromptSubmit` (all platforms)
- **Global Location**: `~/Documents/Cline/Hooks/UserPromptSubmit`
- **Workspace Location**: `.clinerules/hooks/UserPromptSubmit`
### PreToolUse Hook
- **When**: Runs BEFORE a tool is executed
- **Purpose**: Validate parameters, block execution, or add context
- **Global Location**: `~/Documents/Cline/Hooks/PreToolUse` (all platforms)
- **Workspace Location**: `.clinerules/hooks/PreToolUse` (all platforms)
- **Global Location**: `~/Documents/Cline/Hooks/PreToolUse`
- **Workspace Location**: `.clinerules/hooks/PreToolUse`
### PostToolUse Hook
- **When**: Runs AFTER a tool completes
- **Purpose**: Observe results, track patterns, or add context
- **Global Location**: `~/Documents/Cline/Hooks/PostToolUse` (all platforms)
- **Workspace Location**: `.clinerules/hooks/PostToolUse` (all platforms)
- **Global Location**: `~/Documents/Cline/Hooks/PostToolUse`
- **Workspace Location**: `.clinerules/hooks/PostToolUse`
### PreCompact Hook
### PreCompact Hook (coming soon!)
- **When**: Runs BEFORE the conversation context is compacted/truncated
- **Purpose**: Observe compaction events, log context management, track token usage
- **Global Location**: `~/Documents/Cline/Hooks/PreCompact` (all platforms)
- **Workspace Location**: `.clinerules/hooks/PreCompact` (all platforms)
- **Global Location**: `~/Documents/Cline/Hooks/PreCompact`
- **Workspace Location**: `.clinerules/hooks/PreCompact`
## Cross-Platform Hook Format
@@ -74,13 +74,12 @@ Cline uses a git-style approach for hooks that works consistently across all pla
- **No file extensions**: Hooks are named exactly `PreToolUse` or `PostToolUse` (no `.bat`, `.cmd`, `.sh` etc.)
- **Shebang required**: First line must be a shebang (e.g., `#!/usr/bin/env bash` or `#!/usr/bin/env node`)
- **Executable on Unix**: On Unix/Linux/macOS, hooks must be executable: `chmod +x PreToolUse`
- **Windows**: No special permissions needed - hooks are executed through the shell
- **Windows**: Not currently supported.
### How It Works
Like git hooks, Cline executes hook files through a shell that interprets the shebang line:
- On Unix/Linux/macOS: Native shell execution with shebang support
- On Windows: Shell execution handles shebang interpretation
This means:
- ✅ Same hook script works on all platforms
@@ -98,12 +97,6 @@ nano ~/Documents/Cline/Hooks/PreToolUse
chmod +x ~/Documents/Cline/Hooks/PreToolUse
```
**On Windows:**
```batch
REM Create hook file (note: no file extension)
notepad %USERPROFILE%\Documents\Cline\Hooks\PreToolUse
```
## Context Injection Timing
**IMPORTANT**: Context injected by hooks affects **FUTURE AI decisions**, not the current tool execution.
@@ -218,27 +211,6 @@ All hooks must return:
- `false` (or omitted): Allow execution to continue
- `true`: Block execution and show error message to user
## Context Modification Format
Use structured prefixes to help the AI understand context type:
- `WORKSPACE_RULES:` - Project conventions and requirements
- `FILE_OPERATIONS:` - File creation/modification patterns
- `TOOL_RESULT:` - Outcomes of tool executions
- `PERFORMANCE:` - Performance concerns
- `VALIDATION:` - Validation results
- Custom prefixes as needed
Example:
```bash
cat <<EOF
{
"cancel": false,
"contextModification": "WORKSPACE_RULES: This is a TypeScript project. All new files must use .ts or .tsx extensions."
}
EOF
```
## Hook Execution Limits
- **Timeout**: Hooks must complete within 30 seconds (configurable via `HOOK_EXECUTION_TIMEOUT_MS`)
@@ -260,7 +232,7 @@ if [[ "$tool_name" == "write_to_file" && "$path" == *.js ]]; then
{
"cancel": true,
"errorMessage": "Cannot create .js files in TypeScript project",
"contextModification": "WORKSPACE_RULES: Use .ts/.tsx extensions only"
"contextModification": "Use .ts/.tsx extensions only"
}
EOF
exit 0
@@ -282,7 +254,7 @@ if [[ "$tool_name" == "write_to_file" && "$success" == "true" ]]; then
cat <<EOF
{
"cancel": false,
"contextModification": "FILE_OPERATIONS: Created '$path'. Maintain consistency with this file's patterns in future operations."
"contextModification": "Created '$path'. Maintain consistency with this file's patterns in future operations."
}
EOF
else
@@ -302,7 +274,7 @@ if [[ "$execution_time" -gt 5000 ]]; then
cat <<EOF
{
"cancel": false,
"contextModification": "PERFORMANCE: Tool '$tool_name' took ${execution_time}ms. Consider optimizing future similar operations."
"contextModification": "Tool '$tool_name' took ${execution_time}ms. Consider optimizing future similar operations."
}
EOF
else
@@ -328,7 +300,7 @@ echo '{"cancel": false}'
Cline supports two levels of hooks:
### Global Hooks
- **Location**: `~/Documents/Cline/Hooks/` (macOS/Linux) or `%USERPROFILE%\Documents\Cline\Hooks\` (Windows)
- **Location**: `~/Documents/Cline/Hooks/` (macOS/Linux)
- **Scope**: Apply to ALL workspaces and projects
- **Use Case**: Organization-wide policies, personal preferences, universal validations
- **Priority**: Order not guaranteed when combined with workspace hooks
@@ -356,16 +328,12 @@ When multiple hooks exist (global and/or workspace):
1. The global hooks directory is automatically created at:
- macOS/Linux: `~/Documents/Cline/Hooks/`
- Windows: `%USERPROFILE%\Documents\Cline\Hooks\`
2. Add your hook script:
```bash
# Unix/Linux/macOS
nano ~/Documents/Cline/Hooks/PreToolUse
chmod +x ~/Documents/Cline/Hooks/PreToolUse
# Windows
notepad %USERPROFILE%\Documents\Cline\Hooks\PreToolUse
```
3. Enable hooks in Cline settings
@@ -433,7 +401,6 @@ If you have multiple workspace roots, you can place hooks in each root's `.cline
### Context Not Affecting Behavior
- Remember: context affects FUTURE decisions, not the current tool
- Use PreToolUse for validation (blocking) if you need immediate effect
- Ensure context modifications are clear and actionable
- Check that context isn't being truncated (50KB limit)
+10 -6
View File
@@ -1,15 +1,19 @@
#!/usr/bin/env bash
for i in {1..100}; do
echo "TaskCancel running inside local cline/.clinerules/hooks/ directory"
input=$(cat)
echo $input | jq .
for i in {1..5}; do
sleep 1
echo "$i"
done
sleep 3
cat <<EOF
{
"cancel", false,
"contextModification": "WORKSPACE_RULES: This is from the local bar/ workspace.",
"errorMessage": "TaskCancel hook custom errorMessage: foo"
"cancel": false,
"contextModification": "TaskCancel response from the local cline/.clinerules/hooks/ directory.",
"errorMessage": "TaskCancel hook custom errorMessage"
}
EOF
+9 -5
View File
@@ -1,15 +1,19 @@
#!/usr/bin/env bash
for i in {1..100}; do
echo "TaskResume running inside local cline/.clinerules/hooks/ directory"
input=$(cat)
echo $input | jq .
for i in {1..5}; do
sleep 1
echo "$i"
done
sleep 3
cat <<EOF
{
"cancel": false,
"contextModification": "WORKSPACE_RULES: This is from the local bar/ workspace.",
"errorMessage": "TaskResume hook custom errorMessage: foo"
"contextModification": "TaskResume response from the local cline/.clinerules/hooks/ directory.",
"errorMessage": "TaskResume hook custom errorMessage"
}
EOF
+9 -5
View File
@@ -1,15 +1,19 @@
#!/usr/bin/env bash
for i in {1..100}; do
echo "TaskStart running inside local cline/.clinerules/hooks/ directory"
input=$(cat)
echo $input | jq .
for i in {1..5}; do
sleep 1
echo "$i"
done
sleep 1
cat <<EOF
{
"cancel": false,
"contextModification": "WORKSPACE_RULES: This is from the local bar/ workspace.",
"errorMessage": "TaskStart hook custom errorMessage: foo"
"contextModification": "TaskStart response from the local cline/.clinerules/hooks/ directory.",
"errorMessage": "TaskStart hook custom errorMessage"
}
EOF
+9 -5
View File
@@ -1,15 +1,19 @@
#!/usr/bin/env bash
for i in {1..100}; do
echo "UserPromptSubmit running inside local cline/.clinerules/hooks/ directory"
input=$(cat)
echo $input | jq .
for i in {1..5}; do
sleep 1
echo "$i"
done
sleep 3
cat <<EOF
{
"cancel": false,
"contextModification": "WORKSPACE_RULES: This is from the local bar/ workspace.",
"errorMessage": "UserPromptSubmit hook custom errorMessage: foo"
"contextModification": "UserPromptSubmit response from the local cline/.clinerules/hooks/ directory.",
"errorMessage": "UserPromptSubmit hook custom errorMessage"
}
EOF
+3
View File
@@ -20,6 +20,9 @@ eslint-rules/**
.husky/**
.env
# cli
cli/**
# Custom
**/demo.gif
.nvmrc
+49
View File
@@ -1,5 +1,54 @@
# Changelog
## 3.37.1
- cf8dd1c: Comprehensive changes to better support GPT 5.1 - System prompt, tools, deep-planning, focus chain, etc.
- 02abbcf: Add AGENTS.md support
- 855db7d: feat(models): Add free minimax/mimax-m2 model to the model picker
## [3.37.0]
## Added
- GPT-5.1 with model-specific prompting: tailored system prompts, tool usage, focus chain, and deep-planning optimizations
- Nous Research provider with Hermes 4 model family and custom system prompts
- Switched to Aqua Voice's Avalon model in speech to text transcription
- Added Linux support for speech to text
- Model-family breakouts for deep-planning prompting, laying groundwork for enhanced slash commands
- Expanded HTTP proxy support throughout the codebase
- Improved focus chain prompting for frontier models (Anthropic, OpenAI, Gemini, xAI)
## Fixed
- Duplicate tool results prevention through existence checking
- XML entity escaping in model content processor
- Commit message generation in command palette
- OpenAI Compatible provider temperature parameter type conversion
## Documentation
- Added missing proto generation step in CONTRIBUTING.md
- New `npm run dev` script for streamlined terminal workflow (fixes #7335)
## [3.36.1]
- fix: remove native tool calling support from Gemini and XAI provider due to invalid tool names issues
- fix: disable native tool callings for grok code models
- Add MCP tool usage to GLM
- Removes reasoning_details content field from Anthropic providers
## [3.36.0]
- Add: Hooks allow you to inject custom logic into Cline's workflow
- Add: new provider AIhubmix
- Add: Use http_proxy, https_proxy and no_proxy in JetBrains
- Fix: Oca Token Refresh logic
- Fix: issues where assistant message with empty content is added to conversation history
- Fix: bug where the checkbox shows in the model selector dropdown
- Fix: Switch from defaultUserAgentProvider to customUserAgent for Bedrock
- Fix: support for `<think>` tags for better compatibility with open-source models
- Fix: refinements to the GLM-4.6 system prompt
## [3.35.1]
- Add: Hicap API integration as provider
+7 -1
View File
@@ -46,7 +46,11 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
```bash
npm run install:all
```
4. Launch by pressing `F5` (or `Run`->`Start Debugging`) to open a new VSCode window with the extension loaded. (You may need to install the [esbuild problem matchers extension](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) if you run into issues building the project.)
4. Generate Protocol Buffer files (required before first build):
```bash
npm run protos
```
5. Launch by pressing `F5` (or `Run`->`Start Debugging`) to open a new VSCode window with the extension loaded. (You may need to install the [esbuild problem matchers extension](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) if you run into issues building the project.)
@@ -85,8 +89,10 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
2. **Local Development**
- Run `npm run install:all` to install dependencies
- Run `npm run protos` to generate Protocol Buffer files (required before first build)
- Run `npm run test` to run tests locally
- Run → Start Debugging or `>Debug: Select and Start Debugging` and wait for a new VS Code instance to open
- **Terminal Workflow**: Use `npm run dev` (generates protos + runs watch mode) or `npm run watch` (if protos already generated)
- Before submitting PR, run `npm run format:fix` to format your code
3. **Linux-specific Setup**
+7
View File
@@ -75,6 +75,12 @@ func QuickSetupFromFlags(ctx context.Context, provider, apiKey, modelID, baseURL
}
}
// Flush pending state changes to disk immediately
// This ensures all configuration changes are persisted before the instance terminates
if _, err := manager.GetClient().State.FlushPendingState(ctx, &cline.EmptyRequest{}); err != nil {
return fmt.Errorf("failed to flush pending state: %w", err)
}
// Success message
fmt.Printf("\n✓ Successfully configured %s provider\n", GetProviderDisplayName(providerEnum))
fmt.Printf(" Model: %s\n", finalModelID)
@@ -170,6 +176,7 @@ func validateQuickSetupProvider(providerID string) (cline.ApiProvider, error) {
cline.ApiProvider_XAI: true,
cline.ApiProvider_CEREBRAS: true,
cline.ApiProvider_OLLAMA: true,
cline.ApiProvider_NOUSRESEARCH: true,
}
if !supportedProviders[provider] {
+3
View File
@@ -26,6 +26,7 @@ func GetBYOProviderList() []BYOProviderOption {
{Name: "Google Gemini", Provider: cline.ApiProvider_GEMINI},
{Name: "Ollama", Provider: cline.ApiProvider_OLLAMA},
{Name: "Cerebras", Provider: cline.ApiProvider_CEREBRAS},
{Name: "NousResearch", Provider: cline.ApiProvider_NOUSRESEARCH},
{Name: "Oracle Code Assist", Provider: cline.ApiProvider_OCA},
}
}
@@ -100,6 +101,8 @@ func GetBYOProviderPlaceholder(provider cline.ApiProvider) string {
return "e.g., qwen3-coder:30b"
case cline.ApiProvider_CEREBRAS:
return "e.g., gpt-oss-120b"
case cline.ApiProvider_NOUSRESEARCH:
return "e.g., Hermes-4-405B"
case cline.ApiProvider_OCA:
return "e.g., oca/llama4"
default:
+8
View File
@@ -111,6 +111,7 @@ func (r *ProviderListResult) GetAllReadyProviders() []*ProviderDisplay {
cline.ApiProvider_GEMINI,
cline.ApiProvider_OLLAMA,
cline.ApiProvider_CEREBRAS,
cline.ApiProvider_NOUSRESEARCH,
cline.ApiProvider_OCA,
cline.ApiProvider_HICAP,
}
@@ -241,6 +242,8 @@ func mapProviderStringToEnum(providerStr string) (cline.ApiProvider, bool) {
return cline.ApiProvider_OCA, true
case "hicap":
return cline.ApiProvider_HICAP, true
case "nousResearch":
return cline.ApiProvider_NOUSRESEARCH, true
default:
return cline.ApiProvider_ANTHROPIC, false // Return 0 value with false
}
@@ -274,6 +277,8 @@ func GetProviderIDForEnum(provider cline.ApiProvider) string {
return "oca"
case cline.ApiProvider_HICAP:
return "hicap"
case cline.ApiProvider_NOUSRESEARCH:
return "nousResearch"
default:
return ""
}
@@ -353,6 +358,8 @@ func GetProviderDisplayName(provider cline.ApiProvider) string {
return "Oracle Code Assist"
case cline.ApiProvider_HICAP:
return "Hicap"
case cline.ApiProvider_NOUSRESEARCH:
return "NousResearch"
default:
return "Unknown"
}
@@ -475,6 +482,7 @@ func DetectAllConfiguredProviders(ctx context.Context, manager *task.Manager) ([
{cline.ApiProvider_OLLAMA, "ollamaBaseUrl"}, // Ollama uses baseUrl instead of API key
{cline.ApiProvider_CEREBRAS, "cerebrasApiKey"},
{cline.ApiProvider_HICAP, "hicapApiKey"},
{cline.ApiProvider_NOUSRESEARCH, "nousResearchApiKey"},
}
for _, providerCheck := range providersToCheck {
@@ -163,6 +163,15 @@ func GetProviderFields(provider cline.ApiProvider) (ProviderFields, error) {
ActModeProviderSpecificModelIDField: "actModeHicapModelId",
}, nil
case cline.ApiProvider_NOUSRESEARCH:
return ProviderFields{
APIKeyField: "nousResearchApiKey",
PlanModeModelIDField: "planModeApiModelId",
ActModeModelIDField: "actModeApiModelId",
PlanModeProviderSpecificModelIDField: "planModeNousResearchModelId",
ActModeProviderSpecificModelIDField: "actModeNousResearchModelId",
}, nil
default:
return ProviderFields{}, fmt.Errorf("unsupported provider: %v", provider)
}
@@ -278,6 +287,8 @@ func setAPIKeyField(apiConfig *cline.ModelsApiConfiguration, fieldName string, v
apiConfig.OcaApiKey = value
case "hicapApiKey":
apiConfig.HicapApiKey = value
case "nousResearchApiKey":
apiConfig.NousResearchApiKey = value
}
}
@@ -302,6 +313,9 @@ func setProviderSpecificModelID(apiConfig *cline.ModelsApiConfiguration, fieldNa
case "planModeHicapModelId":
apiConfig.PlanModeHicapModelId = value
apiConfig.ActModeHicapModelId = value
case "planModeNousResearchModelId":
apiConfig.PlanModeNousResearchModelId = value
apiConfig.ActModeNousResearchModelId = value
}
}
+11 -2
View File
@@ -106,6 +106,14 @@ func (tr *ToolRenderer) generateToolHeader(tool *types.ToolMessage, verbTense st
}
return fmt.Sprintf("### Cline %s `%s`", action, tool.Path)
case string(types.ToolTypeFileDeleted):
if verbTense == "wants to" {
action = "wants to delete"
} else {
action = "is deleting"
}
return fmt.Sprintf("### Cline %s `%s`", action, tool.Path)
case string(types.ToolTypeListFilesTopLevel):
if verbTense == "wants to" {
action = "wants to list files in"
@@ -199,7 +207,7 @@ func (tr *ToolRenderer) GenerateToolContentPreview(tool *types.ToolMessage) stri
previewMd := fmt.Sprintf("```\n%s\n```", preview)
return tr.renderMarkdown(previewMd)
case string(types.ToolTypeReadFile), string(types.ToolTypeWebFetch):
case string(types.ToolTypeReadFile), string(types.ToolTypeWebFetch), string(types.ToolTypeFileDeleted):
// No preview for read/fetch operations
return ""
@@ -226,7 +234,8 @@ func (tr *ToolRenderer) GenerateToolContentBody(tool *types.ToolMessage) string
toolParser := NewToolResultParser(tr.mdRenderer)
switch tool.Tool {
case string(types.ToolTypeReadFile):
case string(types.ToolTypeReadFile),
string(types.ToolTypeFileDeleted):
// readFile: show header only, no body
return ""
+2
View File
@@ -256,6 +256,8 @@ func determineAutoApprovalAction(msg *types.ClineMessage) (string, error) {
case types.ToolTypeEditedExistingFile,
types.ToolTypeNewFileCreated:
return "edit_files", nil
case types.ToolTypeFileDeleted:
return "apply_patch", nil
default:
return "", fmt.Errorf("unsupported tool type: %s", toolMsg.Tool)
}
+1
View File
@@ -107,6 +107,7 @@ const (
ToolTypeEditedExistingFile ToolType = "editedExistingFile"
ToolTypeNewFileCreated ToolType = "newFileCreated"
ToolTypeReadFile ToolType = "readFile"
ToolTypeFileDeleted ToolType = "fileDeleted"
ToolTypeListFilesTopLevel ToolType = "listFilesTopLevel"
ToolTypeListFilesRecursive ToolType = "listFilesRecursive"
ToolTypeListCodeDefinitionNames ToolType = "listCodeDefinitionNames"
+71
View File
@@ -145,6 +145,7 @@ const (
XAI = "xai"
CEREBRAS = "cerebras"
OCA = "oca"
NOUSRESEARCH = "nousResearch"
)
// AllProviders returns a slice of enabled provider IDs for the CLI build.
@@ -161,6 +162,7 @@ var AllProviders = []string{
"xai",
"cerebras",
"oca",
"nousResearch",
}
// ConfigField represents a configuration field requirement
@@ -318,6 +320,15 @@ var rawConfigFields = ` [
"fieldType": "password",
"placeholder": "Enter your API key"
},
{
"name": "nousResearchApiKey",
"type": "string",
"comment": "",
"category": "nousResearch",
"required": true,
"fieldType": "password",
"placeholder": "Enter your API key"
},
{
"name": "ulid",
"type": "string",
@@ -435,6 +446,15 @@ var rawConfigFields = ` [
"fieldType": "url",
"placeholder": "https://api.example.com"
},
{
"name": "minimaxApiLine",
"type": "string",
"comment": "",
"category": "general",
"required": false,
"fieldType": "string",
"placeholder": ""
},
{
"name": "ocaMode",
"type": "string",
@@ -775,6 +795,24 @@ var rawModelDefinitions = ` {
"supportsImages": false,
"supportsPromptCache": false,
"description": "A compact 20B open-weight Mixture-of-Experts language model designed for strong reasoning and tool use, ideal for edge devices and local inference."
},
"qwen.qwen3-coder-30b-a3b-v1:0": {
"maxTokens": 8192,
"contextWindow": 262144,
"inputPrice": 0,
"outputPrice": 0,
"supportsImages": false,
"supportsPromptCache": false,
"description": "Qwen3 Coder 30B MoE model with 3.3B activated parameters, optimized for code generation and analysis with 256K context window."
},
"qwen.qwen3-coder-480b-a35b-v1:0": {
"maxTokens": 8192,
"contextWindow": 262144,
"inputPrice": 0,
"outputPrice": 1,
"supportsImages": false,
"supportsPromptCache": false,
"description": "Qwen3 Coder 480B flagship MoE model with 35B activated parameters, designed for complex coding tasks with advanced reasoning capabilities and 256K context window."
}
},
"gemini": {
@@ -1263,6 +1301,26 @@ var rawModelDefinitions = ` {
"supportsPromptCache": false,
"description": "SOTA performance with ~1500 tokens/s"
}
},
"nousResearch": {
"Hermes-4-405B": {
"maxTokens": 8192,
"contextWindow": 128000,
"inputPrice": 0,
"outputPrice": 0,
"supportsImages": false,
"supportsPromptCache": false,
"description": "This is the largest model in the Hermes 4 family, and it is the fullest expression of our design, focused on advanced reasoning and creative depth rather than optimizing inference speed or cost."
},
"Hermes-4-70B": {
"maxTokens": 8192,
"contextWindow": 128000,
"inputPrice": 0,
"outputPrice": 0,
"supportsImages": false,
"supportsPromptCache": false,
"description": "This incarnation of Hermes 4 balances scale and size. It handles complex reasoning tasks, while staying fast and cost effective. A versatile choice for many use cases."
}
}
}`
@@ -1432,6 +1490,18 @@ func GetProviderDefinitions() (map[string]ProviderDefinition, error) {
HasDynamicModels: false,
SetupInstructions: `Configure Oca API credentials`,
}
// NousResearch
definitions["nousResearch"] = ProviderDefinition{
ID: "nousResearch",
Name: "NousResearch",
RequiredFields: getFieldsByProvider("nousResearch", configFields, true),
OptionalFields: getFieldsByProvider("nousResearch", configFields, false),
Models: modelDefinitions["nousResearch"],
DefaultModelID: "Hermes-4-405B",
HasDynamicModels: false,
SetupInstructions: `Configure NousResearch API credentials`,
}
return definitions, nil
}
@@ -1459,6 +1529,7 @@ func GetProviderDisplayName(providerID string) string {
"xai": "X AI (Grok)",
"cerebras": "Cerebras",
"oca": "Oca",
"nousResearch": "NousResearch",
}
if name, exists := displayNames[providerID]; exists {
@@ -0,0 +1,324 @@
---
title: "GitHub Actions Integration"
description: "Automatically respond to GitHub issues by mentioning @cline in comments using Cline CLI in GitHub Actions."
---
# GitHub Integration Sample
Automate GitHub issue analysis with AI. Mention `@cline` in any issue comment to trigger an autonomous investigation that reads files, analyzes code, and provides actionable insights - all running automatically in GitHub Actions.
<Note>
**New to Cline CLI?** This sample assumes you understand Cline CLI basics and have completed the [Installation Guide](https://docs.cline.bot/cline-cli/installation). If you're new to Cline CLI, we recommend starting with the [GitHub RCA sample](../github-issue-rca) first, as it's simpler and will help you understand the fundamentals before setting up GitHub Actions.
</Note>
## The Workflow
Trigger Cline by mentioning `@cline` in any issue comment:
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/ss0a-comment.png" alt="Issue comment with @cline mention" width="600" />
</Frame>
Cline's automated analysis appears as a new comment, with insights drawn from your actual codebase:
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/ss0b-final.png" alt="Automated analysis response from Cline" width="600" />
</Frame>
The entire investigation runs autonomously in GitHub Actions - from file exploration to posting results.
Let's configure your repository.
## Prerequisites
Before you begin, you'll need:
- **Cline CLI knowledge** - Completed the [Installation Guide](https://docs.cline.bot/cline-cli/installation) and understand basic usage
- **GitHub repository** - With admin access to configure Actions and secrets
- **GitHub Actions familiarity** - Basic understanding of workflows and CI/CD
- **API provider account** - OpenRouter, Anthropic, or similar with API key
## Setup
### 1. Copy the Workflow File
Copy the workflow file from this sample to your repository. The workflow file must be placed in the `.github/workflows/` directory in your repository root for GitHub Actions to detect and run it. In this case, we'll name it `cline-responder.yml`.
```bash
# In your repository root
mkdir -p .github/workflows
curl -o .github/workflows/cline-responder.yml https://raw.githubusercontent.com/cline/cline/main/src/samples/cli/github-integration/cline-responder.yml
```
Alternatively, you can copy the full workflow file directly into `.github/workflows/cline-responder.yml`:
<Accordion title="Click to view the complete cline-responder.yml workflow">
```yaml
name: Cline Issue Assistant
on:
issue_comment:
types: [created, edited]
permissions:
issues: write
jobs:
respond:
runs-on: ubuntu-latest
environment: cline-actions
steps:
- name: Check for @cline mention
id: detect
uses: actions/github-script@v7
with:
script: |
const body = context.payload.comment?.body || "";
const isPR = !!context.payload.issue?.pull_request;
const hit = body.toLowerCase().includes("@cline");
core.setOutput("hit", (!isPR && hit) ? "true" : "false");
core.setOutput("issue_number", String(context.payload.issue?.number || ""));
core.setOutput("issue_url", context.payload.issue?.html_url || "");
core.setOutput("comment_body", body);
- name: Checkout repository
if: steps.detect.outputs.hit == 'true'
uses: actions/checkout@v4
# Node v20 is needed for Cline CLI on GitHub Actions Linux
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Setup Cline CLI
if: steps.detect.outputs.hit == 'true'
run: |
# Install the Cline CLI
sudo npm install -g cline
- name: Create Cline Instance
if: steps.detect.outputs.hit == 'true'
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
CLINE_DIR: ${{ runner.temp }}/cline
run: |
# Create instance and capture output
INSTANCE_OUTPUT=$(cline instance new 2>&1)
# Parse address from output (format: " Address: 127.0.0.1:36733")
CLINE_ADDRESS=$(echo "$INSTANCE_OUTPUT" | grep "Address:" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]+')
echo "CLINE_ADDRESS=$CLINE_ADDRESS" >> $GITHUB_ENV
# Configure API key
cline config set open-router-api-key=$OPENROUTER_API_KEY --address $CLINE_ADDRESS -v
- name: Download analyze script
if: steps.detect.outputs.hit == 'true'
run: |
export GITORG="YOUR-GITHUB-ORG"
export GITREPO="YOUR-GITHUB-REPO"
curl -L https://raw.githubusercontent.com/${GITORG}/${GITREPO}/refs/heads/main/git-scripts/analyze-issue.sh -o analyze-issue.sh
chmod +x analyze-issue.sh
- name: Run analysis
if: steps.detect.outputs.hit == 'true'
id: analyze
env:
ISSUE_URL: ${{ steps.detect.outputs.issue_url }}
COMMENT: ${{ steps.detect.outputs.comment_body }}
CLINE_ADDRESS: ${{ env.CLINE_ADDRESS }}
run: |
set -euo pipefail
RESULT=$(./analyze-issue.sh "${ISSUE_URL}" "Analyze this issue. The user asked: ${COMMENT}" "$CLINE_ADDRESS")
{
echo 'result<<EOF'
printf "%s\n" "$RESULT"
echo 'EOF'
} >> "$GITHUB_OUTPUT"
- name: Post response
if: steps.detect.outputs.hit == 'true'
uses: actions/github-script@v7
env:
ISSUE_NUMBER: ${{ steps.detect.outputs.issue_number }}
RESULT: ${{ steps.analyze.outputs.result }}
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(process.env.ISSUE_NUMBER),
body: process.env.RESULT || "(no output)"
});
```
</Accordion>
<Warning>
**You MUST edit the workflow file before committing!**
Open `.github/workflows/cline-responder.yml` and update the "Download analyze script" step within the workflow to specify your GitHub organization and repository where the analysis script is stored:
```yaml
export GITORG="YOUR-GITHUB-ORG" # Change this!
export GITREPO="YOUR-GITHUB-REPO" # Change this!
```
**Example:** If your repository is `github.com/acme/myproject`, set:
```yaml
export GITORG="acme"
export GITREPO="myproject"
```
This tells the workflow where to download the analysis script from your repository after you commit it in step 3.
</Warning>
The workflow will look for new or updated issues, check for `@cline` mentions, and then
start up an instance of the Cline CLI to dig into the issue, providing feedback
as a reply to the issue.
### 2. Configure API Keys
Add your AI provider API keys as repository secrets:
1. Go to your GitHub repository
2. Navigate to **Settings** → **Environment** and Add a new environment.
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/ss01-environment.png" alt="Navigate to Actions secrets" width="600" />
</Frame>
Make sure to name it "cline-actions" so that it matches the `environment`
value at the top of the `cline-responder.yml` file.
3. Click **New repository secret**
4. Add a secret for the `OPENROUTER_API_KEY` with a value of an API key from
[openrouter.com](https://openrouter.com).
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/ss02-api-key.png" alt="Add API key secret" width="600" />
</Frame>
5. Verify your secret is configured:
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/ss03-ready.png" alt="API key configured" width="600" />
</Frame>
Now you're ready to supply Cline with the credentials it needs in a GitHub Action.
### 3. Add Analysis Script
Add the analysis script from the `github-issue-rca` sample to your repository. **First, you'll need to create a `git-scripts` directory in your repository root where the script will be located.** Choose one of these options:
**Option A: Download directly (Recommended)**
```bash
# In your repository root, create the directory and download the script
mkdir -p git-scripts
curl -o git-scripts/analyze-issue.sh https://raw.githubusercontent.com/cline/cline/main/src/samples/cli/github-issue-rca/analyze-issue.sh
chmod +x git-scripts/analyze-issue.sh
```
**Option B: Manual copy-paste**
Create the directory and file manually, then paste the script content:
```bash
# In your repository root
mkdir -p git-scripts
# Create and edit the file with your preferred editor
nano git-scripts/analyze-issue.sh # or use vim, code, etc.
```
<Accordion title="Click to view the complete analyze-issue.sh script">
```bash
#!/bin/bash
# Analyze a GitHub issue using Cline CLI
if [ -z "$1" ]; then
echo "Usage: $0 <github-issue-url> [prompt] [address]"
echo "Example: $0 https://github.com/owner/repo/issues/123"
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?'"
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?' 127.0.0.1:46529"
exit 1
fi
# Gather the args
ISSUE_URL="$1"
PROMPT="${2:-What is the root cause of this issue?}"
if [ -n "$3" ]; then
ADDRESS="--address $3"
fi
# Ask Cline for its analysis, showing only the summary
cline -y "$PROMPT: $ISSUE_URL" --mode act $ADDRESS -F json | \
sed -n '/^{/,$p' | \
jq -r 'select(.say == "completion_result") | .text' | \
sed 's/\\n/\n/g'
```
After pasting the script content, make it executable:
```bash
chmod +x git-scripts/analyze-issue.sh
```
</Accordion>
This analysis script calls Cline to execute a prompt on a GitHub issue,
summarizing the output to populate the reply to the issue.
### 4. Commit and Push
```bash
git add .github/workflows/cline-responder.yml
git add git-scripts/analyze-issue.sh
git commit -m "Add Cline issue assistant workflow"
git push
```
## Usage
Once set up, simply mention `@cline` in any issue comment:
```
@cline what's causing this error?
@cline analyze the root cause
@cline what are the security implications?
```
GitHub Actions will:
1. Detect the `@cline` mention
2. Start a Cline CLI instance
3. Download the analysis script
4. Analyze the issue using act mode with yolo (fully autonomous)
5. Post Cline's analysis as a new comment
**Note**: The workflow only triggers on issue comments, not pull request
comments.
## How It Works
The workflow (`cline-responder.yml`):
1. **Triggers** on issue comments (created or edited)
2. **Detects** `@cline` mentions (case-insensitive)
3. **Installs** Cline CLI globally using npm
4. **Creates** a Cline instance using `cline instance new`
5. **Configures** authentication using `cline config set open-router-api-key=...
--address ...`
6. **Downloads** the reusable `analyze-issue.sh` script from the
`github-issue-rca` sample
7. **Runs** analysis with the instance address
8. **Posts** the analysis result as a comment
## Related Samples
- **[github-issue-rca](./github-issue-rca)**: The reusable script that powers this integration
+383
View File
@@ -0,0 +1,383 @@
---
title: "GitHub Issue RCA Sample"
description: "Automated GitHub issue analysis using Cline CLI to identify root causes."
---
# GitHub Root Cause Analysis
Automated GitHub issue analysis using Cline CLI. This script uses Cline's autonomous AI capabilities to fetch, analyze, and identify root causes of GitHub issues, outputting clean, parseable results that can be easily integrated into your development workflows.
<Note>
**New to Cline CLI?** This sample assumes you have already completed the [Installation Guide](https://docs.cline.bot/cline-cli/installation) and authenticated with `cline auth`. If you haven't set up Cline CLI yet, please start there first.
</Note>
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/cli-rca.gif" alt="CLI Root Cause Analysis Demo" width="600" />
</Frame>
## Prerequisites
This sample assumes you have already:
- **Cline CLI** installed and authenticated ([Installation Guide](https://docs.cline.bot/cline-cli/installation))
- **At least one AI model provider** configured (e.g., OpenRouter, Anthropic, OpenAI)
- **Basic familiarity** with Cline CLI commands
Additionally, you'll need:
- **GitHub CLI** (`gh`) installed and authenticated
- **jq** installed for JSON parsing
- **bash** shell (or compatible shell)
### Installation Instructions
#### macOS
<Note>
These instructions require [Homebrew](https://brew.sh/) to be installed. If you don't have Homebrew, install it first by running:
```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```
</Note>
```bash
# Install GitHub CLI
brew install gh
# Install jq
brew install jq
# Authenticate with GitHub
gh auth login
```
#### Linux
```bash
# Install GitHub CLI (Debian/Ubuntu)
sudo apt install gh
# Or for other Linux distributions, see: https://cli.github.com/manual/installation
# Install jq (Debian/Ubuntu)
sudo apt install jq
# Authenticate with GitHub
gh auth login
```
## Getting the Script
**Option 1: Download directly with curl**
```bash
curl -O https://raw.githubusercontent.com/cline/cline/main/src/samples/cli/github-issue-rca/analyze-issue.sh
```
**Option 2: Copy the full script**
<Accordion title="Click to view the complete analyze-issue.sh script">
```bash
#!/bin/bash
# Analyze a GitHub issue using Cline CLI
if [ -z "$1" ]; then
echo "Usage: $0 <github-issue-url> [prompt] [address]"
echo "Example: $0 https://github.com/owner/repo/issues/123"
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?'"
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?' 127.0.0.1:46529"
exit 1
fi
# Gather the args
ISSUE_URL="$1"
PROMPT="${2:-What is the root cause of this issue?}"
if [ -n "$3" ]; then
ADDRESS="--address $3"
fi
# Ask Cline for its analysis, showing only the summary
cline -y "$PROMPT: $ISSUE_URL" --mode act $ADDRESS -F json | \
sed -n '/^{/,$p' | \
jq -r 'select(.say == "completion_result") | .text' | \
sed 's/\\n/\n/g'
```
</Accordion>
<Note>
**After downloading or creating the script**, make it executable by running:
```bash
chmod +x analyze-issue.sh
```
</Note>
## Quick Usage Examples
### Basic Usage
Run this command in your terminal from the directory where you saved the script to analyze an issue with the default root cause prompt:
```bash
./analyze-issue.sh https://github.com/owner/repo/issues/123
```
This will:
- Fetch issue #123 from the repository
- Analyze the issue to identify root causes
- Provide detailed analysis with recommendations
### Custom Analysis Prompt
Ask specific questions about the issue:
```bash
./analyze-issue.sh https://github.com/owner/repo/issues/456 "What is the security impact?"
```
### Using Specific Cline Instance
Target a particular Cline instance by address:
```bash
./analyze-issue.sh https://github.com/owner/repo/issues/123 \
"What is the root cause of this issue?" \
127.0.0.1:46529
```
<Warning>
This is useful when:
- Running multiple Cline instances
- Using a remote Cline server
- Testing with specific configurations
</Warning>
<Note>
The script will automatically handle everything: fetching the issue, analyzing it with Cline, and displaying the results. The analysis typically takes 30-60 seconds depending on the issue complexity.
</Note>
## How It Works
Let's analyze each component of the script to understand how it works.
### Argument Validation
The script validates input and provides usage instructions:
```bash
if [ -z "$1" ]; then
echo "Usage: $0 <github-issue-url> [prompt] [address]"
echo "Example: $0 https://github.com/owner/repo/issues/123"
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause?'"
echo "Example: $0 https://github.com/owner/repo/issues/123 'Analyze security impact' 127.0.0.1:46529"
exit 1
fi
```
**Key Points:**
- Validates required GitHub issue URL
- Shows clear usage examples
- Supports optional custom prompt
- Supports optional Cline instance address
### Argument Parsing
The script extracts and sets up the arguments:
```bash
# Gather the args
ISSUE_URL="$1"
PROMPT="${2:-What is the root cause of this issue?}"
if [ -n "$3" ]; then
ADDRESS="--address $3"
fi
```
**Explanation:**
- `ISSUE_URL="$1"` - First argument is always the issue URL
- `PROMPT="${2:-...}"` - Second argument is optional, defaults to root cause analysis
- `ADDRESS` - Third argument is optional, only set if provided
### The Core Analysis Pipeline
This is where the magic happens:
```bash
# Ask Cline for his analysis, showing only the summary
cline -y "$PROMPT: $ISSUE_URL" --mode act $ADDRESS -F json | \
sed -n '/^{/,$p' | \
jq -r 'select(.say == "completion_result") | .text' | \
sed 's/\\n/\n/g'
```
<Accordion title="Pipeline Breakdown: Understanding Each Component">
**1. `cline -y "$PROMPT: $ISSUE_URL"`**
- `-y` enables yolo mode (no user interaction)
- Constructs prompt with issue URL
**2. `--mode act`**
- Enables act mode for active investigation
- Allows Cline to use tools (read files, run commands, etc.)
**3. `$ADDRESS`**
- Optional address flag for specific instance
- Expands to `--address <ip:port>` if set
**4. `-F json`**
- Outputs in JSON format for parsing
**5. `sed -n '/^{/,$p'`**
- Extracts JSON from output
- Skips any non-JSON prefix lines
**6. `jq -r 'select(.say == "completion_result") | .text'`**
- Filters for completion result messages
- Extracts the text field
- `-r` outputs raw strings (no JSON quotes)
**7. `sed 's/\\n/\n/g'`**
- Converts escaped newlines to actual newlines
- Makes output readable
</Accordion>
## Sample Output
Here's an example analyzing a real Flutter issue:
```bash
$ ./analyze-issue.sh https://github.com/csells/flutter_counter/issues/2
```
**Output:**
```markdown
**Root Cause Analysis of Issue #2: "setState isn't cutting it"**
After examining the GitHub issue and analyzing the Flutter counter codebase,
I've identified the root cause of why setState() is insufficient for this
project's needs:
## Current Implementation Problems
The current Flutter counter app uses setState() for state management, which
has several limitations:
1. **Local State Only**: setState() only works within a single widget, making
it difficult to share state across the app
2. **Rebuild Overhead**: Every setState() call rebuilds the entire widget tree,
causing performance issues with complex UIs
3. **No State Persistence**: State is lost when the widget is disposed
4. **Testing Challenges**: setState-based logic is tightly coupled to the UI,
making unit testing difficult
## Why This Matters
As the app grows beyond a simple counter, these limitations become critical:
- Multiple screens need to access the count
- State needs to persist across navigation
- Business logic should be testable independently
- UI should only rebuild when necessary
## Recommended Solutions
The issue mentions "Provider or Bloc" - both are excellent alternatives:
1. **Provider**: Simple, lightweight state management using InheritedWidget
- Easy migration path from setState
- Good for small to medium apps
- Official Flutter recommendation
2. **Bloc**: More structured approach with clear separation between events,
states, and business logic
- Better for complex apps
- Excellent testability
- Clear architectural patterns
3. **Riverpod**: Modern alternative to Provider with better performance and
developer experience
- Compile-time safety
- Better testing support
- More flexible than Provider
4. **GetX**: Full-featured solution with state management, routing, and
dependency injection
- Minimal boilerplate
- Fast and lightweight
- All-in-one solution
## Next Steps
The current codebase needs refactoring to implement proper state management
architecture to handle more complex state scenarios effectively. Provider
would be the easiest migration path while Bloc provides better long-term
scalability.
```
## When to Use This Pattern
This script pattern is ideal for various development scenarios where automated GitHub issue analysis can accelerate your workflow.
### Bug Investigation
Quickly analyze bug reports and identify root causes without manual code exploration:
```bash
./analyze-issue.sh https://github.com/project/repo/issues/123 \
"What is the root cause of this bug?"
```
### Feature Request Analysis
Understand context and implications of feature requests:
```bash
./analyze-issue.sh https://github.com/project/repo/issues/456 \
"What are the implementation challenges?"
```
### Security Audits
Assess security implications of reported issues:
```bash
./analyze-issue.sh https://github.com/project/repo/issues/789 \
"What are the security implications?"
```
### Documentation Generation
Generate detailed technical documentation from issues:
```bash
./analyze-issue.sh https://github.com/project/repo/issues/654 \
"Provide detailed technical documentation for this issue"
```
### Code Review Assistance
Get second opinions on proposed changes:
```bash
./analyze-issue.sh https://github.com/project/repo/issues/987 \
"Review the proposed solution approach"
```
## Conclusion
This sample demonstrates how to build an autonomous GitHub issue analysis tool using Cline CLI:
1. **Building autonomous CLI tools** using Cline's capabilities
2. **Parsing structured JSON output** from Cline CLI
3. **Creating flexible automation scripts** with custom prompting
4. **Integrating with GitHub** for issue analysis
5. **Handling command-line arguments** effectively
This pattern can be adapted for many other automation scenarios, from pull request reviews to documentation generation to code quality analysis.
## Related Resources
- [CLI Installation Guide](https://docs.cline.bot/cline-cli/installation)
- [CLI Reference Documentation](https://docs.cline.bot/cline-cli/cli-reference)
- [Three Core Flows](https://docs.cline.bot/cline-cli/three-core-flows)
+32
View File
@@ -0,0 +1,32 @@
---
title: "Samples Overview"
description: Example implementations demonstrating Cline CLI capabilities
---
This section provides sample implementations that demonstrate various Cline CLI features and capabilities. Each sample includes complete code, detailed explanations, and real-world usage examples.
## Available Samples
<CardGroup cols={1}>
<Card
title="GitHub Root Cause Analysis"
icon="magnifying-glass-chart"
href="/cline-cli/samples/github-issue-rca"
>
A command-line script that uses Cline's autonomous AI capabilities to fetch, analyze, and identify root causes of GitHub issues. Features JSON output parsing and non-interactive execution.
</Card>
<Card
title="GitHub Integration (Actions)"
icon="github"
href="/cline-cli/samples/github-integration"
>
Automatically respond to GitHub issues by mentioning @cline in comments. Uses Cline CLI in GitHub Actions to create an AI-powered issue assistant that analyzes and responds autonomously.
</Card>
</CardGroup>
## Additional Resources
- [CLI Installation Guide](/cline-cli/installation)
- [CLI Reference Documentation](/cline-cli/cli-reference)
- [Three Core Flows](/cline-cli/three-core-flows)
+13
View File
@@ -88,6 +88,14 @@
"cline-cli/overview",
"cline-cli/installation",
"cline-cli/three-core-flows",
{
"group": "CLI Samples",
"pages": [
"cline-cli/samples/overview",
"cline-cli/samples/github-issue-rca",
"cline-cli/samples/github-integration"
]
},
"cline-cli/cli-reference"
]
},
@@ -130,6 +138,7 @@
"features/drag-and-drop",
"features/editing-messages",
"features/focus-chain",
"features/hooks",
"features/multiroot-workspace",
"features/plan-and-act",
{
@@ -315,6 +324,10 @@
{
"source": "/getting-started/your-first-task",
"destination": "/getting-started/your-first-project"
},
{
"source": "/cline-cli/samples",
"destination": "/cline-cli/samples/overview"
}
],
"search": {
+14
View File
@@ -81,6 +81,20 @@ your-project/
Cline's system prompt, on the other hand, is not user-editable ([here's where you can find it](https://github.com/cline/cline/blob/main/src/core/prompts/system.ts)). For a broader look at prompt engineering best practices, check out [this resource](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/overview).
### AGENTS.md Standard Support
Cline also supports the [AGENTS.md](https://agents.md/) standard as a fallback
(in addition to Cline Rules) by automatically detecting `AGENTS.md` files in
your workspace root. This allows you to use the same rules file across different AI
coding tools.
```
your-project/
├── AGENTS.md
├── src/
└── ...
```
### Tips for Writing Effective Cline Rules
- Be Clear and Concise: Use simple language and avoid ambiguity.
+5 -2
View File
@@ -3,7 +3,7 @@ title: "Dictation"
description: "Communicate with Cline using your voice for faster, more natural AI collaboration"
---
Dictation transforms how you work with AI. Instead of typing out complex thoughts, you speak naturally and share your complete intent. This isn't just about speed - though voice is faster - it's about unlocking the kind of fluid collaboration that typing can't match.
Dictation transforms how you work with AI. Instead of typing out complex thoughts, you speak naturally and share your complete intent. This isn't just about speed - though voice is faster - it's about enabling fluid collaboration that typing can't match.
## Why Voice Changes Everything
@@ -35,11 +35,14 @@ Dictation works with any AI model you've configured. The transcription happens t
## System Requirements
<Note>
Dictation is currently not available on Windows. Support for Windows is planned for a future release.
</Note>
Dictation uses FFmpeg to capture your voice across all platforms:
- **macOS**: FFmpeg (via Homebrew: `brew install ffmpeg`)
- **Linux**: FFmpeg (via apt: `sudo apt-get install ffmpeg`)
- **Windows**: FFmpeg (via winget: `winget install Gyan.FFmpeg`)
If you don't have FFmpeg installed, Cline will automatically detect this and prompt you to install it with a single click.
+419
View File
@@ -0,0 +1,419 @@
---
title: "Hooks"
sidebarTitle: "Hooks"
description: "Inject custom logic into Cline's workflow to validate operations, monitor tool usage, and shape AI decisions"
---
Hooks let you inject custom logic into Cline's workflow at key moments. Think of them as automated checkpoints where you can validate operations before they execute, monitor tool usage as it happens, and shape how Cline makes decisions.
Hooks run automatically when specific events happen during development. They receive detailed information about each operation, can block problematic actions before they cause issues, and can inject context that guides future AI decisions.
The real power comes from combining these capabilities. You can:
- Stop operations before they cause problems (like creating `.js` files in a TypeScript project)
- Learn from what's happening and build up project knowledge over time
- Monitor performance and catch issues as they emerge
- Track everything for analytics or compliance
- Trigger external tools or services at the right moments
<Warning>
Hooks are currently supported on macOS and Linux only. Windows support is not available.
</Warning>
## Getting Started
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/hooks.gif" alt="Hooks in action" />
</Frame>
Enabling hooks in Cline is straightforward. Here's what you need to do:
<Steps>
<Step title="Enable Hooks in Settings">
Open Cline settings and check the **"Enable Hooks"** checkbox.
You can find this setting by:
1. Opening Cline
2. Click the "Settings" button on the top right corner
3. Click the "Feature" section in the left side navigation menu.
4. Scroll down until you see the "Enable Hooks" checkbox and check it.
</Step>
<Step title="Choose Your Hook Location">
Decide where to place your hooks:
**For personal or organization-wide hooks:**
- Create hooks in `~/Documents/Cline/Rules/Hooks/`
- These apply to all workspaces automatically
**For project-specific hooks:**
- Create hooks in `.clinerules/hooks/` in your project root
- These only apply to the specific workspace
- Commit them to version control so your team can use them too
</Step>
<Step title="Create Your First Hook">
Hook files must have exact names with no file extensions. For example, to create a TaskStart hook:
```bash
# Create the hook file
vim .clinerules/hooks/TaskStart
```
Add your script (must start with shebang)
``` bash
#!/usr/bin/env bash
# Store piped input into a variable
input=$(cat)
# Dump the entire JSON payload
echo "$input" | jq .
# Get the type of a field
echo "$input" | jq -r '.timestamp | type'
```
This example script demonstrates the key mechanics of hook input/output: reading the JSON payload from stdin with `input=$(cat)`, and using `jq` to inspect the data structure and field types that your hook receives. This helps you understand what data is available before building more complex hook logic.
**Make it executable**
```bash
chmod +x .clinerules/hooks/TaskStart
```
</Step>
<Step title="Test Your Hook">
Start a task in Cline and verify your hook executes.
</Step>
</Steps>
<Tip>
Start with a simple hook that just logs information before building complex validation logic. This helps you understand the data structure and timing.
</Tip>
## What You Can Build
Once you understand the basics, hooks open up creative possibilities:
<CardGroup cols={2}>
<Card title="Intelligent Code Review" icon="code-branch">
Run linters or custom validators before files get saved. Block commits that don't pass checks. Track code quality metrics over time.
</Card>
<Card title="Security Enforcement" icon="shield-halved">
Prevent operations that violate security policies. Detect when sensitive data might be exposed. Audit all file access for compliance.
</Card>
<Card title="Development Analytics" icon="chart-line">
Measure how long different operations take. Identify patterns in how the AI works. Generate productivity reports from hook data.
</Card>
<Card title="Integration Hub" icon="plug">
Connect to issue trackers when certain keywords appear. Update project management tools. Sync with external APIs at the right moments.
</Card>
</CardGroup>
The key is combining hooks with external tools. A hook can be the glue between Cline's workflow and the rest of your development ecosystem.
## Hook Types
Cline provides multiple hook types that let you tap into different stages of the AI workflow. They're organized into categories based on their trigger points and use cases.
<Note>
The hook names below are the exact file names you need to create. For example, to use the TaskStart hook, create a file named `TaskStart` (no file extension) in your hooks directory.
</Note>
Each hook receives base fields in addition to its specific data: `clineVersion`, `hookName`, `timestamp`, `taskId`, `workspaceRoots`, `userId`.
### Tool Execution
These hooks intercept and validate tool operations before and after they execute. Use them to enforce policies, track changes, and learn from operations.
#### PreToolUse
Runs before any tool executes. Use it to block invalid operations, validate parameters, and enforce project policies before changes happen.
**Input Fields:**
```json
{
"clineVersion": "string",
"hookName": "PreToolUse",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"preToolUse": {
"toolName": "string",
"parameters": {}
}
}
```
#### PostToolUse
Runs after a tool completes. Use it to learn from results, track performance metrics, and build project knowledge based on operations performed.
**Input Fields:**
```json
{
"clineVersion": "string",
"hookName": "PostToolUse",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"postToolUse": {
"toolName": "string",
"parameters": {},
"result": "string",
"success": boolean,
"executionTimeMs": number
}
}
```
### User Interaction
These hooks monitor and enhance user communication with Cline. Use them to validate input, inject context, and track interaction patterns.
#### UserPromptSubmit
Runs when a user sends a message to Cline. Use it to validate input, inject context based on the prompt, and track interaction patterns.
**Input Fields:**
```json
{
"clineVersion": "string",
"hookName": "UserPromptSubmit",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"userPromptSubmit": {
"prompt": "string",
"attachments": ["string"]
}
}
```
### Task Lifecycle
These hooks monitor and respond to task state changes from start to finish. Use them to track progress, restore state, and trigger workflows.
#### TaskStart
Runs when a new task begins. Use it to detect project type, initialize tracking, and inject initial context that shapes how Cline approaches the work.
**Input Fields:**
```json
{
"clineVersion": "string",
"hookName": "TaskStart",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"taskStart": {
"taskMetadata": {
"taskId": "string",
"ulid": "string",
"initialTask": "string"
}
}
}
```
#### TaskResume
Runs when a task resumes after interruption. Use it to restore state, refresh context, and log resumption for analytics or external system notifications.
**Input Fields:**
```json
{
"clineVersion": "string",
"hookName": "TaskResume",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"taskResume": {
"taskMetadata": {
"taskId": "string",
"ulid": "string"
},
"previousState": {
"lastMessageTs": "string",
"messageCount": "string",
"conversationHistoryDeleted": "string"
}
}
}
```
#### TaskCancel
Runs when a task is cancelled. Use it to cleanup resources, log cancellation details, and notify external systems about interrupted work.
**Input Fields:**
```json
{
"clineVersion": "string",
"hookName": "TaskCancel",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"taskCancel": {
"taskMetadata": {
"taskId": "string",
"ulid": "string",
"completionStatus": "string"
}
}
}
```
{/*
#### TaskComplete
Runs when a task finishes successfully. Use it for final cleanup, tracking metrics, generating reports, and triggering post-task workflows.
**Input Fields:**
```json
{
"clineVersion": "string",
"hookName": "TaskComplete",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"taskComplete": {
"taskMetadata": {
"taskId": "string",
"ulid": "string"
}
}
}
```
*/}
### System Events
These hooks monitor internal Cline operations and system-level events. Use them to track context usage, log system behavior, and analyze performance patterns.
{/*
#### PreCompact
Runs before conversation context is truncated to fit token limits. Use it to monitor compaction frequency, log events, and track context usage patterns.
**Input Fields:**
```json
{
"clineVersion": "string",
"hookName": "PreCompact",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"preCompact": {
"contextSize": number,
"messagesToCompact": number,
"compactionStrategy": "string"
}
}
```
*/}
### JSON Communication
Hooks receive JSON via stdin and return JSON via stdout.
**Output structure:**
```json
{
"cancel": false,
"contextModification": "WORKSPACE_RULES: Use TypeScript",
"errorMessage": "Error details if blocking"
}
```
Your hook script can output logging or diagnostic information to stdout during execution, as long as the JSON response is the last thing written. Cline will parse only the final JSON object from stdout.
For example:
```bash
#!/usr/bin/env bash
echo "Processing hook..." # This is fine
echo "Tool: $tool_name" # This is also fine
# The JSON must be last:
echo '{"cancel": false}'
```
The `cancel` field controls whether execution continues. Set it to `true` to block an action, `false` to allow it.
The `contextModification` field injects text into the conversation. This affects future AI decisions, not the current one. Use prefixes like `WORKSPACE_RULES:` or `PERFORMANCE:` to help categorize the context.
### Understanding Context Timing
Context injection affects future decisions, not current ones. When a hook runs:
1. The AI has already decided what to do
2. The hook can block or allow it
3. Any context gets added to the conversation
4. The next AI request sees that context
This means PreToolUse hooks are for blocking bad actions, while PostToolUse hooks are for learning from completed ones.
## Troubleshooting
### Hook Not Running
- Ensure the "Enable Hooks" setting is checked
- Verify the hook file is executable (`chmod +x hookname`)
- Check the hook file has no syntax errors
- Look for errors in VSCode's Output panel (Cline channel)
### Hook Timing Out
- Reduce complexity of the hook script
- Avoid expensive operations (network calls, heavy computations)
- Consider moving complex logic to a background process
### Context Not Affecting Behavior
Remember that context modifications affect future AI decisions, not the current operation. The AI's current behavior is based on the previous "API Request..." block, and your `contextModification` gets injected into the next "API Request..." block. This means if you need immediate effect, you should use PreToolUse hooks for validation and return `cancel: true` in your hook's JSON response to block Cline from continuing.
When adding context, ensure your modifications are clear and actionable so the AI can understand and apply them effectively. Also check that your context isn't being truncated due to the 50KB limit, as this could prevent important information from reaching the AI.
### Handling Strings with Quotes in JSON Payloads
When your hook needs to include strings containing unescaped quote characters (`"`) in JSON output, use jq's `--arg` flag for proper escaping:
```bash
#!/usr/bin/env bash
# When $output contains unescaped quote characters (")...
output='{"foo":"bar"}'
# Use the --arg flag for automatic string escaping
jq -n --arg ctx "$output" '{cancel: false, contextModification: $ctx}'
# This will result in:
# {
# "cancel": false,
# "contextModification": "{\"foo\":\"bar\"}"
# }
```
The `--arg` flag automatically escapes special characters, preventing JSON parsing errors when your context modification includes complex strings or nested JSON structures.
<Warning>
Hooks run with the same permissions as VS Code. They can access all workspace files and environment variables. Review hooks from untrusted sources before enabling them.
</Warning>
## Related Features
Hooks complement other Cline features:
- [Cline Rules](/features/cline-rules) define high-level guidance that hooks can enforce
- [Checkpoints](/features/checkpoints) let you roll back changes if a hook didn't catch an issue
- [Auto-Approve](/features/auto-approve) works well with hooks as safety nets for automated operations
+58 -6
View File
@@ -29,11 +29,33 @@ The "Remote Servers" tab allows you to connect to any MCP server that's accessib
2. Fill in the required information:
- **Server Name**: Provide a unique, descriptive name for the server
- **Server URL**: Enter the complete URL endpoint of the MCP server (e.g., `https://example.com/mcp-sse`)
- **Transport Type**: Select the connection protocol (Streamable HTTP is recommended for modern servers)
3. Click "Add Server" to initiate the connection
4. Cline will attempt to connect to the server and display the connection status
> **Note**: When connecting to a remote server, ensure you trust the source, as MCP servers can execute code in your environment.
#### Transport Types
Cline supports two transport protocols for remote MCP servers:
- **Streamable HTTP (Recommended)**: The modern MCP transport protocol with better performance, reliability, and full OAuth 2.1 authentication support. Use this for most remote servers.
- **SSE (Legacy)**: Server-Sent Events transport. Use this only if the server specifically requires SSE or doesn't support Streamable HTTP.
#### OAuth Authentication
Some MCP servers (like Vercel's MCP) require OAuth authentication to access your data securely. When connecting to an OAuth-enabled server:
1. Add the server as usual with its URL
2. If the server requires authentication, you'll see an error message asking to authenticate.
3. Click the **"Authenticate"** button that appears
4. Your browser will open to the server's authorization page
5. Sign in and grant permission
6. You'll be redirected back to Cline automatically
7. The server will connect and show a green status dot
Once authenticated, your credentials are securely stored and the server will reconnect automatically when you reload Cline. You won't need to authenticate again unless you delete the server or your credentials expire.
### Remote Server Discovery
If you're looking for MCP servers to connect to, several third-party marketplaces provide directories of available servers with various capabilities.
@@ -90,9 +112,20 @@ Toggle the switch next to each server to enable or disable it:
If a server fails to connect:
1. An error message will be displayed with details about the failure
2. Check that the server URL is correct and the server is running
3. Use the "Restart Server" button to attempt reconnection
4. If problems persist, you can delete the server and try adding it again
2. **For OAuth errors**: Click the "Authenticate" button to complete the authorization flow
3. Check that the server URL is correct and the server is running
4. Try selecting a different transport type (Streamable HTTP vs SSE)
5. Use the "Restart Server" button to attempt reconnection
6. If problems persist, you can delete the server and try adding it again
#### OAuth-Specific Issues
If you're having trouble authenticating with an OAuth-enabled server:
- **"Authentication required" persists**: Make sure you completed the authorization flow in your browser and didn't cancel it
- **Browser doesn't open**: Check your system's default browser settings and ensure external URLs can be opened
- **Redirect errors**: Verify you're using the latest version of Cline - older versions may not support OAuth
- **Reset authentication**: Delete the server and re-add it to start fresh with a new OAuth flow
### Advanced Configuration
@@ -105,10 +138,11 @@ For advanced users, Cline stores MCP server configurations in a JSON file that c
{
"mcpServers": {
"exampleServer": {
"url": "https://example.com/mcp-sse",
"url": "https://example.com/mcp-server",
"type": "streamableHttp",
"disabled": false,
"autoApprove": ["tool1", "tool2"],
"timeout": 30
"timeout": 60
}
}
}
@@ -117,9 +151,10 @@ For advanced users, Cline stores MCP server configurations in a JSON file that c
Key configuration options:
- **url**: The endpoint URL (for remote servers)
- **type**: Transport protocol - `"streamableHttp"` (recommended) or `"sse"` (legacy)
- **disabled**: Whether the server is currently enabled (true/false)
- **autoApprove**: List of tool names that don't require confirmation
- **timeout**: Maximum time in seconds to wait for server responses
- **timeout**: Maximum time in seconds to wait for server responses (default: 60)
For additional MCP settings, click the "Advanced MCP Settings" link to access VSCode settings.
@@ -130,3 +165,20 @@ Once connected, Cline can use the tools and resources provided by the MCP server
1. A tool approval prompt will appear (unless auto-approved)
2. Review the tool details and parameters before approving
3. The tool will execute and return results to Cline
### Example: Connecting to Vercel MCP
[Vercel MCP](https://vercel.com/docs/mcp/vercel-mcp) is an OAuth-enabled server that provides tools for managing your Vercel projects and deployments:
1. Click "Remote Servers" tab
2. Enter:
- **Server Name**: `vercel`
- **Server URL**: `https://mcp.vercel.com`
- **Transport Type**: Streamable HTTP (pre-selected)
3. Click "Add Server"
4. You'll see "Authentication required" - click the **"Authenticate"** button
5. Sign in to Vercel in your browser and authorize Cline
6. Return to Cline - the server will automatically connect
7. Vercel's tools (deploy, logs, projects) are now available to Cline!
Your Vercel authentication persists across sessions, so you won't need to re-authenticate each time you use Cline.
+1 -2
View File
@@ -18,8 +18,7 @@ Cerebras delivers the world's fastest AI inference through their revolutionary w
Cline supports the following Cerebras models:
- `qwen-3-coder-480b-free` (Free tier) - High-performance coding model at no cost
- `qwen-3-coder-480b` - Flagship 480B parameter coding model
- `zai-glm-4.6` - Intelligent general purpose model with 1,500 tokens/s
- `qwen-3-235b-a22b-instruct-2507` - Advanced instruction-following model
- `qwen-3-235b-a22b-thinking-2507` - Reasoning model with step-by-step thinking
- `llama-3.3-70b` - Meta's Llama 3.3 model optimized for speed
+187 -68
View File
@@ -1,12 +1,12 @@
{
"name": "claude-dev",
"version": "3.35.1",
"version": "3.37.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-dev",
"version": "3.35.1",
"version": "3.37.0",
"license": "Apache-2.0",
"dependencies": {
"@anthropic-ai/sdk": "^0.37.0",
@@ -41,8 +41,8 @@
"@opentelemetry/sdk-trace-node": "^1.30.1",
"@opentelemetry/semantic-conventions": "^1.37.0",
"@playwright/test": "^1.55.1",
"@sap-ai-sdk/ai-api": "^1.17.0",
"@sap-ai-sdk/orchestration": "^1.17.0",
"@sap-ai-sdk/ai-api": "^2.1.0",
"@sap-ai-sdk/orchestration": "^2.1.0",
"@sentry/browser": "^9.12.0",
"@streamparser/json": "^0.0.22",
"@tailwindcss/vite": "^4.1.14",
@@ -72,6 +72,7 @@
"jschardet": "^3.1.4",
"jwt-decode": "^4.0.0",
"mammoth": "^1.11.0",
"nanoid": "^5.1.6",
"nice-grpc": "^2.1.12",
"node-machine-id": "^1.1.12",
"ollama": "^0.5.13",
@@ -1960,6 +1961,8 @@
},
"node_modules/@colors/colors": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
"integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
"license": "MIT",
"engines": {
"node": ">=0.1.90"
@@ -1987,6 +1990,8 @@
},
"node_modules/@dabh/diagnostics": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
"integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==",
"license": "MIT",
"dependencies": {
"colorspace": "1.1.x",
@@ -4827,92 +4832,110 @@
}
},
"node_modules/@sap-ai-sdk/ai-api": {
"version": "1.17.0",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@sap-ai-sdk/ai-api/-/ai-api-2.1.0.tgz",
"integrity": "sha512-zmG+TufFLTSx9G+s5lkWhYCoslQZdNhCM5AbmvsyVm4U6gD6i9Q+ShCGJRhW9QOxJkNATFIUI+qNplAkHVnSlA==",
"license": "Apache-2.0",
"dependencies": {
"@sap-ai-sdk/core": "^1.17.0",
"@sap-cloud-sdk/connectivity": "^4.1.1",
"@sap-cloud-sdk/util": "^4.1.1"
"@sap-ai-sdk/core": "^2.1.0",
"@sap-cloud-sdk/connectivity": "^4.1.2",
"@sap-cloud-sdk/util": "^4.1.2"
}
},
"node_modules/@sap-ai-sdk/core": {
"version": "1.17.0",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@sap-ai-sdk/core/-/core-2.1.0.tgz",
"integrity": "sha512-RVds/ArJR3jMTP1iuMS8TPHPHqvyO3vGYpdxIVNaDydtKdBwnR4crcbJbAkbAzP5HuakjIK1R0JLxro7XN2Cwg==",
"license": "Apache-2.0",
"dependencies": {
"@sap-cloud-sdk/connectivity": "^4.1.0",
"@sap-cloud-sdk/http-client": "^4.1.0",
"@sap-cloud-sdk/openapi": "^4.1.0",
"@sap-cloud-sdk/util": "^4.1.0"
"@sap-cloud-sdk/connectivity": "^4.1.2",
"@sap-cloud-sdk/http-client": "^4.1.2",
"@sap-cloud-sdk/openapi": "^4.1.2",
"@sap-cloud-sdk/util": "^4.1.2"
}
},
"node_modules/@sap-ai-sdk/orchestration": {
"version": "1.17.0",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@sap-ai-sdk/orchestration/-/orchestration-2.1.0.tgz",
"integrity": "sha512-1WahSmmoD2aUW1ipFw+ioKIPCuA01QGoF1Rhx6nyVCeRq9y8WXciO70JJzBa0LAy5zF/zrNOeODeHF4gh1rXNg==",
"license": "Apache-2.0",
"dependencies": {
"@sap-ai-sdk/ai-api": "^1.17.0",
"@sap-ai-sdk/core": "^1.17.0",
"@sap-ai-sdk/prompt-registry": "^1.17.0",
"@sap-cloud-sdk/util": "^4.1.1",
"@sap-ai-sdk/ai-api": "^2.1.0",
"@sap-ai-sdk/core": "^2.1.0",
"@sap-ai-sdk/prompt-registry": "^2.1.0",
"@sap-cloud-sdk/util": "^4.1.2",
"yaml": "^2.8.1"
}
},
"node_modules/@sap-ai-sdk/prompt-registry": {
"version": "1.17.0",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@sap-ai-sdk/prompt-registry/-/prompt-registry-2.1.0.tgz",
"integrity": "sha512-vCfJv8Er8knfcwKRYBiEaRWXgb6iYz8f/t4ZU86ad7EyjKfEVb/KRQVw2rGs1p9XW5QCJpdE5/S2h6zSvhErdA==",
"license": "Apache-2.0",
"dependencies": {
"@sap-ai-sdk/core": "^1.17.0",
"@sap-ai-sdk/core": "^2.1.0",
"zod": "^3.25.76"
}
},
"node_modules/@sap-cloud-sdk/connectivity": {
"version": "4.1.1",
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/@sap-cloud-sdk/connectivity/-/connectivity-4.1.2.tgz",
"integrity": "sha512-PJNBg+yhyo4Y/PZBcqbN8eD+RFVolgOcWgqVTdHef6Wch8t6SVlDqxLzf4mJ4nTpAY2E3ERFjIwlP+MUUXcf5A==",
"license": "Apache-2.0",
"dependencies": {
"@sap-cloud-sdk/resilience": "^4.1.1",
"@sap-cloud-sdk/util": "^4.1.1",
"@sap/xsenv": "^5.6.1",
"@sap/xssec": "^4.9.0",
"@sap-cloud-sdk/resilience": "^4.1.2",
"@sap-cloud-sdk/util": "^4.1.2",
"@sap/xsenv": "^6.0.0",
"@sap/xssec": "^4.9.2",
"async-retry": "^1.3.3",
"axios": "^1.11.0",
"axios": "^1.12.2",
"jsonwebtoken": "^9.0.2"
}
},
"node_modules/@sap-cloud-sdk/http-client": {
"version": "4.1.1",
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/@sap-cloud-sdk/http-client/-/http-client-4.1.2.tgz",
"integrity": "sha512-qQny7j22oyZxMZ64S6FSmtt5CCB4JyZND043ZTURamoIyuXsM3RegRN+rhjzwoBP4NLws4Rt4VfUOQ/X+DXXFQ==",
"license": "Apache-2.0",
"dependencies": {
"@sap-cloud-sdk/connectivity": "^4.1.1",
"@sap-cloud-sdk/resilience": "^4.1.1",
"@sap-cloud-sdk/util": "^4.1.1",
"axios": "^1.11.0"
"@sap-cloud-sdk/connectivity": "^4.1.2",
"@sap-cloud-sdk/resilience": "^4.1.2",
"@sap-cloud-sdk/util": "^4.1.2",
"axios": "^1.12.2"
}
},
"node_modules/@sap-cloud-sdk/openapi": {
"version": "4.1.1",
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/@sap-cloud-sdk/openapi/-/openapi-4.1.2.tgz",
"integrity": "sha512-KJ/xjnmvKwLhlUv6duoH6pLDYS34NHryS44j5vSTJMK4EzIfKo2R62RKAM3Pm+3zkWnQh9U2Q48Hbpornt8k1Q==",
"license": "Apache-2.0",
"dependencies": {
"@sap-cloud-sdk/connectivity": "^4.1.1",
"@sap-cloud-sdk/http-client": "^4.1.1",
"@sap-cloud-sdk/resilience": "^4.1.1",
"@sap-cloud-sdk/util": "^4.1.1",
"axios": "^1.11.0"
"@sap-cloud-sdk/connectivity": "^4.1.2",
"@sap-cloud-sdk/http-client": "^4.1.2",
"@sap-cloud-sdk/resilience": "^4.1.2",
"@sap-cloud-sdk/util": "^4.1.2",
"axios": "^1.12.2"
}
},
"node_modules/@sap-cloud-sdk/resilience": {
"version": "4.1.1",
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/@sap-cloud-sdk/resilience/-/resilience-4.1.2.tgz",
"integrity": "sha512-rMcM6Sn0WswNQK9583UCBEwzqvPSETae8GzzFrTQ7+JM6RA6PU5/WiPmhqzQQw1MFQvwuX6uMDwPf4U3AdemMQ==",
"license": "Apache-2.0",
"dependencies": {
"@sap-cloud-sdk/util": "^4.1.1",
"@sap-cloud-sdk/util": "^4.1.2",
"async-retry": "^1.3.3",
"axios": "^1.11.0",
"axios": "^1.12.2",
"opossum": "^9.0.0"
}
},
"node_modules/@sap-cloud-sdk/util": {
"version": "4.1.1",
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/@sap-cloud-sdk/util/-/util-4.1.2.tgz",
"integrity": "sha512-lsxsBc60pokMDCHvs0/CXTa6fjVEMzTeSyB8CerG6L0sN4sK8Ppm63VVcSh/E+X+U4aVMHmLuOmmd6XyLGAc3Q==",
"license": "Apache-2.0",
"dependencies": {
"axios": "^1.11.0",
"axios": "^1.12.2",
"chalk": "^4.1.0",
"logform": "^2.7.0",
"voca": "^1.4.1",
@@ -4922,6 +4945,8 @@
},
"node_modules/@sap-cloud-sdk/util/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
@@ -4936,6 +4961,8 @@
},
"node_modules/@sap-cloud-sdk/util/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
@@ -4945,19 +4972,36 @@
}
},
"node_modules/@sap/xsenv": {
"version": "5.6.1",
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@sap/xsenv/-/xsenv-6.0.0.tgz",
"integrity": "sha512-9bNpJXmxndWn5JbRCPPtbeMqldXOn2Od17ybS92PHd1rNkZ80IMmOURHNct5YSVQ1MKBIDAyC+ck6VL7cVAfUA==",
"license": "SEE LICENSE IN LICENSE file",
"dependencies": {
"debug": "4.4.0",
"debug": "4.4.1",
"node-cache": "^5.1.2",
"verror": "1.10.1"
},
"engines": {
"node": "^18.0.0 || ^20.0.0 || ^22.0.0"
"node": "^20.0.0 || ^22.0.0 || ^24.0.0"
}
},
"node_modules/@sap/xsenv/node_modules/debug": {
"version": "4.4.0",
"node_modules/@sap/xssec": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@sap/xssec/-/xssec-4.10.0.tgz",
"integrity": "sha512-6SxDorJpQRNjI0sCTwHoFkyECI5IrxnRZ3adoTY0XeGM8QcMoYmLnJIPXoa6AYSzMB89rjJ02j1PQVWACYt/Hg==",
"license": "SAP DEVELOPER LICENSE AGREEMENT",
"dependencies": {
"debug": "^4.4.3",
"jwt-decode": "^4"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sap/xssec/node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -4971,17 +5015,6 @@
}
}
},
"node_modules/@sap/xssec": {
"version": "4.9.0",
"license": "SAP DEVELOPER LICENSE AGREEMENT",
"dependencies": {
"debug": "^4.3.4",
"jwt-decode": "^4"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sec-ant/readable-stream": {
"version": "0.4.1",
"license": "MIT"
@@ -6534,6 +6567,8 @@
},
"node_modules/@types/triple-beam": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
"license": "MIT"
},
"node_modules/@types/turndown": {
@@ -7400,6 +7435,8 @@
},
"node_modules/assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
"license": "MIT",
"engines": {
"node": ">=0.8"
@@ -7445,6 +7482,8 @@
},
"node_modules/async-retry": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz",
"integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==",
"license": "MIT",
"dependencies": {
"retry": "0.13.1"
@@ -8308,6 +8347,8 @@
},
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"license": "MIT",
"engines": {
"node": ">=0.8"
@@ -8339,6 +8380,8 @@
},
"node_modules/color": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
"integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.3",
@@ -8361,6 +8404,8 @@
},
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"license": "MIT",
"dependencies": {
"color-name": "^1.0.0",
@@ -8376,6 +8421,8 @@
},
"node_modules/color/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
@@ -8383,6 +8430,8 @@
},
"node_modules/color/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT"
},
"node_modules/colorette": {
@@ -8392,6 +8441,8 @@
},
"node_modules/colorspace": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz",
"integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==",
"license": "MIT",
"dependencies": {
"color": "^3.1.3",
@@ -9133,6 +9184,8 @@
},
"node_modules/enabled": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==",
"license": "MIT"
},
"node_modules/encodeurl": {
@@ -9929,6 +9982,8 @@
},
"node_modules/extsprintf": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz",
"integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==",
"engines": [
"node >=0.6.0"
],
@@ -10026,6 +10081,8 @@
},
"node_modules/fecha": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==",
"license": "MIT"
},
"node_modules/figures": {
@@ -10205,6 +10262,8 @@
},
"node_modules/fn.name": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==",
"license": "MIT"
},
"node_modules/follow-redirects": {
@@ -12131,6 +12190,8 @@
},
"node_modules/kuler": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
"license": "MIT"
},
"node_modules/lazystream": {
@@ -12851,6 +12912,8 @@
},
"node_modules/logform": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
"integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
"license": "MIT",
"dependencies": {
"@colors/colors": "1.6.0",
@@ -13385,9 +13448,9 @@
}
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz",
"integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==",
"funding": [
{
"type": "github",
@@ -13395,12 +13458,11 @@
}
],
"license": "MIT",
"peer": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
"node": "^18 || >=20"
}
},
"node_modules/napi-build-utils": {
@@ -13483,6 +13545,8 @@
},
"node_modules/node-cache": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
"integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
"license": "MIT",
"dependencies": {
"clone": "2.x"
@@ -14287,6 +14351,8 @@
},
"node_modules/one-time": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
"integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
"license": "MIT",
"dependencies": {
"fn.name": "1.x.x"
@@ -14344,7 +14410,9 @@
}
},
"node_modules/openai": {
"version": "4.83.0",
"version": "4.104.0",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz",
"integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==",
"license": "Apache-2.0",
"dependencies": {
"@types/node": "^18.11.18",
@@ -14372,7 +14440,9 @@
}
},
"node_modules/openai/node_modules/@types/node": {
"version": "18.19.43",
"version": "18.19.130",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
"integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
@@ -14380,6 +14450,8 @@
},
"node_modules/opossum": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/opossum/-/opossum-9.0.0.tgz",
"integrity": "sha512-K76U0QkxOfUZamneQuzz+AP0fyfTJcCplZ2oZL93nxeupuJbN4s6uFNbmVCt4eWqqGqRnnowdFuBicJ1fLMVxw==",
"license": "Apache-2.0",
"engines": {
"node": "^24 || ^22 || ^20"
@@ -15093,6 +15165,25 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss/node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"peer": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/posthog-node": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.8.0.tgz",
@@ -15658,6 +15749,8 @@
},
"node_modules/retry": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
"license": "MIT",
"engines": {
"node": ">= 4"
@@ -15917,6 +16010,8 @@
},
"node_modules/safe-stable-stringify": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
"license": "MIT",
"engines": {
"node": ">=10"
@@ -16397,14 +16492,18 @@
}
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/simple-swizzle/node_modules/is-arrayish": {
"version": "0.3.2",
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
"license": "MIT"
},
"node_modules/sinon": {
@@ -16688,6 +16787,8 @@
},
"node_modules/stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
"integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
"license": "MIT",
"engines": {
"node": "*"
@@ -17188,6 +17289,8 @@
},
"node_modules/text-hex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
"license": "MIT"
},
"node_modules/text-table": {
@@ -17311,6 +17414,8 @@
},
"node_modules/triple-beam": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
"integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==",
"license": "MIT",
"engines": {
"node": ">= 14.0.0"
@@ -17822,6 +17927,8 @@
},
"node_modules/verror": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
"integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
"license": "MIT",
"dependencies": {
"assert-plus": "^1.0.0",
@@ -17834,6 +17941,8 @@
},
"node_modules/verror/node_modules/core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
"license": "MIT"
},
"node_modules/version-range": {
@@ -17955,6 +18064,8 @@
},
"node_modules/voca": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/voca/-/voca-1.4.1.tgz",
"integrity": "sha512-NJC/BzESaHT1p4B5k4JykxedeltmNbau4cummStd4RjFojgq/kLew5TzYge9N2geeWyI2w8T30wUET5v+F7ZHA==",
"license": "MIT"
},
"node_modules/vscode-uri": {
@@ -18252,6 +18363,8 @@
},
"node_modules/winston": {
"version": "3.17.0",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz",
"integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==",
"license": "MIT",
"dependencies": {
"@colors/colors": "^1.6.0",
@@ -18272,6 +18385,8 @@
},
"node_modules/winston-transport": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
"integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
"license": "MIT",
"dependencies": {
"logform": "^2.7.0",
@@ -18284,6 +18399,8 @@
},
"node_modules/winston-transport/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
@@ -18296,6 +18413,8 @@
},
"node_modules/winston/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
+14 -8
View File
@@ -2,7 +2,7 @@
"name": "claude-dev",
"displayName": "Cline",
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
"version": "3.35.1",
"version": "3.37.1",
"icon": "assets/icons/icon.png",
"engines": {
"vscode": "^1.84.0"
@@ -149,6 +149,12 @@
"category": "Cline",
"when": "cline.isDevMode"
},
{
"command": "cline.dev.expireMcpOAuthTokens",
"title": "Expire MCP OAuth Tokens (for testing)",
"category": "Cline",
"when": "cline.isDevMode"
},
{
"command": "cline.addToChat",
"title": "Add to Cline",
@@ -278,11 +284,11 @@
"commandPalette": [
{
"command": "cline.generateGitCommitMessage",
"when": "config.git.enabled && scmProvider == git && !cline.isGeneratingCommit"
"when": "config.git.enabled && !cline.isGeneratingCommit"
},
{
"command": "cline.abortGitCommitMessage",
"when": "config.git.enabled && scmProvider == git && cline.isGeneratingCommit"
"when": "config.git.enabled && cline.isGeneratingCommit"
}
]
},
@@ -304,6 +310,7 @@
"dev:cli:watch": "node scripts/dev-cli-watch.mjs",
"postcompile-standalone": "node scripts/package-standalone.mjs",
"postcompile-standalone-npm": "node scripts/package-standalone.mjs --target=npm",
"dev": "npm run protos && npm run watch",
"watch": "npm-run-all -p watch:*",
"watch:esbuild": "node esbuild.mjs --watch",
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
@@ -330,7 +337,7 @@
"pretest": "npm run compile && npm run compile-tests && npm run compile-standalone && npm run lint",
"test": "npm-run-all test:unit test:integration",
"test:integration": "vscode-test",
"test:unit": "cross-env TS_NODE_PROJECT=./tsconfig.unit-test.json mocha",
"test:unit": "cross-env TS_NODE_PROJECT=./tsconfig.unit-test.json mocha # Use `UPDATE_SNAPSHOTS=true npm run test:unit` to rebuild prompt snapshots",
"test:coverage": "vscode-test --coverage",
"test:sca-server": "npx tsx watch scripts/test-standalone-core-api-server.ts",
"test:tp-orchestrator": "npx tsx scripts/testing-platform-orchestrator.ts",
@@ -435,8 +442,8 @@
"@opentelemetry/sdk-trace-node": "^1.30.1",
"@opentelemetry/semantic-conventions": "^1.37.0",
"@playwright/test": "^1.55.1",
"@sap-ai-sdk/ai-api": "^1.17.0",
"@sap-ai-sdk/orchestration": "^1.17.0",
"@sap-ai-sdk/ai-api": "^2.1.0",
"@sap-ai-sdk/orchestration": "^2.1.0",
"@sentry/browser": "^9.12.0",
"@streamparser/json": "^0.0.22",
"@tailwindcss/vite": "^4.1.14",
@@ -454,7 +461,6 @@
"exceljs": "^4.4.0",
"execa": "^9.5.2",
"fast-deep-equal": "^3.1.3",
"firebase": "^11.2.0",
"fzf": "^0.5.2",
"get-folder-size": "^5.0.0",
"globby": "^14.0.2",
@@ -464,8 +470,8 @@
"image-size": "^2.0.2",
"isbinaryfile": "^5.0.2",
"jschardet": "^3.1.4",
"jwt-decode": "^4.0.0",
"mammoth": "^1.11.0",
"nanoid": "^5.1.6",
"nice-grpc": "^2.1.12",
"node-machine-id": "^1.1.12",
"ollama": "^0.5.13",
+23 -4
View File
@@ -49,6 +49,9 @@ service FileService {
// Toggle a Windsurf rule (enable or disable)
rpc toggleWindsurfRule(ToggleWindsurfRuleRequest) returns (ClineRulesToggles);
// Toggle an Agents rule (enable or disable)
rpc toggleAgentsRule(ToggleAgentsRuleRequest) returns (ClineRulesToggles);
// Refreshes all rule toggles (Cline, External, and Workflows)
rpc refreshRules(EmptyRequest) returns (RefreshedRules);
@@ -74,8 +77,9 @@ message RefreshedRules {
ClineRulesToggles local_cline_rules_toggles = 2;
ClineRulesToggles local_cursor_rules_toggles = 3;
ClineRulesToggles local_windsurf_rules_toggles = 4;
ClineRulesToggles local_workflow_toggles = 5;
ClineRulesToggles global_workflow_toggles = 6;
ClineRulesToggles local_agents_rules_toggles = 5;
ClineRulesToggles local_workflow_toggles = 6;
ClineRulesToggles global_workflow_toggles = 7;
}
// Request to toggle a Windsurf rule
@@ -85,6 +89,13 @@ message ToggleWindsurfRuleRequest {
bool enabled = 3; // Whether to enable or disable the rule
}
// Request to toggle an Agents rule
message ToggleAgentsRuleRequest {
Metadata metadata = 1;
string rule_path = 2; // Path to the rule file
bool enabled = 3; // Whether to enable or disable the rule
}
// Request to convert a list of URIs to relative paths
message RelativePathsRequest {
Metadata metadata = 1;
@@ -156,10 +167,17 @@ message RuleFile {
bool already_exists = 3; // For createRuleFile, indicates if file already existed
}
// Enum for rule scope (local, global, or remote)
enum RuleScope {
LOCAL = 0;
GLOBAL = 1;
REMOTE = 2;
}
// Request to toggle a Cline rule
message ToggleClineRuleRequest {
Metadata metadata = 1;
bool is_global = 2; // Whether this is a global rule or workspace rule
RuleScope scope = 2; // Scope of the rule (local, global, or remote)
string rule_path = 3; // Path to the rule file
bool enabled = 4; // Whether to enable or disable the rule
}
@@ -173,6 +191,7 @@ message ClineRulesToggles {
message ToggleClineRules {
ClineRulesToggles global_cline_rules_toggles = 1;
ClineRulesToggles local_cline_rules_toggles = 2;
ClineRulesToggles remote_rules_toggles = 3;
}
// Request to toggle a Cursor rule
@@ -187,5 +206,5 @@ message ToggleWorkflowRequest {
Metadata metadata = 1;
string workflow_path = 2;
bool enabled = 3;
bool is_global = 4;
RuleScope scope = 4; // Scope of the workflow (local, global, or remote)
}
+4
View File
@@ -18,6 +18,7 @@ service McpService {
rpc toggleToolAutoApprove(ToggleToolAutoApproveRequest) returns (McpServers);
rpc refreshMcpMarketplace(EmptyRequest) returns (McpMarketplaceCatalog);
rpc openMcpSettings(EmptyRequest) returns (Empty);
rpc authenticateMcpServer(StringRequest) returns (Empty);
// Subscribe to MCP marketplace catalog updates
rpc subscribeToMcpMarketplaceCatalog(EmptyRequest) returns (stream McpMarketplaceCatalog);
@@ -43,6 +44,7 @@ message AddRemoteMcpServerRequest {
Metadata metadata = 1;
string server_name = 2;
string server_url = 3;
optional string transport_type = 4;
}
message ToggleToolAutoApproveRequest {
@@ -91,6 +93,8 @@ message McpServer {
repeated McpResourceTemplate resource_templates = 7;
optional bool disabled = 8;
optional int32 timeout = 9;
optional bool oauth_required = 10;
optional string oauth_auth_status = 11;
}
message McpServers {
+7 -2
View File
@@ -96,6 +96,7 @@ message OpenRouterModelInfo {
optional ThinkingConfig thinking_config = 10;
optional bool supports_global_endpoint = 11;
repeated ModelTier tiers = 12;
optional string name = 13;
}
// Shared response message for model information
@@ -312,7 +313,7 @@ message ApiConfiguration {
// Request for updating API configuration (new - uses separate options and secrets)
message UpdateApiConfigurationRequestNew {
Metadata metadata = 1;
ApiConfiguration api_configuration = 2;
ApiConfiguration updates = 2;
// Required field mask specifying which fields to update.
// Field paths use dot notation with camelCase field names:
@@ -320,7 +321,7 @@ message UpdateApiConfigurationRequestNew {
// - "options.openAiHeaders" (for options fields)
// - "secrets.apiKey" (for secrets fields)
// - "secrets.openRouterApiKey" (for secrets fields)
google.protobuf.FieldMask update_mask = 3;
repeated string update_mask = 3;
}
// Request for partially updating API configuration using FieldMask
@@ -421,6 +422,7 @@ enum ApiProvider {
MINIMAX = 36;
HICAP = 37;
AIHUBMIX = 38;
NOUSRESEARCH = 39;
}
// Model info for OpenAI-compatible models
@@ -545,6 +547,7 @@ message ModelsApiConfiguration {
optional string aihubmix_api_key = 82;
optional string aihubmix_base_url = 83;
optional string aihubmix_app_code = 84;
optional string nous_research_api_key = 85;
// Plan mode configurations
optional ApiProvider plan_mode_api_provider = 100;
@@ -584,6 +587,7 @@ message ModelsApiConfiguration {
optional OpenRouterModelInfo plan_mode_hicap_model_info = 134;
optional string plan_mode_aihubmix_model_id = 135;
optional OpenAiCompatibleModelInfo plan_mode_aihubmix_model_info = 136;
optional string plan_mode_nous_research_model_id = 137;
// Act mode configurations
optional ApiProvider act_mode_api_provider = 200;
@@ -623,4 +627,5 @@ message ModelsApiConfiguration {
optional OpenRouterModelInfo act_mode_hicap_model_info = 234;
optional string act_mode_aihubmix_model_id = 235;
optional OpenAiCompatibleModelInfo act_mode_aihubmix_model_info = 236;
optional string act_mode_nous_research_model_id = 237;
}
+11
View File
@@ -23,6 +23,7 @@ service StateService {
rpc updateSettingsCli(UpdateSettingsRequestCli) returns (Empty);
rpc updateTaskSettings(UpdateTaskSettingsRequest) returns (Empty);
rpc updateTelemetrySetting(TelemetrySettingRequest) returns (Empty);
rpc captureOnboardingProgress(OnboardingProgressRequest) returns (Empty);
rpc setWelcomeViewCompleted(BooleanRequest) returns (Empty);
rpc updateInfoBannerVersion(Int64Request) returns (Empty);
rpc updateModelBannerVersion(Int64Request) returns (Empty);
@@ -30,6 +31,7 @@ service StateService {
rpc installClineCli(EmptyRequest) returns (Empty);
rpc checkCliInstallation(EmptyRequest) returns (Boolean);
rpc getProcessInfo(EmptyRequest) returns (ProcessInfo);
rpc flushPendingState(EmptyRequest) returns (Empty);
}
message AutoApprovalActions {
@@ -88,6 +90,7 @@ message Secrets {
optional string oca_api_key = 37;
optional string oca_refresh_token = 38;
optional string hicap_api_key = 39;
optional string mcp_oauth_secrets = 40;
}
message Settings {
@@ -359,6 +362,7 @@ message UpdateSettingsRequest {
optional int32 subagent_terminal_output_line_limit = 30;
optional string cline_env = 31;
optional bool native_tool_call_enabled = 32;
optional bool show_onboarding_flow = 33;
}
message UpdateTerminalConnectionTimeoutRequest {
@@ -379,3 +383,10 @@ message ProcessInfo {
optional string version = 2;
optional int64 uptime_ms = 3;
}
message OnboardingProgressRequest {
int32 step = 1;
optional string action = 2;
optional bool completed = 3;
optional string model_selected = 4;
}
+2
View File
@@ -70,6 +70,7 @@ message TaskResponse {
int32 tokens_out = 8;
int32 cache_writes = 9;
int32 cache_reads = 10;
string model_id = 11;
}
// Request for getting task history with filtering
@@ -99,6 +100,7 @@ message TaskItem {
int32 tokens_out = 8;
int32 cache_writes = 9;
int32 cache_reads = 10;
string model_id = 11;
}
// Request for ask response operation
+8
View File
@@ -32,6 +32,7 @@ enum ClineAsk {
CONDENSE = 13;
REPORT_BUG = 14;
SUMMARIZE_TASK = 15;
ACT_MODE_RESPOND = 16;
}
// Enum for ClineSay types
@@ -77,6 +78,7 @@ enum ClineSayToolType {
LIST_CODE_DEFINITION_NAMES = 5;
SEARCH_FILES = 6;
WEB_FETCH = 7;
FILE_DELETED = 8;
}
// Enum for browser actions
@@ -183,6 +185,11 @@ message ClineApiReqInfo {
ApiReqRetryStatus retry_status = 9;
}
message ClineModelInfo {
string provider_id = 1;
string model_id = 2;
}
// Main ClineMessage type
message ClineMessage {
int64 ts = 1;
@@ -209,6 +216,7 @@ message ClineMessage {
ClineAskQuestion ask_question = 20;
ClineAskNewTask ask_new_task = 21;
ClineApiReqInfo api_req_info = 22;
ClineModelInfo model_info = 23;
}
// UiService provides methods for managing UI interactions
+1
View File
@@ -95,6 +95,7 @@ const ENABLED_PROVIDERS = [
"ollama", // Ollama local models
"cerebras", // Cerebras models
"oca", // Oracle Code Assist
"nousResearch", // NousResearch provider
]
/**
+3 -32
View File
@@ -9,14 +9,6 @@ export interface EnvironmentConfig {
appBaseUrl: string
apiBaseUrl: string
mcpBaseUrl: string
firebase: {
apiKey: string
authDomain: string
projectId: string
storageBucket?: string
messagingSenderId?: string
appId?: string
}
}
class ClineEndpoint {
@@ -28,8 +20,8 @@ class ClineEndpoint {
private environment: Environment = Environment.production
private constructor() {
// Set environment at module load
const _env = process?.env?.CLINE_ENVIRONMENT
// Set environment at module load. Use override if provided.
const _env = process?.env?.CLINE_ENVIRONMENT_OVERRIDE || process?.env?.CLINE_ENVIRONMENT
if (_env && Object.values(Environment).includes(_env as Environment)) {
this.environment = _env as Environment
return
@@ -62,15 +54,7 @@ class ClineEndpoint {
environment: Environment.staging,
appBaseUrl: "https://staging-app.cline.bot",
apiBaseUrl: "https://core-api.staging.int.cline.bot",
mcpBaseUrl: "https://api.cline.bot/v1/mcp",
firebase: {
apiKey: "AIzaSyASSwkwX1kSO8vddjZkE5N19QU9cVQ0CIk",
authDomain: "cline-staging.firebaseapp.com",
projectId: "cline-staging",
storageBucket: "cline-staging.firebasestorage.app",
messagingSenderId: "853479478430",
appId: "1:853479478430:web:2de0dba1c63c3262d4578f",
},
mcpBaseUrl: "https://core-api.staging.int.cline.bot/v1/mcp",
}
case Environment.local:
return {
@@ -78,11 +62,6 @@ class ClineEndpoint {
appBaseUrl: "http://localhost:3000",
apiBaseUrl: "http://localhost:7777",
mcpBaseUrl: "https://api.cline.bot/v1/mcp",
firebase: {
apiKey: "AIzaSyD8wtkd1I-EICuAg6xgAQpRdwYTvwxZG2w",
authDomain: "cline-preview.firebaseapp.com",
projectId: "cline-preview",
},
}
default:
return {
@@ -90,14 +69,6 @@ class ClineEndpoint {
appBaseUrl: "https://app.cline.bot",
apiBaseUrl: "https://api.cline.bot",
mcpBaseUrl: "https://api.cline.bot/v1/mcp",
firebase: {
apiKey: "AIzaSyC5rx59Xt8UgwdU3PCfzUF7vCwmp9-K2vk",
authDomain: "cline-prod.firebaseapp.com",
projectId: "cline-prod",
storageBucket: "cline-prod.firebasestorage.app",
messagingSenderId: "941048379330",
appId: "1:941048379330:web:45058eedeefc5cdfcc485b",
},
}
}
}
+7
View File
@@ -25,6 +25,7 @@ import { MinimaxHandler } from "./providers/minimax"
import { MistralHandler } from "./providers/mistral"
import { MoonshotHandler } from "./providers/moonshot"
import { NebiusHandler } from "./providers/nebius"
import { NousResearchHandler } from "./providers/nousresearch"
import { OcaHandler } from "./providers/oca"
import { OllamaHandler } from "./providers/ollama"
import { OpenAiHandler } from "./providers/openai"
@@ -416,6 +417,12 @@ function createHandlerForProvider(
hicapApiKey: options.hicapApiKey,
hicapModelId: mode === "plan" ? options.planModeHicapModelId : options.actModeHicapModelId,
})
case "nousResearch":
return new NousResearchHandler({
onRetryAttempt: options.onRetryAttempt,
nousResearchApiKey: options.nousResearchApiKey,
apiModelId: mode === "plan" ? options.planModeNousResearchModelId : options.actModeNousResearchModelId,
})
default:
return new AnthropicHandler({
onRetryAttempt: options.onRetryAttempt,
+1 -13
View File
@@ -27,7 +27,7 @@ export class AIhubmixHandler implements ApiHandler {
const { baseURL, appCode, ...rest } = options
this.options = {
baseURL: baseURL ?? "https://aihubmix.com",
appCode: appCode ?? "KUWF9311", // 应用代码,享受折扣
appCode: appCode ?? "KUWF9311",
...rest,
}
}
@@ -81,7 +81,6 @@ export class AIhubmixHandler implements ApiHandler {
this.geminiClient = new GoogleGenAI({
apiKey: this.options.apiKey,
httpOptions: {
// AIhubmix Gemini 兼容网关,按 Google GenAI 路径规范
baseUrl: `${this.options.baseURL}/gemini`,
headers: {
// @ts-expect-error
@@ -97,9 +96,6 @@ export class AIhubmixHandler implements ApiHandler {
return this.geminiClient
}
/**
* 根据模型名称路由到对应的客户端
*/
private routeModel(modelName: string): "anthropic" | "openai" | "gemini" | "openai-response" {
const id = modelName || ""
if (id.startsWith("claude")) {
@@ -114,9 +110,6 @@ export class AIhubmixHandler implements ApiHandler {
return "openai"
}
/**
* 修复空工具时的 tool_choice 问题
*/
private fixToolChoice(requestBody: any): any {
if (requestBody.tools?.length === 0 && requestBody.tool_choice) {
delete requestBody.tool_choice
@@ -203,25 +196,21 @@ export class AIhubmixHandler implements ApiHandler {
const client = this.ensureOpenaiClient()
const modelId = this.options.modelId || "gpt-4o-mini"
// 将 Anthropic 风格消息转换为 Responses API 的 input 结构
const input = (messages || []).map((m: any) => {
const role = m.role || "user"
const contentArray = Array.isArray(m.content) ? m.content : [{ type: "text", text: m.content }]
const content = contentArray
.filter((c: any) => c != null)
.map((c: any) => {
// 图片
if (c.type === "image" || c.type === "input_image" || c.type === "image_url") {
return { type: "input_image", image_url: c.image_url || c.url || c.source?.url }
}
// 文本(用户 -> input_text,助手 -> output_text
const text = c.text ?? (typeof c === "string" ? c : "")
return { type: role === "assistant" ? "output_text" : "input_text", text }
})
return { role, content }
})
// 使用 Responses 流式 API,以事件驱动产出
const stream = await (client as any).responses.stream({
model: modelId,
instructions: systemPrompt,
@@ -261,7 +250,6 @@ export class AIhubmixHandler implements ApiHandler {
stream: true,
}
// 修复空工具问题
const fixedRequestBody = this.fixToolChoice(requestBody)
const stream = await client.chat.completions.create(fixedRequestBody)
+21 -58
View File
@@ -1,11 +1,12 @@
import { Anthropic } from "@anthropic-ai/sdk"
import { Tool as AnthropicTool, MessageParam } from "@anthropic-ai/sdk/resources/index"
import { Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/index"
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
import { AnthropicModelId, anthropicDefaultModelId, anthropicModels, CLAUDE_SONNET_1M_SUFFIX, ModelInfo } from "@shared/api"
import { fetch } from "@/shared/net"
import { ClineTool } from "@/shared/tools"
import { ApiHandler, CommonApiHandlerOptions } from "../index"
import { withRetry } from "../retry"
import { sanitizeAnthropicMessages } from "../transform/anthropic-format"
import { ApiStream } from "../transform/stream"
interface AnthropicHandlerOptions extends CommonApiHandlerOptions {
@@ -63,6 +64,7 @@ export class AnthropicHandler implements ApiHandler {
switch (modelId) {
// 'latest' alias does not support cache_control
case "claude-haiku-4-5-20251001":
case "claude-sonnet-4-5-20250929:1m":
case "claude-sonnet-4-5-20250929":
case "claude-sonnet-4-20250514":
case "claude-3-7-sonnet-20250219":
@@ -84,35 +86,7 @@ export class AnthropicHandler implements ApiHandler {
const lastUserMsgIndex = userMsgIndices[userMsgIndices.length - 1] ?? -1
const secondLastMsgUserIndex = userMsgIndices[userMsgIndices.length - 2] ?? -1
const anthropicMessages: Array<MessageParam> = messages.map((message, index) => {
if (index === lastUserMsgIndex || index === secondLastMsgUserIndex) {
return {
...message,
content:
typeof message.content === "string"
? [
{
type: "text",
text: message.content,
cache_control: {
type: "ephemeral",
},
},
]
: message.content.map((content, contentIndex) =>
contentIndex === message.content.length - 1
? {
...content,
cache_control: {
type: "ephemeral",
},
}
: content,
),
}
}
return message
})
const anthropicMessages = sanitizeAnthropicMessages(messages, lastUserMsgIndex, secondLastMsgUserIndex)
stream = await client.messages.create(
{
@@ -161,7 +135,7 @@ export class AnthropicHandler implements ApiHandler {
max_tokens: model.info.maxTokens || 8192,
temperature: 0,
system: [{ text: systemPrompt, type: "text" }],
messages,
messages: sanitizeAnthropicMessages(messages),
// tools,
// tool_choice: { type: "auto" },
stream: true,
@@ -170,20 +144,21 @@ export class AnthropicHandler implements ApiHandler {
}
}
let thinkingDeltaAccumulator = ""
const lastStartedToolCall = { id: "", name: "", arguments: "" }
for await (const chunk of stream) {
switch (chunk?.type) {
case "message_start":
// tells us cache reads/writes/input/output
const usage = chunk.message.usage
yield {
type: "usage",
inputTokens: usage.input_tokens || 0,
outputTokens: usage.output_tokens || 0,
cacheWriteTokens: usage.cache_creation_input_tokens || undefined,
cacheReadTokens: usage.cache_read_input_tokens || undefined,
{
// tells us cache reads/writes/input/output
const usage = chunk.message.usage
yield {
type: "usage",
inputTokens: usage.input_tokens || 0,
outputTokens: usage.output_tokens || 0,
cacheWriteTokens: usage.cache_creation_input_tokens || undefined,
cacheReadTokens: usage.cache_read_input_tokens || undefined,
}
}
break
case "message_delta":
@@ -204,15 +179,7 @@ export class AnthropicHandler implements ApiHandler {
yield {
type: "reasoning",
reasoning: chunk.content_block.thinking || "",
}
const thinking = chunk.content_block.thinking
const signature = chunk.content_block.signature
if (thinking && signature) {
yield {
type: "ant_thinking",
thinking,
signature,
}
signature: chunk.content_block.signature,
}
break
case "redacted_thinking":
@@ -220,10 +187,7 @@ export class AnthropicHandler implements ApiHandler {
yield {
type: "reasoning",
reasoning: "[Redacted thinking block]",
}
yield {
type: "ant_redacted_thinking",
data: chunk.content_block.data,
redacted_data: chunk.content_block.data,
}
break
case "tool_use":
@@ -252,20 +216,19 @@ export class AnthropicHandler implements ApiHandler {
case "content_block_delta":
switch (chunk.delta.type) {
case "thinking_delta":
// 'reasoning' type just displays in the UI, but ant_thinking will be used to send the thinking traces back to the API
// 'reasoning' type just displays in the UI, but reasoning with signature will be used to send the thinking traces back to the API
yield {
type: "reasoning",
reasoning: chunk.delta.thinking,
}
thinkingDeltaAccumulator += chunk.delta.thinking
break
case "signature_delta":
// It's used when sending the thinking block back to the API
// API expects this in completed form, not as array of deltas
if (thinkingDeltaAccumulator && chunk.delta.signature) {
if (chunk.delta.signature) {
yield {
type: "ant_thinking",
thinking: thinkingDeltaAccumulator,
type: "reasoning",
reasoning: "", // reasoning text is already sent via thinking_delta
signature: chunk.delta.signature,
}
}
+12 -1
View File
@@ -113,7 +113,18 @@ export class ClaudeCodeHandler implements ApiHandler {
}
break
case "tool_use":
console.error(`tool_use is not supported yet. Received: ${JSON.stringify(content)}`)
// Yield tool_use blocks to the streaming pipeline for proper tool execution
yield {
type: "tool_calls",
tool_call: {
call_id: content.id,
function: {
id: content.id,
name: content.name,
arguments: content.input,
},
},
}
break
}
}
+5 -5
View File
@@ -165,8 +165,7 @@ export class ClineHandler implements ApiHandler {
if ("reasoning" in delta && delta.reasoning && !shouldSkipReasoningForModel(this.options.openRouterModelId)) {
yield {
type: "reasoning",
// @ts-ignore-next-line
reasoning: delta.reasoning,
reasoning: typeof delta.reasoning === "string" ? delta.reasoning : JSON.stringify(delta.reasoning),
}
}
@@ -185,8 +184,9 @@ export class ClineHandler implements ApiHandler {
!shouldSkipReasoningForModel(this.options.openRouterModelId)
) {
yield {
type: "reasoning_details",
reasoning_details: delta.reasoning_details,
type: "reasoning",
reasoning: "",
details: delta.reasoning_details,
}
}
@@ -194,7 +194,7 @@ export class ClineHandler implements ApiHandler {
// @ts-ignore-next-line
let totalCost = (chunk.usage.cost || 0) + (chunk.usage.cost_details?.upstream_inference_cost || 0)
if (this.getModel().id === "x-ai/grok-code-fast-1") {
if (this.getModel().id === "x-ai/grok-code-fast-1" || this.getModel().id === "minimax/minimax-m2") {
totalCost = 0
}
+11 -17
View File
@@ -66,12 +66,11 @@ export class MinimaxHandler implements ApiHandler {
tool_choice: nativeToolsOn ? { type: "any" } : undefined,
})
let thinkingDeltaAccumulator = ""
const lastStartedToolCall = { id: "", name: "", arguments: "" }
for await (const chunk of stream) {
switch (chunk?.type) {
case "message_start":
case "message_start": {
// tells us cache reads/writes/input/output
const usage = chunk.message.usage
yield {
@@ -82,6 +81,7 @@ export class MinimaxHandler implements ApiHandler {
cacheReadTokens: usage.cache_read_input_tokens || undefined,
}
break
}
case "message_delta":
// tells us stop_reason, stop_sequence, and output tokens along the way and at the end of the message
yield {
@@ -100,13 +100,11 @@ export class MinimaxHandler implements ApiHandler {
type: "reasoning",
reasoning: chunk.content_block.thinking || "",
}
const thinking = chunk.content_block.thinking
const signature = chunk.content_block.signature
if (thinking && signature) {
if (chunk.content_block.thinking && chunk.content_block.signature) {
yield {
type: "ant_thinking",
thinking,
signature,
type: "reasoning",
reasoning: chunk.content_block.thinking,
signature: chunk.content_block.signature,
}
}
break
@@ -115,10 +113,7 @@ export class MinimaxHandler implements ApiHandler {
yield {
type: "reasoning",
reasoning: "[Redacted thinking block]",
}
yield {
type: "ant_redacted_thinking",
data: chunk.content_block.data,
redacted_data: chunk.content_block.data,
}
break
case "tool_use":
@@ -147,20 +142,19 @@ export class MinimaxHandler implements ApiHandler {
case "content_block_delta":
switch (chunk.delta.type) {
case "thinking_delta":
// 'reasoning' type just displays in the UI, but ant_thinking will be used to send the thinking traces back to the API
// 'reasoning' type just displays in the UI, but reasoning with signature will be used to send the thinking traces back to the API
yield {
type: "reasoning",
reasoning: chunk.delta.thinking,
}
thinkingDeltaAccumulator += chunk.delta.thinking
break
case "signature_delta":
// It's used when sending the thinking block back to the API
// API expects this in completed form, not as array of deltas
if (thinkingDeltaAccumulator && chunk.delta.signature) {
if (chunk.delta.signature) {
yield {
type: "ant_thinking",
thinking: thinkingDeltaAccumulator,
type: "reasoning",
reasoning: "",
signature: chunk.delta.signature,
}
}
+92
View File
@@ -0,0 +1,92 @@
import { Anthropic } from "@anthropic-ai/sdk"
import { ModelInfo, NousResearchModelId, nousResearchDefaultModelId, nousResearchModels } from "@shared/api"
import OpenAI from "openai"
import { ApiHandler, CommonApiHandlerOptions } from "../index"
import { withRetry } from "../retry"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { ApiStream } from "../transform/stream"
interface NousResearchHandlerOptions extends CommonApiHandlerOptions {
nousResearchApiKey?: string
apiModelId?: string
}
export class NousResearchHandler implements ApiHandler {
private options: NousResearchHandlerOptions
private client: OpenAI | undefined
constructor(options: NousResearchHandlerOptions) {
this.options = options
}
private ensureClient(): OpenAI {
if (!this.client) {
if (!this.options.nousResearchApiKey) {
throw new Error("NousResearch API key is required")
}
try {
this.client = new OpenAI({
baseURL: "https://inference-api.nousResearch.com/v1",
apiKey: this.options.nousResearchApiKey,
})
} catch (error: any) {
throw new Error(`Error creating NousResearch client: ${error.message}`)
}
}
return this.client
}
@withRetry()
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
const client = this.ensureClient()
const model = this.getModel()
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages),
]
const stream = await 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: NousResearchModelId; info: ModelInfo } {
const modelId = this.options.apiModelId
if (modelId && modelId in nousResearchModels) {
const id = modelId as NousResearchModelId
return { id, info: nousResearchModels[id] }
}
return { id: nousResearchDefaultModelId, info: nousResearchModels[nousResearchDefaultModelId] }
}
}
+4
View File
@@ -115,6 +115,9 @@ export class OpenAiNativeHandler implements ApiHandler {
case "gpt-5-2025-08-07":
case "gpt-5-mini-2025-08-07":
case "gpt-5-nano-2025-08-07":
case "gpt-5.1-2025-11-13":
case "gpt-5.1-chat-latest":
case "gpt-5.1": {
const stream = await client.chat.completions.create({
model: model.id,
temperature: 1,
@@ -148,6 +151,7 @@ export class OpenAiNativeHandler implements ApiHandler {
}
}
break
}
default: {
const stream = await client.chat.completions.create({
model: model.id,
+7 -1
View File
@@ -81,7 +81,13 @@ export class OpenAiHandler implements ApiHandler {
{ role: "system", content: systemPrompt },
...convertToOpenAiMessages(messages),
]
let temperature: number | undefined = this.options.openAiModelInfo?.temperature ?? openAiModelInfoSaneDefaults.temperature
let temperature: number | undefined
if (this.options.openAiModelInfo?.temperature !== undefined) {
const tempValue = Number(this.options.openAiModelInfo.temperature)
temperature = tempValue === 0 ? undefined : tempValue
} else {
temperature = openAiModelInfoSaneDefaults.temperature
}
let reasoningEffort: ChatCompletionReasoningEffort | undefined
let maxTokens: number | undefined
+4 -4
View File
@@ -127,8 +127,7 @@ export class OpenRouterHandler implements ApiHandler {
if ("reasoning" in delta && delta.reasoning && !shouldSkipReasoningForModel(this.options.openRouterModelId)) {
yield {
type: "reasoning",
// @ts-ignore-next-line
reasoning: delta.reasoning,
reasoning: typeof delta.reasoning === "string" ? delta.reasoning : JSON.stringify(delta.reasoning),
}
}
@@ -142,8 +141,9 @@ export class OpenRouterHandler implements ApiHandler {
!shouldSkipReasoningForModel(this.options.openRouterModelId)
) {
yield {
type: "reasoning_details",
reasoning_details: delta.reasoning_details,
type: "reasoning",
reasoning: "",
details: delta.reasoning_details,
}
}
+1 -1
View File
@@ -2,8 +2,8 @@ import { Anthropic } from "@anthropic-ai/sdk"
import { ModelInfo, requestyDefaultModelId, requestyDefaultModelInfo } from "@shared/api"
import { calculateApiCostOpenAI } from "@utils/cost"
import OpenAI from "openai"
import { toRequestyServiceStringUrl } from "@/shared/clients/requesty"
import { fetch } from "@/shared/net"
import { toRequestyServiceStringUrl } from "@/shared/providers/requesty"
import { ApiHandler, CommonApiHandlerOptions } from "../index"
import { withRetry } from "../retry"
import { convertToOpenAiMessages } from "../transform/openai-format"
+18 -18
View File
@@ -4,7 +4,7 @@ import {
ConversationRole as BedrockConversationRole,
type Message as BedrockMessage,
} from "@aws-sdk/client-bedrock-runtime"
import { ChatMessages, LlmModuleConfig, OrchestrationClient, TemplatingModuleConfig } from "@sap-ai-sdk/orchestration"
import { ChatMessage, OrchestrationClient } from "@sap-ai-sdk/orchestration"
import { ModelInfo, SapAiCoreModelId, sapAiCoreDefaultModelId, sapAiCoreModels } from "@shared/api"
import axios from "axios"
import OpenAI from "openai"
@@ -495,22 +495,22 @@ export class SapAiCoreHandler implements ApiHandler {
// Ensure AI Core environment variable is set up (only runs once)
this.ensureAiCoreEnvSetup()
const model = this.getModel()
// Define the LLM to be used by the Orchestration pipeline
const llm: LlmModuleConfig = {
model_name: model.id,
}
const templating: TemplatingModuleConfig = {
template: [
{
role: "system",
content: systemPrompt,
},
],
}
const orchestrationClient = new OrchestrationClient(
{ llm, templating },
{
promptTemplating: {
model: {
name: model.id,
},
prompt: {
template: [
{
role: "system",
content: systemPrompt,
},
],
},
},
},
{ resourceGroup: this.options.sapAiResourceGroup || "default" },
)
@@ -1040,8 +1040,8 @@ export class SapAiCoreHandler implements ApiHandler {
}
return { id: sapAiCoreDefaultModelId, info: sapAiCoreModels[sapAiCoreDefaultModelId] }
}
private convertMessageParamToSAPMessages(messages: Anthropic.Messages.MessageParam[]): ChatMessages {
private convertMessageParamToSAPMessages(messages: Anthropic.Messages.MessageParam[]): ChatMessage[] {
// Use the existing OpenAI converter since the logic is identical
return convertToOpenAiMessages(messages) as ChatMessages
return convertToOpenAiMessages(messages) as ChatMessage[]
}
}
+3 -2
View File
@@ -95,8 +95,9 @@ export class VercelAIGatewayHandler implements ApiHandler {
delta.reasoning_details.length // exists and non-0
) {
yield {
type: "reasoning_details",
reasoning_details: delta.reasoning_details,
type: "reasoning",
reasoning: "",
details: delta.reasoning_details,
}
}
+5 -53
View File
@@ -6,6 +6,7 @@ import { ModelInfo, VertexModelId, vertexDefaultModelId, vertexModels } from "@s
import { ClineTool } from "@/shared/tools"
import { ApiHandler, CommonApiHandlerOptions } from "../"
import { withRetry } from "../retry"
import { sanitizeAnthropicMessages } from "../transform/anthropic-format"
import { ApiStream } from "../transform/stream"
import { GeminiHandler } from "./gemini"
@@ -122,46 +123,7 @@ export class VertexHandler implements ApiHandler {
cache_control: { type: "ephemeral" },
},
],
messages: messages.map((message, index) => {
if (index === lastUserMsgIndex || index === secondLastMsgUserIndex) {
return {
...message,
content:
typeof message.content === "string"
? [
{
type: "text",
text: message.content,
cache_control: {
type: "ephemeral",
},
},
]
: message.content.map((content, contentIndex) =>
contentIndex === message.content.length - 1
? {
...content,
cache_control: {
type: "ephemeral",
},
}
: content,
),
}
}
return {
...message,
content:
typeof message.content === "string"
? [
{
type: "text",
text: message.content,
},
]
: message.content,
}
}),
messages: sanitizeAnthropicMessages(messages, lastUserMsgIndex, secondLastMsgUserIndex),
stream: true,
tools: tools?.length ? (tools as AnthropicTool[]) : undefined,
// tool_choice options:
@@ -187,18 +149,7 @@ export class VertexHandler implements ApiHandler {
type: "text",
},
],
messages: messages.map((message) => ({
...message,
content:
typeof message.content === "string"
? [
{
type: "text",
text: message.content,
},
]
: message.content,
})),
messages: sanitizeAnthropicMessages(messages),
stream: true,
tools: tools?.length ? (tools as AnthropicTool[]) : undefined,
// tool_choice options:
@@ -215,7 +166,7 @@ export class VertexHandler implements ApiHandler {
for await (const chunk of stream) {
switch (chunk?.type) {
case "message_start":
case "message_start": {
const usage = chunk.message.usage
yield {
type: "usage",
@@ -225,6 +176,7 @@ export class VertexHandler implements ApiHandler {
cacheReadTokens: usage.cache_read_input_tokens || undefined,
}
break
}
case "message_delta":
yield {
type: "usage",
+69 -12
View File
@@ -1,19 +1,76 @@
import { Tool as AnthropicTool } from "@anthropic-ai/sdk/resources/index"
import { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
import Anthropic from "@anthropic-ai/sdk"
import { ClineStorageMessage } from "@/shared/messages/content"
/**
* Converts an OpenAI ChatCompletionTool into an Anthropic Tool definition
* Sanitize Anthropic messages by removing reasoning details and adding ephemeral cache control
* to the last two user messages to prevent them from being stored in Anthropic's cache.
*/
export function openAIToolToAnthropic(openAITool: OpenAITool): AnthropicTool {
const func = openAITool.function
export function sanitizeAnthropicMessages(
messages: Array<ClineStorageMessage>,
lastUserMsgIndex?: number,
secondLastMsgUserIndex?: number,
): Array<Anthropic.Messages.MessageParam> {
return messages.map((_message, index) => {
const message = removeUnknownParams(_message)
const addCacheControl = lastUserMsgIndex !== undefined && secondLastMsgUserIndex !== undefined
// Construct message
if (addCacheControl && (index === lastUserMsgIndex || index === secondLastMsgUserIndex)) {
return {
...message,
content:
typeof message.content === "string"
? [
{
type: "text",
text: message.content,
cache_control: {
type: "ephemeral",
},
},
]
: message.content.map((content, contentIndex) =>
contentIndex === message.content.length - 1
? {
...content,
cache_control: {
type: "ephemeral",
},
}
: content,
),
}
}
return {
...message,
content:
typeof message.content === "string"
? [
{
type: "text",
text: message.content,
},
]
: message.content,
}
})
}
/**
* Remove reasoning details and other known params that are not Anthropic specific.
*/
function removeUnknownParams(param: ClineStorageMessage): Anthropic.Messages.MessageParam {
// Construct new content array with known Anthropic content blocks only.
return {
name: func.name,
description: func.description || "",
input_schema: {
type: "object",
properties: func.parameters?.properties || {},
required: func.parameters?.required || [],
},
role: param.role === "user" ? "user" : "assistant",
content: Array.isArray(param.content)
? param.content.map((item) => {
return {
...item,
// Ensure reasoning_details is removed
reasoning_details: undefined,
}
})
: param.content, // String content remains unchanged
}
}
+37 -33
View File
@@ -5,40 +5,44 @@ export function convertAnthropicContentToGemini(content: string | Anthropic.Cont
if (typeof content === "string") {
return [{ text: content }]
}
return content.flatMap((block): Part => {
switch (block.type) {
case "text":
return { text: block.text }
case "image":
if (block.source.type !== "base64") {
throw new Error("Unsupported image source type")
}
return {
inlineData: {
data: block.source.data,
mimeType: block.source.media_type,
},
}
case "tool_use":
return {
functionCall: {
name: block.name,
args: block.input as Record<string, unknown>,
},
}
case "tool_result":
return {
functionResponse: {
name: block.tool_use_id,
response: {
result: block.content,
return content
.flatMap((block): Part | undefined => {
switch (block.type) {
case "text":
return { text: block.text }
case "image":
if (block.source.type !== "base64") {
throw new Error("Unsupported image source type")
}
return {
inlineData: {
data: block.source.data,
mimeType: block.source.media_type,
},
},
}
default:
throw new Error(`Unsupported content block type: ${block.type}`)
}
})
}
case "tool_use":
return {
functionCall: {
name: block.name,
args: block.input as Record<string, unknown>,
},
}
case "tool_result":
return {
functionResponse: {
name: block.tool_use_id,
response: {
result: block.content,
},
},
}
case "thinking":
return { text: block.thinking, thought: true, thoughtSignature: block.signature }
default:
return undefined
}
})
.filter((part): part is Part => part !== undefined) // Filter out unsupported blocks
}
export function convertAnthropicMessageToGemini(message: Anthropic.Messages.MessageParam): Content {
+12 -6
View File
@@ -1,7 +1,13 @@
import { Anthropic } from "@anthropic-ai/sdk"
import { Message } from "ollama"
import {
ClineAssistantToolUseBlock,
ClineImageContentBlock,
ClineStorageMessage,
ClineTextContentBlock,
ClineUserToolResultContentBlock,
} from "@/shared/messages/content"
export function convertToOllamaMessages(anthropicMessages: Anthropic.Messages.MessageParam[]): Message[] {
export function convertToOllamaMessages(anthropicMessages: Omit<ClineStorageMessage, "modelInfo">[]): Message[] {
const ollamaMessages: Message[] = []
for (const anthropicMessage of anthropicMessages) {
@@ -13,8 +19,8 @@ export function convertToOllamaMessages(anthropicMessages: Anthropic.Messages.Me
} else {
if (anthropicMessage.role === "user") {
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[]
toolMessages: Anthropic.ToolResultBlockParam[]
nonToolMessages: (ClineTextContentBlock | ClineImageContentBlock)[]
toolMessages: ClineUserToolResultContentBlock[]
}>(
(acc, part) => {
if (part.type === "tool_result") {
@@ -70,8 +76,8 @@ export function convertToOllamaMessages(anthropicMessages: Anthropic.Messages.Me
}
} else if (anthropicMessage.role === "assistant") {
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[]
toolMessages: Anthropic.ToolUseBlockParam[]
nonToolMessages: (ClineTextContentBlock | ClineImageContentBlock)[]
toolMessages: ClineAssistantToolUseBlock[]
}>(
(acc, part) => {
if (part.type === "tool_use") {
+27 -9
View File
@@ -1,8 +1,17 @@
import { Anthropic } from "@anthropic-ai/sdk"
import OpenAI from "openai"
import {
ClineAssistantRedactedThinkingBlock,
ClineAssistantThinkingBlock,
ClineAssistantToolUseBlock,
ClineImageContentBlock,
ClineStorageMessage,
ClineTextContentBlock,
ClineUserToolResultContentBlock,
} from "@/shared/messages/content"
export function convertToOpenAiMessages(
anthropicMessages: Anthropic.Messages.MessageParam[],
anthropicMessages: Omit<ClineStorageMessage, "modelInfo">[],
): OpenAI.Chat.ChatCompletionMessageParam[] {
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = []
@@ -23,8 +32,8 @@ export function convertToOpenAiMessages(
*/
if (anthropicMessage.role === "user") {
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[]
toolMessages: Anthropic.ToolResultBlockParam[]
nonToolMessages: (ClineTextContentBlock | ClineImageContentBlock)[]
toolMessages: ClineUserToolResultContentBlock[]
}>(
(acc, part) => {
if (part.type === "tool_result") {
@@ -38,7 +47,7 @@ export function convertToOpenAiMessages(
)
// Process tool result messages FIRST since they must follow the tool use messages
const toolResultImages: Anthropic.Messages.ImageBlockParam[] = []
const toolResultImages: ClineImageContentBlock[] = []
toolMessages.forEach((toolMessage) => {
// The Anthropic SDK allows tool results to be a string or an array of text and image blocks, enabling rich and structured content. In contrast, the OpenAI SDK only supports tool results as a single string, so we map the Anthropic tool result parts into one concatenated string to maintain compatibility.
let content: string
@@ -102,8 +111,13 @@ export function convertToOpenAiMessages(
}
} else if (anthropicMessage.role === "assistant") {
const { nonToolMessages, toolMessages } = anthropicMessage.content.reduce<{
nonToolMessages: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[]
toolMessages: Anthropic.ToolUseBlockParam[]
nonToolMessages: (
| ClineTextContentBlock
| ClineImageContentBlock
| ClineAssistantThinkingBlock
| ClineAssistantRedactedThinkingBlock
)[]
toolMessages: ClineAssistantToolUseBlock[]
}>(
(acc, part) => {
if (part.type === "tool_use") {
@@ -119,6 +133,7 @@ export function convertToOpenAiMessages(
// Process non-tool messages
let content: string | undefined
const reasoningDetails: any[] = []
const thinkingBlock = []
if (nonToolMessages.length > 0) {
nonToolMessages.forEach((part) => {
// @ts-ignore-next-line
@@ -134,13 +149,16 @@ export function convertToOpenAiMessages(
// @ts-ignore-next-line
// delete part.reasoning_details
}
if (part.type === "thinking" && part.thinking) {
thinkingBlock.push(part)
}
})
content = nonToolMessages
.map((part) => {
if (part.type === "image") {
return "" // impossible as the assistant cannot send images
if (part.type === "text" && part.text) {
return part.text
}
return part.text
return ""
})
.join("\n")
}
+9 -29
View File
@@ -1,39 +1,11 @@
export type ApiStream = AsyncGenerator<ApiStreamChunk>
export type ApiStreamChunk =
| ApiStreamTextChunk
| ApiStreamReasoningChunk
| ApiStreamReasoningDetailsChunk
| ApiStreamAnthropicThinkingChunk
| ApiStreamAnthropicRedactedThinkingChunk
| ApiStreamUsageChunk
| ApiStreamToolCallsChunk
export type ApiStreamChunk = ApiStreamTextChunk | ApiStreamThinkingChunk | ApiStreamUsageChunk | ApiStreamToolCallsChunk
export interface ApiStreamTextChunk {
type: "text"
text: string
}
export interface ApiStreamReasoningChunk {
type: "reasoning"
reasoning: string
}
export interface ApiStreamReasoningDetailsChunk {
type: "reasoning_details"
reasoning_details: any // openrouter has various properties that we can pass back unmodified in api requests to preserve reasoning traces
}
export interface ApiStreamAnthropicThinkingChunk {
type: "ant_thinking"
thinking: string
signature: string
}
export interface ApiStreamAnthropicRedactedThinkingChunk {
type: "ant_redacted_thinking"
data: string
}
export interface ApiStreamUsageChunk {
type: "usage"
inputTokens: number
@@ -58,3 +30,11 @@ export interface ApiStreamToolCall {
arguments?: any
}
}
export interface ApiStreamThinkingChunk {
type: "reasoning"
reasoning: string
details?: unknown // openrouter has various properties that we can pass back unmodified in api requests to preserve reasoning traces
signature?: string
redacted_data?: string
}
@@ -79,7 +79,7 @@ export function getOpenAIToolParams(tools?: OpenAITool[]) {
? {
tools,
tool_choice: tools ? ("auto" as ChatCompletionToolChoiceOption) : undefined,
parallel_tool_calls: tools ? true : undefined,
parallel_tool_calls: tools ? false : undefined, // Set to false to force single tool calls
}
: {
tools: undefined,
+5 -5
View File
@@ -1,8 +1,8 @@
import { Anthropic } from "@anthropic-ai/sdk"
import type { ToolUse } from "@core/assistant-message"
import { JSONParser } from "@streamparser/json"
import { McpHub } from "@/services/mcp/McpHub"
import { CLINE_MCP_TOOL_IDENTIFIER } from "@/shared/mcp"
import { ClineAssistantToolUseBlock } from "@/shared/messages/content"
import { ClineDefaultTool } from "@/shared/tools"
export interface PendingToolUse {
@@ -32,7 +32,7 @@ const ESCAPE_MAP: Record<string, string> = {
const ESCAPE_PATTERN = /\\[ntr"\\]/g
/**
* Handles streaming native tool use blocks and converts them to Anthropic.ToolUseBlockParam format
* Handles streaming native tool use blocks and converts them to ClineAssistantToolUseBlock format
*/
export class ToolUseHandler {
private pendingToolUses = new Map<string, PendingToolUse>()
@@ -60,7 +60,7 @@ export class ToolUseHandler {
}
}
getFinalizedToolUse(id: string): Anthropic.ToolUseBlockParam | undefined {
getFinalizedToolUse(id: string): ClineAssistantToolUseBlock | undefined {
const pending = this.pendingToolUses.get(id)
if (!pending?.name) {
return undefined
@@ -85,8 +85,8 @@ export class ToolUseHandler {
}
}
getAllFinalizedToolUses(): Anthropic.ToolUseBlockParam[] {
const results: Anthropic.ToolUseBlockParam[] = []
getAllFinalizedToolUses(): ClineAssistantToolUseBlock[] {
const results: ClineAssistantToolUseBlock[] = []
for (const id of this.pendingToolUses.keys()) {
const toolUse = this.getFinalizedToolUse(id)
if (toolUse) {
+14 -2
View File
@@ -1,9 +1,10 @@
import { ClineDefaultTool } from "@shared/tools"
export type AssistantMessageContent = TextContent | ToolUse
export type AssistantMessageContent = TextStreamContent | ToolUse | ReasoningStreamContent
export { parseAssistantMessageV2 } from "./parse-assistant-message"
export interface TextContent {
export interface TextStreamContent {
type: "text"
content: string
partial: boolean
@@ -23,6 +24,7 @@ export const toolParamNames = [
"url",
"coordinate",
"text",
"prompt",
"server_name",
"tool_name",
"arguments",
@@ -54,3 +56,13 @@ export interface ToolUse {
// Whether this tool use was initiated by a native tool call
isNativeToolCall?: boolean
}
export interface ReasoningStreamContent {
type: "reasoning"
reasoning: string
details?: any // openrouter has various properties that we can pass back unmodified in api requests to preserve reasoning traces
signature?: string
redacted?: boolean // whether this reasoning block has been redacted
data?: string // redacted data
partial: boolean
}
@@ -1,5 +1,5 @@
import { ClineDefaultTool, toolUseNames } from "@shared/tools"
import { AssistantMessageContent, TextContent, ToolParamName, ToolUse, toolParamNames } from "." // Assuming types are defined in index.ts or a similar file
import { AssistantMessageContent, TextStreamContent, ToolParamName, ToolUse, toolParamNames } from "." // Assuming types are defined in index.ts or a similar file
// parseAssistantmessageV1 removed in https://github.com/cline/cline/pull/5425
@@ -27,7 +27,7 @@ import { AssistantMessageContent, TextContent, ToolParamName, ToolUse, toolParam
export function parseAssistantMessageV2(assistantMessage: string): AssistantMessageContent[] {
const contentBlocks: AssistantMessageContent[] = []
let currentTextContentStart = 0 // Index where the current text block started
let currentTextContent: TextContent | undefined
let currentTextContent: TextStreamContent | undefined
let currentToolUseStart = 0 // Index *after* the opening tag of the current tool use
let currentToolUse: ToolUse | undefined
let currentParamValueStart = 0 // Index *after* the opening tag of the current param
@@ -1,6 +1,7 @@
import { getRuleFilesTotalContent, synchronizeRuleToggles } from "@core/context/instructions/user-instructions/rule-helpers"
import { formatResponse } from "@core/prompts/responses"
import { ensureRulesDirectoryExists, GlobalFileNames } from "@core/storage/disk"
import { StateManager } from "@core/storage/StateManager"
import { ClineRulesToggles } from "@shared/cline-rules"
import { fileExistsAtPath, isDirectory, readDirectory } from "@utils/fs"
import fs from "fs/promises"
@@ -8,27 +9,48 @@ import path from "path"
import { Controller } from "@/core/controller"
export const getGlobalClineRules = async (globalClineRulesFilePath: string, toggles: ClineRulesToggles) => {
let combinedContent = ""
// 1. Get file-based rules
if (await fileExistsAtPath(globalClineRulesFilePath)) {
if (await isDirectory(globalClineRulesFilePath)) {
try {
const rulesFilePaths = await readDirectory(globalClineRulesFilePath)
const rulesFilesTotalContent = await getRuleFilesTotalContent(rulesFilePaths, globalClineRulesFilePath, toggles)
if (rulesFilesTotalContent) {
const clineRulesFileInstructions = formatResponse.clineRulesGlobalDirectoryInstructions(
globalClineRulesFilePath,
rulesFilesTotalContent,
)
return clineRulesFileInstructions
combinedContent = rulesFilesTotalContent
}
} catch {
console.error(`Failed to read .clinerules directory at ${globalClineRulesFilePath}`)
}
} else {
console.error(`${globalClineRulesFilePath} is not a directory`)
return undefined
}
}
// 2. Append remote config rules
const stateManager = StateManager.get()
const remoteConfigSettings = stateManager.getRemoteConfigSettings()
const remoteRules = remoteConfigSettings.remoteGlobalRules || []
const remoteToggles = stateManager.getGlobalStateKey("remoteRulesToggles") || {}
for (const rule of remoteRules) {
// If alwaysEnabled, always include; otherwise check toggle
const isEnabled = rule.alwaysEnabled || remoteToggles[rule.name] !== false
if (isEnabled) {
if (combinedContent) {
combinedContent += "\n\n"
}
combinedContent += `${rule.name}\n${rule.contents}`
}
}
// 3. Return formatted instructions
if (combinedContent) {
return formatResponse.clineRulesGlobalDirectoryInstructions(globalClineRulesFilePath, combinedContent)
}
return undefined
}
@@ -6,6 +6,7 @@ import {
} from "@core/context/instructions/user-instructions/rule-helpers"
import { formatResponse } from "@core/prompts/responses"
import { GlobalFileNames } from "@core/storage/disk"
import { listFiles } from "@services/glob/list-files"
import { ClineRulesToggles } from "@shared/cline-rules"
import { fileExistsAtPath, isDirectory } from "@utils/fs"
import fs from "fs/promises"
@@ -13,7 +14,7 @@ import path from "path"
import { Controller } from "@/core/controller"
/**
* Refreshes the toggles for windsurf and cursor rules
* Refreshes the toggles for windsurf, cursor, and agents rules
*/
export async function refreshExternalRulesToggles(
controller: Controller,
@@ -21,6 +22,7 @@ export async function refreshExternalRulesToggles(
): Promise<{
windsurfLocalToggles: ClineRulesToggles
cursorLocalToggles: ClineRulesToggles
agentsLocalToggles: ClineRulesToggles
}> {
// local windsurf toggles
const localWindsurfRulesToggles = controller.stateManager.getWorkspaceStateKey("localWindsurfRulesToggles")
@@ -42,9 +44,16 @@ export async function refreshExternalRulesToggles(
const updatedLocalCursorToggles = combineRuleToggles(updatedLocalCursorToggles1, updatedLocalCursorToggles2)
controller.stateManager.setWorkspaceState("localCursorRulesToggles", updatedLocalCursorToggles)
// local agents toggles
const localAgentsRulesToggles = controller.stateManager.getWorkspaceStateKey("localAgentsRulesToggles")
const localAgentsRulesFilePath = path.resolve(workingDirectory, GlobalFileNames.agentsRulesFile)
const updatedLocalAgentsToggles = await synchronizeRuleToggles(localAgentsRulesFilePath, localAgentsRulesToggles)
controller.stateManager.setWorkspaceState("localAgentsRulesToggles", updatedLocalAgentsToggles)
return {
windsurfLocalToggles: updatedLocalWindsurfToggles,
cursorLocalToggles: updatedLocalCursorToggles,
agentsLocalToggles: updatedLocalAgentsToggles,
}
}
@@ -117,3 +126,76 @@ export const getLocalCursorRules = async (cwd: string, toggles: ClineRulesToggle
return [cursorRulesFileInstructions, cursorRulesDirInstructions]
}
/**
* Helper function to find all agents.md files recursively (case-insensitive)
* Only searches if a top-level agents.md file exists
*/
async function findAgentsMdFiles(cwd: string): Promise<string[]> {
try {
// First check if top-level agents.md exists
const topLevelAgentsPath = path.resolve(cwd, GlobalFileNames.agentsRulesFile)
const topLevelExists = await fileExistsAtPath(topLevelAgentsPath)
// Only search recursively if top-level agents.md exists
if (!topLevelExists) {
return []
}
// Search recursively for all agents.md files
const [allFiles] = await listFiles(cwd, true, 500)
return allFiles.filter((filePath) => {
const basename = path.basename(filePath).toLowerCase()
return basename === GlobalFileNames.agentsRulesFile.toLowerCase()
})
} catch (error) {
console.error(`Failed to find agents.md files in ${cwd}:`, error)
return []
}
}
/**
* Gather formatted agents rules - searches recursively and combines all agents.md files
*/
export const getLocalAgentsRules = async (cwd: string, toggles: ClineRulesToggles) => {
const agentsRulesFilePath = path.resolve(cwd, GlobalFileNames.agentsRulesFile)
// Check if the top-level agents.md file is enabled
if (agentsRulesFilePath in toggles && toggles[agentsRulesFilePath] === false) {
return undefined
}
try {
const agentsMdFiles = await findAgentsMdFiles(cwd)
if (agentsMdFiles.length === 0) {
return undefined
}
// Read and combine all agents.md files
const combinedContent = await Promise.all(
agentsMdFiles.map(async (filePath) => {
try {
const fullPath = path.resolve(cwd, filePath)
const content = (await fs.readFile(fullPath, "utf8")).trim()
if (content) {
const relativePath = path.relative(cwd, fullPath)
return `## ${relativePath}\n\n${content}`
}
return null
} catch (error) {
console.error(`Failed to read agents.md file at ${filePath}:`, error)
return null
}
}),
).then((contents) => contents.filter(Boolean).join("\n\n"))
if (combinedContent) {
return formatResponse.agentsRulesLocalFileInstructions(cwd, combinedContent)
}
} catch (error) {
console.error("Failed to read agents.md files:", error)
}
return undefined
}
@@ -1,5 +1,6 @@
import { ensureRulesDirectoryExists, ensureWorkflowsDirectoryExists, GlobalFileNames } from "@core/storage/disk"
import { ClineRulesToggles } from "@shared/cline-rules"
import { GlobalInstructionsFile } from "@shared/remote-config/schema"
import { fileExistsAtPath, isDirectory, readDirectory } from "@utils/fs"
import fs from "fs/promises"
import * as path from "path"
@@ -101,6 +102,36 @@ export async function synchronizeRuleToggles(
return updatedToggles
}
/**
* Synchronizes remote rule toggles with current remote config
* Removes toggles for rules that no longer exist, adds defaults for new rules
*/
export function synchronizeRemoteRuleToggles(
remoteRules: GlobalInstructionsFile[],
currentToggles: ClineRulesToggles,
): ClineRulesToggles {
const updatedToggles: ClineRulesToggles = {}
// Create set of current remote rule names
const existingRuleNames = new Set(remoteRules.map((rule) => rule.name))
// Keep toggles only for rules that still exist
for (const [ruleName, enabled] of Object.entries(currentToggles)) {
if (existingRuleNames.has(ruleName)) {
updatedToggles[ruleName] = enabled
}
}
// Add default toggles for new rules (default to enabled)
for (const rule of remoteRules) {
if (!(rule.name in updatedToggles)) {
updatedToggles[rule.name] = true
}
}
return updatedToggles
}
/**
* Certain project rules have more than a single location where rules are allowed to be stored
*/
@@ -268,6 +299,10 @@ export async function deleteRuleFile(
const toggles = controller.stateManager.getWorkspaceStateKey("localWindsurfRulesToggles")
delete toggles[rulePath]
controller.stateManager.setWorkspaceState("localWindsurfRulesToggles", toggles)
} else if (type === "agents") {
const toggles = controller.stateManager.getWorkspaceStateKey("localAgentsRulesToggles")
delete toggles[rulePath]
controller.stateManager.setWorkspaceState("localAgentsRulesToggles", toggles)
} else {
const toggles = controller.stateManager.getWorkspaceStateKey("localClineRulesToggles")
delete toggles[rulePath]
@@ -1,6 +1,6 @@
import { Empty, StringRequest } from "@shared/proto/cline/common"
import { toRequestyServiceUrl } from "@shared/providers/requesty"
import { HostProvider } from "@/hosts/host-provider"
import { toRequestyServiceUrl } from "@/shared/clients/requesty"
import { openExternal } from "@/utils/env"
import { Controller } from ".."
+5 -1
View File
@@ -16,7 +16,10 @@ export async function refreshRules(controller: Controller, _request: EmptyReques
try {
const cwd = await getCwd(getDesktopDir())
const { globalToggles, localToggles } = await refreshClineRulesToggles(controller, cwd)
const { cursorLocalToggles, windsurfLocalToggles } = await refreshExternalRulesToggles(controller, cwd)
const { cursorLocalToggles, windsurfLocalToggles, agentsLocalToggles } = await refreshExternalRulesToggles(
controller,
cwd,
)
const { localWorkflowToggles, globalWorkflowToggles } = await refreshWorkflowToggles(controller, cwd)
return RefreshedRules.create({
@@ -24,6 +27,7 @@ export async function refreshRules(controller: Controller, _request: EmptyReques
localClineRulesToggles: { toggles: localToggles },
localCursorRulesToggles: { toggles: cursorLocalToggles },
localWindsurfRulesToggles: { toggles: windsurfLocalToggles },
localAgentsRulesToggles: { toggles: agentsLocalToggles },
localWorkflowToggles: { toggles: localWorkflowToggles },
globalWorkflowToggles: { toggles: globalWorkflowToggles },
})
@@ -0,0 +1,33 @@
import type { ToggleAgentsRuleRequest } from "@shared/proto/cline/file"
import { ClineRulesToggles } from "@shared/proto/cline/file"
import type { Controller } from "../index"
/**
* Toggles an Agents rule (enable or disable)
* @param controller The controller instance
* @param request The toggle request
* @returns The updated Agents rule toggles
*/
export async function toggleAgentsRule(controller: Controller, request: ToggleAgentsRuleRequest): Promise<ClineRulesToggles> {
const { rulePath, enabled } = request
if (!rulePath || typeof enabled !== "boolean") {
console.error("toggleAgentsRule: Missing or invalid parameters", {
rulePath,
enabled: typeof enabled === "boolean" ? enabled : `Invalid: ${typeof enabled}`,
})
throw new Error("Missing or invalid parameters for toggleAgentsRule")
}
// Update the toggle in workspace state
const toggles = controller.stateManager.getWorkspaceStateKey("localAgentsRulesToggles")
toggles[rulePath] = enabled
controller.stateManager.setWorkspaceState("localAgentsRulesToggles", toggles)
// Get the current state to return in the response
const agentsToggles = controller.stateManager.getWorkspaceStateKey("localAgentsRulesToggles")
return ClineRulesToggles.create({
toggles: agentsToggles,
})
}
+29 -13
View File
@@ -1,6 +1,6 @@
import { getWorkspaceBasename } from "@core/workspace"
import type { ToggleClineRuleRequest } from "@shared/proto/cline/file"
import { ToggleClineRules } from "@shared/proto/cline/file"
import { RuleScope, ToggleClineRules } from "@shared/proto/cline/file"
import { telemetryService } from "@/services/telemetry"
import type { Controller } from "../index"
@@ -11,41 +11,57 @@ import type { Controller } from "../index"
* @returns The updated Cline rule toggles
*/
export async function toggleClineRule(controller: Controller, request: ToggleClineRuleRequest): Promise<ToggleClineRules> {
const { isGlobal, rulePath, enabled } = request
const { scope, rulePath, enabled } = request
if (!rulePath || typeof enabled !== "boolean" || typeof isGlobal !== "boolean") {
if (!rulePath || typeof enabled !== "boolean" || scope === undefined) {
console.error("toggleClineRule: Missing or invalid parameters", {
rulePath,
isGlobal: typeof isGlobal === "boolean" ? isGlobal : `Invalid: ${typeof isGlobal}`,
scope,
enabled: typeof enabled === "boolean" ? enabled : `Invalid: ${typeof enabled}`,
})
throw new Error("Missing or invalid parameters for toggleClineRule")
}
// This is the same core logic as in the original handler
if (isGlobal) {
const toggles = controller.stateManager.getGlobalSettingsKey("globalClineRulesToggles")
toggles[rulePath] = enabled
controller.stateManager.setGlobalState("globalClineRulesToggles", toggles)
} else {
const toggles = controller.stateManager.getWorkspaceStateKey("localClineRulesToggles")
toggles[rulePath] = enabled
controller.stateManager.setWorkspaceState("localClineRulesToggles", toggles)
// Handle the three different scopes
switch (scope) {
case RuleScope.GLOBAL: {
const toggles = controller.stateManager.getGlobalSettingsKey("globalClineRulesToggles")
toggles[rulePath] = enabled
controller.stateManager.setGlobalState("globalClineRulesToggles", toggles)
break
}
case RuleScope.LOCAL: {
const toggles = controller.stateManager.getWorkspaceStateKey("localClineRulesToggles")
toggles[rulePath] = enabled
controller.stateManager.setWorkspaceState("localClineRulesToggles", toggles)
break
}
case RuleScope.REMOTE: {
const toggles = controller.stateManager.getGlobalStateKey("remoteRulesToggles")
toggles[rulePath] = enabled
controller.stateManager.setGlobalState("remoteRulesToggles", toggles)
break
}
default:
throw new Error(`Invalid scope: ${scope}`)
}
// Track rule toggle telemetry with current task context
if (controller.task?.ulid) {
// Extract just the filename for privacy (no full paths)
const ruleFileName = getWorkspaceBasename(rulePath, "Controller.toggleClineRule")
const isGlobal = scope === RuleScope.GLOBAL
telemetryService.captureClineRuleToggled(controller.task.ulid, ruleFileName, enabled, isGlobal)
}
// Get the current state to return in the response
const globalToggles = controller.stateManager.getGlobalSettingsKey("globalClineRulesToggles")
const localToggles = controller.stateManager.getWorkspaceStateKey("localClineRulesToggles")
const remoteToggles = controller.stateManager.getGlobalStateKey("remoteRulesToggles")
return ToggleClineRules.create({
globalClineRulesToggles: { toggles: globalToggles },
localClineRulesToggles: { toggles: localToggles },
remoteRulesToggles: { toggles: remoteToggles },
})
}
+32 -21
View File
@@ -1,4 +1,4 @@
import { ClineRulesToggles, ToggleWorkflowRequest } from "@shared/proto/cline/file"
import { ClineRulesToggles, RuleScope, ToggleWorkflowRequest } from "@shared/proto/cline/file"
import { Controller } from ".."
/**
@@ -8,34 +8,45 @@ import { Controller } from ".."
* @returns The updated workflow toggles
*/
export async function toggleWorkflow(controller: Controller, request: ToggleWorkflowRequest): Promise<ClineRulesToggles> {
const { workflowPath, enabled, isGlobal } = request
const { workflowPath, enabled, scope } = request
if (!workflowPath || typeof enabled !== "boolean") {
if (!workflowPath || typeof enabled !== "boolean" || scope === undefined) {
console.error("toggleWorkflow: Missing or invalid parameters", {
workflowPath,
scope,
enabled: typeof enabled === "boolean" ? enabled : `Invalid: ${typeof enabled}`,
})
throw new Error("Missing or invalid parameters for toggleWorkflow")
}
// Update the toggles based on isGlobal flag
if (isGlobal) {
// Global workflows
const toggles = controller.stateManager.getGlobalSettingsKey("globalWorkflowToggles")
toggles[workflowPath] = enabled
controller.stateManager.setGlobalState("globalWorkflowToggles", toggles)
await controller.postStateToWebview()
// Handle the three different scopes
let toggles: Record<string, boolean>
// Return the global toggles
return ClineRulesToggles.create({ toggles: toggles })
} else {
// Workspace workflows
const toggles = controller.stateManager.getWorkspaceStateKey("workflowToggles")
toggles[workflowPath] = enabled
controller.stateManager.setWorkspaceState("workflowToggles", toggles)
await controller.postStateToWebview()
// Return the workspace toggles
return ClineRulesToggles.create({ toggles: toggles })
switch (scope) {
case RuleScope.GLOBAL: {
toggles = controller.stateManager.getGlobalSettingsKey("globalWorkflowToggles")
toggles[workflowPath] = enabled
controller.stateManager.setGlobalState("globalWorkflowToggles", toggles)
break
}
case RuleScope.LOCAL: {
toggles = controller.stateManager.getWorkspaceStateKey("workflowToggles")
toggles[workflowPath] = enabled
controller.stateManager.setWorkspaceState("workflowToggles", toggles)
break
}
case RuleScope.REMOTE: {
toggles = controller.stateManager.getGlobalStateKey("remoteWorkflowToggles")
toggles[workflowPath] = enabled
controller.stateManager.setGlobalState("remoteWorkflowToggles", toggles)
break
}
default:
throw new Error(`Invalid scope: ${scope}`)
}
await controller.postStateToWebview()
// Return the updated toggles
return ClineRulesToggles.create({ toggles: toggles })
}
+33 -7
View File
@@ -33,6 +33,7 @@ import { LogoutReason } from "@/services/auth/types"
import { featureFlagsService } from "@/services/feature-flags"
import { getDistinctId } from "@/services/logging/distinctId"
import { telemetryService } from "@/services/telemetry"
import { getAxiosSettings } from "@/shared/net"
import { ShowMessageType } from "@/shared/proto/host/window"
import { AuthState } from "@/shared/proto/index.cline"
import { getLatestAnnouncementId } from "@/utils/announcements"
@@ -639,6 +640,23 @@ export class Controller {
}
}
async handleMcpOAuthCallback(serverHash: string, code: string, state: string | null) {
try {
await this.mcpHub.completeOAuth(serverHash, code, state)
await this.postStateToWebview()
HostProvider.window.showMessage({
type: ShowMessageType.INFORMATION,
message: `Successfully authenticated MCP server`,
})
} catch (error) {
console.error("Failed to complete MCP OAuth:", error)
HostProvider.window.showMessage({
type: ShowMessageType.ERROR,
message: `Failed to authenticate MCP server`,
})
}
}
async handleTaskCreation(prompt: string) {
await sendChatButtonClickedEvent()
await this.initTask(prompt)
@@ -651,6 +669,7 @@ export class Controller {
"Content-Type": "application/json",
"User-Agent": "cline-vscode-extension",
},
...getAxiosSettings(),
})
if (!response.data) {
@@ -698,7 +717,7 @@ export class Controller {
async handleOpenRouterCallback(code: string) {
let apiKey: string
try {
const response = await axios.post("https://openrouter.ai/api/v1/auth/keys", { code })
const response = await axios.post("https://openrouter.ai/api/v1/auth/keys", { code }, getAxiosSettings())
if (response.data && response.data.key) {
apiKey = response.data.key
} else {
@@ -848,14 +867,16 @@ export class Controller {
const enableCheckpointsSetting = this.stateManager.getGlobalSettingsKey("enableCheckpointsSetting")
const globalClineRulesToggles = this.stateManager.getGlobalSettingsKey("globalClineRulesToggles")
const globalWorkflowToggles = this.stateManager.getGlobalSettingsKey("globalWorkflowToggles")
const remoteRulesToggles = this.stateManager.getGlobalStateKey("remoteRulesToggles")
const remoteWorkflowToggles = this.stateManager.getGlobalStateKey("remoteWorkflowToggles")
const shellIntegrationTimeout = this.stateManager.getGlobalSettingsKey("shellIntegrationTimeout")
const terminalReuseEnabled = this.stateManager.getGlobalStateKey("terminalReuseEnabled")
const vscodeTerminalExecutionMode = this.stateManager.getGlobalStateKey("vscodeTerminalExecutionMode")
const defaultTerminalProfile = this.stateManager.getGlobalSettingsKey("defaultTerminalProfile")
const isNewUser = this.stateManager.getGlobalStateKey("isNewUser")
const welcomeViewCompleted = Boolean(
this.stateManager.getGlobalStateKey("welcomeViewCompleted") || this.authService.getInfo()?.user?.uid,
)
// Can be undefined but is set to either true or false by the migration that runs on extension launch in extension.ts
const welcomeViewCompleted = !!this.stateManager.getGlobalStateKey("welcomeViewCompleted")
const customPrompt = this.stateManager.getGlobalSettingsKey("customPrompt")
const mcpResponsesCollapsed = this.stateManager.getGlobalStateKey("mcpResponsesCollapsed")
const terminalOutputLineLimit = this.stateManager.getGlobalSettingsKey("terminalOutputLineLimit")
@@ -870,6 +891,7 @@ export class Controller {
const localClineRulesToggles = this.stateManager.getWorkspaceStateKey("localClineRulesToggles")
const localWindsurfRulesToggles = this.stateManager.getWorkspaceStateKey("localWindsurfRulesToggles")
const localCursorRulesToggles = this.stateManager.getWorkspaceStateKey("localCursorRulesToggles")
const localAgentsRulesToggles = this.stateManager.getWorkspaceStateKey("localAgentsRulesToggles")
const workflowToggles = this.stateManager.getWorkspaceStateKey("workflowToggles")
const autoCondenseThreshold = this.stateManager.getGlobalSettingsKey("autoCondenseThreshold")
@@ -892,7 +914,7 @@ export class Controller {
// Set feature flag in dictation settings based on platform
const updatedDictationSettings = {
...dictationSettings,
featureEnabled: process.platform === "darwin", // Enable dictation only on macOS
featureEnabled: process.platform === "darwin" || process.platform === "linux", // Enable dictation on macOS and Linux
}
return {
@@ -925,14 +947,18 @@ export class Controller {
localClineRulesToggles: localClineRulesToggles || {},
localWindsurfRulesToggles: localWindsurfRulesToggles || {},
localCursorRulesToggles: localCursorRulesToggles || {},
localAgentsRulesToggles: localAgentsRulesToggles || {},
localWorkflowToggles: workflowToggles || {},
globalWorkflowToggles: globalWorkflowToggles || {},
remoteRulesToggles: remoteRulesToggles,
remoteWorkflowToggles: remoteWorkflowToggles,
shellIntegrationTimeout,
terminalReuseEnabled,
vscodeTerminalExecutionMode: vscodeTerminalExecutionMode,
defaultTerminalProfile,
isNewUser,
welcomeViewCompleted: welcomeViewCompleted as boolean, // Can be undefined but is set to either true or false by the migration that runs on extension launch in extension.ts
welcomeViewCompleted,
showOnboardingFlow: featureFlagsService.getOnboardingEnabled(),
mcpResponsesCollapsed,
terminalOutputLineLimit,
maxConsecutiveMistakes,
@@ -954,7 +980,7 @@ export class Controller {
},
hooksEnabled: {
user: this.stateManager.getGlobalStateKey("hooksEnabled"),
featureFlag: featureFlagsService.getHooksEnabled(),
featureFlag: true, // Hooks feature is now always available
},
lastDismissedInfoBannerVersion,
lastDismissedModelBannerVersion,
@@ -20,7 +20,7 @@ export async function addRemoteMcpServer(controller: Controller, request: AddRem
}
// Call the McpHub method to add the remote server
const servers = await controller.mcpHub?.addRemoteServer(request.serverName, request.serverUrl)
const servers = await controller.mcpHub?.addRemoteServer(request.serverName, request.serverUrl, request.transportType)
const protoServers = convertMcpServersToProtoMcpServers(servers)
@@ -0,0 +1,26 @@
import type { StringRequest } from "@shared/proto/cline/common"
import { Empty } from "@shared/proto/cline/common"
import type { Controller } from "../index"
/**
* Initiates OAuth authentication for an MCP server
* @param controller The controller instance
* @param request The request containing server name
* @returns Empty response
*/
export async function authenticateMcpServer(controller: Controller, request: StringRequest): Promise<Empty> {
try {
const serverName = request.value
if (!serverName) {
throw new Error("Server name is required")
}
// Call the McpHub method to initiate OAuth
await controller.mcpHub?.initiateOAuth(serverName)
return Empty.create()
} catch (error) {
console.error(`Failed to initiate OAuth for MCP server:`, error)
throw error
}
}
+2
View File
@@ -3,6 +3,7 @@ import { StringRequest } from "@shared/proto/cline/common"
import { McpDownloadResponse } from "@shared/proto/cline/mcp"
import axios from "axios"
import { ClineEnv } from "@/config"
import { getAxiosSettings } from "@/shared/net"
import { Controller } from ".."
import { sendChatButtonClickedEvent } from "../ui/subscribeToChatButtonClicked"
@@ -36,6 +37,7 @@ export async function downloadMcp(controller: Controller, request: StringRequest
{
headers: { "Content-Type": "application/json" },
timeout: 10000,
...getAxiosSettings(),
},
)
@@ -1,6 +1,7 @@
import { EmptyRequest } from "@shared/proto/cline/common"
import { OpenRouterCompatibleModelInfo, OpenRouterModelInfo } from "@shared/proto/cline/models"
import axios from "axios"
import { getAxiosSettings } from "@/shared/net"
import { Controller } from ".."
/**
@@ -11,7 +12,7 @@ import { Controller } from ".."
*/
export async function getAihubmixModels(_controller: Controller, _request: EmptyRequest): Promise<OpenRouterCompatibleModelInfo> {
try {
const response = await axios.get("https://aihubmix.com/call/mdl_info_platform?tag=coding")
const response = await axios.get("https://aihubmix.com/call/mdl_info_platform?tag=coding", getAxiosSettings())
if (!response.data?.success || !Array.isArray(response.data?.data)) {
console.error("Invalid response from AIhubmix API:", response.data)
@@ -1,5 +1,6 @@
import { StringArray, StringRequest } from "@shared/proto/cline/common"
import axios from "axios"
import { getAxiosSettings } from "@/shared/net"
import { Controller } from ".."
/**
@@ -16,7 +17,7 @@ export async function getOllamaModels(_controller: Controller, request: StringRe
return StringArray.create({ values: [] })
}
const response = await axios.get(`${baseUrl}/api/tags`)
const response = await axios.get(`${baseUrl}/api/tags`, getAxiosSettings())
const modelsArray = response.data?.models?.map((model: any) => model.name) || []
const models = [...new Set<string>(modelsArray)].sort()
@@ -1,4 +1,5 @@
import axios from "axios"
import { getAxiosSettings } from "@/shared/net"
import { SapAiCoreModelDeployment, SapAiCoreModelsRequest, SapAiCoreModelsResponse } from "@/shared/proto/cline/models"
import { Controller } from ".."
@@ -33,6 +34,7 @@ async function getToken(clientId: string, clientSecret: string, tokenUrl: string
const url = tokenUrl.replace(/\/+$/, "") + "/oauth/token"
const response = await axios.post(url, payload, {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
...getAxiosSettings(),
})
const token = response.data as Token
token.expires_at = Date.now() + token.expires_in * 1000
@@ -65,7 +67,7 @@ async function fetchAiCoreDeploymentsAndOrchestration(
const url = `${baseUrl}/v2/lm/deployments?$top=10000&$skip=0`
try {
const response = await axios.get(url, { headers })
const response = await axios.get(url, { headers, ...getAxiosSettings() })
const allDeployments = response.data.resources
// Filter running deployments
@@ -5,6 +5,7 @@ import { parsePrice } from "@utils/model-utils"
import axios from "axios"
import fs from "fs/promises"
import path from "path"
import { getAxiosSettings } from "@/shared/net"
import { basetenModels } from "../../../shared/api"
import { Controller } from ".."
@@ -50,6 +51,7 @@ export async function refreshBasetenModels(controller: Controller): Promise<Reco
"User-Agent": "Cline-VSCode-Extension",
},
timeout: 10000, // 10 second timeout
...getAxiosSettings(),
})
if (response.data?.data) {
@@ -5,6 +5,7 @@ import axios from "axios"
import fs from "fs/promises"
import path from "path"
import { telemetryService } from "@/services/telemetry"
import { getAxiosSettings } from "@/shared/net"
import { groqModels } from "../../../shared/api"
import { Controller } from ".."
@@ -52,6 +53,7 @@ export async function refreshGroqModels(controller: Controller): Promise<Record<
"User-Agent": "Cline-VSCode-Extension",
},
timeout: 10000, // 10 second timeout
...getAxiosSettings(),
})
if (response.data?.data) {
@@ -5,6 +5,7 @@ import { fileExistsAtPath } from "@utils/fs"
import axios from "axios"
import fs from "fs/promises"
import path from "path"
import { getAxiosSettings } from "@/shared/net"
import { Controller } from ".."
/**
@@ -33,6 +34,7 @@ export async function refreshHicapModels(controller: Controller, _request: Empty
headers: {
"api-key": hicapApiKey,
},
...getAxiosSettings(),
})
if (response.data?.data) {
@@ -6,6 +6,7 @@ import axios from "axios"
import fs from "fs/promises"
import path from "path"
import { ensureCacheDirectoryExists } from "@/core/storage/disk"
import { getAxiosSettings } from "@/shared/net"
import { Controller } from ".."
/**
@@ -26,6 +27,7 @@ export async function refreshHuggingFaceModels(
// Fetch models from Hugging Face API
const response = await axios.get("https://router.huggingface.co/v1/models", {
timeout: 10000,
...getAxiosSettings(),
})
if (response.data?.data) {
@@ -2,6 +2,7 @@ import { StringArray } from "@shared/proto/cline/common"
import { OpenAiModelsRequest } from "@shared/proto/cline/models"
import type { AxiosRequestConfig } from "axios"
import axios from "axios"
import { getAxiosSettings } from "@/shared/net"
import { Controller } from ".."
/**
@@ -25,7 +26,7 @@ export async function refreshOpenAiModels(_controller: Controller, request: Open
config["headers"] = { Authorization: `Bearer ${request.apiKey}` }
}
const response = await axios.get(`${request.baseUrl}/models`, config)
const response = await axios.get(`${request.baseUrl}/models`, { ...config, ...getAxiosSettings() })
const modelsArray = response.data?.data?.map((model: any) => model.id) || []
const models = [...new Set<string>(modelsArray)]
@@ -4,7 +4,13 @@ import axios from "axios"
import cloneDeep from "clone-deep"
import fs from "fs/promises"
import path from "path"
import { CLAUDE_SONNET_1M_TIERS, openRouterClaudeSonnet41mModelId, openRouterClaudeSonnet451mModelId } from "@/shared/api"
import {
ANTHROPIC_MAX_THINKING_BUDGET,
CLAUDE_SONNET_1M_TIERS,
openRouterClaudeSonnet41mModelId,
openRouterClaudeSonnet451mModelId,
} from "@/shared/api"
import { getAxiosSettings } from "@/shared/net"
import type { Controller } from ".."
type OpenRouterSupportedParams =
@@ -59,7 +65,6 @@ interface OpenRouterRawModelInfo {
input_cache_read: string
input_cache_write: string
} | null
thinking_config: Record<string, unknown> | null
supports_global_endpoint: boolean | null
tiers: any[] | null
supported_parameters?: OpenRouterSupportedParams[] | null
@@ -75,7 +80,7 @@ export async function refreshOpenRouterModels(controller: Controller): Promise<R
const models: Record<string, ModelInfo> = {}
try {
const response = await axios.get("https://openrouter.ai/api/v1/models")
const response = await axios.get("https://openrouter.ai/api/v1/models", getAxiosSettings())
if (response.data?.data) {
const rawModels = response.data.data
@@ -86,8 +91,10 @@ export async function refreshOpenRouterModels(controller: Controller): Promise<R
return undefined
}
for (const rawModel of rawModels as OpenRouterRawModelInfo[]) {
const supportThinking = rawModel.supported_parameters?.some((p) => p === "include_reasoning")
const supportThinking = rawModel.supported_parameters?.some((p) => p === "include_reasoning" || p === "reasoning")
const modelInfo: ModelInfo = {
name: rawModel.name,
maxTokens: rawModel.top_provider?.max_completion_tokens ?? 0,
contextWindow: rawModel.context_length ?? 0,
supportsImages: rawModel.architecture?.modality?.includes("image") ?? false,
@@ -97,7 +104,9 @@ export async function refreshOpenRouterModels(controller: Controller): Promise<R
cacheWritesPrice: parsePrice(rawModel.pricing?.input_cache_write),
cacheReadsPrice: parsePrice(rawModel.pricing?.input_cache_read),
description: rawModel.description ?? "",
thinkingConfig: (supportThinking && rawModel.thinking_config) || undefined,
// If thinking is supported, set maxBudget with a default value as a placeholder
// to ensure it has a valid thinkingConfig that lets the application know thinking is supported.
thinkingConfig: supportThinking ? { maxBudget: ANTHROPIC_MAX_THINKING_BUDGET } : undefined,
supportsGlobalEndpoint: rawModel.supports_global_endpoint ?? undefined,
tiers: rawModel.tiers ?? undefined,
}
@@ -1,7 +1,8 @@
import { EmptyRequest } from "@shared/proto/cline/common"
import { OpenRouterCompatibleModelInfo, OpenRouterModelInfo } from "@shared/proto/cline/models"
import axios from "axios"
import { toRequestyServiceUrl } from "@/shared/providers/requesty"
import { getAxiosSettings } from "@/shared/net"
import { toRequestyServiceUrl } from "@/shared/clients/requesty"
import { Controller } from ".."
/**
@@ -33,7 +34,7 @@ export async function refreshRequestyModels(controller: Controller, _: EmptyRequ
const headers = {
Authorization: `Bearer ${apiKey}`,
}
const response = await axios.get(url, { headers })
const response = await axios.get(url, { headers, ...getAxiosSettings() })
if (response.data?.data) {
for (const model of response.data.data) {
const modelInfo: OpenRouterModelInfo = OpenRouterModelInfo.create({
@@ -4,6 +4,7 @@ import { fileExistsAtPath } from "@utils/fs"
import axios from "axios"
import fs from "fs/promises"
import path from "path"
import { getAxiosSettings } from "@/shared/net"
import { Controller } from ".."
/**
@@ -17,7 +18,7 @@ export async function refreshVercelAiGatewayModels(_controller: Controller): Pro
let models: Record<string, ModelInfo> = {}
try {
const response = await axios.get("https://ai-gateway.vercel.sh/v1/models")
const response = await axios.get("https://ai-gateway.vercel.sh/v1/models", getAxiosSettings())
if (response.data?.data) {
const rawModels = response.data.data
@@ -31,6 +31,20 @@ function parseFieldMask(updateMask: string[]): {
return { options, secrets }
}
/**
* Gets the alternate mode field name (e.g., planModeX <-> actModeX)
* @param fieldName The field name to get alternate for
* @returns The alternate mode field name or null if not a mode-specific field
*/
function getAlternateModeField(fieldName: string): string | null {
if (fieldName.startsWith("planMode")) {
return fieldName.replace("planMode", "actMode")
} else if (fieldName.startsWith("actMode")) {
return fieldName.replace("actMode", "planMode")
}
return null
}
/**
* Updates API configuration using field mask
* @param controller The controller instance
@@ -39,9 +53,9 @@ function parseFieldMask(updateMask: string[]): {
*/
export async function updateApiConfiguration(controller: Controller, request: UpdateApiConfigurationRequestNew): Promise<Empty> {
try {
const { apiConfiguration, updateMask } = request
const { updates, updateMask } = request
if (!apiConfiguration) {
if (!updates) {
throw new Error("API configuration is required")
}
@@ -49,7 +63,7 @@ export async function updateApiConfiguration(controller: Controller, request: Up
throw new Error("Update mask is required and must contain at least one path")
}
const { options: protoOptions, secrets: protoSecrets } = apiConfiguration
const { options: protoOptions, secrets: protoSecrets } = updates
// Parse the field mask to determine which fields to update
const { options: maskOptionsFields, secrets: maskSecretsFields } = parseFieldMask(updateMask)
@@ -81,6 +95,10 @@ export async function updateApiConfiguration(controller: Controller, request: Up
throw new Error(`Field "${fieldName}" specified in mask but not found in options`)
}
}
// Check if mode-specific configurations should be kept separate
const separateModeConfigs = controller.stateManager.getGlobalSettingsKey("planActSeparateModelsSetting")
// Process entries that are in the mask
for (const [key, value] of Object.entries(protoOptions)) {
if (maskOptionsFields.has(key)) {
@@ -92,6 +110,20 @@ export async function updateApiConfiguration(controller: Controller, request: Up
} else {
options[key as keyof ApiHandlerOptions] = value
}
// If mode configs should be synced, also update the alternate mode field
if (!separateModeConfigs) {
const alternateField = getAlternateModeField(key)
if (alternateField) {
if (alternateField === "planModeApiProvider") {
options.planModeApiProvider = convertProtoToApiProvider(value)
} else if (alternateField === "actModeApiProvider") {
options.actModeApiProvider = convertProtoToApiProvider(value)
} else {
options[alternateField as keyof ApiHandlerOptions] = value
}
}
}
}
}
}
@@ -0,0 +1,25 @@
import { Empty } from "@shared/proto/cline/common"
import { OnboardingProgressRequest } from "@shared/proto/cline/state"
import { telemetryService } from "../../../services/telemetry"
import type { Controller } from "../index"
/**
* Captures the onboarding progress step
* @param controller The controller instance
* @param request The request containing the step number
* @returns Empty response
*/
export async function captureOnboardingProgress(_controller: Controller, request: OnboardingProgressRequest): Promise<Empty> {
try {
telemetryService.captureOnboardingProgress({
step: Number(request.step),
model: request.modelSelected,
action: request.action,
completed: !!request.completed,
})
return Empty.create({})
} catch (error) {
console.error("Failed to set welcome view completed:", error)
throw error
}
}
@@ -0,0 +1,17 @@
import type { EmptyRequest } from "@shared/proto/cline/common"
import { Empty } from "@shared/proto/cline/common"
import type { Controller } from "../index"
/**
* Flush all pending state changes immediately to disk
* Bypasses the debounced persistence and forces immediate writes
*/
export async function flushPendingState(controller: Controller, request: EmptyRequest): Promise<Empty> {
try {
await controller.stateManager.flushPendingState()
return Empty.create({})
} catch (error) {
console.error("[flushPendingState] Error flushing pending state:", error)
throw error
}
}
@@ -103,6 +103,7 @@ export async function getTaskHistory(controller: Controller, request: GetTaskHis
tokensOut: item.tokensOut || 0,
cacheWrites: item.cacheWrites || 0,
cacheReads: item.cacheReads || 0,
modelId: item.modelId || "",
}))
return TaskHistoryArray.create({
@@ -0,0 +1,10 @@
/**
* Error thrown when a PreToolUse hook requests cancellation.
* This signals to the tool handler that execution should be aborted.
*/
export class PreToolUseHookCancellationError extends Error {
constructor(message: string = "PreToolUse hook requested cancellation") {
super(message)
this.name = "PreToolUseHookCancellationError"
}
}
+42 -3
View File
@@ -308,10 +308,49 @@ class StdioHookRunner<Name extends HookName> extends HookRunner<Name> {
return output
} catch (parseError) {
// Try to extract JSON from stdout (it might have debug output before/after)
const jsonMatch = stdout.match(/\{[\s\S]*\}/)
if (jsonMatch) {
// Scan from the end to find the last complete JSON object
// This handles cases where hooks output debug info before the actual JSON response
const lines = stdout.split("\n")
let jsonCandidate = ""
let braceCount = 0
let startCollecting = false
// Scan from the end to find the last complete JSON object
for (let i = lines.length - 1; i >= 0; i--) {
const line = lines[i].trimEnd()
// Count braces to track JSON object boundaries
for (let j = line.length - 1; j >= 0; j--) {
if (line[j] === "}") {
braceCount++
if (!startCollecting) {
startCollecting = true
}
} else if (line[j] === "{") {
braceCount--
}
}
if (startCollecting) {
jsonCandidate = line + "\n" + jsonCandidate
}
// If we've closed all braces, we have a complete JSON object
if (startCollecting && braceCount === 0) {
break
}
}
if (jsonCandidate.trim()) {
try {
const outputData = JSON.parse(jsonMatch[0])
// Trim everything before the first opening bracket
const trimmedCandidate = jsonCandidate.trim()
const firstBraceIndex = trimmedCandidate.indexOf("{")
const cleanedJson =
firstBraceIndex !== -1 ? trimmedCandidate.slice(firstBraceIndex) : trimmedCandidate
const outputData = JSON.parse(cleanedJson)
// Validate structure
const validation = validateHookOutput(outputData)
+10 -253
View File
@@ -1,4 +1,5 @@
import { getShell } from "@utils/shell"
import type { ApiProviderInfo } from "@/core/api"
import { getDeepPlanningPrompt } from "./commands/deep-planning"
export const newTaskToolResponse = () =>
`<explicit_instructions type="new_task">
@@ -208,256 +209,12 @@ cline "<prompt>"
</explicit_instructions>\n
`
export const deepPlanningToolResponse = (focusChainSettings?: { enabled: boolean }) => {
const detectedShell = getShell()
// FIXME: detectedShell returns a non-string value on some Windows machines
let isPowerShell = false
try {
isPowerShell =
detectedShell != null &&
typeof detectedShell === "string" &&
(detectedShell.toLowerCase().includes("powershell") || detectedShell.toLowerCase().includes("pwsh"))
} catch {}
return `<explicit_instructions type="deep-planning">
Your task is to create a comprehensive implementation plan before writing any code. This process has four distinct steps that must be completed in order.
Your behavior should be methodical and thorough - take time to understand the codebase completely before making any recommendations. The quality of your investigation directly impacts the success of the implementation.
## STEP 1: Silent Investigation
<important>
until explicitly instructed by the user to proceed with coding.
You must thoroughly understand the existing codebase before proposing any changes.
Perform your research without commentary or narration. Execute commands and read files without explaining what you're about to do. Only speak up if you have specific questions for the user.
</important>
### Required Research Activities
You must use the read_file tool to examine relevant source files, configuration files, and documentation. You must use terminal commands to gather information about the codebase structure and patterns. All terminal output must be piped to cat for visibility.
### Essential Terminal Commands
First, determine the language(s) used in the codebase, then execute these commands to build your understanding. You must tailor them to the codebase and ensure the output is not overly verbose. For example, you should exclude dependency folders such as node_modules, venv or php vendor, etc. These are only examples, the exact commands will differ depending on the codebase.
${
isPowerShell
? `
# Discover project structure and file types
Get-ChildItem -Recurse -Include "*.py","*.js","*.ts","*.java","*.cpp","*.go" | Select-Object -First 30 | Select-Object FullName
# Find all class and function definitions
Get-ChildItem -Recurse -Include "*.py","*.js","*.ts","*.java","*.cpp","*.go" | Select-String -Pattern "class|function|def|interface|struct"
# Analyze import patterns and dependencies
Get-ChildItem -Recurse -Include "*.py","*.js","*.ts","*.java","*.cpp" | Select-String -Pattern "import|from|require|#include" | Sort-Object | Get-Unique
# Find dependency manifests
Get-ChildItem -Recurse -Include "requirements*.txt","package.json","Cargo.toml","pom.xml","Gemfile","go.mod" | Get-Content
# Identify technical debt and TODOs
Get-ChildItem -Recurse -Include "*.py","*.js","*.ts","*.java","*.cpp","*.go" | Select-String -Pattern "TODO|FIXME|XXX|HACK|NOTE"
`
: `
# Discover project structure and file types
find . -type f -name "*.py" -o -name "*.js" -o -name "*.ts" -o -name "*.java" -o -name "*.cpp" -o -name "*.go" | head -30 | cat
# Find all class and function definitions
grep -r "class\|function\|def\|interface\|struct\|func\|type.*struct\|type.*interface" --include="*.py" --include="*.js" --include="*.ts" --include="*.java" --include="*.cpp" --include="*.go" . | cat
# Analyze import patterns and dependencies
grep -r "import\|from\|require\|#include" --include="*.py" --include="*.js" --include="*.ts" --include="*.java" --include="*.cpp" . | sort | uniq | cat
# Find dependency manifests
find . -name "requirements*.txt" -o -name "package.json" -o -name "Cargo.toml" -o -name "pom.xml" -o -name "Gemfile" -o -name "go.mod" | xargs cat
# Identify technical debt and TODOs
grep -r "TODO\|FIXME\|XXX\|HACK\|NOTE" --include="*.py" --include="*.js" --include="*.ts" --include="*.java" --include="*.cpp" --include="*.go" . | cat
`
}
## STEP 2: Discussion and Questions
Ask the user brief, targeted questions that will influence your implementation plan. Keep your questions concise and conversational. Ask only essential questions needed to create an accurate plan.
**Ask questions only when necessary for:**
- Clarifying ambiguous requirements or specifications
- Choosing between multiple equally valid implementation approaches
- Confirming assumptions about existing system behavior or constraints
- Understanding preferences for specific technical decisions that will affect the implementation
Your questions should be direct and specific. Avoid long explanations or multiple questions in one response.
## STEP 3: Create Implementation Plan Document
Create a structured markdown document containing your complete implementation plan. The document must follow this exact format with clearly marked sections:
### Document Structure Requirements
Your implementation plan must be saved as implementation_plan.md, and *must* be structured as follows:
# Implementation Plan
[Overview]
Single sentence describing the overall goal.
Multiple paragraphs outlining the scope, context, and high-level approach. Explain why this implementation is needed and how it fits into the existing system.
[Types]
Single sentence describing the type system changes.
Detailed type definitions, interfaces, enums, or data structures with complete specifications. Include field names, types, validation rules, and relationships.
[Files]
Single sentence describing file modifications.
Detailed breakdown:
- New files to be created (with full paths and purpose)
- Existing files to be modified (with specific changes)
- Files to be deleted or moved
- Configuration file updates
[Functions]
Single sentence describing function modifications.
Detailed breakdown:
- New functions (name, signature, file path, purpose)
- Modified functions (exact name, current file path, required changes)
- Removed functions (name, file path, reason, migration strategy)
[Classes]
Single sentence describing class modifications.
Detailed breakdown:
- New classes (name, file path, key methods, inheritance)
- Modified classes (exact name, file path, specific modifications)
- Removed classes (name, file path, replacement strategy)
[Dependencies]
Single sentence describing dependency modifications.
Details of new packages, version changes, and integration requirements.
[Testing]
Single sentence describing testing approach.
Test file requirements, existing test modifications, and validation strategies.
[Implementation Order]
Single sentence describing the implementation sequence.
Numbered steps showing the logical order of changes to minimize conflicts and ensure successful integration.
## STEP 4: Create Implementation Task
Use the new_task command to create a task for implementing the plan. The task must include a <task_progress> list that breaks down the implementation into trackable steps.
### Task Creation Requirements
Your new task should be self-contained and reference the plan document rather than requiring additional codebase investigation. Include these specific instructions in the task description:
**Plan Document Navigation Commands:**
The implementation agent should use these commands to read specific sections of the implementation plan. You should adapt these examples to conform to the structure of the .md file you createdm, and explicitly provide them when creating the new task:
${
isPowerShell
? `
# Read Overview section
$content = Get-Content implementation_plan.md; $start = ($content | Select-String -Pattern '\\[Overview\\]').LineNumber; $end = ($content | Select-String -Pattern '\\[Types\\]').LineNumber; $content[($start-1)..($end-2)]
# Read Types section
$content = Get-Content implementation_plan.md; $start = ($content | Select-String -Pattern '\\[Types\\]').LineNumber; $end = ($content | Select-String -Pattern '\\[Files\\]').LineNumber; $content[($start-1)..($end-2)]
# Read Files section
$content = Get-Content implementation_plan.md; $start = ($content | Select-String -Pattern '\\[Files\\]').LineNumber; $end = ($content | Select-String -Pattern '\\[Functions\\]').LineNumber; $content[($start-1)..($end-2)]
# Read Functions section
$content = Get-Content implementation_plan.md; $start = ($content | Select-String -Pattern '\\[Functions\\]').LineNumber; $end = ($content | Select-String -Pattern '\\[Classes\\]').LineNumber; $content[($start-1)..($end-2)]
# Read Classes section
$content = Get-Content implementation_plan.md; $start = ($content | Select-String -Pattern '\\[Classes\\]').LineNumber; $end = ($content | Select-String -Pattern '\\[Dependencies\\]').LineNumber; $content[($start-1)..($end-2)]
# Read Dependencies section
$content = Get-Content implementation_plan.md; $start = ($content | Select-String -Pattern '\\[Dependencies\\]').LineNumber; $end = ($content | Select-String -Pattern '\\[Testing\\]').LineNumber; $content[($start-1)..($end-2)]
# Read Testing section
$content = Get-Content implementation_plan.md; $start = ($content | Select-String -Pattern '\\[Testing\\]').LineNumber; $end = ($content | Select-String -Pattern '\\[Implementation Order\\]').LineNumber; $content[($start-1)..($end-2)]
# Read Implementation Order section
$content = Get-Content implementation_plan.md; $start = ($content | Select-String -Pattern '\\[Implementation Order\\]').LineNumber; $content[($start-1)..($content.Length-1)]
`
: `
# Read Overview section
sed -n '/\[Overview\]/,/\[Types\]/p' implementation_plan.md | head -n 1 | cat
# Read Types section
sed -n '/\[Types\]/,/\[Files\]/p' implementation_plan.md | head -n 1 | cat
# Read Files section
sed -n '/\[Files\]/,/\[Functions\]/p' implementation_plan.md | head -n 1 | cat
# Read Functions section
sed -n '/\[Functions\]/,/\[Classes\]/p' implementation_plan.md | head -n 1 | cat
# Read Classes section
sed -n '/\[Classes\]/,/\[Dependencies\]/p' implementation_plan.md | head -n 1 | cat
# Read Dependencies section
sed -n '/\[Dependencies\]/,/\[Testing\]/p' implementation_plan.md | head -n 1 | cat
# Read Testing section
sed -n '/\[Testing\]/,/\[Implementation Order\]/p' implementation_plan.md | head -n 1 | cat
# Read Implementation Order section
sed -n '/\[Implementation Order\]/,$p' implementation_plan.md | cat
`
}
**Task Progress Format:**
<IMPORTANT>
You absolutely must include the task_progress contents in context when creating the new task. When providing it, do not wrap it in XML tags- instead provide it like this:
task_progress Items:
- [ ] Step 1: Brief description of first implementation step
- [ ] Step 2: Brief description of second implementation step
- [ ] Step 3: Brief description of third implementation step
- [ ] Step N: Brief description of final implementation step
You also MUST include the path to the markdown file you have created in your new task prompt. You should do this as follows:
Refer to @path/to/file/markdown.md for a complete breakdown of the task requirements and steps. You should periodically read this file again.
${
focusChainSettings?.enabled
? `
**Task Progress Parameter:**
When creating the new task, you must include a task_progress parameter that breaks down the implementation into trackable steps. This should follow the standard Markdown checklist format with "- [ ]" for incomplete items.`
: ""
}
### Mode Switching
When creating the new task, request a switch to "act mode" if you are currently in "plan mode". This ensures the implementation agent operates in execution mode rather than planning mode.
</IMPORTANT>
## Quality Standards
You must be specific with exact file paths, function names, and class names. You must be comprehensive and avoid assuming implicit understanding. You must be practical and consider real-world constraints and edge cases. You must use precise technical language and avoid ambiguity.
Your implementation plan should be detailed enough that another developer could execute it without additional investigation.
---
**Execute all four steps in sequence. Your role is to plan thoroughly, not to implement. Code creation begins only after the new task is created and you receive explicit instruction to proceed.**
Below is the user's input when they indicated that they wanted to create a comprehensive implementation plan.
</explicit_instructions>\n
`
/**
* Generates the deep-planning slash command response with model-family-aware variant selection
* @param focusChainSettings Optional focus chain settings to include in the prompt
* @param providerInfo Optional API provider info for model family detection
* @returns The deep-planning prompt string with appropriate variant and focus chain settings applied
*/
export const deepPlanningToolResponse = (focusChainSettings?: { enabled: boolean }, providerInfo?: ApiProviderInfo) => {
return getDeepPlanningPrompt(focusChainSettings, providerInfo)
}
@@ -0,0 +1,43 @@
import type { ApiProviderInfo } from "@/core/api"
import type { SystemPromptContext } from "@/core/prompts/system-prompt/types"
import { getDeepPlanningRegistry } from "./registry"
import { generateGPT51Template } from "./variants/gpt5"
/**
* Generates the deep-planning slash command response with model-family-aware variant selection
* @param focusChainSettings Optional focus chain settings to include in the prompt
* @param providerInfo Optional API provider info for model family detection
* @returns The deep-planning prompt string with appropriate variant and focus chain settings applied
*/
export function getDeepPlanningPrompt(focusChainSettings?: { enabled: boolean }, providerInfo?: ApiProviderInfo): string {
// Create context for variant selection
const context: SystemPromptContext = {
providerInfo: providerInfo || ({} as ApiProviderInfo),
ide: "vscode",
}
// Get the appropriate variant from registry
const registry = getDeepPlanningRegistry()
const variant = registry.get(context)
// For variants with extensive focus chain prompting, generate template with focus chain flag
let template: string
if (variant.id === "gpt-5") {
template = generateGPT51Template(focusChainSettings?.enabled ?? false)
} else {
template = variant.template
}
// For variants with simpler focus chain prompting, Replace the FOCUS_CHAIN_PARAM placeholder with actual content
const focusChainParam = focusChainSettings?.enabled
? `**Task Progress Parameter:**
When creating the new task, you must include a task_progress parameter that breaks down the implementation into trackable steps. This parameter should be included inside the tool call, but not located inside of other content/argument blocks. This should follow the standard Markdown checklist format with "- [ ]" for incomplete items.`
: ""
template = template.replace("{{FOCUS_CHAIN_PARAM}}", focusChainParam)
return template
}
// Export types for external use
export type { DeepPlanningRegistry, DeepPlanningVariant } from "./types"
@@ -0,0 +1,92 @@
import type { SystemPromptContext } from "@/core/prompts/system-prompt/types"
import type { DeepPlanningVariant, DeepPlanningRegistry as IDeepPlanningRegistry } from "./types"
import { createAnthropicVariant, createGeminiVariant, createGenericVariant, createGPT51Variant } from "./variants"
/**
* Singleton registry for managing deep-planning prompt variants
* Selects appropriate variant based on model family detection
*/
class DeepPlanningRegistry implements IDeepPlanningRegistry {
private static instance: DeepPlanningRegistry | null = null
private variants: Map<string, DeepPlanningVariant> = new Map()
private genericVariant: DeepPlanningVariant
private constructor() {
// Initialize all variants
this.registerVariant(createAnthropicVariant())
this.registerVariant(createGeminiVariant())
this.registerVariant(createGPT51Variant())
// Generic variant must be registered last as fallback
const genericVariant = createGenericVariant()
this.registerVariant(genericVariant)
this.genericVariant = genericVariant
}
/**
* Get the singleton instance of the registry
*/
public static getInstance(): DeepPlanningRegistry {
if (!DeepPlanningRegistry.instance) {
DeepPlanningRegistry.instance = new DeepPlanningRegistry()
}
return DeepPlanningRegistry.instance
}
/**
* Register a new variant in the registry
*/
public register(variant: DeepPlanningVariant): void {
this.registerVariant(variant)
}
/**
* Internal method to register a variant
*/
private registerVariant(variant: DeepPlanningVariant): void {
this.variants.set(variant.id, variant)
}
/**
* Get the appropriate variant based on the system prompt context
* Uses matcher functions to determine which variant to use
* Falls back to generic variant if no match or on error
*/
public get(context: SystemPromptContext): DeepPlanningVariant {
try {
// Try each variant's matcher function (except generic which is last)
for (const variant of this.variants.values()) {
// Skip generic variant in iteration (it's the fallback)
if (variant.id === "generic") {
continue
}
// Test if this variant matches the context
if (variant.matcher(context)) {
return variant
}
}
// No match found, return generic variant
return this.genericVariant
} catch (error) {
// On any error, safely fall back to generic variant
console.warn("Error selecting deep-planning variant, falling back to generic:", error)
return this.genericVariant
}
}
/**
* Get all registered variants
*/
public getAll(): DeepPlanningVariant[] {
return Array.from(this.variants.values())
}
}
/**
* Export singleton instance getter
*/
export function getDeepPlanningRegistry(): DeepPlanningRegistry {
return DeepPlanningRegistry.getInstance()
}
@@ -0,0 +1,38 @@
import type { SystemPromptContext } from "@/core/prompts/system-prompt/types"
/**
* Configuration for a deep-planning prompt variant
*/
export interface DeepPlanningVariant {
/** Unique identifier for this variant (e.g., "anthropic", "gemini", "gpt-5", "generic") */
id: string
/** Human-readable description of this variant */
description: string
/** The model family this variant is designed for */
family: string
/** Version number for this variant */
version: number
/** Matcher function to determine if this variant should be used */
matcher: (context: SystemPromptContext) => boolean
/** The complete prompt template string */
template: string
}
/**
* Registry for deep-planning prompt variants
*/
export interface DeepPlanningRegistry {
/** Get the appropriate variant based on context */
get(context: SystemPromptContext): DeepPlanningVariant
/** Register a new variant */
register(variant: DeepPlanningVariant): void
/** Get all registered variants */
getAll(): DeepPlanningVariant[]
}

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