Models stage-d (default) + stage-e (next-edit companion); extension 0.6.1
ci / build (push) Canceled after 0s
ci / build (push) Canceled after 0s
- stage-d: next-line / block-close / multi-line fixes, symbols format 2, TS errors in the header. Held-out: 0.426 exact / 0.700 edit sim (stage-c 0.405 / 0.689); block close 0.27 -> 0.79. - stage-e: next-edit (80% exact region rewrites, abstains on 100% of no-edit samples). - Manifest: nextEditModel (companion) and nextEditFormat (older extensions ignore it, so they never use next-edit with the empty-output deletion bug). - Daemon: next-edit served by the companion model in a second llama-server, started after the main model loads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J39imRDT8L7xYbWzsVWd8A
This commit is contained in:
@@ -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.0",
|
||||
"version": "0.6.1",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.90.0"
|
||||
|
||||
@@ -30,13 +30,19 @@ export interface ModelEntry {
|
||||
contextFormat?: 1 | 2
|
||||
errors?: boolean
|
||||
members?: boolean
|
||||
/** Old (≤ 0.6.0) flag: ignored, because those daemons applied an empty "no edit" output as a deletion. */
|
||||
nextEdit?: boolean
|
||||
/** Trained for next-edit (shared/nextedit.ts format version); read since 0.6.1. */
|
||||
nextEditFormat?: 1
|
||||
/** Companion model (another manifest entry) that serves next-edit for this one; downloaded alongside it. */
|
||||
nextEditModel?: string
|
||||
}
|
||||
|
||||
const profileOf = (m: ModelEntry) => ({
|
||||
const profileOf = (m: ModelEntry, available: Set<string>) => ({
|
||||
order: m.order, modes: m.modes, defaultMode: m.defaultMode,
|
||||
...(m.contextFormat ? { contextFormat: m.contextFormat } : {}), ...(m.errors ? { errors: true } : {}),
|
||||
...(m.members !== undefined ? { members: m.members } : {}), ...(m.nextEdit ? { nextEdit: true } : {}),
|
||||
...(m.members !== undefined ? { members: m.members } : {}), ...(m.nextEditFormat ? { nextEdit: true } : {}),
|
||||
...(m.nextEditModel && available.has(m.nextEditModel) ? { nextEditModel: m.nextEditModel } : {}),
|
||||
})
|
||||
|
||||
export interface Manifest {
|
||||
@@ -229,12 +235,12 @@ export async function prepare(storage: string, daemonDir: string, extensionVersi
|
||||
if (!entry) throw new Error(`model "${s.model || manifest.default}" is not in the manifest (available: ${manifest.models.map(m => m.name).join(', ')})`)
|
||||
const file = await ensureFile(entry.url, path.join(storage, 'models', entry.file), entry.sha256, `model ${entry.name}`, progress, signal)
|
||||
defaultModel = entry.name
|
||||
models[entry.name] = { path: file, ...profileOf(entry) }
|
||||
// Other models already on disk stay switchable from the menu.
|
||||
for (const m of manifest.models) {
|
||||
const f = path.join(storage, 'models', m.file)
|
||||
if (m.name !== entry.name && fs.existsSync(`${f}.verified`)) models[m.name] = { path: f, ...profileOf(m) }
|
||||
}
|
||||
const companion = entry.nextEditModel ? manifest.models.find(m => m.name === entry.nextEditModel && m.nextEditFormat) : undefined
|
||||
if (companion) await ensureFile(companion.url, path.join(storage, 'models', companion.file), companion.sha256, `next-edit model ${companion.name}`, progress, signal)
|
||||
// The selected model, its companion and other models already on disk (switchable from the menu).
|
||||
const onDisk = manifest.models.filter(m => m.name === entry.name || fs.existsSync(`${path.join(storage, 'models', m.file)}.verified`))
|
||||
const available = new Set(onDisk.filter(m => m.nextEditFormat).map(m => m.name))
|
||||
for (const m of onDisk) models[m.name] = { path: path.join(storage, 'models', m.file), ...profileOf(m, available) }
|
||||
}
|
||||
|
||||
const binary = s.llamaServerPath ? s.llamaServerPath.replace(/^~(?=$|\/)/, os.homedir()) : await ensureLlamaServer(storage, llamaCpp, progress, signal)
|
||||
|
||||
@@ -36,20 +36,29 @@ const step = (name, fn) => fn().then(r => { console.log(`PASS ${name}`); return
|
||||
|
||||
// 1. Simulate an interrupted model download: first 100 MB already on disk.
|
||||
fs.mkdirSync(path.join(storage, 'models'), { recursive: true })
|
||||
const modelSrc = path.join(releaseDir, 'stage-c-q8_0.gguf')
|
||||
const modelSrc = path.join(releaseDir, 'stage-d-q8_0.gguf')
|
||||
const fd = fs.openSync(modelSrc, 'r'); const buf = Buffer.alloc(100e6); fs.readSync(fd, buf, 0, buf.length, 0); fs.closeSync(fd)
|
||||
fs.writeFileSync(path.join(storage, 'models', 'stage-c-q8_0.gguf.part'), buf)
|
||||
fs.writeFileSync(path.join(storage, 'models', 'stage-d-q8_0.gguf.part'), buf)
|
||||
|
||||
let t0 = Date.now()
|
||||
const prepared = await step('first run: manifest, resumed + verified model download, llama.cpp download + unpack', () => core.prepare(storage, daemonDir, '0.4.0-test', settings, progress))
|
||||
console.log(` ${((Date.now() - t0) / 1000).toFixed(1)} s; range requests: ${rangeRequests}; model verified: ${fs.existsSync(path.join(storage, 'models', 'stage-c-q8_0.gguf.verified'))}`)
|
||||
console.log(` ${((Date.now() - t0) / 1000).toFixed(1)} s; range requests: ${rangeRequests}; model verified: ${fs.existsSync(path.join(storage, 'models', 'stage-d-q8_0.gguf.verified'))}`)
|
||||
assert.ok(rangeRequests >= 1, 'download should have resumed with a Range request')
|
||||
const cfg = JSON.parse(fs.readFileSync(prepared.configPath, 'utf8'))
|
||||
assert.ok(fs.existsSync(cfg.llamaServer.binary), 'llama-server unpacked')
|
||||
await step('next-edit companion downloaded and wired (stage-d → stage-e)', async () => {
|
||||
assert.equal(cfg.defaultModel, 'stage-d')
|
||||
assert.equal(cfg.models['stage-d'].nextEditModel, 'stage-e')
|
||||
assert.equal(cfg.models['stage-e'].nextEdit, true)
|
||||
assert.ok(fs.existsSync(path.join(storage, 'models', 'stage-e-q8_0.gguf.verified')))
|
||||
})
|
||||
|
||||
await step('daemon starts on the editor runtime', async () => assert.equal(await core.ensureRunning(storage, daemonDir, prepared, settings.port, progress), 'started'))
|
||||
const fim = await (await fetch('http://127.0.0.1:7111/v1/fim', { method: 'POST', body: JSON.stringify({ repo: 'e2e', path: 'src/a.ts', prefix: "import { z } from 'zod'\nexport const s = z.object({ a: z.string() })\nexport type S = ", suffix: '\n' }) })).json()
|
||||
await step(`completion through the managed daemon (${JSON.stringify(fim.text)})`, async () => assert.match(fim.text, /z\.infer<typeof s>/))
|
||||
const neText = "export function f(s: string) {\n const t = s.trim()\n return parse(t))\n}\n"
|
||||
const ne = await (await fetch('http://127.0.0.1:7111/v1/next-edit', { method: 'POST', body: JSON.stringify({ repo: 'e2e', path: 'src/f.ts', text: neText, cursor: neText.indexOf('(t)') + 1 }) })).json()
|
||||
await step(`next-edit through the companion model (${JSON.stringify(ne.newText ?? ne.error ?? ne)})`, async () => assert.ok(ne.newText?.includes('return parse(t)\n')))
|
||||
|
||||
t0 = Date.now()
|
||||
const again = await step('second window: no re-download, daemon reused', async () => {
|
||||
|
||||
Reference in New Issue
Block a user