From f2a2bd11c600435c47fb0fc426688e5e13c9ebd8 Mon Sep 17 00:00:00 2001 From: Fahad Iftikhar Date: Fri, 14 Aug 2026 10:39:00 +0500 Subject: [PATCH 1/3] fix(autocomplete): no ghost text in the Copilot Chat prompt box The provider registers on '**', and VS Code exposes the chat prompt as a virtual editor document with the chatSessionInput / sessions-chat schemes (chatInputSchemes in VS Code's chat/common/constants.ts), so suggestions were also offered while typing prompts. The provider now early-returns for chat-input documents via the pure isChatInputDocument() helper; unit-tested (278 tests). --- CHANGELOG.md | 2 ++ src/autocomplete/context.ts | 14 ++++++++++++++ src/autocomplete/provider.ts | 8 +++++++- src/test/autocomplete.test.ts | 14 +++++++++++++- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a051bd..e126b22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to the **OpenCode Go BYOK Provider** extension are documente ### Added +- **`[Autocomplete]` Ghost text no longer appears in the Copilot Chat prompt box.** The provider registers on `"**"`, and VS Code exposes the chat prompt as a virtual editor document (`chatSessionInput` / `sessions-chat` schemes — `chatInputSchemes` in VS Code's `chat/common/constants.ts`), so suggestions were also offered while writing prompts. The provider now skips chat-input documents (`isChatInputDocument`), keeping completions in code editors only. + - **`[Autocomplete]` Known limitation (follow-up planned):** inline-completion requests are not yet wired into the Go usage tracker's cost accounting (`tracker.record()` only runs in the chat provider path). The panel does track **Suggested / Approved** counts per day (see the usage dashboard entry), but the USD cost of completions is not attributed until a follow-up ships the transport summary from the completion engine. This is tracked as a documented TODO on #136/#138 rather than an oversight. - **`[Autocomplete]` Inline code suggestions (experimental, #49).** Ghost-text completions while typing, powered by the OpenCode gateway with thinking forced off. Opt-in via `opencodego.inlineSuggestions` (default `false`); model via `opencodego.inlineSuggestionsModel` (default `qwen3.5-plus`, whose `enable_thinking=false` mode is a genuine no-reasoning path — measured ~1.5s time-to-first-token with zero hidden reasoning). Requests are tiny (10 lines before the cursor + a short suffix), debounced 300ms, time out at 3s and abort on the next keystroke. The gateway exposes no FIM endpoint, so completions emulate fill-in-the-middle with FIM tokens over `/chat/completions`. New `src/autocomplete/` module (context, prompt, throttle, engine, provider, registration) with unit tests; `scripts/probe-completion-latency.ts` measures engine latency live. All timing/size knobs are user-tunable: `inlineSuggestionsDebounceMs`, `inlineSuggestionsTimeoutMs`, `inlineSuggestionsMaxTokens`, `inlineSuggestionsPrefixLines`, `inlineSuggestionsSuffixChars`. diff --git a/src/autocomplete/context.ts b/src/autocomplete/context.ts index 292671a..a9c49ba 100644 --- a/src/autocomplete/context.ts +++ b/src/autocomplete/context.ts @@ -15,6 +15,20 @@ export { DEFAULT_INLINE_MAX_TOKENS as DEFAULT_MAX_TOKENS, } from "../config"; +/** + * VS Code exposes the Copilot Chat prompt box as a virtual editor document + * with one of these schemes (see `chatInputSchemes` in the VS Code source: + * `workbench/contrib/chat/common/constants.ts`). Inline-completion providers + * registered on `"**"` are asked for suggestions there too — ghost text must + * never appear while the user is typing a prompt. + */ +export const CHAT_INPUT_SCHEMES = new Set(["chatSessionInput", "sessions-chat"]); + +/** Whether a document is a chat/interactive prompt box (no completions there). */ +export function isChatInputDocument(uri: { scheme: string }): boolean { + return CHAT_INPUT_SCHEMES.has(uri.scheme); +} + export interface CompletionWindowOptions { prefixLines?: number; suffixChars?: number; diff --git a/src/autocomplete/provider.ts b/src/autocomplete/provider.ts index 82b6382..5f61df5 100644 --- a/src/autocomplete/provider.ts +++ b/src/autocomplete/provider.ts @@ -7,7 +7,7 @@ */ import * as vscode from "vscode"; -import { buildCompletionWindow } from "./context"; +import { buildCompletionWindow, isChatInputDocument } from "./context"; import { Debouncer } from "./throttle"; import type { CompletionContext, CompletionEngine } from "./types"; @@ -48,6 +48,12 @@ export class OpenCodeInlineCompletionProvider implements vscode.InlineCompletion return Promise.resolve(undefined); } + // Never suggest in the Copilot Chat prompt box (virtual chatSessionInput + // document) — ghost text belongs in code, not while writing a prompt. + if (isChatInputDocument(document.uri)) { + return Promise.resolve(undefined); + } + // Keep the debounce window live: a config change applies on the next // keystroke instead of requiring the provider to be recreated. const debounceMs = this.options.resolveDebounceMs(); diff --git a/src/test/autocomplete.test.ts b/src/test/autocomplete.test.ts index 57f5e79..6a36443 100644 --- a/src/test/autocomplete.test.ts +++ b/src/test/autocomplete.test.ts @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import { describe, it } from "node:test"; -import { buildCompletionWindow, DEFAULT_PREFIX_LINES, DEFAULT_SUFFIX_CHARS } from "../autocomplete/context"; +import { buildCompletionWindow, DEFAULT_PREFIX_LINES, DEFAULT_SUFFIX_CHARS, isChatInputDocument } from "../autocomplete/context"; import { buildCompletionPrompt, completionFamily, COMPLETION_SYSTEM_PROMPT } from "../autocomplete/prompt"; import { cleanCompletion, extractChatCompletionText, parseSseData } from "../autocomplete/engine"; import { Debouncer } from "../autocomplete/throttle"; @@ -103,6 +103,18 @@ describe("autocomplete — engine parsing", () => { }); }); +describe("autocomplete — isChatInputDocument", () => { + it("rejects the Copilot Chat prompt box schemes", () => { + assert.ok(isChatInputDocument({ scheme: "chatSessionInput" })); + assert.ok(isChatInputDocument({ scheme: "sessions-chat" })); + }); + + it("accepts real code documents", () => { + assert.ok(!isChatInputDocument({ scheme: "file" })); + assert.ok(!isChatInputDocument({ scheme: "untitled" })); + }); +}); + describe("autocomplete — Debouncer", () => { it("honors a custom delay", async () => { const d = new Debouncer(120); From 5f9be067fe864368e1977b8ce20d75a07298a065 Mon Sep 17 00:00:00 2001 From: Fahad Iftikhar Date: Fri, 14 Aug 2026 10:47:45 +0500 Subject: [PATCH 2/3] feat(autocomplete): make chat-prompt suggestions opt-in Instead of unconditionally blocking the Copilot Chat prompt box, a new setting opencodego.inlineSuggestionsChatInput (default false) lets users opt in to completions while typing prompts; the provider skips chat-input documents unless it is enabled. The pure isChatInputDocument() helper remains as the gate (278 tests). --- CHANGELOG.md | 2 +- package.json | 5 +++++ src/autocomplete/index.ts | 3 +++ src/autocomplete/provider.ts | 9 ++++++--- src/config.ts | 3 +++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e126b22..cb47266 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to the **OpenCode Go BYOK Provider** extension are documente ### Added -- **`[Autocomplete]` Ghost text no longer appears in the Copilot Chat prompt box.** The provider registers on `"**"`, and VS Code exposes the chat prompt as a virtual editor document (`chatSessionInput` / `sessions-chat` schemes — `chatInputSchemes` in VS Code's `chat/common/constants.ts`), so suggestions were also offered while writing prompts. The provider now skips chat-input documents (`isChatInputDocument`), keeping completions in code editors only. +- **`[Autocomplete]` Chat-prompt suggestions are now opt-in.** VS Code exposes the Copilot Chat prompt box as a virtual `chatSessionInput` document, and the `"**"` provider selector matched it — so ghost text appeared while writing prompts. The provider now skips chat-input documents by default (`isChatInputDocument`), and users who want completions there can opt in via `opencodego.inlineSuggestionsChatInput`. - **`[Autocomplete]` Known limitation (follow-up planned):** inline-completion requests are not yet wired into the Go usage tracker's cost accounting (`tracker.record()` only runs in the chat provider path). The panel does track **Suggested / Approved** counts per day (see the usage dashboard entry), but the USD cost of completions is not attributed until a follow-up ships the transport summary from the completion engine. This is tracked as a documented TODO on #136/#138 rather than an oversight. diff --git a/package.json b/package.json index b228d1c..0d4451a 100644 --- a/package.json +++ b/package.json @@ -300,6 +300,11 @@ "default": "qwen3.5-plus", "markdownDescription": "Model used for inline code suggestions. Prefer the non-thinking Qwen options — measured: `qwen3.5-plus` with `enable_thinking=false` returns in ~1.5s with zero hidden reasoning, while reasoning models (e.g. `deepseek-v4-flash`) burn 100+ reasoning tokens even with thinking off." }, + "opencodego.inlineSuggestionsChatInput": { + "type": "boolean", + "default": false, + "description": "Also offer inline suggestions inside the GitHub Copilot Chat prompt box while typing prompts. Off by default — completions are meant for code editors." + }, "opencodego.inlineSuggestionsDebounceMs": { "type": "number", "default": 300, diff --git a/src/autocomplete/index.ts b/src/autocomplete/index.ts index 13b9a9b..01a93ce 100644 --- a/src/autocomplete/index.ts +++ b/src/autocomplete/index.ts @@ -15,6 +15,7 @@ import { COMPLETION_USAGE_KEY, COMPLETION_USAGE_MAX_DAYS, CONFIG_SECTION, + DEFAULT_INLINE_SUGGESTIONS_CHAT_INPUT, DEFAULT_INLINE_DEBOUNCE_MS, DEFAULT_INLINE_MAX_TOKENS, DEFAULT_INLINE_MODEL, @@ -28,6 +29,7 @@ import { INLINE_SUGGESTIONS_SETTING, INLINE_SUFFIX_CHARS_SETTING, INLINE_TIMEOUT_MS_SETTING, + SETTING_INLINE_SUGGESTIONS_CHAT_INPUT, } from "../config"; import { toFiniteNumber } from "../utils"; import { bumpCompletionUsage, matchesAcceptance, utcDayStart, type CompletionUsageDay } from "./usage"; @@ -146,6 +148,7 @@ export function registerInlineCompletions(context: vscode.ExtensionContext, deps resolveMaxTokens: () => readNumberSetting(INLINE_MAX_TOKENS_SETTING, DEFAULT_INLINE_MAX_TOKENS, 16, 1_024), resolvePrefixLines: () => readNumberSetting(INLINE_PREFIX_LINES_SETTING, DEFAULT_INLINE_PREFIX_LINES, 1, 100), resolveSuffixChars: () => readNumberSetting(INLINE_SUFFIX_CHARS_SETTING, DEFAULT_INLINE_SUFFIX_CHARS, 0, 5_000), + resolveChatInputEnabled: () => readSetting(SETTING_INLINE_SUGGESTIONS_CHAT_INPUT, DEFAULT_INLINE_SUGGESTIONS_CHAT_INPUT), }); const registration = vscode.languages.registerInlineCompletionItemProvider({ pattern: "**" }, provider); diff --git a/src/autocomplete/provider.ts b/src/autocomplete/provider.ts index 5f61df5..e8e5fb3 100644 --- a/src/autocomplete/provider.ts +++ b/src/autocomplete/provider.ts @@ -21,6 +21,8 @@ export interface InlineCompletionProviderOptions { isEnabled: () => boolean; /** The model to use for suggestions (config-driven). */ resolveModelId: () => string; + /** Whether suggestions are allowed inside the chat prompt box (opt-in). */ + resolveChatInputEnabled: () => boolean; /** Debounce delay in ms before a request is sent (config-driven). */ resolveDebounceMs: () => number; /** Max tokens a completion may produce (config-driven). */ @@ -48,9 +50,10 @@ export class OpenCodeInlineCompletionProvider implements vscode.InlineCompletion return Promise.resolve(undefined); } - // Never suggest in the Copilot Chat prompt box (virtual chatSessionInput - // document) — ghost text belongs in code, not while writing a prompt. - if (isChatInputDocument(document.uri)) { + // The Copilot Chat prompt box is a virtual chatSessionInput document. + // Off by default (completions belong in code editors); users can opt in + // via opencodego.inlineSuggestionsChatInput. + if (isChatInputDocument(document.uri) && !this.options.resolveChatInputEnabled()) { return Promise.resolve(undefined); } diff --git a/src/config.ts b/src/config.ts index f0ca714..f4680b7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -56,6 +56,9 @@ export const SETTING_THINKING_MIMO = "thinking.mimo"; export const INLINE_SUGGESTIONS_SETTING = "inlineSuggestions"; export const INLINE_SUGGESTIONS_MODEL_SETTING = "inlineSuggestionsModel"; +/** Opt-in: also offer completions inside the Copilot Chat prompt box. */ +export const SETTING_INLINE_SUGGESTIONS_CHAT_INPUT = "inlineSuggestionsChatInput"; +export const DEFAULT_INLINE_SUGGESTIONS_CHAT_INPUT = false; export const INLINE_DEBOUNCE_MS_SETTING = "inlineSuggestionsDebounceMs"; export const INLINE_TIMEOUT_MS_SETTING = "inlineSuggestionsTimeoutMs"; export const INLINE_MAX_TOKENS_SETTING = "inlineSuggestionsMaxTokens"; From fd7981b936e6847937e4803a8d0a0255828fe8e8 Mon Sep 17 00:00:00 2001 From: Fahad Iftikhar Date: Fri, 14 Aug 2026 14:29:29 +0500 Subject: [PATCH 3/3] feat(autocomplete): code-editor allowlist for completions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the #154 review note: CHAT_INPUT_SCHEMES hardcodes two schemes, so future VS Code interactive surfaces would need a manual update. The provider now serves only real editable code surfaces via a CODE_EDITOR_SCHEMES allowlist (file, untitled, git, vscode-userdata, vscode-notebook-cell) — any new chat/interactive/webview surface is excluded automatically — with the opt-in chat-prompt carve-out preserved. isCompletionDocument() is pure and unit-tested (280 tests). --- CHANGELOG.md | 2 +- src/autocomplete/context.ts | 15 +++++++++++++++ src/autocomplete/provider.ts | 11 ++++++----- src/test/autocomplete.test.ts | 26 +++++++++++++++++++++++++- 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb47266..9133e6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to the **OpenCode Go BYOK Provider** extension are documente ### Added -- **`[Autocomplete]` Chat-prompt suggestions are now opt-in.** VS Code exposes the Copilot Chat prompt box as a virtual `chatSessionInput` document, and the `"**"` provider selector matched it — so ghost text appeared while writing prompts. The provider now skips chat-input documents by default (`isChatInputDocument`), and users who want completions there can opt in via `opencodego.inlineSuggestionsChatInput`. +- **`[Autocomplete]` Chat-prompt suggestions are now opt-in.** VS Code exposes the Copilot Chat prompt box as a virtual `chatSessionInput` document, and the `"**"` provider selector matched it — so ghost text appeared while writing prompts. The provider now only serves real editable code surfaces (`isCompletionDocument` — a code-editor scheme allowlist, so future interactive surfaces are excluded automatically) with an opt-in carve-out for the chat prompt via `opencodego.inlineSuggestionsChatInput`. - **`[Autocomplete]` Known limitation (follow-up planned):** inline-completion requests are not yet wired into the Go usage tracker's cost accounting (`tracker.record()` only runs in the chat provider path). The panel does track **Suggested / Approved** counts per day (see the usage dashboard entry), but the USD cost of completions is not attributed until a follow-up ships the transport summary from the completion engine. This is tracked as a documented TODO on #136/#138 rather than an oversight. diff --git a/src/autocomplete/context.ts b/src/autocomplete/context.ts index a9c49ba..5db3c70 100644 --- a/src/autocomplete/context.ts +++ b/src/autocomplete/context.ts @@ -29,6 +29,21 @@ export function isChatInputDocument(uri: { scheme: string }): boolean { return CHAT_INPUT_SCHEMES.has(uri.scheme); } +/** + * Document schemes that are real editable code surfaces. Inline completions + * are only offered there. An ALLOWLIST (instead of only blocking known chat + * schemes) means any new interactive surface VS Code introduces in the + * future — chat prompt variants, webviews, output, custom editors — is + * excluded automatically without a manual update; genuinely new CODE + * surfaces are rare. + */ +export const CODE_EDITOR_SCHEMES = new Set(["file", "untitled", "git", "vscode-userdata", "vscode-notebook-cell"]); + +/** Whether a document is a normal editable code surface. */ +export function isCompletionDocument(uri: { scheme: string }): boolean { + return CODE_EDITOR_SCHEMES.has(uri.scheme); +} + export interface CompletionWindowOptions { prefixLines?: number; suffixChars?: number; diff --git a/src/autocomplete/provider.ts b/src/autocomplete/provider.ts index e8e5fb3..1119408 100644 --- a/src/autocomplete/provider.ts +++ b/src/autocomplete/provider.ts @@ -7,7 +7,7 @@ */ import * as vscode from "vscode"; -import { buildCompletionWindow, isChatInputDocument } from "./context"; +import { buildCompletionWindow, isChatInputDocument, isCompletionDocument } from "./context"; import { Debouncer } from "./throttle"; import type { CompletionContext, CompletionEngine } from "./types"; @@ -50,10 +50,11 @@ export class OpenCodeInlineCompletionProvider implements vscode.InlineCompletion return Promise.resolve(undefined); } - // The Copilot Chat prompt box is a virtual chatSessionInput document. - // Off by default (completions belong in code editors); users can opt in - // via opencodego.inlineSuggestionsChatInput. - if (isChatInputDocument(document.uri) && !this.options.resolveChatInputEnabled()) { + // Only offer completions in real editable code surfaces. The Copilot + // Chat prompt box is a virtual chatSessionInput document — excluded by + // default (completions belong in code editors); users can opt in via + // opencodego.inlineSuggestionsChatInput. + if (!isCompletionDocument(document.uri) && !(isChatInputDocument(document.uri) && this.options.resolveChatInputEnabled())) { return Promise.resolve(undefined); } diff --git a/src/test/autocomplete.test.ts b/src/test/autocomplete.test.ts index 6a36443..139e550 100644 --- a/src/test/autocomplete.test.ts +++ b/src/test/autocomplete.test.ts @@ -1,6 +1,12 @@ import assert from "node:assert/strict"; import { describe, it } from "node:test"; -import { buildCompletionWindow, DEFAULT_PREFIX_LINES, DEFAULT_SUFFIX_CHARS, isChatInputDocument } from "../autocomplete/context"; +import { + buildCompletionWindow, + DEFAULT_PREFIX_LINES, + DEFAULT_SUFFIX_CHARS, + isChatInputDocument, + isCompletionDocument, +} from "../autocomplete/context"; import { buildCompletionPrompt, completionFamily, COMPLETION_SYSTEM_PROMPT } from "../autocomplete/prompt"; import { cleanCompletion, extractChatCompletionText, parseSseData } from "../autocomplete/engine"; import { Debouncer } from "../autocomplete/throttle"; @@ -115,6 +121,24 @@ describe("autocomplete — isChatInputDocument", () => { }); }); +describe("autocomplete — isCompletionDocument (code-editor allowlist)", () => { + it("accepts real editable code surfaces", () => { + assert.ok(isCompletionDocument({ scheme: "file" })); + assert.ok(isCompletionDocument({ scheme: "untitled" })); + assert.ok(isCompletionDocument({ scheme: "git" })); + assert.ok(isCompletionDocument({ scheme: "vscode-userdata" })); + assert.ok(isCompletionDocument({ scheme: "vscode-notebook-cell" })); + }); + + it("excludes chat prompts and other non-code surfaces", () => { + assert.ok(!isCompletionDocument({ scheme: "chatSessionInput" })); + assert.ok(!isCompletionDocument({ scheme: "sessions-chat" })); + assert.ok(!isCompletionDocument({ scheme: "output" })); + assert.ok(!isCompletionDocument({ scheme: "webviewPanel" })); + assert.ok(!isCompletionDocument({ scheme: "vscode-interactive-input" })); + }); +}); + describe("autocomplete — Debouncer", () => { it("honors a custom delay", async () => { const d = new Debouncer(120);