Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ccf108835 | ||
|
|
3dc916834f | ||
|
|
01e778daf6 |
@@ -2,6 +2,25 @@
|
||||
|
||||
Extension versions are released as `vscode-v<version>` on the [Releases](https://git.harvmaster.com/Harvmaster/autocomplete-model/releases) page. Models are versioned by their name in [`models/manifest.json`](models/manifest.json).
|
||||
|
||||
## vscode 0.6.4
|
||||
|
||||
- **Accepting a suggestion asks for the next edit immediately** (setting `nextEdit.afterAccept`, on by default). After
|
||||
Tab on a completion or a next-edit, the follow-up is requested right away instead of after the typing pause, so
|
||||
accepted next-edits chain (rename → next use → …).
|
||||
|
||||
## vscode 0.6.3
|
||||
|
||||
- **Fix: model choice didn't stick in managed mode.** The daemon's saved state (last model used) overrode both the
|
||||
model picked in the menu and a new default from a release, so it kept restarting on the old model (e.g.
|
||||
`stage-c`). A changed default now wins; switching through the daemon API still persists as before.
|
||||
|
||||
## vscode 0.6.2
|
||||
|
||||
- **Next-edit is ~3–5× faster:** the next-edit server uses n-gram speculative decoding (the rewritten region is mostly
|
||||
a copy of the current one, so drafted tokens are verified in batches). Same output, since decoding is greedy.
|
||||
Rewriting a 13-line region on a laptop CPU: ~2.1 s → ~0.7 s (~0.4 s when the prompt is cached).
|
||||
- Next-edit timings in "Show Last Prompt" now split TS errors / prompt / generation time.
|
||||
|
||||
## vscode 0.6.1
|
||||
|
||||
- **New default model `stage-d`**, with **`stage-e` for next-edit**. Both download automatically (~530 MB each); the
|
||||
|
||||
@@ -32,6 +32,9 @@ export interface DaemonConfig {
|
||||
ctx: number
|
||||
/** Extra flags, e.g. ["-ngl", "99"] on Apple Silicon, ["-t", "16"] on the laptop CPU. */
|
||||
args: string[]
|
||||
/** Extra flags for the next-edit companion server only. Default: n-gram speculative decoding (the output is
|
||||
* mostly a copy of the region, so drafts from the prompt are accepted; exact with greedy sampling). */
|
||||
nextEditArgs?: string[]
|
||||
}
|
||||
models: Record<string, ModelProfile>
|
||||
defaultModel: string
|
||||
@@ -73,6 +76,8 @@ export interface RuntimeState {
|
||||
enabled: boolean
|
||||
model: string
|
||||
mode: ContextMode
|
||||
/** config.defaultModel when this state was saved: a changed default (editor model setting, new release) wins. */
|
||||
defaultModel?: string
|
||||
}
|
||||
|
||||
export function loadConfig(configPath: string): DaemonConfig {
|
||||
@@ -94,12 +99,13 @@ export function loadConfig(configPath: string): DaemonConfig {
|
||||
}
|
||||
|
||||
export function loadState(config: DaemonConfig): RuntimeState {
|
||||
const fallback: RuntimeState = { enabled: true, model: config.defaultModel, mode: config.models[config.defaultModel].defaultMode }
|
||||
const fallback: RuntimeState = { enabled: true, model: config.defaultModel, mode: config.models[config.defaultModel].defaultMode, defaultModel: config.defaultModel }
|
||||
try {
|
||||
const s = JSON.parse(fs.readFileSync(config.stateFile, 'utf8')) as Partial<RuntimeState>
|
||||
const model = s.model && config.models[s.model] ? s.model : fallback.model
|
||||
const keepModel = s.defaultModel === config.defaultModel && s.model && config.models[s.model]
|
||||
const model = keepModel ? s.model! : fallback.model
|
||||
const mode = s.mode && config.models[model].modes.includes(s.mode) ? s.mode : config.models[model].defaultMode
|
||||
return { enabled: s.enabled ?? true, model, mode }
|
||||
return { enabled: s.enabled ?? true, model, mode, defaultModel: config.defaultModel }
|
||||
} catch {
|
||||
return fallback
|
||||
}
|
||||
|
||||
+5
-2
@@ -52,7 +52,8 @@ function loadModel(name: string) {
|
||||
}
|
||||
|
||||
// Next-edit companion model (profile.nextEditModel): a second llama-server, started on first use.
|
||||
const neLlama = new LlamaServer({ ...config.llamaServer, port: config.llamaServer.port + 1 })
|
||||
const NEXT_EDIT_SPECULATION = ['--spec-type', 'ngram-simple', '--spec-draft-n-max', '64', '--spec-ngram-simple-size-n', '3']
|
||||
const neLlama = new LlamaServer({ ...config.llamaServer, port: config.llamaServer.port + 1, args: [...config.llamaServer.args, ...(config.llamaServer.nextEditArgs ?? NEXT_EDIT_SPECULATION)] })
|
||||
let neLoading: Promise<void> | null = null
|
||||
/** The server that answers next-edit for the active model, or an error message. */
|
||||
async function nextEditServer(): Promise<LlamaServer | string> {
|
||||
@@ -273,9 +274,11 @@ async function handleNextEdit(body: NextEditRequest, res: http.ServerResponse, c
|
||||
const endLine = Math.min(lines.length, cursorLine + o.regionAfter + 1)
|
||||
const recentEdits = edits.observe(`${body.workspaceRoot ?? body.repo}/${body.path}`, body.path, body.text, cursorLine)
|
||||
const lang = symbolLang(body.languageId ?? '')
|
||||
const tErrors = performance.now()
|
||||
const errors = body.workspaceRoot && lang
|
||||
? await symbols.errors({ root: body.workspaceRoot, filePath: path.join(body.workspaceRoot, body.path), lang, text: body.text, cursor: body.cursor, waitMs: 300 })
|
||||
: undefined
|
||||
const errorsMs = performance.now() - tErrors
|
||||
const ids = builder.buildNextEdit({ repo: body.repo, path: body.path, file: body.text, regionStartLine: startLine, regionEndLine: endLine, cursor: body.cursor, recentEdits: recentEdits || undefined, errors },
|
||||
{ beforeTokens: o.beforeTokens, afterTokens: o.afterTokens })
|
||||
|
||||
@@ -294,7 +297,7 @@ async function handleNextEdit(body: NextEditRequest, res: http.ServerResponse, c
|
||||
const response = {
|
||||
requestId: body.requestId, noEdit, startLine, endLine, oldText, newText: noEdit ? null : newText,
|
||||
recentEdits, errors: errors ?? null, model: profile.nextEditModel ?? state.model,
|
||||
timings: { promptTokens: ids.length, processedTokens: result.timings?.prompt_n ?? null, genTokens: result.timings?.predicted_n ?? null, totalMs: round(performance.now() - t0) },
|
||||
timings: { promptTokens: ids.length, processedTokens: result.timings?.prompt_n ?? null, genTokens: result.timings?.predicted_n ?? null, errorsMs: round(errorsMs), promptMs: round(result.timings?.prompt_ms ?? 0), genMs: round(result.timings?.predicted_ms ?? 0), totalMs: round(performance.now() - t0) },
|
||||
}
|
||||
remember({ kind: 'next-edit', requestId: body.requestId, path: body.path, model: state.model, prompt: builder.decode(ids), raw, noEdit, timings: response.timings })
|
||||
json(res, 200, response)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"displayName": "Local Autocomplete",
|
||||
"description": "Inline code completion from a local fine-tuned model via the acd daemon",
|
||||
"publisher": "harvmaster",
|
||||
"version": "0.6.1",
|
||||
"version": "0.6.4",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.90.0"
|
||||
@@ -218,6 +218,11 @@
|
||||
"default": 700,
|
||||
"description": "Pause after typing before asking for a next-edit suggestion."
|
||||
},
|
||||
"localAutocomplete.nextEdit.afterAccept": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "After you accept a completion or a next-edit suggestion, immediately ask for the next edit (without waiting for the typing pause). Accepted next-edits chain: a rename suggests the next use, and so on."
|
||||
},
|
||||
"localAutocomplete.triggerOnCursorMove": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
||||
@@ -259,6 +259,7 @@ const modelSupportsNextEdit = () => !!(daemonConfig && daemonConfig.models[daemo
|
||||
// ---------- next-edit prediction (models trained for it, e.g. Stage E)
|
||||
const nextEdit = new NextEditController(s => {
|
||||
api('POST', '/v1/feedback', { event: 'next-edit-accepted', lines: s.newText.split('\n').length }).catch(() => {})
|
||||
nextEditAfterAccept('accepted-next-edit')
|
||||
})
|
||||
let nextEditTimer: NodeJS.Timeout | null = null
|
||||
let nextEditAbort: AbortController | null = null
|
||||
@@ -281,6 +282,15 @@ async function requestNextEdit(editor: vscode.TextEditor, reason: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Accepting a suggestion (next-edit or completion) asks for the follow-up right away instead of after the typing pause. */
|
||||
function nextEditAfterAccept(reason: string) {
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor || !modelSupportsNextEdit() || !settings().get<boolean>('nextEdit.afterAccept', true)) return
|
||||
if (nextEditTimer) clearTimeout(nextEditTimer)
|
||||
// Next tick: the accepted text and the cursor move have been applied by then.
|
||||
nextEditTimer = setTimeout(() => void requestNextEdit(editor, reason), 0)
|
||||
}
|
||||
|
||||
function scheduleNextEdit(editor: vscode.TextEditor, reason: string, delay: number) {
|
||||
if (nextEditTimer) clearTimeout(nextEditTimer)
|
||||
nextEditTimer = setTimeout(() => void requestNextEdit(editor, reason), delay)
|
||||
@@ -535,6 +545,7 @@ export async function activate(ctx: vscode.ExtensionContext) {
|
||||
vscode.commands.registerCommand('localAutocomplete.showLog', () => log.show()),
|
||||
vscode.commands.registerCommand('localAutocomplete.accepted', (requestId: string, text: string) => {
|
||||
api('POST', '/v1/feedback', { requestId, event: 'accepted', chars: text.length }).catch(() => {})
|
||||
nextEditAfterAccept('accepted-completion')
|
||||
}),
|
||||
vscode.workspace.onDidChangeConfiguration(e => {
|
||||
if (!e.affectsConfiguration('localAutocomplete')) return
|
||||
|
||||
Reference in New Issue
Block a user