Parses Qwen-format FIM prompts (prefix- or suffix-first, optional repo/file headers) back into prefix/suffix and rebuilds them in the trained layout; mode from the model string (ac:symbols / ac:simple); files located via workspaceRoots by path or content. extensions/zed/README.md documents the Zed settings; untested against real Zed traffic yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J39imRDT8L7xYbWzsVWd8A
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
/**
|
||||
* Parses a FIM prompt built by another client (e.g. Zed's `open_ai_compatible_api` edit-prediction provider with
|
||||
* prompt_format "qwen") back into prefix / suffix / file path, so the daemon can rebuild the prompt in the layout
|
||||
* our models were trained on (suffix-first, repo header, symbols, healing).
|
||||
*
|
||||
* Accepts, in order of preference:
|
||||
* [<|repo_name|>repo\n] [<|file_sep|>path\n …] <|fim_prefix|>{prefix}<|fim_suffix|>{suffix}<|fim_middle|>
|
||||
* <|fim_prefix|><|fim_suffix|>{suffix}<|fim_middle|>{prefix} (suffix-first)
|
||||
* Returns null for anything else (e.g. a plain completion prompt): the caller then treats it as prefix-only.
|
||||
*/
|
||||
export function parseFimPrompt(prompt: string): { prefix: string; suffix: string; path?: string; repo?: string } | null {
|
||||
const repo = /<\|repo_name\|>([^\n<]*)\n/.exec(prompt)?.[1]?.trim() || undefined
|
||||
const seps = [...prompt.matchAll(/<\|file_sep\|>([^\n<]*)\n/g)]
|
||||
const filePath = seps.length ? seps.at(-1)![1].trim() || undefined : undefined
|
||||
const psm = /<\|fim_prefix\|>([\s\S]*?)<\|fim_suffix\|>([\s\S]*?)<\|fim_middle\|>\s*$/.exec(prompt)
|
||||
if (psm && !psm[1].includes('<|fim_suffix|>')) {
|
||||
if (psm[1] === '') {
|
||||
// Possibly suffix-first: "<pre><suf>suffix<mid>prefix"
|
||||
const spm = /<\|fim_prefix\|><\|fim_suffix\|>([\s\S]*?)<\|fim_middle\|>([\s\S]*)$/.exec(prompt)
|
||||
if (spm) return { prefix: spm[2], suffix: spm[1], path: filePath, repo }
|
||||
}
|
||||
return { prefix: psm[1], suffix: psm[2], path: filePath, repo }
|
||||
}
|
||||
const spm = /<\|fim_prefix\|><\|fim_suffix\|>([\s\S]*?)<\|fim_middle\|>([\s\S]*)$/.exec(prompt)
|
||||
if (spm) return { prefix: spm[2], suffix: spm[1], path: filePath, repo }
|
||||
return null
|
||||
}
|
||||
|
||||
/** "ac:symbols" / "ac:simple" / "ac:raw" select the context mode; anything else keeps the current toggle. */
|
||||
export function modeFromModel(model: unknown): 'symbols' | 'simple' | 'raw' | undefined {
|
||||
const m = /^ac:(symbols|simple|raw)$/.exec(String(model ?? ''))
|
||||
return m ? (m[1] as 'symbols' | 'simple' | 'raw') : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the workspace for a request: the root containing `relPath` (if the client sent a path), else the root
|
||||
* containing a file whose content starts with the prompt's prefix (Zed may not send a path).
|
||||
*/
|
||||
export function locateFile(roots: string[], relPath: string | undefined, prefix: string): { root: string; path: string } | null {
|
||||
if (relPath) {
|
||||
for (const root of roots) {
|
||||
if (path.isAbsolute(relPath) && relPath.startsWith(root + path.sep)) return { root, path: path.relative(root, relPath).split(path.sep).join('/') }
|
||||
if (fs.existsSync(path.join(root, relPath))) return { root, path: relPath }
|
||||
}
|
||||
}
|
||||
const probe = prefix.slice(0, 400)
|
||||
if (probe.length < 40) return null
|
||||
for (const root of roots) {
|
||||
const stack = [root]
|
||||
let visited = 0
|
||||
while (stack.length && visited < 5000) {
|
||||
const dir = stack.pop()!
|
||||
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
if (e.name.startsWith('.') || e.name === 'node_modules' || e.name === 'dist') continue
|
||||
const p = path.join(dir, e.name)
|
||||
if (e.isDirectory()) stack.push(p)
|
||||
else if (/\.(ts|tsx|js|jsx|vue|mts|cts)$/.test(e.name) && ++visited) {
|
||||
try {
|
||||
if (fs.readFileSync(p, 'utf8').startsWith(probe)) return { root, path: path.relative(root, p).split(path.sep).join('/') }
|
||||
} catch { /* unreadable */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user