Skip to content
| Marketplace
Sign in
Visual Studio Code>AI>EasyMod AI ChatNew to Visual Studio Code? Get it now.
EasyMod AI Chat

EasyMod AI Chat

EasyMod

|
15 installs
| (0) | Free
VS Code sidebar chat client for EasyMod API
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

EasyMod VS Code Extension

VS Code sidebar chat extension for EasyMod backend gateway APIs.

What It Does

  • Adds an EasyMod Activity Bar view with a webview chat panel.
  • Sends chat requests through backend POST /api/vscode/send (including streaming).
  • Supports extension sign-in with auth code exchange + refresh token flow.
  • Supports organization-aware requests (org_id) and model loading.
  • Chat model dropdown includes an Auto option (sends model: "auto"; backend performs routing selection) alongside concrete models; a small resolved-model badge is shown on assistant messages that were answered by an Auto turn. Auto is currently withheld for the Grok provider — a Grok Auto turn loses the patch card, so pick a concrete Grok model for edit requests until the gateway fix lands.
  • Provider dropdown is dynamic: it lists every provider the backend's model list actually returns (e.g. OpenAI, Anthropic, DeepSeek, Gemini, Grok), with an Auto option always first — no provider needs a code change to show up once the backend serves its models.
  • Models the workspace cannot use yet (e.g. a paid model on a Trial or Free plan without top-up or enough referral credits) stay in the model dropdown but are greyed out with "(upgrade required)" and the backend's reason as a tooltip, instead of failing with "requires a paid plan" after you send. A saved selection that has become locked falls back to the first usable model.
  • Includes local workspace tool loop (rg, file read, file list) for retrieval-style workflows. In a multi-root workspace, a bare rg search (no path argument) now searches EVERY open workspace root, not just the first — matches from a non-primary root come back prefixed with that root's folder name.
  • Adds persistent workspace memory index (.easymod/workspace-memory.json) for cross-language symbol/impact recall between turns. Remembered file content is validated against disk each time it's reused: a changed file is re-read and refreshed automatically, and a removed file is dropped from memory, so the assistant no longer quotes a file's pre-edit content back at you. Reused session context snippets (the per-conversation context ledger) are validated the same way and are dropped when the file no longer matches. In a multi-root workspace, a file read from any open folder is remembered under THAT folder's own memory snapshot (not always the primary root). Later turns rank remembered chunks across every open root under one global six-chunk / token budget, then validate only the globally selected files against their owning roots. Once per session each root's snapshot is also swept to drop entries that can no longer be resolved.
  • Supports assistant patch preview/apply from unified diff output with workspace safety checks, replay protection, and preservation of existing CRLF/LF line endings.
  • Supports multiple local chat sessions (new/switch/rename/delete/clear).
  • Keeps in-flight assistant streaming alive across Activity Bar/sidebar switches by retaining and rehydrating the chat webview state.

Requirements

  • VS Code ^1.90.0
  • Node/npm for build and test
  • EasyMod backend reachable from easymod.apiUrl

Setup

npm install
npm run compile

For development:

npm run watch

Run extension in Extension Development Host with F5.

Test

npm test

This rebuilds out/ and runs Node tests from compiled output (out/test/**/*.test.js) if present.

Development Tools

  • npm run compile runs the TypeScript strict compile check.
  • npm run watch keeps TypeScript compiling during extension development.
  • npm test runs the repository safety gates (check-db-safety, check-test-suite), rebuilds out/, and then runs the compiled Node test suite.

Commands

  • EasyMod: Open Chat Panel
  • EasyMod: Focus Chat View
  • EasyMod: Sign In
  • EasyMod: Sign Out
  • EasyMod: Switch Organization
  • EasyMod: Set Manual JWT Session
  • EasyMod: Toggle Full File Access

Diagnostic — listed in the Command Palette only while easymod.enableDeveloperDebugPage is on, and off unless configured (see Direct-gateway probe):

  • EasyMod: Probe Direct Gateway (single turn)
  • EasyMod: Probe Direct Gateway (worker release under load)
  • EasyMod: Run Direct Turn (spends credit) — see Direct turn

Settings (easymod.*)

Core connection/auth:

  • apiUrl (default: https://backend.easymodularity.com)
  • model (default: empty; uses the EasyMod default chat model)
  • defaultOrgId (default: empty)
  • extensionAuthBridgeUrl (default: https://easymodularity.com/extension-auth/bridge/start; set empty to use <apiUrl>/extension-auth/bridge/start)
  • extensionId (default: empty)
  • directTurns (default: true) — send chat straight to the gateway on a per-turn token instead of proxying it through the backend. See Direct turns.
  • directGatewayUrl (default: empty — diagnostic only, see Direct-gateway probe; chat never reads it)
  • directGatewayApiKey (default: empty — diagnostic only; use a dev gateway's key)
  • mcpJson (default: empty JSON string field)

Direct turns

With directTurns on, a chat send is admitted by the backend and then goes straight to the gateway:

extension ──POST /api/turns──▶ backend      reserves credit, binds the model,
                                            writes the user's message, mints a
                                            turn token
extension ──POST /turns/<id>/chat──▶ gateway   streams the answer
                    gateway ──internal callback──▶ backend   settles, files the reply

Nothing about billing or history moves: the backend still prices the turn, still owns the ledger, and still writes both halves of the conversation. What changes is that the answer no longer travels through it.

The address is not configurable — the admission response names the gateway, so a gateway that moves does not strand a client that was configured before it moved. directGatewayUrl is a separate, diagnostic-only setting and chat never reads it.

Tool loops go direct too. A loop turns one user message into several model calls, and on this path each call is its own admitted turn. Only the first records the question; the rest name the conversation and ask nothing, so the transcript ends up with one question and one answer — the intermediate turns produce tool calls and no text, and nothing empty is filed.

What still stays on the proxied path is anything admission cannot serve. An issuer that is switched off, a backend that is down, a response with no token: all fall back silently, and the turn runs exactly as it did before. A policy answer does not fall back — an insufficient-credit refusal is shown, because the proxied path would reserve credit and fail for the same reason on the way to a worse message.

An admitted turn is sent once. A turn token is claimed exactly once, so the retries the proxied path uses (a 5xx retry, a limiter wait-and-resend, the non-stream fallbacks) are skipped rather than turned into a guaranteed 409.

Turn it off and chat returns to the proxied path on the next message — no reload.

Context and prompt assembly:

  • includeSelection (default: true)
  • includeWholeFile (default: false)
  • contextMode (focused | fullActiveFile, default: focused)
  • maxContextChars (default: 32000)
  • maxPromptChars (default: 52000)
  • reservedResponseChars (default: 12000)
  • maxPromptTokens (default: 32000)
  • maxContextTokens (default: 12000)
  • reservedResponseTokens (default: 4000)
  • historyMaxTokens (default: 3000)
  • memorySummaryMaxTokens (default: 800)

Retrieval and memory behavior:

  • contextLedgerEnabled (default: true)
  • contextLedgerMaxEntries (default: 24)
  • minRelevanceScore (default: 24)
  • retrievalChunkLines (default: 80)
  • retrievalChunkOverlapLines (default: 8)
  • retrievalMaxFiles (default: 80)
  • retrievalMaxSnippets (default: 3)
  • retrievalPerFileCharLimit (default: 120000)
  • maxToolResultChars (default: 30000)

Patch and tooling behavior:

  • autoApplyPatchWithConfirmation (default: true, legacy/unused)
  • enableWorkspaceToolLoop (default: true)
  • commandLoopEnabled (default: true)
  • commandLoopMaxRetries (default: 2)
  • commandExecTimeoutMs (default: 120000)
  • alwaysAutoRunCommands (default: false)
  • autoRunProjectStartup (default: false)
  • fullFileAccess (default: false)
  • enableDeveloperDebugPage (default: false)
  • apiKey is legacy/unused in JWT backend mode.

Patch flow notes:

  • fullFileAccess: true means new assistant patches auto-apply, and it also turns on trusted command auto-run plus startup auto-run.
  • fullFileAccess: false means new assistant patches stay in preview mode until manually applied from the latest patch card.
  • Replaying an already-satisfied patch is reported as already applied without rewriting the file; a structurally new diff can still intentionally repeat content. Existing CRLF/LF files keep their line-ending convention through patch writes.
  • When a prompt explicitly names a target workspace root (e.g. in easymod-api workspace, … or in easymod-api, create …) and that name matches no existing root, auto-apply is blocked before any patch is applied; the extension surfaces the available roots and asks you to re-send.
  • alwaysAutoRunCommands: true skips the manual confirmation dialog only for trusted command runs that pass the extension policy.
  • autoRunProjectStartup: true lets EasyMod auto-start exactly one trusted startup command after prompts like "serve this project", "run this app", or natural variants such as "help me serve it". Auto-run is now allowlist-based: only common local startup commands such as npm run dev, npm start, npx serve, npx http-server, python -m http.server, php artisan serve, vite, and similar dev-server patterns qualify. Before launching a long-running startup command in the terminal, EasyMod now performs a short startup probe. If the command fails immediately, the captured stderr/stdout is fed into the command repair loop instead of being lost in terminal-only output. It still skips auto-run when there are multiple commands, the command is not startup-like, the command is outside the trusted auto-run allowlist, or a patch is still waiting for manual apply. The terminal working directory is anchored at the workspace root whose basename matches the command's leading cd <target> prefix, and the redundant root-name segment is stripped from the cd before the command is sent to the terminal (e.g. cd easymod-api/calculator && npm run dev spawns in the easymod-api root and sends cd calculator && npm run dev; bare-root cd easymod-api && npm run dev drops the cd entirely and sends npm run dev). Commands with no cd prefix or an unrecognized first segment fall back to the active-editor folder or the first workspace folder, and are sent verbatim.

Direct-gateway probe

A diagnostic path that exists to answer one question before anyone commits to rebuilding the transport: if the extension reads the stream straight from the gateway instead of through the Laravel backend, does the backend hold zero workers?

Today the backend proxies the byte stream, which pins an Octane worker for the whole turn — a long turn occupies a PHP process that cannot serve anyone else.

It is not a feature and no chat turn can reach it. Both commands refuse to run until two settings are filled in, and they are empty by default:

Both values are the ones the backend already uses to reach the gateway — its EASYMOD_API_URL and EASYMOD_API_KEY, which are PORT and API_KEY in easymod-api/.env:

"easymod.directGatewayUrl":    "http://127.0.0.1:4000",   // easymod-api PORT
"easymod.directGatewayApiKey": "dev-easymod-api-key"      // easymod-api API_KEY

Put them in User settings, not this repo's .vscode/settings.json — that file is tracked in git, so a key placed there is one git add away from being committed. User settings apply in the Extension Development Host too.

Two credentials, and why the probe prefers the second

shared API_KEY per-turn token
Opens every /v1 route exactly one turn
Expires never minutes
Carries identity no user_id / org_id
Carries a budget no yes
Can ship to users no yes, once the backend mints them

The probe cannot mint a turn token, by design. It used to: an easymod.directGatewayTurnSecret setting held the gateway's signing secret and the probe signed its own tokens, back when the backend had no issuer to stand in for. The backend has one now — POST /api/turns (one endpoint for every client, which names itself in the body), which checks auth, membership and plan, reserves the credit, records the turn, and only then signs — so the stand-in is gone, and the setting with it. A client that can sign is a client that can assert any identity and any budget it likes.

What remains is a credential-free check of the turn route, run automatically after the single-turn probe: POST /turns/<id>/chat with no Authorization header at all.

It answers Meaning
401 the healthy answer — the route is deployed and its verifier is live
404 the route is absent: an older gateway, or directGatewayUrl points at the backend
503 the gateway has no GATEWAY_TURN_TOKEN_SECRET and cannot verify tokens at all
any 2xx alarming — the turn route served a request that presented nothing. The report says so

Driving a real turn is the issuer's job, not this probe's.

Then turn on easymod.enableDeveloperDebugPage (the diagnostic commands are listed in the Command Palette only while it is on) and, from the Command Palette:

  1. EasyMod: Probe Direct Gateway (single turn) — proves the pipe. If this fails, the load run's numbers mean nothing. The two failures that actually happen first are 401 (key mismatch) and 404 (the URL points at the backend, not the gateway).
  2. EasyMod: Probe Direct Gateway (worker release under load) — fires N concurrent turns at the gateway while sampling the backend once a second, and reports whether the backend stayed responsive.

Output goes to its own EasyMod Probe output channel, which the commands reveal — deliberately not the shared debug channel, which is silent unless enableDeveloperDebugPage is on.

Three things that decide whether a run is meaningful:

  • Both URLs must point at the same environment. The probe loads directGatewayUrl and samples apiUrl. A local gateway sampled against a remote backend gives a flat backend column because that backend was never under load — which reads as a pass. The confirmation dialog prints both URLs and warns when one is local and the other is not.
  • The backend sample hits an arbitrary path and a 404 is fine. A PHP server queues requests when every worker is busy, so a route that does not exist still gets slow when the pool is saturated. No auth, no new backend route.
  • This run loads the gateway only. A flat backend column proves the direct path consumed no backend capacity. It says nothing about what the backend would have done under the same load — for that contrast, send the same number of turns through normal chat while a run is sampling.

Local note: npm run dev:workspace starts the backend as php artisan serve --port=8010, which is a single-process dev server, and php artisan octane:start cannot run on Windows at all (pcntl/SIGINT are POSIX-only). Production runs RoadRunner with num_workers: 4 (.rr.yaml). So a local run demonstrates the mechanism — direct turns hold zero backend capacity — but the magnitude of the ceiling it removes is not transferable from a one-request-at-a-time dev server to a four-worker pool.

What a green result does not mean. A direct turn bypasses the backend, so it is not billed, it uses the gateway's shared API key (which can never ship to users), and it bypasses the backend's admission and rate limiting. Resume after a dropped connection is untouched. A clean run means the mechanism holds — not that this path is shippable.

Direct turn

The probe above proves the mechanism. This runs a real turn on it: the backend admits one, reserves credit, signs a short-lived token, and the extension takes that token straight to the gateway and reads the stream itself.

EasyMod: Run Direct Turn (spends credit) does exactly what its title says. It is diagnostic, no chat turn reaches it, and it needs easymod.directGatewayUrl set and you signed in. The spending is the point — an unspent turn proves nothing about admission or settlement, which is what this exists to check.

The four steps, and the one hard part

1. build the body you are going to send
2. MEASURE it            -> that number is `budget_tokens`
3. POST <backend>/api/turns   with your session JWT
                              -> a token bound to ONE turn, model and client
4. POST <gateway>/turns/:id/chat  with THAT token -> read the SSE yourself

The HTTP is unremarkable. What a second client (web, mobile) will get wrong is what to do when one of those says no, because the refusals are not interchangeable:

Step Status Means The client must
admission 404 the issuer is switched off here fall back, silently
admission 402 out of credit show it — NEVER fall back
admission 403 not entitled show it
admission 5xx / network infrastructure fall back, like the 404
send 413 body exceeds what was declared trim, retry on the SAME token
send 409 this token was already spent stop — a retry is a second charge
send 403 wrong model or client for this token a client bug; do not retry

404 is the normal answer from a deployment with the issuer off, which during a rollout is most of them — surfacing it would show every user an error for a feature they never asked for. 402 is the opposite: falling back is actively harmful, because the proxied path reserves credit first and then refuses for the same reason, so the user waits longer, reads a worse message, and has credit locked up for a turn that was never going to run.

413 is the only one a client recovers from unaided, and that is by design: the gateway's budget guard runs before the claim, so the turn is still admitted and the same token works for a smaller body.

The measurement is a contract, not a detail

src/services/turnInputBudget.ts must produce exactly the number easymod-api/src/services/turnInputBudget.ts computes — the gateway recomputes it from the body it receives and refuses the turn if the body measures larger. One token of drift is a turn refused for no reason the user can see, so both repositories assert the same table of golden vectors: change one side and the other side's test fails.

It counts what a naive estimate loses — tool schemas, file bodies, tool-call turns — and prices an image as a flat constant, never its base64 length: providers bill images by pixel dimensions, so the same screenshot costs the same as a 40KB JPEG or a 4MB PNG.

Two details that look like typos

  • An Auto turn declares auto at admission but sends an empty model on the wire. The token records that the reservation was not priced against one specific model; the empty body field is what makes the gateway's routing pick one.
  • The body's source must equal the client the token was admitted for. The gateway forks its response handling on it, so a turn admitted as one client and run as another executes a branch the backend never authorised — refused 403.

What this does not do yet

Chat does not use any of this. gatewayChatClient.ts still goes through the backend. Repointing it waited on knowing the gateway's SSE frames matched what it already parses; a live run on 2026-09-07 confirmed they do — every frame is a bare data: with no event: name, and the text arrives as choices[0].delta.content, both of which that client already handles.

Local Dev Tip

For local development against a local backend/frontend, use workspace overrides in .vscode/settings.json instead of changing defaults in package.json.

Example:

{
  "easymod.apiUrl": "http://127.0.0.1:8010",
  "easymod.extensionAuthBridgeUrl": "https://easymodularity.com/extension-auth/bridge/start",
  "easymod.autoRunProjectStartup": true,
  "easymod.fullFileAccess": false,
  "easymod.enableDeveloperDebugPage": true
}

Notes

  • Backend remains source of truth for auth, org access, and request handling.
  • Extension-side tool loop and context assembly are configurable and can be disabled.
  • Patch writes are guarded by workspace boundary checks and optional confirmation.
  • In-flight assistant streams continue in the extension host even if the EasyMod sidebar is temporarily hidden; reopening the view rehydrates the current streamed text instead of starting from a blank panel.

Stream Log Semantics

  • chat stream chunk means raw bytes/chunks were read from backend SSE/stream transport.
  • assistant delta received means user-visible assistant text delta was emitted into the extension turn pipeline.
  • ui first flush is tied to the first assistant delta received, not the first chat stream chunk.
  • In tool-loop or multi-turn synthesis flows, chat stream chunk can appear long before first UI flush.

Example A (small-talk, fast path):

  • chat stream chunk at ~5ms
  • assistant delta received at ~600ms
  • ui first flush immediately after first delta

Example B (tool-loop heavy prompt):

  • early chat stream chunk events appear quickly (transport is active)
  • multiple loop/follow-up turns happen
  • first assistant delta received may appear much later (for example 50s+)
  • ui first flush then occurs right after that first assistant delta
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft