mirror of
https://github.com/cline/cline.git
synced 2026-09-01 23:19:18 +08:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eebb99c1e3 | |||
| 9ed44f7a83 | |||
| 6a95cc5f19 | |||
| 1d0637f39c | |||
| f2c16bae7a | |||
| 204f15ce1c | |||
| 8118e11596 | |||
| f7b593df35 | |||
| 2e0358a7a1 | |||
| 0fbc10f807 | |||
| c093ca1760 | |||
| dc85299a73 | |||
| 4533ed3ea8 | |||
| 66d7664d1b | |||
| 50aeb7098f | |||
| c2e6eafb45 | |||
| 0220628c39 | |||
| 5052220195 | |||
| abf3081e56 | |||
| 125cb78a31 | |||
| 17539228ea | |||
| bdb7cc36fa | |||
| d3e1065707 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add claude 4.5 haiku
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: finalize document content during approval flow
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Expose --version in cline cli command
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: prevent duplicate diff errors when parallel tool calling is enabled
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Log Persistence errors to PostHog
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
feat: add OpenAI Codex (ChatGPT Plus/Pro) provider
|
||||
|
||||
Users with ChatGPT Plus or Pro subscriptions can now use GPT-5 models directly through Cline without needing an API key. Authentication is handled via OAuth through OpenAI's authentication system.
|
||||
|
||||
Models available:
|
||||
- gpt-5.2-codex (default)
|
||||
- gpt-5.1-codex-max
|
||||
- gpt-5.1-codex-mini
|
||||
- gpt-5.2
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix two bugs in DiffViewProvider file editing:
|
||||
|
||||
1. **Line boundary validation**: Add `safelyTruncateDocument()` to prevent out-of-bounds line errors on JetBrains hosts (fixes #8423, #8429). The gRPC protocol strictly validates line numbers, causing "truncateDocument INTERNAL: Wrong line" errors when `truncateDocument()` was called with a line number >= document line count.
|
||||
|
||||
2. **Content concatenation on final update**: When replacing content without a trailing newline, the old content at line N+1 was concatenated to the new content. Fixed by extending the replacement range to cover the entire document on final update.
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
docs: fix outdated Ollama model names in documentation
|
||||
|
||||
Updated recommended Ollama models to use correct identifiers:
|
||||
- Changed qwen3-coder-30b to qwen2.5-coder:32b
|
||||
- Changed devstral-small to codellama:34b-code
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix(extract-text): strip notebook outputs to reduce context size
|
||||
|
||||
Strip notebook cell outputs when extracting text content from Jupyter notebooks, significantly reducing the amount of context sent to the LLM while preserving the essential code and markdown content.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
OpenAI GPT-5 Codex models are now using Apply Patch tool for diff edits.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix issue where tool call ids are invalid when switching between models using the chat completion format and the responses api format.
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: improve Jupyter notebook diff view and reduce LLM context for notebook edits
|
||||
|
||||
- Restore switchToSpecializedEditor() for Jupyter notebook diff views that was accidentally removed during rebase
|
||||
- Open .ipynb files in Jupyter notebook editor after save instead of leaving stale diff view
|
||||
- Strip notebook outputs from content sent to LLM, reducing context by 95% (196KB → 9KB)
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: add chat output on skill use
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Adding telemetry for background exec terminal
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
This pull request introduces comprehensive Jupyter Notebook support for Cline, enabling AI-assisted editing of `.ipynb` files with full cell-level context awareness. The feature allows users to seamlessly work with Jupyter notebooks using Cline's AI capabilities while preserving the notebook's JSON structure.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"cline": patch
|
||||
---
|
||||
|
||||
Remove deprecated zai-glm-4.6 model from Cerebras provider
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Make Sonnet 4.5 the default Amazon Bedrock model id
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: removes retry message from UI after retry succeeds
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Support native tool calling for LM Studio and Ollama provider
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fixing integration tests from testing framework
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
add claude 4.5 opus into sap provider.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Limite Vertex and LiteLLM options when they're remote configured
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix crash when the Context Menu has a type but no options
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Throttle the remote config fetch
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Improve history view filter menu
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add git worktree management UI for running parallel Cline sessions
|
||||
+20
-2
@@ -1,10 +1,28 @@
|
||||
# Changelog
|
||||
|
||||
## [3.53.0]
|
||||
|
||||
### Fixed
|
||||
- Removed grok model from free tier
|
||||
|
||||
## [3.52.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Users with ChatGPT Plus or Pro subscriptions can now use GPT-5 models directly through Cline without needing an API key. Authentication is handled via OAuth through OpenAI's authentication system.
|
||||
- Grok models are now moving out of free tier and into paid plans.
|
||||
- Introduces comprehensive Jupyter Notebook support for Cline, enabling AI-assisted editing of `.ipynb` files with full cell-level context awareness.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bugs in DiffViewProvider for file editing
|
||||
- Ollama's recommended models to use correct identifiers
|
||||
|
||||
## [3.51.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Adding OpenAI gpt-5.2-codex model to the model picker
|
||||
- Adding OpenAI gpt-5.2-codex model to the model picker
|
||||
|
||||
## [3.50.0]
|
||||
|
||||
@@ -1737,4 +1755,4 @@ Add Opus 4.1 through Claude Code
|
||||
|
||||
## [0.0.6]
|
||||
|
||||
- Initial release
|
||||
- Initial release
|
||||
|
||||
+23
@@ -147,6 +147,29 @@
|
||||
"src/dev/grit/vscode-api.grit"
|
||||
]
|
||||
},
|
||||
{
|
||||
// Do not use console logging directly, use the Logger service instead.
|
||||
"plugins": [
|
||||
"src/dev/grit/console-log.grit"
|
||||
],
|
||||
"includes": [
|
||||
"**",
|
||||
"!**/webview-ui/**",
|
||||
"!**/evals/**",
|
||||
"!**/standalone/**",
|
||||
"!**/e2e/**",
|
||||
"!**/test/**",
|
||||
"!**/__tests__/**",
|
||||
"!**/*.test.ts",
|
||||
"!**/*.stories.ts",
|
||||
"!src/dev/**",
|
||||
"!**/*.mjs",
|
||||
"!**/*.js",
|
||||
"!**/scripts/**",
|
||||
"!**/*.tsx",
|
||||
"!**/testing-platform/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"includes": [
|
||||
"**",
|
||||
|
||||
@@ -231,7 +231,7 @@ func promptForInitialTask(ctx context.Context, modeFlag string, workspaces []str
|
||||
|
||||
prompt, err := output.PromptForInitialTask(
|
||||
"Start a new Cline task",
|
||||
"/plan or /act to switch modes\nctrl+e to open editor\ntab to autocomplete commands",
|
||||
"/plan or /act to switch modes\ntab to autocomplete commands\nctrl+e to open editor\nctrl+c to exit",
|
||||
modeFlag,
|
||||
slash.NewRegistry(ctx),
|
||||
)
|
||||
|
||||
@@ -298,6 +298,64 @@ cline task view
|
||||
\f[I]# Start interactive chat with this task\f[R]
|
||||
cline task chat
|
||||
.EE
|
||||
.SH ENVIRONMENT
|
||||
.TP
|
||||
\f[B]CLINE_COMMAND_PERMISSIONS\f[R]
|
||||
JSON configuration for restricting which shell commands Cline can
|
||||
execute.
|
||||
When set, commands are validated against allow/deny patterns before
|
||||
execution.
|
||||
When not set, all commands are allowed (backward compatibility).
|
||||
.RS
|
||||
.PP
|
||||
Format:
|
||||
\f[CR]{"allow": ["pattern1", "pattern2"], "deny": ["pattern3"], "allowRedirects": true}\f[R]
|
||||
.PP
|
||||
\f[B]Fields:\f[R]
|
||||
.IP \(bu 2
|
||||
\f[B]allow\f[R] (array of strings): Glob patterns for allowed commands.
|
||||
If specified, only matching commands are permitted.
|
||||
Uses \f[CR]*\f[R] to match any characters and \f[CR]?\f[R] to match a
|
||||
single character.
|
||||
.IP \(bu 2
|
||||
\f[B]deny\f[R] (array of strings): Glob patterns for denied commands.
|
||||
Deny rules take precedence over allow rules.
|
||||
.IP \(bu 2
|
||||
\f[B]allowRedirects\f[R] (boolean): Whether to allow shell redirects
|
||||
(\f[CR]>\f[R], \f[CR]>>\f[R], \f[CR]<\f[R], etc.).
|
||||
Defaults to false.
|
||||
.PP
|
||||
\f[B]Rule evaluation:\f[R]
|
||||
.IP "1." 3
|
||||
Check for dangerous characters (backticks outside single quotes,
|
||||
unquoted newlines)
|
||||
.IP "2." 3
|
||||
Parse command into segments split by operators (\f[CR]&&\f[R],
|
||||
\f[CR]||\f[R], \f[CR]|\f[R], \f[CR];\f[R])
|
||||
.IP "3." 3
|
||||
If redirects detected and \f[CR]allowRedirects\f[R] is not true, command
|
||||
is denied
|
||||
.IP "4." 3
|
||||
Each segment is validated against deny rules first, then allow rules
|
||||
.IP "5." 3
|
||||
Subshell contents (\f[CR]$(...)\f[R] and \f[CR](...)\f[R]) are
|
||||
recursively validated
|
||||
.IP "6." 3
|
||||
All segments must pass for the command to be allowed
|
||||
.PP
|
||||
\f[B]Examples:\f[R]
|
||||
.IP
|
||||
.EX
|
||||
\f[I]# Allow only npm and git commands\f[R]
|
||||
export CLINE_COMMAND_PERMISSIONS=\(aq{"allow": ["npm *", "git *"]}\(aq
|
||||
|
||||
\f[I]# Allow development commands but deny dangerous ones\f[R]
|
||||
export CLINE_COMMAND_PERMISSIONS=\(aq{"allow": ["npm *", "git *", "node *"], "deny": ["rm \-rf *", "sudo *"]}\(aq
|
||||
|
||||
\f[I]# Allow file operations with redirects\f[R]
|
||||
export CLINE_COMMAND_PERMISSIONS=\(aq{"allow": ["cat *", "echo *"], "allowRedirects": true}\(aq
|
||||
.EE
|
||||
.RE
|
||||
.SH ARCHITECTURE
|
||||
Cline operates on a three\-layer architecture:
|
||||
.TP
|
||||
|
||||
@@ -323,6 +323,42 @@ cline task view
|
||||
cline task chat
|
||||
```
|
||||
|
||||
# ENVIRONMENT
|
||||
|
||||
**CLINE_COMMAND_PERMISSIONS**
|
||||
|
||||
: JSON configuration for restricting which shell commands Cline can execute. When set, commands are validated against allow/deny patterns before execution. When not set, all commands are allowed.
|
||||
|
||||
Format: `{"allow": ["pattern1", "pattern2"], "deny": ["pattern3"], "allowRedirects": true}`
|
||||
|
||||
**Fields:**
|
||||
|
||||
- **allow** (array of strings): Glob patterns for allowed commands. If specified, only matching commands are permitted. Uses `*` to match any characters and `?` to match a single character. Setting allow on anything will deny all others.
|
||||
- **deny** (array of strings): Glob patterns for denied commands. Deny rules take precedence over allow rules.
|
||||
- **allowRedirects** (boolean): Whether to allow shell redirects (`>`, `>>`, `<`, etc.). Defaults to false.
|
||||
|
||||
**Rule evaluation:**
|
||||
|
||||
1. Check for dangerous characters (backticks outside single quotes, unquoted newlines)
|
||||
2. Parse command into segments split by operators (`&&`, `||`, `|`, `;`)
|
||||
3. If redirects detected and `allowRedirects` is not true, command is denied
|
||||
4. Each segment is validated against deny rules first, then allow rules
|
||||
5. Subshell contents (`$(...)` and `(...)`) are recursively validated
|
||||
6. All segments must pass for the command to be allowed
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Allow only npm and git commands.
|
||||
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *"]}'
|
||||
|
||||
# Allow development commands but deny dangerous ones. Deny not strictly required here since allow is set.
|
||||
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *", "node *"], "deny": ["rm -rf *", "sudo *"]}'
|
||||
|
||||
# Allow file operations with redirects
|
||||
export CLINE_COMMAND_PERMISSIONS='{"allow": ["cat *", "echo *"], "allowRedirects": true}'
|
||||
```
|
||||
|
||||
# ARCHITECTURE
|
||||
|
||||
Cline operates on a three-layer architecture:
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cline",
|
||||
"version": "1.0.9",
|
||||
"version": "1.0.10",
|
||||
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
|
||||
"main": "cline-core.js",
|
||||
"bin": {
|
||||
|
||||
@@ -110,7 +110,6 @@ func (m *Manager) ListSettings(ctx context.Context) error {
|
||||
"dictationSettings",
|
||||
"autoCondenseThreshold",
|
||||
"autoApprovalSettings",
|
||||
"hooksEnabled",
|
||||
}
|
||||
|
||||
// Render each field using the renderer
|
||||
|
||||
@@ -79,7 +79,7 @@ func RenderField(key string, value interface{}, censor bool) error {
|
||||
"planActSeparateModelsSetting", "enableCheckpointsSetting",
|
||||
"terminalReuseEnabled", "mcpResponsesCollapsed", "strictPlanModeEnabled",
|
||||
"useAutoCondense", "yoloModeToggled", "shellIntegrationTimeout",
|
||||
"terminalOutputLineLimit", "autoCondenseThreshold", "hooksEnabled":
|
||||
"terminalOutputLineLimit", "autoCondenseThreshold":
|
||||
fmt.Printf("%s: %s\n", camelToKebab(key), formatValue(value, key, censor))
|
||||
return nil
|
||||
|
||||
|
||||
@@ -348,7 +348,7 @@ func (m *InputModel) handleSubmit() (tea.Model, tea.Cmd) {
|
||||
return ChangeInputTypeMsg{
|
||||
InputType: InputTypeFeedback,
|
||||
Title: "Your feedback",
|
||||
Placeholder: "/plan or /act to switch modes\nctrl+e to open editor",
|
||||
Placeholder: "/plan or /act to switch modes\nctrl+e to open editor\nctrl+c to exit",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ func (ih *InputHandler) promptForInput(ctx context.Context) (string, bool, error
|
||||
model := output.NewInputModelWithRegistry(
|
||||
output.InputTypeMessage,
|
||||
"Cline is ready for your message...",
|
||||
"/plan or /act to switch modes\nctrl+e to open editor\ntab to autocomplete commands",
|
||||
"/plan or /act to switch modes\ntab to autocomplete commands\nctrl+e to open editor\nctrl+c to exit",
|
||||
currentMode,
|
||||
ih.slashCommandRegistry,
|
||||
)
|
||||
|
||||
@@ -290,12 +290,6 @@ func setSimpleField(settings *cline.Settings, key, value string) error {
|
||||
return err
|
||||
}
|
||||
settings.ActModeAwsBedrockCustomSelected = boolPtr(val)
|
||||
case "hooks_enabled":
|
||||
val, err := parseBool(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.HooksEnabled = boolPtr(val)
|
||||
case "azure_identity":
|
||||
val, err := parseBool(value)
|
||||
if err != nil {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.9 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.2 MiB |
@@ -148,6 +148,7 @@
|
||||
"features/hooks/samples"
|
||||
]
|
||||
},
|
||||
"features/jupyter-notebooks",
|
||||
"features/multiroot-workspace",
|
||||
"features/plan-and-act",
|
||||
"features/skills",
|
||||
@@ -198,6 +199,7 @@
|
||||
"provider-config/anthropic",
|
||||
"provider-config/claude-code",
|
||||
"provider-config/openai",
|
||||
"provider-config/openai-codex",
|
||||
"provider-config/openrouter",
|
||||
"provider-config/cerebras",
|
||||
"provider-config/deepseek",
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
---
|
||||
title: "Jupyter Notebooks"
|
||||
description: "AI-assisted editing of Jupyter notebooks with cell-level context awareness"
|
||||
---
|
||||
|
||||
# Jupyter Notebooks
|
||||
|
||||
Cline provides comprehensive support for Jupyter notebooks (`.ipynb` files), enabling AI-assisted editing with full cell-level context awareness. This feature was developed in collaboration with Amazon to bring AI coding assistance to data science workflows.
|
||||
|
||||
## Getting started
|
||||
|
||||
Jupyter notebook support is a built-in feature of Cline. To use it, you just need to have the Jupyter notebook extension enabled in VS Code. Once you open any `.ipynb` file, you'll see AI-assisted buttons in your notebook interface.
|
||||
|
||||
## How to use
|
||||
|
||||
### Generate Cell
|
||||
|
||||
Click the sparkle icon (✨) in the notebook toolbar to generate new cells with AI assistance.
|
||||
|
||||

|
||||
|
||||
**How it works:** The AI receives context from surrounding cells, so it understands the variables and imports already in scope. This means you can reference existing DataFrames, functions, and other objects without re-explaining them.
|
||||
|
||||
**Example prompt:** "Create a visualization showing the correlation matrix of numeric columns with a heatmap"
|
||||
|
||||
The cell is inserted with proper notebook JSON structure, preserving metadata and ready to execute.
|
||||
|
||||
### Explain Cell
|
||||
|
||||
Click the Explain button in any cell's title bar to get a detailed explanation of what the cell does.
|
||||
|
||||
This is useful for:
|
||||
|
||||
- Revisiting old notebooks
|
||||
- Onboarding to a teammate's analysis
|
||||
- Understanding complex transformations
|
||||
|
||||
**How it works:** Cline extracts the full cell context, including outputs, so explanations can reference actual results like column names, row counts, and computed values.
|
||||
|
||||
### Improve Cell
|
||||
|
||||
Click the Improve button in any cell's title bar to enhance existing cells with AI suggestions.
|
||||
|
||||

|
||||
|
||||
Use this to:
|
||||
|
||||
- Optimize slow pandas operations
|
||||
- Add error handling
|
||||
- Refactor for clarity
|
||||
- Convert loops to vectorized operations
|
||||
|
||||
**How it works:** Cline suggests improvements while preserving the cell's position and metadata in the notebook structure. The AI explains what was changed and why.
|
||||
|
||||
## How cell context works
|
||||
|
||||
Unlike traditional file editing, Jupyter notebooks are JSON documents containing arrays of cells. Each cell has its own type, source content, metadata, execution count, and outputs.
|
||||
|
||||
When you use a Jupyter command, Cline extracts structured context that includes:
|
||||
|
||||
- **Cell type** (code, markdown, or raw)
|
||||
- **Source content** as an array of lines
|
||||
- **Cell metadata** and configuration
|
||||
- **Execution count** for code cells
|
||||
- **Outputs** including data, text, and error traces
|
||||
|
||||
This structured representation allows the AI to understand not just the code, but its context within the notebook and its actual output.
|
||||
|
||||
### JSON structure preservation
|
||||
|
||||
Cline is designed to work carefully with the cell JSON structure, aiming to:
|
||||
|
||||
- Keep cell boundaries intact
|
||||
- Preserve execution counts
|
||||
- Maintain cell metadata
|
||||
- Keep outputs associated with their source cells
|
||||
|
||||
The AI is specifically prompted to preserve notebook structure, though you should always review changes to ensure your notebook format remains correct.
|
||||
|
||||
## Keyboard shortcuts
|
||||
|
||||
You can bind any of these commands to keyboard shortcuts for faster access:
|
||||
|
||||
1. Open VS Code keyboard shortcuts (Cmd/Ctrl + K, Cmd/Ctrl + S)
|
||||
2. Search for `cline.jupyterGenerateCell`, `cline.jupyterExplainCell`, or `cline.jupyterImproveCell`
|
||||
3. Assign your preferred key combinations
|
||||
|
||||
## Tips for best results
|
||||
|
||||
**For Generate Cell:**
|
||||
- Be specific about what you want the cell to do
|
||||
- Reference existing variables by name (the AI can see them)
|
||||
- Mention preferred libraries if you have a preference (e.g., "use seaborn" or "use plotly")
|
||||
|
||||
**For Explain Cell:**
|
||||
- Works best on cells that have been executed (outputs provide additional context)
|
||||
- Good for complex chained operations like pandas groupby/merge sequences
|
||||
|
||||
**For Improve Cell:**
|
||||
- Mention what aspect you want to improve (performance, readability, error handling)
|
||||
- The AI will explain the changes it suggests
|
||||
|
||||
## Limitations
|
||||
|
||||
- Notebook support requires the Jupyter notebook extension to be enabled in VS Code
|
||||
- Cell context extraction depends on VS Code's notebook API
|
||||
- Very large notebooks may require more context than some models can handle efficiently
|
||||
|
||||
## Related
|
||||
|
||||
- [Cline Tools Guide](/exploring-clines-tools/cline-tools-guide) -- Overview of all Cline commands
|
||||
- [Model Selection Guide](/core-features/model-selection-guide) -- Choosing the right model for your workflow
|
||||
@@ -25,11 +25,28 @@ The easiest way to get started is with **Cline** as your provider:
|
||||
|
||||
## Alternative: Use Another Provider
|
||||
|
||||
If you prefer to use your own API keys, you can select from providers like:
|
||||
If you prefer to use your own accounts or API keys, you have several options:
|
||||
|
||||
### Sign in with OpenAI (Recommended)
|
||||
|
||||
The easiest way to use OpenAI models is with **OpenAI Codex**—no API keys needed:
|
||||
|
||||
1. Select **"OpenAI Codex"** from the API Provider dropdown
|
||||
2. Click **"Sign in with OpenAI"**
|
||||
3. Authorize Cline in your browser
|
||||
4. Choose your model
|
||||
|
||||
<Card title="OpenAI Codex Setup Guide" icon="key" href="/provider-config/openai-codex">
|
||||
See the full setup guide with screenshots and troubleshooting tips.
|
||||
</Card>
|
||||
|
||||
### Other Providers
|
||||
|
||||
You can also use API keys with these providers:
|
||||
|
||||
- **OpenRouter** - Great value, multiple models
|
||||
- **Anthropic** - Direct access to Claude models
|
||||
- **OpenAI** - Access to GPT models
|
||||
- **OpenAI** - Access to GPT models via API key
|
||||
- **Google Gemini** - Google's AI models
|
||||
- **Ollama** - Run models locally on your computer
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ Cerebras delivers the world's fastest AI inference through their revolutionary w
|
||||
|
||||
Cline supports the following Cerebras models:
|
||||
|
||||
- `zai-glm-4.6` - Fast general-purpose model on Cerebras (up to 1,000 tokens/s). To be deprecated soon.
|
||||
- `zai-glm-4.7` - Highly capable general-purpose model on Cerebras (up to 1,000 tokens/s), competitive with leading proprietary models on coding tasks.
|
||||
- `qwen-3-235b-a22b-instruct-2507` - Advanced instruction-following model
|
||||
- `qwen-3-235b-a22b-thinking-2507` - Reasoning model with step-by-step thinking
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: "OpenAI Codex"
|
||||
sidebarTitle: "OpenAI Codex"
|
||||
description: "Connect your OpenAI account to Cline via OAuth for seamless access to OpenAI models."
|
||||
---
|
||||
|
||||
OpenAI Codex lets you connect your OpenAI account directly to Cline using OAuth, meaning you don't need to manage API keys. Simply sign in with your OpenAI account through a one-click browser authentication, and you'll automatically have access to all the models available on your OpenAI plan. No additional configuration required.
|
||||
|
||||
<Tip>
|
||||
If you prefer to use API keys instead, see the [OpenAI (API Key)](/provider-config/openai) provider configuration.
|
||||
</Tip>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- An [OpenAI account](https://platform.openai.com/signup)
|
||||
- An active OpenAI subscription or API access plan
|
||||
|
||||
<Note>
|
||||
The models available to you depend on your OpenAI account's subscription tier. See [OpenAI's pricing page](https://openai.com/pricing) for details on what's included in each plan.
|
||||
</Note>
|
||||
|
||||
## Connecting Your OpenAI Account
|
||||
|
||||
<Steps>
|
||||
<Step title="Open Cline Settings">
|
||||
Click the settings gear icon in the top-right corner of the Cline extension.
|
||||
</Step>
|
||||
<Step title="Select OpenAI Codex Provider">
|
||||
Choose **"OpenAI Codex"** from the "API Provider" dropdown menu.
|
||||
</Step>
|
||||
<Step title="Sign In with OpenAI">
|
||||
Click the **"Sign in with OpenAI"** button. This will open a browser window for authentication.
|
||||
</Step>
|
||||
<Step title="Authorize Cline">
|
||||
In the browser window that opens:
|
||||
1. Sign in to your OpenAI account (if not already signed in)
|
||||
2. Review the permissions Cline is requesting
|
||||
3. Click **"Authorize"** to grant access
|
||||
</Step>
|
||||
<Step title="Select Your Model">
|
||||
Once authorized, you'll be returned to Cline. Choose your desired model from the **"Model"** dropdown.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Note>
|
||||
If a model doesn't appear in your dropdown, it may not be available on your OpenAI subscription tier.
|
||||
</Note>
|
||||
|
||||
For the most current list of available models and their capabilities, see the official [OpenAI Models documentation](https://platform.openai.com/docs/models).
|
||||
|
||||
## Managing Your Connection
|
||||
|
||||
### Disconnecting Your Account
|
||||
|
||||
To disconnect your OpenAI account from Cline:
|
||||
|
||||
1. Open Cline Settings
|
||||
2. With OpenAI Codex selected as the provider, click **"Sign out"**
|
||||
3. Confirm the disconnection
|
||||
|
||||
This removes the OAuth connection. You can reconnect at any time by signing in again.
|
||||
|
||||
### Token Refresh
|
||||
|
||||
OAuth tokens are automatically refreshed by Cline. If you encounter authentication errors:
|
||||
|
||||
1. Try disconnecting and reconnecting your account
|
||||
2. Ensure your OpenAI account is in good standing
|
||||
3. Check that your subscription is active
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Authentication failed" error
|
||||
|
||||
- Ensure you're signing in with the correct OpenAI account
|
||||
- Check that your OpenAI subscription is active
|
||||
- Try clearing your browser cache and signing in again
|
||||
|
||||
### Models not appearing
|
||||
|
||||
- The available models depend on your OpenAI subscription tier
|
||||
- Some models may require specific plan upgrades
|
||||
- Check [OpenAI's pricing page](https://openai.com/pricing) for model availability by plan
|
||||
|
||||
### OAuth window doesn't open
|
||||
|
||||
- Check if pop-ups are blocked in your browser
|
||||
- Try using a different browser
|
||||
- Ensure you have a stable internet connection
|
||||
|
||||
### Connection keeps expiring
|
||||
|
||||
- This is rare but can happen if your OpenAI account session expired
|
||||
- Disconnect and reconnect to refresh your authentication
|
||||
|
||||
## Good to Know
|
||||
|
||||
Cline only requests the permissions necessary to make API calls on your behalf. Your OpenAI credentials are never stored by Cline.
|
||||
|
||||
When using OpenAI Codex, Cline accesses models through your OpenAI/ChatGPT subscription. There is no separate per-token API billing or consumption of OpenAI API credits for this provider; usage and limits are governed by your ChatGPT (or OpenAI account) subscription plan, not by Cline.
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [OpenAI (API Key)](/provider-config/openai): Alternative configuration using API keys
|
||||
- [Model Selection Guide](/core-features/model-selection-guide): Help choosing the right model
|
||||
Generated
+48
-65
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "claude-dev",
|
||||
"version": "3.51.0",
|
||||
"version": "3.53.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-dev",
|
||||
"version": "3.51.0",
|
||||
"version": "3.53.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.37.0",
|
||||
@@ -1184,6 +1184,7 @@
|
||||
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.3",
|
||||
@@ -2646,6 +2647,7 @@
|
||||
"node_modules/@grpc/grpc-js": {
|
||||
"version": "1.9.15",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@grpc/proto-loader": "^0.7.8",
|
||||
"@types/node": ">=12.12.47"
|
||||
@@ -3229,6 +3231,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.1.tgz",
|
||||
"integrity": "sha512-yO28oVFFC7EBoiKdAn+VqRm+plcfv4v0xp6osG/VsCB0NlPZWi87ajbCZZ8f/RvOFLEu7//rSRmuZZ7lMoe3gQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.19.7",
|
||||
"ajv": "^8.17.1",
|
||||
@@ -3297,6 +3300,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
@@ -4912,8 +4916,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.52.4",
|
||||
@@ -4926,8 +4929,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.52.4",
|
||||
@@ -4940,8 +4942,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.52.4",
|
||||
@@ -4954,8 +4955,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.52.4",
|
||||
@@ -4968,8 +4968,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.52.4",
|
||||
@@ -4982,8 +4981,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.52.4",
|
||||
@@ -4996,8 +4994,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.52.4",
|
||||
@@ -5010,8 +5007,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.52.4",
|
||||
@@ -5024,8 +5020,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.52.4",
|
||||
@@ -5038,8 +5033,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.52.4",
|
||||
@@ -5052,8 +5046,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.52.4",
|
||||
@@ -5066,8 +5059,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.52.4",
|
||||
@@ -5080,8 +5072,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.52.4",
|
||||
@@ -5094,8 +5085,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.52.4",
|
||||
@@ -5108,8 +5098,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.52.4",
|
||||
@@ -5122,8 +5111,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.52.4",
|
||||
@@ -5136,8 +5124,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.52.4",
|
||||
@@ -5150,8 +5137,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.52.4",
|
||||
@@ -5164,8 +5150,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.52.4",
|
||||
@@ -5178,8 +5163,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.52.4",
|
||||
@@ -5192,8 +5176,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.52.4",
|
||||
@@ -5206,8 +5189,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@sap-ai-sdk/ai-api": {
|
||||
"version": "2.1.0",
|
||||
@@ -6770,8 +6752,7 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/get-folder-size": {
|
||||
"version": "3.0.4",
|
||||
@@ -6803,6 +6784,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.21.tgz",
|
||||
"integrity": "sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
@@ -7519,6 +7501,7 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -8320,6 +8303,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.3",
|
||||
"caniuse-lite": "^1.0.30001741",
|
||||
@@ -8654,9 +8638,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cheerio/node_modules/undici": {
|
||||
"version": "6.22.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz",
|
||||
"integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==",
|
||||
"version": "6.23.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
|
||||
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
@@ -9525,7 +9509,8 @@
|
||||
},
|
||||
"node_modules/devtools-protocol": {
|
||||
"version": "0.0.1342118",
|
||||
"license": "BSD-3-Clause"
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "5.2.0",
|
||||
@@ -12476,6 +12461,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
@@ -12709,6 +12695,7 @@
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
||||
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
|
||||
"license": "MPL-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3"
|
||||
},
|
||||
@@ -14860,9 +14847,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/open-graph-scraper/node_modules/undici": {
|
||||
"version": "6.22.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz",
|
||||
"integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==",
|
||||
"version": "6.23.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
|
||||
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
@@ -15430,6 +15417,7 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -15611,7 +15599,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -15632,7 +15619,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
@@ -16322,7 +16308,6 @@
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
|
||||
"integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
@@ -17800,7 +17785,6 @@
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.3"
|
||||
@@ -17817,7 +17801,6 @@
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
@@ -18178,6 +18161,7 @@
|
||||
"version": "5.5.3",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -18250,9 +18234,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz",
|
||||
"integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==",
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.19.0.tgz",
|
||||
"integrity": "sha512-Heho1hJD81YChi+uS2RkSjcVO+EQLmLSyUlHyp7Y/wFbxQaGb4WXVKD073JytrjXJVkSZVzoE2MCSOKugFGtOQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
@@ -18430,7 +18414,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
||||
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -18505,7 +18488,6 @@
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
@@ -19193,6 +19175,7 @@
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
+1
-1
@@ -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.51.0",
|
||||
"version": "3.53.0",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
|
||||
@@ -108,7 +108,6 @@ message Secrets {
|
||||
message Settings {
|
||||
optional string lite_llm_base_url = 1;
|
||||
optional bool lite_llm_use_prompt_cache = 2;
|
||||
map<string, string> open_ai_headers = 3;
|
||||
optional string anthropic_base_url = 4;
|
||||
optional string open_router_provider_sorting = 5;
|
||||
optional string aws_region = 6;
|
||||
@@ -257,7 +256,6 @@ message Settings {
|
||||
optional FocusChainSettings focus_chain_settings = 149;
|
||||
optional string custom_prompt = 150;
|
||||
optional double auto_condense_threshold = 151;
|
||||
optional bool hooks_enabled = 152;
|
||||
optional bool subagents_enabled = 153;
|
||||
optional bool enable_parallel_tool_calling = 154;
|
||||
optional bool background_edit_enabled = 155;
|
||||
@@ -278,6 +276,7 @@ message Settings {
|
||||
optional int32 open_telemetry_log_batch_timeout = 170;
|
||||
optional int32 open_telemetry_log_max_queue_size = 171;
|
||||
optional bool worktrees_enabled = 172;
|
||||
map<string, string> open_ai_headers = 173;
|
||||
}
|
||||
|
||||
message DictationSettings {
|
||||
@@ -384,6 +383,8 @@ message UpdateTaskSettingsRequest {
|
||||
|
||||
// Message for updating settings
|
||||
message UpdateSettingsRequest {
|
||||
reserved 26; // was hooks_enabled (removed - now always enabled on macOS/Linux)
|
||||
|
||||
Metadata metadata = 1;
|
||||
optional ModelsApiConfiguration api_configuration = 2;
|
||||
optional string telemetry_setting = 3;
|
||||
@@ -408,7 +409,6 @@ message UpdateSettingsRequest {
|
||||
optional DictationSettings dictation_settings = 23;
|
||||
optional double auto_condense_threshold = 24;
|
||||
optional bool multi_root_enabled = 25;
|
||||
optional bool hooks_enabled = 26;
|
||||
optional string vscode_terminal_execution_mode = 27;
|
||||
optional int32 max_consecutive_mistakes = 28;
|
||||
optional bool subagents_enabled = 29;
|
||||
|
||||
@@ -70,6 +70,7 @@ enum ClineSay {
|
||||
HOOK_STATUS = 30;
|
||||
HOOK_OUTPUT_STREAM = 31;
|
||||
COMMAND_PERMISSION_DENIED = 32;
|
||||
CONDITIONAL_RULES_APPLIED = 33;
|
||||
}
|
||||
|
||||
// Enum for ClineSayTool tool types
|
||||
|
||||
+7
-8
@@ -2,18 +2,18 @@ import * as vscode from "vscode"
|
||||
import {
|
||||
cleanupMcpMarketplaceCatalogFromGlobalState,
|
||||
migrateCustomInstructionsToGlobalRules,
|
||||
migrateHooksEnabledToBoolean,
|
||||
migrateTaskHistoryToFile,
|
||||
migrateWelcomeViewCompleted,
|
||||
migrateWorkspaceToGlobalStorage,
|
||||
} from "./core/storage/state-migrations"
|
||||
import { WebviewProvider } from "./core/webview"
|
||||
import { Logger } from "./services/logging/Logger"
|
||||
import "./utils/path" // necessary to have access to String.prototype.toPosix
|
||||
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { FileContextTracker } from "./core/context/context-tracking/FileContextTracker"
|
||||
import { StateManager } from "./core/storage/StateManager"
|
||||
import { openAiCodexOAuthManager } from "./integrations/openai-codex/oauth"
|
||||
import { ExtensionRegistryInfo } from "./registry"
|
||||
import { BannerService } from "./services/banner/BannerService"
|
||||
import { audioRecordingService } from "./services/dictation/AudioRecordingService"
|
||||
@@ -26,7 +26,6 @@ import { ShowMessageType } from "./shared/proto/host/window"
|
||||
import { syncWorker } from "./shared/services/worker/sync"
|
||||
import { getBlobStoreSettingsFromEnv } from "./shared/services/worker/worker"
|
||||
import { getLatestAnnouncementId } from "./utils/announcements"
|
||||
import { openAiCodexOAuthManager } from "./integrations/openai-codex/oauth"
|
||||
import { arePathsEqual } from "./utils/path"
|
||||
/**
|
||||
* Performs intialization for Cline that is common to all platforms.
|
||||
@@ -35,10 +34,13 @@ import { arePathsEqual } from "./utils/path"
|
||||
* @returns The webview provider
|
||||
*/
|
||||
export async function initialize(context: vscode.ExtensionContext): Promise<WebviewProvider> {
|
||||
// Configure the shared Logging class to use HostProvider's output channel
|
||||
Logger.setOutput((msg: string) => HostProvider.get().logToChannel(msg))
|
||||
|
||||
try {
|
||||
await StateManager.initialize(context)
|
||||
} catch (error) {
|
||||
console.error("[Controller] CRITICAL: Failed to initialize StateManager - extension may not function properly:", error)
|
||||
Logger.error("[Controller] CRITICAL: Failed to initialize StateManager - extension may not function properly:", error)
|
||||
HostProvider.window.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Failed to initialize Cline's application state. Please restart the extension.",
|
||||
@@ -70,9 +72,6 @@ export async function initialize(context: vscode.ExtensionContext): Promise<Webv
|
||||
// Ensure taskHistory.json exists and migrate legacy state (runs once)
|
||||
await migrateTaskHistoryToFile(context)
|
||||
|
||||
// Migrate hooksEnabled from ClineFeatureSetting to boolean (one-time cleanup)
|
||||
await migrateHooksEnabledToBoolean(context)
|
||||
|
||||
// Clean up MCP marketplace catalog from global state (moved to disk cache)
|
||||
await cleanupMcpMarketplaceCatalogFromGlobalState(context)
|
||||
|
||||
@@ -127,7 +126,7 @@ async function showVersionUpdateAnnouncement(context: vscode.ExtensionContext) {
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
console.error(`Error during post-update actions: ${errorMessage}, Stack trace: ${error.stack}`)
|
||||
Logger.error(`Error during post-update actions: ${errorMessage}, Stack trace: ${error.stack}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ class ClineEndpoint {
|
||||
this.environment = Environment.production
|
||||
break
|
||||
}
|
||||
console.info("Cline environment updated: ", this.environment)
|
||||
}
|
||||
|
||||
public getEnvironment(): EnvironmentConfig {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ApiConfiguration, ModelInfo, QwenApiRegions } from "@shared/api"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { ClineTool } from "@/shared/tools"
|
||||
import { AIhubmixHandler } from "./providers/aihubmix"
|
||||
import { AnthropicHandler } from "./providers/anthropic"
|
||||
@@ -480,7 +481,7 @@ export function buildApiHandler(configuration: ApiConfiguration, mode: Mode): Ap
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("buildApiHandler error:", error)
|
||||
Logger.error("buildApiHandler error:", error)
|
||||
}
|
||||
|
||||
return createHandlerForProvider(apiProvider, options, mode)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AskSageModelId, askSageDefaultModelId, askSageDefaultURL, askSageModels, ModelInfo } from "@shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
@@ -58,7 +59,7 @@ export class AskSageHandler implements ApiHandler {
|
||||
private apiKey: string
|
||||
|
||||
constructor(options: AskSageHandlerOptions) {
|
||||
console.log("init api url", options.asksageApiUrl, askSageDefaultURL)
|
||||
Logger.log("init api url", options.asksageApiUrl, askSageDefaultURL)
|
||||
this.options = options
|
||||
this.apiKey = options.asksageApiKey || ""
|
||||
this.apiUrl = options.asksageApiUrl || askSageDefaultURL
|
||||
@@ -164,7 +165,7 @@ export class AskSageHandler implements ApiHandler {
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
console.error("Failed to fetch AskSage usage", await response.text())
|
||||
Logger.error("Failed to fetch AskSage usage", await response.text())
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -177,7 +178,7 @@ export class AskSageHandler implements ApiHandler {
|
||||
outputTokens: 0,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching AskSage usage:", error)
|
||||
Logger.error("Error fetching AskSage usage:", error)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { BedrockModelId, bedrockDefaultModelId, bedrockModels, CLAUDE_SONNET_1M_
|
||||
import { calculateApiCostOpenAI, calculateApiCostQwen } from "@utils/cost"
|
||||
import { ExtensionRegistryInfo } from "@/registry"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
@@ -109,9 +110,13 @@ interface ProviderChainOptions {
|
||||
profile?: string
|
||||
}
|
||||
|
||||
// a special jp inference profile was created for sonnet 4.5
|
||||
// a special jp inference profile was created for sonnet 4.5 & haiku 4.5
|
||||
// https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html
|
||||
const JP_SUPPORTED_CRIS_MODELS = ["anthropic.claude-sonnet-4-5-20250929-v1:0", "anthropic.claude-sonnet-4-5-20250929-v1:0:1m"]
|
||||
const JP_SUPPORTED_CRIS_MODELS = [
|
||||
"anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||
"anthropic.claude-sonnet-4-5-20250929-v1:0:1m",
|
||||
"anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||
]
|
||||
|
||||
// https://docs.anthropic.com/en/api/claude-on-amazon-bedrock
|
||||
export class AwsBedrockHandler implements ApiHandler {
|
||||
@@ -449,7 +454,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing Deepseek response chunk:", error)
|
||||
Logger.error("Error parsing Deepseek response chunk:", error)
|
||||
// Propagate the error by yielding a text response with error information
|
||||
yield {
|
||||
type: "text",
|
||||
@@ -550,7 +555,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
|
||||
for await (const chunk of response.stream) {
|
||||
// Debug logging to see actual response structure
|
||||
// console.log("Bedrock chunk:", JSON.stringify(chunk, null, 2))
|
||||
// Logger.log("Bedrock chunk:", JSON.stringify(chunk, null, 2))
|
||||
|
||||
// Handle thinking response in additionalModelResponseFields (LangChain format)
|
||||
const metadata = chunk.metadata as ExtendedMetadata | undefined
|
||||
@@ -854,7 +859,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
// Log unsupported content types for debugging
|
||||
console.warn(`Unsupported content type: ${(item as ContentItem).type}`)
|
||||
Logger.warn(`Unsupported content type: ${(item as ContentItem).type}`)
|
||||
return null
|
||||
})
|
||||
.filter((item): item is ContentBlock => item !== null)
|
||||
@@ -912,7 +917,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to process image content:", error)
|
||||
Logger.error("Failed to process image content:", error)
|
||||
// Return a text content indicating the error instead of null
|
||||
// This ensures users are aware of the issue
|
||||
return {
|
||||
@@ -1111,7 +1116,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error with OpenAI model via Converse API:", error)
|
||||
Logger.error("Error with OpenAI model via Converse API:", error)
|
||||
|
||||
// Try to extract more detailed error information
|
||||
let errorMessage = "Failed to process OpenAI model request"
|
||||
@@ -1246,7 +1251,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error with Qwen model via Converse API:", error)
|
||||
Logger.error("Error with Qwen model via Converse API:", error)
|
||||
|
||||
// Try to extract more detailed error information
|
||||
let errorMessage = "Failed to process Qwen model request"
|
||||
|
||||
@@ -112,10 +112,11 @@ export class CerebrasHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
try {
|
||||
const model = this.getModel()
|
||||
const stream = await client.chat.completions.create({
|
||||
model: this.getModel().id,
|
||||
model: model.id,
|
||||
messages: cerebrasMessages,
|
||||
temperature: 0,
|
||||
temperature: model.info.temperature ?? 0,
|
||||
stream: true,
|
||||
max_tokens: CEREBRAS_DEFAULT_MAX_TOKENS,
|
||||
})
|
||||
|
||||
@@ -7,10 +7,10 @@ import { ClineEnv } from "@/config"
|
||||
import { ClineAccountService } from "@/services/account/ClineAccountService"
|
||||
import { AuthService } from "@/services/auth/AuthService"
|
||||
import { buildClineExtraHeaders } from "@/services/EnvUtils"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { CLINE_ACCOUNT_AUTH_ERROR_MESSAGE } from "@/shared/ClineAccount"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch, getAxiosSettings } from "@/shared/net"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { createOpenRouterStream } from "../transform/openrouter-stream"
|
||||
@@ -126,7 +126,7 @@ export class ClineHandler implements ApiHandler {
|
||||
// openrouter returns an error object instead of the openai sdk throwing an error
|
||||
if ("error" in chunk) {
|
||||
const error = chunk.error as OpenRouterErrorResponse["error"]
|
||||
console.error(`Cline API Error: ${error?.code} - ${error?.message}`)
|
||||
Logger.error(`Cline API Error: ${error?.code} - ${error?.message}`)
|
||||
// Include metadata in the error message if available
|
||||
const metadataStr = error.metadata ? `\nMetadata: ${JSON.stringify(error.metadata, null, 2)}` : ""
|
||||
throw new Error(`Cline API Error ${error.code}: ${error.message}${metadataStr}`)
|
||||
@@ -143,7 +143,7 @@ export class ClineHandler implements ApiHandler {
|
||||
const choiceWithError = choice as any
|
||||
if (choiceWithError.error) {
|
||||
const error = choiceWithError.error
|
||||
console.error(`Cline Mid-Stream Error: ${error.code || error.type || "Unknown"} - ${error.message}`)
|
||||
Logger.error(`Cline Mid-Stream Error: ${error.code || error.type || "Unknown"} - ${error.message}`)
|
||||
throw new Error(`Cline Mid-Stream Error: ${error.code || error.type || "Unknown"} - ${error.message}`)
|
||||
} else {
|
||||
throw new Error(
|
||||
@@ -199,7 +199,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 (["x-ai/grok-code-fast-1", "kwaipilot/kat-coder-pro"].includes(this.getModel().id)) {
|
||||
if (["kwaipilot/kat-coder-pro"].includes(this.getModel().id)) {
|
||||
totalCost = 0
|
||||
}
|
||||
|
||||
@@ -217,14 +217,14 @@ export class ClineHandler implements ApiHandler {
|
||||
|
||||
// Fallback to generation endpoint if usage chunk not returned
|
||||
if (!didOutputUsage) {
|
||||
console.warn("Cline API did not return usage chunk, fetching from generation endpoint")
|
||||
Logger.warn("Cline API did not return usage chunk, fetching from generation endpoint")
|
||||
const apiStreamUsage = await this.getApiStreamUsage()
|
||||
if (apiStreamUsage) {
|
||||
yield apiStreamUsage
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Cline API Error:", error)
|
||||
Logger.error("Cline API Error:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -260,7 +260,7 @@ export class ClineHandler implements ApiHandler {
|
||||
}
|
||||
} catch (error) {
|
||||
// ignore if fails
|
||||
console.error("Error fetching cline generation details:", error)
|
||||
Logger.error("Error fetching cline generation details:", error)
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { ModelInfo } from "../../../shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
@@ -84,7 +85,7 @@ export class DifyHandler implements ApiHandler {
|
||||
this.apiKey = options.difyApiKey || ""
|
||||
this.baseUrl = options.difyBaseUrl || ""
|
||||
|
||||
console.log("[DIFY DEBUG] Constructor called with:", {
|
||||
Logger.log("[DIFY DEBUG] Constructor called with:", {
|
||||
hasApiKey: !!this.apiKey,
|
||||
baseUrl: this.baseUrl,
|
||||
})
|
||||
@@ -98,7 +99,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[]): ApiStream {
|
||||
console.log("[DIFY DEBUG] createMessage called with:", {
|
||||
Logger.log("[DIFY DEBUG] createMessage called with:", {
|
||||
systemPromptLength: systemPrompt?.length || 0,
|
||||
messagesCount: messages?.length || 0,
|
||||
})
|
||||
@@ -115,8 +116,8 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
const fullUrl = `${this.baseUrl}/chat-messages`
|
||||
console.log("[DIFY DEBUG] Making request to:", fullUrl)
|
||||
console.log("[DIFY DEBUG] Request body:", JSON.stringify(requestBody, null, 2))
|
||||
Logger.log("[DIFY DEBUG] Making request to:", fullUrl)
|
||||
Logger.log("[DIFY DEBUG] Request body:", JSON.stringify(requestBody, null, 2))
|
||||
|
||||
let response: Response
|
||||
try {
|
||||
@@ -129,21 +130,21 @@ export class DifyHandler implements ApiHandler {
|
||||
body: JSON.stringify(requestBody),
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error("[DIFY DEBUG] Network error during fetch:", error)
|
||||
Logger.error("[DIFY DEBUG] Network error during fetch:", error)
|
||||
const cause = error.cause ? ` | Cause: ${error.cause}` : ""
|
||||
throw new Error(`Dify API network error: ${error.message}${cause}`)
|
||||
}
|
||||
|
||||
console.log("[DIFY DEBUG] Response status:", response.status)
|
||||
Logger.log("[DIFY DEBUG] Response status:", response.status)
|
||||
const headersObj: Record<string, string> = {}
|
||||
response.headers.forEach((value, key) => {
|
||||
headersObj[key] = value
|
||||
})
|
||||
console.log("[DIFY DEBUG] Response headers:", headersObj)
|
||||
Logger.log("[DIFY DEBUG] Response headers:", headersObj)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
console.error("[DIFY DEBUG] Error response:", errorText)
|
||||
Logger.error("[DIFY DEBUG] Error response:", errorText)
|
||||
throw new Error(`Dify API error: ${response.status} ${response.statusText} - ${errorText}`)
|
||||
}
|
||||
|
||||
@@ -159,14 +160,14 @@ export class DifyHandler implements ApiHandler {
|
||||
const processedEvents: string[] = []
|
||||
let lastEventTime = Date.now()
|
||||
|
||||
console.log("[DIFY DEBUG] Starting to read streaming response...")
|
||||
Logger.log("[DIFY DEBUG] Starting to read streaming response...")
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) {
|
||||
console.log("[DIFY DEBUG] Stream ended naturally")
|
||||
console.log(
|
||||
Logger.log("[DIFY DEBUG] Stream ended naturally")
|
||||
Logger.log(
|
||||
"[DIFY DEBUG] Final state - hasYieldedContent:",
|
||||
hasYieldedContent,
|
||||
"fullText length:",
|
||||
@@ -178,7 +179,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
const chunk = decoder.decode(value, { stream: true })
|
||||
console.log("[DIFY DEBUG] Raw chunk received:", JSON.stringify(chunk))
|
||||
Logger.log("[DIFY DEBUG] Raw chunk received:", JSON.stringify(chunk))
|
||||
|
||||
buffer += chunk
|
||||
const lines = buffer.split("\n")
|
||||
@@ -187,41 +188,41 @@ export class DifyHandler implements ApiHandler {
|
||||
buffer = lines.pop() || ""
|
||||
|
||||
for (const line of lines) {
|
||||
console.log("[DIFY DEBUG] Processing line:", JSON.stringify(line))
|
||||
Logger.log("[DIFY DEBUG] Processing line:", JSON.stringify(line))
|
||||
|
||||
if (line.startsWith("data: ")) {
|
||||
const data = line.slice(6).trim()
|
||||
console.log("[DIFY DEBUG] Extracted data:", JSON.stringify(data))
|
||||
Logger.log("[DIFY DEBUG] Extracted data:", JSON.stringify(data))
|
||||
|
||||
if (data === "[DONE]") {
|
||||
console.log("[DIFY DEBUG] Received [DONE] signal")
|
||||
Logger.log("[DIFY DEBUG] Received [DONE] signal")
|
||||
break
|
||||
}
|
||||
|
||||
if (data === "") {
|
||||
console.log("[DIFY DEBUG] Empty data line, skipping")
|
||||
Logger.log("[DIFY DEBUG] Empty data line, skipping")
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data)
|
||||
console.log("[DIFY DEBUG] Parsed JSON:", parsed)
|
||||
Logger.log("[DIFY DEBUG] Parsed JSON:", parsed)
|
||||
processedEvents.push(parsed.event || "unknown")
|
||||
lastEventTime = Date.now()
|
||||
|
||||
// Capture conversation_id as soon as it's available
|
||||
if (parsed.conversation_id && !this.conversationId) {
|
||||
this.conversationId = parsed.conversation_id
|
||||
console.log("[DIFY DEBUG] Captured conversation_id:", this.conversationId)
|
||||
Logger.log("[DIFY DEBUG] Captured conversation_id:", this.conversationId)
|
||||
}
|
||||
|
||||
// Handle different Dify event types based on actual Dify API
|
||||
if (parsed.event === "message") {
|
||||
console.log("[DIFY DEBUG] Message event, answer:", parsed.answer)
|
||||
Logger.log("[DIFY DEBUG] Message event, answer:", parsed.answer)
|
||||
// Dify sends the full text in each "answer" chunk, so we replace.
|
||||
if (typeof parsed.answer === "string") {
|
||||
fullText = parsed.answer
|
||||
console.log("[DIFY DEBUG] Updated fullText length:", fullText.length)
|
||||
Logger.log("[DIFY DEBUG] Updated fullText length:", fullText.length)
|
||||
yield {
|
||||
type: "text",
|
||||
text: fullText,
|
||||
@@ -229,10 +230,10 @@ export class DifyHandler implements ApiHandler {
|
||||
hasYieldedContent = true
|
||||
}
|
||||
} else if (parsed.event === "message_replace") {
|
||||
console.log("[DIFY DEBUG] Replace message event:", parsed)
|
||||
Logger.log("[DIFY DEBUG] Replace message event:", parsed)
|
||||
if (parsed.answer) {
|
||||
fullText = parsed.answer // Replace instead of append
|
||||
console.log("[DIFY DEBUG] Replaced fullText length:", fullText.length)
|
||||
Logger.log("[DIFY DEBUG] Replaced fullText length:", fullText.length)
|
||||
yield {
|
||||
type: "text",
|
||||
text: fullText,
|
||||
@@ -240,7 +241,7 @@ export class DifyHandler implements ApiHandler {
|
||||
hasYieldedContent = true
|
||||
}
|
||||
} else if (parsed.event === "message_end") {
|
||||
console.log("[DIFY DEBUG] Message end event", parsed)
|
||||
Logger.log("[DIFY DEBUG] Message end event", parsed)
|
||||
// Message completed. Yield final text if we have any.
|
||||
if (fullText) {
|
||||
yield {
|
||||
@@ -260,19 +261,19 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
return // End of stream
|
||||
} else if (parsed.event === "error") {
|
||||
console.error("[DIFY DEBUG] Error event:", parsed)
|
||||
Logger.error("[DIFY DEBUG] Error event:", parsed)
|
||||
throw new Error(`Dify API error: ${parsed.message || "Unknown error"}`)
|
||||
} else if (parsed.event === "workflow_started" || parsed.event === "workflow_finished") {
|
||||
console.log("[DIFY DEBUG] Workflow event:", parsed.event)
|
||||
Logger.log("[DIFY DEBUG] Workflow event:", parsed.event)
|
||||
// These are informational events, continue processing
|
||||
} else if (parsed.event === "node_started" || parsed.event === "node_finished") {
|
||||
console.log("[DIFY DEBUG] Node event:", parsed.event, parsed.data)
|
||||
Logger.log("[DIFY DEBUG] Node event:", parsed.event, parsed.data)
|
||||
// These are informational events, continue processing
|
||||
} else if (parsed.event === "ping") {
|
||||
console.log("[DIFY DEBUG] Ping event received, keeping connection alive.")
|
||||
Logger.log("[DIFY DEBUG] Ping event received, keeping connection alive.")
|
||||
// Ping event, do nothing
|
||||
} else {
|
||||
console.log("[DIFY DEBUG] Unknown event type:", parsed.event, "Full object:", parsed)
|
||||
Logger.log("[DIFY DEBUG] Unknown event type:", parsed.event, "Full object:", parsed)
|
||||
// Try to extract text from other possible fields
|
||||
if (parsed.text) {
|
||||
fullText += parsed.text
|
||||
@@ -299,17 +300,17 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("[DIFY DEBUG] Failed to parse JSON:", data, "Error:", e)
|
||||
Logger.warn("[DIFY DEBUG] Failed to parse JSON:", data, "Error:", e)
|
||||
}
|
||||
} else if (line.trim() !== "") {
|
||||
console.log(
|
||||
Logger.log(
|
||||
"[DIFY DEBUG] Non-data line (not starting with 'data:'), trying to parse as direct JSON:",
|
||||
JSON.stringify(line),
|
||||
)
|
||||
// Try to parse as direct JSON (fallback for non-SSE responses, though Dify uses SSE)
|
||||
try {
|
||||
const parsed = JSON.parse(line.trim())
|
||||
console.log("[DIFY DEBUG] Parsed direct JSON:", parsed)
|
||||
Logger.log("[DIFY DEBUG] Parsed direct JSON:", parsed)
|
||||
processedEvents.push(parsed.event || "direct-json")
|
||||
|
||||
// Handle the same event types as above
|
||||
@@ -330,7 +331,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
return
|
||||
} else if (parsed.event === "error") {
|
||||
console.error("[DIFY DEBUG] Direct JSON Error event:", parsed)
|
||||
Logger.error("[DIFY DEBUG] Direct JSON Error event:", parsed)
|
||||
throw new Error(`Dify API error: ${parsed.message || "Unknown error"}`)
|
||||
} else if (parsed.answer || parsed.text || parsed.content) {
|
||||
// Fallback for any content in direct JSON
|
||||
@@ -344,7 +345,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
} catch (e) {
|
||||
// Not JSON, continue
|
||||
console.log("[DIFY DEBUG] Line is not direct JSON, continuing")
|
||||
Logger.log("[DIFY DEBUG] Line is not direct JSON, continuing")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -359,11 +360,11 @@ export class DifyHandler implements ApiHandler {
|
||||
streamDuration: Date.now() - lastEventTime,
|
||||
conversationId: this.conversationId,
|
||||
}
|
||||
console.error("[DIFY DEBUG] No content was yielded! Diagnostic info:", diagnosticInfo)
|
||||
Logger.error("[DIFY DEBUG] No content was yielded! Diagnostic info:", diagnosticInfo)
|
||||
|
||||
// If we have any accumulated text at all, yield it as a fallback
|
||||
if (fullText.trim()) {
|
||||
console.log("[DIFY DEBUG] Yielding accumulated text as fallback:", fullText)
|
||||
Logger.log("[DIFY DEBUG] Yielding accumulated text as fallback:", fullText)
|
||||
yield {
|
||||
type: "text",
|
||||
text: fullText,
|
||||
@@ -380,7 +381,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock()
|
||||
console.log("[DIFY DEBUG] Stream reader released")
|
||||
Logger.log("[DIFY DEBUG] Stream reader released")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,7 +400,7 @@ export class DifyHandler implements ApiHandler {
|
||||
|
||||
// Only prepend the system prompt if it's the very first message of a new conversation.
|
||||
if (!this.conversationId && systemPrompt) {
|
||||
console.log("[DIFY DEBUG] Prepending system prompt for new conversation.")
|
||||
Logger.log("[DIFY DEBUG] Prepending system prompt for new conversation.")
|
||||
return `${systemPrompt}\n\n---\n\n${userQuery}`
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { GeminiModelId, geminiDefaultModelId, geminiModels, ModelInfo } from "@shared/api"
|
||||
import { telemetryService } from "@/services/telemetry"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { RetriableError, withRetry } from "../retry"
|
||||
import { convertAnthropicMessageToGemini } from "../transform/gemini-format"
|
||||
@@ -332,7 +333,7 @@ export class GeminiHandler implements ApiHandler {
|
||||
throughputTokensPerSec: throughputTokensPerSecSdk,
|
||||
})
|
||||
} else {
|
||||
console.warn("GeminiHandler: ulid not available for telemetry in createMessage.")
|
||||
Logger.warn("GeminiHandler: ulid not available for telemetry in createMessage.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,7 +409,7 @@ export class GeminiHandler implements ApiHandler {
|
||||
trace.cacheRead = { price: cacheReadsPrice, tokens: cacheReadTokens ?? 0, cost: cacheReadCost }
|
||||
}
|
||||
|
||||
// console.log(`[GeminiHandler] calculateCost -> ${totalCost}`, trace)
|
||||
// Logger.log(`[GeminiHandler] calculateCost -> ${totalCost}`, trace)
|
||||
return totalCost
|
||||
}
|
||||
|
||||
@@ -450,13 +451,13 @@ export class GeminiHandler implements ApiHandler {
|
||||
})
|
||||
|
||||
if (response.totalTokens === undefined) {
|
||||
console.warn("Gemini token counting returned undefined, using fallback")
|
||||
Logger.warn("Gemini token counting returned undefined, using fallback")
|
||||
return this.estimateTokens(content)
|
||||
}
|
||||
|
||||
return response.totalTokens
|
||||
} catch (error) {
|
||||
console.warn("Gemini token counting failed, using fallback", error)
|
||||
Logger.warn("Gemini token counting failed, using fallback", error)
|
||||
return this.estimateTokens(content)
|
||||
}
|
||||
}
|
||||
@@ -475,7 +476,7 @@ export class GeminiHandler implements ApiHandler {
|
||||
const jsonStr = JSON.stringify(block)
|
||||
return total + jsonStr.length
|
||||
} catch (e) {
|
||||
console.warn("Failed to stringify block for token estimation", e)
|
||||
Logger.warn("Failed to stringify block for token estimation", e)
|
||||
return total
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import OpenAI from "openai"
|
||||
import { StateManager } from "@/core/storage/StateManager"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { isAnthropicModelId } from "@/utils/model-utils"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
@@ -70,7 +71,7 @@ export async function fetchLiteLlmModelsInfo(baseUrl: string, apiKey: string): P
|
||||
const data: LiteLlmModelInfoResponse = await response.json()
|
||||
return data
|
||||
} else {
|
||||
console.error("Failed to fetch LiteLLM model info:", response.statusText)
|
||||
Logger.error("Failed to fetch LiteLLM model info:", response.statusText)
|
||||
// Try with Authorization header instead
|
||||
const retryResponse = await fetch(url, {
|
||||
method: "GET",
|
||||
@@ -84,12 +85,12 @@ export async function fetchLiteLlmModelsInfo(baseUrl: string, apiKey: string): P
|
||||
const data: LiteLlmModelInfoResponse = await retryResponse.json()
|
||||
return data
|
||||
} else {
|
||||
console.error("Failed to fetch LiteLLM model info with Authorization header:", retryResponse.statusText)
|
||||
Logger.error("Failed to fetch LiteLLM model info with Authorization header:", retryResponse.statusText)
|
||||
throw new Error(`Failed to fetch LiteLLM model info: ${retryResponse.statusText}`)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching LiteLLM model info:", error)
|
||||
Logger.error("Error fetching LiteLLM model info:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -169,7 +170,7 @@ export class LiteLlmHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Error getting LiteLLM model cost info:", error)
|
||||
Logger.warn("Error getting LiteLLM model cost info:", error)
|
||||
}
|
||||
|
||||
// Fallback to zero costs if we can't get the information
|
||||
@@ -200,7 +201,7 @@ export class LiteLlmHandler implements ApiHandler {
|
||||
|
||||
return totalCost
|
||||
} catch (error) {
|
||||
console.error("Error calculating spend:", error)
|
||||
Logger.error("Error calculating spend:", error)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ import {
|
||||
OCI_HEADER_OPC_REQUEST_ID,
|
||||
} from "@/services/auth/oca/utils/constants"
|
||||
import { createOcaHeaders } from "@/services/auth/oca/utils/utils"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { OcaModelInfo } from "@/shared/api"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiFormat } from "@/shared/proto/index.cline"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { ApiHandler, type CommonApiHandlerOptions } from ".."
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -134,11 +134,11 @@ export class OcaHandler implements ApiHandler {
|
||||
const data: { cost: number } = await response.json()
|
||||
return data.cost
|
||||
} else {
|
||||
console.error("Error calculating spend:", response.statusText)
|
||||
Logger.error("Error calculating spend:", response.statusText)
|
||||
return undefined
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error calculating spend:", error)
|
||||
Logger.error("Error calculating spend:", error)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { type ModelInfo, openAiModelInfoSaneDefaults } from "@shared/api"
|
||||
import { type Config, type Message, Ollama } from "ollama"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import type { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOllamaMessages } from "../transform/ollama-format"
|
||||
@@ -93,7 +94,7 @@ export class OllamaHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} catch (streamError: any) {
|
||||
console.error("Error processing Ollama stream:", streamError)
|
||||
Logger.error("Error processing Ollama stream:", streamError)
|
||||
throw new Error(`Ollama stream processing error: ${streamError.message || "Unknown error"}`)
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -107,7 +108,7 @@ export class OllamaHandler implements ApiHandler {
|
||||
const statusCode = error.status || error.statusCode
|
||||
const errorMessage = error.message || "Unknown error"
|
||||
|
||||
console.error(`Ollama API error (${statusCode || "unknown"}): ${errorMessage}`)
|
||||
Logger.error(`Ollama API error (${statusCode || "unknown"}): ${errorMessage}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import * as os from "os"
|
||||
import { v7 as uuidv7 } from "uuid"
|
||||
import { ModelInfo, OpenAiCodexModelId, openAiCodexDefaultModelId, openAiCodexModels } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionTool } from "openai/resources/chat/completions"
|
||||
import {
|
||||
ModelInfo,
|
||||
OpenAiCodexModelId,
|
||||
openAiCodexDefaultModelId,
|
||||
openAiCodexModels,
|
||||
} from "@shared/api"
|
||||
import * as os from "os"
|
||||
import { v7 as uuidv7 } from "uuid"
|
||||
import { openAiCodexOAuthManager } from "@/integrations/openai-codex/oauth"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { openAiCodexOAuthManager } from "@/integrations/openai-codex/oauth"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { convertToOpenAIResponsesInput } from "../transform/openai-response-format"
|
||||
import { ApiStream, ApiStreamUsageChunk } from "../transform/stream"
|
||||
@@ -53,7 +48,9 @@ export class OpenAiCodexHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
private normalizeUsage(usage: any, model: { id: string; info: ModelInfo }): ApiStreamUsageChunk | undefined {
|
||||
if (!usage) return undefined
|
||||
if (!usage) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const inputDetails = usage.input_tokens_details ?? usage.prompt_tokens_details
|
||||
|
||||
@@ -90,11 +87,7 @@ export class OpenAiCodexHandler implements ApiHandler {
|
||||
return out
|
||||
}
|
||||
|
||||
async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: ClineStorageMessage[],
|
||||
tools?: ChatCompletionTool[],
|
||||
): ApiStream {
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: ChatCompletionTool[]): ApiStream {
|
||||
const model = this.getModel()
|
||||
|
||||
// Reset state for this request
|
||||
@@ -104,9 +97,7 @@ export class OpenAiCodexHandler implements ApiHandler {
|
||||
// Get access token from OAuth manager
|
||||
let accessToken = await openAiCodexOAuthManager.getAccessToken()
|
||||
if (!accessToken) {
|
||||
throw new Error(
|
||||
"Not authenticated with OpenAI Codex. Please sign in using the OpenAI Codex OAuth flow in settings.",
|
||||
)
|
||||
throw new Error("Not authenticated with OpenAI Codex. Please sign in using the OpenAI Codex OAuth flow in settings.")
|
||||
}
|
||||
|
||||
// Format conversation for Responses API
|
||||
@@ -179,11 +170,7 @@ export class OpenAiCodexHandler implements ApiHandler {
|
||||
return body
|
||||
}
|
||||
|
||||
private async *executeRequest(
|
||||
requestBody: any,
|
||||
model: { id: string; info: ModelInfo },
|
||||
accessToken: string,
|
||||
): ApiStream {
|
||||
private async *executeRequest(requestBody: any, model: { id: string; info: ModelInfo }, accessToken: string): ApiStream {
|
||||
// Create AbortController for cancellation
|
||||
this.abortController = new AbortController()
|
||||
|
||||
@@ -237,11 +224,7 @@ export class OpenAiCodexHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private async *makeCodexRequest(
|
||||
requestBody: any,
|
||||
model: { id: string; info: ModelInfo },
|
||||
accessToken: string,
|
||||
): ApiStream {
|
||||
private async *makeCodexRequest(requestBody: any, model: { id: string; info: ModelInfo }, accessToken: string): ApiStream {
|
||||
const url = `${CODEX_API_BASE_URL}/responses`
|
||||
|
||||
// Get ChatGPT account ID for organization subscriptions
|
||||
@@ -302,10 +285,7 @@ export class OpenAiCodexHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private async *handleStreamResponse(
|
||||
body: ReadableStream<Uint8Array>,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
private async *handleStreamResponse(body: ReadableStream<Uint8Array>, model: { id: string; info: ModelInfo }): ApiStream {
|
||||
const reader = body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
let buffer = ""
|
||||
@@ -317,7 +297,9 @@ export class OpenAiCodexHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
if (done) {
|
||||
break
|
||||
}
|
||||
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
const lines = buffer.split("\n")
|
||||
@@ -380,10 +362,7 @@ export class OpenAiCodexHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
// Handle tool/function call deltas
|
||||
if (
|
||||
event?.type === "response.tool_call_arguments.delta" ||
|
||||
event?.type === "response.function_call_arguments.delta"
|
||||
) {
|
||||
if (event?.type === "response.tool_call_arguments.delta" || event?.type === "response.function_call_arguments.delta") {
|
||||
const callId = event.call_id || event.tool_call_id || event.id || this.pendingToolCallId
|
||||
const name = event.name || event.function_name || this.pendingToolCallName
|
||||
const args = event.delta || event.arguments
|
||||
@@ -484,10 +463,7 @@ export class OpenAiCodexHandler implements ApiHandler {
|
||||
getModel(): { id: OpenAiCodexModelId; info: ModelInfo } {
|
||||
const modelId = this.options.apiModelId
|
||||
|
||||
const id =
|
||||
modelId && modelId in openAiCodexModels
|
||||
? (modelId as OpenAiCodexModelId)
|
||||
: openAiCodexDefaultModelId
|
||||
const id = modelId && modelId in openAiCodexModels ? (modelId as OpenAiCodexModelId) : openAiCodexDefaultModelId
|
||||
|
||||
const info: ModelInfo = openAiCodexModels[id]
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import OpenAI from "openai"
|
||||
import type { ChatCompletionReasoningEffort, ChatCompletionTool } from "openai/resources/chat/completions"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { ApiFormat } from "@/shared/proto/cline/models"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { isGPT5ModelFamily } from "@/utils/model-utils"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
@@ -135,7 +135,7 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
try {
|
||||
yield* toolCallProcessor.processToolCallDeltas(delta.tool_calls)
|
||||
} catch (error) {
|
||||
console.error("Error processing tool call delta:", error, delta.tool_calls)
|
||||
Logger.error("Error processing tool call delta:", error, delta.tool_calls)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,11 +316,11 @@ export class OpenAiNativeHandler implements ApiHandler {
|
||||
chunk.response?.status === "incomplete" &&
|
||||
chunk.response?.incomplete_details?.reason === "max_output_tokens"
|
||||
) {
|
||||
console.log("Ran out of tokens")
|
||||
Logger.log("Ran out of tokens")
|
||||
if (chunk.response?.output_text?.length > 0) {
|
||||
console.log("Partial output:", chunk.response.output_text)
|
||||
Logger.log("Partial output:", chunk.response.output_text)
|
||||
} else {
|
||||
console.log("Ran out of tokens during reasoning")
|
||||
Logger.log("Ran out of tokens during reasoning")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch, getAxiosSettings } from "@/shared/net"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { createOpenRouterStream } from "../transform/openrouter-stream"
|
||||
@@ -80,7 +81,7 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
// Check for error field directly on chunk
|
||||
if ("error" in chunk) {
|
||||
const error = chunk.error as OpenRouterErrorResponse["error"]
|
||||
console.error(`OpenRouter API Error: ${error?.code} - ${error?.message}`)
|
||||
Logger.error(`OpenRouter API Error: ${error?.code} - ${error?.message}`)
|
||||
// Include metadata in the error message if available
|
||||
const metadataStr = error.metadata ? `\nMetadata: ${JSON.stringify(error.metadata, null, 2)}` : ""
|
||||
throw new Error(`OpenRouter API Error ${error.code}: ${error.message}${metadataStr}`)
|
||||
@@ -95,7 +96,7 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
const choiceWithError = choice as any
|
||||
if (choiceWithError.error) {
|
||||
const error = choiceWithError.error
|
||||
console.error(
|
||||
Logger.error(
|
||||
`OpenRouter Mid-Stream Error: ${error?.code || "Unknown"} - ${error?.message || "Unknown error"}`,
|
||||
)
|
||||
// Format error details
|
||||
@@ -179,7 +180,7 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
try {
|
||||
const generationIterator = this.fetchGenerationDetails(this.lastGenerationId)
|
||||
const generation = (await generationIterator.next()).value
|
||||
// console.log("OpenRouter generation details:", generation)
|
||||
// Logger.log("OpenRouter generation details:", generation)
|
||||
return {
|
||||
type: "usage",
|
||||
cacheWriteTokens: 0,
|
||||
@@ -191,7 +192,7 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
}
|
||||
} catch (error) {
|
||||
// ignore if fails
|
||||
console.error("Error fetching OpenRouter generation details:", error)
|
||||
Logger.error("Error fetching OpenRouter generation details:", error)
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
@@ -199,7 +200,7 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
|
||||
@withRetry({ maxRetries: 4, baseDelay: 250, maxDelay: 1000, retryAllErrors: true })
|
||||
async *fetchGenerationDetails(genId: string) {
|
||||
// console.log("Fetching generation details for:", genId)
|
||||
// Logger.log("Fetching generation details for:", genId)
|
||||
try {
|
||||
const response = await axios.get(`https://openrouter.ai/api/v1/generation?id=${genId}`, {
|
||||
headers: {
|
||||
@@ -211,7 +212,7 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
yield response.data?.data
|
||||
} catch (error) {
|
||||
// ignore if fails
|
||||
console.error("Error fetching OpenRouter generation details:", error)
|
||||
Logger.error("Error fetching OpenRouter generation details:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as os from "os"
|
||||
import * as path from "path"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -76,7 +77,7 @@ export class QwenCodeHandler implements ApiHandler {
|
||||
const credsStr = await fs.readFile(keyFile, "utf-8")
|
||||
return JSON.parse(credsStr)
|
||||
} catch (error) {
|
||||
console.error(
|
||||
Logger.error(
|
||||
`Error reading or parsing credentials file at ${getQwenCachedCredentialPath(this.options.qwenCodeOauthPath)}`,
|
||||
)
|
||||
throw new Error(`Failed to load Qwen OAuth credentials: ${error}`)
|
||||
|
||||
@@ -12,6 +12,7 @@ import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -134,7 +135,7 @@ export class QwenHandler implements ApiHandler {
|
||||
try {
|
||||
yield* toolCallProcessor.processToolCallDeltas(delta.tool_calls)
|
||||
} catch (error) {
|
||||
console.error("Error processing tool call delta:", error, delta.tool_calls)
|
||||
Logger.error("Error processing tool call delta:", error, delta.tool_calls)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import JSON5 from "json5"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { getAxiosSettings } from "@/shared/net"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
@@ -144,7 +145,7 @@ namespace Bedrock {
|
||||
}
|
||||
|
||||
// Log unsupported content types for debugging
|
||||
console.warn(`Unsupported content type: ${(item as ContentItem).type}`)
|
||||
Logger.warn(`Unsupported content type: ${(item as ContentItem).type}`)
|
||||
return null
|
||||
})
|
||||
.filter((item): item is BedrockContentBlock => item !== null)
|
||||
@@ -213,7 +214,7 @@ namespace Bedrock {
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to process image content:", error)
|
||||
Logger.error("Failed to process image content:", error)
|
||||
// Return a text content indicating the error instead of null
|
||||
// This ensures users are aware of the issue
|
||||
return {
|
||||
@@ -412,7 +413,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
url: destination.url || this.options.sapAiCoreBaseUrl!,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to create AI Core destination:", error)
|
||||
Logger.error("Failed to create AI Core destination:", error)
|
||||
throw new Error(`Unable to create AI Core destination: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
}
|
||||
@@ -473,7 +474,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
})
|
||||
.filter((deployment: any) => deployment !== null)
|
||||
} catch (error) {
|
||||
console.error("Error fetching deployments:", error)
|
||||
Logger.error("Error fetching deployments:", error)
|
||||
throw new Error("Failed to fetch deployments")
|
||||
}
|
||||
}
|
||||
@@ -577,7 +578,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error in SAP orchestration mode:", error)
|
||||
Logger.error("Error in SAP orchestration mode:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -596,7 +597,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
|
||||
if (!deploymentId) {
|
||||
// Fallback to runtime deployment id fetching for users who haven't opened the SAP provider UI
|
||||
console.log(`No pre-configured deployment ID found for model ${model.id}, falling back to runtime fetching`)
|
||||
Logger.log(`No pre-configured deployment ID found for model ${model.id}, falling back to runtime fetching`)
|
||||
deploymentId = await this.getDeploymentForModel(model.id)
|
||||
}
|
||||
|
||||
@@ -800,8 +801,8 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
if (error.response) {
|
||||
// The request was made and the server responded with a status code
|
||||
// that falls out of the range of 2xx
|
||||
console.error("Error status:", error.response.status)
|
||||
console.error("Error headers:", error.response.headers)
|
||||
Logger.error("Error status:", error.response.status)
|
||||
Logger.error("Error headers:", error.response.headers)
|
||||
|
||||
// Handle error data - need to read stream if responseType was 'stream'
|
||||
let errorMessage = "Unknown error"
|
||||
@@ -830,10 +831,10 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
} else if (typeof error.response.data === "object") {
|
||||
errorMessage = JSON.stringify(error.response.data, null, 2)
|
||||
}
|
||||
console.error("Error data:", errorMessage)
|
||||
Logger.error("Error data:", errorMessage)
|
||||
} catch (e) {
|
||||
console.error("Failed to read error data:", e)
|
||||
console.error("Raw error data:", error.response.data)
|
||||
Logger.error("Failed to read error data:", e)
|
||||
Logger.error("Raw error data:", error.response.data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -846,11 +847,11 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
throw new Error(`HTTP ${error.response.status}: ${errorMessage}`)
|
||||
} else if (error.request) {
|
||||
// The request was made but no response was received
|
||||
console.error("Error request:", error.request)
|
||||
Logger.error("Error request:", error.request)
|
||||
throw new Error("No response received from server")
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.error("Error message:", error.message)
|
||||
Logger.error("Error message:", error.message)
|
||||
throw new Error(`Error setting up request: ${error.message}`)
|
||||
}
|
||||
}
|
||||
@@ -898,13 +899,13 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to parse JSON data:", error)
|
||||
Logger.error("Failed to parse JSON data:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error streaming completion:", error)
|
||||
Logger.error("Error streaming completion:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -965,7 +966,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to parse JSON data:", error)
|
||||
Logger.error("Failed to parse JSON data:", error)
|
||||
yield {
|
||||
type: "text",
|
||||
text: `[ERROR] Failed to parse response data: ${error instanceof Error ? error.message : String(error)}`,
|
||||
@@ -975,7 +976,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error streaming completion:", error)
|
||||
Logger.error("Error streaming completion:", error)
|
||||
yield {
|
||||
type: "text",
|
||||
text: `[ERROR] Failed to process stream: ${error instanceof Error ? error.message : String(error)}`,
|
||||
@@ -1044,13 +1045,13 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to parse GPT JSON data:", error)
|
||||
Logger.error("Failed to parse GPT JSON data:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error streaming GPT completion:", error)
|
||||
Logger.error("Error streaming GPT completion:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -1109,13 +1110,13 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to parse Gemini JSON data:", error)
|
||||
Logger.error("Failed to parse Gemini JSON data:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error streaming Gemini completion:", error)
|
||||
Logger.error("Error streaming Gemini completion:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import OpenAI from "openai"
|
||||
import type { ChatCompletionTool as OpenAITool } from "openai/resources/chat/completions"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
@@ -125,11 +126,11 @@ export class VercelAIGatewayHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
if (!didOutputUsage) {
|
||||
console.warn("Vercel AI Gateway did not provide usage information in stream")
|
||||
Logger.warn("Vercel AI Gateway did not provide usage information in stream")
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Vercel AI Gateway error details:", error)
|
||||
console.error("Error stack:", error.stack)
|
||||
Logger.error("Vercel AI Gateway error details:", error)
|
||||
Logger.error("Error stack:", error.stack)
|
||||
throw new Error(`Vercel AI Gateway error: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { SELECTOR_SEPARATOR, stringifyVsCodeLmModelSelector } from "@shared/vsCo
|
||||
import { calculateApiCostAnthropic } from "@utils/cost"
|
||||
import * as vscode from "vscode"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { ApiHandler, CommonApiHandlerOptions, SingleCompletionHandler } from "../"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
@@ -123,7 +124,7 @@ declare module "vscode" {
|
||||
* const systemPrompt = "You are a helpful assistant";
|
||||
* const messages = [{ role: "user", content: "Hello!" }];
|
||||
* for await (const chunk of handler.createMessage(systemPrompt, messages)) {
|
||||
* console.log(chunk);
|
||||
* Logger.log(chunk);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@@ -147,7 +148,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
this.client = null
|
||||
this.ensureCleanState()
|
||||
} catch (error) {
|
||||
console.error("Error during configuration change cleanup:", error)
|
||||
Logger.error("Error during configuration change cleanup:", error)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -279,7 +280,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
|
||||
private async getClient(): Promise<vscode.LanguageModelChat> {
|
||||
if (!this.client) {
|
||||
console.debug("Cline <Language Model API>: Getting client with options:", {
|
||||
Logger.debug("Cline <Language Model API>: Getting client with options:", {
|
||||
vsCodeLmModelSelector: this.options.vsCodeLmModelSelector,
|
||||
hasOptions: !!this.options,
|
||||
selectorKeys: this.options.vsCodeLmModelSelector ? Object.keys(this.options.vsCodeLmModelSelector) : [],
|
||||
@@ -288,11 +289,11 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
try {
|
||||
// Use default empty selector if none provided to get all available models
|
||||
const selector = this.options?.vsCodeLmModelSelector || {}
|
||||
console.debug("Cline <Language Model API>: Creating client with selector:", selector)
|
||||
Logger.debug("Cline <Language Model API>: Creating client with selector:", selector)
|
||||
this.client = await this.createClient(selector)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Unknown error"
|
||||
console.error("Cline <Language Model API>: Client creation failed:", message)
|
||||
Logger.error("Cline <Language Model API>: Client creation failed:", message)
|
||||
throw new Error(`Cline <Language Model API>: Failed to create client: ${message}`)
|
||||
}
|
||||
}
|
||||
@@ -413,7 +414,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
if (chunk instanceof vscode.LanguageModelTextPart) {
|
||||
// Validate text part value
|
||||
if (typeof chunk.value !== "string") {
|
||||
console.warn("Cline <Language Model API>: Invalid text part value received:", chunk.value)
|
||||
Logger.warn("Cline <Language Model API>: Invalid text part value received:", chunk.value)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -426,18 +427,18 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
try {
|
||||
// Validate tool call parameters
|
||||
if (!chunk.name || typeof chunk.name !== "string") {
|
||||
console.warn("Cline <Language Model API>: Invalid tool name received:", chunk.name)
|
||||
Logger.warn("Cline <Language Model API>: Invalid tool name received:", chunk.name)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!chunk.callId || typeof chunk.callId !== "string") {
|
||||
console.warn("Cline <Language Model API>: Invalid tool callId received:", chunk.callId)
|
||||
Logger.warn("Cline <Language Model API>: Invalid tool callId received:", chunk.callId)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure input is a valid object
|
||||
if (!chunk.input || typeof chunk.input !== "object") {
|
||||
console.warn("Cline <Language Model API>: Invalid tool input received:", chunk.input)
|
||||
Logger.warn("Cline <Language Model API>: Invalid tool input received:", chunk.input)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -453,7 +454,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
accumulatedText += toolCallText
|
||||
|
||||
// Log tool call for debugging
|
||||
console.debug("Cline <Language Model API>: Processing tool call:", {
|
||||
Logger.debug("Cline <Language Model API>: Processing tool call:", {
|
||||
name: chunk.name,
|
||||
callId: chunk.callId,
|
||||
inputSize: JSON.stringify(chunk.input).length,
|
||||
@@ -464,10 +465,10 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
text: toolCallText,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Cline <Language Model API>: Failed to process tool call:", error)
|
||||
Logger.error("Cline <Language Model API>: Failed to process tool call:", error)
|
||||
}
|
||||
} else {
|
||||
console.warn("Cline <Language Model API>: Unknown chunk type received:", chunk)
|
||||
Logger.warn("Cline <Language Model API>: Unknown chunk type received:", chunk)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,7 +490,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
console.error("Cline <Language Model API>: Stream error details:", {
|
||||
Logger.error("Cline <Language Model API>: Stream error details:", {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
name: error.name,
|
||||
@@ -500,12 +501,12 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
} else if (typeof error === "object" && error !== null) {
|
||||
// Handle error-like objects
|
||||
const errorDetails = JSON.stringify(error, null, 2)
|
||||
console.error("Cline <Language Model API>: Stream error object:", errorDetails)
|
||||
Logger.error("Cline <Language Model API>: Stream error object:", errorDetails)
|
||||
throw new Error(`Cline <Language Model API>: Response stream error: ${errorDetails}`)
|
||||
} else {
|
||||
// Fallback for unknown error types
|
||||
const errorMessage = String(error)
|
||||
console.error("Cline <Language Model API>: Unknown stream error:", errorMessage)
|
||||
Logger.error("Cline <Language Model API>: Unknown stream error:", errorMessage)
|
||||
throw new Error(`Cline <Language Model API>: Response stream error: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
@@ -526,7 +527,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
// Log any missing properties for debugging
|
||||
for (const [prop, value] of Object.entries(requiredProps)) {
|
||||
if (!value && value !== 0) {
|
||||
console.warn(`Cline <Language Model API>: Client missing ${prop} property`)
|
||||
Logger.warn(`Cline <Language Model API>: Client missing ${prop} property`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,7 +558,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
? stringifyVsCodeLmModelSelector(this.options.vsCodeLmModelSelector)
|
||||
: "vscode-lm"
|
||||
|
||||
console.debug("Cline <Language Model API>: No client available, using fallback model info")
|
||||
Logger.debug("Cline <Language Model API>: No client available, using fallback model info")
|
||||
|
||||
return {
|
||||
id: fallbackId,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
|
||||
interface RetryOptions {
|
||||
maxRetries?: number
|
||||
baseDelay?: number
|
||||
@@ -72,7 +74,7 @@ export function withRetry(options: RetryOptions = {}) {
|
||||
try {
|
||||
await handlerInstance.options.onRetryAttempt(attempt + 1, maxRetries, delay, error)
|
||||
} catch (e) {
|
||||
console.error("Error in onRetryAttempt callback:", e)
|
||||
Logger.error("Error in onRetryAttempt callback:", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
|
||||
const o1SystemPrompt = (systemPrompt: string) => `
|
||||
# System Prompt
|
||||
@@ -317,7 +318,7 @@ function parseToolCall(toolName: string, content: string): ToolCall | null {
|
||||
|
||||
// Validate required parameters
|
||||
if (!validateToolInput(toolName, tool_input)) {
|
||||
console.error(`Invalid tool call for ${toolName}:`, content)
|
||||
Logger.error(`Invalid tool call for ${toolName}:`, content)
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -358,8 +359,8 @@ function validateToolInput(toolName: string, tool_input: Record<string, string>)
|
||||
// </write_to_file>`;
|
||||
//
|
||||
// const { normalText, toolCalls } = parseAIResponse(aiResponse);
|
||||
// console.log(normalText);
|
||||
// console.log(toolCalls);
|
||||
// Logger.log(normalText);
|
||||
// Logger.log(toolCalls);
|
||||
|
||||
// Convert OpenAI response to Anthropic format
|
||||
export function convertO1ResponseToAnthropicMessage(
|
||||
@@ -432,4 +433,4 @@ export function convertO1ResponseToAnthropicMessage(
|
||||
// usage: { prompt_tokens: 50, completion_tokens: 100 }
|
||||
// };
|
||||
// const anthropicMessage = convertO1ResponseToAnthropicMessage(openAICompletion);
|
||||
// console.log(anthropicMessage);
|
||||
// Logger.log(anthropicMessage);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ClineTextContentBlock,
|
||||
ClineUserToolResultContentBlock,
|
||||
} from "@/shared/messages/content"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
|
||||
// OpenAI API has a maximum tool call ID length of 40 characters
|
||||
const MAX_TOOL_CALL_ID_LENGTH = 40
|
||||
@@ -424,7 +425,7 @@ export function convertToAnthropicMessage(completion: OpenAI.Chat.Completions.Ch
|
||||
try {
|
||||
parsedInput = JSON.parse(toolCall.function?.arguments || "{}")
|
||||
} catch (error) {
|
||||
console.error("Failed to parse tool arguments:", error)
|
||||
Logger.error("Failed to parse tool arguments:", error)
|
||||
}
|
||||
return {
|
||||
type: "tool_use",
|
||||
@@ -439,7 +440,7 @@ export function convertToAnthropicMessage(completion: OpenAI.Chat.Completions.Ch
|
||||
return anthropicMessage
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error converting OpenAI message to Anthropic format:", error)
|
||||
Logger.error("Error converting OpenAI message to Anthropic format:", error)
|
||||
}
|
||||
|
||||
return anthropicMessage
|
||||
|
||||
@@ -3,7 +3,7 @@ import type {
|
||||
ChatCompletionToolChoiceOption,
|
||||
ChatCompletionTool as OpenAITool,
|
||||
} from "openai/resources/chat/completions"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import type { ApiStreamToolCallsChunk } from "./stream"
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import * as vscode from "vscode"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
|
||||
/**
|
||||
* Safely converts a value into a plain object.
|
||||
@@ -23,7 +24,7 @@ export function asObjectSafe(value: any): object {
|
||||
|
||||
return {}
|
||||
} catch (error) {
|
||||
console.warn("Cline <Language Model API>: Failed to parse object:", error)
|
||||
Logger.warn("Cline <Language Model API>: Failed to parse object:", error)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import OpenAI from "openai"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { ModelInfo } from "@/shared/api"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
|
||||
// Type that represents the OpenAI ResponseStream with its private properties
|
||||
// The #private property issue can be resolved by using the AsyncIterable interface
|
||||
@@ -143,11 +143,11 @@ export async function* handleResponsesApiStreamResponse(
|
||||
chunk.response?.status === "incomplete" &&
|
||||
chunk.response?.incomplete_details?.reason === "max_output_tokens"
|
||||
) {
|
||||
console.log("Ran out of tokens")
|
||||
Logger.log("Ran out of tokens")
|
||||
if (chunk.response?.output_text?.length > 0) {
|
||||
console.log("Partial output:", chunk.response.output_text)
|
||||
Logger.log("Partial output:", chunk.response.output_text)
|
||||
} else {
|
||||
console.log("Ran out of tokens during reasoning")
|
||||
Logger.log("Ran out of tokens during reasoning")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ import { describe, it } from "mocha"
|
||||
import { constructNewFileContent as cnfc } from "./diff"
|
||||
|
||||
async function cnfc2(diffContent: string, originalContent: string, isFinal: boolean): Promise<string> {
|
||||
return cnfc(diffContent, originalContent, isFinal, "v2")
|
||||
const result = await cnfc(diffContent, originalContent, isFinal, "v2")
|
||||
return result.newContent
|
||||
}
|
||||
|
||||
describe("constructNewFileContent", () => {
|
||||
@@ -217,13 +218,13 @@ replaced
|
||||
} else {
|
||||
const result1 = await cnfc(diff, original, isFinal ?? true)
|
||||
const result2 = await cnfc2(diff, original, isFinal ?? true)
|
||||
const _equal = result1 === result2
|
||||
const _equal2 = result1 === expected
|
||||
const _equal = result1.newContent === result2
|
||||
const _equal2 = result1.newContent === expected
|
||||
// Verify both implementations produce same result
|
||||
expect(result1).to.equal(result2)
|
||||
expect(result1.newContent).to.equal(result2)
|
||||
|
||||
// Verify result matches expected
|
||||
expect(result1).to.equal(expected)
|
||||
expect(result1.newContent).to.equal(expected)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -264,7 +265,7 @@ replaced`
|
||||
// Should still work and replace line2 with "replaced"
|
||||
const expected = "line1\nreplaced\nline3"
|
||||
|
||||
expect(result1).to.equal(expected)
|
||||
expect(result1.newContent).to.equal(expected)
|
||||
})
|
||||
|
||||
it("should handle missing final REPLACE marker with multiple lines of replacement", async () => {
|
||||
@@ -281,7 +282,7 @@ replaced`
|
||||
const result1 = await cnfc(diff, original, true) // isFinal = true
|
||||
const expected = "function test() {\n\tconst a = 42;\n\tconsole.log('updated');\n\treturn a;\n}"
|
||||
|
||||
expect(result1).to.equal(expected)
|
||||
expect(result1.newContent).to.equal(expected)
|
||||
})
|
||||
|
||||
// it("should NOT process incomplete replacement when isFinal is false", async () => {
|
||||
@@ -319,7 +320,7 @@ new second
|
||||
+++++++ REPLACE`
|
||||
const result1 = await cnfc(diff, original, isFinal)
|
||||
const expectedResult = "first\nnew second\nthird\nnew fourth\n"
|
||||
expect(result1).to.equal(expectedResult)
|
||||
expect(result1.newContent).to.equal(expectedResult)
|
||||
})
|
||||
|
||||
it("should handle multiple out-of-order replacements", async () => {
|
||||
@@ -342,7 +343,7 @@ fifth
|
||||
+++++++ REPLACE`
|
||||
const result1 = await cnfc(diff, original, isFinal)
|
||||
const expectedResult = "one\nsecond\nthree\nfourth\nfifth\n"
|
||||
expect(result1).to.equal(expectedResult)
|
||||
expect(result1.newContent).to.equal(expectedResult)
|
||||
})
|
||||
|
||||
it("should handle out-of-order replacements with indentation", async () => {
|
||||
@@ -360,7 +361,7 @@ fifth
|
||||
+++++++ REPLACE`
|
||||
const result1 = await cnfc(diff, original, isFinal)
|
||||
const expectedResult = "function test() {\n\tconst a = 10;\n\tconst b = 2;\n\tconst c = 30;\n\n}"
|
||||
expect(result1).to.equal(expectedResult)
|
||||
expect(result1.newContent).to.equal(expectedResult)
|
||||
})
|
||||
|
||||
it("should handle out-of-order replacements with empty lines", async () => {
|
||||
@@ -380,6 +381,6 @@ new body content
|
||||
+++++++ REPLACE`
|
||||
const result1 = await cnfc(diff, original, isFinal)
|
||||
const expectedResult = "header\nnew body content\nnew footer\n"
|
||||
expect(result1).to.equal(expectedResult)
|
||||
expect(result1.newContent).to.equal(expectedResult)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,6 +2,17 @@ const SEARCH_BLOCK_START = "------- SEARCH"
|
||||
const SEARCH_BLOCK_END = "======="
|
||||
const REPLACE_BLOCK_END = "+++++++ REPLACE"
|
||||
|
||||
/**
|
||||
* Converts a character index in a string to a 1-based line number.
|
||||
* @param content - The full content string
|
||||
* @param charIndex - The character index in the content
|
||||
* @returns The 1-based line number where charIndex falls
|
||||
*/
|
||||
export function getLineNumberFromCharIndex(content: string, charIndex: number): number {
|
||||
if (charIndex <= 0) return 1
|
||||
return content.substring(0, charIndex).split("\n").length
|
||||
}
|
||||
|
||||
const SEARCH_BLOCK_CHAR = "-"
|
||||
const REPLACE_BLOCK_CHAR = "+"
|
||||
const LEGACY_SEARCH_BLOCK_CHAR = "<"
|
||||
@@ -236,7 +247,7 @@ export async function constructNewFileContent(
|
||||
originalContent: string,
|
||||
isFinal: boolean,
|
||||
version: "v1" | "v2" = "v1",
|
||||
): Promise<string> {
|
||||
): Promise<{ newContent: string; matchIndices: number[] }> {
|
||||
const constructor = constructNewFileContentVersionMapping[version]
|
||||
if (!constructor) {
|
||||
throw new Error(`Invalid version '${version}' for file content constructor`)
|
||||
@@ -246,13 +257,17 @@ export async function constructNewFileContent(
|
||||
|
||||
const constructNewFileContentVersionMapping: Record<
|
||||
string,
|
||||
(diffContent: string, originalContent: string, isFinal: boolean) => Promise<string>
|
||||
(diffContent: string, originalContent: string, isFinal: boolean) => Promise<{ newContent: string; matchIndices: number[] }>
|
||||
> = {
|
||||
v1: constructNewFileContentV1,
|
||||
v2: constructNewFileContentV2,
|
||||
} as const
|
||||
|
||||
async function constructNewFileContentV1(diffContent: string, originalContent: string, isFinal: boolean): Promise<string> {
|
||||
async function constructNewFileContentV1(
|
||||
diffContent: string,
|
||||
originalContent: string,
|
||||
isFinal: boolean,
|
||||
): Promise<{ newContent: string; matchIndices: number[] }> {
|
||||
let result = ""
|
||||
let lastProcessedIndex = 0
|
||||
|
||||
@@ -470,7 +485,9 @@ async function constructNewFileContentV1(diffContent: string, originalContent: s
|
||||
result += originalContent.slice(currentPos)
|
||||
}
|
||||
|
||||
return result
|
||||
// Return all match indices from the replacements
|
||||
// This is used to determine the line numbers for each SEARCH/REPLACE block in the UI
|
||||
return { newContent: result, matchIndices: replacements.map((r) => r.start) }
|
||||
}
|
||||
|
||||
enum ProcessingState {
|
||||
@@ -566,7 +583,7 @@ class NewFileContentConstructor {
|
||||
this.internalProcessLine(line, true, this.pendingNonStandardLines.length)
|
||||
}
|
||||
|
||||
public getResult() {
|
||||
public getResult(): { newContent: string; matchIndices: number[] } {
|
||||
// If this is the final chunk, append any remaining original content
|
||||
if (this.isFinal && this.lastProcessedIndex < this.originalContent.length) {
|
||||
this.result += this.originalContent.slice(this.lastProcessedIndex)
|
||||
@@ -574,7 +591,10 @@ class NewFileContentConstructor {
|
||||
if (this.isFinal && this.state !== ProcessingState.Idle) {
|
||||
throw new Error("File processing incomplete - SEARCH/REPLACE operations still active during finalization")
|
||||
}
|
||||
return this.result
|
||||
// Note: V2 implementation doesn't currently track match indices
|
||||
// For now, return empty array. If V2 becomes the default and we need line numbers,
|
||||
// we should add state to track all match indices.
|
||||
return { newContent: this.result, matchIndices: [] }
|
||||
}
|
||||
|
||||
private internalProcessLine(
|
||||
@@ -800,7 +820,11 @@ class NewFileContentConstructor {
|
||||
}
|
||||
}
|
||||
|
||||
export async function constructNewFileContentV2(diffContent: string, originalContent: string, isFinal: boolean): Promise<string> {
|
||||
export async function constructNewFileContentV2(
|
||||
diffContent: string,
|
||||
originalContent: string,
|
||||
isFinal: boolean,
|
||||
): Promise<{ newContent: string; matchIndices: number[] }> {
|
||||
const newFileContentConstructor = new NewFileContentConstructor(originalContent, isFinal)
|
||||
|
||||
const lines = diffContent.split("\n")
|
||||
|
||||
@@ -3,7 +3,8 @@ import { describe, it } from "mocha"
|
||||
import { constructNewFileContent as cnfc } from "./diff"
|
||||
|
||||
async function cnfc2(diffContent: string, originalContent: string, isFinal: boolean): Promise<string> {
|
||||
return cnfc(diffContent, originalContent, isFinal, "v2")
|
||||
const result = await cnfc(diffContent, originalContent, isFinal, "v2")
|
||||
return result.newContent
|
||||
}
|
||||
|
||||
describe("Diff Format Edge Cases", () => {
|
||||
@@ -18,7 +19,7 @@ new content
|
||||
const result1 = await cnfc(diff, original, isFinal)
|
||||
const result2 = await cnfc2(diff, original, isFinal)
|
||||
const expectedResult = "before\nnew content\nafter"
|
||||
expect(result1).to.equal(expectedResult)
|
||||
expect(result1.newContent).to.equal(expectedResult)
|
||||
expect(result2).to.equal(expectedResult)
|
||||
})
|
||||
|
||||
@@ -33,7 +34,7 @@ new content
|
||||
const result1 = await cnfc(diff, original, isFinal)
|
||||
const result2 = await cnfc2(diff, original, isFinal)
|
||||
const expectedResult = "before\nnew content\nafter"
|
||||
expect(result1).to.equal(expectedResult)
|
||||
expect(result1.newContent).to.equal(expectedResult)
|
||||
expect(result2).to.equal(expectedResult)
|
||||
})
|
||||
|
||||
@@ -48,7 +49,7 @@ new content
|
||||
const result1 = await cnfc(diff, original, isFinal)
|
||||
const result2 = await cnfc2(diff, original, isFinal)
|
||||
const expectedResult = "before\nnew content\nafter"
|
||||
expect(result1).to.equal(expectedResult)
|
||||
expect(result1.newContent).to.equal(expectedResult)
|
||||
expect(result2).to.equal(expectedResult)
|
||||
})
|
||||
|
||||
@@ -62,7 +63,7 @@ new content
|
||||
+++++++ REPLACE`
|
||||
const result1 = await cnfc(diff, original, isFinal)
|
||||
const result2 = await cnfc2(diff, original, isFinal)
|
||||
expect(result1).to.equal("before\nnew content\nafter")
|
||||
expect(result1.newContent).to.equal("before\nnew content\nafter")
|
||||
expect(result2).to.equal("before\nnew content\nafter")
|
||||
})
|
||||
|
||||
@@ -77,7 +78,7 @@ new content
|
||||
const result1 = await cnfc(diff, original, isFinal)
|
||||
const result2 = await cnfc2(diff, original, isFinal)
|
||||
const expectedResult = "before\nnew content\nafter"
|
||||
expect(result1).to.equal(expectedResult)
|
||||
expect(result1.newContent).to.equal(expectedResult)
|
||||
expect(result2).to.equal(expectedResult)
|
||||
})
|
||||
|
||||
@@ -92,7 +93,7 @@ new content
|
||||
const result1 = await cnfc(diff, original, isFinal)
|
||||
const result2 = await cnfc2(diff, original, isFinal)
|
||||
const expectedResult = "before\nnew content\nafter"
|
||||
expect(result1).to.equal(expectedResult)
|
||||
expect(result1.newContent).to.equal(expectedResult)
|
||||
expect(result2).to.equal(expectedResult)
|
||||
})
|
||||
|
||||
@@ -112,7 +113,7 @@ second new content
|
||||
const result1 = await cnfc(diff, original, isFinal)
|
||||
const result2 = await cnfc2(diff, original, isFinal)
|
||||
const expectedResult = "before\nfirst new content\nafter\nsecond new content\nend"
|
||||
expect(result1).to.equal(expectedResult)
|
||||
expect(result1.newContent).to.equal(expectedResult)
|
||||
expect(result2).to.equal(expectedResult)
|
||||
})
|
||||
|
||||
@@ -132,7 +133,7 @@ second new content
|
||||
const result1 = await cnfc(diff, original, isFinal)
|
||||
const result2 = await cnfc2(diff, original, isFinal)
|
||||
const expectedResult = "before\nfirst new content\nafter\nsecond new content\nend"
|
||||
expect(result1).to.equal(expectedResult)
|
||||
expect(result1.newContent).to.equal(expectedResult)
|
||||
expect(result2).to.equal(expectedResult)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ShowMessageType } from "@shared/proto/host/window"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import * as path from "path"
|
||||
import { ulid } from "ulid"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
|
||||
interface TaskReconstructionResult {
|
||||
totalTasks: number
|
||||
@@ -143,7 +144,7 @@ async function backupExistingTaskHistory(): Promise<void> {
|
||||
}
|
||||
} catch (error) {
|
||||
// Non-fatal error, just log it
|
||||
console.warn("Failed to backup existing task history:", error)
|
||||
Logger.warn("Failed to backup existing task history:", error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { fileExistsAtPath } from "@utils/fs"
|
||||
import cloneDeep from "clone-deep"
|
||||
import fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { getContextWindowInfo } from "./context-window-utils"
|
||||
|
||||
enum EditType {
|
||||
@@ -119,7 +120,7 @@ export class ContextManager {
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load context history:", error)
|
||||
Logger.error("Failed to load context history:", error)
|
||||
}
|
||||
return new Map()
|
||||
}
|
||||
@@ -139,7 +140,7 @@ export class ContextManager {
|
||||
"utf8",
|
||||
)
|
||||
} catch (error) {
|
||||
console.error("Failed to save context history:", error)
|
||||
Logger.error("Failed to save context history:", error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +208,7 @@ export class ContextManager {
|
||||
maxContextWindow: contextWindow,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing API request info for context telemetry:", error)
|
||||
Logger.error("Error parsing API request info for context telemetry:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,7 +358,7 @@ export class ContextManager {
|
||||
// Validate and fix tool_use/tool_result pairing
|
||||
this.ensureToolResultsFollowToolUse(updatedMessages)
|
||||
|
||||
// OLD NOTE: if you try to console log these, don't forget that logging a reference to an array may not provide the same result as logging a slice() snapshot of that array at that exact moment. The following DOES in fact include the latest assistant message.
|
||||
// OLD NOTE: if you try to Logger log these, don't forget that logging a reference to an array may not provide the same result as logging a slice() snapshot of that array at that exact moment. The following DOES in fact include the latest assistant message.
|
||||
return updatedMessages
|
||||
}
|
||||
|
||||
@@ -743,7 +744,7 @@ export class ContextManager {
|
||||
return true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("applyFirstUserMessageReplacement:", error)
|
||||
Logger.error("applyFirstUserMessageReplacement:", error)
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -4,6 +4,7 @@ import chokidar, { FSWatcher } from "chokidar"
|
||||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
import { Controller } from "@/core/controller"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { getCwd } from "@/utils/path"
|
||||
import type { FileMetadataEntry } from "./ContextTrackerTypes"
|
||||
|
||||
@@ -46,7 +47,7 @@ export class FileContextTracker {
|
||||
|
||||
const cwd = await getCwd()
|
||||
if (!cwd) {
|
||||
console.info("No workspace folder available - cannot determine current working directory")
|
||||
Logger.info("No workspace folder available - cannot determine current working directory")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -85,7 +86,7 @@ export class FileContextTracker {
|
||||
try {
|
||||
const cwd = await getCwd()
|
||||
if (!cwd) {
|
||||
console.info("No workspace folder available - cannot determine current working directory")
|
||||
Logger.info("No workspace folder available - cannot determine current working directory")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -95,7 +96,7 @@ export class FileContextTracker {
|
||||
// Set up file watcher for this file
|
||||
await this.setupFileWatcher(filePath)
|
||||
} catch (error) {
|
||||
console.error("Failed to track file operation:", error)
|
||||
Logger.error("Failed to track file operation:", error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +158,7 @@ export class FileContextTracker {
|
||||
metadata.files_in_context.push(newEntry)
|
||||
await saveTaskMetadata(taskId, metadata)
|
||||
} catch (error) {
|
||||
console.error("Failed to add file to metadata:", error)
|
||||
Logger.error("Failed to add file to metadata:", error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +209,7 @@ export class FileContextTracker {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error checking file context metadata:", error)
|
||||
Logger.error("Error checking file context metadata:", error)
|
||||
}
|
||||
|
||||
// Also check deleted task messages for file operations
|
||||
@@ -222,7 +223,7 @@ export class FileContextTracker {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error checking task messages:", error)
|
||||
Logger.error("Error checking task messages:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,7 +240,7 @@ export class FileContextTracker {
|
||||
// are legitimate workspace state keys but don't fit the strict LocalStateKey type system
|
||||
this.controller.stateManager.setWorkspaceState(key as any, files)
|
||||
} catch (error) {
|
||||
console.error("Error storing pending file context warning:", error)
|
||||
Logger.error("Error storing pending file context warning:", error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +253,7 @@ export class FileContextTracker {
|
||||
const files = this.controller.stateManager.getWorkspaceStateKey(key as any) as string[]
|
||||
return files
|
||||
} catch (error) {
|
||||
console.error("Error retrieving pending file context warning:", error)
|
||||
Logger.error("Error retrieving pending file context warning:", error)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
@@ -268,7 +269,7 @@ export class FileContextTracker {
|
||||
return files
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error retrieving pending file context warning:", error)
|
||||
Logger.error("Error retrieving pending file context warning:", error)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
@@ -301,11 +302,11 @@ export class FileContextTracker {
|
||||
}
|
||||
|
||||
const duration = Date.now() - startTime
|
||||
console.log(
|
||||
Logger.log(
|
||||
`FileContextTracker: Processed ${existingTaskIds.size} tasks, found ${pendingWarningKeys.length} pending warnings, ${orphanedPendingContextTasks.length} orphaned, deleted ${orphanedPendingContextTasks.length}, took ${duration}ms`,
|
||||
)
|
||||
} catch (error) {
|
||||
console.error("[FileContextTracker] Error cleaning up orphaned file context warnings:", error)
|
||||
Logger.error("[FileContextTracker] Error cleaning up orphaned file context warnings:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { extractPathLikeStrings, RuleEvaluationContext, toWorkspaceRelativePosixPath } from "./rule-conditionals"
|
||||
|
||||
type WorkspaceRoot = { path: string }
|
||||
type WorkspaceManagerLike = { getRoots(): WorkspaceRoot[] }
|
||||
|
||||
type ClineMessageLike = {
|
||||
type: string
|
||||
ask?: string
|
||||
say?: string
|
||||
text?: string
|
||||
}
|
||||
|
||||
type MessageStateHandlerLike = {
|
||||
getClineMessages(): ClineMessageLike[]
|
||||
}
|
||||
|
||||
export type RuleContextBuilderDeps = {
|
||||
cwd: string
|
||||
messageStateHandler: MessageStateHandlerLike
|
||||
workspaceManager?: WorkspaceManagerLike
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the evaluation context used for conditional Cline Rules (e.g. YAML frontmatter `paths:`).
|
||||
*
|
||||
* Kept in the user-instructions domain so Task remains orchestration-focused.
|
||||
*
|
||||
* Path context is gathered from multiple sources in clineMessages:
|
||||
* - User messages (task, user_feedback)
|
||||
* - Visible/open tabs
|
||||
* - Tool results (say="tool") - completed operations
|
||||
* - Tool requests (ask="tool") - pending operations (captures intent before execution)
|
||||
*/
|
||||
export class RuleContextBuilder {
|
||||
/**
|
||||
* Maximum number of path candidates to consider for rule activation.
|
||||
* This cap prevents performance degradation in long-running tasks with many file operations.
|
||||
*/
|
||||
static readonly MAX_RULE_PATH_CANDIDATES = 100
|
||||
|
||||
static async buildEvaluationContext(deps: RuleContextBuilderDeps): Promise<RuleEvaluationContext> {
|
||||
return {
|
||||
paths: await RuleContextBuilder.getRulePathContext(deps),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse apply_patch input to extract target file paths from patch headers.
|
||||
* Matches lines like: *** Add File: path/to/file.ts
|
||||
*/
|
||||
private static extractPathsFromApplyPatch(input: string): string[] {
|
||||
if (typeof input !== "string" || !input) return []
|
||||
|
||||
const paths: string[] = []
|
||||
const fileHeaderRegex = /^\*\*\* (?:Add|Update|Delete) File: (.+?)(?:\n|$)/gm
|
||||
let m: RegExpExecArray | null
|
||||
while ((m = fileHeaderRegex.exec(input))) {
|
||||
const filePath = (m[1] || "").trim()
|
||||
if (filePath) {
|
||||
paths.push(filePath)
|
||||
}
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
private static async getRulePathContext(deps: RuleContextBuilderDeps): Promise<string[]> {
|
||||
const candidates: string[] = []
|
||||
const clineMessages = deps.messageStateHandler.getClineMessages()
|
||||
|
||||
// (1) Current-turn user message evidence:
|
||||
// Use the most recent user-authored text (initial task or subsequent feedback).
|
||||
// NOTE: We intentionally prefer the latest user_feedback over the original task to
|
||||
// support first-turn activation on later turns.
|
||||
const lastUserMsg = [...clineMessages]
|
||||
.reverse()
|
||||
.find((m) => m.type === "say" && (m.say === "user_feedback" || m.say === "task") && typeof m.text === "string")
|
||||
if (lastUserMsg?.text) {
|
||||
candidates.push(...extractPathLikeStrings(lastUserMsg.text))
|
||||
}
|
||||
|
||||
// (2) Visible + open tabs
|
||||
const roots = deps.workspaceManager?.getRoots().map((r) => r.path) ?? [deps.cwd]
|
||||
const rawVisiblePaths = (await HostProvider.window.getVisibleTabs({}))?.paths ?? []
|
||||
const rawOpenTabPaths = (await HostProvider.window.getOpenTabs({}))?.paths ?? []
|
||||
for (const abs of [...rawVisiblePaths, ...rawOpenTabPaths]) {
|
||||
for (const root of roots) {
|
||||
const rel = toWorkspaceRelativePosixPath(abs, root)
|
||||
if (rel) {
|
||||
candidates.push(rel)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (3) Files edited by Cline during this task (completed operations):
|
||||
// Parse say="tool" messages for tool results indicating file operations.
|
||||
for (const msg of clineMessages) {
|
||||
if (msg.type !== "say" || msg.say !== "tool" || !msg.text) continue
|
||||
try {
|
||||
const tool = JSON.parse(msg.text) as { tool?: string; path?: string }
|
||||
if (
|
||||
(tool.tool === "editedExistingFile" || tool.tool === "newFileCreated" || tool.tool === "fileDeleted") &&
|
||||
tool.path
|
||||
) {
|
||||
candidates.push(tool.path)
|
||||
}
|
||||
} catch {
|
||||
// ignore parse errors
|
||||
}
|
||||
}
|
||||
|
||||
// (4) Tool requests (pending operations):
|
||||
// Parse ask="tool" messages to capture the assistant's intent BEFORE tool execution.
|
||||
// This enables rule activation even when:
|
||||
// - The tool hasn't completed yet
|
||||
// - The tool fails (intent was still expressed)
|
||||
// - Files don't exist yet (new file creation)
|
||||
for (const msg of clineMessages) {
|
||||
if (msg.type !== "ask" || msg.ask !== "tool" || !msg.text) continue
|
||||
try {
|
||||
const tool = JSON.parse(msg.text) as {
|
||||
tool?: string
|
||||
path?: string
|
||||
content?: string // apply_patch stores patch content here
|
||||
}
|
||||
|
||||
// Extract path from standard file tools
|
||||
if (tool.path) {
|
||||
candidates.push(tool.path)
|
||||
}
|
||||
|
||||
// Handle apply_patch specially: parse patch headers for file paths
|
||||
if (tool.tool === "applyPatch" && tool.content) {
|
||||
candidates.push(...RuleContextBuilder.extractPathsFromApplyPatch(tool.content))
|
||||
}
|
||||
} catch {
|
||||
// ignore parse errors
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize/dedupe/cap
|
||||
const seen = new Set<string>()
|
||||
const normalized: string[] = []
|
||||
for (const c of candidates) {
|
||||
const posix = c.replace(/\\/g, "/").replace(/^\//, "")
|
||||
if (!posix || posix === "/") continue
|
||||
if (seen.has(posix)) continue
|
||||
seen.add(posix)
|
||||
normalized.push(posix)
|
||||
if (normalized.length >= RuleContextBuilder.MAX_RULE_PATH_CANDIDATES) break
|
||||
}
|
||||
return normalized.sort()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
import { expect } from "chai"
|
||||
import sinon from "sinon"
|
||||
import { RuleContextBuilder, RuleContextBuilderDeps } from "../RuleContextBuilder"
|
||||
|
||||
// Mock HostProvider to avoid actual VSCode API calls
|
||||
const mockHostProvider = {
|
||||
window: {
|
||||
getVisibleTabs: sinon.stub().resolves({ paths: [] }),
|
||||
getOpenTabs: sinon.stub().resolves({ paths: [] }),
|
||||
},
|
||||
}
|
||||
|
||||
describe("RuleContextBuilder", () => {
|
||||
let hostProviderStub: sinon.SinonStub
|
||||
|
||||
beforeEach(() => {
|
||||
// Stub HostProvider to use mock
|
||||
hostProviderStub = sinon.stub(require("@/hosts/host-provider"), "HostProvider").value(mockHostProvider)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
describe("getRulePathContext from ask='tool' messages", () => {
|
||||
it("extracts path from ask='tool' message with write_to_file", async () => {
|
||||
const deps: RuleContextBuilderDeps = {
|
||||
cwd: "/workspace",
|
||||
messageStateHandler: {
|
||||
getClineMessages: () => [
|
||||
{
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "write_to_file",
|
||||
path: "src/components/Button.tsx",
|
||||
content: "// new file",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const context = await RuleContextBuilder.buildEvaluationContext(deps)
|
||||
expect(context.paths).to.include("src/components/Button.tsx")
|
||||
})
|
||||
|
||||
it("extracts paths from multiple sequential tool requests", async () => {
|
||||
const deps: RuleContextBuilderDeps = {
|
||||
cwd: "/workspace",
|
||||
messageStateHandler: {
|
||||
getClineMessages: () => [
|
||||
{
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "write_to_file",
|
||||
path: "src/utils/helper.ts",
|
||||
}),
|
||||
},
|
||||
{
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "replace_in_file",
|
||||
path: "src/index.ts",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const context = await RuleContextBuilder.buildEvaluationContext(deps)
|
||||
expect(context.paths).to.include("src/utils/helper.ts")
|
||||
expect(context.paths).to.include("src/index.ts")
|
||||
})
|
||||
|
||||
it("handles malformed JSON gracefully", async () => {
|
||||
const deps: RuleContextBuilderDeps = {
|
||||
cwd: "/workspace",
|
||||
messageStateHandler: {
|
||||
getClineMessages: () => [
|
||||
{
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
text: "not valid json {{{",
|
||||
},
|
||||
{
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "write_to_file",
|
||||
path: "valid/path.ts",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
// Should not throw and should extract the valid path
|
||||
const context = await RuleContextBuilder.buildEvaluationContext(deps)
|
||||
expect(context.paths).to.include("valid/path.ts")
|
||||
})
|
||||
|
||||
it("extracts paths from apply_patch tool request", async () => {
|
||||
const patchContent = `*** Add File: src/new-feature.ts
|
||||
+const x = 1
|
||||
|
||||
*** Update File: src/existing.ts
|
||||
---
|
||||
+++
|
||||
@@ 1,1 @@
|
||||
-const y = 2
|
||||
+const y = 3`
|
||||
|
||||
const deps: RuleContextBuilderDeps = {
|
||||
cwd: "/workspace",
|
||||
messageStateHandler: {
|
||||
getClineMessages: () => [
|
||||
{
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "applyPatch",
|
||||
content: patchContent,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const context = await RuleContextBuilder.buildEvaluationContext(deps)
|
||||
expect(context.paths).to.include("src/new-feature.ts")
|
||||
expect(context.paths).to.include("src/existing.ts")
|
||||
})
|
||||
|
||||
it("deduplicates paths from multiple sources", async () => {
|
||||
const deps: RuleContextBuilderDeps = {
|
||||
cwd: "/workspace",
|
||||
messageStateHandler: {
|
||||
getClineMessages: () => [
|
||||
{
|
||||
type: "say",
|
||||
say: "task",
|
||||
text: "Update src/index.ts",
|
||||
},
|
||||
{
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "write_to_file",
|
||||
path: "src/index.ts",
|
||||
}),
|
||||
},
|
||||
{
|
||||
type: "say",
|
||||
say: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "editedExistingFile",
|
||||
path: "src/index.ts",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const context = await RuleContextBuilder.buildEvaluationContext(deps)
|
||||
// Should only appear once despite being in 3 messages
|
||||
const indexCount = (context.paths ?? []).filter((p) => p === "src/index.ts").length
|
||||
expect(indexCount).to.equal(1)
|
||||
})
|
||||
|
||||
it("normalizes Windows-style paths to POSIX", async () => {
|
||||
const deps: RuleContextBuilderDeps = {
|
||||
cwd: "/workspace",
|
||||
messageStateHandler: {
|
||||
getClineMessages: () => [
|
||||
{
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "write_to_file",
|
||||
path: "src\\components\\Button.tsx",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const context = await RuleContextBuilder.buildEvaluationContext(deps)
|
||||
expect(context.paths).to.include("src/components/Button.tsx")
|
||||
})
|
||||
|
||||
it("respects MAX_RULE_PATH_CANDIDATES limit", async () => {
|
||||
// Create more messages than the limit
|
||||
const messages: Array<{ type: string; ask: string; text: string }> = []
|
||||
for (let i = 0; i < RuleContextBuilder.MAX_RULE_PATH_CANDIDATES + 50; i++) {
|
||||
messages.push({
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "write_to_file",
|
||||
path: `src/file${i}.ts`,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
const deps: RuleContextBuilderDeps = {
|
||||
cwd: "/workspace",
|
||||
messageStateHandler: {
|
||||
getClineMessages: () => messages,
|
||||
},
|
||||
}
|
||||
|
||||
const context = await RuleContextBuilder.buildEvaluationContext(deps)
|
||||
expect((context.paths ?? []).length).to.be.at.most(RuleContextBuilder.MAX_RULE_PATH_CANDIDATES)
|
||||
})
|
||||
})
|
||||
|
||||
describe("extractPathsFromApplyPatch", () => {
|
||||
it("extracts paths from Add File headers", async () => {
|
||||
const deps: RuleContextBuilderDeps = {
|
||||
cwd: "/workspace",
|
||||
messageStateHandler: {
|
||||
getClineMessages: () => [
|
||||
{
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "applyPatch",
|
||||
content: "*** Add File: src/new.ts\n+content",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const context = await RuleContextBuilder.buildEvaluationContext(deps)
|
||||
expect(context.paths).to.include("src/new.ts")
|
||||
})
|
||||
|
||||
it("extracts paths from Update File headers", async () => {
|
||||
const deps: RuleContextBuilderDeps = {
|
||||
cwd: "/workspace",
|
||||
messageStateHandler: {
|
||||
getClineMessages: () => [
|
||||
{
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "applyPatch",
|
||||
content: "*** Update File: src/existing.ts\n--- \n+++ \n@@ 1,1 @@\n-old\n+new",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const context = await RuleContextBuilder.buildEvaluationContext(deps)
|
||||
expect(context.paths).to.include("src/existing.ts")
|
||||
})
|
||||
|
||||
it("extracts paths from Delete File headers", async () => {
|
||||
const deps: RuleContextBuilderDeps = {
|
||||
cwd: "/workspace",
|
||||
messageStateHandler: {
|
||||
getClineMessages: () => [
|
||||
{
|
||||
type: "ask",
|
||||
ask: "tool",
|
||||
text: JSON.stringify({
|
||||
tool: "applyPatch",
|
||||
content: "*** Delete File: src/old.ts",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const context = await RuleContextBuilder.buildEvaluationContext(deps)
|
||||
expect(context.paths).to.include("src/old.ts")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -50,5 +50,24 @@ describe("rule-conditionals", () => {
|
||||
const res = extractPathLikeStrings("see https://example.com/a/b and edit src/index.ts")
|
||||
expect(res).to.deep.equal(["src/index.ts"])
|
||||
})
|
||||
|
||||
it("ignores fenced code blocks", () => {
|
||||
const text =
|
||||
"Please update src/index.ts\n\n```ts\n// example code\nconst p = 'apps/web/src/App.tsx'\n// also: packages/foo/src\n```\n\nThanks!"
|
||||
const res = extractPathLikeStrings(text)
|
||||
expect(res).to.deep.equal(["src/index.ts"])
|
||||
})
|
||||
|
||||
it("does not extract URLs inside code fences", () => {
|
||||
const text = "```\nSee https://example.com/a/b and src/index.ts\n```\nBut edit docs/readme.md"
|
||||
const res = extractPathLikeStrings(text)
|
||||
expect(res).to.deep.equal(["docs/readme.md"])
|
||||
})
|
||||
|
||||
it("extracts paths from stack traces (outside code fences)", () => {
|
||||
const text = "Error: boom\n at foo (src/index.ts:12:3)\n at bar (apps/web/src/App.tsx:5:1)"
|
||||
const res = extractPathLikeStrings(text)
|
||||
expect(res).to.deep.equal(["src/index.ts", "apps/web/src/App.tsx"])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -25,7 +25,7 @@ describe("rule loading with paths frontmatter", () => {
|
||||
expect(res1.content).to.contain("universal.md")
|
||||
expect(res1.content).to.contain("scoped.md")
|
||||
expect(res1.content).to.not.contain("paths:")
|
||||
expect(res1.activatedConditionalRules.map((r) => r.name)).to.include("scoped.md")
|
||||
expect(res1.activatedConditionalRules.map((r) => r.name)).to.include("global:scoped.md")
|
||||
|
||||
const res2 = await getRuleFilesTotalContentWithMetadata(files, rulesDir, toggles, {
|
||||
evaluationContext: { paths: ["docs/readme.md"] },
|
||||
@@ -109,7 +109,7 @@ describe("rule loading with paths frontmatter", () => {
|
||||
evaluationContext: { paths: ["src/index.ts"] },
|
||||
})
|
||||
|
||||
expect(res.activatedConditionalRules.map((r) => r.name)).to.deep.equal(files)
|
||||
expect(res.activatedConditionalRules.map((r) => r.name)).to.deep.equal(files.map((f) => `global:${f}`))
|
||||
} finally {
|
||||
await fs.rm(tmp, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as path from "path"
|
||||
import * as sinon from "sinon"
|
||||
|
||||
import * as disk from "@/core/storage/disk"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import * as fsUtils from "@/utils/fs"
|
||||
import { discoverSkills, getAvailableSkills, getSkillContent } from "../skills"
|
||||
|
||||
@@ -29,8 +30,8 @@ describe("Skills Utility Functions", () => {
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
// Stub console.warn to avoid noise in test output
|
||||
sandbox.stub(console, "warn")
|
||||
// Stub Logger.warn to avoid noise in test output
|
||||
sandbox.stub(Logger, "warn")
|
||||
|
||||
// Stub filesystem utilities
|
||||
fileExistsStub = sandbox.stub(fsUtils, "fileExistsAtPath")
|
||||
@@ -295,7 +296,7 @@ Content`)
|
||||
const skills = await discoverSkills(TEST_CWD)
|
||||
|
||||
expect(skills).to.have.lengthOf(0)
|
||||
sinon.assert.calledWithMatch(console.warn as sinon.SinonStub, /missing required 'name' field/)
|
||||
sinon.assert.calledWithMatch(Logger.warn as sinon.SinonStub, /missing required 'name' field/)
|
||||
})
|
||||
|
||||
it("should reject skill with missing description field", async () => {
|
||||
@@ -315,7 +316,7 @@ Content`)
|
||||
const skills = await discoverSkills(TEST_CWD)
|
||||
|
||||
expect(skills).to.have.lengthOf(0)
|
||||
sinon.assert.calledWithMatch(console.warn as sinon.SinonStub, /missing required 'description' field/)
|
||||
sinon.assert.calledWithMatch(Logger.warn as sinon.SinonStub, /missing required 'description' field/)
|
||||
})
|
||||
|
||||
it("should reject skill when name doesn't match directory name", async () => {
|
||||
@@ -336,7 +337,7 @@ Content`)
|
||||
const skills = await discoverSkills(TEST_CWD)
|
||||
|
||||
expect(skills).to.have.lengthOf(0)
|
||||
sinon.assert.calledWithMatch(console.warn as sinon.SinonStub, /doesn't match directory/)
|
||||
sinon.assert.calledWithMatch(Logger.warn as sinon.SinonStub, /doesn't match directory/)
|
||||
})
|
||||
|
||||
it("should handle malformed YAML frontmatter gracefully", async () => {
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { getRuleFilesTotalContent, synchronizeRuleToggles } from "@core/context/instructions/user-instructions/rule-helpers"
|
||||
import {
|
||||
ActivatedConditionalRule,
|
||||
getRemoteRulesTotalContentWithMetadata,
|
||||
getRuleFilesTotalContentWithMetadata,
|
||||
RULE_SOURCE_PREFIX,
|
||||
RuleLoadResultWithInstructions,
|
||||
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"
|
||||
@@ -7,24 +14,42 @@ import { fileExistsAtPath, isDirectory, readDirectory } from "@utils/fs"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { Controller } from "@/core/controller"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { parseYamlFrontmatter } from "./frontmatter"
|
||||
import { evaluateRuleConditionals, type RuleEvaluationContext } from "./rule-conditionals"
|
||||
|
||||
export const getGlobalClineRules = async (globalClineRulesFilePath: string, toggles: ClineRulesToggles) => {
|
||||
export const getGlobalClineRules = async (
|
||||
globalClineRulesFilePath: string,
|
||||
toggles: ClineRulesToggles,
|
||||
opts?: { evaluationContext?: RuleEvaluationContext },
|
||||
): Promise<RuleLoadResultWithInstructions> => {
|
||||
let combinedContent = ""
|
||||
const activatedConditionalRules: ActivatedConditionalRule[] = []
|
||||
|
||||
// 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) {
|
||||
combinedContent = rulesFilesTotalContent
|
||||
// Note: ruleNamePrefix explicitly set to "global" for clarity (matches the default)
|
||||
const rulesFilesTotal = await getRuleFilesTotalContentWithMetadata(
|
||||
rulesFilePaths,
|
||||
globalClineRulesFilePath,
|
||||
toggles,
|
||||
{
|
||||
evaluationContext: opts?.evaluationContext,
|
||||
ruleNamePrefix: "global",
|
||||
},
|
||||
)
|
||||
if (rulesFilesTotal.content) {
|
||||
combinedContent = rulesFilesTotal.content
|
||||
activatedConditionalRules.push(...rulesFilesTotal.activatedConditionalRules)
|
||||
}
|
||||
} catch {
|
||||
console.error(`Failed to read .clinerules directory at ${globalClineRulesFilePath}`)
|
||||
Logger.error(`Failed to read .clinerules directory at ${globalClineRulesFilePath}`)
|
||||
}
|
||||
} else {
|
||||
console.error(`${globalClineRulesFilePath} is not a directory`)
|
||||
Logger.error(`${globalClineRulesFilePath} is not a directory`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,31 +58,35 @@ export const getGlobalClineRules = async (globalClineRulesFilePath: string, togg
|
||||
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}`
|
||||
}
|
||||
const remoteResult = getRemoteRulesTotalContentWithMetadata(remoteRules, remoteToggles, {
|
||||
evaluationContext: opts?.evaluationContext,
|
||||
})
|
||||
if (remoteResult.content) {
|
||||
if (combinedContent) combinedContent += "\n\n"
|
||||
combinedContent += remoteResult.content
|
||||
activatedConditionalRules.push(...remoteResult.activatedConditionalRules)
|
||||
}
|
||||
|
||||
// 3. Return formatted instructions
|
||||
if (combinedContent) {
|
||||
return formatResponse.clineRulesGlobalDirectoryInstructions(globalClineRulesFilePath, combinedContent)
|
||||
if (!combinedContent) {
|
||||
return { instructions: undefined, activatedConditionalRules: [] }
|
||||
}
|
||||
|
||||
return undefined
|
||||
return {
|
||||
instructions: formatResponse.clineRulesGlobalDirectoryInstructions(globalClineRulesFilePath, combinedContent),
|
||||
activatedConditionalRules,
|
||||
}
|
||||
}
|
||||
|
||||
export const getLocalClineRules = async (cwd: string, toggles: ClineRulesToggles) => {
|
||||
export const getLocalClineRules = async (
|
||||
cwd: string,
|
||||
toggles: ClineRulesToggles,
|
||||
opts?: { evaluationContext?: RuleEvaluationContext },
|
||||
): Promise<RuleLoadResultWithInstructions> => {
|
||||
const clineRulesFilePath = path.resolve(cwd, GlobalFileNames.clineRules)
|
||||
|
||||
let clineRulesFileInstructions: string | undefined
|
||||
let instructions: string | undefined
|
||||
const activatedConditionalRules: ActivatedConditionalRule[] = []
|
||||
|
||||
if (await fileExistsAtPath(clineRulesFilePath)) {
|
||||
if (await isDirectory(clineRulesFilePath)) {
|
||||
@@ -68,28 +97,53 @@ export const getLocalClineRules = async (cwd: string, toggles: ClineRulesToggles
|
||||
[".clinerules", "skills"],
|
||||
])
|
||||
|
||||
const rulesFilesTotalContent = await getRuleFilesTotalContent(rulesFilePaths, cwd, toggles)
|
||||
if (rulesFilesTotalContent) {
|
||||
clineRulesFileInstructions = formatResponse.clineRulesLocalDirectoryInstructions(cwd, rulesFilesTotalContent)
|
||||
const rulesFilesTotal = await getRuleFilesTotalContentWithMetadata(rulesFilePaths, cwd, toggles, {
|
||||
evaluationContext: opts?.evaluationContext,
|
||||
ruleNamePrefix: "workspace",
|
||||
})
|
||||
if (rulesFilesTotal.content) {
|
||||
instructions = formatResponse.clineRulesLocalDirectoryInstructions(cwd, rulesFilesTotal.content)
|
||||
activatedConditionalRules.push(...rulesFilesTotal.activatedConditionalRules)
|
||||
}
|
||||
} catch {
|
||||
console.error(`Failed to read .clinerules directory at ${clineRulesFilePath}`)
|
||||
Logger.error(`Failed to read .clinerules directory at ${clineRulesFilePath}`)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
if (clineRulesFilePath in toggles && toggles[clineRulesFilePath] !== false) {
|
||||
const ruleFileContent = (await fs.readFile(clineRulesFilePath, "utf8")).trim()
|
||||
if (ruleFileContent) {
|
||||
clineRulesFileInstructions = formatResponse.clineRulesLocalFileInstructions(cwd, ruleFileContent)
|
||||
const raw = (await fs.readFile(clineRulesFilePath, "utf8")).trim()
|
||||
if (raw) {
|
||||
// Keep single-file .clinerules behavior consistent with directory/remote rules:
|
||||
// - Parse YAML frontmatter (fail-open on parse errors)
|
||||
// - Evaluate conditionals against the request's evaluation context
|
||||
const parsed = parseYamlFrontmatter(raw)
|
||||
if (parsed.hadFrontmatter && parsed.parseError) {
|
||||
// Fail-open: preserve the raw contents so the LLM can still see the author's intent.
|
||||
instructions = formatResponse.clineRulesLocalFileInstructions(cwd, raw)
|
||||
} else {
|
||||
const { passed, matchedConditions } = evaluateRuleConditionals(
|
||||
parsed.data,
|
||||
opts?.evaluationContext ?? {},
|
||||
)
|
||||
if (passed) {
|
||||
instructions = formatResponse.clineRulesLocalFileInstructions(cwd, parsed.body.trim())
|
||||
if (parsed.hadFrontmatter && Object.keys(matchedConditions).length > 0) {
|
||||
activatedConditionalRules.push({
|
||||
name: `${RULE_SOURCE_PREFIX.workspace}:${GlobalFileNames.clineRules}`,
|
||||
matchedConditions,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.error(`Failed to read .clinerules file at ${clineRulesFilePath}`)
|
||||
Logger.error(`Failed to read .clinerules file at ${clineRulesFilePath}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clineRulesFileInstructions
|
||||
return { instructions, activatedConditionalRules }
|
||||
}
|
||||
|
||||
export async function refreshClineRulesToggles(
|
||||
|
||||
@@ -12,6 +12,7 @@ import { fileExistsAtPath, isDirectory } from "@utils/fs"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { Controller } from "@/core/controller"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
|
||||
/**
|
||||
* Refreshes the toggles for windsurf, cursor, and agents rules
|
||||
@@ -75,7 +76,7 @@ export const getLocalWindsurfRules = async (cwd: string, toggles: ClineRulesTogg
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.error(`Failed to read .windsurfrules file at ${windsurfRulesFilePath}`)
|
||||
Logger.error(`Failed to read .windsurfrules file at ${windsurfRulesFilePath}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,7 +102,7 @@ export const getLocalCursorRules = async (cwd: string, toggles: ClineRulesToggle
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.error(`Failed to read .cursorrules file at ${cursorRulesFilePath}`)
|
||||
Logger.error(`Failed to read .cursorrules file at ${cursorRulesFilePath}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,7 +120,7 @@ export const getLocalCursorRules = async (cwd: string, toggles: ClineRulesToggle
|
||||
cursorRulesDirInstructions = formatResponse.cursorRulesLocalDirectoryInstructions(cwd, rulesFilesTotalContent)
|
||||
}
|
||||
} catch {
|
||||
console.error(`Failed to read .cursor/rules directory at ${cursorRulesDirPath}`)
|
||||
Logger.error(`Failed to read .cursor/rules directory at ${cursorRulesDirPath}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,7 +150,7 @@ async function findAgentsMdFiles(cwd: string): Promise<string[]> {
|
||||
return basename === GlobalFileNames.agentsRulesFile.toLowerCase()
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(`Failed to find agents.md files in ${cwd}:`, error)
|
||||
Logger.error(`Failed to find agents.md files in ${cwd}:`, error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -184,7 +185,7 @@ export const getLocalAgentsRules = async (cwd: string, toggles: ClineRulesToggle
|
||||
}
|
||||
return null
|
||||
} catch (error) {
|
||||
console.error(`Failed to read agents.md file at ${filePath}:`, error)
|
||||
Logger.error(`Failed to read agents.md file at ${filePath}:`, error)
|
||||
return null
|
||||
}
|
||||
}),
|
||||
@@ -194,7 +195,7 @@ export const getLocalAgentsRules = async (cwd: string, toggles: ClineRulesToggle
|
||||
return formatResponse.agentsRulesLocalFileInstructions(cwd, combinedContent)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to read agents.md files:", error)
|
||||
Logger.error("Failed to read agents.md files:", error)
|
||||
}
|
||||
|
||||
return undefined
|
||||
|
||||
@@ -46,6 +46,7 @@ const evaluatePathsConditional: ConditionalEvaluatorWithMatch = (frontmatterValu
|
||||
}
|
||||
|
||||
const patterns = frontmatterValue.map((p) => p.trim()).filter(Boolean)
|
||||
|
||||
// Policy:
|
||||
// - `paths` omitted => universal (because this evaluator is never invoked)
|
||||
// - `paths: []` (or `paths` that trims to no usable patterns) => match nothing (fail-closed)
|
||||
@@ -111,8 +112,13 @@ export function evaluateRuleConditionals(
|
||||
export function extractPathLikeStrings(text: string): string[] {
|
||||
if (!text) return []
|
||||
|
||||
// 0) Strip fenced code blocks to avoid extracting paths from pasted code.
|
||||
// This dramatically reduces false positives from snippets containing `foo/bar` or `a.b.c`.
|
||||
// Note: We intentionally keep this simple and fail-open (if fences are unbalanced, we do nothing special).
|
||||
const withoutCodeFences = text.replace(/```[\s\S]*?```/g, " ")
|
||||
|
||||
// 1) Remove URLs to avoid false positives.
|
||||
const withoutUrls = text.replace(/\b\w+:\/\/[^\s]+/g, " ")
|
||||
const withoutUrls = withoutCodeFences.replace(/\b\w+:\/\/[^\s]+/g, " ")
|
||||
|
||||
// 2) Match tokens that look like paths.
|
||||
// - Either contain at least one slash (e.g. src/index.ts)
|
||||
|
||||
@@ -5,6 +5,7 @@ import { fileExistsAtPath, isDirectory, readDirectory } from "@utils/fs"
|
||||
import fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import { Controller } from "@/core/controller"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { parseYamlFrontmatter } from "./frontmatter"
|
||||
import { evaluateRuleConditionals, RuleEvaluationContext } from "./rule-conditionals"
|
||||
|
||||
@@ -30,7 +31,7 @@ export async function readDirectoryRecursive(
|
||||
}
|
||||
return results
|
||||
} catch (error) {
|
||||
console.error(`Error reading directory ${directoryPath}: ${error}`)
|
||||
Logger.error(`Error reading directory ${directoryPath}: ${error}`)
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -98,7 +99,7 @@ export async function synchronizeRuleToggles(
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to synchronize rule toggles for path: ${rulesDirectoryPath}`, error)
|
||||
Logger.error(`Failed to synchronize rule toggles for path: ${rulesDirectoryPath}`, error)
|
||||
}
|
||||
|
||||
return updatedToggles
|
||||
@@ -153,25 +154,43 @@ export type ActivatedConditionalRule = {
|
||||
matchedConditions: Record<string, string[]>
|
||||
}
|
||||
|
||||
// Prefixes used to make activated conditional rule identifiers self-explanatory in the UI.
|
||||
// NOTE: These are display identifiers (not toggle keys).
|
||||
export const RULE_SOURCE_PREFIX = {
|
||||
workspace: "workspace",
|
||||
global: "global",
|
||||
remote: "remote",
|
||||
} as const
|
||||
|
||||
export type RuleLoadResult = {
|
||||
content: string
|
||||
activatedConditionalRules: ActivatedConditionalRule[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Result type for rule loading functions that return formatted instructions.
|
||||
* Used by getGlobalClineRules and getLocalClineRules.
|
||||
*/
|
||||
export type RuleLoadResultWithInstructions = {
|
||||
instructions?: string
|
||||
activatedConditionalRules: ActivatedConditionalRule[]
|
||||
}
|
||||
|
||||
export const getRuleFilesTotalContentWithMetadata = async (
|
||||
rulesFilePaths: string[],
|
||||
basePath: string,
|
||||
toggles: ClineRulesToggles,
|
||||
opts?: { evaluationContext?: RuleEvaluationContext },
|
||||
opts?: { evaluationContext?: RuleEvaluationContext; ruleNamePrefix?: keyof typeof RULE_SOURCE_PREFIX },
|
||||
): Promise<RuleLoadResult> => {
|
||||
const evaluationContext = opts?.evaluationContext ?? {}
|
||||
const prefix = RULE_SOURCE_PREFIX[opts?.ruleNamePrefix ?? "global"]
|
||||
|
||||
type RuleLoadPart = {
|
||||
contentPart: string | null
|
||||
activatedRule: ActivatedConditionalRule | null
|
||||
}
|
||||
|
||||
const parts: RuleLoadPart[] = await Promise.all(
|
||||
const parts = await Promise.all(
|
||||
rulesFilePaths.map(async (filePath) => {
|
||||
const ruleFilePath = path.resolve(basePath, filePath)
|
||||
const ruleFilePathRelative = path.relative(basePath, ruleFilePath)
|
||||
@@ -199,7 +218,7 @@ export const getRuleFilesTotalContentWithMetadata = async (
|
||||
}
|
||||
const activatedRule =
|
||||
hadFrontmatter && Object.keys(matchedConditions).length > 0
|
||||
? { name: ruleFilePathRelative, matchedConditions }
|
||||
? { name: `${prefix}:${ruleFilePathRelative}`, matchedConditions }
|
||||
: null
|
||||
|
||||
return { contentPart: `${ruleFilePathRelative}\n${body.trim()}`, activatedRule }
|
||||
@@ -245,7 +264,7 @@ export function getRemoteRulesTotalContentWithMetadata(
|
||||
if (!passed) continue
|
||||
|
||||
if (hadFrontmatter && Object.keys(matchedConditions).length > 0) {
|
||||
activatedConditionalRules.push({ name: rule.name, matchedConditions })
|
||||
activatedConditionalRules.push({ name: `${RULE_SOURCE_PREFIX.remote}:${rule.name}`, matchedConditions })
|
||||
}
|
||||
|
||||
if (combinedContent) combinedContent += "\n\n"
|
||||
@@ -413,7 +432,7 @@ export async function deleteRuleFile(
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
console.error(`Error deleting file: ${errorMessage}`, error)
|
||||
Logger.error(`Error deleting file: ${errorMessage}`, error)
|
||||
return {
|
||||
success: false,
|
||||
message: `Failed to delete file.`,
|
||||
|
||||
@@ -3,13 +3,14 @@ import type { SkillContent, SkillMetadata } from "@shared/skills"
|
||||
import { fileExistsAtPath, isDirectory } from "@utils/fs"
|
||||
import * as fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { parseYamlFrontmatter } from "./frontmatter"
|
||||
|
||||
/** Parse YAML frontmatter from markdown content (shared helper). */
|
||||
function parseFrontmatter(fileContent: string): { data: Record<string, unknown>; content: string } {
|
||||
const result = parseYamlFrontmatter(fileContent)
|
||||
if (result.parseError) {
|
||||
console.warn("Failed to parse YAML frontmatter:", result.parseError)
|
||||
Logger.warn("Failed to parse YAML frontmatter:", result.parseError)
|
||||
}
|
||||
return { data: result.data, content: result.body }
|
||||
}
|
||||
@@ -39,7 +40,7 @@ async function scanSkillsDirectory(dirPath: string, source: "global" | "project"
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "EACCES") {
|
||||
console.warn(`Permission denied reading skills directory: ${dirPath}`)
|
||||
Logger.warn(`Permission denied reading skills directory: ${dirPath}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,17 +64,17 @@ async function loadSkillMetadata(
|
||||
|
||||
// Validate required fields
|
||||
if (!frontmatter.name || typeof frontmatter.name !== "string") {
|
||||
console.warn(`Skill at ${skillDir} missing required 'name' field`)
|
||||
Logger.warn(`Skill at ${skillDir} missing required 'name' field`)
|
||||
return null
|
||||
}
|
||||
if (!frontmatter.description || typeof frontmatter.description !== "string") {
|
||||
console.warn(`Skill at ${skillDir} missing required 'description' field`)
|
||||
Logger.warn(`Skill at ${skillDir} missing required 'description' field`)
|
||||
return null
|
||||
}
|
||||
|
||||
// Name must match directory name per spec
|
||||
if (frontmatter.name !== skillName) {
|
||||
console.warn(`Skill name "${frontmatter.name}" doesn't match directory "${skillName}"`)
|
||||
Logger.warn(`Skill name "${frontmatter.name}" doesn't match directory "${skillName}"`)
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -84,7 +85,7 @@ async function loadSkillMetadata(
|
||||
source,
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load skill at ${skillDir}:`, error)
|
||||
Logger.warn(`Failed to load skill at ${skillDir}:`, error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AuthState, AuthStateChangedRequest } from "@shared/proto/cline/account"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import type { Controller } from "../index"
|
||||
|
||||
/**
|
||||
@@ -16,7 +17,7 @@ export async function authStateChanged(controller: Controller, request: AuthStat
|
||||
// Return the same user info
|
||||
return AuthState.create({ user: request.user })
|
||||
} catch (error) {
|
||||
console.error(`Failed to update auth state: ${error}`)
|
||||
Logger.error(`Failed to update auth state: ${error}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GetOrganizationCreditsRequest, OrganizationCreditsData, OrganizationUsageTransaction } from "@shared/proto/cline/account"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import type { Controller } from "../index"
|
||||
|
||||
/**
|
||||
@@ -50,7 +51,7 @@ export async function getOrganizationCredits(
|
||||
) || [],
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch organization credits data: ${error}`)
|
||||
Logger.error(`Failed to fetch organization credits data: ${error}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { UserCreditsData } from "@shared/proto/cline/account"
|
||||
import type { EmptyRequest } from "@shared/proto/cline/common"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import type { Controller } from "../index"
|
||||
|
||||
/**
|
||||
@@ -32,7 +33,7 @@ export async function getUserCredits(controller: Controller, _request: EmptyRequ
|
||||
paymentTransactions: paymentTransactions,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch user credits data: ${error}`)
|
||||
Logger.error(`Failed to fetch user credits data: ${error}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Empty, EmptyRequest } from "@shared/proto/cline/common"
|
||||
import { openAiCodexOAuthManager } from "@/integrations/openai-codex/oauth"
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { openExternal } from "@/utils/env"
|
||||
import { ShowMessageType } from "@shared/proto/host/window"
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { openAiCodexOAuthManager } from "@/integrations/openai-codex/oauth"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { openExternal } from "@/utils/env"
|
||||
import { Controller } from ".."
|
||||
|
||||
/**
|
||||
@@ -29,7 +30,7 @@ export async function openAiCodexSignIn(controller: Controller, _: EmptyRequest)
|
||||
await controller.postStateToWebview()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("[openAiCodexSignIn] OAuth callback failed:", error)
|
||||
Logger.error("[openAiCodexSignIn] OAuth callback failed:", error)
|
||||
openAiCodexOAuthManager.cancelAuthorizationFlow()
|
||||
// Don't show notification for timeouts (user likely just abandoned)
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
@@ -41,7 +42,7 @@ export async function openAiCodexSignIn(controller: Controller, _: EmptyRequest)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("[openAiCodexSignIn] Failed to start OAuth flow:", error)
|
||||
Logger.error("[openAiCodexSignIn] Failed to start OAuth flow:", error)
|
||||
openAiCodexOAuthManager.cancelAuthorizationFlow()
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Empty, EmptyRequest } from "@shared/proto/cline/common"
|
||||
import { openAiCodexOAuthManager } from "@/integrations/openai-codex/oauth"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { Controller } from ".."
|
||||
|
||||
/**
|
||||
@@ -16,7 +17,7 @@ export async function openAiCodexSignOut(controller: Controller, _: EmptyRequest
|
||||
// Update the state to reflect sign out
|
||||
await controller.postStateToWebview()
|
||||
} catch (error) {
|
||||
console.error("[openAiCodexSignOut] Failed to sign out:", error)
|
||||
Logger.error("[openAiCodexSignOut] Failed to sign out:", error)
|
||||
throw error
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BrowserConnectionInfo } from "@shared/proto/cline/browser"
|
||||
import { EmptyRequest } from "@shared/proto/cline/common"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { Controller } from "../index"
|
||||
|
||||
/**
|
||||
@@ -36,7 +37,7 @@ export async function getBrowserConnectionInfo(controller: Controller, _: EmptyR
|
||||
host: browserSettings.remoteBrowserHost || "",
|
||||
})
|
||||
} catch (error: unknown) {
|
||||
console.error("Error getting browser connection info:", error)
|
||||
Logger.error("Error getting browser connection info:", error)
|
||||
return BrowserConnectionInfo.create({
|
||||
isConnected: false,
|
||||
isRemote: false,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ChromePath } from "@shared/proto/cline/browser"
|
||||
import { EmptyRequest } from "@shared/proto/cline/common"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { BrowserSession } from "../../../services/browser/BrowserSession"
|
||||
import { Controller } from "../index"
|
||||
|
||||
@@ -19,7 +20,7 @@ export async function getDetectedChromePath(controller: Controller, _: EmptyRequ
|
||||
isBundled: result.isBundled,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Error getting detected Chrome path:", error)
|
||||
Logger.error("Error getting detected Chrome path:", error)
|
||||
return ChromePath.create({
|
||||
path: "",
|
||||
isBundled: false,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Empty } from "@shared/proto/cline/common"
|
||||
import pWaitFor from "p-wait-for"
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { ShowMessageType } from "@/shared/proto/index.host"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { ClineCheckpointRestore } from "../../../shared/WebviewMessage"
|
||||
import { Controller } from ".."
|
||||
|
||||
@@ -14,7 +15,7 @@ export async function checkpointRestore(controller: Controller, request: Checkpo
|
||||
await pWaitFor(() => controller.task?.taskState.isInitialized === true, {
|
||||
timeout: 3_000,
|
||||
}).catch((error) => {
|
||||
console.log("Failed to init new Cline instance to restore checkpoint", error)
|
||||
Logger.log("Failed to init new Cline instance to restore checkpoint", error)
|
||||
HostProvider.window.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: "Failed to restore checkpoint",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CheckpointEvent, CheckpointEvent_OperationType, CheckpointSubscriptionRequest } from "@shared/proto/cline/checkpoints"
|
||||
import { Timestamp } from "@shared/proto/google/protobuf/timestamp"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { getRequestRegistry, StreamingResponseHandler } from "../grpc-handler"
|
||||
import { Controller } from "../index"
|
||||
|
||||
@@ -105,7 +106,7 @@ export async function sendCheckpointEvent(eventData: CheckpointEventData): Promi
|
||||
try {
|
||||
await responseStream(event, false) // Not the last message
|
||||
} catch (error) {
|
||||
console.error("Error sending checkpoint event:", error)
|
||||
Logger.error("Error sending checkpoint event:", error)
|
||||
subscriptions.delete(responseStream)
|
||||
if (subscriptions.size === 0) {
|
||||
activeCheckpointSubscriptions.delete(cwdHash)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { getFileMentionFromPath } from "@/core/mentions"
|
||||
import { singleFileDiagnosticsToProblemsString } from "@/integrations/diagnostics"
|
||||
import { Logger } from "@/services/logging/Logger"
|
||||
import { telemetryService } from "@/services/telemetry"
|
||||
import { CommandContext, Empty } from "@/shared/proto/index.cline"
|
||||
import { Logger } from "@/shared/services/Logger"
|
||||
import { Controller } from "../index"
|
||||
import { sendAddToInputEvent } from "../ui/subscribeToAddToInput"
|
||||
|
||||
@@ -39,7 +39,7 @@ export async function addToCline(controller: Controller, request: CommandContext
|
||||
await sendAddToInputEvent(input)
|
||||
}
|
||||
|
||||
console.log("addToCline", request.selectedText, filePath, request.language, notebookContext ? "with notebook context" : "")
|
||||
Logger.log("addToCline", request.selectedText, filePath, request.language)
|
||||
telemetryService.captureButtonClick("codeAction_addToChat", controller.task?.ulid)
|
||||
|
||||
return {}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user