Models stage-d (default) + stage-e (next-edit companion); extension 0.6.1
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:
2026-10-04 00:23:15 +00:00
co-authored by Claude Opus 5.5
parent 5c99f011e8
commit d246fb9349
12 changed files with 144 additions and 28 deletions
+3
View File
@@ -18,6 +18,8 @@ export interface ModelProfile {
members?: boolean
/** Trained for next-edit prediction (shared/nextedit.ts), enables /v1/next-edit. */
nextEdit?: boolean
/** Another profile (trained with nextEdit) that serves /v1/next-edit for this model, in a second llama-server. */
nextEditModel?: string
}
export interface DaemonConfig {
@@ -86,6 +88,7 @@ export function loadConfig(configPath: string): DaemonConfig {
if (!raw.models[raw.defaultModel]) throw new Error(`defaultModel ${raw.defaultModel} is not in models`)
for (const [name, m] of Object.entries(raw.models)) {
if (!m.modes.includes(m.defaultMode)) throw new Error(`model ${name}: defaultMode must be one of its modes`)
if (m.nextEditModel && !raw.models[m.nextEditModel]?.nextEdit) throw new Error(`model ${name}: nextEditModel ${m.nextEditModel} must be a profile with nextEdit`)
}
return raw
}
+31 -11
View File
@@ -47,17 +47,34 @@ function loadModel(name: string) {
loadError = null
loading = llama.start(config.models[name].path)
.catch(e => { loadError = (e as Error).message; console.error(loadError) })
.finally(() => { loading = null })
.finally(() => { loading = null; if (config.models[name].nextEditModel) void nextEditServer() })
return loading
}
// 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 })
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> {
const profile = config.models[state.model]
if (profile.nextEdit) return loading || !llama.running ? (loadError ?? 'model loading') : llama
const companion = profile.nextEditModel
if (!companion) return `model ${state.model} is not trained for next-edit`
const wanted = config.models[companion].path
if (neLlama.model !== wanted && !neLoading) {
neLoading = neLlama.start(wanted).catch(e => console.error(`next-edit model ${companion}: ${(e as Error).message}`)).finally(() => { neLoading = null })
}
if (neLoading) await neLoading
return neLlama.running && neLlama.model === wanted ? neLlama : `next-edit model ${companion} failed to start`
}
let inflight: AbortController | null = null
function describeConfig() {
return {
...state,
effectiveMode: state.mode,
models: Object.fromEntries(Object.entries(config.models).map(([n, m]) => [n, { order: m.order, modes: m.modes, defaultMode: m.defaultMode, nextEdit: m.nextEdit ?? false, errors: m.errors ?? false, contextFormat: m.contextFormat ?? 1 }])),
models: Object.fromEntries(Object.entries(config.models).map(([n, m]) => [n, { order: m.order, modes: m.modes, defaultMode: m.defaultMode, nextEdit: !!(m.nextEdit || m.nextEditModel), nextEditModel: m.nextEditModel ?? null, errors: m.errors ?? false, contextFormat: m.contextFormat ?? 1 }])),
llama: { running: llama.running, loading: loading !== null, error: loadError },
}
}
@@ -245,8 +262,8 @@ async function handleNextEdit(body: NextEditRequest, res: http.ServerResponse, c
const t0 = performance.now()
const profile = config.models[state.model]
if (!state.enabled) return json(res, 200, { noEdit: true, disabled: true })
if (!profile.nextEdit) return json(res, 400, { error: `model ${state.model} is not trained for next-edit` })
if (loading || !llama.running) return json(res, 503, { error: loadError ?? 'model loading' })
const server = await nextEditServer()
if (typeof server === 'string') return json(res, server.includes('not trained') ? 400 : 503, { error: server })
if (typeof body.text !== 'string' || typeof body.cursor !== 'number') return json(res, 400, { error: 'text and cursor are required' })
const o = config.nextEditOptions ?? { regionBefore: 4, regionAfter: 8, maxTokens: 400, beforeTokens: 1024, afterTokens: 384 }
@@ -266,7 +283,7 @@ async function handleNextEdit(body: NextEditRequest, res: http.ServerResponse, c
const controller = new AbortController()
inflight = controller
clientGone.addEventListener('abort', () => controller.abort())
const result = await llama.complete(ids, { nPredict: o.maxTokens, stop: [NEXT_EDIT_TOKENS.outputEnd, ...STOP_STRINGS] }, controller.signal)
const result = await server.complete(ids, { nPredict: o.maxTokens, stop: [NEXT_EDIT_TOKENS.outputEnd, ...STOP_STRINGS] }, controller.signal)
if (inflight === controller) inflight = null
if (result.stoppedBy === 'aborted') return json(res, 200, { noEdit: true, cancelled: true })
@@ -276,7 +293,7 @@ async function handleNextEdit(body: NextEditRequest, res: http.ServerResponse, c
const noEdit = newText === null || result.stoppedBy === 'limit'
const response = {
requestId: body.requestId, noEdit, startLine, endLine, oldText, newText: noEdit ? null : newText,
recentEdits, errors: errors ?? null, model: state.model,
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) },
}
remember({ kind: 'next-edit', requestId: body.requestId, path: body.path, model: state.model, prompt: builder.decode(ids), raw, noEdit, timings: response.timings })
@@ -303,7 +320,7 @@ if (config.idleShutdownMinutes) {
setInterval(() => {
if (Date.now() - lastActivity > config.idleShutdownMinutes! * 60_000) {
console.log(`idle for ${config.idleShutdownMinutes} min, shutting down`)
void llama.stop().then(() => process.exit(0))
void Promise.all([llama.stop(), neLlama.stop()]).then(() => process.exit(0))
}
}, 30_000).unref()
}
@@ -318,7 +335,7 @@ const server = http.createServer(async (req, res) => {
if (route === 'GET /health') return json(res, 200, { ok: llama.running, loading: loading !== null, model: state.model, error: loadError, instanceTag: config.instanceTag ?? null, pid: process.pid })
if (route === 'POST /v1/shutdown') {
json(res, 200, { ok: true })
void llama.stop().then(() => process.exit(0))
void Promise.all([llama.stop(), neLlama.stop()]).then(() => process.exit(0))
return
}
if (route === 'GET /v1/config') return json(res, 200, describeConfig())
@@ -331,7 +348,10 @@ const server = http.createServer(async (req, res) => {
const modelChanged = model !== state.model
Object.assign(state, { model, mode, enabled: body.enabled ?? state.enabled })
saveState(config, state)
if (modelChanged) void loadModel(model)
if (modelChanged) {
void loadModel(model)
if (neLlama.model && neLlama.model !== config.models[config.models[model].nextEditModel ?? '']?.path) void neLlama.stop()
}
return json(res, 200, describeConfig())
}
if (route === 'POST /v1/fim') return await handleFim(await readBody(req) as FimRequest, res, gone.signal)
@@ -357,8 +377,8 @@ const server = http.createServer(async (req, res) => {
}
})
process.on('SIGINT', () => void llama.stop().then(() => process.exit(0)))
process.on('SIGTERM', () => void llama.stop().then(() => process.exit(0)))
process.on('SIGINT', () => void Promise.all([llama.stop(), neLlama.stop()]).then(() => process.exit(0)))
process.on('SIGTERM', () => void Promise.all([llama.stop(), neLlama.stop()]).then(() => process.exit(0)))
server.listen(config.port, config.host, () => {
console.log(`acd listening on http://${config.host}:${config.port} (model ${state.model}, mode ${state.mode})`)