Skip to content

AI completion: remote FIM (fill-in-the-middle) provider for the web IDE #8

Description

@JonathanTurnock

Summary

Add Copilot-style inline ghost-text completion to the web IDE, backed by a remote, provider-agnostic FIM (fill-in-the-middle) LLM. Sits beside the existing grammar dropdown completer — structural candidates stay local and deterministic; the LLM only supplies multi-token / next-line suggestions.

GitHub Copilot itself is not viable: no public completion API, gated behind a Copilot subscription + GitHub OAuth and served through their own LSP. We mimic the pattern with an open, OpenAI-compatible provider (hosted or local Ollama).

Current state

  • Editor is CodeMirror 6 + Svelte 5, fully client-side, no network calls today.
  • Grammar dropdown completer: web-ide/src/lib/pseudoscript-language.js:123 (sync override).
  • WASM bridge (JSON-in/JSON-out): web-ide/src/lib/pds.js; exports incl. parse, check, outline.
  • No credential/settings store yet (keybindings store is the pattern: web-ide/src/lib/keybindings.svelte.js:70).

Key constraint

CodeMirror 6 has no native inline/ghost-text completion — @codemirror/autocomplete only does the dropdown. Greyed inline text must be a custom ViewPlugin + decoration (recommended, ~150 lines) or a community package. This is the largest single item.

No Rust/WASM changes needed for v1 — FIM is a JS-side network call; validation reuses the existing parse/check exports.

Architecture

```
keystroke (idle) -> assemble context -> FIM client -> provider adapter -> HTTP
|
ghost text <- validate (wasm parse) <- raw completion
```

Plan

Phase 1 — Settings store + UI

  • New web-ide/src/lib/llm.svelte.js, localStorage-backed. Fields: enabled, baseUrl, apiKey, model, mode (`fim`|`chat`).
  • "AI Completion" tab in web-ide/src/lib/components/Settings.svelte: masked key, endpoint, model, on/off. Sane defaults.

Phase 2 — Provider-agnostic FIM client

  • New web-ide/src/lib/fim-provider.js. Adapter interface complete({prefix, suffix, signal}) -> string.
  • Two adapters: FIM (Codestral/DeepSeek /v1/fim/completions) and chat (OpenAI-compatible /v1/chat/completions, FIM-shaped prompt). One OpenAI-compatible client covers OpenRouter / Together / Ollama / vLLM.
  • AbortController per request; cancel on next keystroke.

Phase 3 — Context assembly + grammar primer

  • Prefix/suffix from the doc around the cursor, windowed.
  • Cached primer: compact LANG.md §10 grammar summary + 1–2 worked examples + in-scope symbols (reuse outline/outline_modules). This is what makes a general model emit valid .pds.

Phase 4 — Inline ghost-text extension

  • New web-ide/src/lib/inline-completion.js: ViewPlugin + Decoration.widget greyed text, keymap (Tab accept / Esc dismiss), debounced idle trigger (~300–500ms).
  • Wire into languageBundle() at web-ide/src/lib/components/Editor.svelte:554, gated on llm.enabled.

Phase 5 — Validation + robustness

  • Splice suggestion at cursor, run WASM parse, drop hard syntax breakage (lenient on static warnings).
  • Debounce, cancel-on-keystroke, swallow network errors (must never block typing), handle unconfigured state.

Phase 6 — Polish

  • Status indicator, error/empty states, request timeout.

Open decisions

  • Key storage: browser-only BYO key in localStorage (zero infra, key exposed to page — acceptable for a dev tool) vs a proxy holding the key (safer, infra + inference cost).
  • Default provider: local Ollama (free/private, needs install) vs hosted Codestral/OpenRouter (instant, needs key). Affects defaults/docs, not architecture.

Scope notes

  • All frontend; no breaking changes. Existing grammar completion, linting, symbol resolution unchanged.
  • Could split into sub-tasks per phase (cf. Lineage epic Data lineage canvas for the web IDE #1).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions