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: "
suffix 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 }