diff --git a/.gitattributes b/.gitattributes
index d351e0fc83e..30441642037 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -18,3 +18,7 @@ packages/kilo-vscode/tests/**/*.png filter=lfs diff=lfs merge=lfs -text
**/i18n/parity.test.ts linguist-generated=false
packages/kilo-i18n/src/*.ts linguist-generated=true
packages/kilo-i18n/src/en.ts linguist-generated=false
+
+# Auto-generated CLI reference docs
+packages/kilo-docs/markdoc/partials/cli-commands-table.md linguist-generated=true
+packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md linguist-generated=true
diff --git a/AGENTS.md b/AGENTS.md
index 1d4c3f77eb2..8f3ed22f506 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -10,6 +10,7 @@ Kilo CLI is an open source AI coding agent that generates code from natural lang
## Build and Dev
- **Dev**: `bun run dev` (runs from root) or `bun run --cwd packages/opencode --conditions=browser src/index.ts`
+- **Dev with params**: `bun dev -- help`
- **Extension**: `bun run extension` (build + launch VS Code with the extension in dev mode). Pass `--no-build` to skip the build.
- **Typecheck**: `bun turbo typecheck` (uses `tsgo`, not `tsc`)
- **Test**: `bun test` from `packages/opencode/` (NOT from root -- root blocks tests)
@@ -174,6 +175,8 @@ Tests MUST test actual implementation, do not duplicate logic into a test.
Kilo CLI is a fork of [opencode](https://github.com/anomalyco/opencode).
+**Very important**: when planning or coding, update shared files with OpenCode as last resort! Everything is shared code from OpenCode, except folders that contain `kilo` in the name or have a parent directory that contains `kilo` in the name. Example of kilo specific folders: `packages/opencode/src/kilocode/` and `packages/kilo-docs/`. Always look for ways to implement your feature or fix in a way that minimizes changes to shared code.
+
### Minimizing Merge Conflicts
We regularly merge upstream changes from opencode. To minimize merge conflicts and keep the sync process smooth:
@@ -217,6 +220,21 @@ const bar = 2
// kilocode_change - new file
```
+
+**JSX/TSX (inside JSX templates):**
+
+
+```tsx
+{/* kilocode_change */}
+```
+
+
+```tsx
+{/* kilocode_change start */}
+
+{/* kilocode_change end */}
+```
+
#### When markers are NOT needed
Code in these paths is Kilo Code-specific and does NOT need `kilocode_change` markers:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 65565b37ab7..d7950ddfe8f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -72,12 +72,10 @@ During development, `bun dev` is the local equivalent of the built `kilo` comman
# Development (from project root)
bun dev --help # Show all available commands
bun dev serve # Start headless API server
-bun dev web # Start server + open web interface
# Production
kilo --help # Show all available commands
kilo serve # Start headless API server
-kilo web # Start server + open web interface
```
### Testing with a local backend
diff --git a/bun.lock b/bun.lock
index 19f273a6ac1..297c0b0bd9c 100644
--- a/bun.lock
+++ b/bun.lock
@@ -1,6 +1,5 @@
{
"lockfileVersion": 1,
- "configVersion": 1,
"workspaces": {
"": {
"name": "@kilocode/kilo",
@@ -27,7 +26,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
- "version": "7.2.0",
+ "version": "7.2.1",
"dependencies": {
"@kilocode/kilo-i18n": "workspace:*",
"@kilocode/kilo-ui": "workspace:*",
@@ -79,7 +78,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
- "version": "7.2.0",
+ "version": "7.2.1",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -112,7 +111,7 @@
},
"packages/desktop-electron": {
"name": "@opencode-ai/desktop-electron",
- "version": "7.2.0",
+ "version": "7.2.1",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -142,7 +141,7 @@
},
"packages/kilo-docs": {
"name": "@kilocode/kilo-docs",
- "version": "7.2.0",
+ "version": "7.2.1",
"dependencies": {
"@docsearch/css": "^4",
"@docsearch/js": "^4",
@@ -171,7 +170,7 @@
},
"packages/kilo-gateway": {
"name": "@kilocode/kilo-gateway",
- "version": "7.2.0",
+ "version": "7.2.1",
"dependencies": {
"@ai-sdk/anthropic": "2.0.65",
"@ai-sdk/openai": "2.0.101",
@@ -206,7 +205,7 @@
},
"packages/kilo-i18n": {
"name": "@kilocode/kilo-i18n",
- "version": "7.2.0",
+ "version": "7.2.1",
"devDependencies": {
"@tsconfig/node22": "catalog:",
"@types/bun": "catalog:",
@@ -219,7 +218,7 @@
},
"packages/kilo-telemetry": {
"name": "@kilocode/kilo-telemetry",
- "version": "7.2.0",
+ "version": "7.2.1",
"dependencies": {
"@kilocode/kilo-gateway": "workspace:*",
"@opentelemetry/api": "1.9.0",
@@ -239,7 +238,7 @@
},
"packages/kilo-ui": {
"name": "@kilocode/kilo-ui",
- "version": "7.2.0",
+ "version": "7.2.1",
"dependencies": {
"@kobalte/core": "0.13.11",
"@opencode-ai/util": "workspace:*",
@@ -274,7 +273,7 @@
},
"packages/kilo-vscode": {
"name": "kilo-code",
- "version": "7.2.0",
+ "version": "7.2.1",
"dependencies": {
"@anthropic-ai/sdk": "^0.39.0",
"@kilocode/kilo-i18n": "workspace:*",
@@ -327,7 +326,7 @@
},
"packages/opencode": {
"name": "@kilocode/cli",
- "version": "7.2.0",
+ "version": "7.2.1",
"bin": {
"kilo": "./bin/kilo",
"kilocode": "./bin/kilo",
@@ -451,7 +450,7 @@
},
"packages/plugin": {
"name": "@kilocode/plugin",
- "version": "7.2.0",
+ "version": "7.2.1",
"dependencies": {
"@kilocode/sdk": "workspace:*",
"zod": "catalog:",
@@ -465,14 +464,14 @@
},
"packages/script": {
"name": "@opencode-ai/script",
- "version": "7.2.0",
+ "version": "7.2.1",
"devDependencies": {
"@types/bun": "catalog:",
},
},
"packages/sdk/js": {
"name": "@kilocode/sdk",
- "version": "7.2.0",
+ "version": "7.2.1",
"devDependencies": {
"@hey-api/openapi-ts": "0.90.10",
"@tsconfig/node22": "catalog:",
@@ -483,7 +482,7 @@
},
"packages/storybook": {
"name": "@opencode-ai/storybook",
- "version": "7.2.0",
+ "version": "7.2.1",
"devDependencies": {
"@opencode-ai/ui": "workspace:*",
"@solidjs/meta": "catalog:",
@@ -506,7 +505,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
- "version": "7.2.0",
+ "version": "7.2.1",
"dependencies": {
"@kilocode/sdk": "workspace:*",
"@kobalte/core": "catalog:",
@@ -553,7 +552,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
- "version": "7.2.0",
+ "version": "7.2.1",
"dependencies": {
"zod": "catalog:",
},
diff --git a/package.json b/package.json
index 654595cb6e9..94da05a7500 100644
--- a/package.json
+++ b/package.json
@@ -122,6 +122,6 @@
"@openrouter/ai-sdk-provider@1.5.4": "patches/@openrouter%2Fai-sdk-provider@1.5.4.patch",
"ghostty-web@0.3.0": "patches/ghostty-web@0.3.0.patch"
},
- "version": "7.2.0",
+ "version": "7.2.1",
"peerDependencies": {}
}
diff --git a/packages/app/package.json b/packages/app/package.json
index b13141ca050..9a2833fd5b4 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
- "version": "7.2.0",
+ "version": "7.2.1",
"description": "",
"type": "module",
"exports": {
diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json
index 37418e6d590..1ede57d19cd 100644
--- a/packages/desktop-electron/package.json
+++ b/packages/desktop-electron/package.json
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop-electron",
"private": true,
- "version": "7.2.0",
+ "version": "7.2.1",
"type": "module",
"license": "MIT",
"homepage": "https://opencode.ai",
diff --git a/packages/desktop/package.json b/packages/desktop/package.json
index 5722e09ba89..ef17c1279c2 100644
--- a/packages/desktop/package.json
+++ b/packages/desktop/package.json
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
- "version": "7.2.0",
+ "version": "7.2.1",
"type": "module",
"license": "MIT",
"scripts": {
diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml
index 95cc4d7dcfb..62ad6a8d4eb 100644
--- a/packages/extensions/zed/extension.toml
+++ b/packages/extensions/zed/extension.toml
@@ -1,7 +1,7 @@
id = "kilo"
name = "Kilo"
description = "The open source coding agent."
-version = "7.2.0"
+version = "7.2.1"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/Kilo-Org/kilocode"
@@ -11,26 +11,26 @@ name = "Kilo"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
-archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.0/opencode-darwin-arm64.zip"
+archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.1/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
-archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.0/opencode-darwin-x64.zip"
+archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.1/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
-archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.0/opencode-linux-arm64.tar.gz"
+archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.1/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
-archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.0/opencode-linux-x64.tar.gz"
+archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.1/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
-archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.0/opencode-windows-x64.zip"
+archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.1/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
diff --git a/packages/kilo-docs/components/ThemeToggle.tsx b/packages/kilo-docs/components/ThemeToggle.tsx
index 24a21c9cc9b..e73ae559556 100644
--- a/packages/kilo-docs/components/ThemeToggle.tsx
+++ b/packages/kilo-docs/components/ThemeToggle.tsx
@@ -58,19 +58,19 @@ export function ThemeToggle() {
if (!mounted) {
return (
)
}
const getIcon = () => {
if (theme === "system") {
- return "💻"
+ return
}
if (theme === "dark") {
- return "🌙"
+ return
}
- return "☀️"
+ return
}
const getLabel = () => {
@@ -86,7 +86,7 @@ export function ThemeToggle() {
return (
<>
>
)
}
+
+function SunIcon() {
+ return (
+
+ )
+}
+
+function MoonIcon() {
+ return (
+
+ )
+}
+
+function SystemIcon() {
+ return (
+
+ )
+}
diff --git a/packages/kilo-docs/lib/nav/code-with-ai.ts b/packages/kilo-docs/lib/nav/code-with-ai.ts
index 69a90dc7786..e1d7b8f039d 100644
--- a/packages/kilo-docs/lib/nav/code-with-ai.ts
+++ b/packages/kilo-docs/lib/nav/code-with-ai.ts
@@ -14,7 +14,11 @@ export const CodeWithAiNav: NavSection[] = [
href: "/code-with-ai/platforms/jetbrains",
children: "JetBrains Extension",
},
- { href: "/code-with-ai/platforms/cli", children: "CLI" },
+ {
+ href: "/code-with-ai/platforms/cli",
+ children: "CLI",
+ subLinks: [{ href: "/code-with-ai/platforms/cli-reference", children: "Command Reference" }],
+ },
{ href: "/code-with-ai/platforms/cloud-agent", children: "Cloud Agent" },
{ href: "/code-with-ai/platforms/mobile", children: "Mobile Apps" },
{ href: "/code-with-ai/platforms/slack", children: "Slack" },
@@ -45,10 +49,6 @@ export const CodeWithAiNav: NavSection[] = [
children: "Custom Models",
platform: "new",
},
- {
- href: "/code-with-ai/agents/free-and-budget-models",
- children: "Free & Budget Models",
- },
{
href: "/code-with-ai/agents/using-agents",
children: "Agents",
diff --git a/packages/kilo-docs/mappingplan.md b/packages/kilo-docs/mappingplan.md
index ce3a63b9b28..f61bd750bfa 100644
--- a/packages/kilo-docs/mappingplan.md
+++ b/packages/kilo-docs/mappingplan.md
@@ -33,7 +33,6 @@
| Using Modes | `basic-usage/using-modes` |
| Orchestrator Mode | `basic-usage/orchestrator-mode` |
| Model Selection | `basic-usage/model-selection-guide` |
-| Free & Budget Models | `advanced-usage/free-and-budget-models` |
| **Features** (subheader) | |
| Autocomplete | `basic-usage/autocomplete/index`, `basic-usage/autocomplete/mistral-setup` |
| Code Actions | `features/code-actions` |
diff --git a/packages/kilo-docs/markdoc/partials/cli-commands-table.md b/packages/kilo-docs/markdoc/partials/cli-commands-table.md
new file mode 100644
index 00000000000..d6437cac497
--- /dev/null
+++ b/packages/kilo-docs/markdoc/partials/cli-commands-table.md
@@ -0,0 +1,25 @@
+
+
+| Command | Description |
+| --- | --- |
+| `kilo acp` | start ACP (Agent Client Protocol) server |
+| `kilo mcp` | manage MCP (Model Context Protocol) servers |
+| `kilo [project]` | start kilo tui |
+| `kilo attach ` | attach to a running kilo server |
+| `kilo run [message..]` | run kilo with a message |
+| `kilo debug` | debugging and troubleshooting tools |
+| `kilo auth` | manage credentials |
+| `kilo agent` | manage agents |
+| `kilo upgrade [target]` | upgrade kilo to the latest or a specific version |
+| `kilo uninstall` | uninstall kilo and remove all related files |
+| `kilo serve` | starts a headless kilo server |
+| `kilo models [provider]` | list all available models |
+| `kilo stats` | show token usage and cost statistics |
+| `kilo export [sessionID]` | export session data as JSON |
+| `kilo import ` | import session data from JSON file or URL |
+| `kilo pr ` | fetch and checkout a GitHub PR branch, then run kilo |
+| `kilo session` | manage sessions |
+| `kilo remote` | enable remote connection for real-time session relay |
+| `kilo db` | database tools |
+| `kilo help [command]` | show full CLI reference |
+| `kilo completion` | generate shell completion script |
diff --git a/packages/kilo-docs/package.json b/packages/kilo-docs/package.json
index c503bf17710..1f3e7678f18 100644
--- a/packages/kilo-docs/package.json
+++ b/packages/kilo-docs/package.json
@@ -1,6 +1,6 @@
{
"name": "@kilocode/kilo-docs",
- "version": "7.2.0",
+ "version": "7.2.1",
"private": true,
"scripts": {
"dev": "next dev --webpack --port 3002",
diff --git a/packages/kilo-docs/pages/automate/agent-manager.md b/packages/kilo-docs/pages/automate/agent-manager.md
index 5e2c539e6a1..c06f9f23099 100644
--- a/packages/kilo-docs/pages/automate/agent-manager.md
+++ b/packages/kilo-docs/pages/automate/agent-manager.md
@@ -31,6 +31,17 @@ The panel opens as an editor tab and stays active across focus changes.
Each Agent Manager session runs in an isolated git worktree on a separate branch, keeping your main branch clean.
+### PR Status Badges
+
+Worktree items in the sidebar display a **PR status badge** when the branch has an associated pull request:
+
+- **Open** — badge indicating the PR is open (its color can also reflect review and check status)
+- **Merged** — purple badge indicating the PR has been merged
+- **Closed** — red badge indicating the PR was closed without merging
+- **Draft** — gray badge indicating the PR is in draft state
+
+The badge appears on the right side of each worktree item and updates automatically via polling. If the worktree's branch doesn't have a PR yet, no badge is shown.
+
### Creating a New Worktree Session
1. Click **New Worktree** or press `Cmd+N` (macOS) / `Ctrl+N` (Windows/Linux) to create a new worktree
diff --git a/packages/kilo-docs/pages/code-with-ai/agents/auto-model.md b/packages/kilo-docs/pages/code-with-ai/agents/auto-model.md
index 69ec4bdc5f0..a7d3fe5aab6 100644
--- a/packages/kilo-docs/pages/code-with-ai/agents/auto-model.md
+++ b/packages/kilo-docs/pages/code-with-ai/agents/auto-model.md
@@ -132,4 +132,4 @@ Auto Model is actively being improved. We'd love to hear how it's working for yo
- [Model Selection Guide](/docs/code-with-ai/agents/model-selection) - General guidance on choosing models
- [Using Agents](/docs/code-with-ai/agents/using-agents) - Learn about different Kilo Code agents
-- [Free & Budget Models](/docs/code-with-ai/agents/free-and-budget-models) - Cost-effective alternatives
+- [Using Kilo for Free](/docs/getting-started/using-kilo-for-free) - Cost-effective alternatives
diff --git a/packages/kilo-docs/pages/code-with-ai/agents/free-and-budget-models.md b/packages/kilo-docs/pages/code-with-ai/agents/free-and-budget-models.md
deleted file mode 100644
index ce24af58ff3..00000000000
--- a/packages/kilo-docs/pages/code-with-ai/agents/free-and-budget-models.md
+++ /dev/null
@@ -1,290 +0,0 @@
----
-title: Free and Budget Models
-description: Learn how to use Kilo Code effectively while minimizing or eliminating costs through free models, budget-friendly alternatives, and smart usage strategies.
----
-
-# Free and Budget Models
-
-**Why this matters:** AI model costs can add up quickly during development. This guide shows you how to use Kilo Code effectively while minimizing or eliminating costs through free models, budget-friendly alternatives, and smart usage strategies.
-
-## Completely Free Options
-
-### Kilo Gateway Free Models
-
-From time to time, Kilo works with AI inference providers to offer free models. These are available through the Kilo Gateway. Currently, we are offering these free models:
-
-- **MiniMax M2.1 (free)** - A capable model from MiniMax with strong general-purpose performance.
-- **Z.AI: GLM 4.7 (free)** - Latest variant of the GLM family, purpose-built for agent-centric applications.
-- **MoonshotAI: Kimi K2.5 (free)** - Optimized for agentic capabilities, including advanced tool use, reasoning, and code synthesis.
-- **Giga Potato (free)** - A stealth release model that is free in its evaluation period.
-- **Arcee AI: Trinity Large Preview (free)** - A preview model from Arcee AI with strong capabilities.
-
-### OpenRouter Free Tier Models
-
-OpenRouter offers several models with generous free tiers. **Note:** You'll need to create a free OpenRouter account to access these models.
-
-**Setup:**
-
-1. Create a free [OpenRouter account](https://openrouter.ai)
-2. Get your API key from the dashboard
-3. Configure Kilo Code with the OpenRouter provider
-
-**Available free models:**
-
-- **Qwen3 Coder (free)** - Optimized for agentic coding tasks such as function calling, tool use, and long-context reasoning over repositories.
-- **Z.AI: GLM 4.5 Air (free)** - Lightweight variant of the GLM-4.5 family, purpose-built for agent-centric applications.
-- **DeepSeek: R1 0528 (free)** - Performance on par with OpenAI o1, but open-sourced and with fully open reasoning tokens.
-- **MoonshotAI: Kimi K2 (free)** - Optimized for agentic capabilities, including advanced tool use, reasoning, and code synthesis.
-
-## Cost-Effective Premium Models
-
-When you need more capability than free models provide, these options deliver excellent value:
-
-### Ultra-Budget Champions (Under $0.50 per million tokens)
-
-**Mistral Devstral Small**
-
-- **Cost:** ~$0.20 per million input tokens
-- **Best for:** Code generation, debugging, refactoring
-- **Performance:** 85% of premium model capability at 10% of the cost
-
-**Llama 4 Maverick**
-
-- **Cost:** ~$0.30 per million input tokens
-- **Best for:** Complex reasoning, architecture planning
-- **Performance:** Excellent for most development tasks
-
-**DeepSeek v3**
-
-- **Cost:** ~$0.27 per million input tokens
-- **Best for:** Code analysis, large codebase understanding
-- **Performance:** Strong technical reasoning
-
-### Mid-Range Value Models ($0.50-$2.00 per million tokens)
-
-**Qwen3 235B**
-
-- **Cost:** ~$1.20 per million input tokens
-- **Best for:** Complex projects requiring high accuracy
-- **Performance:** Near-premium quality at 40% of the cost
-
-## Smart Usage Strategies
-
-### The 50% Rule
-
-**Principle:** Use budget models for 50% of your tasks, premium models for the other 50%.
-
-**Budget model tasks:**
-
-- Code reviews and analysis
-- Documentation writing
-- Simple bug fixes
-- Boilerplate generation
-- Refactoring existing code
-
-**Premium model tasks:**
-
-- Complex architecture decisions
-- Debugging difficult issues
-- Performance optimization
-- New feature design
-- Critical production code
-
-### Context Management for Cost Savings
-
-**Minimize context size:**
-
-```typescript
-// Instead of mentioning entire files
-@src/components/UserProfile.tsx
-
-// Mention specific functions or sections
-@src/components/UserProfile.tsx:45-67
-```
-
-**Reuse context effectively:**
-
-- Keep key project notes in your repository (e.g., a AGENTS.md or docs folder)
-- Reduces need to re-explain project details
-- Saves tokens per conversation
-
-**Strategic file mentions:**
-
-- Only include files directly relevant to the task
-- Use [`@folder/`](/docs/code-with-ai/agents/context-mentions) for broad context, specific files for targeted work
-
-### Model Switching Strategies
-
-**Start cheap, escalate when needed:**
-
-1. **Begin with free models** (Qwen3 Coder, GLM-4.5-Air)
-2. **Switch to budget models** if free models struggle
-3. **Escalate to premium models** only for complex tasks
-
-**Use API Configuration Profiles:**
-
-- Set up [multiple profiles](/docs/ai-providers) for different cost tiers
-- Quick switching between free, budget, and premium models
-- Match model capability to task complexity
-
-### Mode-Based Cost Optimization
-
-**Use appropriate modes to limit expensive operations:**
-
-- **[Ask Agent](/docs/code-with-ai/agents/using-agents#ask):** Information gathering without code changes
-- **[Plan Agent](/docs/code-with-ai/agents/using-agents#plan):** Planning without expensive file operations
-- **[Debug Agent](/docs/code-with-ai/agents/using-agents#debug):** Focused troubleshooting
-
-**Custom modes for budget control:**
-
-- Create modes that restrict expensive tools
-- Limit file access to specific directories
-- Control which operations are auto-approved
-
-## Real-World Performance Comparisons
-
-### Code Generation Tasks
-
-**Simple function creation:**
-
-- **Mistral Devstral Small:** 95% success rate
-- **GPT-4:** 98% success rate
-- **Cost difference:** Free vs $0.20 vs $30 per million tokens
-
-**Complex refactoring:**
-
-- **Budget models:** 70-80% success rate
-- **Premium models:** 90-95% success rate
-- **Recommendation:** Start with budget, escalate if needed
-
-### Debugging Performance
-
-**Simple bugs:**
-
-- **Free models:** Usually sufficient
-- **Budget models:** Excellent performance
-- **Premium models:** Overkill for most cases
-
-**Complex system issues:**
-
-- **Free models:** 40-60% success rate
-- **Budget models:** 60-80% success rate
-- **Premium models:** 85-95% success rate
-
-## Hybrid Approach Recommendations
-
-### Daily Development Workflow
-
-**Morning planning session:**
-
-- Use **Architect mode** with **DeepSeek R1**
-- Plan features and architecture
-- Create task breakdowns
-
-**Implementation phase:**
-
-- Use **Code mode** with **budget models**
-- Generate and modify code
-- Handle routine development tasks
-
-**Complex problem solving:**
-
-- Switch to **premium models** when stuck
-- Use for critical debugging
-- Architecture decisions affecting multiple systems
-
-### Project Phase Strategy
-
-**Early development:**
-
-- Free and budget models for prototyping
-- Rapid iteration without cost concerns
-- Establish patterns and structure
-
-**Production preparation:**
-
-- Premium models for critical code review
-- Performance optimization
-- Security considerations
-
-## Cost Monitoring and Control
-
-### Track Your Usage
-
-**Monitor credit consumption:**
-
-- Check cost estimates in chat history
-- Review monthly usage patterns
-- Identify high-cost operations
-
-**Set spending limits:**
-
-- Use provider billing alerts
-- Configure [provider rate limits](/docs/ai-providers) to control usage
-- Set daily/monthly budgets
-
-### Cost-Saving Tips
-
-**Reduce system prompt size:**
-
-- [Disable MCP](/docs/automate/mcp/using-in-kilo-code) if not using external tools
-- Use focused custom modes
-- Minimize unnecessary context
-
-**Optimize conversation length:**
-
-- Use [Checkpoints](/docs/code-with-ai/features/checkpoints) to reset context
-- Start fresh conversations for unrelated tasks
-- Archive completed work
-
-**Batch similar tasks:**
-
-- Group related code changes
-- Handle multiple files in single requests
-- Reduce conversation overhead
-
-## Getting Started with Budget Models
-
-### Quick Setup Guide
-
-1. **Create OpenRouter account** for free models
-2. **Configure multiple providers** in Kilo Code
-3. **Set up API Configuration Profiles** for easy switching
-4. **Escalate to budget models** when needed
-5. **Reserve premium models** for complex work
-
-### Recommended Provider Mix
-
-**Free tier foundation:**
-
-- [OpenRouter](/docs/ai-providers/openrouter) - Free models
-- [Groq](/docs/ai-providers/groq) - Fast inference for supported models
-- [Z.ai](https://z.ai/model-api) - Provides a free model GLM-4.5-Flash
-
-**Budget tier options:**
-
-- [DeepSeek](/docs/ai-providers/deepseek) - Excellent value models
-- [Mistral](/docs/ai-providers/mistral) - Specialized coding models
-
-**Premium tier backup:**
-
-- [Anthropic](/docs/ai-providers/anthropic) - Claude for complex reasoning
-- [OpenAI](/docs/ai-providers/openai) - GPT-4 for critical tasks
-
-## Measuring Success
-
-**Track these metrics:**
-
-- Monthly AI costs vs. development productivity
-- Task completion rates by model tier
-- Time saved vs. money spent
-- Code quality improvements
-
-**Success indicators:**
-
-- 70%+ of tasks completed with free/budget models
-- Monthly costs under your target budget
-- Maintained or improved code quality
-- Faster development cycles
-
-By combining free models, strategic budget model usage, and smart optimization techniques, you can harness the full power of AI-assisted development while keeping costs minimal. Start with free options and gradually incorporate budget models as your needs and comfort with costs grow.
diff --git a/packages/kilo-docs/pages/code-with-ai/index.md b/packages/kilo-docs/pages/code-with-ai/index.md
index 2e229d00fdc..9ec66e7591a 100644
--- a/packages/kilo-docs/pages/code-with-ai/index.md
+++ b/packages/kilo-docs/pages/code-with-ai/index.md
@@ -38,7 +38,6 @@ Kilo uses specialized agents to help with different tasks:
- [**Model Selection**](/docs/code-with-ai/agents/model-selection) — Choose the right AI model for each task
- [**Context Mentions**](/docs/code-with-ai/agents/context-mentions) — Reference files, functions, and symbols
- [**Orchestrator Mode**](/docs/code-with-ai/agents/orchestrator-mode) — Legacy orchestration (now built into all agents)
-- [**Free & Budget Models**](/docs/code-with-ai/agents/free-and-budget-models) — Cost-effective AI options
## Features
diff --git a/packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md b/packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md
new file mode 100644
index 00000000000..ed944d4407a
--- /dev/null
+++ b/packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md
@@ -0,0 +1,816 @@
+---
+title: "CLI Command Reference"
+description: "Complete reference for all Kilo CLI commands and subcommands"
+---
+
+# CLI Command Reference
+
+
+
+## kilo acp
+
+```
+start ACP (Agent Client Protocol) server
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ --port port to listen on [number] [default: 0]
+ --hostname hostname to listen on [string] [default: "127.0.0.1"]
+ --mdns enable mDNS service discovery (defaults hostname to 0.0.0.0) [boolean] [default: false]
+ --mdns-domain custom domain name for mDNS service (default: kilo.local) [string] [default: "kilo.local"]
+ --cors additional domains to allow for CORS [array] [default: []]
+ --cwd working directory [string] [default: "."]
+```
+
+## kilo mcp
+
+```
+manage MCP (Model Context Protocol) servers
+
+Commands:
+ kilo mcp add add an MCP server
+ kilo mcp list list MCP servers and their status [aliases: ls]
+ kilo mcp auth [name] authenticate with an OAuth-enabled MCP server
+ kilo mcp logout [name] remove OAuth credentials for an MCP server
+ kilo mcp debug debug OAuth connection for an MCP server
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo mcp add
+
+```
+add an MCP server
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo mcp list
+
+```
+list MCP servers and their status
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo mcp auth
+
+```
+authenticate with an OAuth-enabled MCP server
+
+Commands:
+ kilo mcp auth list list OAuth-capable MCP servers and their auth status [aliases: ls]
+
+Positionals:
+ name name of the MCP server [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo mcp auth list
+
+```
+list OAuth-capable MCP servers and their auth status
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo mcp logout
+
+```
+remove OAuth credentials for an MCP server
+
+Positionals:
+ name name of the MCP server [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo mcp debug
+
+```
+debug OAuth connection for an MCP server
+
+Positionals:
+ name name of the MCP server [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+## kilo [project]
+
+```
+start kilo tui
+
+Positionals:
+ project path to start kilo in [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ --port port to listen on [number] [default: 0]
+ --hostname hostname to listen on [string] [default: "127.0.0.1"]
+ --mdns enable mDNS service discovery (defaults hostname to 0.0.0.0) [boolean] [default: false]
+ --mdns-domain custom domain name for mDNS service (default: kilo.local) [string] [default: "kilo.local"]
+ --cors additional domains to allow for CORS [array] [default: []]
+ -m, --model model to use in the format of provider/model [string]
+ -c, --continue continue the last session [boolean]
+ -s, --session session id to continue [string]
+ --fork fork the session when continuing (use with --continue or --session) [boolean]
+ --cloud-fork fetch session from cloud and continue locally (use with --session) [boolean]
+ --prompt prompt to use [string]
+ --agent agent to use [string]
+```
+
+## kilo attach
+
+```
+attach to a running kilo server
+
+Positionals:
+ url http://localhost:4096 [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ --dir directory to run in [string]
+ -c, --continue continue the last session [boolean]
+ -s, --session session id to continue [string]
+ --fork fork the session when continuing (use with --continue or --session) [boolean]
+ --cloud-fork fetch session from cloud and continue locally (use with --session) [boolean]
+ -p, --password basic auth password (defaults to KILO_SERVER_PASSWORD) [string]
+```
+
+## kilo run
+
+```
+run kilo with a message
+
+Positionals:
+ message message to send [string] [default: []]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ --command the command to run, use message for args [string]
+ -c, --continue continue the last session [boolean]
+ -s, --session session id to continue [string]
+ --fork fork the session before continuing (requires --continue or --session) [boolean]
+ --cloud-fork fetch session from cloud and continue locally (requires --session) [boolean]
+ --share share the session [boolean]
+ -m, --model model to use in the format of provider/model [string]
+ --agent agent to use [string]
+ --format format: default (formatted) or json (raw JSON events) [string] [choices: "default", "json"] [default: "default"]
+ -f, --file file(s) to attach to message [array]
+ --title title for the session (uses truncated prompt if no value provided) [string]
+ --attach attach to a running opencode server (e.g., http://localhost:4096) [string]
+ -p, --password basic auth password (defaults to KILO_SERVER_PASSWORD) [string]
+ --dir directory to run in, path on remote server if attaching [string]
+ --port port for the local server (defaults to random port if no value provided) [number]
+ --variant model variant (provider-specific reasoning effort, e.g., high, max, minimal) [string]
+ --thinking show thinking blocks [boolean] [default: false]
+ --auto auto-approve all permissions (for autonomous/pipeline usage) [boolean] [default: false]
+```
+
+## kilo debug
+
+```
+debugging and troubleshooting tools
+
+Commands:
+ kilo debug config show resolved configuration
+ kilo debug lsp LSP debugging utilities
+ kilo debug rg ripgrep debugging utilities
+ kilo debug file file system debugging utilities
+ kilo debug scrap list all known projects
+ kilo debug skill list all available skills
+ kilo debug snapshot snapshot debugging utilities
+ kilo debug agent show agent configuration details
+ kilo debug paths show global paths (data, config, cache, state)
+ kilo debug wait wait indefinitely (for debugging)
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug config
+
+```
+show resolved configuration
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug lsp
+
+```
+LSP debugging utilities
+
+Commands:
+ kilo debug lsp diagnostics get diagnostics for a file
+ kilo debug lsp symbols search workspace symbols
+ kilo debug lsp document-symbols get symbols from a document
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug lsp diagnostics
+
+```
+get diagnostics for a file
+
+Positionals:
+ file [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug lsp symbols
+
+```
+search workspace symbols
+
+Positionals:
+ query [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug lsp document-symbols
+
+```
+get symbols from a document
+
+Positionals:
+ uri [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug rg
+
+```
+ripgrep debugging utilities
+
+Commands:
+ kilo debug rg tree show file tree using ripgrep
+ kilo debug rg files list files using ripgrep
+ kilo debug rg search search file contents using ripgrep
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug rg tree
+
+```
+show file tree using ripgrep
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ --limit [number]
+```
+
+### kilo debug rg files
+
+```
+list files using ripgrep
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ --query Filter files by query [string]
+ --glob Glob pattern to match files [string]
+ --limit Limit number of results [number]
+```
+
+### kilo debug rg search
+
+```
+search file contents using ripgrep
+
+Positionals:
+ pattern Search pattern [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ --glob File glob patterns [array]
+ --limit Limit number of results [number]
+```
+
+### kilo debug file
+
+```
+file system debugging utilities
+
+Commands:
+ kilo debug file read read file contents as JSON
+ kilo debug file status show file status information
+ kilo debug file list list files in a directory
+ kilo debug file search search files by query
+ kilo debug file tree [dir] show directory tree
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug file read
+
+```
+read file contents as JSON
+
+Positionals:
+ path File path to read [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug file status
+
+```
+show file status information
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug file list
+
+```
+list files in a directory
+
+Positionals:
+ path File path to list [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug file search
+
+```
+search files by query
+
+Positionals:
+ query Search query [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug file tree
+
+```
+show directory tree
+
+Positionals:
+ dir Directory to tree [string] [default: "."]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug scrap
+
+```
+list all known projects
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug skill
+
+```
+list all available skills
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug snapshot
+
+```
+snapshot debugging utilities
+
+Commands:
+ kilo debug snapshot track track current snapshot state
+ kilo debug snapshot patch show patch for a snapshot hash
+ kilo debug snapshot diff show diff for a snapshot hash
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug snapshot track
+
+```
+track current snapshot state
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug snapshot patch
+
+```
+show patch for a snapshot hash
+
+Positionals:
+ hash hash [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug snapshot diff
+
+```
+show diff for a snapshot hash
+
+Positionals:
+ hash hash [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug agent
+
+```
+show agent configuration details
+
+Positionals:
+ name Agent name [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ --tool Tool id to execute [string]
+ --params Tool params as JSON or a JS object literal [string]
+```
+
+### kilo debug paths
+
+```
+show global paths (data, config, cache, state)
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo debug wait
+
+```
+wait indefinitely (for debugging)
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+## kilo auth
+
+```
+manage credentials
+
+Commands:
+ kilo auth login [url] log in to a provider
+ kilo auth logout log out from a configured provider
+ kilo auth list list providers [aliases: ls]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo auth login
+
+```
+log in to a provider
+
+Positionals:
+ url kilo auth provider [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ -p, --provider provider id or name to log in to (skips provider selection) [string]
+ -m, --method login method label (skips method selection) [string]
+```
+
+### kilo auth logout
+
+```
+log out from a configured provider
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo auth list
+
+```
+list providers
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+## kilo agent
+
+```
+manage agents
+
+Commands:
+ kilo agent create create a new agent
+ kilo agent list list all available agents
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo agent create
+
+```
+create a new agent
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ --path directory path to generate the agent file [string]
+ --description what the agent should do [string]
+ --mode agent mode [string] [choices: "all", "primary", "subagent"]
+ --tools comma-separated list of tools to enable (default: all). Available: "bash, read, write, edit, list, glob, grep, webfetch, task, todowrite, todoread" [string]
+ -m, --model model to use in the format of provider/model [string]
+```
+
+### kilo agent list
+
+```
+list all available agents
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+## kilo upgrade
+
+```
+upgrade kilo to the latest or a specific version
+
+Positionals:
+ target version to upgrade to, for ex '0.1.48' or 'v0.1.48' [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ -m, --method installation method to use [string] [choices: "curl", "npm", "pnpm", "bun", "brew", "choco", "scoop"]
+```
+
+## kilo uninstall
+
+```
+uninstall kilo and remove all related files
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ -c, --keep-config keep configuration files [boolean] [default: false]
+ -d, --keep-data keep session data and snapshots [boolean] [default: false]
+ --dry-run show what would be removed without removing [boolean] [default: false]
+ -f, --force skip confirmation prompts [boolean] [default: false]
+```
+
+## kilo serve
+
+```
+starts a headless kilo server
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ --port port to listen on [number] [default: 0]
+ --hostname hostname to listen on [string] [default: "127.0.0.1"]
+ --mdns enable mDNS service discovery (defaults hostname to 0.0.0.0) [boolean] [default: false]
+ --mdns-domain custom domain name for mDNS service (default: kilo.local) [string] [default: "kilo.local"]
+ --cors additional domains to allow for CORS [array] [default: []]
+```
+
+## kilo models
+
+```
+list all available models
+
+Positionals:
+ provider provider ID to filter models by [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ --verbose use more verbose model output (includes metadata like costs) [boolean]
+ --refresh refresh the models cache from models.dev [boolean]
+```
+
+## kilo stats
+
+```
+show token usage and cost statistics
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ --days show stats for the last N days (default: all time) [number]
+ --tools number of tools to show (default: all) [number]
+ --models show model statistics (default: hidden). Pass a number to show top N, otherwise shows all
+ --project filter by project (default: all projects, empty string: current project) [string]
+```
+
+## kilo export
+
+```
+export session data as JSON
+
+Positionals:
+ sessionID session id to export [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+## kilo import
+
+```
+import session data from JSON file or URL
+
+Positionals:
+ file path to JSON file or share URL [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+## kilo pr
+
+```
+fetch and checkout a GitHub PR branch, then run kilo
+
+Positionals:
+ number PR number to checkout [number]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+## kilo session
+
+```
+manage sessions
+
+Commands:
+ kilo session list list sessions
+ kilo session delete delete a session
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo session list
+
+```
+list sessions
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ -n, --max-count limit to N most recent sessions [number]
+ --format output format [string] [choices: "table", "json"] [default: "table"]
+```
+
+### kilo session delete
+
+```
+delete a session
+
+Positionals:
+ sessionID session ID to delete [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+## kilo remote
+
+```
+enable remote connection for real-time session relay
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+## kilo db
+
+```
+database tools
+
+Commands:
+ kilo db [query] open an interactive sqlite3 shell or run a query [default]
+ kilo db path print the database path
+ kilo db migrate migrate JSON data to SQLite (merges with existing data)
+
+Positionals:
+ query SQL query to execute [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ --format Output format [string] [choices: "json", "tsv"] [default: "tsv"]
+```
+
+### kilo db path
+
+```
+print the database path
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+### kilo db migrate
+
+```
+migrate JSON data to SQLite (merges with existing data)
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+```
+
+## kilo help
+
+```
+show full CLI reference
+
+Positionals:
+ command command to show help for [string]
+
+Options:
+ --help Show help [boolean]
+ --version Show version number [boolean]
+ --all show help for all commands [boolean] [default: false]
+ --format output format [string] [choices: "md", "text"] [default: "md"]
+```
diff --git a/packages/kilo-docs/pages/code-with-ai/platforms/cli.md b/packages/kilo-docs/pages/code-with-ai/platforms/cli.md
index 1ee5cbfe61d..ed6ff494c9c 100644
--- a/packages/kilo-docs/pages/code-with-ai/platforms/cli.md
+++ b/packages/kilo-docs/pages/code-with-ai/platforms/cli.md
@@ -61,27 +61,9 @@ Or use npm:
### Top-Level CLI Commands
-| Command | Description |
-| ------------------------- | ------------------------------------------ |
-| `kilo [project]` | Start the TUI (Terminal User Interface) |
-| `kilo run [message..]` | Run with a message (non-interactive mode) |
-| `kilo attach ` | Attach to a running kilo server |
-| `kilo serve` | Start a headless server |
-| `kilo web` | Start server and open web interface |
-| `kilo auth` | Manage credentials (login, logout, list) |
-| `kilo agent` | Manage agents (create, list) |
-| `kilo mcp` | Manage MCP servers (list, add, auth) |
-| `kilo models [provider]` | List available models |
-| `kilo stats` | Show token usage and cost statistics |
-| `kilo session` | Manage sessions (list) |
-| `kilo export [sessionID]` | Export session data as JSON |
-| `kilo import ` | Import session data from JSON file or URL |
-| `kilo upgrade [target]` | Upgrade kilo to latest or specific version |
-| `kilo uninstall` | Uninstall kilo and remove related files |
-| `kilo pr ` | Fetch and checkout a GitHub PR branch |
-| `kilo github` | Manage GitHub agent (install, run) |
-| `kilo debug` | Debugging and troubleshooting tools |
-| `kilo completion` | Generate shell completion script |
+{% partial file="cli-commands-table.md" /%}
+
+For detailed help on every command and subcommand, see the [CLI Command Reference](/docs/code-with-ai/platforms/cli-reference).
### Global Options
@@ -139,10 +121,11 @@ Or use npm:
#### Kilo Gateway Commands (when connected)
-| Command | Aliases | Description |
-| ---------- | ------------------------ | --------------------------------- |
-| `/profile` | `/me`, `/whoami` | View your Kilo Gateway profile |
-| `/teams` | `/team`, `/org`, `/orgs` | Switch between Kilo Gateway teams |
+| Command | Aliases | Description |
+| ---------- | ------------------------ | ----------------------------------------- |
+| `/profile` | `/me`, `/whoami` | View your Kilo Gateway profile |
+| `/teams` | `/team`, `/org`, `/orgs` | Switch between Kilo Gateway teams |
+| `/remote` | - | Toggle remote mode for Cloud Agent access |
#### Built-in Commands
@@ -460,6 +443,44 @@ kilo --continue
- Cannot be used with a prompt argument
- Only works when there's at least one previous session in the workspace
+## Remote Connections
+
+Remote Connections let you access your local CLI sessions from the Cloud Agents web interface. Requires [Kilo Gateway](/docs/gateway) connection.
+
+### Enabling Remote Mode
+
+**Toggle during a session:**
+
+```
+/remote
+```
+
+Requires connection to Kilo Gateway. The `/remote` command appears only when authenticated.
+
+**Enable by default:**
+
+Add to `~/.config/kilo/config.json`:
+
+```json
+{
+ "remote_control": true
+}
+```
+
+### Using Remote Mode
+
+Once enabled, start a CLI session and open [Cloud Agents](https://app.kilo.ai/cloud). Your local session appears in the dashboard. See [Cloud Agent Remote Connections](/docs/code-with-ai/platforms/cloud-agent#remote-connections) for details.
+
+### Requirements
+
+- Connection to Kilo Gateway
+- Same Kilo account on CLI and Cloud Agent
+- CLI must remain running with internet connection
+
+{% callout type="warning" title="Security Warning" %}
+Anyone with access to your Kilo account can send messages to your computer when remote mode is enabled.
+{% /callout %}
+
## Environment Variable Overrides
The CLI supports overriding config values with environment variables. The supported environment variables are:
@@ -482,6 +503,6 @@ Your selection is persisted locally so it carries over to future sessions.
There is no `--org` or `--team` flag on `kilo run`. Instead, the organization is determined from the following sources, in order of priority (highest first):
-1. **`KILO_ORG_ID` environment variable** — Best for non-interactive and CI environments.
+1. **`KILO_ORG_ID` environment variable** — Best for non-interactive and CI environments.
2. **`Persisted selection from the last `/teams` pick`** — If you've run an interactive session and selected an organization via `/teams`, that selection is stored in the CLI auth file and reused automatically.
diff --git a/packages/kilo-docs/pages/code-with-ai/platforms/cloud-agent.md b/packages/kilo-docs/pages/code-with-ai/platforms/cloud-agent.md
index fc511e0e225..4e0f2a18bb3 100644
--- a/packages/kilo-docs/pages/code-with-ai/platforms/cloud-agent.md
+++ b/packages/kilo-docs/pages/code-with-ai/platforms/cloud-agent.md
@@ -102,6 +102,33 @@ Cloud Agents support project-level [skills](/docs/code-with-ai/platforms/cli#ski
Global skills (`~/.kilocode/skills/`) are not available in Cloud Agents since there is no persistent user home directory.
{% /callout %}
+## Remote Connections
+
+Remote Connections let you access and control local CLI sessions from the Cloud Agents web interface. Your computer handles the compute; the cloud gives you a window into it from any device.
+
+### How It Works
+
+When remote mode is enabled in the CLI, your active local sessions appear in the Cloud Agents dashboard alongside cloud sessions. The connection is two-way:
+
+- **Messages and responses** sync in real-time
+- **Agent questions** appear in both places — answer wherever you are
+- **Permission requests** route to your active connection
+- **Full editing capabilities** work remotely
+
+### Enabling Remote Mode
+
+Remote mode must be enabled from the CLI. See [CLI Remote Connections](/docs/code-with-ai/platforms/cli#remote-connections) for setup instructions.
+
+### Requirements
+
+- Same Kilo account on both CLI and Cloud Agent
+- Active internet connection on the local machine
+- CLI must remain running
+
+{% callout type="warning" title="Security Warning" %}
+Anyone with access to your Kilo account can send messages to your computer when remote mode is enabled.
+{% /callout %}
+
## Perfect For
Cloud Agents are great for:
diff --git a/packages/kilo-docs/pages/code-with-ai/platforms/vscode/whats-new.md b/packages/kilo-docs/pages/code-with-ai/platforms/vscode/whats-new.md
index 408d4e69652..1ed95d5f199 100644
--- a/packages/kilo-docs/pages/code-with-ai/platforms/vscode/whats-new.md
+++ b/packages/kilo-docs/pages/code-with-ai/platforms/vscode/whats-new.md
@@ -37,7 +37,13 @@ See [Auto-Approving Actions](/docs/getting-started/settings/auto-approving-actio
### Is the context progress graph still available?
-The context progress graph will be [added soon](https://github.com/Kilo-Org/kilocode/issues/8210) for users who like to see it.
+Yes — the context progress graph (also known as the task timeline) is now available. It appears at the top of the chat panel and shows:
+
+- **Timeline bars** — colored bars representing session activity (different colors for read, write, tool, error, and text parts)
+- **Context window progress** — a three-segment bar showing used, reserved, and available tokens, with a visual indicator when usage exceeds 50%
+- **Token breakdown** — input, output, cache writes, and cache reads display
+
+You can expand or collapse the graph — your preference is saved in the `kilo-code.new.showTaskTimeline` setting.
### I like to closely monitor and approve the behavior of the agent. How can I do that better in the new version?
diff --git a/packages/kilo-docs/pages/getting-started/using-kilo-for-free.md b/packages/kilo-docs/pages/getting-started/using-kilo-for-free.md
index 4f6a70d9f63..00a189c0ebd 100644
--- a/packages/kilo-docs/pages/getting-started/using-kilo-for-free.md
+++ b/packages/kilo-docs/pages/getting-started/using-kilo-for-free.md
@@ -1,92 +1,77 @@
---
title: "Using Kilo for Free"
-description: "Learn how to use Kilo Code without spending money by configuring free models for agentic tasks, autocomplete, and CLI background tasks"
+description: "How to use Kilo Code for free — Auto Model Free, finding free models, free autocomplete, and free background tasks"
---
# Using Kilo for Free
-Kilo Code can be used completely free of charge, but you need to understand where Kilo uses AI models and configure each one appropriately.
+Kilo Code can be used completely free of charge. There are three places where Kilo uses AI model inference, and each can be configured to use free models.
-## When Kilo Uses Model Inference
+## Where Kilo Uses Models
-Kilo uses AI model inference in three places:
+1. **Agentic interactions** — Conversations with coding agents in IDE extensions (VS Code, JetBrains), CLI, and cloud services like App Builder and Code Reviewer
+2. **Autocomplete** — In-editor code completions as you type (IDE extensions only)
+3. **Background tasks** — Automatic session titles and context summarization
-1. **Agentic interactions** - Coding assistant conversations in IDE extensions (VS Code, JetBrains), CLI, and cloud services like App Builder and Code Reviewer
-2. **Autocomplete** - In-editor code completions as you type (IDE extensions only)
-3. **CLI Background tasks** - Automatic session titles and context summarization (CLI only)
-
-Each of these can consume credits by default. **For a completely free Kilo experience, you must configure all three to use free models.**
+Each of these consumes credits by default. **To use Kilo entirely for free, configure all three to use free models.**
## Free Agentic Usage
-Kilo Code provides access to [free models](/docs/code-with-ai/agents/free-and-budget-models) for your coding tasks through the Kilo Gateway and partner providers.
+Kilo provides free models for coding tasks through the Kilo Gateway and partner providers.
-### Finding Free Models
+### Auto Model Free
-Free models are clearly labeled in the model picker across all Kilo platforms. To find and use them:
+The easiest way to get started is [**Auto Model Free**](/docs/code-with-ai/agents/auto-model) (`kilo-auto/free`). This is a Kilo-provided model tier that automatically routes your requests to the best available free models — no configuration needed.
+
+### Finding Other Free Models
+
+You can also browse and select individual free models. In the model picker, type `free` to filter the list — free models are clearly labeled across all platforms.
**In the IDE Extensions (VS Code, JetBrains):**
1. Click on the current model below the chat window
-2. Browse the model list—free models are labeled as "(free)"
-3. Select your preferred free model
+2. Type `free` in the search box
+3. Select any model labeled "(free)"
**In the CLI:**
-1. Open the CLI by running `kilo`
-2. Use the `/models` command to browse available models
-3. Free models are labeled as "free"
-4. Select a free model for your tasks
+1. Run `kilo` to open the CLI
+2. Use the `/models` command
+3. Type `free` to filter the list
-### Free Models for Cloud Tasks
+{% callout type="note" %}
+Some free models may be rate limited by the upstream provider. If you hit a rate limit, try switching to a different free model.
+{% /callout %}
-Kilo's cloud services—including App Builder, Code Reviewer, and other cloud-based features—also support free models. When configuring a cloud task:
+### Cloud Tasks
-1. Look for the model selection dropdown
-2. Free models are labeled as "(free)" in the dropdown
-3. Select any free model to avoid using credits
+Kilo's cloud services — App Builder, Code Reviewer, and others — also support free models. Select any model labeled "(free)" in the model dropdown when configuring a cloud task.
{% callout type="tip" %}
-The available free models change over time as Kilo partners with different AI inference providers. Check our [free and budget models guide](/docs/code-with-ai/agents/free-and-budget-models) for the latest options, and subscribe to our blog or join our Discord for updates.
+Available free models change over time as Kilo partners with different inference providers. Subscribe to our blog or join our [Discord](https://kilo.ai/discord) for updates.
{% /callout %}
## Free Autocomplete
-Kilo Code's autocomplete feature provides AI-powered code completions as you type in the IDE extensions.
+Kilo's autocomplete feature provides AI-powered code completions as you type in IDE extensions.
-### Default Behavior
-
-By default, autocomplete is routed through the Kilo Code provider and uses credits from your account.
-
-### If You Don't Have Credits
-
-If you run out of credits and haven't configured a free alternative, autocomplete will stop working. Your main coding workflow won't be affected -- you just won't get AI-powered completions.
+By default, autocomplete routes through the Kilo provider and uses credits. If you run out of credits without a free alternative configured, autocomplete stops working — but your main coding workflow is unaffected.
### How to Get It Free
-Add your own Mistral Codestral API key via **BYOK (Bring Your Own Key)** on the Kilo Gateway. Mistral offers a free tier for Codestral, and when you configure a BYOK key, autocomplete requests are routed using your key — billed directly by Mistral at $0 on your Kilo balance.
+Add your own Mistral AI (Codestral) API key via **BYOK (Bring Your Own Key)** on the Kilo Gateway. Mistral offers a free tier for Codestral. When you configure a BYOK key, autocomplete requests use your key directly — at no cost on your Kilo balance.
-For step-by-step instructions, see our [Mistral Setup Guide](/docs/code-with-ai/features/autocomplete/mistral-setup).
+See the [Mistral Setup Guide](/docs/code-with-ai/features/autocomplete/mistral-setup) for step-by-step instructions.
-## Free CLI Background Tasks
+## Free Background Tasks
-The Kilo CLI uses AI in the background for quality-of-life features that enhance your experience like context compression and titling sessions.
+Kilo uses a small model in the background for tasks like session titling. By default this is Kilo Auto Small, which consumes credits. If the small model is unavailable, Kilo falls back to your primary model — which may also consume credits if it's a paid model.
-### Default Behavior
+To avoid credit usage for background tasks, set the small model to a free model:
-By default, CLI background tasks use `gpt-5-nano`, which consumes credits.
+**In the VS Code extension:** Go to **Settings → Models** and change the small model to any free model.
-### If You Don't Have Credits
-
-Background tasks degrade gracefully when you don't have credits:
-
-- **Session titles** fall back to truncating your first message instead of generating a smart summary
-- **Context management** uses simple truncation instead of intelligent summarization
-- **Your main workflow continues uninterrupted** - these are convenience features, not requirements
-
-### How to Get It Free
-
-Configure the `small_model` parameter in `~/.config/kilo/config.json` to use a free model:
+**In the CLI:** Set the `small_model` parameter in `~/.config/kilo/config.json`:
```json
{
@@ -94,11 +79,11 @@ Configure the `small_model` parameter in `~/.config/kilo/config.json` to use a f
}
```
-Replace `your-preferred-free-model` with any free model available in the model picker.
+Replace `your-preferred-free-model` with any free model from the model picker.
## Related Resources
-- [Free and Budget Models](/docs/code-with-ai/agents/free-and-budget-models) - Complete guide to free and budget-friendly model options
-- [Mistral Setup Guide](/docs/code-with-ai/features/autocomplete/mistral-setup) - Step-by-step autocomplete setup via BYOK
-- [Autocomplete](/docs/code-with-ai/features/autocomplete) - Full autocomplete documentation
-- [CLI Documentation](/docs/code-with-ai/platforms/cli) - Complete CLI reference
+- [Auto Model](/docs/code-with-ai/agents/auto-model) — Smart model routing including the free tier
+- [Mistral Setup Guide](/docs/code-with-ai/features/autocomplete/mistral-setup) — Free autocomplete via BYOK
+- [Autocomplete](/docs/code-with-ai/features/autocomplete) — Full autocomplete documentation
+- [CLI Documentation](/docs/code-with-ai/platforms/cli) — Complete CLI reference
diff --git a/packages/kilo-docs/pages/kiloclaw/chat-platforms/slack.md b/packages/kilo-docs/pages/kiloclaw/chat-platforms/slack.md
index 4826ef7c3fe..e0e35b525c5 100644
--- a/packages/kilo-docs/pages/kiloclaw/chat-platforms/slack.md
+++ b/packages/kilo-docs/pages/kiloclaw/chat-platforms/slack.md
@@ -47,10 +47,7 @@ You need two tokens from Slack:
## Step 4: Pair Slack with KiloClaw
-1. In Slack, DM the app and type your slash command (e.g., `/claw`) followed by anything — this triggers the pairing flow
-
-> 📝 **Note**
-> The slash command is whatever you defined in the manifest. Any text after the command will work to trigger pairing.
+1. In Slack, DM the app and send any message — this triggers the pairing flow
2. The app will return a pairing code
3. Return to [app.kilocode.ai/claw](https://app.kilocode.ai/claw) and confirm the pairing code and approve
diff --git a/packages/kilo-docs/previous-docs-redirects.js b/packages/kilo-docs/previous-docs-redirects.js
index c6d3804f430..f20702d05fb 100644
--- a/packages/kilo-docs/previous-docs-redirects.js
+++ b/packages/kilo-docs/previous-docs-redirects.js
@@ -691,7 +691,13 @@ module.exports = [
},
{
source: "/docs/advanced-usage/free-and-budget-models",
- destination: "/docs/code-with-ai/agents/free-and-budget-models",
+ destination: "/docs/getting-started/using-kilo-for-free",
+ basePath: false,
+ permanent: true,
+ },
+ {
+ source: "/docs/code-with-ai/agents/free-and-budget-models",
+ destination: "/docs/getting-started/using-kilo-for-free",
basePath: false,
permanent: true,
},
diff --git a/packages/kilo-gateway/package.json b/packages/kilo-gateway/package.json
index 34bc04c675e..14701b21175 100644
--- a/packages/kilo-gateway/package.json
+++ b/packages/kilo-gateway/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-gateway",
- "version": "7.2.0",
+ "version": "7.2.1",
"type": "module",
"license": "MIT",
"description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration",
diff --git a/packages/kilo-i18n/package.json b/packages/kilo-i18n/package.json
index 780eb6d7052..22f32cbb2a5 100644
--- a/packages/kilo-i18n/package.json
+++ b/packages/kilo-i18n/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-i18n",
- "version": "7.2.0",
+ "version": "7.2.1",
"type": "module",
"license": "MIT",
"description": "Kilo-specific i18n translations and overrides",
diff --git a/packages/kilo-telemetry/package.json b/packages/kilo-telemetry/package.json
index 121b2d317a7..16a6bdea84c 100644
--- a/packages/kilo-telemetry/package.json
+++ b/packages/kilo-telemetry/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@kilocode/kilo-telemetry",
- "version": "7.2.0",
+ "version": "7.2.1",
"type": "module",
"license": "MIT",
"description": "Telemetry for Kilo CLI - PostHog analytics integration",
diff --git a/packages/kilo-telemetry/src/events.ts b/packages/kilo-telemetry/src/events.ts
index ddfde6a9a81..c621da8dc0b 100644
--- a/packages/kilo-telemetry/src/events.ts
+++ b/packages/kilo-telemetry/src/events.ts
@@ -25,6 +25,9 @@ export enum TelemetryEvent {
MCP_SERVER_CONNECTED = "MCP Server Connected",
MCP_SERVER_ERROR = "MCP Server Error",
+ // Remote Events
+ REMOTE_CONNECTION_OPENED = "Remote Connection Opened",
+
// Auth Events
AUTH_SUCCESS = "Auth Success",
AUTH_LOGOUT = "Auth Logout",
diff --git a/packages/kilo-telemetry/src/telemetry.ts b/packages/kilo-telemetry/src/telemetry.ts
index 5b353aadcab..5e4379892da 100644
--- a/packages/kilo-telemetry/src/telemetry.ts
+++ b/packages/kilo-telemetry/src/telemetry.ts
@@ -184,6 +184,11 @@ export namespace Telemetry {
track(TelemetryEvent.MCP_SERVER_ERROR, { server, error })
}
+ // Remote
+ export function trackRemoteConnectionOpened() {
+ track(TelemetryEvent.REMOTE_CONNECTION_OPENED)
+ }
+
// Auth
export function trackAuthSuccess(provider: string) {
track(TelemetryEvent.AUTH_SUCCESS, { provider })
diff --git a/packages/kilo-ui/package.json b/packages/kilo-ui/package.json
index 0450b0335f9..d5bd9cf3225 100644
--- a/packages/kilo-ui/package.json
+++ b/packages/kilo-ui/package.json
@@ -1,6 +1,6 @@
{
"name": "@kilocode/kilo-ui",
- "version": "7.2.0",
+ "version": "7.2.1",
"type": "module",
"license": "MIT",
"exports": {
diff --git a/packages/kilo-ui/src/components/diff.tsx b/packages/kilo-ui/src/components/diff.tsx
index a96af21fe11..66c8176cbc3 100644
--- a/packages/kilo-ui/src/components/diff.tsx
+++ b/packages/kilo-ui/src/components/diff.tsx
@@ -142,6 +142,24 @@ export function Diff(props: DiffProps) {
host.removeAttribute("data-color-scheme")
}
+ // Patch a bug in @pierre/diffs where `grid-template-columns: 100% auto` is set
+ // for `line-info-basic` separators under `@media (pointer: fine)`, causing the
+ // expand button to consume 100% of the gutter width and overlap the separator
+ // content text. We inject into `@layer unsafe` which overrides `@layer base`.
+ let separatorPatchSheet: CSSStyleSheet | null = null
+ const patchSeparatorLayout = () => {
+ const root = getRoot()
+ if (!root) return
+ if (!separatorPatchSheet) {
+ separatorPatchSheet = new CSSStyleSheet()
+ separatorPatchSheet.replaceSync(
+ `@layer unsafe { @media (pointer: fine) { [data-separator='line-info-basic'][data-expand-index] [data-separator-wrapper] { grid-template-columns: 34px auto; } } }`,
+ )
+ }
+ if (!root.adoptedStyleSheets.includes(separatorPatchSheet))
+ root.adoptedStyleSheets = [...root.adoptedStyleSheets, separatorPatchSheet]
+ }
+
const lineIndex = (split: boolean, element: HTMLElement) => {
const raw = element.dataset.lineIndex
if (!raw) return
@@ -576,6 +594,7 @@ export function Diff(props: DiffProps) {
})
applyScheme()
+ patchSeparatorLayout()
setRendered((value) => value + 1)
notifyRendered()
diff --git a/packages/kilo-ui/src/components/message-part.css b/packages/kilo-ui/src/components/message-part.css
index 307ae9fccdd..802bea6c7f9 100644
--- a/packages/kilo-ui/src/components/message-part.css
+++ b/packages/kilo-ui/src/components/message-part.css
@@ -19,6 +19,18 @@
height: 20px;
}
}
+
+ [data-slot="assistant-copy-wrapper"] {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ margin-top: 2px;
+
+ [data-component="icon-button"] {
+ width: 20px;
+ height: 20px;
+ }
+ }
}
/* Prevent long title/path from hiding the collapsible expand arrow */
diff --git a/packages/kilo-ui/src/components/message-part.tsx b/packages/kilo-ui/src/components/message-part.tsx
index 187a6a2df21..53eecf21386 100644
--- a/packages/kilo-ui/src/components/message-part.tsx
+++ b/packages/kilo-ui/src/components/message-part.tsx
@@ -1155,6 +1155,7 @@ PART_MAPPING["compaction"] = function CompactionPartDisplay() {
PART_MAPPING["text"] = function TextPartDisplay(props) {
const data = useData()
+ const i18n = useI18n()
const part = () => props.part as TextPart
const displayText = () => (part().text ?? "").trim()
@@ -1166,6 +1167,21 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
return props.turnDiffSummary
})
+ const showCopy = createMemo(() => {
+ if (props.message.role !== "assistant") return false
+ if (props.showAssistantCopyPartID === null) return false
+ return props.showAssistantCopyPartID === part().id
+ })
+ const [copied, setCopied] = createSignal(false)
+
+ const handleCopy = async () => {
+ const content = displayText()
+ if (!content) return
+ await navigator.clipboard.writeText(content)
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
+ }
+
const handleMarkdownClick = (e: MouseEvent) => {
if (!data.openFile) return
const target = e.target
@@ -1200,6 +1216,24 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
+
{(render) => (
@@ -1220,6 +1254,9 @@ const streamed = new Set()
// Tracks parts that have already been auto-collapsed once, so component
// recreation (from store updates while other parts stream) won't collapse again.
const autocollapsed = new Set()
+// Tracks parts that the user has explicitly opened, so auto-collapse won't
+// override the user's intent when reasoning finishes or a tool call starts.
+const userOpened = new Set()
// Overrides upstream flat markdown render with streaming reasoning block + auto-collapse.
// Also filters encrypted reasoning data from OpenRouter that appears as [REDACTED].
@@ -1249,14 +1286,24 @@ PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props: MessagePartProp
// Streaming → open. Just finished (was streaming, now done) → open briefly
// then collapse. Historical → collapsed from the start.
- const [open, setOpen] = createSignal(!done() || was)
+ // Restore user's explicit open preference across component recreations.
+ const [open, setOpen] = createSignal(!done() || was || userOpened.has(id))
+
+ // Propagate user intent to the module-level set so it survives component
+ // recreations (e.g. when a tool call arrives while reading reasoning).
+ const track = (value: boolean) => {
+ if (value) userOpened.add(id)
+ else userOpened.delete(id)
+ setOpen(value)
+ }
// Auto-collapse once when reasoning finishes (streaming → done transition).
// Collapses immediately so the grid transition runs in sync with the
// streaming-height removal. Module-level Set prevents re-triggering on
- // component recreation or when the user manually reopens.
+ // component recreation. Skipped entirely if the user has explicitly opened
+ // the block, so reading is not interrupted by a subsequent tool call.
createEffect(() => {
- if (done() && open() && !autocollapsed.has(id)) {
+ if (done() && open() && !autocollapsed.has(id) && !userOpened.has(id)) {
autocollapsed.add(id)
setOpen(false)
}
@@ -1264,13 +1311,32 @@ PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props: MessagePartProp
onCleanup(() => {
if (done()) streamed.delete(id)
+ // userOpened is intentionally NOT deleted here. The component recreates
+ // frequently while other parts stream (same as autocollapsed), so removing
+ // the entry on unmount would discard the user's explicit preference and
+ // re-collapse the block on the next remount.
})
- // Auto-scroll the content container while streaming
+ // Auto-scroll the content container while streaming.
+ // Use a plain mutable flag rather than checking dist inside the reactive
+ // effect: by the time the effect runs the DOM has already grown, so reading
+ // scrollHeight post-update incorrectly reports the user as scrolled away
+ // whenever a streaming chunk is > 10px tall.
let ref: HTMLDivElement | undefined
+ let scrolled = false
+
+ const onScroll = (e: Event) => {
+ const el = e.currentTarget as HTMLDivElement
+ if (el.scrollHeight - el.clientHeight - el.scrollTop < 10) scrolled = false
+ }
+
+ const onWheel = (e: WheelEvent) => {
+ if (e.deltaY < 0) scrolled = true
+ }
+
createEffect(() => {
display()
- if (!done() && ref) {
+ if (!done() && ref && !scrolled) {
ref.scrollTop = ref.scrollHeight
}
})
@@ -1278,7 +1344,7 @@ PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props: MessagePartProp
return (
-
+
@@ -1287,7 +1353,7 @@ PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props: MessagePartProp
-
+
diff --git a/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-expanded-chromium-linux.png b/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-expanded-chromium-linux.png
index ca56f4c8fad..db854802662 100644
--- a/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-expanded-chromium-linux.png
+++ b/packages/kilo-ui/tests/visual-regression.spec.ts-snapshots/components-messagepart/with-reasoning-expanded-chromium-linux.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:6b0f41c54aca88874a3c74151b77ef20f0f17fa9bb2f149c13adfaf4f48de286
-size 14702
+oid sha256:cc32d99eeff1cb3061caa4a75e3353e30e6373073bbd7ebf17048c917367e11a
+size 28005
diff --git a/packages/kilo-vscode/docs/features/file-attachments.md b/packages/kilo-vscode/docs/features/file-attachments.md
index 6c452631f04..b9e90c02081 100644
--- a/packages/kilo-vscode/docs/features/file-attachments.md
+++ b/packages/kilo-vscode/docs/features/file-attachments.md
@@ -5,6 +5,19 @@
Image attachments and `@file` path mentions work. Non-image file content attachments are missing.
+## Supported Image Types
+
+The following image formats are supported for paste and drag-and-drop:
+
+- PNG (`image/png`)
+- JPEG (`image/jpeg`)
+- GIF (`image/gif`)
+- WebP (`image/webp`)
+
+## Drag-and-Drop (Shift Required)
+
+VS Code disables webview pointer-events during drag operations so it can handle drops in the editor area. To drop images into the chat input, **hold Shift while dragging**. This re-enables the webview to receive drop events (VS Code 1.91+, see [microsoft/vscode#182449](https://github.com/microsoft/vscode/issues/182449)).
+
## Remaining Work
- Add a file attachment button to the chat input toolbar (paperclip icon or similar)
diff --git a/packages/kilo-vscode/esbuild.js b/packages/kilo-vscode/esbuild.js
index 69b79df8544..549e60910ed 100644
--- a/packages/kilo-vscode/esbuild.js
+++ b/packages/kilo-vscode/esbuild.js
@@ -191,23 +191,34 @@ async function main() {
// Build Diff Viewer webview (SolidJS, reuses Agent Manager diff components)
const diffViewerCtx = await createBrowserWebviewContext("webview-ui/diff-viewer/index.tsx", "dist/diff-viewer.js")
+ // Build Diff Virtual webview (lightweight single-file diff for permission approval)
+ const diffVirtualCtx = await createBrowserWebviewContext("webview-ui/diff-virtual/index.tsx", "dist/diff-virtual.js")
+
// Build webview
const webviewCtx = await createBrowserWebviewContext("webview-ui/src/index.tsx", "dist/webview.js")
if (watch) {
- await Promise.all([extensionCtx.watch(), webviewCtx.watch(), agentManagerCtx.watch(), diffViewerCtx.watch()])
+ await Promise.all([
+ extensionCtx.watch(),
+ webviewCtx.watch(),
+ agentManagerCtx.watch(),
+ diffViewerCtx.watch(),
+ diffVirtualCtx.watch(),
+ ])
} else {
await Promise.all([
extensionCtx.rebuild(),
webviewCtx.rebuild(),
agentManagerCtx.rebuild(),
diffViewerCtx.rebuild(),
+ diffVirtualCtx.rebuild(),
])
await Promise.all([
extensionCtx.dispose(),
webviewCtx.dispose(),
agentManagerCtx.dispose(),
diffViewerCtx.dispose(),
+ diffVirtualCtx.dispose(),
])
}
}
diff --git a/packages/kilo-vscode/eslint.config.mjs b/packages/kilo-vscode/eslint.config.mjs
index f82b5a9d931..8c4b597d0c8 100644
--- a/packages/kilo-vscode/eslint.config.mjs
+++ b/packages/kilo-vscode/eslint.config.mjs
@@ -29,13 +29,76 @@ export default [
eqeqeq: "warn",
"no-throw-literal": "warn",
"max-lines": ["error", 3000],
+ complexity: ["error", 20],
},
},
+
+ // ── Complexity exceptions ─────────────────────────────────────────
+ // Existing violations capped at their current max.
+ // New code must stay ≤ 20. Do not raise these caps; refactor instead.
{
files: ["src/KiloProvider.ts"],
- rules: {
- "max-lines": ["error", 3200],
- },
+ rules: { complexity: ["error", 140], "max-lines": ["error", 3300] },
},
+ {
+ files: ["webview-ui/agent-manager/AgentManagerApp.tsx"],
+ rules: { complexity: ["error", 73] },
+ },
+ {
+ files: ["src/agent-manager/AgentManagerProvider.ts"],
+ rules: { complexity: ["error", 63] },
+ },
+ {
+ files: ["webview-ui/src/components/chat/PromptInput.tsx"],
+ rules: { complexity: ["error", 48] },
+ },
+ {
+ files: ["src/legacy-migration/migration-service.ts"],
+ rules: { complexity: ["error", 45] },
+ },
+ {
+ files: ["webview-ui/src/components/migration/MigrationWizard.tsx"],
+ rules: { complexity: ["error", 37] },
+ },
+ {
+ files: ["webview-ui/src/context/session.tsx"],
+ rules: { complexity: ["error", 31] },
+ },
+ {
+ files: ["src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts"],
+ rules: { complexity: ["error", 30] },
+ },
+ {
+ files: ["src/agent-manager/WorktreeManager.ts", "webview-ui/src/components/chat/QuestionDock.tsx"],
+ rules: { complexity: ["error", 28] },
+ },
+ {
+ files: [
+ "src/kilo-provider-utils.ts",
+ "src/services/autocomplete/continuedev/core/autocomplete/postprocessing/index.ts",
+ ],
+ rules: { complexity: ["error", 27] },
+ },
+ {
+ files: ["webview-ui/src/components/settings/CustomProviderDialog.tsx"],
+ rules: { complexity: ["error", 26] },
+ },
+ {
+ files: ["src/agent-manager/WorktreeStateManager.ts"],
+ rules: { complexity: ["error", 24] },
+ },
+ {
+ files: ["webview-ui/src/utils/errorUtils.ts"],
+ rules: { complexity: ["error", 23] },
+ },
+ {
+ files: ["src/services/autocomplete/continuedev/core/autocomplete/filtering/BracketMatchingService.ts"],
+ rules: { complexity: ["error", 22] },
+ },
+ {
+ files: ["webview-ui/src/context/server.tsx"],
+ rules: { complexity: ["error", 21] },
+ },
+
eslintConfigPrettier,
]
diff --git a/packages/kilo-vscode/knip.json b/packages/kilo-vscode/knip.json
index 806593bc571..3e718495627 100644
--- a/packages/kilo-vscode/knip.json
+++ b/packages/kilo-vscode/knip.json
@@ -4,6 +4,7 @@
"src/extension.ts",
"webview-ui/agent-manager/index.tsx",
"webview-ui/diff-viewer/index.tsx",
+ "webview-ui/diff-virtual/index.tsx",
"webview-ui/src/index.tsx",
"src/**/__tests__/**/*.{ts,spec.ts}",
"src/**/*.test.ts",
@@ -11,7 +12,6 @@
"script/*.ts"
],
"project": ["src/**/*.ts", "webview-ui/**/*.{ts,tsx}"],
- "ignore": ["src/services/autocomplete/**"],
"ignoreExportsUsedInFile": true,
"exclude": ["dependencies", "devDependencies", "optionalPeerDependencies", "unlisted", "unresolved", "binaries"]
}
diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json
index 4d01cca1a3e..7d3dadc16b2 100644
--- a/packages/kilo-vscode/package.json
+++ b/packages/kilo-vscode/package.json
@@ -2,7 +2,7 @@
"name": "kilo-code",
"displayName": "Kilo Code: AI Coding Agent, Copilot, and Autocomplete",
"description": "Open Source AI coding agent that generates code from natural language, automates tasks, and runs terminal commands. Features inline autocomplete, browser automation, automated refactoring, and custom modes for planning, coding, and debugging. Supports 500+ AI models including Claude (Anthropic), Gemini, Grok, GPT, Codex and GLM.",
- "version": "7.2.0",
+ "version": "7.2.1",
"icon": "assets/icons/logo-outline-black.png",
"galleryBanner": {
"color": "#FFFFFF",
diff --git a/packages/kilo-vscode/src/DiffVirtualProvider.ts b/packages/kilo-vscode/src/DiffVirtualProvider.ts
new file mode 100644
index 00000000000..b585ee71fdd
--- /dev/null
+++ b/packages/kilo-vscode/src/DiffVirtualProvider.ts
@@ -0,0 +1,107 @@
+import * as vscode from "vscode"
+import { buildWebviewHtml } from "./utils"
+import { appendOutput, getWorkspaceRoot } from "./review-utils"
+
+export interface DiffVirtualFile {
+ file: string
+ before: string
+ after: string
+ additions: number
+ deletions: number
+}
+
+/**
+ * DiffVirtualProvider opens a lightweight diff viewer for a single in-memory
+ * file diff (not backed by git). Used by the permission approval dock to show
+ * edit changes before the user approves or rejects them.
+ */
+export class DiffVirtualProvider implements vscode.Disposable {
+ private panel: vscode.WebviewPanel | undefined
+ private pending: DiffVirtualFile | undefined
+ private outputChannel: vscode.OutputChannel
+
+ constructor(private readonly extensionUri: vscode.Uri) {
+ this.outputChannel = vscode.window.createOutputChannel("Kilo Diff Virtual")
+ }
+
+ private log(...args: unknown[]) {
+ appendOutput(this.outputChannel, "DiffVirtual", ...args)
+ }
+
+ public open(diff: DiffVirtualFile): void {
+ this.pending = diff
+ const filename = diff.file.split("/").pop() ?? diff.file
+ const title = `Changes: ${filename}`
+
+ if (this.panel) {
+ this.panel.title = title
+ this.panel.reveal(vscode.ViewColumn.One)
+ this.pushData()
+ return
+ }
+
+ const panel = vscode.window.createWebviewPanel("kilo-code.new.DiffVirtualPanel", title, vscode.ViewColumn.One, {
+ enableScripts: true,
+ retainContextWhenHidden: true,
+ localResourceRoots: [this.extensionUri],
+ })
+
+ panel.iconPath = {
+ light: vscode.Uri.joinPath(this.extensionUri, "assets", "icons", "kilo-light.svg"),
+ dark: vscode.Uri.joinPath(this.extensionUri, "assets", "icons", "kilo-dark.svg"),
+ }
+
+ panel.webview.html = this.getHtml(panel.webview)
+ panel.webview.onDidReceiveMessage((msg) => this.onMessage(msg))
+ panel.onDidDispose(() => {
+ this.log("Panel disposed")
+ this.panel = undefined
+ this.pending = undefined
+ })
+
+ this.panel = panel
+ }
+
+ private onMessage(msg: Record): void {
+ const type = msg.type as string
+
+ if (type === "webviewReady") {
+ this.post({
+ type: "ready",
+ vscodeLanguage: vscode.env.language,
+ languageOverride: vscode.workspace.getConfiguration("kilo-code.new").get("language"),
+ workspaceDirectory: getWorkspaceRoot(),
+ })
+ this.pushData()
+ return
+ }
+
+ if (type === "diffVirtual.close") {
+ this.panel?.dispose()
+ }
+ }
+
+ private pushData(): void {
+ if (!this.pending) return
+ this.post({ type: "diffVirtual.data", diff: this.pending })
+ }
+
+ private post(message: Record): void {
+ if (this.panel?.webview) void this.panel.webview.postMessage(message)
+ }
+
+ private getHtml(webview: vscode.Webview): string {
+ return buildWebviewHtml(webview, {
+ scriptUri: webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, "dist", "diff-virtual.js")),
+ styleUri: webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, "dist", "diff-virtual.css")),
+ iconsBaseUri: webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, "assets", "icons")),
+ title: "Diff Virtual",
+ extraStyles: "#root { display: flex; flex-direction: column; height: 100%; }",
+ })
+ }
+
+ public dispose(): void {
+ this.panel?.dispose()
+ this.outputChannel.dispose()
+ }
+}
diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts
index 1421a25b0ab..62bc0b9960e 100644
--- a/packages/kilo-vscode/src/KiloProvider.ts
+++ b/packages/kilo-vscode/src/KiloProvider.ts
@@ -35,7 +35,7 @@ import {
import { GitOps } from "./agent-manager/GitOps"
import { GitStatsPoller, type LocalStats } from "./agent-manager/GitStatsPoller"
import { getWorkspaceRoot } from "./review-utils"
-import { MarketplaceService } from "./services/marketplace"
+import { MarketplaceService, type MarketplaceItem, type RemoveResult } from "./services/marketplace"
import { resolveProjectDirectory } from "./project-directory"
import { getBusySessionCount, seedSessionStatuses } from "./session-status"
import { retry } from "./services/cli-backend/retry"
@@ -96,12 +96,26 @@ import {
saveCustomProvider as saveCustomProviderAction,
} from "./provider-actions"
import { fetchOpenAIModels, FetchModelsError } from "./shared/fetch-models"
+import type { Agent } from "@kilocode/sdk/v2/client"
type KiloProviderOptions = {
projectDirectory?: string | null
slimEditMetadata?: boolean
}
+// Helper to map agent data to the subset of fields sent to the webview
+const mapAgent = (a: Agent) => ({
+ name: a.name,
+ displayName: a.displayName,
+ description: a.description,
+ mode: a.mode,
+ native: a.native,
+ hidden: a.hidden,
+ color: a.color,
+ deprecated: a.deprecated,
+ permission: a.permission,
+})
+
export class KiloProvider implements vscode.WebviewViewProvider, TelemetryPropertiesProvider {
public static readonly viewType = "kilo-code.SidebarProvider"
@@ -189,6 +203,8 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
| ((sessionId: string, progress: (status: string, detail?: string, error?: string) => void) => Promise)
| null = null
+ private diffVirtualProvider: import("./DiffVirtualProvider").DiffVirtualProvider | undefined
+
constructor(
private readonly extensionUri: vscode.Uri,
private readonly connectionService: KiloConnectionService,
@@ -207,6 +223,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
this.postMessage({ type: "workspaceDirectoryChanged", directory: directory ?? "" })
}
+ public setDiffVirtualProvider(provider: import("./DiffVirtualProvider").DiffVirtualProvider): void {
+ this.diffVirtualProvider = provider
+ }
+
getTelemetryProperties(): Record {
return {
appName: "kilo-code",
@@ -619,6 +639,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
case "openChanges":
vscode.commands.executeCommand("kilo-code.new.showChanges")
break
+ case "openDiffVirtual":
+ if (this.diffVirtualProvider && message.diff) {
+ this.diffVirtualProvider.open(message.diff)
+ }
+ break
case "continueInWorktree":
if (message.sessionId && this.continueInWorktreeHandler) {
this.continueInWorktreeHandler(message.sessionId, (status: string, detail?: string, error?: string) => {
@@ -982,12 +1007,8 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
break
}
case "removeInstalledMarketplaceItem": {
- const workspace = this.getProjectDirectory(this.currentSession?.id)
const scope = message.mpInstallOptions?.target ?? "project"
- const result = await this.getMarketplace().remove(message.mpItem, scope, workspace)
- if (result.success) {
- await this.invalidateAfterMarketplaceChange(scope)
- }
+ const result = await this.removeMarketplaceItem(message.mpItem, scope)
this.postMessage({
type: "marketplaceRemoveResult",
success: result.success,
@@ -1171,6 +1192,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
])
this.sendNotificationSettings()
this.sendTimelineSetting()
+ this.postMessage({ type: "extensionDataReady" })
// Start polling worktree diff stats for the sidebar badge
this.startStatsPolling()
@@ -1640,15 +1662,8 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
const message = {
type: "agentsLoaded",
- agents: visible.map((a) => ({
- name: a.name,
- displayName: a.displayName,
- description: a.description,
- mode: a.mode,
- native: a.native,
- color: a.color,
- deprecated: a.deprecated,
- })),
+ agents: visible.map(mapAgent),
+ allAgents: agents.map(mapAgent),
defaultAgent,
}
this.cachedAgentsMessage = message
@@ -1762,59 +1777,87 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
*/
private async handleRemoveMode(name: string): Promise {
if (!this.client) return
- let removed = false
// 1. Try CLI removal (handles .md files and legacy .kilocodemodes)
try {
const dir = this.getWorkspaceDirectory()
const result = await this.client.kilocode.removeAgent({ name, directory: dir })
- if (!result.error) removed = true
+ if (!result.error) {
+ this.cachedAgentsMessage = null
+ await this.fetchAndSendAgents()
+ return
+ }
} catch {
// CLI removal failed — agent may be in kilo.json instead
}
// 2. Try removing from kilo.json (handles marketplace-installed modes)
- if (!removed) {
- const workspace = this.getProjectDirectory(this.currentSession?.id)
- const mp = this.getMarketplace()
- const stub = { id: name, type: "mode" as const, name, description: "", content: "" }
- const project = await mp.remove(stub, "project", workspace)
- const global = await mp.remove(stub, "global", workspace)
- if (project.success || global.success) {
- await this.disposeCliInstance("global")
- removed = true
- }
- }
-
+ const stub = { id: name, type: "mode" as const, name, description: "", content: "" }
+ const removed = await this.removeMarketplaceItemFromAllScopes(stub)
if (!removed) {
console.error("[Kilo New] KiloProvider: Failed to remove mode:", name)
}
-
- this.cachedAgentsMessage = null
- await this.fetchAndSendAgents()
}
private async handleRemoveMcp(name: string): Promise {
- const workspace = this.getProjectDirectory(this.currentSession?.id)
- const mp = this.getMarketplace()
+ // Remove from legacy files first so that the subsequent invalidation
+ // causes the CLI to re-read config without the legacy entry.
+ await this.removeLegacyMcp(name)
+
const stub = { id: name, type: "mcp" as const, name, description: "", url: "", content: "" }
-
- // Remove from both scopes — an MCP could exist in project, global, or both
- const project = await mp.remove(stub, "project", workspace)
- const global = await mp.remove(stub, "global", workspace)
-
- if (project.success || global.success) {
- // Use global scope when removed from global (or both) so the global
- // config cache is also invalidated; project scope is a subset.
- const scope = global.success ? "global" : "project"
- await this.disposeCliInstance(scope)
- this.cachedConfigMessage = null
- await this.fetchAndSendConfig()
- } else {
+ const removed = await this.removeMarketplaceItemFromAllScopes(stub)
+ if (!removed) {
console.error("[Kilo New] KiloProvider: Failed to remove MCP server:", name)
}
}
+ /**
+ * Remove an MCP server from legacy config files (.kilo/mcp.json, .kilocode/mcp.json,
+ * and the VS Code global storage mcp_settings.json). These files are read by the
+ * CLI-side McpMigrator and merged into config at the lowest precedence level.
+ * Returns true if the entry was found and removed from at least one file.
+ */
+ private async removeLegacyMcp(name: string): Promise {
+ const workspace = this.getProjectDirectory(this.currentSession?.id)
+ const files: vscode.Uri[] = []
+
+ // Project-level legacy files
+ if (workspace) {
+ files.push(vscode.Uri.file(path.join(workspace, ".kilo", "mcp.json")))
+ files.push(vscode.Uri.file(path.join(workspace, ".kilocode", "mcp.json")))
+ }
+
+ // Global legacy file (VS Code extension global storage)
+ const storage = this.extensionContext?.globalStorageUri
+ if (storage) {
+ files.push(vscode.Uri.joinPath(storage, "settings", "mcp_settings.json"))
+ }
+
+ let removed = false
+ for (const uri of files) {
+ const bytes = await vscode.workspace.fs.readFile(uri).then(
+ (b) => b,
+ () => null,
+ )
+ if (!bytes) continue
+
+ try {
+ const parsed = JSON.parse(Buffer.from(bytes).toString("utf8")) as Record
+ const servers = parsed.mcpServers as Record | undefined
+ if (!servers?.[name]) continue
+
+ delete servers[name]
+ const content = Buffer.from(JSON.stringify(parsed, null, 2), "utf8")
+ await vscode.workspace.fs.writeFile(uri, content)
+ removed = true
+ } catch (err) {
+ console.warn("[Kilo New] KiloProvider: Failed to remove legacy MCP from", uri.fsPath, err)
+ }
+ }
+
+ return removed
+ }
+
private async fetchAndSendMcpStatus(): Promise {
if (!this.client) {
if (this.cachedMcpStatusMessage) {
@@ -1861,23 +1904,35 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
}
/**
- * Dispose the CLI backend instance so it re-reads config from disk.
- * Call after any marketplace install/remove that writes config files directly.
- * Global-scope changes need global.dispose() to also reset the global config cache.
+ * Remove a marketplace item from a single scope and invalidate CLI caches.
*/
- private async disposeCliInstance(scope: "project" | "global"): Promise {
- if (!this.client) return
- if (scope === "global") {
- await this.client.global.dispose().catch((e: unknown) => {
- console.warn("[Kilo New] global.dispose() after marketplace change failed:", e)
- })
+ private async removeMarketplaceItem(item: MarketplaceItem, scope: "project" | "global"): Promise {
+ const workspace = this.getProjectDirectory(this.currentSession?.id)
+ const result = await this.getMarketplace().remove(item, scope, workspace)
+ if (result.success) {
+ await this.invalidateAfterMarketplaceChange(scope)
}
- // Always dispose the per-project instance so it rebuilds state from
- // the (possibly updated) global + project config on the next request.
- const dir = this.getWorkspaceDirectory()
- await this.client.instance.dispose({ directory: dir }).catch((e: unknown) => {
- console.warn("[Kilo New] instance.dispose() after marketplace change failed:", e)
- })
+ return result
+ }
+
+ /**
+ * Remove a marketplace item from both project and global scopes.
+ * mp.remove returns success even when the entry doesn't exist (no-op),
+ * so we must attempt both scopes to cover dual-scope installations.
+ * Returns true if at least one scope removal succeeded.
+ */
+ private async removeMarketplaceItemFromAllScopes(item: MarketplaceItem): Promise {
+ const workspace = this.getProjectDirectory(this.currentSession?.id)
+ const mp = this.getMarketplace()
+ const project = await mp.remove(item, "project", workspace)
+ const global = await mp.remove(item, "global", workspace)
+
+ if (project.success || global.success) {
+ const scope = global.success ? "global" : "project"
+ await this.invalidateAfterMarketplaceChange(scope)
+ return true
+ }
+ return false
}
/**
diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts
index 8ebb82f9a89..bebd61406a2 100644
--- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts
+++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts
@@ -6,6 +6,7 @@ import { getErrorMessage } from "../kilo-provider-utils"
import { isAbsolutePath } from "../path-utils"
import { WorktreeManager, type CreateWorktreeResult } from "./WorktreeManager"
import { WorktreeStateManager, remoteRef } from "./WorktreeStateManager"
+import { handleSection } from "./section-handler"
import { chooseBaseBranch, normalizeBaseBranch } from "./base-branch"
import { GitStatsPoller, type WorktreePresenceResult } from "./GitStatsPoller"
import { PRStatusBridge } from "./pr-status-bridge"
@@ -341,6 +342,7 @@ export class AgentManagerProvider implements Disposable {
this.state?.setSessionsCollapsed(m.collapsed)
return null
}
+ if (this.handleSection(m)) return null
if (m.type === "agentManager.setReviewDiffStyle") {
this.state?.setReviewDiffStyle(m.style)
return null
@@ -1488,6 +1490,7 @@ export class AgentManagerProvider implements Disposable {
type: "agentManager.state",
worktrees,
sessions: state.getSessions(),
+ sections: state.getSections(),
staleWorktreeIds,
tabOrder: state.getTabOrder(),
worktreeOrder: state.getWorktreeOrder(),
@@ -1911,6 +1914,10 @@ export class AgentManagerProvider implements Disposable {
)
}
+ private handleSection(m: AgentManagerInMessage): boolean {
+ return handleSection(this.state, m, () => this.pushState())
+ }
+
public postMessage(message: unknown): void {
this.panel?.postMessage(message)
}
diff --git a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts
index a1d37297c16..6ec85c11ea1 100644
--- a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts
+++ b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts
@@ -14,6 +14,9 @@ interface PRStatusPollerOptions {
const GH_PROBE_TTL = 300_000 // 5 minutes — gh installation state rarely changes at runtime
const MAX_BACKOFF = 120_000 // 2 minutes — cap for exponential backoff on repeated errors
const BACKOFF_MULTIPLIER = 2
+const PR_LOOKUP_TTL = 10_000 // 10 seconds — short TTL; only the active worktree polls so this stays cheap
+const FULL_SYNC_INTERVAL = 120_000 // 2 minutes — periodic sync of ALL worktrees (badges stay fresh)
+const FULL_SYNC_CONCURRENCY = 3 // max parallel gh processes during a full sync (caps the burst)
export class PRStatusPoller {
private timer: ReturnType | undefined
@@ -27,6 +30,8 @@ export class PRStatusPoller {
private ghProbeTime = 0
private activeWorktreeId: string | undefined
private cachedRepo: { owner: string; name: string; cwd: string } | undefined
+ private prCache = new Map()
+ private lastFullSync = 0 // timestamp of last full (all-worktree) sync
private readonly intervalMs: number
constructor(private readonly options: PRStatusPollerOptions) {
@@ -48,9 +53,13 @@ export class PRStatusPoller {
this.visible = visible
if (!this.active) return
if (visible) {
- // Resume — poll immediately then schedule normally
+ // Resume — expire all PR caches and fetch all worktrees once to catch up,
+ // then resume the normal active-only poll cycle.
if (this.timer) clearTimeout(this.timer)
this.timer = undefined
+ this.prCache.clear()
+ this.lastHash.clear()
+ this.lastFullSync = 0
void this.poll()
return
}
@@ -74,16 +83,24 @@ export class PRStatusPoller {
this.ghAvailable = undefined
this.ghProbeTime = 0
this.cachedRepo = undefined
+ this.prCache.clear()
+ this.lastFullSync = 0
}
- /** Force-refresh a specific worktree immediately. */
+ /** Force-refresh a specific worktree immediately, bypassing the PR cache. */
refresh(worktreeId: string): void {
if (!this.active) return
+ const wt = this.options.getWorktrees().find((w) => w.id === worktreeId)
+ if (wt) this.prCache.delete(wt.branch)
void this.fetchOne(worktreeId)
}
setActiveWorktreeId(id: string | undefined): void {
+ const prev = this.activeWorktreeId
this.activeWorktreeId = id
+ // When switching to a different worktree, fetch it immediately so the
+ // badge updates without waiting for the next poll cycle.
+ if (id && id !== prev && this.active) void this.fetchOne(id)
}
private start(): void {
@@ -146,8 +163,27 @@ export class PRStatusPoller {
}
this.lastError = undefined
+
+ // Most ticks only poll the active worktree for fast feedback. Every
+ // FULL_SYNC_INTERVAL we poll ALL worktrees so badges stay current even
+ // for sessions that aren't selected (e.g. CI results changing).
+ // The very first poll (lastHash empty) also fetches everything.
const worktrees = this.options.getWorktrees()
- const results = await Promise.allSettled(worktrees.map((wt) => this.fetchOne(wt.id)))
+ const now = Date.now()
+ const initial = this.lastHash.size === 0
+ const full = initial || now - this.lastFullSync >= FULL_SYNC_INTERVAL
+ const targets = full ? worktrees : worktrees.filter((wt) => wt.id === this.activeWorktreeId)
+ if (full) this.lastFullSync = now
+
+ if (targets.length === 0) {
+ this.failures = 0
+ return
+ }
+
+ const thunks = targets.map((wt) => () => this.fetchOne(wt.id))
+ const results = full
+ ? await settled(thunks, FULL_SYNC_CONCURRENCY)
+ : await Promise.allSettled(thunks.map((fn) => fn()))
const ok = results.every((r) => r.status === "fulfilled")
if (ok) {
this.failures = 0
@@ -164,7 +200,7 @@ export class PRStatusPoller {
if (!this.options.getWorkspaceRoot()) return
try {
- const pr = await this.fetchPRForBranch(wt.branch, wt.path)
+ const pr = await this.cachedFetchPR(wt.branch, wt.path)
if (!pr) {
const hash = `${worktreeId}:none`
if (this.lastHash.get(worktreeId) === hash) return
@@ -217,6 +253,17 @@ export class PRStatusPoller {
private static readonly PR_JSON_FIELDS =
"number,title,url,state,isDraft,reviewDecision,additions,deletions,changedFiles,headRefName,headRefOid"
+ /** Return a cached PR lookup if still fresh, otherwise fetch and cache.
+ * Keyed by branch name so multiple worktrees on the same branch share
+ * the cache, and a branch switch in a worktree naturally misses. */
+ private async cachedFetchPR(branch: string, cwd: string): Promise {
+ const cached = this.prCache.get(branch)
+ if (cached && Date.now() < cached.expires) return cached.result
+ const result = await this.fetchPRForBranch(branch, cwd)
+ this.prCache.set(branch, { result, expires: Date.now() + PR_LOOKUP_TTL })
+ return result
+ }
+
private async fetchPRForBranch(branch: string, cwd: string): Promise {
// Strategy 1: bare `gh pr view` — resolves via the branch's tracking ref.
// Works for fork PRs checked out with `gh pr checkout` (tracking ref = refs/pull/N/head).
@@ -484,3 +531,22 @@ function formatCheckDuration(startedAt?: string, completedAt?: string): string |
const secs = Math.round((new Date(completedAt).getTime() - new Date(startedAt).getTime()) / 1000)
return secs < 60 ? `${secs}s` : `${Math.floor(secs / 60)}m ${secs % 60}s`
}
+
+/** Run async thunks with bounded concurrency, returning settled results. */
+async function settled(thunks: (() => Promise)[], concurrency: number): Promise[]> {
+ const results: PromiseSettledResult[] = new Array(thunks.length)
+ let idx = 0
+ async function run(): Promise {
+ while (idx < thunks.length) {
+ const i = idx++
+ const fn = thunks[i]!
+ try {
+ results[i] = { status: "fulfilled", value: await fn() }
+ } catch (reason) {
+ results[i] = { status: "rejected", reason }
+ }
+ }
+ }
+ await Promise.all(Array.from({ length: Math.min(concurrency, thunks.length) }, () => run()))
+ return results
+}
diff --git a/packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts b/packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts
index c41ce547aa8..3765785d96f 100644
--- a/packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts
+++ b/packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts
@@ -35,6 +35,18 @@ export interface Worktree {
/** Original branch created with the worktree, used for cleanup on deletion.
* Set automatically when `branch` is updated via live sync. */
originalBranch?: string
+ /** Section this worktree belongs to, or undefined for ungrouped. */
+ sectionId?: string
+}
+
+export interface Section {
+ id: string
+ name: string
+ /** Color label (e.g. "Red", "Blue") mapped to VS Code theme CSS vars at render time, or null for default. */
+ color: string | null
+ /** Position among top-level sidebar children (interleaved with ungrouped worktrees). */
+ order: number
+ collapsed: boolean
}
/**
@@ -55,6 +67,7 @@ export interface ManagedSession {
interface StateFile {
worktrees: Record>
sessions: Record>
+ sections?: Record>
tabOrder?: Record
worktreeOrder?: string[]
sessionsCollapsed?: boolean
@@ -76,6 +89,7 @@ export class WorktreeStateManager {
private readonly file: string
private worktrees = new Map()
private sessions = new Map()
+ private sections = new Map()
private tabOrder: Record = {}
private worktreeOrder: string[] = []
private collapsed = false
@@ -204,12 +218,12 @@ export class WorktreeStateManager {
const removed = this.worktrees.delete(id)
if (!removed) return []
- // Dissociate all sessions from this worktree (set worktreeId to null)
+ // Collect and remove all sessions belonging to this worktree
const orphaned: ManagedSession[] = []
for (const s of this.sessions.values()) {
if (s.worktreeId === id) {
- s.worktreeId = null
- orphaned.push(s)
+ orphaned.push({ ...s })
+ this.sessions.delete(s.id)
}
}
@@ -220,7 +234,7 @@ export class WorktreeStateManager {
const idx = this.worktreeOrder.indexOf(id)
if (idx !== -1) this.worktreeOrder.splice(idx, 1)
- this.log(`Removed worktree ${id}, orphaned ${orphaned.length} sessions`)
+ this.log(`Removed worktree ${id}, removed ${orphaned.length} sessions`)
void this.save()
return orphaned
}
@@ -284,7 +298,128 @@ export class WorktreeStateManager {
}
setWorktreeOrder(order: string[]): void {
- this.worktreeOrder = order
+ const top = new Set()
+ for (const sec of this.sections.values()) top.add(sec.id)
+ for (const wt of this.worktrees.values()) {
+ if (!wt.sectionId) top.add(wt.id)
+ }
+ this.worktreeOrder = order.filter((id) => top.has(id))
+ void this.save()
+ }
+
+ // ---------------------------------------------------------------------------
+ // Sections
+ // ---------------------------------------------------------------------------
+
+ getSections(): Section[] {
+ return [...this.sections.values()]
+ }
+
+ getSection(id: string): Section | undefined {
+ return this.sections.get(id)
+ }
+
+ addSection(name: string, color: string | null, worktreeIds?: string[]): Section {
+ const id = generateId("sec")
+ const order = this.worktreeOrder.length
+ const sec: Section = { id, name, color, order, collapsed: false }
+ this.sections.set(id, sec)
+ this.worktreeOrder.push(id)
+ if (worktreeIds) {
+ for (const wtId of worktreeIds) {
+ const wt = this.worktrees.get(wtId)
+ if (wt) {
+ wt.sectionId = id
+ // Remove from top-level worktreeOrder since it's now inside a section
+ const idx = this.worktreeOrder.indexOf(wtId)
+ if (idx !== -1) this.worktreeOrder.splice(idx, 1)
+ }
+ }
+ }
+ this.log(`Added section ${id}: "${name}"`)
+ void this.save()
+ return sec
+ }
+
+ renameSection(id: string, name: string): void {
+ const sec = this.sections.get(id)
+ if (!sec || !name) return
+ sec.name = name
+ this.log(`Renamed section ${id} to "${name}"`)
+ void this.save()
+ }
+
+ setSectionColor(id: string, color: string | null): void {
+ const sec = this.sections.get(id)
+ if (!sec) return
+ sec.color = color
+ void this.save()
+ }
+
+ toggleSection(id: string): void {
+ const sec = this.sections.get(id)
+ if (!sec) return
+ sec.collapsed = !sec.collapsed
+ void this.save()
+ }
+
+ deleteSection(id: string): void {
+ if (!this.sections.delete(id)) return
+ // Ungroup all worktrees in this section — do NOT delete them
+ for (const wt of this.worktrees.values()) {
+ if (wt.sectionId === id) {
+ wt.sectionId = undefined
+ if (!this.worktreeOrder.includes(wt.id)) this.worktreeOrder.push(wt.id)
+ }
+ }
+ // Remove from sidebar order
+ const idx = this.worktreeOrder.indexOf(id)
+ if (idx !== -1) this.worktreeOrder.splice(idx, 1)
+ this.log(`Deleted section ${id}, ungrouped its worktrees`)
+ void this.save()
+ }
+
+ moveSection(id: string, dir: -1 | 1): void {
+ const top = this.worktreeOrder.filter((item) => {
+ if (this.sections.has(item)) return true
+ const wt = this.worktrees.get(item)
+ return !!wt && !wt.sectionId
+ })
+ const idx = top.indexOf(id)
+ const next = idx + dir
+ if (idx === -1 || next < 0 || next >= top.length) return
+ const target = top[next]!
+ const result = [...this.worktreeOrder]
+ const fi = result.indexOf(id)
+ if (fi === -1 || result.indexOf(target) === -1) return
+ result.splice(fi, 1)
+ const insertAt = result.indexOf(target) + (dir === 1 ? 1 : 0)
+ result.splice(insertAt, 0, id)
+ this.worktreeOrder = result
+ void this.save()
+ }
+
+ moveToSection(worktreeIds: string[], sectionId: string | null): void {
+ // Expand to include all multi-version siblings (same groupId)
+ const expanded = new Set(worktreeIds)
+ for (const wtId of worktreeIds) {
+ const wt = this.worktrees.get(wtId)
+ if (!wt?.groupId) continue
+ for (const sibling of this.worktrees.values()) {
+ if (sibling.groupId === wt.groupId) expanded.add(sibling.id)
+ }
+ }
+ for (const wtId of expanded) {
+ const wt = this.worktrees.get(wtId)
+ if (!wt) continue
+ wt.sectionId = sectionId ?? undefined
+ if (sectionId) {
+ const idx = this.worktreeOrder.indexOf(wtId)
+ if (idx !== -1) this.worktreeOrder.splice(idx, 1)
+ } else {
+ if (!this.worktreeOrder.includes(wtId)) this.worktreeOrder.push(wtId)
+ }
+ }
void this.save()
}
@@ -343,6 +478,7 @@ export class WorktreeStateManager {
const data = JSON.parse(content) as StateFile
this.worktrees.clear()
this.sessions.clear()
+ this.sections.clear()
this.tabOrder = {}
this.worktreeOrder = []
this.reviewDiffStyle = "unified"
@@ -355,21 +491,42 @@ export class WorktreeStateManager {
}) ?? wt.path
this.worktrees.set(id, { id, ...wt, path: fixed })
}
+ let pruned = 0
for (const [id, s] of Object.entries(data.sessions ?? {})) {
+ // Skip orphaned sessions (null worktreeId or referencing a deleted worktree)
+ if (!s.worktreeId || !this.worktrees.has(s.worktreeId)) {
+ pruned++
+ continue
+ }
this.sessions.set(id, { id, ...s })
}
+ for (const [id, sec] of Object.entries(data.sections ?? {})) {
+ this.sections.set(id, { id, ...sec })
+ }
if (data.tabOrder) {
this.tabOrder = data.tabOrder
}
if (data.worktreeOrder) {
this.worktreeOrder = data.worktreeOrder
}
+ // Normalize: ensure all section IDs and ungrouped worktree IDs are in worktreeOrder
+ const present = new Set(this.worktreeOrder)
+ for (const id of this.sections.keys()) {
+ if (!present.has(id)) this.worktreeOrder.push(id)
+ }
+ for (const wt of this.worktrees.values()) {
+ if (!wt.sectionId && !present.has(wt.id)) this.worktreeOrder.push(wt.id)
+ }
this.collapsed = data.sessionsCollapsed ?? false
if (data.reviewDiffStyle === "split") {
this.reviewDiffStyle = "split"
}
this.defaultBase = data.defaultBaseBranch
this.log(`Loaded state: ${this.worktrees.size} worktrees, ${this.sessions.size} sessions`)
+ if (pruned > 0) {
+ this.log(`Pruned ${pruned} orphaned sessions`)
+ void this.save()
+ }
} catch (error) {
const code = (error as NodeJS.ErrnoException).code
if (code !== "ENOENT") {
@@ -379,7 +536,7 @@ export class WorktreeStateManager {
return migration
}
- /** Remove worktrees whose directories no longer exist on disk. */
+ /** Remove worktrees whose directories no longer exist on disk and prune orphaned sessions. */
async validate(root: string): Promise {
let changed = false
for (const wt of [...this.worktrees.values()]) {
@@ -390,7 +547,17 @@ export class WorktreeStateManager {
changed = true
}
}
- if (changed) await this.save()
+ // Prune orphaned sessions (worktreeId is null or references a deleted worktree)
+ for (const s of [...this.sessions.values()]) {
+ if (!s.worktreeId || !this.worktrees.has(s.worktreeId)) {
+ this.sessions.delete(s.id)
+ changed = true
+ }
+ }
+ if (changed) {
+ this.log(`Pruned orphaned sessions during validation`)
+ await this.save()
+ }
}
/** Wait for any in-flight save to complete without triggering a new one. */
@@ -433,6 +600,13 @@ export class WorktreeStateManager {
const { id: _, ...rest } = s
data.sessions[id] = rest
}
+ if (this.sections.size > 0) {
+ data.sections = {}
+ for (const [id, sec] of this.sections) {
+ const { id: _, ...rest } = sec
+ data.sections[id] = rest
+ }
+ }
if (Object.keys(this.tabOrder).length > 0) {
data.tabOrder = this.tabOrder
}
diff --git a/packages/kilo-vscode/src/agent-manager/section-handler.ts b/packages/kilo-vscode/src/agent-manager/section-handler.ts
new file mode 100644
index 00000000000..624a23eb10e
--- /dev/null
+++ b/packages/kilo-vscode/src/agent-manager/section-handler.ts
@@ -0,0 +1,21 @@
+import type { WorktreeStateManager } from "./WorktreeStateManager"
+import type { AgentManagerInMessage } from "./types"
+
+/** Handle section CRUD messages. Returns true if handled. */
+export function handleSection(
+ state: WorktreeStateManager | undefined,
+ m: AgentManagerInMessage,
+ push: () => void,
+): boolean {
+ if (!state) return false
+ if (m.type === "agentManager.createSection") state.addSection(m.name, m.color ?? null, m.worktreeIds)
+ else if (m.type === "agentManager.renameSection") state.renameSection(m.sectionId, m.name)
+ else if (m.type === "agentManager.deleteSection") state.deleteSection(m.sectionId)
+ else if (m.type === "agentManager.setSectionColor") state.setSectionColor(m.sectionId, m.color)
+ else if (m.type === "agentManager.toggleSectionCollapsed") state.toggleSection(m.sectionId)
+ else if (m.type === "agentManager.moveToSection") state.moveToSection(m.worktreeIds, m.sectionId)
+ else if (m.type === "agentManager.moveSection") state.moveSection(m.sectionId, m.dir)
+ else return false
+ push()
+ return true
+}
diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts
index 6c116034638..264f1f46107 100644
--- a/packages/kilo-vscode/src/agent-manager/types.ts
+++ b/packages/kilo-vscode/src/agent-manager/types.ts
@@ -8,7 +8,7 @@
*/
import type { FileDiff } from "@kilocode/sdk/v2/client"
-import type { Worktree, ManagedSession } from "./WorktreeStateManager"
+import type { Worktree, ManagedSession, Section } from "./WorktreeStateManager"
import type { WorktreeStats, LocalStats } from "./GitStatsPoller"
import type { ApplyConflict } from "./GitOps"
import type { BranchListItem, WorktreeSetupErrorCode } from "./git-import"
@@ -118,6 +118,7 @@ interface StateMessage {
type: "agentManager.state"
worktrees: Worktree[]
sessions: ManagedSession[]
+ sections?: Section[]
staleWorktreeIds?: string[]
tabOrder?: Record
worktreeOrder?: string[]
@@ -523,6 +524,47 @@ interface ContinueInWorktreeIn {
sessionId: string
}
+interface CreateSectionIn {
+ type: "agentManager.createSection"
+ name: string
+ color?: string
+ worktreeIds?: string[]
+}
+
+interface RenameSectionIn {
+ type: "agentManager.renameSection"
+ sectionId: string
+ name: string
+}
+
+interface DeleteSectionIn {
+ type: "agentManager.deleteSection"
+ sectionId: string
+}
+
+interface SetSectionColorIn {
+ type: "agentManager.setSectionColor"
+ sectionId: string
+ color: string | null
+}
+
+interface ToggleSectionCollapsedIn {
+ type: "agentManager.toggleSectionCollapsed"
+ sectionId: string
+}
+
+interface MoveToSectionIn {
+ type: "agentManager.moveToSection"
+ worktreeIds: string[]
+ sectionId: string | null
+}
+
+interface MoveSectionIn {
+ type: "agentManager.moveSection"
+ sectionId: string
+ dir: -1 | 1
+}
+
/** All messages the Agent Manager expects from the webview (onMessage input). */
export type AgentManagerInMessage =
| CreateWorktreeIn
@@ -570,3 +612,10 @@ export type AgentManagerInMessage =
| ClearSessionIn
| AbortIn
| ContinueInWorktreeIn
+ | CreateSectionIn
+ | RenameSectionIn
+ | DeleteSectionIn
+ | SetSectionColorIn
+ | ToggleSectionCollapsedIn
+ | MoveToSectionIn
+ | MoveSectionIn
diff --git a/packages/kilo-vscode/src/agent-manager/vscode-host.ts b/packages/kilo-vscode/src/agent-manager/vscode-host.ts
index 7ea4403cbc0..d0eadb6e381 100644
--- a/packages/kilo-vscode/src/agent-manager/vscode-host.ts
+++ b/packages/kilo-vscode/src/agent-manager/vscode-host.ts
@@ -9,17 +9,24 @@ import * as vscode from "vscode"
import type { Host, PanelContext, OutputHandle, SessionProvider, Disposable } from "./host"
import type { KiloConnectionService } from "../services/cli-backend"
import { KiloProvider } from "../KiloProvider"
+import { DiffVirtualProvider } from "../DiffVirtualProvider"
import { buildWebviewHtml } from "../utils"
import { openFileInEditor, getWorkspaceRoot } from "../review-utils"
import { TelemetryProxy, type TelemetryEventName } from "../services/telemetry"
export class VscodeHost implements Host {
+ private diffVirtual: DiffVirtualProvider | undefined
+
constructor(
private readonly extensionUri: vscode.Uri,
private readonly connectionService: KiloConnectionService,
private readonly context: vscode.ExtensionContext,
) {}
+ setDiffVirtualProvider(provider: DiffVirtualProvider): void {
+ this.diffVirtual = provider
+ }
+
openPanel(opts: {
onBeforeMessage: (msg: Record) => Promise | null>
}): PanelContext {
@@ -74,6 +81,9 @@ export class VscodeHost implements Host {
const provider = new KiloProvider(this.extensionUri, this.connectionService, this.context, {
slimEditMetadata: true,
})
+ if (this.diffVirtual) {
+ provider.setDiffVirtualProvider(this.diffVirtual)
+ }
provider.attachToWebview(panel.webview, {
onBeforeMessage: opts.onBeforeMessage,
})
diff --git a/packages/kilo-vscode/src/extension.ts b/packages/kilo-vscode/src/extension.ts
index 438b26c3a9c..df9681c0fc1 100644
--- a/packages/kilo-vscode/src/extension.ts
+++ b/packages/kilo-vscode/src/extension.ts
@@ -3,6 +3,7 @@ import { KiloProvider } from "./KiloProvider"
import { AgentManagerProvider } from "./agent-manager/AgentManagerProvider"
import { VscodeHost } from "./agent-manager/vscode-host"
import { DiffViewerProvider } from "./DiffViewerProvider"
+import { DiffVirtualProvider } from "./DiffVirtualProvider"
import { SettingsEditorProvider } from "./SettingsEditorProvider"
import { SubAgentViewerProvider } from "./SubAgentViewerProvider"
import { EXTENSION_DISPLAY_NAME } from "./constants"
@@ -105,6 +106,7 @@ export function activate(context: vscode.ExtensionContext) {
tabProvider.setContinueInWorktreeHandler((sessionId, progress) =>
agentManagerProvider.continueFromSidebar(sessionId, progress),
)
+ tabProvider.setDiffVirtualProvider(diffVirtualProvider)
tabProvider.resolveWebviewPanel(panel)
tabPanels.set(panel, tabProvider)
panel.onDidDispose(
@@ -128,6 +130,12 @@ export function activate(context: vscode.ExtensionContext) {
})
context.subscriptions.push(diffViewerProvider)
+ // Create diff virtual provider (lightweight single-file diff for permission approval)
+ const diffVirtualProvider = new DiffVirtualProvider(context.extensionUri)
+ provider.setDiffVirtualProvider(diffVirtualProvider)
+ agentManagerHost.setDiffVirtualProvider(diffVirtualProvider)
+ context.subscriptions.push(diffVirtualProvider)
+
// Create settings/profile editor provider (opens in editor area, not sidebar)
const settingsEditorProvider = new SettingsEditorProvider(context.extensionUri, connectionService, context)
context.subscriptions.push(settingsEditorProvider)
@@ -221,7 +229,7 @@ export function activate(context: vscode.ExtensionContext) {
provider.postMessage({ type: "triggerTask", text: `Generate a terminal command: ${input}` })
}),
vscode.commands.registerCommand("kilo-code.new.openInTab", () => {
- return openKiloInNewTab(context, connectionService, agentManagerProvider, tabPanels)
+ return openKiloInNewTab(context, connectionService, agentManagerProvider, tabPanels, diffVirtualProvider)
}),
vscode.commands.registerCommand("kilo-code.new.showChanges", () => {
diffViewerProvider.openPanel()
@@ -353,6 +361,7 @@ async function openKiloInNewTab(
connectionService: KiloConnectionService,
agentManagerProvider: AgentManagerProvider,
tabPanels: Map,
+ diffVirtualProvider: DiffVirtualProvider,
) {
const lastCol = Math.max(...vscode.window.visibleTextEditors.map((e) => e.viewColumn || 0), 0)
const hasVisibleEditors = vscode.window.visibleTextEditors.length > 0
@@ -378,6 +387,7 @@ async function openKiloInNewTab(
tabProvider.setContinueInWorktreeHandler((sessionId, progress) =>
agentManagerProvider.continueFromSidebar(sessionId, progress),
)
+ tabProvider.setDiffVirtualProvider(diffVirtualProvider)
tabProvider.resolveWebviewPanel(panel)
tabPanels.set(panel, tabProvider)
diff --git a/packages/kilo-vscode/src/kilo-provider/slim-metadata.ts b/packages/kilo-vscode/src/kilo-provider/slim-metadata.ts
index 3ec760d7c4a..a7748b5d004 100644
--- a/packages/kilo-vscode/src/kilo-provider/slim-metadata.ts
+++ b/packages/kilo-vscode/src/kilo-provider/slim-metadata.ts
@@ -112,13 +112,29 @@ function slimMultiedit(state: Record): Record
return next
}
-/** write: strip input.content (entire file). Keep filePath + diagnostics. */
+/** write: strip input.content, raw diff text, and filediff.before/after. Keep filepath + exists + diagnostics. */
function slimWrite(state: Record): Record {
const next = { ...state }
const input = state.input
if (isObj(input) && typeof input.content === "string") {
next.input = { ...input, content: undefined }
}
+ const meta = state.metadata
+ if (isObj(meta)) {
+ const slim: Record = {}
+ if (meta.filepath) slim.filepath = meta.filepath
+ if (meta.exists !== undefined) slim.exists = meta.exists
+ if (meta.diagnostics) slim.diagnostics = meta.diagnostics
+ const fd = meta.filediff
+ if (isObj(fd)) {
+ slim.filediff = {
+ ...(typeof fd.file === "string" ? { file: fd.file } : {}),
+ additions: typeof fd.additions === "number" ? fd.additions : 0,
+ deletions: typeof fd.deletions === "number" ? fd.deletions : 0,
+ }
+ }
+ next.metadata = slim
+ }
return next
}
diff --git a/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/ChatTextAreaAutocomplete.ts b/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/ChatTextAreaAutocomplete.ts
index 690f0e1261a..bb4e52f000d 100644
--- a/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/ChatTextAreaAutocomplete.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/ChatTextAreaAutocomplete.ts
@@ -1,15 +1,24 @@
import * as vscode from "vscode"
import { AutocompleteModel } from "../AutocompleteModel"
-import { AutocompleteContext, VisibleCodeContext } from "../types"
+import type { AutocompleteContext, VisibleCodeContext } from "../types"
import { removePrefixOverlap } from "../continuedev/core/autocomplete/postprocessing/removePrefixOverlap.js"
import { AutocompleteTelemetry } from "../classic-auto-complete/AutocompleteTelemetry"
import { postprocessAutocompleteSuggestion } from "../classic-auto-complete/uselessSuggestionFilter"
import { VisibleCodeTracker } from "../context/VisibleCodeTracker"
import { FileIgnoreController } from "../shims/FileIgnoreController"
import type { KiloConnectionService } from "../../cli-backend"
-import type { ChatCompletionRequestMessage, ChatCompletionResponseSender } from "./handleChatCompletionRequest"
import { finalizeChatSuggestion, buildChatPrefix } from "./chat-autocomplete-utils"
+interface ChatCompletionRequestMessage {
+ type: "requestChatCompletion"
+ text: string
+ requestId: string
+}
+
+interface ChatCompletionResponseSender {
+ postMessage(message: { type: "chatCompletionResult"; text: string; requestId: string }): void
+}
+
/**
* Chat textarea autocomplete with cached per-request objects.
*
diff --git a/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/handleChatCompletionAccepted.ts b/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/handleChatCompletionAccepted.ts
deleted file mode 100644
index 9bb9a153bd1..00000000000
--- a/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/handleChatCompletionAccepted.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { AutocompleteTelemetry } from "../classic-auto-complete/AutocompleteTelemetry"
-
-export interface ChatCompletionAcceptedMessage {
- type: "chatCompletionAccepted"
- suggestionLength?: number
-}
-
-// Singleton telemetry instance for chat-textarea autocomplete
-// This ensures we use the same instance across requests and acceptance events
-let telemetryInstance: AutocompleteTelemetry | null = null
-
-/**
- * Get or create the telemetry instance for chat-textarea autocomplete
- */
-export function getChatAutocompleteTelemetry(): AutocompleteTelemetry {
- if (!telemetryInstance) {
- telemetryInstance = new AutocompleteTelemetry("chat-textarea")
- }
- return telemetryInstance
-}
-
-/**
- * Handles a chat completion accepted event from the webview.
- * Captures telemetry when the user accepts a suggestion via Tab or ArrowRight.
- */
-export function handleChatCompletionAccepted(message: ChatCompletionAcceptedMessage): void {
- const telemetry = getChatAutocompleteTelemetry()
- telemetry.captureAcceptSuggestion(message.suggestionLength)
-}
diff --git a/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/handleChatCompletionRequest.ts b/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/handleChatCompletionRequest.ts
deleted file mode 100644
index eeac049683e..00000000000
--- a/packages/kilo-vscode/src/services/autocomplete/chat-autocomplete/handleChatCompletionRequest.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import * as vscode from "vscode"
-import { VisibleCodeTracker } from "../context/VisibleCodeTracker"
-import { FileIgnoreController } from "../shims/FileIgnoreController"
-import { ChatTextAreaAutocomplete } from "./ChatTextAreaAutocomplete"
-import type { KiloConnectionService } from "../../cli-backend"
-
-export interface ChatCompletionRequestMessage {
- type: "requestChatCompletion"
- text?: string
- requestId?: string
-}
-
-export interface ChatCompletionResponseSender {
- postMessage(message: { type: "chatCompletionResult"; text: string; requestId: string }): void
-}
-
-/**
- * Handles a chat completion request from the webview.
- * Captures visible code context and generates an autocomplete suggestion.
- */
-export async function handleChatCompletionRequest(
- message: ChatCompletionRequestMessage,
- responseSender: ChatCompletionResponseSender,
- connectionService: KiloConnectionService,
-): Promise {
- const userText = message.text || ""
- const requestId = message.requestId || ""
-
- const workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? ""
-
- const ignoreController = new FileIgnoreController(workspacePath)
- await ignoreController.initialize()
-
- const tracker = new VisibleCodeTracker(workspacePath, ignoreController)
- const visibleContext = await tracker.captureVisibleCode()
-
- const autocomplete = new ChatTextAreaAutocomplete(connectionService)
- const { suggestion } = await autocomplete.getCompletion(userText, visibleContext)
-
- responseSender.postMessage({ type: "chatCompletionResult", text: suggestion, requestId })
-
- ignoreController.dispose()
-}
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/API_REFERENCE.md b/packages/kilo-vscode/src/services/autocomplete/continuedev/API_REFERENCE.md
deleted file mode 100644
index 216538b0c7f..00000000000
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/API_REFERENCE.md
+++ /dev/null
@@ -1,807 +0,0 @@
-# API Reference
-
-Complete API documentation for the Autocomplete & NextEdit library.
-
-## Table of Contents
-
-- [CompletionProvider](#completionprovider)
-- [NextEditProvider](#nexteditprovider)
-- [MinimalConfigProvider](#minimalconfigprovider)
-- [Core Interfaces](#core-interfaces)
-- [LLM Adapters](#llm-adapters)
-- [Types and Interfaces](#types-and-interfaces)
-
----
-
-## CompletionProvider
-
-The main class for providing AI-powered code autocompletion.
-
-**Location**: [`core/autocomplete/CompletionProvider.ts`](core/autocomplete/CompletionProvider.ts)
-
-### Constructor
-
-```typescript
-constructor(
- configHandler: MinimalConfigProvider,
- ide: IDE,
- _injectedGetLlm: () => Promise,
- _onError: (e: any) => void,
- getDefinitionsFromLsp: GetLspDefinitionsFunction
-)
-```
-
-**Parameters**:
-
-- `configHandler`: Configuration provider for autocomplete options
-- `ide`: IDE interface implementation for file I/O and editor operations
-- `_injectedGetLlm`: Async function that returns the LLM to use for completions
-- `_onError`: Error callback function for handling autocomplete errors
-- `getDefinitionsFromLsp`: Function to retrieve LSP definitions for enhanced context
-
-### Methods
-
-#### `provideInlineCompletionItems()`
-
-Generates an autocomplete completion for the given input.
-
-```typescript
-async provideInlineCompletionItems(
- input: AutocompleteInput,
- token: AbortSignal | undefined,
- force?: boolean
-): Promise
-```
-
-**Parameters**:
-
-- `input`: Autocomplete context including file path, cursor position, recent edits
-- `token`: AbortSignal to cancel the request
-- `force`: If true, bypasses debouncing
-
-**Returns**: `AutocompleteOutcome` containing the completion text and metadata, or `undefined` if no completion
-
-**Example**:
-
-```typescript
-const outcome = await completionProvider.provideInlineCompletionItems(
- {
- filepath: "/path/to/file.ts",
- pos: { line: 10, character: 5 },
- completionId: "unique-completion-id",
- recentlyEditedRanges: [],
- recentlyEditedFiles: new Map(),
- clipboardText: "",
- },
- abortController.signal,
-)
-```
-
-#### `accept()`
-
-Marks a completion as accepted by the user.
-
-```typescript
-accept(completionId: string): void
-```
-
-**Parameters**:
-
-- `completionId`: Unique identifier for the accepted completion
-
-**Side Effects**: Updates bracket matching service and completion cache
-
-#### `markDisplayed()`
-
-Marks a completion as having been displayed to the user.
-
-```typescript
-markDisplayed(completionId: string, outcome: AutocompleteOutcome): void
-```
-
-**Parameters**:
-
-- `completionId`: Unique identifier for the completion
-- `outcome`: The autocomplete outcome that was displayed
-
-#### `cancel()`
-
-Cancels any in-progress autocomplete requests.
-
-```typescript
-cancel(): void
-```
-
-### Configuration Options
-
-See [`MinimalConfigProvider`](#minimalconfigprovider) for configuration options.
-
----
-
-## NextEditProvider
-
-The main class for providing predictive multi-location code edits.
-
-**Location**: [`core/nextEdit/NextEditProvider.ts`](core/nextEdit/NextEditProvider.ts)
-
-### Constructor
-
-```typescript
-constructor(
- configHandler: MinimalConfigProvider,
- ide: IDE,
- _injectedGetLlm: () => Promise,
- _onError: (e: any) => void
-)
-```
-
-**Parameters**:
-
-- `configHandler`: Configuration provider
-- `ide`: IDE interface implementation
-- `_injectedGetLlm`: Function returning the LLM for edit predictions
-- `_onError`: Error callback
-
-### Methods
-
-#### `getNextEditPrediction()`
-
-Generates predicted edits based on context and recent changes.
-
-```typescript
-async getNextEditPrediction(
- context: ModelSpecificContext,
- signal?: AbortSignal,
- usingFullFileDiff?: boolean
-): Promise
-```
-
-**Parameters**:
-
-- `context`: Context including file contents, cursor position, recent edits
-- `signal`: Optional AbortSignal to cancel the request
-- `usingFullFileDiff`: If true, generates full-file diffs; if false, only edits within a region
-
-**Returns**: `NextEditOutcome` containing predicted edits and final cursor position
-
-**Example**:
-
-```typescript
-const outcome = await nextEditProvider.getNextEditPrediction(
- {
- filepath: "/path/to/file.ts",
- pos: { line: 15, character: 0 },
- fileContents: currentFileContents,
- userEdits: recentDiff,
- // ... other context
- },
- abortController.signal,
- false, // Use partial file diff
-)
-
-if (outcome) {
- console.log("Edit regions:", outcome.editableRegions)
- console.log("Diff lines:", outcome.diffLines)
- console.log("New cursor:", outcome.finalCursorPosition)
-}
-```
-
----
-
-## MinimalConfigProvider
-
-Simple configuration provider that replaces the complex Continue config system.
-
-**Location**: [`core/autocomplete/MinimalConfig.ts`](core/autocomplete/MinimalConfig.ts)
-
-### Constructor
-
-```typescript
-constructor(config?: Partial)
-```
-
-**Parameters**:
-
-- `config`: Optional partial configuration to override defaults
-
-**Example**:
-
-```typescript
-const configProvider = new MinimalConfigProvider({
- tabAutocompleteOptions: {
- debounceDelay: 200,
- maxPromptTokens: 2048,
- prefixPercentage: 0.5,
- suffixPercentage: 0.3,
- useCache: true,
- onlyMyCode: false,
- },
- experimental: {
- enableStaticContextualization: true,
- },
-})
-```
-
-### Methods
-
-#### `loadConfig()`
-
-Returns the configuration object.
-
-```typescript
-async loadConfig(): Promise<{ config: MinimalConfig }>
-```
-
-**Returns**: Promise resolving to an object containing the config
-
-#### `getAutocompleteOptions()`
-
-Gets autocomplete-specific options.
-
-```typescript
-getAutocompleteOptions(): TabAutocompleteOptions
-```
-
-**Returns**: Autocomplete configuration options
-
-#### `isStaticContextualizationEnabled()`
-
-Checks if static contextualization is enabled.
-
-```typescript
-isStaticContextualizationEnabled(): boolean
-```
-
-**Returns**: True if enabled, false otherwise
-
-### Configuration Interface
-
-```typescript
-interface MinimalConfig {
- tabAutocompleteOptions?: TabAutocompleteOptions
- experimental?: {
- enableStaticContextualization?: boolean
- }
- modelsByRole?: {
- autocomplete?: ILLM[]
- }
- selectedModelByRole?: {
- autocomplete?: ILLM
- }
-}
-```
-
-### TabAutocompleteOptions
-
-```typescript
-interface TabAutocompleteOptions {
- debounceDelay?: number // Debounce delay in ms (default: 150)
- maxPromptTokens?: number // Max tokens for prompt (default: 1024)
- prefixPercentage?: number // Percentage of tokens for prefix (default: 0.5)
- suffixPercentage?: number // Percentage of tokens for suffix (default: 0.3)
- maxSuffixPercentage?: number // Max suffix tokens percentage (default: 0.5)
- useCache?: boolean // Enable completion caching (default: true)
- onlyMyCode?: boolean // Only use workspace files for context (default: false)
- template?: string // Custom prompt template
- useFileSuffix?: boolean // Include file suffix in context (default: true)
- multilineCompletions?: "always" | "never" | "auto" // Multiline behavior (default: 'auto')
- slidingWindowPrefixPercentage?: number // Sliding window prefix % (default: 0.75)
- slidingWindowSize?: number // Sliding window size (default: 500)
- maxSnippetPercentage?: number // Max tokens for snippets (default: 0.6)
- recentlyEditedSimilarityThreshold?: number // Similarity threshold (default: 0.3)
- useOtherFiles?: boolean // Use other files for context (default: true)
- disableInFiles?: string[] // Glob patterns to disable autocomplete
- stopTokens?: string[] // Custom stop tokens
- tokensPerCompletion?: number // Tokens per completion (default: 256)
- transform?: boolean // Apply post-processing transforms (default: true)
-}
-```
-
----
-
-## Core Interfaces
-
-### IDE Interface
-
-The IDE interface abstracts editor operations. Implement this to integrate with your editor.
-
-**Location**: [`core/index.d.ts`](core/index.d.ts)
-
-```typescript
-interface IDE {
- // File Operations
- readFile(filepath: string): Promise
- writeFile(filepath: string, contents: string): Promise
-
- // Workspace
- getWorkspaceDirs(): Promise
- listWorkspaceContents(directory?: string): Promise
-
- // Editor State
- getCurrentFile(): Promise
- getCursorPosition(): Promise
- getVisibleFiles(): Promise
-
- // Code Navigation
- getDefinition(filepath: string, position: Position): Promise
- getReferences(filepath: string, position: Position): Promise
- getSymbols(filepath: string): Promise
-
- // File Information
- readRangeInFile(filepath: string, range: Range): Promise
- getStats(filepath: string): Promise
-
- // Edits
- applyEdits(edits: FileEdit[]): Promise
-
- // Diff/SCM
- getDiff(includeUnstaged: boolean): Promise
- getRepoName(dir: string): Promise
- getBranch(dir: string): Promise
-
- // UI
- showMessage(message: string, severity?: "info" | "warning" | "error"): Promise
- showToast(type: "info" | "warning" | "error", message: string, ...actions: string[]): Promise
-
- // Terminal
- runCommand(command: string, options?: TerminalOptions): Promise
-
- // Clipboard
- getClipboardContent(): Promise<{ text: string; copiedAt: number } | undefined>
-
- // Search
- getSearchResults(query: string): Promise
- subprocess(command: string, cwd?: string): Promise<[string, string]>
-
- // Other
- getIdeInfo(): Promise
- getIdeSettings(): Promise
- isTelemetryEnabled(): Promise
- getUniqueId(): Promise
-}
-```
-
-**Key Methods to Implement**:
-
-- `readFile()`, `writeFile()`: Essential for file I/O
-- `getWorkspaceDirs()`: Returns workspace root directories
-- `getCurrentFile()`, `getCursorPosition()`: Current editor state
-- `applyEdits()`: Apply code changes
-- `getDefinition()`: LSP-like functionality for context gathering
-
-### ILLM Interface
-
-The ILLM (Language Model) interface abstracts LLM providers.
-
-**Location**: [`core/index.d.ts`](core/index.d.ts)
-
-```typescript
-interface ILLM {
- // Required properties
- uniqueId: string
- model: string
- contextLength: number
- completionOptions: CompletionOptions
-
- // Provider info
- get providerName(): string
- get underlyingProviderName(): string
-
- // Optional
- apiKey?: string
- apiBase?: string
- autocompleteOptions?: Partial
- promptTemplates?: PromptTemplates
-
- // Completion methods
- complete(prompt: string, signal: AbortSignal, options?: LLMFullCompletionOptions): Promise
-
- streamComplete(
- prompt: string,
- signal: AbortSignal,
- options?: LLMFullCompletionOptions,
- ): AsyncGenerator
-
- streamFim(
- prefix: string,
- suffix: string,
- signal: AbortSignal,
- options?: LLMFullCompletionOptions,
- ): AsyncGenerator
-
- // Chat methods
- chat(messages: ChatMessage[], signal: AbortSignal, options?: LLMFullCompletionOptions): Promise
-
- streamChat(
- messages: ChatMessage[],
- signal: AbortSignal,
- options?: LLMFullCompletionOptions,
- ): AsyncGenerator
-
- // Utility methods
- countTokens(text: string): number
- supportsImages(): boolean
- supportsCompletions(): boolean
- supportsFim(): boolean
- listModels(): Promise
-}
-```
-
----
-
-## LLM Adapters
-
-### OpenAI
-
-Pre-built adapter for OpenAI and OpenAI-compatible APIs.
-
-**Location**: [`core/llm/llms/OpenAI.ts`](core/llm/llms/OpenAI.ts)
-
-```typescript
-import OpenAI from "@continuedev/core/llm/llms/OpenAI"
-
-const llm = new OpenAI({
- model: "gpt-4",
- apiKey: process.env.OPENAI_API_KEY,
- apiBase: "https://api.openai.com/v1", // Optional custom base URL
- completionOptions: {
- temperature: 0.1,
- maxTokens: 1000,
- },
-})
-```
-
-**Constructor Options**:
-
-```typescript
-interface OpenAIOptions {
- model: string
- apiKey: string
- apiBase?: string
- completionOptions?: CompletionOptions
- contextLength?: number
- autocompleteOptions?: Partial
-}
-```
-
-### Creating Custom LLM Adapters
-
-To create a custom LLM adapter, implement the `ILLM` interface:
-
-```typescript
-import { ILLM, CompletionOptions } from "@continuedev/core"
-
-class CustomLLM implements ILLM {
- uniqueId = "custom-llm"
- model: string
- contextLength: number
- completionOptions: CompletionOptions
-
- get providerName() {
- return "custom"
- }
- get underlyingProviderName() {
- return "custom"
- }
-
- constructor(options: { model: string }) {
- this.model = options.model
- this.contextLength = 4096
- this.completionOptions = {
- model: options.model,
- temperature: 0.1,
- }
- }
-
- async complete(prompt: string, signal: AbortSignal): Promise {
- // Call your LLM API
- const response = await fetch("your-api-endpoint", {
- method: "POST",
- body: JSON.stringify({ prompt }),
- signal,
- })
- return await response.text()
- }
-
- async *streamComplete(prompt: string, signal: AbortSignal) {
- // Stream from your LLM API
- for await (const chunk of streamFromAPI(prompt, signal)) {
- yield chunk
- }
- return { modelTitle: this.model, prompt, completion: "" }
- }
-
- // Implement other required methods...
- countTokens(text: string): number {
- return text.length / 4 // Rough estimate
- }
-
- supportsImages() {
- return false
- }
- supportsCompletions() {
- return true
- }
- supportsFim() {
- return false
- }
-
- // ... other methods
-}
-```
-
----
-
-## Types and Interfaces
-
-### AutocompleteInput
-
-Input for autocomplete requests.
-
-```typescript
-interface AutocompleteInput {
- filepath: string // Path to the file being edited
- pos: Position // Cursor position
- completionId: string // Unique ID for this completion request
- recentlyEditedRanges: Range[] // Recently edited ranges in this file
- recentlyEditedFiles: Map // Recently edited files
- clipboardText: string // Current clipboard content
- manuallyPassFileContext?: RangeInFile[] // Manually provided context
-}
-```
-
-### AutocompleteOutcome
-
-Result of an autocomplete request.
-
-```typescript
-interface AutocompleteOutcome {
- completion: string // The completion text
- completionId: string // Unique ID for this completion
- filepath: string // File path
- prefix: string // Code prefix (before cursor)
- suffix: string // Code suffix (after cursor)
- prompt: string // Full prompt sent to LLM
- modelTitle: string // Model used
- modelProvider: string // Provider used
- completionOptions: CompletionOptions // Options used
- cacheHit: boolean // Whether cached
- latency: number // Response latency in ms
-}
-```
-
-### NextEditOutcome
-
-Result of a NextEdit prediction.
-
-```typescript
-interface NextEditOutcome {
- edits: string // The predicted edit text
- diffLines: DiffLine[] // Diff representation
- editableRegions: Range[] // Regions that were edited
- finalCursorPosition: Position // Predicted cursor position after edits
- filepath: string // File path
- prompt: string // Prompt sent to LLM
- modelTitle: string // Model used
- latency: number // Response latency in ms
-}
-```
-
-### Position
-
-Position in a text document.
-
-```typescript
-interface Position {
- line: number // 0-based line number
- character: number // 0-based character offset
-}
-```
-
-### Range
-
-Range in a text document.
-
-```typescript
-interface Range {
- start: Position // Start position (inclusive)
- end: Position // End position (exclusive)
-}
-```
-
-### RangeInFile
-
-Range in a specific file.
-
-```typescript
-interface RangeInFile {
- filepath: string // File path
- range: Range // Range within the file
-}
-```
-
-### DiffLine
-
-A single line in a diff.
-
-```typescript
-interface DiffLine {
- type: "same" | "new" | "old" // Type of change
- line: string // Line content
- lineNumber: number // Line number in file
-}
-```
-
-### FileEdit
-
-An edit to apply to a file.
-
-```typescript
-interface FileEdit {
- filepath: string // File to edit
- range: Range // Range to replace
- replacement: string // New content
-}
-```
-
----
-
-## Usage Examples
-
-### Complete Example: Autocomplete with Custom IDE
-
-```typescript
-import { CompletionProvider, MinimalConfigProvider } from "@continuedev/core/autocomplete"
-import { IDE, ILLM, Position } from "@continuedev/core"
-import OpenAI from "@continuedev/core/llm/llms/OpenAI"
-
-// 1. Implement IDE interface
-class MyIDE implements IDE {
- async readFile(filepath: string): Promise {
- return fs.readFileSync(filepath, "utf-8")
- }
-
- async getWorkspaceDirs(): Promise {
- return ["/path/to/workspace"]
- }
-
- async getCurrentFile() {
- return {
- filepath: this.currentFilePath,
- contents: await this.readFile(this.currentFilePath),
- }
- }
-
- async getCursorPosition(): Promise {
- return this.cursorPosition
- }
-
- // ... implement other methods
-}
-
-// 2. Set up configuration
-const config = new MinimalConfigProvider({
- tabAutocompleteOptions: {
- debounceDelay: 150,
- maxPromptTokens: 1024,
- useCache: true,
- },
-})
-
-// 3. Set up LLM
-const getLlm = async (): Promise => {
- return new OpenAI({
- apiKey: process.env.OPENAI_API_KEY,
- model: "gpt-4",
- })
-}
-
-// 4. Create completion provider
-const ide = new MyIDE()
-const provider = new CompletionProvider(
- config,
- ide,
- getLlm,
- (error) => console.error(error),
- async () => [], // LSP definitions function
-)
-
-// 5. Request completion
-const outcome = await provider.provideInlineCompletionItems(
- {
- filepath: "/path/to/file.ts",
- pos: { line: 10, character: 5 },
- completionId: "completion-1",
- recentlyEditedRanges: [],
- recentlyEditedFiles: new Map(),
- clipboardText: "",
- },
- new AbortController().signal,
-)
-
-if (outcome) {
- console.log("Completion:", outcome.completion)
- provider.markDisplayed("completion-1", outcome)
-}
-```
-
----
-
-## Error Handling
-
-Both `CompletionProvider` and `NextEditProvider` accept an error callback:
-
-```typescript
-const onError = (error: any) => {
- if (error instanceof Error) {
- console.error("Error:", error.message)
- // Show user notification
- showNotification(error.message)
- }
-}
-
-const provider = new CompletionProvider(
- config,
- ide,
- getLlm,
- onError, // Error callback
- getLspDefinitions,
-)
-```
-
-Common errors:
-
-- **LLM API errors**: Network failures, invalid API keys, rate limits
-- **File I/O errors**: Missing files, permission errors
-- **Timeout errors**: Long-running requests that are aborted
-
----
-
-## Performance Considerations
-
-### Caching
-
-Completions are automatically cached using an LRU cache. Configure caching:
-
-```typescript
-const config = new MinimalConfigProvider({
- tabAutocompleteOptions: {
- useCache: true, // Enable caching (default)
- },
-})
-```
-
-### Debouncing
-
-Prevent excessive LLM calls during rapid typing:
-
-```typescript
-const config = new MinimalConfigProvider({
- tabAutocompleteOptions: {
- debounceDelay: 150, // Wait 150ms before requesting (default)
- },
-})
-```
-
-### Abort Signals
-
-Always provide AbortSignals to cancel in-progress requests:
-
-```typescript
-const controller = new AbortController()
-
-// Start request
-const promise = provider.provideInlineCompletionItems(input, controller.signal)
-
-// Cancel if needed
-controller.abort()
-```
-
----
-
-## See Also
-
-- [README.md](README.md) - Project overview and quick start
-- [ARCHITECTURE.md](ARCHITECTURE.md) - Technical architecture details
-- [EXAMPLES.md](EXAMPLES.md) - More usage examples
-- [Core TypeScript Definitions](core/index.d.ts) - Full type definitions
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/CompletionProvider.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/CompletionProvider.ts
deleted file mode 100644
index 8871fc5e0fb..00000000000
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/CompletionProvider.ts
+++ /dev/null
@@ -1,272 +0,0 @@
-import { MinimalConfigProvider } from "./MinimalConfig.js"
-import { IDE, ILLM } from "../index.js"
-import { DEFAULT_AUTOCOMPLETE_OPTS } from "../util/parameters.js"
-import { shouldCompleteMultiline } from "./classification/shouldCompleteMultiline.js"
-import { ContextRetrievalService } from "./context/ContextRetrievalService.js"
-import { isSecurityConcern } from "../indexing/ignore.js"
-import { BracketMatchingService } from "./filtering/BracketMatchingService.js"
-import { CompletionStreamer } from "./generation/CompletionStreamer.js"
-import { postprocessCompletion } from "./postprocessing/index.js"
-import { shouldPrefilter } from "./prefiltering/index.js"
-import { getAllSnippetsWithoutRace } from "./snippets/index.js"
-import { renderPromptWithTokenLimit } from "./templating/index.js"
-import { GetLspDefinitionsFunction } from "./types.js"
-import { AutocompleteDebouncer } from "./util/AutocompleteDebouncer.js"
-import { AutocompleteLoggingService } from "./util/AutocompleteLoggingService.js"
-import { AutocompleteLruCacheInMem } from "./util/AutocompleteLruCacheInMem.js"
-import { HelperVars } from "./util/HelperVars.js"
-import { AutocompleteInput, AutocompleteOutcome } from "./util/types.js"
-
-// Errors that can be expected on occasion even during normal functioning should not be shown.
-// Not worth disrupting the user to tell them that a single autocomplete request didn't go through
-const ERRORS_TO_IGNORE = [
- // From Ollama
- "unexpected server status",
- "operation was aborted",
-]
-
-export class CompletionProvider {
- private autocompleteCache = AutocompleteLruCacheInMem.get()
- public errorsShown: Set = new Set()
- private bracketMatchingService = new BracketMatchingService()
- private debouncer = new AutocompleteDebouncer()
- private completionStreamer: CompletionStreamer
- private loggingService = new AutocompleteLoggingService()
- private contextRetrievalService: ContextRetrievalService
-
- constructor(
- private readonly configHandler: MinimalConfigProvider,
- private readonly ide: IDE,
- private readonly _injectedGetLlm: () => Promise,
- private readonly _onError: (e: unknown) => void,
- private readonly getDefinitionsFromLsp: GetLspDefinitionsFunction,
- ) {
- this.completionStreamer = new CompletionStreamer(this.onError.bind(this))
- this.contextRetrievalService = new ContextRetrievalService(this.ide)
- }
-
- private async _prepareLlm(): Promise {
- const llm = await this._injectedGetLlm()
-
- if (!llm) {
- return undefined
- }
-
- // Temporary fix for JetBrains autocomplete bug as described in https://github.com/continuedev/continue/pull/3022
- if (llm.model === undefined && llm.completionOptions?.model !== undefined) {
- llm.model = llm.completionOptions.model
- }
-
- // Ignore empty API keys for Mistral since we currently write
- // a template provider without one during onboarding
- if (llm.providerName === "mistral" && llm.apiKey === "") {
- return undefined
- }
-
- // Set temperature (but don't override)
- if (llm.completionOptions.temperature === undefined) {
- llm.completionOptions.temperature = 0.01
- }
-
- return llm
- }
-
- private onError(e: unknown) {
- if (
- ERRORS_TO_IGNORE.some((err) => (typeof e === "string" ? e.includes(err) : (e as Error)?.message?.includes(err)))
- ) {
- return
- }
-
- console.warn("Error generating autocompletion: ", e)
- const errorMessage = e instanceof Error ? e.message : String(e)
- if (!this.errorsShown.has(errorMessage)) {
- this.errorsShown.add(errorMessage)
- this._onError(e)
- }
- }
-
- public cancel() {
- this.loggingService.cancel()
- }
-
- public accept(completionId: string) {
- const outcome = this.loggingService.accept(completionId)
- if (!outcome) {
- return
- }
- this.bracketMatchingService.handleAcceptedCompletion(outcome.completion, outcome.filepath)
- }
-
- public markDisplayed(completionId: string, outcome: AutocompleteOutcome) {
- this.loggingService.markDisplayed(completionId, outcome)
- }
-
- private async _getAutocompleteOptions(llm: ILLM) {
- const { config } = await this.configHandler.loadConfig()
- const options = {
- ...DEFAULT_AUTOCOMPLETE_OPTS,
- ...config?.tabAutocompleteOptions,
- ...llm.autocompleteOptions,
- }
-
- // Enable static contextualization if defined.
- if (config?.experimental?.enableStaticContextualization) {
- options.experimental_enableStaticContextualization = false
- }
-
- return options
- }
-
- public async provideInlineCompletionItems(
- input: AutocompleteInput,
- token: AbortSignal | undefined,
- force?: boolean,
- ): Promise {
- try {
- // Create abort signal if not given
- if (!token) {
- const controller = this.loggingService.createAbortController(input.completionId)
- token = controller.signal
- }
- const startTime = Date.now()
-
- const llm = await this._prepareLlm()
- if (!llm) {
- return undefined
- }
-
- if (isSecurityConcern(input.filepath)) {
- return undefined
- }
-
- const options = await this._getAutocompleteOptions(llm)
-
- // Debounce
- if (!force) {
- if (await this.debouncer.delayAndShouldDebounce(options.debounceDelay)) {
- return undefined
- }
- }
-
- const helper = await HelperVars.create(input, options, llm.model, this.ide)
-
- if (await shouldPrefilter(helper, await this.ide.getWorkspaceDirs())) {
- return undefined
- }
-
- const [snippetPayload, workspaceDirs] = await Promise.all([
- getAllSnippetsWithoutRace({
- helper,
- ide: this.ide,
- getDefinitionsFromLsp: this.getDefinitionsFromLsp,
- contextRetrievalService: this.contextRetrievalService,
- }),
- this.ide.getWorkspaceDirs(),
- ])
-
- const { prompt, prefix, suffix, completionOptions } = renderPromptWithTokenLimit({
- snippetPayload,
- workspaceDirs,
- helper,
- llm,
- })
-
- // Completion
- let completion: string | undefined = ""
-
- const cache = await this.autocompleteCache
- const cachedCompletion = helper.options.useCache ? await cache.get(helper.prunedPrefix) : undefined
- let cacheHit = false
- if (cachedCompletion) {
- // Cache
- cacheHit = true
- completion = cachedCompletion
- } else {
- const multiline = !helper.options.transform || shouldCompleteMultiline(helper)
-
- const completionStream = this.completionStreamer.streamCompletionWithFilters(
- token,
- llm,
- prefix,
- suffix,
- prompt,
- multiline,
- completionOptions,
- helper,
- )
-
- for await (const update of completionStream) {
- completion += update
- }
-
- // Don't postprocess if aborted
- if (token.aborted) {
- return undefined
- }
-
- const processedCompletion = helper.options.transform
- ? postprocessCompletion({
- completion,
- prefix: helper.prunedPrefix,
- suffix: helper.prunedSuffix,
- llm,
- })
- : completion
-
- completion = processedCompletion
- }
-
- if (!completion) {
- return undefined
- }
-
- const outcome: AutocompleteOutcome = {
- time: Date.now() - startTime,
- completion,
- prefix,
- suffix,
- prompt,
- modelProvider: llm.underlyingProviderName,
- modelName: llm.model,
- completionOptions,
- cacheHit,
- filepath: helper.filepath,
- numLines: completion.split("\n").length,
- completionId: helper.input.completionId,
- gitRepo: "fake-placeholder", //MINIMAL_REPO - came from git
- uniqueId: await this.ide.getUniqueId(),
- timestamp: new Date().toISOString(),
- profileType: this.configHandler.currentProfile?.profileDescription.profileType,
- ...helper.options,
- }
-
- if (options.experimental_enableStaticContextualization) {
- outcome.enabledStaticContextualization = true
- }
-
- //////////
- // Save to cache
- if (!outcome.cacheHit && helper.options.useCache) {
- ;(await this.autocompleteCache)
- .put(outcome.prefix, outcome.completion)
- .catch((e) => console.warn(`Failed to save to cache: ${e.message}`))
- }
-
- // When using the JetBrains extension, Mark as displayed
- const ideType = (await this.ide.getIdeInfo()).ideType
- if (ideType === "jetbrains") {
- this.markDisplayed(input.completionId, outcome)
- }
-
- return outcome
- } catch (e: unknown) {
- this.onError(e)
- return undefined
- } finally {
- this.loggingService.deleteAbortController(input.completionId)
- }
- }
-}
-
-export default CompletionProvider
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/MinimalConfig.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/MinimalConfig.ts
deleted file mode 100644
index c834949ff15..00000000000
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/MinimalConfig.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- * Minimal configuration for autocomplete features.
- * This replaces the complex ConfigHandler system with simple hardcoded defaults.
- *
- * Analysis of ConfigHandler usage:
- * - CompletionProvider needs: config.tabAutocompleteOptions, config.experimental.enableStaticContextualization, currentProfile.profileType
- *
- * The profileType is only used for logging/telemetry, so we can set it to undefined for a minimal extraction.
- */
-
-import { ILLM, TabAutocompleteOptions } from "../index.js"
-import { DEFAULT_AUTOCOMPLETE_OPTS } from "../util/parameters.js"
-
-interface MinimalConfig {
- tabAutocompleteOptions?: TabAutocompleteOptions
- experimental?: {
- enableStaticContextualization?: boolean
- }
- // Minimal model selection support for NextEdit context fetching
- modelsByRole?: {
- autocomplete?: ILLM[]
- }
- selectedModelByRole?: {
- autocomplete?: ILLM
- edit?: ILLM
- chat?: ILLM
- rerank?: ILLM
- }
- rules?: unknown[]
-}
-
-interface MinimalProfile {
- profileDescription: {
- profileType?: "control-plane" | "local" | "platform"
- }
-}
-
-/**
- * Default configuration with hardcoded values suitable for autocomplete/NextEdit.
- * Uses the same defaults from DEFAULT_AUTOCOMPLETE_OPTS.
- */
-const DEFAULT_MINIMAL_CONFIG: MinimalConfig = {
- tabAutocompleteOptions: {
- ...DEFAULT_AUTOCOMPLETE_OPTS,
- },
- experimental: {
- enableStaticContextualization: false,
- },
- modelsByRole: {
- autocomplete: [],
- },
- selectedModelByRole: {
- autocomplete: undefined,
- },
-}
-
-/**
- * Simple config provider that replaces ConfigHandler for autocomplete/NextEdit.
- * Returns hardcoded configuration without dependencies on control-plane.
- */
-export class MinimalConfigProvider {
- private config: MinimalConfig
- public currentProfile: MinimalProfile | undefined
-
- constructor(config?: Partial) {
- this.config = {
- ...DEFAULT_MINIMAL_CONFIG,
- ...config,
- tabAutocompleteOptions: {
- ...DEFAULT_AUTOCOMPLETE_OPTS,
- ...config?.tabAutocompleteOptions,
- } as TabAutocompleteOptions,
- experimental: {
- ...DEFAULT_MINIMAL_CONFIG.experimental,
- ...config?.experimental,
- },
- }
-
- // Set a minimal profile for logging purposes
- // In a minimal extraction, we don't have a control-plane profile
- this.currentProfile = undefined
- }
-
- /**
- * Returns the config in the same shape as ConfigHandler.loadConfig()
- * This maintains API compatibility with existing code.
- */
- async loadConfig(): Promise<{ config: MinimalConfig }> {
- return { config: this.config }
- }
-
- /**
- * Get autocomplete options directly
- */
- getAutocompleteOptions(): TabAutocompleteOptions {
- return this.config.tabAutocompleteOptions || DEFAULT_AUTOCOMPLETE_OPTS
- }
-
- /**
- * Check if static contextualization is enabled
- */
- isStaticContextualizationEnabled(): boolean {
- return this.config.experimental?.enableStaticContextualization ?? false
- }
-
- /**
- * Reload config (stub for compatibility)
- */
- async reloadConfig(..._args: unknown[]): Promise {
- // No-op for minimal config
- }
-
- /**
- * Register config update handler (stub for compatibility)
- */
- onConfigUpdate(_handler: (event: { config: MinimalConfig; configLoadInterrupted: boolean }) => void): void {
- // No-op for minimal config
- }
-
- /**
- * Register custom context provider (stub for compatibility)
- */
- registerCustomContextProvider(_provider: unknown): void {
- // No-op for minimal config
- }
-}
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/classification/shouldCompleteMultiline.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/classification/shouldCompleteMultiline.ts
deleted file mode 100644
index e24b7008402..00000000000
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/classification/shouldCompleteMultiline.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { AutocompleteLanguageInfo } from "../constants/AutocompleteLanguageInfo"
-import { HelperVars } from "../util/HelperVars"
-
-function shouldCompleteMultilineBasedOnLanguage(language: AutocompleteLanguageInfo, prefix: string, suffix: string) {
- return language.useMultiline?.({ prefix, suffix }) ?? true
-}
-
-export function shouldCompleteMultiline(helper: HelperVars) {
- switch (helper.options.multilineCompletions) {
- case "always":
- return true
- case "never":
- return false
- default:
- break
- }
-
- // Always single-line if an intellisense option is selected
- if (helper.input.selectedCompletionInfo) {
- return true
- }
-
- // // Don't complete multi-line if you are mid-line
- // if (isMidlineCompletion(helper.fullPrefix, helper.fullSuffix)) {
- // return false;
- // }
-
- // Don't complete multi-line for single-line comments
- if (
- helper.lang.singleLineComment &&
- helper.fullPrefix.split("\n").slice(-1)[0]?.trimStart().startsWith(helper.lang.singleLineComment)
- ) {
- return false
- }
-
- return shouldCompleteMultilineBasedOnLanguage(helper.lang, helper.prunedPrefix, helper.prunedSuffix)
-}
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__fixtures__/files/typescript/arrowFunctions.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__fixtures__/files/typescript/arrowFunctions.ts
deleted file mode 100644
index cb853ef39b6..00000000000
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__fixtures__/files/typescript/arrowFunctions.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-// @ts-nocheck
-
-const getAddress = (person: Person): Address => {
- // TODO
-}
-
-const logPerson = (person: Person) => {
- // TODO
-}
-
-const getHardcodedAddress = (): Address => {
- // TODO
-}
-
-const getAddresses = (people: Person[]): Address[] => {
- // TODO
-}
-
-const logPersonWithAddres = (person: Person): Person => {
- // TODO
-}
-
-const logPersonOrAddress = (person: Person | Address): Person | Address => {
- // TODO
-}
-
-const logPersonAndAddress = (person: Person, address: Address) => {
- // TODO
-}
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__fixtures__/files/typescript/classMethods.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__fixtures__/files/typescript/classMethods.ts
deleted file mode 100644
index 08f7506a70e..00000000000
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__fixtures__/files/typescript/classMethods.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-// @ts-nocheck
-
-class Group {
- getPersonAddress(person: Person): Address {
- // TODO
- }
-
- getHardcodedAddress(): Address {
- // TODO
- }
-
- addPerson(person: Person) {
- // TODO
- }
-
- addPeople(people: Person[]) {
- // TODO
- }
-
- getAddresses(people: Person[]): Address[] {
- // TODO
- }
-
- logPersonWithAddress(person: Person): Person {
- // TODO
- }
-
- logPersonOrAddress(person: Person | Address): Person | Address {
- // TODO
- }
-
- logPersonAndAddress(person: Person, address: Address) {
- // TODO
- }
-}
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__fixtures__/files/typescript/classes.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__fixtures__/files/typescript/classes.ts
deleted file mode 100644
index 8bb8299868e..00000000000
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__fixtures__/files/typescript/classes.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-// @ts-nocheck
-
-class Group extends BaseClass {}
-
-class Group implements FirstInterface {}
-
-class Group extends BaseClass implements FirstInterface, SecondInterface {}
-
-class Group extends BaseClass implements FirstInterface {}
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__fixtures__/files/typescript/functions.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__fixtures__/files/typescript/functions.ts
deleted file mode 100644
index bb9d39c85d9..00000000000
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__fixtures__/files/typescript/functions.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-// @ts-nocheck
-
-function getAddress(person: Person): Address {
- // TODO
-}
-
-function getFirstAddress(people: Person[]): Address {
- // TODO
-}
-
-function logPerson(person: Person) {
- // TODO
-}
-
-function getHardcodedAddress(): Address {
- // TODO
-}
-
-function getAddresses(people: Person[]): Address[] {
- // TODO
-}
-
-function logPersonWithAddress(person: Person): Person {
- // TODO
-}
-
-function logPersonOrAddress(person: Person | Address): Person | Address {
- // TODO
-}
-
-function logPersonAndAddress(person: Person, address: Address) {
- // TODO
-}
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__fixtures__/files/typescript/generators.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__fixtures__/files/typescript/generators.ts
deleted file mode 100644
index 79811b47873..00000000000
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__fixtures__/files/typescript/generators.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-// @ts-nocheck
-
-function* getAddress(person: Person): Address {
- // TODO
-}
-
-function* getFirstAddress(people: Person[]): Address {
- // TODO
-}
-
-function* logPerson(person: Person) {
- // TODO
-}
-
-function* getHardcodedAddress(): Address {
- // TODO
-}
-
-function* getAddresses(people: Person[]): Address[] {
- // TODO
-}
-
-function* logPersonWithAddress(person: Person): Person {
- // TODO
-}
-
-function* logPersonOrAddress(person: Person | Address): Person | Address {
- // TODO
-}
-
-function* logPersonAndAddress(person: Person, address: Address) {
- // TODO
-}
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__test-cases__/python.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__test-cases__/python.ts
index 68a29efbfa1..cb1c48318c1 100644
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__test-cases__/python.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/root-path-context/__test-cases__/python.ts
@@ -1,98 +1,3 @@
-export const FUNCTIONS = [
- {
- nodeType: "function_definition with argument and return type",
- fileName: "python/functions.py",
- language: "Python",
- cursorPosition: { line: 15, character: 8 },
- definitionPositions: [
- { row: 14, column: 30 }, // Person
- { row: 14, column: 42 }, // Address
- ],
- },
- {
- nodeType: "function_definition with generic argument and generic return type",
- fileName: "python/functions.py",
- language: "Python",
- cursorPosition: { line: 18, character: 8 },
- definitionPositions: [
- { row: 17, column: 35 }, // Group
- { row: 17, column: 42 }, // Person
- { row: 17, column: 53 }, // Group
- { row: 17, column: 61 }, // Address
- ],
- },
- {
- nodeType: "function_definition with single argument and None return type",
- fileName: "python/functions.py",
- language: "Python",
- cursorPosition: { line: 21, character: 8 },
- definitionPositions: [
- { row: 20, column: 29 }, // Person
- ],
- },
- {
- nodeType: "function_definition with no arguments and single return type",
- fileName: "python/functions.py",
- language: "Python",
- cursorPosition: { line: 24, character: 8 },
- definitionPositions: [
- { row: 23, column: 38 }, // Address
- ],
- },
- {
- nodeType: "function_definition with Union arguments and Union return type",
- fileName: "python/functions.py",
- language: "Python",
- cursorPosition: { line: 27, character: 8 },
- definitionPositions: [
- { row: 26, column: 45 }, // Person
- { row: 26, column: 54 }, // Address
- { row: 26, column: 72 }, // Person
- { row: 26, column: 81 }, // Address
- ],
- },
- {
- nodeType: "function_definition with multiple arguments and None return type",
- fileName: "python/functions.py",
- language: "Python",
- cursorPosition: { line: 30, character: 8 },
- definitionPositions: [
- { row: 29, column: 41 }, // Person
- { row: 29, column: 59 }, // Address
- ],
- },
- {
- nodeType: "function_definition with one argument and Generator return type",
- fileName: "python/functions.py",
- language: "Python",
- cursorPosition: { line: 33, character: 9 },
- definitionPositions: [
- { row: 32, column: 40 }, // Person
- { row: 32, column: 62 }, // Address
- ],
- },
- {
- nodeType: "function_definition inside a class",
- fileName: "python/functions.py",
- language: "Python",
- cursorPosition: { line: 38, character: 12 },
- definitionPositions: [
- { row: 37, column: 51 }, // Person
- { row: 37, column: 69 }, // Address
- ],
- },
- {
- nodeType: "function_definition of an async function",
- fileName: "python/functions.py",
- language: "Python",
- cursorPosition: { line: 41, character: 8 },
- definitionPositions: [
- { row: 40, column: 37 }, // Address
- { row: 40, column: 48 }, // Person
- ],
- },
-]
-
export const CLASSES = [
{
nodeType: "class_definition with multiple superclasses",
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/StreamTransformPipeline.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/StreamTransformPipeline.ts
deleted file mode 100644
index 28bedc73429..00000000000
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/StreamTransformPipeline.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { streamLines } from "../../../diff/util"
-import { HelperVars } from "../../util/HelperVars"
-
-import { stopAtStartOf, stopAtStopTokens } from "./charStream"
-import {
- avoidEmptyComments,
- avoidPathLine,
- noDoubleNewLine,
- showWhateverWeHaveAtXMs,
- skipPrefixes,
- stopAtLines,
- stopAtLinesExact,
- stopAtRepeatingLines,
- stopAtSimilarLine,
- streamWithNewLines,
-} from "./lineStream"
-
-const STOP_AT_PATTERNS = ["diff --git"]
-
-export class StreamTransformPipeline {
- async *transform(
- generator: AsyncGenerator,
- prefix: string,
- suffix: string,
- multiline: boolean,
- stopTokens: string[],
- fullStop: () => void,
- helper: HelperVars,
- ): AsyncGenerator {
- let charGenerator = generator
-
- charGenerator = stopAtStopTokens(generator, [...stopTokens, ...STOP_AT_PATTERNS])
- charGenerator = stopAtStartOf(charGenerator, suffix)
- for (const charFilter of helper.lang.charFilters ?? []) {
- charGenerator = charFilter({
- chars: charGenerator,
- prefix,
- suffix,
- filepath: helper.filepath,
- multiline,
- })
- }
-
- let lineGenerator = streamLines(charGenerator)
-
- lineGenerator = stopAtLines(lineGenerator, fullStop)
- const lineBelowCursor = this.getLineBelowCursor(helper)
- if (lineBelowCursor.trim() !== "") {
- lineGenerator = stopAtLinesExact(lineGenerator, fullStop, [lineBelowCursor])
- }
- lineGenerator = stopAtRepeatingLines(lineGenerator, fullStop)
- lineGenerator = avoidEmptyComments(lineGenerator, helper.lang.singleLineComment)
- lineGenerator = avoidPathLine(lineGenerator, helper.lang.singleLineComment)
- lineGenerator = skipPrefixes(lineGenerator)
- lineGenerator = noDoubleNewLine(lineGenerator)
-
- for (const lineFilter of helper.lang.lineFilters ?? []) {
- lineGenerator = lineFilter({ lines: lineGenerator, fullStop })
- }
-
- lineGenerator = stopAtSimilarLine(lineGenerator, this.getLineBelowCursor(helper), fullStop)
-
- const timeoutValue = helper.options.modelTimeout
-
- lineGenerator = showWhateverWeHaveAtXMs(lineGenerator, timeoutValue!)
-
- const finalGenerator = streamWithNewLines(lineGenerator)
- for await (const update of finalGenerator) {
- yield update
- }
- }
-
- private getLineBelowCursor(helper: HelperVars): string {
- let lineBelowCursor = ""
- let i = 1
- while (lineBelowCursor.trim() === "" && helper.pos.line + i <= helper.fileLines.length - 1) {
- lineBelowCursor = helper.fileLines[Math.min(helper.pos.line + i, helper.fileLines.length - 1)]
- i++
- }
- return lineBelowCursor
- }
-}
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/charStream.test.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/charStream.test.ts
deleted file mode 100644
index 488798b60f8..00000000000
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/charStream.test.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-import { describe, expect, it } from "vitest"
-import { stopAtStartOf, stopAtStopTokens } from "./charStream"
-
-async function* createMockStream(chunks: string[]): AsyncGenerator {
- for (const chunk of chunks) {
- yield chunk
- }
-}
-
-async function streamToString(stream: AsyncGenerator): Promise {
- let result = ""
- for await (const chunk of stream) {
- result += chunk
- }
- return result
-}
-
-describe("stopAtStopTokens", () => {
- it("should yield characters until a stop token is encountered", async () => {
- const mockStream = createMockStream(["Hello", " world", "! Stop", "here"])
- const stopTokens = ["Stop"]
- const result = stopAtStopTokens(mockStream, stopTokens)
-
- const output = []
- for await (const char of result) {
- output.push(char)
- }
-
- expect(output.join("")).toBe("Hello world! ")
- })
-
- it("should handle multiple stop tokens", async () => {
- const mockStream = createMockStream(["This", " is a ", "test. END", " of stream"])
- const stopTokens = ["END", "STOP", "HALT"]
- const result = stopAtStopTokens(mockStream, stopTokens)
-
- expect(await streamToString(result)).toBe("This is a test. ")
- })
-
- it("should handle stop tokens split across chunks", async () => {
- const mockStream = createMockStream(["Hello", " wo", "r", "ld! ST", "OP now"])
- const stopTokens = ["STOP"]
- const result = stopAtStopTokens(mockStream, stopTokens)
-
- const output = []
- for await (const char of result) {
- output.push(char)
- }
-
- expect(output.join("")).toBe("Hello world! ")
- })
-
- it("should yield all characters if no stop token is encountered", async () => {
- const mockStream = createMockStream(["This", " is ", "a complete", " stream"])
- const stopTokens = ["END"]
- const result = stopAtStopTokens(mockStream, stopTokens)
-
- expect(await streamToString(result)).toBe("This is a complete stream")
- })
-
- it("should handle empty chunks", async () => {
- const mockStream = createMockStream(["Hello", "", " world", "", "! STOP"])
- const stopTokens = ["STOP"]
- const result = stopAtStopTokens(mockStream, stopTokens)
-
- expect(await streamToString(result)).toBe("Hello world! ")
- })
-
- it("should handle stop token at the beginning of the stream", async () => {
- const mockStream = createMockStream(["STOP", "Hello world"])
- const stopTokens = ["STOP"]
- const result = stopAtStopTokens(mockStream, stopTokens)
-
- const output = []
- for await (const char of result) {
- output.push(char)
- }
-
- expect(output.join("")).toBe("")
- })
-
- it("should handle stop token at the end of the stream", async () => {
- const mockStream = createMockStream(["Hello world", "STOP"])
- const stopTokens = ["STOP"]
- const result = stopAtStopTokens(mockStream, stopTokens)
-
- expect(await streamToString(result)).toBe("Hello world")
- })
-
- it("should handle multiple stop tokens of different lengths", async () => {
- const mockStream = createMockStream(["This is a ", "test with ", "multiple STOP", " tokens END"])
- const stopTokens = ["STOP", "END", "HALT"]
- const result = stopAtStopTokens(mockStream, stopTokens)
-
- expect(await streamToString(result)).toBe("This is a test with multiple ")
- })
-
- it("should handle an empty stream", async () => {
- const mockStream = createMockStream([])
- const stopTokens = ["STOP"]
- const result = stopAtStopTokens(mockStream, stopTokens)
-
- expect(await streamToString(result)).toBe("")
- })
-
- it("should handle an empty stop tokens array", async () => {
- const mockStream = createMockStream(["Hello", " world!"])
- const stopTokens: string[] = []
- const result = stopAtStopTokens(mockStream, stopTokens)
-
- const output = []
- for await (const char of result) {
- output.push(char)
- }
-
- expect(output.join("")).toBe("Hello world!")
- })
-
- it("should handle stop token when remaining buffer is smaller than maximum stop token length", async () => {
- const mockStream = createMockStream(["Hello world!STOP"])
- const stopTokens: string[] = ["STOP", "STOP_TOKEN_THAT_IS_LARGER_THAN_BUFFER"]
- const result = stopAtStopTokens(mockStream, stopTokens)
-
- expect(await streamToString(result)).toBe("Hello world!")
- })
-})
-
-describe("stopAtStartOf", () => {
- const sampleCode = ` {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- Authorization: \`Bearer \${this.workOsAccessToken}\`,
- },
- },
- );
- const data = await response.json();
- return data.items;
- }
-
- async getContextItems(
- query: string,
- extras: ContextProviderExtras,
- ): Promise {
- const response = await extras.fetch(
- new URL(
- \`/proxy/context/\${this.options.id}/retrieve\`,
- controlPlaneEnv.CONTROL_PLANE_URL,
- ),
-`
-
- /* Some LLMs, such as Codestral, repeat the suffix of the query. To test our filtering, we cut the sample code at random positions, remove a part of the input
-and construct a response, containing the removed part and the suffix. The goal of the stopAtStartOf() method is to detect the start of the suffix in the response */
- it("should stop if the start of the suffix is reached", async () => {
- const suffix = `
- const data = await response.json();
- return data.items;
-}`
- const mockStream = createMockStream(sampleCode.split(/(?! )/g))
- const result = stopAtStartOf(mockStream, suffix)
-
- const resultStr = await streamToString(result)
- expect(resultStr).toBe(` {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- Authorization: \`Bearer \${this.workOsAccessToken}\`,
- },
- },
- );
- `)
- })
- it("should stop if the start of the suffix is reached, even if the suffix has a prefix", async () => {
- const suffix = `
- xxxconst data = await response.json();
- return data.items;
-}`
- const mockStream = createMockStream(sampleCode.split(/(?! )/g))
- const result = stopAtStartOf(mockStream, suffix)
-
- const resultStr = await streamToString(result)
- expect(resultStr).toBe(` {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- Authorization: \`Bearer \${this.workOsAccessToken}\`,
- },
- },
- );
- `)
- })
-})
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/charStream.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/charStream.ts
deleted file mode 100644
index d19bed609ca..00000000000
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/charStream.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-/**
- * Asynchronously yields characters from the input stream, stopping if a stop token is encountered.
- *
- * @param {AsyncGenerator} stream - The input stream of characters.
- * @param {string[]} stopTokens - Array of tokens that signal when to stop yielding.
- * @yields {string} Characters from the input stream.
- * @returns {AsyncGenerator} An async generator that yields characters until a stop condition is met.
- * @description
- * 1. If no stop tokens are provided, yields all characters from the stream.
- * 2. Otherwise, buffers incoming chunks and checks for stop tokens.
- * 3. Yields characters one by one if no stop token is found at the start of the buffer.
- * 4. Stops yielding and returns if a stop token is encountered.
- * 5. After the stream ends, filters encountered stop tokens in remaining buffer.
- * 6. Yields any remaining buffered characters.
- */
-export async function* stopAtStopTokens(stream: AsyncGenerator, stopTokens: string[]): AsyncGenerator {
- if (stopTokens.length === 0) {
- for await (const char of stream) {
- yield char
- }
- return
- }
-
- const maxStopTokenLength = Math.max(...stopTokens.map((token) => token.length))
- let buffer = ""
-
- for await (const chunk of stream) {
- buffer += chunk
-
- while (buffer.length >= maxStopTokenLength) {
- let found = false
- for (const stopToken of stopTokens) {
- if (buffer.startsWith(stopToken)) {
- found = true
- return
- }
- }
-
- if (!found) {
- yield buffer[0]
- buffer = buffer.slice(1)
- }
- }
- }
- // Filter out the possible stop tokens from remaining buffer
- stopTokens.forEach((token) => {
- buffer = buffer.replace(token, "")
- })
-
- // Yield any remaining characters in the buffer
- for (const char of buffer) {
- yield char
- }
-}
-
-/**
- * Asynchronously yields characters from the input stream.
- * Stops if the beginning of the suffix is detected in the stream.
- */
-export async function* stopAtStartOf(
- stream: AsyncGenerator,
- suffix: string,
- sequenceLength: number = 20,
-): AsyncGenerator {
- if (suffix.length < sequenceLength) {
- for await (const chunk of stream) {
- yield chunk
- }
- return
- }
- // We use sequenceLength * 1.5 as a heuristic to make sure we don't miss the sequence if the
- // stream is not perfectly aligned with the sequence (small whitespace differences etc).
- const targetPart = suffix.trimStart().slice(0, Math.floor(sequenceLength * 1.5))
-
- let buffer = ""
-
- for await (const chunk of stream) {
- buffer += chunk
-
- // Check if the targetPart contains contains the buffer at any point
- if (buffer.length >= sequenceLength && targetPart.includes(buffer)) {
- return // Stop processing when the sequence is found
- }
-
- // Yield chunk by chunk, ensuring not to exceed sequenceLength in the buffer
- while (buffer.length > sequenceLength) {
- yield buffer[0]
- buffer = buffer.slice(1)
- }
- }
-
- // Yield the remaining buffer if it is not contained in the `targetPart`
- if (buffer.length > 0) {
- yield buffer
- }
-}
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/lineStream.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/lineStream.ts
index 2a0be704a67..a4cc5e15265 100644
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/lineStream.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/streamTransforms/lineStream.ts
@@ -1,4 +1,4 @@
-import { LineStream } from "../../../diff/util"
+import type { LineStream } from "../../../diff/util"
import { lineIsRepeated } from "../../util/textSimilarity"
export { lineIsRepeated }
@@ -177,19 +177,6 @@ export async function* stopAtLines(
}
}
-/**
- * Yield until an exact stop line is encountered, then call fullStop.
- */
-export async function* stopAtLinesExact(stream: LineStream, fullStop: () => void, linesToStopAt: string[]): LineStream {
- for await (const line of stream) {
- if (linesToStopAt.some((stopAt) => line === stopAt)) {
- fullStop()
- break
- }
- yield line
- }
-}
-
/**
* On the first line only, strip any configured prefix (e.g. "").
*/
@@ -230,41 +217,3 @@ export async function* stopAtRepeatingLines(lines: LineStream, fullStop: () => v
previousLine = line
}
}
-
-/**
- * Pass through lines, but if the stream takes longer than ms after we have at least one non-empty line, stop early.
- */
-export async function* showWhateverWeHaveAtXMs(lines: LineStream, ms: number): LineStream {
- const startTime = Date.now()
- let firstNonWhitespaceLineYielded = false
-
- for await (const line of lines) {
- yield line
-
- if (!firstNonWhitespaceLineYielded && line.trim() !== "") {
- firstNonWhitespaceLineYielded = true
- }
-
- const isTakingTooLong = Date.now() - startTime > ms
- if (isTakingTooLong && firstNonWhitespaceLineYielded) {
- break
- }
- }
-}
-
-/**
- * Yield lines until the first blank line after some content; then stop.
- */
-export async function* noDoubleNewLine(lines: LineStream): LineStream {
- let isFirstLine = true
-
- for await (const line of lines) {
- if (line.trim() === "" && !isFirstLine) {
- return
- }
-
- isFirstLine = false
-
- yield line
- }
-}
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/test/NEGATIVE_TEST_CASES/QWEN_JSON.txt b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/test/NEGATIVE_TEST_CASES/QWEN_JSON.txt
deleted file mode 100644
index ff0ba3cb39a..00000000000
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/test/NEGATIVE_TEST_CASES/QWEN_JSON.txt
+++ /dev/null
@@ -1,60 +0,0 @@
-##### Prompt #####
-{
- "active": true,
- "department": "Product Development",
- "location": {
- "country": "USA",
- "state": "California",
- "city": "San BERNARDINO",
- "coordinates": {
-
- }
- },
- "employees": [
- {
- "name": "John Doe",
- "age": 30,
- "position": "Developer",
- "skills": ["JavaScript", "React", "Node.js"],
- "remote": false,
- "salary": {
- "currency": "USD",
- "amount": 95000
- }
- },
- {
- "name": "Jane Smith",
- "age": 25,
- "position": "Designer",
- "skills": ["Photoshop", "Illustrator"],
- "remote": true,
- "salary": {
- "currency": "USD",
- "amount": 70000
- }
- },
- {
- "name": "Emily Johnson",
- "age": 35,
- "position": "Manager",
- "teamSize": 8,
- "remote": true,
- "skills": ["Leadership", "Project Management"],==========================================================================
-==========================================================================
-Completion:
-
- "latitude": 34.10834,
- "longitude": -117.28977
- }
- },
- "employeeCount": 2,
- "averageAge": 30,
- "remoteFriendly": true,
- "salaryRange": {
- "min": 70000,
- "max": 95000,
- "currency": "USD"
- },
- "skills": {
- "required": ["JavaScript", "React", "Node.js", "Leadership", "Project Management"],
- "optional": ["Photoshop", "Illustrator"]
\ No newline at end of file
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/test/NEGATIVE_TEST_CASES/QWEN_TYPESCRIPT.txt b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/test/NEGATIVE_TEST_CASES/QWEN_TYPESCRIPT.txt
deleted file mode 100644
index 241d141f28b..00000000000
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/filtering/test/NEGATIVE_TEST_CASES/QWEN_TYPESCRIPT.txt
+++ /dev/null
@@ -1,128 +0,0 @@
-##### Prompt #####
- }`,
- },
- {
- description: "Should autocomplete Vue computed property",
- filename: "UserComponent.vue",
- input: `
-
- );
-};`,
- llmOutput: "0",
- expectedCompletion: "0",
- filename: "Counter.tsx",
- },
- {
- description: "Should autocomplete React component methods",
- input: `import React from "react";
-
-class Form extends React.Component {
- constructor(props) {
- super(props);
- this.state = { name: '' };
- }
-
- handleChange = (event) => {
- <|fim|>
- }
-
- handleSubmit = (event) => {
- event.preventDefault();
- alert('A name was submitted: ' + this.state.name);
- }
-
- render() {
- return (
-
- );
- }
-}`,
- llmOutput: "this.setState({ name: event.target.value });",
- expectedCompletion: "this.setState({ name: event.target.value });",
- filename: "Form.tsx",
- },
- {
- description: "Should autocomplete Python function definition",
- filename: "test.py",
- input: `def calculate_area(length, width):
- area = length * width
- return area
-
-def calculate_perimeter(length, width):
- <|fim|>
-`,
- llmOutput: `perimeter = 2 * (length + width)
- return perimeter`,
- expectedCompletion: `perimeter = 2 * (length + width)
- return perimeter`,
- },
- {
- description: "Should complete Python class method with self",
- filename: "test.py",
- input: `class BankAccount:
- def __init__(self, owner, balance=0):
- self.owner = owner
- self.balance = balance
-
- def deposit(self, amount):
- self.balance += amount
- return self.balance
-
- def withdraw(self, amount):
- <|fim|>
-`,
- llmOutput: `if amount > self.balance:
- return "Insufficient funds"
- self.balance -= amount
- return self.balance`,
- expectedCompletion: `if amount > self.balance:
- return "Insufficient funds"
- self.balance -= amount
- return self.balance`,
- },
- {
- description: "Should autocomplete Python list comprehension",
- filename: "test.py",
- input: `squares = [x**2 for x in range(10)]
-even_squares = [x for x in squares if x % 2 == 0]
-print(even_squares)
-
-odd_squares = [<|fim|>
-print(odd_squares)
-`,
- llmOutput: "x for x in squares if x % 2 != 0",
- expectedCompletion: "x for x in squares if x % 2 != 0",
- },
- {
- description: "Should autocomplete a simple Go function declaration",
- filename: "simpleFunction.go",
- input: `package main
-
-import (
- "fmt"
-)
-
-func main() {
- fmt.Println("Hello, World!")
-}
-
-func calculateArea<|fim|>`,
- llmOutput: `(length float64, width float64) float64 {
- return length * width
-}`,
- expectedCompletion: `(length float64, width float64) float64 {
- return length * width
-}`,
- },
-
- {
- description: "Should handle autocomplete in the middle of a Go struct definition",
- filename: "structDefinition.go",
- input: `package main
-
-type Person struct {
- FirstName string
- LastName string
- Age int
- Address Address
-}
-
-type Address struct {
- Street string
- City <|fim|>
-}
-
-func main() {}`,
- llmOutput: `string
- State string
- ZipCode string
-}`,
- expectedCompletion: `string
- State string
- ZipCode string`,
- },
-
- {
- description: "Should autocomplete a missing Go function body bracket",
- filename: "missingBracket.go",
- input: `package main
-
-func add(a int, b int) int {
- return a + b<|fim|>
-
-func multiply(a int, b int) int {
- return a * b
-}
-
-func main() {
- result1 := add(2, 3)
- result2 := multiply(4, 5)
- println(result1, result2)
-}`,
- llmOutput: `
-}`,
- expectedCompletion: `
-}`,
- },
- {
- description: "Should autocomplete SQL query with nested functions and missing bracket",
- filename: "_nested_function.sql",
- input: `SELECT name, ROUND(AVG(rating), 2) as avg_rating
- FROM movies
- WHERE release_year > 2000 AND director = 'Christopher Nolan'
- GROUP BY name
- HAVING avg_rating > <|fim|>`,
- llmOutput: "8.5)",
- expectedCompletion: "8.5)",
- },
- {
- description: "Should autocomplete SQL script with employee and product tables",
- filename: "database.sql",
- input: `
- CREATE TABLE employees (
- id INT AUTO_INCREMENT PRIMARY KEY,
- name VARCHAR(100) NOT NULL,
- age INT NOT NULL,
- position VARCHAR(100)
- );
-
- CREATE TABLE products (
- id INT AUTO_INCREMENT PRIMARY KEY,
- name VARCHAR(100),
- price DECIMAL(8,2) NOT NULL<|fim|> '0.00',
- quantity INT NOT NULL DEFAULT '0'
- );
-
- INSERT INTO employees (name, age, position) VALUES ('John Doe', 30, 'Developer');
- INSERT INTO products (name, price, quantity) VALUES ('Apple', '1.99', '47');
-
- SELECT * FROM products ORDER BY name DESC LIMIT 3;
- SELECT * FROM products WHERE price > '0';
- SELECT * FROM products WHERE quantity > '100';
- SELECT * FROM employees WHERE age > 25;
- `,
- llmOutput: " DEFAULT",
- expectedCompletion: " DEFAULT",
- },
- {
- description: "Should autocomplete multi-line SQL query with CASE statements",
- filename: "case_statement.sql",
- input: `SELECT order_id,
- order_date,
- CASE
- WHEN status = 'shipped' THEN 'Completed'
- WHEN status = 'pending' THEN 'Pending Approval'
- <|fim|>
- ELSE 'Unknown'
- END as order_status
- FROM orders`,
- llmOutput: "WHEN status = 'cancelled' THEN 'Cancelled'",
- expectedCompletion: "WHEN status = 'cancelled' THEN 'Cancelled'",
- },
- {
- description: "Should autocomplete HTML paragraph content",
- input: `
-
- Document
-
-
-
<|fim|>
-
-`,
- llmOutput: "This is a paragraph with some sample text.",
- expectedCompletion: "This is a paragraph with some sample text.",
- filename: "test.html",
- },
- {
- description: "Should autocomplete HTML attributes within a tag",
- input: `
- >
-
-
Title
-
Description text.
-
-
`,
- llmOutput: 'alt="Description of image"',
- expectedCompletion: 'alt="Description of image"',
- filename: "test.html",
- },
- {
- description: "Should autocomplete HTML nested tags",
- input: `
-
Item 1
-
Item 2
-
Item 3
-
`,
- llmOutput: ">Item 4",
- expectedCompletion: ">Item 4",
- filename: "test.html",
- },
-
- {
- description: "Should complete a class method in Ruby",
- input: `
-class Greeter
- def initialize(name)
- @name = name
- end
-
- def greet
- puts "Hello, <|fim|>
- end
-end
-
-g = Greeter.new("World")
-g.greet
-`,
- llmOutput: "#{@name}!",
- expectedCompletion: "#{@name}!",
- filename: "greeter.rb",
- },
- {
- description: "Should complete Ruby if-else block",
- input: `
-number = 10
-
-if number > 5
- puts "Number is greater than 5"
-<|fim|>
-end
-`,
- llmOutput: `else
- puts "Number is 5 or less"`,
- expectedCompletion: `else
- puts "Number is 5 or less"`,
- filename: "conditional.rb",
- },
- {
- description: "Should complete Ruby array method",
- input: `
-numbers = [1, 2, 3, 4, 5]
-squared_numbers = numbers.<|fim|>
-`,
- llmOutput: "map { |n| n ** 2 }",
- expectedCompletion: "map { |n| n ** 2 }",
- filename: "array_methods.rb",
- },
- {
- description: "Should autocomplete Java within a string",
- filename: "App.java",
- input: `
-public class App {
- public static void main(String[] args) {
- String message = "Hello, <|fim|>";
- System.out.println(message);
- }
-}`,
- llmOutput: "World",
- expectedCompletion: "World",
- },
- {
- description: "Should autocomplete a C++ class definition with a constructor",
- filename: "test.cpp",
- input: "class Vehicle { public: Vehicle(<|fim|>); };",
- llmOutput: "int wheels, double weight",
- expectedCompletion: "int wheels, double weight",
- },
- {
- description: "Should complete a C++ method declaration inside a class",
- filename: "test.cpp",
- input: "class Calculator { public: int add(int a, int b); int subtract(int a, int b);<|fim|> };",
- llmOutput: " int multiply(int a, int b);",
- expectedCompletion: " int multiply(int a, int b);",
- },
- {
- description: "Should autocomplete C++ for loop syntax",
- filename: "test.cpp",
- input: `
- int sum = 0;
- for (int i = 0; i < 10; <|fim|>) {
- sum += i;
- }
- `,
- llmOutput: "i++",
- expectedCompletion: "i++",
- },
- {
- description: "Should autocomplete JSON object inside an array",
- filename: "data.json",
- input: `{
- "users": [
- { "id": 1, "name": "Alice" },
- { "id": 2, "name": "Bob" }
- <|fim|>
- ]
-}`,
- llmOutput: ', { "id": 3, "name": "Charlie" }',
- expectedCompletion: ', { "id": 3, "name": "Charlie" }',
- },
- {
- description: "Should autocomplete within a CSV record",
- filename: "test.csv",
- input: `Name, Age, City
-John Doe, 30, New York
-Jane Smith<|fim|>`,
- llmOutput: ", 25, Los Angeles",
- expectedCompletion: ", 25, Los Angeles",
- },
- {
- description: "Should complete CSV record when starting in the middle of a word",
- filename: "test.csv",
- input: `Product, Price, Quantity
-Laptop, 1200, 5
-Smart<|fim|>`,
- llmOutput: "phone, 800, 10",
- expectedCompletion: "phone, 800, 10",
- },
- {
- description: "Should complete CSV structure adding closing brackets",
- filename: "test.csv",
- input: `ID, Name, JoiningDate
-1, Alice, 2023-01-10
-2, B<|fim|>`,
- llmOutput: "ob, 2023-02-10",
- expectedCompletion: "ob, 2023-02-10",
- },
- {
- description: "Should autocomplete a Rust function implementation inside a struct",
- filename: "main.rs",
- input: `
-struct Calculator {
- result: f64,
-}
-
-impl Calculator {
- fn new() -> Self {
- Calculator { result: 0.0 }
- }
-
- fn add(&mut self, number: f64) {
- self.result += number;
- }
-
- fn subtract(&mut self, number: f64) {
- self.result -= number;
- }
-
- fn multiply(&mut self, number: f64) {
- self.result *= number;
- }
-
- fn divide(&mut self, number: f64) {
- if number != 0.0 {
- self.result /= number;
- } else {
- println!("Cannot divide by zero.");
- }
- }
-
- fn reset(&mut self) {
- self.result = 0.0;
- }
-
- fn get_result(&self) -> f64 {
- self.result
- }
-
- fn<|fim|>
-}
-`,
- llmOutput: ` divide(&mut self, number: f64) {
- if number != 0.0 {
- self.result /= number;
- } else {
- println!("Cannot divide by zero.");
- }
-}`,
- expectedCompletion: ` divide(&mut self, number: f64) {
- if number != 0.0 {
- self.result /= number;
- } else {
- println!("Cannot divide by zero.");
- }`,
- },
- {
- description: "Should autocomplete Rust struct definition",
- filename: "main.rs",
- input: `
-struct User {
- id: u32,
- username: String,
- email: String,
- is_active: bool,
- <|fim|>
-}
-
-impl User {
- fn new(id: u32, username: String, email: String) -> Self {
- User {
- id,
- username,
- email,
- is_active: true,
- }
- }
-}
-`,
- llmOutput: `created_at: String,
- updated_at: String,`,
- expectedCompletion: `created_at: String,
- updated_at: String,`,
- },
- {
- description: "Haskell: Nested pattern matching with let bindings",
- filename: "NestedPattern.hs",
- input: `module NestedPattern where
-
-data Tree a = Leaf a | Node (Tree a) (Tree a)
-
-sumTree :: Num a => Tree a -> a
-sumTree (Leaf x) = x
-sumTree (Node left right) =
- let leftSum = <|fim|>
- rightSum = sumTree right
- in leftSum + rightSum`,
- llmOutput: "sumTree left",
- expectedCompletion: "sumTree left",
- },
- {
- description: "Haskell: Complex function with where clause and guards",
- filename: "QuadraticSolver.hs",
- input: `module QuadraticSolver where
-
-solveQuadratic :: (Ord a, Floating a) => a -> a -> a -> Maybe (a, a)
-solveQuadratic a b c
- | discriminant < 0 = Nothing
- | otherwise = Just (x1, x2)
- where
- discriminant = b^2 - 4*a*c
- sqrtD = sqrt discriminant
- x1 = (-b + sqrtD) / (2*a)
- <|fim|> = (-b - sqrtD) / (2*a)`,
- llmOutput: "x2",
- expectedCompletion: "x2",
- },
- {
- description: "Haskell: List comprehension with complex filter",
- filename: "PrimeNumbers.hs",
- input: `module PrimeNumbers where
-
-primesUpTo :: Int -> [Int]
-primesUpTo n = [x | x <- [2..n], isPrime x]
- where isPrime num = <|fim|> && all (\\d -> num \`mod\` d /= 0) [2..(floor . sqrt $ fromIntegral num)]`,
- llmOutput: "num > 1",
- expectedCompletion: "num > 1",
- },
- {
- description: "Should autocomplete Dart class methods",
- filename: "calculator.dart",
- input: `
-class Calculator {
- double result = 0.0;
-
- void add(double number) {
- result += number;
- }
-
- void multiply(double number) {
- result *= number;
- }
-
- <|fim|>
-
- double getResult() {
- return result;
- }
-}`,
- llmOutput: `void subtract(double number) {
- result -= number;
- }`,
- expectedCompletion: `void subtract(double number) {
- result -= number;
- }`,
- },
- {
- description: "Should handle string interpolation in Dart",
- filename: "greetings.dart",
- input: `
-void main() {
- var name = "World";
- print('Hello, <|fim|>!');
-}`,
- llmOutput: "${name}",
- expectedCompletion: "${name}",
- },
- {
- description: "Should autocomplete within a Dart function body",
- filename: "counter.dart",
- input: `
-class Counter {
- int count = 0;
-
- void increment() {
- count++;
- }
-
- void decrement() {
- <|fim|>
-
- void reset() {
- count = 0;
- }
-}`,
- llmOutput: "count--;",
- expectedCompletion: "count--;",
- },
- {
- description: "Should autocomplete Clojure function definition with missing closing parenthesis",
- input: `(defn calculate-sum [a b]
- (let [sum (+ a b)]
- (println "The sum is" sum)
- sum<|fim|>`,
- llmOutput: "))",
- expectedCompletion: "))",
- filename: "test.clj",
- },
- {
- description: "Should autocomplete missing part of a Clojure map within a function",
- input: `(defn get-user []
- {:username "johndoe"
- :email "johndoe@example.com"
- :age 30
- <|fim|>
- (println "User information loaded"))`,
- llmOutput: ':location "Unknown"}',
- expectedCompletion: ':location "Unknown"}',
- filename: "test.clj",
- },
- {
- description: "Should autocomplete inside a Clojure vector within a looping construct",
- input: `(defn odd-numbers []
- (loop [nums [1 3 5<|fim|> 9 11]]
- (when (seq nums)
- (println (first nums))
- (recur (rest nums)))))`,
- llmOutput: " 7,",
- expectedCompletion: " 7,",
- filename: "test.clj",
- },
- {
- description: "Should autocomplete R function definition",
- filename: "calculate.R",
- input: `
-calculate_mean <- function(numbers) {
- total <- sum(numbers)
- <|fim|>
-}`,
- llmOutput: `mean_value <- total / length(numbers)
- return(mean_value)`,
- expectedCompletion: `mean_value <- total / length(numbers)
- return(mean_value)`,
- },
- {
- description: "Should complete R loop and print statement",
- filename: "loopPrint.R",
- input: `
-numbers <- c(1, 2, 3, 4, 5)
-for (number in numbers) {
- print(<|fim|>)
-}`,
- llmOutput: "number)",
- expectedCompletion: "number)",
- },
- {
- description: "Should autocomplete R data frame creation",
- filename: "dataFrame.R",
- input: `
-data <- data.frame(
- Name = c("Alice", "Bob", "Charlie"),
- Age = c(25, 30, 35),
- <|fim|>
-)`,
- llmOutput: "Height = c(165, 180, 175)",
- expectedCompletion: "Height = c(165, 180, 175)",
- },
- {
- description: "Should autocomplete R if-else statement",
- filename: "condition.R",
- input: `
-grade <- 85
-if (grade >= 90) {
- print("A")
-} else if (grade >= 80) {
- <|fim|>
-} else {
- print("C")
-}`,
- llmOutput: 'print("B")',
- expectedCompletion: 'print("B")',
- },
- {
- description: "Should autocomplete R ggplot2 plot structure",
- filename: "plot.R",
- input: `
-library(ggplot2)
-
-ggplot(data=mtcars, aes(x=wt, y=mpg)) +
- geom_point() +
- <|fim|>`,
- llmOutput: "geom_smooth(method='lm', se=FALSE)",
- expectedCompletion: "geom_smooth(method='lm', se=FALSE)",
- },
- {
- description: "Should autocomplete Scala class with a method",
- filename: "Person.scala",
- input: `class Person(val name: String, val age: Int) {
- def greet(): String = {
- <|fim|>
- }
- }`,
- llmOutput: 's"Hello, my name is $name and I am $age years old."',
- expectedCompletion: 's"Hello, my name is $name and I am $age years old."',
- },
-
- {
- description: "Should handle Scala case class with a missing field",
- filename: "Person.scala",
- input: `case class Address(city: String, postalCode: String)
- case class Person(name: String, age: Int, address: Address)
-
- val alice = Person("Alice", 30, Address("Wonderland", <|fim|>))`,
- llmOutput: '"12345")',
- expectedCompletion: '"12345")',
- },
-
- {
- description: "Should autocomplete Scala function with missing body bracket",
- filename: "Math.scala",
- input: `object MathUtils {
- def add(a: Int, b: Int): Int = {
- a + b<|fim|>
-
- def multiply(a: Int, b: Int): Int = {
- a * b
- }
- }
-
- object Main extends App {
- println(MathUtils.add(3, 5))
- println(MathUtils.multiply(4, 6))
- }`,
- llmOutput: `
- }`,
- expectedCompletion: `
- }`,
- },
- {
- description: "Should autocomplete C function definition",
- filename: "math_utils.c",
- input: `#include
-
-int add(int a, int b) {
- return a + b;
-}
-
-int multiply(int a, int b) {
- return a * b;
-}
-
-int subtract(int a, int b) {
- <|fim|>
-}
-
-int main() {
- printf("Result: %d", add(2, 3));
- return 0;
-}`,
- llmOutput: "return a - b;",
- expectedCompletion: "return a - b;",
- },
-
- {
- description: "Should handle C struct with missing field initialization",
- filename: "person.c",
- input: `#include
-
-typedef struct {
- char name[50];
- int age;
- float height;
-} Person;
-
-int main() {
- Person alice = {"Alice", 30, <|fim|>};
- printf("Name: %s, Age: %d, Height: %.2f", alice.name, alice.age, alice.height);
- return 0;
-}`,
- llmOutput: "5.5",
- expectedCompletion: "5.5",
- },
-
- {
- description: "Should autocomplete C function with missing body bracket",
- filename: "area.c",
- input: `#include
-
-double calculateCircleArea(double radius) {
- const double pi = 3.14159;
- return pi * radius * radius;<|fim|>
-
-double calculateRectangleArea(double length, double width) {
- return length * width;
-}
-
-int main() {
- printf("Circle Area: %.2f", calculateCircleArea(5.0));
- printf("Rectangle Area: %.2f", calculateRectangleArea(4.0, 6.0));
- return 0;
-}`,
- llmOutput: `
-}`,
- expectedCompletion: `
-}`,
- },
- {
- description: "Should autocomplete a simple Kotlin function declaration",
- filename: "simpleFunction.kt",
- input: `
-fun main() {
- println("Hello, World!")
-}
-
-fun calculateArea(length: Double, width: Double): Double <|fim|>`,
- llmOutput: `{
- return length * width
-}`,
- expectedCompletion: `{
- return length * width
-}`,
- },
- {
- description: "Should handle autocomplete inside a Kotlin data class",
- filename: "dataClass.kt",
- input: `
-data class User(
- val id: Int,
- val name: String,
- val email: String,
- <|fim|>
-)`,
- llmOutput: "val age: Int",
- expectedCompletion: "val age: Int",
- },
- {
- description: "Should complete Kotlin if-else structure with missing brackets",
- filename: "controlStructure.kt",
- input: `
-fun getMax(a: Int, b: Int): Int {
- if (a > b<|fim|>
- } else {
- return b
- }
-}`,
- llmOutput: `) {
- return a`,
- expectedCompletion: `) {
- return a`,
- },
- {
- description: "Should autocomplete Solidity function definition",
- filename: "SimpleStorage.sol",
- input: `
-pragma solidity ^0.8.0;
-
-contract SimpleStorage {
- uint private data;
-
- function set(uint x) public {
- data = x;
- }
-
- function get() public view returns (uint) {
- <|fim|>
- }
-}
- `,
- llmOutput: "return data;",
- expectedCompletion: "return data;",
- },
-
- {
- description: "Should autocomplete Solidity event with parameters",
- filename: "EventExample.sol",
- input: `
-pragma solidity ^0.8.0;
-
-contract EventExample {
- event DataStored(uint indexed id, string content);
-
- function storeData(uint id, string memory content) public {
- emit DataStored(<|fim|>);
- }
-}
- `,
- llmOutput: "id, content",
- expectedCompletion: "id, content",
- },
-
- {
- description: "Should handle Solidity struct definition completion",
- filename: "StructDefinition.sol",
- input: `
-pragma solidity ^0.8.0;
-
-contract StructExample {
- struct Person {
- string name;
- uint age;
- address wallet;
- }
-
- Person[] private people;
-
- function addPerson(string memory name, uint age, address wallet) public {
- people.push(Person(name, age, wallet));
- }
-
- function getFirstPerson<|fim|>
-}
- `,
- llmOutput: `() public view returns (string memory, uint, address) {
- if (people.length > 0) {
- Person storage person = people[0];
- return (person.name, person.age, person.wallet);
- }
- return ("", 0, address(0));
- }`,
- expectedCompletion: `() public view returns (string memory, uint, address) {
- if (people.length > 0) {
- Person storage person = people[0];
- return (person.name, person.age, person.wallet);
- }
- return ("", 0, address(0));
- }`,
- },
- {
- description: "Should autocomplete TypeScript interface properties",
- filename: "User.ts",
- input: `
-interface User {
- id: number;
- name: string;
- e<|fim|>
-}
-`,
- llmOutput: `mail: string;
- age: number;
-}`,
- expectedCompletion: `mail: string;
- age: number;`,
- },
- {
- description: "Should autocomplete TypeScript interface declarations",
- filename: "autocomplete.ts",
- input: `interface AutocompleteDiffSnippet extends BaseAutocompleteSnippet {}
-
-interface AutocompleteCodeSnippet`,
- llmOutput: ` extends BaseAutocompleteSnippet {
- filepath: string;
-}`,
- expectedCompletion: ` extends BaseAutocompleteSnippet {
- filepath: string;
-}`,
- },
- {
- description: "Should autocomplete a TypeScript arrow function inside a variable assignment",
- filename: "mathOperations.ts",
- input: `
-const addNumbers = (a: number, b: number): number => {
- return a + b;
-};
-
-const multiplyNumbers = (a: number, b: number): number => {
- re<|fim|>
-}
-
-console.log(multiplyNumbers(2, 3));
-`,
- llmOutput: "turn a * b;",
- expectedCompletion: "turn a * b;",
- },
-
- // TODO
- // {
- // description:
- // "Should handle autocomplete inside a nested TypeScript class method",
- // filename: "Account.ts",
- // input: `
- // class Account {
- // private balance: number = 0;
-
- // deposit(amount: number) {
- // this.balance += amount;
- // return this.balance;
- // }
-
- // withdraw(amount: number) {
- // if (amount > this.balance) {
- // throw new Error("Insufficient funds");
- // }
- // this.balance -= amount;
- // return thi<|fim|>
- // }
- // }
- // `,
- // llmOutput: `s.balance;`,
- // expectedCompletion: `s.balance;`,
- // },
-
- // TODO
- // {
- // description: "Should autocomplete a TypeScript generic function",
- // filename: "GenericFunction.ts",
- // input: `
- // function identity(arg: T): T {
- // return ar<|fim|>
- // }
-
- // console.log(identity(5));
- // `,
- // llmOutput: `g;`,
- // expectedCompletion: `g;`,
- // },
-
- // TODO
- // {
- // description:
- // "Should autocomplete a TypeScript promise within an asynchronous function",
- // filename: "asyncFunction.ts",
- // input: `
- // async function fetchData(url: string): Promise {
- // const response = await fetch(url);
- // <|fim|>
- // return data;
- // }
-
- // fetchData('https://api.example.com/data');
- // `,
- // llmOutput: `const data = await response.json();`,
- // expectedCompletion: `const data = await response.json();`,
- // },
-
- {
- description: "Should autocomplete a C# class with a constructor and property",
- filename: "Person.cs",
- input: `using System;
-
-public class Person
-{
- public string Name { get; set; }
- public int Age { get; set; }
-
- public Person(string name, int <|fim|>
-}`,
- llmOutput: `age)
- {
- Name = name;
- Age = age;
- }`,
- expectedCompletion: `age)
- {
- Name = name;
- Age = age;
- }`,
- },
- {
- description: "Should autocomplete a C# interface method",
- filename: "IGreetable.cs",
- input: `public interface IGreetable
-{
- void <|fim|>
-}`,
- llmOutput: "Greet();",
- expectedCompletion: "Greet();",
- },
- {
- description: "Should autocomplete inside C# method with if condition",
- filename: "Calculator.cs",
- input: `using System;
-
-public class Calculator
-{
- public int Add(int a, int b)
- {
- if(<|fim|>)
- {
- return a + b;
- }
- return 0;
- }
-}`,
- llmOutput: "a > 0 && b > 0",
- expectedCompletion: "a > 0 && b > 0",
- },
- {
- description: "Should complete a simple Julia function",
- filename: "simpleFunction.jl",
- input: `function calculate_area(length, width)
- <|fim|>
-end
-`,
- llmOutput: "return length * width",
- expectedCompletion: "return length * width",
- },
- {
- description: "Should autocomplete Julia for loop",
- filename: "loop.jl",
- input: `numbers = [1, 2, 3, 4, 5]
-squared_numbers = []
-
-for num in numbers
- <|fim|>
-end
-
-println(squared_numbers)
-`,
- llmOutput: "push!(squared_numbers, num^2)",
- expectedCompletion: "push!(squared_numbers, num^2)",
- },
- {
- description: "Should complete a Julia struct definition",
- filename: "structDefinition.jl",
- input: `struct Person
- first_name::String
- last_name::String
- age::Int
- address::Address
-end
-
-struct Address
- street::String
- city::String
- <|fim|>
-end
-`,
- llmOutput: `state::String
- zip_code::String`,
- expectedCompletion: `state::String
- zip_code::String`,
- },
- {
- description: "Should complete a Julia dictionary access",
- filename: "dictionary.jl",
- input: `grades = Dict("Alice" => 90, "Bob" => 85, "Eve" => 88)
-
-function get_grade(student_name)
- return grades[<|fim|>]
-end
-
-println(get_grade("Alice")) # Should print 90
-`,
- llmOutput: "student_name",
- expectedCompletion: "student_name",
- },
- {
- description: "Should complete a Julia module declaration",
- filename: "moduleDeclaration.jl",
- input: `module MathOperations
-
-export add, subtract
-
-function add(a, b)
- return a + b
-end
-
-function subtract(a, b)
- return a - b
-end
-
-<|fim|>
-`,
- llmOutput: "end",
- expectedCompletion: "end",
- },
- {
- description: "Should complete F# let-binding with function definition",
- filename: "mathModule.fs",
- input: `module MathModule
-
-let calculateArea length width =
- <|fim|>`,
- llmOutput: "length * width",
- expectedCompletion: "length * width",
- },
-
- {
- description: "Should complete incomplete F# type definition",
- filename: "personType.fs",
- input: `type Person = {
- FirstName: string
- LastName: string
- Age: int<|fim|>
-}
-
-let john = { FirstName = "John"; LastName = "Doe"; Age = 30 }`,
- llmOutput: `
- Address: string
-}`,
- expectedCompletion: `
- Address: string`,
- },
-
- {
- description: "Should complete F# pattern matching expression",
- filename: "patternMatching.fs",
- input: `let describeNumber number =
- match number with
- | 0 -> "Zero"
- | 1 -> "One"
- | <|fim|>`,
- llmOutput: `2 -> "Two"
- | _ -> "Other"`,
- expectedCompletion: `2 -> "Two"
- | _ -> "Other"`,
- },
-
- {
- description: "Should complete F# list comprehension expression",
- filename: "listComprehension.fs",
- input: `let squares = [ for x in 1..10 -> x * x ]
-let evenSquares = [ for x in squares do if x % 2 = 0 then yield x ]
-let oddSquares = [<|fim|>]`,
- llmOutput: " for x in squares do if x % 2 <> 0 then yield x ]",
- expectedCompletion: " for x in squares do if x % 2 <> 0 then yield x ]",
- },
- {
- description: "Should complete an F# recursive function",
- filename: "recursiveFunctions.fs",
- input: `let rec factorial n =
- if n <= 1 then 1
- else n <|fim|> factorial (n - 1)`,
- llmOutput: "*",
- expectedCompletion: "*",
- },
- {
- description: "Should complete F# member method inside a class type",
- filename: "bankAccount.fs",
- input: `type BankAccount(owner: string, initialBalance: float) =
- let mutable balance = initialBalance
-
- member this.Deposit amount =
- balance <- balance + amount
- this
- <|fim|>`,
- llmOutput: `
- member this.Withdraw amount =
- if amount > balance then
- failwith "Insufficient funds"
- balance <- balance - amount
- this`,
- expectedCompletion: `
- member this.Withdraw amount =
- if amount > balance then
- failwith "Insufficient funds"
- balance <- balance - amount
- this`,
- },
-
- {
- description: "Should complete F# async workflow function",
- filename: "asyncWorkflow.fs",
- input: `let fetchDataAsync url =
- async {
- use client = new System.Net.Http.HttpClient()
- <|fim|>
- }`,
- llmOutput: `let! response = client.GetStringAsync(url)
- return response`,
- expectedCompletion: `let! response = client.GetStringAsync(url)
- return response`,
- },
- {
- description: "Should autocomplete SCSS nested class starting inside a rule",
- filename: "styles.scss",
- input: `nav {
- display: flex;
- justify-content: space-between;
- .logo {
- font-size: 1.5rem;
- color: #333;
-
- .brand<|fim|>
- }
-
- ul {
- list-style: none;
- display: flex;
- gap: 1rem;
- }
-}`,
- llmOutput: `-name {
- font-weight: bold;
- text-transform: uppercase;
-}`,
- expectedCompletion: `-name {
- font-weight: bold;
- text-transform: uppercase;
-}`,
- },
-
- {
- description: "Should handle SCSS mixin within a class",
- filename: "styles.scss",
- input: `.card {
- border: 1px solid #ccc;
- padding: 10px;
-
- @include<|fim|>
-
- &:hover {
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
- }
-}`,
- llmOutput: " transition(all 0.3s ease);",
- expectedCompletion: " transition(all 0.3s ease);",
- },
-
- {
- description: "Should autocomplete SCSS variable in the middle of a statement",
- filename: "styles.scss",
- input: `$primary-color: #007bff;
-$secondary-color: #6c757d;
-
-.button {
- background-color: <|fim|> color;
- padding: 10px 15px;
- border: none;
- color: #fff;
- border-radius: 4px;
-}`,
- llmOutput: "$primary-",
- expectedCompletion: "$primary-",
- },
- {
- description: "Should autocomplete Vue component method",
- filename: "MyComponent.vue",
- input: `
-