Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
5a6e17a
feat(autocomplete): inline code suggestions engine + provider (issue …
Fahad090NP Aug 12, 2026
a1819e9
feat(autocomplete): wire provider and add settings
Fahad090NP Aug 12, 2026
42e810d
docs(autocomplete): document the experimental inline suggestions
Fahad090NP Aug 12, 2026
63e29a6
test(autocomplete): commit the missing test suite
Fahad090NP Aug 12, 2026
d01ce62
chore(tsconfig): give scripts/ its own editor type-check project
Fahad090NP Aug 12, 2026
76213fd
chore(eslint): scripts are now covered by scripts/tsconfig.json
Fahad090NP Aug 12, 2026
963ac4a
chore(tsconfig): exclude everything .gitignore ignores
Fahad090NP Aug 12, 2026
a474175
chore(tooling): shared .gitignore-patterns helper
Fahad090NP Aug 12, 2026
bbcbae5
fix(config): declare import.meta.dirname for the editor's inferred pr…
Fahad090NP Aug 12, 2026
93df430
feat(autocomplete): dedicated output channel for completions diagnostics
Fahad090NP Aug 12, 2026
ab093f5
fix(package): stop excluding out/autocomplete from the VSIX
Fahad090NP Aug 12, 2026
d912b1b
feat(autocomplete): model dropdown instead of a free-form input
Fahad090NP Aug 12, 2026
9857690
Merge branch 'main' into feat/inline-completions
Fahad090NP Aug 13, 2026
0e9ea30
feat(autocomplete): make timing/size knobs config-driven
Fahad090NP Aug 13, 2026
29ddb12
feat(settings): expose the five inline-suggestion knobs
Fahad090NP Aug 13, 2026
93b09c3
test(autocomplete): cover configurable debounce and window knobs
Fahad090NP Aug 13, 2026
9f7a534
docs(autocomplete): document the five tuning knobs
Fahad090NP Aug 13, 2026
be93383
refactor(config): centralize all tunable constants in src/config.ts
Fahad090NP Aug 13, 2026
88ca11a
refactor(streaming): share BaseResponseExtractor + verify scripts aga…
Fahad090NP Aug 13, 2026
a56ae08
refactor: sweep remaining duplicated error extraction + storage-key l…
Fahad090NP Aug 13, 2026
68c3eb6
test: cover utils + config + live debounce resync
Fahad090NP Aug 13, 2026
14821f5
docs(changelog): document the config/utils refactor and internal fixes
Fahad090NP Aug 13, 2026
96fb097
fix: seven verified logic bugs from the deep audit
Fahad090NP Aug 13, 2026
fee072d
fix: harden fetch-error classification and document the audit fixes
Fahad090NP Aug 13, 2026
9777898
feat(usage): CLI-backed today/yesterday, codebase row, permanent trac…
Fahad090NP Aug 13, 2026
a113589
fix(usage): profile trackers read the CLI history for device rows
Fahad090NP Aug 13, 2026
13c1c47
feat(usage): compact K/M/B/T formatting everywhere
Fahad090NP Aug 13, 2026
075d49a
feat(usage): compact request counts (1.7k) everywhere
Fahad090NP Aug 13, 2026
63a18a0
perf(usage): instant startup + cached CLI history read
Fahad090NP Aug 13, 2026
5af40ce
feat(usage): realtime status bar, tooltip and panel updates
Fahad090NP Aug 13, 2026
1e5b33e
feat(usage): full-page usage dashboard with charts and shortcut
Fahad090NP Aug 13, 2026
7e53726
feat(usage): top-right action buttons, model line charts, refresh com…
Fahad090NP Aug 13, 2026
5b6f0a2
fix(usage): working buttons, tab-preserving refresh, clean axes
Fahad090NP Aug 13, 2026
c80125f
feat(usage): completion charts, window toggle, whole-chart hover
Fahad090NP Aug 13, 2026
486d092
feat(usage): lifetime default, Suggestions/Approved fixes
Fahad090NP Aug 13, 2026
46bc8ec
fix(usage): track approved completions via insert heuristic
Fahad090NP Aug 13, 2026
aa5a0ac
feat(usage): rename Suggestions tab to Suggested
Fahad090NP Aug 13, 2026
12c46e4
feat(usage): rename the workspace row back to Codebase
Fahad090NP Aug 13, 2026
599b5d3
fix(usage): harden CLI history reads + surface failures
Fahad090NP Aug 13, 2026
40b4242
fix(usage): read CLI history via node:sqlite, no binary dependency
Fahad090NP Aug 13, 2026
d1c9c59
fix(usage): freeOnly filter on the metadata-success path + escape mod…
Fahad090NP Aug 13, 2026
5da9b6c
fix(autocomplete): address #136 review — key fallback, failure logs, …
Fahad090NP Aug 13, 2026
1c26fd1
fix(models): add qwen3.7-plus to the bundled fallback lists
Fahad090NP Aug 13, 2026
abbfcd2
docs(changelog): note autocomplete spend attribution as a known limit…
Fahad090NP Aug 13, 2026
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
1 change: 0 additions & 1 deletion .vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ node_modules/**
!node_modules/@silvia-odwyer/photon-node/*.js
!node_modules/@silvia-odwyer/photon-node/*.wasm
out/**/*.js.map
out/autocomplete/**
out/test/**
scripts/**
src/**
Expand Down
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,49 @@ All notable changes to the **OpenCode Go BYOK Provider** extension are documente

### Added

- **`[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`.
- **`[Usage]` Server-accurate Go meters via the official `/zen/go/v1/usage` endpoint (#130).** The status bar, tooltip, quick-pick and usage webview previously showed locally estimated Session/Weekly/Monthly percentages that drifted from opencode.ai (issue #23) because they missed CLI, cross-device and pre-install usage. The tracker now pulls the official endpoint (upstream anomalyco/opencode#16513, verified live) with the existing Go key on startup and after each request (60s TTL cache): rolling/weekly/monthly percent + reset times are server-computed and account-wide, `spent` is derived from the authoritative percent, and Today/Yesterday + per-session spend stay device-local. Failures (401/403/404/network) fall back to the existing SQLite → tracked estimates. The key is only ever sent as the Authorization header and never logged or persisted. New pure module `src/goUsageSync.ts` with unit tests. Documented in `docs/issues/62-20260812-pr132-go-usage-server-sync.md`. PR [#132](https://github.com/ltmoerdani/opencode-copilot-chat/pull/132) by [@Fahad090NP](https://github.com/Fahad090NP).

### Changed

- **`[Internal]` Central configuration + shared utilities (no behavior change).** Every tunable value in the codebase — URLs, timeouts, limits, storage keys, setting keys and defaults — now lives in one dependency-free module `src/config.ts`, so future tuning happens in a single file. Existing modules import from it and re-export the names their callers rely on. A new pure `src/utils.ts` replaces the near-identical helpers that were copy-pasted across modules: `isRecord` (was defined 6×), `firstString`, `compactErrorCode`, `positiveNumber`, `getErrorMessage`, `formatUsd`, `formatTokenCount`, `formatRelativeTime`, `escapeHtml` and the two cancellable delay variants. Dead code removed: `getOrCreateProfile`, `formatGoUsageTooltip`, `formatGoUsageLanguageStatusDetail`, `createUsageDataPart`. Both stream extractors (`OpenAiResponseExtractor`, `AnthropicResponseExtractor`) now share a `BaseResponseExtractor` instead of duplicating reasoning accounting and think-tag filtering. The verification scripts (`verify-estimate-token-count`, `validate-models`) import the real production logic instead of drifted copies. Sanity tests added for the new modules (`utils.test.ts`, `config.test.ts`, 235 unit tests total).
- **`[Tooling]` Strict-but-sane lint stack + intelligent pre-commit gate (PR #129).** ESLint keeps the type-aware rules that catch real bugs (`strict` + `strictTypeChecked`: `no-unsafe-*`, `no-unnecessary-condition`, `no-floating-promises`) and drops the pure-`stylistic` layer that fought prettier. `npm run lint` / `bun lint` now ends with a Tests step (compile + unit tests), making it the single "is everything green" command. A new intelligent pre-commit gate (`scripts/staged-lint.ts`, `npm run lint:staged`) lints staged files **plus their direct import dependents** (resolved from the real import graph) instead of either the whole tree (slow) or only the staged files (blind) — so changing a module can never leave type-aware errors in its consumers. Measured: config-only commit ≈1s, `src/` commit ≈10s; full-tree lint stays in CI. Branch also carries the unified `scripts/lint.ts`/`format.ts` runners, `editorconfig-checker` + `shellcheck` + `tsconfig.check.json` type-check (covers `scripts/`, which the build tsconfig never did), script renames (`*.mjs`/`*.mts` → `*.ts`), eslint 10.8.1 + `@types/node` 26.2 (supersedes dependabot #91), and husky PATH fixes. Post-review refinements (5 commits): all 217 `void describe/it/test` prefixes dropped from test files (`22e04b7`), `@ts-expect-error` allowed for proposed-API workarounds while `@ts-ignore` stays banned (`5246434`), repo moved to TypeScript-first config (`eslint.config.ts`, typed scripts via `tsx`, `76570cc`) and standard extensions (`514a63f`), markdownlint config renamed to `.json` (`c817871`). Documented in `docs/issues/61-20260812-pr129-strict-lint-stack-precommit-gate.md`. PR [#129](https://github.com/ltmoerdani/opencode-copilot-chat/pull/129) by [@Fahad090NP](https://github.com/Fahad090NP).

### Fixed

- **`[Usage]` SQLite reads no longer depend on the `sqlite3` binary.** The zero-usage mystery was the Android SDK's `sqlite3` (`~/Android/Sdk/platform-tools/sqlite3`) being on the PATH only when VS Code launches from a shell that exports it — desktop-launched windows silently lost all CLI history (Today/Yesterday/Codebase = 0 while the fetched quota kept working). The CLI history is now read through Node's built-in `node:sqlite` first (zero external dependencies, retried twice on busy WAL states), falling back to the `sqlite3` binary resolved from PATH **plus** known locations (system, Homebrew, Android SDK). Failures are logged with the exact error to the "OpenCode Go Usage" output channel.

- **`[Usage]` Panel polish + chat-completion charts.** Hovering anywhere on a chart (not just on points) highlights the nearest day with a guide line + dots and a cursor-follow tooltip; on the Models tab the tooltip lists every model's spend for that day. Two new tabs — **Suggested** and **Approved** — chart inline chat completions with whole-number axes and honest tooltips (the day's suggestion/approval counts, not chat token totals); both series share the exact same day buckets, so hovers never resolve to undefined. Acceptances are detected with a bounded heuristic: VS Code's stable API exposes no inline-completion acceptance event, so committing a ghost text is recognized by the document insert starting exactly at the suggested position with a matching multi-character text (30s window, cleared on first match — see `matchesAcceptance`). The default chart window is **Lifetime**, switchable live via the **Window** button (Week → 14 days → Month → Lifetime). The panel brand shows only the profile name, the legend swatches are square and text-aligned, and axes keep round equal tick steps.

- **`[Usage]` Full-page usage panel.** The usage webview is now a complete dashboard (inspired by the reference page): subscription meters as animated rings, stat chips for Today / Yesterday / Codebase, an interactive chart area with Spend / Requests / Tokens tabs (all line charts) and a By-model tab (overlapping colored line charts per model by spend, with a legend and hover tooltips for the model · day · spend/tokens/requests breakdown). Data follows the cursor with a tooltip on every chart element. Chart window: `opencodego.usageChartDays` (default 14 days). The all-time workspace row is labeled **Codebase** everywhere (panel, quick-pick, status-bar tooltip); the old "Top model" / "Models used" chips are gone. Styled action buttons sit in the panel's top-right corner: **Set targets**, **Rename** (when multiple profiles exist) and **Refresh** — wired through `vscode.postMessage`, and a refresh keeps the active tab (data is pushed in place, the page is never reloaded; the background loop still runs at `usageRefreshIntervalSeconds`). Chart axes use round tick steps (1/2/2.5/5 × 10ⁿ) so gaps are always equal and clean (no more 1.25/2.50/3.75 divisions).

- **`[Usage]` Realtime updates.** The status bar, tooltip and usage panel now refresh on a configurable background cadence (`opencodego.usageRefreshIntervalSeconds`, default 60s, min 5s), so terminal-side OpenCode CLI usage, server meters and midnight day rollovers appear without waiting for the next chat request. Changing any usage-view setting repaints the UI immediately, and the refresh interval itself applies live on the next tick.

- **`[Usage]` Instant startup + no more slow usage reads.** The last successful server-usage snapshot is now persisted (`opencodego.serverUsage.v1`) and restored on window start, so the status bar / tooltip render real meters immediately instead of 0s until the network refetch lands (the TTL-guarded refresh still runs in the background). The CLI database read — a blocking `sqlite3` call over a multi-GB file — is memoized for 3 seconds, so a burst of UI refreshes (status bar + tooltip + panel) pays the query cost once.

- **`[Usage]` Compact number formatting everywhere.** Token AND request counts render as `1.2T` / `1.2B` / `1.2M` / `12k` (with correct unit escalation at rounding boundaries — no more `1000.0M`), and dollars as `$1.23M` / `$1.50K` / `$12.30` with sub-cent precision (`$0.0004`) so tiny spend never collapses to `$0.00`. Applied to the status-bar tooltip, tooltip card, quick-pick and usage panel.

- **`[Usage]` Today/Yesterday now merge the OpenCode CLI history with extension-tracked usage.** The CLI database (`~/.local/share/opencode/opencode.db`) stores per-message `cost` + `tokens.{input,output,reasoning,cache}` + `path.cwd` + timestamps, so users who also run OpenCode in the terminal get their real combined daily totals (terminal + VS Code) instead of VS Code-only estimates. Configurable via `opencodego.usageTodayYesterdaySource` (`auto` / `cli` / `extension`).
- **`[Usage]` New "Codebase" row replaces the old "Session (est)" estimate.** Shows all-time usage for the CURRENT workspace (matched per-directory from the CLI history `path.cwd`), forever by default (`opencodego.usageCodebaseWindowDays`, 0 = all history). Toggle with `opencodego.usageCodebaseRow`.
- **`[Usage]` Local tracked data is permanent.** The "Reset tracked usage data" action is removed and the 31-day entry cutoff is gone (only the hard 2000-entry cap applies) — the data today/yesterday/codebase rely on can no longer be deleted or silently expire.
- **`[Usage]` New display options:** `opencodego.usageRollingSessionMeter` (hide the server 5-hour rolling meter in detailed views) and `opencodego.usageDayBoundary` (`utc` / `local` midnight for the Today/Yesterday rows).

- **`[Internal]` `sanitizeToolSchema` emitted `type: "object"` via a ternary whose branches were identical** — collapsed to the constant.
- **`[Internal]` Anthropic stream extractor wrote usage fields onto itself** instead of the request summary — the real summary was already updated by the SSE data callback, so the duplicate (and wrong-object) write is gone.
- **`[Internal]` `formatModalityBadges` duplicated its Audio branch** under two conditions that covered all cases — collapsed.
- **`[Internal]` `formatRelativeTime` rendered 26h as `26h`** instead of `1d 2h` — the days branch now takes precedence.
- **`[Internal]` Debouncer ignored debounce-window config changes until the provider was recreated** — a pending run is now rescheduled when `delayMs` changes, so `opencodego.inlineSuggestionsDebounceMs` applies on the very next keystroke.
- **`[Internal]` Numeric settings sanitized** — a misconfigured string `temperature`/`maxTokens`/timeout value can no longer reach the request body (was a potential HTTP 400 upstream).
- **`[Internal]` `resolveModelMetadata` ignored the bundled `temperature: false` fallback** — kimi-k2.7-code sent an unsupported `temperature` on cold start / failed models.dev fetch and depended on the runtime 400-retry to save every request. The fallback chain now includes the bundled value.
- **`[Internal]` Modality-based vision detection treated any non-text modality as vision** — an audio/pdf/video-only model advertised `imageInput: true`, so VS Code forwarded image attachments the model cannot process. Vision now requires an actual image modality.
- **`[Internal]` The Zen `freeOnly` model filter was only applied on the live fetch path** — when the model-list fetch failed (or was cancelled), the bundled/cached fallback list could show paid models to free-only users. The filter now applies on every path.
- **`[Internal]` The Go-gateway "thinking off" workaround misfired for Kimi K2.7 / MiniMax M3** — those route through chat-completions with an Anthropic-style `thinking` block, which the detector didn't recognize, so genuine chain-of-thought was emitted as visible text. Detection now covers every reasoning channel (`bodyRequestsThinking()`).
- **`[Internal]` `setManualSpentTargets` over-counted re-edited monthly targets on the SQLite path** — the raw-SQLite monthly sum never contains the baseline, so subtracting it (meant for the summary fallback) inflated the manual target (target $60 with $12 raw spend produced a $72 baseline).
- **`[Internal]` `buildSummaryFromRows` reported `sqliteAvailable: false`** for SQLite-backed summaries (only masked by the enriched builder overriding it).
- **`[Internal]` Error classification hardened against hosts without a global `DOMException`.**

- **`[Provider]` Duplicate OpenCode Go/Zen models after setting a per-model option (e.g. `reasoningEffort`) in the model picker (#131).** Setting a per-model option makes VS Code write a settings-only group (no `apiKey`) into `chatLanguageModels.json`; VS Code then resolves that group with `configuration: {}`, and the SecretStorage fallback ran on both the groupless call and the group call, serving every model twice (7 → 14 in the report). A group call whose `configuration` is present but carries no API key is now treated as a per-model configuration group and returns `[]`, leaving the groupless call as the single source. Per-model settings still apply at request time via `modelConfiguration`. Documented in `docs/issues/64-20260813-issue131-permodel-config-duplicate-models.md`. PR [#135](https://github.com/ltmoerdani/opencode-copilot-chat/pull/135) by [@Fahad090NP](https://github.com/Fahad090NP).

## [0.5.2] — 2026-08-11
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,16 @@ Your API key and BYOK group settings are kept, so re-enabling (or the
`Re-add to Language Models` action) restores everything. A window reload is
required after toggling.

### ✍️ Inline Code Suggestions (Experimental)

Ghost-text completions while typing, powered by the OpenCode gateway with **thinking forced off**:

- Opt-in: `"opencodego.inlineSuggestions": true` (requires a window reload).
- Model: `opencodego.inlineSuggestionsModel` — defaults to `qwen3.5-plus`, whose `enable_thinking=false` mode is a genuine no-reasoning path (measured ~1.5s TTFB, zero hidden reasoning). Reasoning models (e.g. `deepseek-v4-flash`) burn 100+ reasoning tokens even with thinking off and are not recommended.
- Tuning knobs (all optional): `inlineSuggestionsDebounceMs` (300), `inlineSuggestionsTimeoutMs` (3000), `inlineSuggestionsMaxTokens` (128), `inlineSuggestionsPrefixLines` (10), `inlineSuggestionsSuffixChars` (300).
- Requests are tiny (10 lines before the cursor + a short suffix by default), debounced 300ms, time out at 3s, and are aborted on the next keystroke.
- The gateway exposes no FIM endpoint, so completions use fill-in-the-middle emulation with FIM tokens over `/chat/completions`.

### 🛠️ Smart Routing & Reliability

- **Native endpoint routing** per family (see [Models](#-models) table)
Expand Down
25 changes: 19 additions & 6 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,30 @@
// via `--max-warnings 0`. The only rules disabled here are ones whose noise
// outweighs their value (see the scoped overrides below).

import { readFileSync } from "node:fs";
import { defineConfig } from "eslint/config";
import tseslint from "typescript-eslint";
import yml from "eslint-plugin-yml";
import jsonc from "eslint-plugin-jsonc";
import { gitignorePatterns } from "./scripts/gitignore";

const gitignore = readFileSync(new URL(".gitignore", import.meta.url), "utf8")
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line && !line.startsWith("#") && !line.startsWith("!"));
// eslint.config.ts is loaded as ESM by ESLint (via jiti) but lives outside
// both tsconfig projects, so the editor's inferred CommonJS project does not
// see @types/node's `import.meta.dirname` (declared only for node16/nodenext
// modules). It exists at runtime; declare it explicitly to silence the
// editor diagnostic. Matches @types/node's own declaration, so type-checked
// consumers merge cleanly.
declare global {
interface ImportMeta {
dirname: string;
}
}

const gitignore = gitignorePatterns();
// Files not covered by tsconfig (which only includes src/), type-checked via
// the default project so strictTypeChecked rules still apply to them.
const nonProjectFiles = ["eslint.config.ts", "scripts/*.ts"];
// eslint.config.ts is ESM-only (loaded via jiti) and lives outside both
// tsconfig projects; scripts/*.ts are covered by scripts/tsconfig.json.
const nonProjectFiles = ["eslint.config.ts"];

// The typescript-eslint `config()` helper is deprecated; ESLint core now
// provides `defineConfig()`. We replicate the helper's `extends` expansion
Expand All @@ -51,6 +61,9 @@ export default defineConfig([
parserOptions: {
projectService: {
allowDefaultProject: nonProjectFiles,
// scripts/ (and eslint.config.ts) are type-checked via the default
// project; keep the cap above the script count as it grows.
maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING: 64,
},
tsconfigRootDir: import.meta.dirname,
},
Expand Down
Loading
Loading