Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ 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 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.

- **`[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`.
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
29 changes: 29 additions & 0 deletions src/autocomplete/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,35 @@ 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);
}

/**
* 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;
Expand Down
3 changes: 3 additions & 0 deletions src/autocomplete/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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";
Expand Down Expand Up @@ -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);
Expand Down
12 changes: 11 additions & 1 deletion src/autocomplete/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import * as vscode from "vscode";
import { buildCompletionWindow } from "./context";
import { buildCompletionWindow, isChatInputDocument, isCompletionDocument } from "./context";
import { Debouncer } from "./throttle";
import type { CompletionContext, CompletionEngine } from "./types";

Expand All @@ -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). */
Expand Down Expand Up @@ -48,6 +50,14 @@ export class OpenCodeInlineCompletionProvider implements vscode.InlineCompletion
return Promise.resolve(undefined);
}

// 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);
}

// 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();
Expand Down
3 changes: 3 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
38 changes: 37 additions & 1 deletion src/test/autocomplete.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
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,
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";
Expand Down Expand Up @@ -103,6 +109,36 @@ 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 — 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);
Expand Down
Loading