continuedev/continue
TypeScript coding assistant for VS Code, JetBrains and a terminal CLI, with chat, plan and agent modes over a shared core.
Overview
Continue is an open-source AI coding assistant. It ships as a VS Code extension, a JetBrains plugin and a terminal CLI called cn. In the IDEs it combines several features that usually come as separate products: chat with @-context, an agent mode that calls tools, a read-only plan mode, tab autocomplete, “next edit” suggestions and codebase indexing for retrieval. Models, rules, MCP servers and tools are declared in a config.yaml, locally or through the Continue hub.
The IDE extension has three parts. core/ is a large TypeScript library that owns config, LLM providers, indexing, context providers and tool implementations. gui/ is a React webview with the chat UI. A thin IDE shim connects the two over a typed message protocol. In VS Code, Core runs in the extension host process. JetBrains runs the same core as a separate Node binary built from binary/.
The agent loop does not live in core. In the IDEs it runs in the GUI as a chain of Redux thunks. The webview streams a reply from core, decides which tool calls need approval, executes them, and then streams again. The CLI has its own, separate loop in extensions/cli, built on the @continuedev/openai-adapters package rather than core’s BaseLLM. That split explains several of Continue’s quirks.
Architecture
flowchart LR
U["User in IDE"] --> GUI["React webview (gui/)"]
GUI --> THK["streamNormalInput thunk"]
THK -->|"llm/streamChat"| CORE["Core (core/core.ts)"]
CORE --> LLM["BaseLLM providers"]
THK -->|"tools/call"| CT["callTool: built-in / MCP"]
THK --> CLT["Client tools: edit, find-replace"]
CLT --> APPLY["ApplyManager + vertical diff"]
CORE --> IDX["CodebaseIndexer: FTS, LanceDB, snippets"]
CORE --> MCP["MCPManagerSingleton"]
CT --> MCP
CLI["cn CLI"] --> CLOOP["streamChatResponse loop"]
CLOOP --> ADP["openai-adapters"]
| Component | Path | Role |
|---|---|---|
| Core | core/core.ts |
Message handlers for chat, tools, config, indexing, compaction |
| LLM layer | core/llm/index.ts, core/llm/llms/ |
BaseLLM, about 65 provider and helper modules, tool-support table |
| GUI loop | gui/src/redux/thunks/ |
streamNormalInput, callToolById, streamResponseAfterToolCall, policy checks |
| Tools | core/tools/ |
Definitions, callTool, built-in implementations, system-message tool fallback |
| Edit engine | core/edit/ |
Find-and-replace matching, lazy apply, streamed diff lines |
| VS Code shim | extensions/vscode/src/ |
Hosts Core in-process, applies edits as vertical diffs |
| JetBrains | extensions/intellij/, binary/ |
Kotlin plugin plus the packaged core binary |
| Indexing | core/indexing/, core/context/retrieval/ |
FTS5, LanceDB embeddings, code snippets, retrieval pipelines |
| Terminal security | packages/terminal-security/ |
Shell command risk classifier |
| CLI | extensions/cli/ |
cn TUI and headless agent with its own permissions |
| Config | packages/config-yaml/ |
config.yaml schema and hub resolution |
How a request flows
Take an agent-mode request in VS Code:
- Build the request.
streamNormalInputpicks the selected chat model and the active tools, applies per-model tool overrides, and decides between native tool calling and the system-message fallback (streamNormalInput.ts). The tools depend on the mode: chat gets none, plan gets only read-only built-ins plus MCP tools, agent gets everything enabled (selectActiveTools.ts). - Compile and prune. The GUI asks core for
llm/compileChat, which fits messages into the context window and reports whether anything was pruned (streamNormalInput.ts, core.ts). - Stream.
llm/streamChatregisters an abort controller per message and runsllmStreamChat, which callsBaseLLM.streamChaton the provider (core.ts). - Policy. After the stream, the GUI preprocesses tool arguments and evaluates policies. Calls marked
allowedWithoutPermissionrun immediately. If any call needs permission, only built-in read-only calls run and the rest wait for the user (streamNormalInput.ts). - Execute.
callToolByIdruns edit tools in the client and sends everything else to core astools/call(callToolById.ts). Core’scallToolroutes MCP tools by URI and built-ins through a switch (callTool.ts). - Loop.
streamResponseAfterToolCalldispatchesstreamNormalInputagain withdepth + 1. The recursion ends when the model returns no tool calls or a call waits for approval (streamNormalInput.ts). The only depth cap is a check that applies in tests.
Key components
Edits as client tools
edit_existing_file, single_find_and_replace and multi_edit are client tools (builtIn.ts). The find-and-replace version reads the file through the IDE, computes the new contents with executeFindAndReplace, and hands the full text to the apply flow (singleFindAndReplaceImpl.ts). Matching tries exact, trimmed, case-insensitive and whitespace-ignored strategies in that order (findSearchMatch.ts). In VS Code, ApplyManager.applyToFile opens the file and shows the change as a vertical diff. Find-and-replace results apply instantly. Whole-file edits stream through an “apply” model (ApplyManager.ts). The user can accept or reject the diff, which is the main undo path. There is no git checkpointing.
Tool policies and shell classification
Each tool has a default policy: allowedWithoutPermission, allowedWithPermission or disabled. Tools can tighten that per call. run_terminal_command defaults to asking, and then passes the command through evaluateTerminalCommandSecurity (runTerminalCommand.ts). That function tokenises with shell-quote, evaluates each line and each pipe segment, and keeps the most restrictive result. A parse failure falls back to “ask” (evaluateTerminalCommandSecurity.ts). Separate checks cover critical commands (disabled), high-risk package managers, network tools, interpreters and rm, and safe read-only commands. Nothing is sandboxed. Approved commands run in the IDE terminal with your permissions.
Codebase index and context
CodebaseIndexer batches files 200 at a time into a SQLite FTS5 index, a chunk index, a code-snippets index and LanceDB embeddings (CodebaseIndexer.ts). Retrieval combines them, with optional reranking that retrieves twice as many candidates as it keeps. The codebase tool and the @codebase provider sit on top of it. More than 30 context providers cover files, terminal output, git diff, docs and a repo map. Conversation compaction is a manual action: conversation/compact writes an LLM summary onto a history item, and later prompts start from that summary (core.ts, conversationCompaction.ts).
Models
BaseLLM in core/llm/index.ts is subclassed per provider (Anthropic, OpenAI, Gemini, Bedrock, Vertex, Mistral, Ollama, LM Studio, llama.cpp, vLLM and many more). toolSupport.ts records which provider and model pairs support native tools. The rest use SystemMessageToolCodeblocksFramework, which describes tools in the system prompt and parses tool calls out of code blocks. Models are assigned to roles (chat, edit, apply, autocomplete, embed, rerank), so a cheap model can autocomplete while a larger one runs the agent.
The cn CLI
streamChatResponse is a straightforward while (true) loop. On each iteration it rebuilds the system message and tools for the current permission mode, compacts before the API call if needed, streams, handles tool calls, checks the context again after tools, and auto-compacts at 80% (streamChatResponse.ts). Its permission policy is first-match-wins. Edits and writes ask. Read-only tools are allowed. In headless -p mode, Bash and the wildcard are allowed without asking (defaultPolicies.ts).
Extending it
- MCP servers. Declare them in
config.yaml. Their tools are called throughcallToolFromUri, and their resources appear as context. - Rules. Markdown rules, including
AGENTS.md,AGENT.mdandCLAUDE.md, are loaded and attached to prompts. - Tool overrides. Per-model
toolOverridesrename, re-describe or disable tools. - Context providers. Subclass
BaseContextProviderincore/context/providers/. - Built-in tools. Add a definition under
core/tools/definitions/, an implementation, and a case incallBuiltInTool. - Other hosts.
Coretakes anyIDEandIMessenger, which is how the JetBrains binary reuses it.packages/continue-sdkandcn servecover programmatic use.
Running it
- IDE. Install the VS Code or JetBrains extension, then add models in
config.yaml(local or hub). Indexing starts in the background on open. - CLI.
npm i -g @continuedev/cli, thencnfor the TUI,cn -p "..."for headless output, orcn serveto expose a session over HTTP. - Required. Node.js for development builds, a model provider key or a local server (Ollama, LM Studio, llama.cpp), and optionally an embeddings model for
@codebase.
Strengths and caveats
- Strength: complete IDE assistant. Autocomplete, next-edit, chat, plan and agent modes share one config and one provider layer.
- Strength: reviewable edits. Every agent edit lands as an accept/reject diff in the editor, not as a silent write.
- Strength: careful shell classifier. The terminal-security package parses commands properly, including pipes, multi-line input and obfuscation.
- Strength: real retrieval. Hybrid FTS and vector search with optional reranking, which most terminal agents lack.
- Caveat: the loop lives in UI code. IDE agent behaviour is spread across Redux thunks and recursion, and has no turn cap outside tests. The CLI has a second, separate loop and LLM client, so the two can drift.
- Caveat: no sandbox or checkpoints. Safety is policy plus diff review. Headless
cn -pauto-allows shell and MCP tools by default. - Caveat: manual compaction in the IDE. The IDE prunes to fit and offers a compact action. Only the CLI auto-summarises.
Sources: code at 5522c6f, verified Q&A.
How it answers the Open-source coding agents questions
Each answer was drafted by a code-reading agent at commit 5522c6f. Its citations were checked mechanically. Compare with the other open-source coding agents →
How is the agent loop implemented?
answeredThree modes sharing one streaming loop. Continue has chat, agent, and plan modes — selected via UI, each picking a different system message from core/llm/defaultSystemMessages.ts. All modes flow through llmStreamChat() in core/llm/streamChat.ts which calls model.streamChat() on BaseLLM (core/llm/llms/llm.ts). Tool-call schema. Tools are passed as options.tools to streamChat() (llm.ts line 1105). Each provider converts the generic Tool[] to its format — Anthropic (core/llm/llms/Anthropic.ts line 69) maps via convertToolToAnthropicTool(). For non-native models, interceptSystemToolCalls() (core/tools/systemMessageTools/interceptSystemToolCalls.ts) parses markdown code-block tool calls. Turn structure. Edit tools dispatch via core/tools/callTool.ts routing to callBuiltInTool() (lines 187-230) or callToolFromUri() for MCP (lines 67-185). Stop conditions. No hard step limit. Per-message AbortControllers (core/core.ts lines 98-109) enable cancellation. No sub-agent system in core.
How is repository context gathered and kept within the context window?
answeredRepository context gathered via multiple paths. Codebase indexing. CodebaseIndexer (core/indexing/CodebaseIndexer.ts) maintains FullTextSearchCodebaseIndex (SQLite FTS5 trigram), LanceDbIndex (vector embeddings), and CodeSnippetsCodebaseIndex. Retrieval pipelines. retrieveContextItemsFromEmbeddings() (core/context/retrieval/retrieval.ts) runs NoRerankerRetrievalPipeline or RerankerRetrievalPipeline (core/context/retrieval/pipelines/), combining FTS with vector search, optionally reranking via LLMReranker. Repository map. RepoMapContextProvider (core/context/providers/RepoMapContextProvider.ts) generates structural overview via generateRepoMap() (core/util/generateRepoMap.ts) bounded to 50% of context length. Search tools. codebaseToolImpl (core/tools/implementations/codebaseTool.ts) delegates to retrieveContextItemsFromEmbeddings(). Conversation compaction. compactConversation() (core/util/conversationCompaction.ts) generates structured summaries triggered via conversation/compact (core/core.ts lines 622-642). Context providers. 30+ providers in core/context/providers/ give @mention access to files, terminals, git, and more.
How are code edits applied?
answeredThree edit formats with client-side application. Whole file via Edit. edit_existing_file tool (core/tools/definitions/editFile.ts) takes filepath and changes. Merge runs via ApplyManager.applyToFile() (extensions/vscode/src/apply/ApplyManager.ts lines 28-83), delegating to VerticalDiffManager or streamDiffLines(). Search/replace. single_find_and_replace (core/tools/definitions/singleFindAndReplace.ts) uses executeFindAndReplace() (core/edit/searchAndReplace/performReplace.ts lines 85-141). multi_edit (core/tools/definitions/multiEdit.ts) applies multiple operations via executeMultiFindAndReplace(). Fuzzy matching. findSearchMatch() (core/edit/searchAndReplace/findSearchMatch.ts lines 324-344) tries exact, trimmed, case-insensitive, and whitespace-ignored match strategies. Validation. validateSingleEdit() (core/edit/searchAndReplace/findAndReplaceUtils.ts) and validateMultiEdit() (core/edit/searchAndReplace/multiEditValidation.ts) check arguments. Indentation auto-adjusts (performReplace.ts lines 43-83). Undo. No built-in git. VS Code Ctrl+Z available.
How are shell commands and file writes kept safe?
answeredThree-tier tool policy with deep command analysis. Policy levels. Each tool has defaultToolPolicy (packages/terminal-security/src/types.ts): allowedWithoutPermission (read-only), allowedWithPermission (write tools), or disabled. Terminal command security. evaluateTerminalCommandSecurity() (packages/terminal-security/src/evaluateTerminalCommandSecurity.ts) tokenizes with shell-quote, then runs 30+ checks: isCriticalCommand() blocks rm -rf /, mkfs.*, dd, sudo, eval/exec (lines 402-560); isHighRiskCommand() flags package managers, network tools, scripts, Docker, cron, obfuscation (lines 942-974); isSafeCommand() auto-approves ls, cat, echo, safe git (lines 979-1095). Multi-line commands and pipe chains (evaluatePipeChain() lines 292-357) analyzed per line. File boundaries. evaluateFileAccessPolicy() (core/tools/policies/fileAccess.ts) tightens for files outside workspace. Dynamic policies. Tools like createNewFileTool (core/tools/definitions/createNewFile.ts lines 53-65) adjust policy via evaluateToolCallPolicy(). No sandboxing. No containers, landlock/seccomp, or server-side checkpoints.
Which models are supported and how are they called?
answered50+ provider implementations extending BaseLLM. Providers. Each in core/llm/llms/ extends BaseLLM (core/llm/llms/llm.ts): Anthropic.ts, OpenAI.ts, Gemini.ts, Mistral.ts, Ollama.ts, LlamaCpp.ts, Bedrock.ts, VertexAI.ts, Cohere.ts, Deepseek.ts, Together.ts, Fireworks.ts, Groq.ts, Replicate.ts, and more. Local models. Ollama.ts, LlamaCpp.ts, LMStudio.ts, Llamafile.ts, TextGenWebUI.ts, Vllm.ts support local inference. autodetect.ts detects template types. Tool calling vs text. toolSupport.ts maps providers to capability via PROVIDER_TOOL_SUPPORT (lines 3-175). Non-native models fall back to system-message framework (core/tools/systemMessageTools/). Prompt tuning. BaseLLM constructor (llm.ts lines 206-303) autodetects templates. Models override baseChatSystemMessage, baseAgentSystemMessage, basePlanSystemMessage. Cost tracking. LLMLogger logs all calls. Tokens persist to DevDataSqliteDb, exposed via stats/getTokensPerDay and stats/getTokensPerModel (core/core.ts lines 787-794).
How can it be extended and customised?
answeredMultiple extension points: MCP, tools, context providers, rules, SDK. MCP (Model Context Protocol). MCPManagerSingleton (core/context/mcp/MCPManagerSingleton.ts) manages MCP connections. Tools called via callToolFromUri() (core/tools/callTool.ts lines 67-185) with mcp:// URI scheme. MCPContextProvider (core/context/providers/MCPContextProvider.ts) exposes MCP resources. Custom tools. Add a definition in core/tools/definitions/, register in core/tools/definitions/index.ts, add ToolImpl in core/tools/implementations/, wire in callBuiltInTool() (core/tools/callTool.ts lines 187-230). Context providers. 30+ providers in core/context/providers/ extend BaseContextProvider, implementing getContextItems() and loadSubmenuItems(). Rules. loadMarkdownRules() (core/config/markdown/loadMarkdownRules.ts) loads AGENTS.md, AGENT.md, CLAUDE.md as always-applied rules. Tool overrides. applyToolOverrides() (core/tools/applyToolOverrides.ts) renames or disables tools per model. Headless. packages/continue-sdk/ provides TypeScript SDK. Core class accepts any IDE and IMessenger.