@orquentra/codebuddy 2.9.0 → 2.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/ARCHITECTURE.md +98 -0
  2. package/CLAUDE_CODEX.md +33 -0
  3. package/README.md +114 -9
  4. package/dist/{adapters-S4HHTWJW.js → adapters-OJRSVFV3.js} +2 -2
  5. package/dist/{chunk-GXOML4UZ.js → chunk-4BSO3HST.js} +126 -18
  6. package/dist/chunk-4BSO3HST.js.map +1 -0
  7. package/dist/{chunk-CAEPHLHC.js → chunk-57642NGU.js} +2 -44
  8. package/dist/chunk-57642NGU.js.map +1 -0
  9. package/dist/{chunk-ESZXCZIN.js → chunk-6YI2D5F5.js} +33 -5
  10. package/dist/chunk-6YI2D5F5.js.map +1 -0
  11. package/dist/{chunk-GKNYMNFM.js → chunk-7IPSDDO5.js} +10 -3
  12. package/dist/chunk-7IPSDDO5.js.map +1 -0
  13. package/dist/chunk-BTTQWSDS.js +372 -0
  14. package/dist/chunk-BTTQWSDS.js.map +1 -0
  15. package/dist/{chunk-2WH2Q4TA.js → chunk-D5E7SGI5.js} +304 -77
  16. package/dist/chunk-D5E7SGI5.js.map +1 -0
  17. package/dist/chunk-EMWGLKPR.js +45 -0
  18. package/dist/chunk-EMWGLKPR.js.map +1 -0
  19. package/dist/chunk-GQKWUHMA.js +20 -0
  20. package/dist/chunk-GQKWUHMA.js.map +1 -0
  21. package/dist/chunk-ITXE27SZ.js +358 -0
  22. package/dist/chunk-ITXE27SZ.js.map +1 -0
  23. package/dist/{chunk-UK6XTS2Y.js → chunk-JJQDVGF7.js} +23 -23
  24. package/dist/chunk-JJQDVGF7.js.map +1 -0
  25. package/dist/{chunk-F4BJ3DXW.js → chunk-KQCP3ZYH.js} +1273 -331
  26. package/dist/chunk-KQCP3ZYH.js.map +1 -0
  27. package/dist/{chunk-QN76UJP7.js → chunk-KYRM3HPP.js} +89 -21
  28. package/dist/chunk-KYRM3HPP.js.map +1 -0
  29. package/dist/{chunk-KNR2P2UY.js → chunk-M75SN4D4.js} +19 -4
  30. package/dist/chunk-M75SN4D4.js.map +1 -0
  31. package/dist/{chunk-J4T6HHI4.js → chunk-RZ266NHE.js} +538 -317
  32. package/dist/chunk-RZ266NHE.js.map +1 -0
  33. package/dist/chunk-U7KDM62P.js +43 -0
  34. package/dist/chunk-U7KDM62P.js.map +1 -0
  35. package/dist/chunk-UATJCD2T.js +368 -0
  36. package/dist/chunk-UATJCD2T.js.map +1 -0
  37. package/dist/{chunk-ENKDFLOO.js → chunk-UOTKHSQ5.js} +25 -8
  38. package/dist/chunk-UOTKHSQ5.js.map +1 -0
  39. package/dist/{chunk-TLWGZVPC.js → chunk-UR3DF74I.js} +1007 -132
  40. package/dist/chunk-UR3DF74I.js.map +1 -0
  41. package/dist/chunk-UTPC66MV.js +128 -0
  42. package/dist/chunk-UTPC66MV.js.map +1 -0
  43. package/dist/{chunk-W7GAXL6C.js → chunk-V7PT6ER5.js} +6 -5
  44. package/dist/chunk-V7PT6ER5.js.map +1 -0
  45. package/dist/chunk-WHKXUXHE.js +156 -0
  46. package/dist/chunk-WHKXUXHE.js.map +1 -0
  47. package/dist/{chunk-SRA4MDVG.js → chunk-YMXFSHXT.js} +978 -950
  48. package/dist/chunk-YMXFSHXT.js.map +1 -0
  49. package/dist/cli/index.js +23 -9
  50. package/dist/cli/index.js.map +1 -1
  51. package/dist/{codebuddy-DO7ZhoWn.d.ts → codebuddy-CHqMptZB.d.ts} +35 -0
  52. package/dist/config-TXQJCCVI.js +15 -0
  53. package/dist/expand-KWYGNCKZ.js +98 -0
  54. package/dist/expand-KWYGNCKZ.js.map +1 -0
  55. package/dist/file-vectors-MKXTSGGH.js +19 -0
  56. package/dist/index-facts-4MTEHKMN.js +38 -0
  57. package/dist/index-pass-TBDUEZCB.js +14 -0
  58. package/dist/index.d.ts +6 -4
  59. package/dist/index.js +25 -23
  60. package/dist/install-E3JWINTN.js +31 -0
  61. package/dist/langgraph/index.d.ts +1 -1
  62. package/dist/lean-MLTAFS5L.js +29 -0
  63. package/dist/local-75DJECVR.js +48 -0
  64. package/dist/local-llm-MECVJTVU.js +19 -0
  65. package/dist/{main-YJKSYFPT.js → main-RLMKQZW4.js} +999 -328
  66. package/dist/main-RLMKQZW4.js.map +1 -0
  67. package/dist/{memory-TGHVTBME.js → memory-PFFVMTCH.js} +9 -6
  68. package/dist/{memory-TGHVTBME.js.map → memory-PFFVMTCH.js.map} +1 -1
  69. package/dist/{operations-OG4ISWIM.js → operations-STR7S5UO.js} +8 -7
  70. package/dist/operations-STR7S5UO.js.map +1 -0
  71. package/dist/{pretool-command-PRFOIHMW.js → pretool-command-B5GY5T6E.js} +2 -2
  72. package/dist/pretool-command-B5GY5T6E.js.map +1 -0
  73. package/dist/prompt-gate-TDTFFQW2.js +8 -0
  74. package/dist/prompt-gate-TDTFFQW2.js.map +1 -0
  75. package/dist/{rerank-IQAEWLQR.js → rerank-XXKWDI5Q.js} +7 -4
  76. package/dist/{rerank-IQAEWLQR.js.map → rerank-XXKWDI5Q.js.map} +1 -1
  77. package/dist/{runtime-GCSEIAVD.js → runtime-SWFWGTQ4.js} +18 -9
  78. package/dist/runtime-SWFWGTQ4.js.map +1 -0
  79. package/dist/summaries-QXWFFDUZ.js +31 -0
  80. package/dist/summaries-QXWFFDUZ.js.map +1 -0
  81. package/dist/{zvec-XDZNZ2OL.js → zvec-F73IBWO5.js} +13 -6
  82. package/dist/zvec-F73IBWO5.js.map +1 -0
  83. package/package.json +13 -3
  84. package/dist/chunk-2WH2Q4TA.js.map +0 -1
  85. package/dist/chunk-6NZEZJYD.js +0 -206
  86. package/dist/chunk-6NZEZJYD.js.map +0 -1
  87. package/dist/chunk-CAEPHLHC.js.map +0 -1
  88. package/dist/chunk-ENKDFLOO.js.map +0 -1
  89. package/dist/chunk-ESZXCZIN.js.map +0 -1
  90. package/dist/chunk-F4BJ3DXW.js.map +0 -1
  91. package/dist/chunk-GKNYMNFM.js.map +0 -1
  92. package/dist/chunk-GXOML4UZ.js.map +0 -1
  93. package/dist/chunk-ITQO632W.js +0 -673
  94. package/dist/chunk-ITQO632W.js.map +0 -1
  95. package/dist/chunk-J4T6HHI4.js.map +0 -1
  96. package/dist/chunk-KNR2P2UY.js.map +0 -1
  97. package/dist/chunk-QN76UJP7.js.map +0 -1
  98. package/dist/chunk-SFMIMLXR.js +0 -63
  99. package/dist/chunk-SFMIMLXR.js.map +0 -1
  100. package/dist/chunk-SRA4MDVG.js.map +0 -1
  101. package/dist/chunk-TLWGZVPC.js.map +0 -1
  102. package/dist/chunk-UK6XTS2Y.js.map +0 -1
  103. package/dist/chunk-W7GAXL6C.js.map +0 -1
  104. package/dist/config-GEZFLXK6.js +0 -12
  105. package/dist/install-BXM56QCD.js +0 -19
  106. package/dist/lean-command-7DFW32MA.js +0 -19
  107. package/dist/main-YJKSYFPT.js.map +0 -1
  108. package/dist/summaries-ML53R7EL.js +0 -21
  109. /package/dist/{adapters-S4HHTWJW.js.map → adapters-OJRSVFV3.js.map} +0 -0
  110. /package/dist/{config-GEZFLXK6.js.map → config-TXQJCCVI.js.map} +0 -0
  111. /package/dist/{install-BXM56QCD.js.map → file-vectors-MKXTSGGH.js.map} +0 -0
  112. /package/dist/{lean-command-7DFW32MA.js.map → index-facts-4MTEHKMN.js.map} +0 -0
  113. /package/dist/{operations-OG4ISWIM.js.map → index-pass-TBDUEZCB.js.map} +0 -0
  114. /package/dist/{pretool-command-PRFOIHMW.js.map → install-E3JWINTN.js.map} +0 -0
  115. /package/dist/{runtime-GCSEIAVD.js.map → lean-MLTAFS5L.js.map} +0 -0
  116. /package/dist/{summaries-ML53R7EL.js.map → local-75DJECVR.js.map} +0 -0
  117. /package/dist/{zvec-XDZNZ2OL.js.map → local-llm-MECVJTVU.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/pretool.ts","../src/hooks/pretool-command.ts"],"sourcesContent":["import { open, stat } from \"node:fs/promises\";\nimport { extname, isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { extractSymbolTable, supportsSymbols } from \"../indexer/symbols.js\";\nimport type { HookInput } from \"./runtime.js\";\n\n/**\n * PreToolUse tool guard (Claude Code): cut tool output that would otherwise\n * stay in context for the rest of the session, without touching permissions.\n *\n * - Read of a large file with no range → denied with an outline (symbols or\n * headings with line ranges) and how to read a range or the whole file.\n * The model sees the deny reason; any Read with offset/limit passes.\n * - Grep in content mode with no head_limit → the same input plus a limit.\n * - Bash that only prints a large file whole (`cat`, `less`, `head -n 5000`) →\n * denied the same way; any other command (pipes, builds, tests) passes.\n *\n * Never emits `permissionDecision: \"allow\"`: Claude Code checks permissions on\n * the (possibly rewritten) input as usual. Verified on Claude Code 2.1.287.\n */\n/**\n * Only files whose full read is clearly expensive: below this, an agent that\n * re-reads the whole file after the outline (observed live) costs a turn for\n * nothing.\n */\nexport const TOOL_GUARD = {\n readLines: 1_000,\n readBytes: 80_000,\n /** Symbols shorter than this are noise in an outline (one-line constants). */\n outlineMinSymbolLines: 3,\n /** Fewer top-level symbols than this: list class methods too. */\n minTopLevelEntries: 10,\n outlineEntries: 40,\n blockLines: 100,\n grepHeadLimit: 100,\n} as const;\n\n/** Read renders these itself (images, PDFs, notebooks); never guard them. */\nconst SPECIAL_READ = new Set([\n \".png\",\n \".jpg\",\n \".jpeg\",\n \".gif\",\n \".webp\",\n \".bmp\",\n \".svg\",\n \".pdf\",\n \".ipynb\",\n]);\nconst MAX_GUARD_BYTES = 2_000_000;\n\nexport type PreToolDecision = {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\";\n permissionDecision?: \"deny\";\n permissionDecisionReason?: string;\n updatedInput?: Record<string, unknown>;\n };\n} | null;\n\nexport async function decidePreTool(\n input: HookInput,\n env: NodeJS.ProcessEnv = process.env,\n): Promise<PreToolDecision> {\n if (env.CODEBUDDY_TOOL_GUARD === \"0\") return null;\n const toolInput = input.tool_input ?? {};\n if (input.tool_name === \"Grep\") return capGrep(toolInput);\n if (input.tool_name === \"Read\") return guardRead(resolve(input.cwd ?? process.cwd()), toolInput);\n if (input.tool_name === \"Bash\") return guardBash(resolve(input.cwd ?? process.cwd()), toolInput);\n return null;\n}\n\nfunction capGrep(toolInput: Record<string, unknown>): PreToolDecision {\n if (toolInput.output_mode !== \"content\" || toolInput.head_limit !== undefined) return null;\n return {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n updatedInput: { ...toolInput, head_limit: TOOL_GUARD.grepHeadLimit },\n },\n };\n}\n\n/** A file inside the repository too large to read whole, with its outline; else null. */\nexport async function largeFile(\n root: string,\n filePath: string,\n): Promise<{ path: string; lines: number; size: number; outline: string[] } | null> {\n const absolute = isAbsolute(filePath) ? resolve(filePath) : resolve(root, filePath);\n const rel = relative(root, absolute);\n if (rel === \"\" || rel === \"..\" || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;\n if (SPECIAL_READ.has(extname(absolute).toLowerCase())) return null;\n let size: number;\n try {\n const info = await stat(absolute);\n if (!info.isFile()) return null;\n size = info.size;\n } catch {\n return null;\n }\n if (size > MAX_GUARD_BYTES) return null; // Read refuses huge files itself.\n const text = await readText(absolute);\n if (text === null) return null;\n const lines = text.split(/\\r?\\n/);\n if (lines.at(-1) === \"\") lines.pop();\n if (lines.length <= TOOL_GUARD.readLines && size <= TOOL_GUARD.readBytes) return null;\n const path = rel.replace(/\\\\/g, \"/\");\n return { path, lines: lines.length, size, outline: outlineOf(path, text, lines) };\n}\n\nasync function guardRead(\n root: string,\n toolInput: Record<string, unknown>,\n): Promise<PreToolDecision> {\n if (toolInput.offset !== undefined || toolInput.limit !== undefined) return null;\n const filePath = toolInput.file_path;\n if (typeof filePath !== \"string\" || !filePath) return null;\n const file = await largeFile(root, filePath);\n if (!file) return null;\n return deny(file, [\n \"Read only the part you need with offset and limit (offset = first line, limit = line count).\",\n \"To find specific text, use Grep with output_mode content and -n instead of reading the file.\",\n `To read the whole file anyway, call Read again with limit: ${file.lines}.`,\n ]);\n}\n\nfunction deny(\n file: { path: string; lines: number; size: number; outline: string[] },\n advice: string[],\n): PreToolDecision {\n return {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: [\n `CodeBuddy read guard: ${file.path} is ${file.lines} lines (~${Math.ceil(file.size / 4)} tokens), too large to read whole.`,\n `Outline: ${file.outline.join(\"; \")}`,\n ...advice,\n ].join(\"\\n\"),\n },\n };\n}\n\n/** Programs that print whole files; `head`/`tail` only when asked for more lines than the guard. */\nconst WHOLE_FILE_PRINTERS = new Set([\"cat\", \"less\", \"more\", \"nl\", \"bat\", \"batcat\"]);\n/**\n * Anything that makes a command more than one plain read: pipes, lists,\n * redirection, substitution, globbing over many files. Such commands are never\n * touched, so builds, tests, pipelines and scripts behave exactly as before.\n */\nconst SHELL_SYNTAX = /[|;&<>`$(){}*?[\\]\\n\\\\]/;\n\n/**\n * Bash that would print a large file whole (`cat src/big.ts`, `head -n 5000 x`).\n * Claude reads files through Bash to get around the Read guard; the output then\n * stays in context for the rest of the session. Only a single plain read\n * command of a file over the Read guard's size is denied, with the same outline\n * and how to read a range; every other command passes untouched.\n */\nasync function guardBash(\n root: string,\n toolInput: Record<string, unknown>,\n): Promise<PreToolDecision> {\n const command = typeof toolInput.command === \"string\" ? toolInput.command.trim() : \"\";\n if (!command || SHELL_SYNTAX.test(command)) return null;\n const tokens = (command.match(/\"[^\"]*\"|'[^']*'|\\S+/g) ?? []).map((token) =>\n token.replace(/^([\"'])(.*)\\1$/, \"$2\"),\n );\n // Leading `VAR=value` assignments do not change what runs.\n while (tokens[0] && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[0])) tokens.shift();\n const program = (tokens.shift() ?? \"\").split(\"/\").at(-1) ?? \"\";\n const files: string[] = [];\n if (WHOLE_FILE_PRINTERS.has(program)) {\n for (const token of tokens) if (!token.startsWith(\"-\")) files.push(token);\n } else if (program === \"head\" || program === \"tail\") {\n let lines = 10;\n for (let index = 0; index < tokens.length; index += 1) {\n const token = tokens[index] ?? \"\";\n const count = /^-n$|^--lines$/.test(token)\n ? tokens[++index]\n : (token.match(/^(?:-n|--lines=)\\+?(\\d+)$/)?.[1] ?? token.match(/^-(\\d+)$/)?.[1]);\n if (count !== undefined && /^\\+?\\d+$/.test(count)) lines = Number(count.replace(\"+\", \"\"));\n else if (token === \"-f\" || token === \"-F\" || token === \"--follow\") return null;\n else if (!token.startsWith(\"-\")) files.push(token);\n }\n if (lines <= TOOL_GUARD.readLines) return null;\n } else {\n return null;\n }\n for (const filePath of files) {\n const file = await largeFile(root, filePath);\n if (file) {\n return deny(file, [\n \"Read only the part you need with the Read tool (offset = first line, limit = line count) or `sed -n 'START,ENDp' FILE`.\",\n \"To find specific text, use Grep with output_mode content and -n instead of printing the file.\",\n `To read the whole file anyway, use Read with limit: ${file.lines}.`,\n ]);\n }\n }\n return null;\n}\n\n/** Whole file as UTF-8 text, or null when it looks binary. */\nasync function readText(path: string): Promise<string | null> {\n const handle = await open(path, \"r\");\n try {\n const content = await handle.readFile();\n if (content.includes(0)) return null;\n return content.toString(\"utf8\");\n } finally {\n await handle.close();\n }\n}\n\nfunction outlineOf(path: string, text: string, lines: string[]): string[] {\n const cap = (entries: string[]) =>\n entries.length > TOOL_GUARD.outlineEntries\n ? [\n ...entries.slice(0, TOOL_GUARD.outlineEntries),\n `… ${entries.length - TOOL_GUARD.outlineEntries} more`,\n ]\n : entries;\n const all = supportsSymbols(path) ? extractSymbolTable(text, path) : [];\n if (all[0]?.kind === \"heading\") {\n return cap(all.map((heading) => `${heading.signature.slice(0, 80)} L${heading.startLine}`));\n }\n if (all.length > 0) {\n const table = all.filter(\n (symbol) => symbol.endLine - symbol.startLine + 1 >= TOOL_GUARD.outlineMinSymbolLines,\n );\n const topLevel = table.filter((symbol) => symbol.kind !== \"method\");\n // One big class says little on its own; then list its methods (`Class.method`).\n const symbols = topLevel.length >= TOOL_GUARD.minTopLevelEntries ? topLevel : table;\n if (symbols.length > 0) {\n // The symbol table is capped, so say where the outline stops covering the file.\n const covered = Math.max(...symbols.map((symbol) => symbol.endLine));\n return [\n ...cap(symbols.map((s) => `${s.name} L${s.startLine}-${s.endLine}`)),\n ...(covered < lines.length ? [`not outlined L${covered + 1}-${lines.length}`] : []),\n ];\n }\n }\n const blocks: string[] = [];\n for (let start = 1; start <= lines.length; start += TOOL_GUARD.blockLines) {\n blocks.push(`L${start}-${Math.min(lines.length, start + TOOL_GUARD.blockLines - 1)}`);\n }\n return cap(blocks);\n}\n","import { recordTrace } from \"../tracing/log.js\";\nimport { decidePreTool } from \"./pretool.js\";\nimport { hookRoot } from \"./root.js\";\nimport { failSoft, readHookInput, writeStdout } from \"./stdio.js\";\n\n/** `codebuddy hooks pretool`: runs on every Read/Grep, so it loads only its own module. */\nexport async function runPreToolHookCommand(): Promise<void> {\n await failSoft(async () => {\n const input = await readHookInput();\n const decision = await decidePreTool(input);\n if (!decision) return;\n await writeStdout(JSON.stringify(decision));\n const reason = decision.hookSpecificOutput.permissionDecisionReason;\n if (decision.hookSpecificOutput.permissionDecision === \"deny\" && reason) {\n // Measured, not modelled: the file the agent did not load whole, minus the\n // outline it got instead. Shown by `codebuddy dashboard`.\n const full = Number(reason.match(/\\(~(\\d+) tokens\\)/)?.[1] ?? 0);\n const outline = Math.ceil(reason.length / 4);\n await recordTrace(hookRoot(input.cwd), {\n kind: \"guard\",\n name: input.tool_name ?? \"tool\",\n ms: 0,\n ok: true,\n tokensOut: outline,\n ...(input.session_id ? { session: input.session_id } : {}),\n detail: {\n file: reason.match(/read guard: (\\S+) is/)?.[1] ?? \"\",\n avoided: Math.max(0, full - outline),\n },\n });\n }\n });\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/llm/budget.ts","../src/llm/client.ts"],"sourcesContent":["import { mkdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { writeAtomic } from \"../core/markdown-store-fs.js\";\n\n/**\n * Free-tier hygiene for the LLM layer, all in `.codebuddy/cache/` (gitignored,\n * rebuildable): a per-day token ledger per provider:model, a cooldown after\n * 429s, and a response cache. Background work stops at 70% of the daily\n * budget so prompt-time calls keep a reserved 30%.\n *\n * ponytail: read-modify-write JSON without a lock; two hook processes can lose\n * one update. Fine for a soft budget; add the staging lock if it ever matters.\n */\nconst BACKGROUND_SHARE = 0.7;\nconst CACHE_LIMIT = 2_000;\n\nconst file = (root: string, name: string) => join(root, \".codebuddy\", \"cache\", name);\n\nasync function readJson<T>(path: string, fallback: T): Promise<T> {\n try {\n return JSON.parse(await readFile(path, \"utf8\")) as T;\n } catch {\n return fallback;\n }\n}\n\nasync function writeJson(path: string, value: unknown): Promise<void> {\n await mkdir(join(path, \"..\"), { recursive: true });\n await writeAtomic(path, `${JSON.stringify(value)}\\n`);\n}\n\ntype Ledger = { day: string; used: Record<string, number> };\nconst utcDay = (now: Date) => now.toISOString().slice(0, 10);\n\nexport async function usageToday(root: string, now = new Date()): Promise<Record<string, number>> {\n const ledger = await readJson<Ledger>(file(root, \"llm-usage.json\"), { day: \"\", used: {} });\n return ledger.day === utcDay(now) ? ledger.used : {};\n}\n\nexport async function recordUsage(\n root: string,\n key: string,\n tokens: number,\n now = new Date(),\n): Promise<void> {\n const used = await usageToday(root, now);\n used[key] = (used[key] ?? 0) + tokens;\n await writeJson(file(root, \"llm-usage.json\"), { day: utcDay(now), used });\n}\n\n/** Whether `estimate` more tokens fit today's share for this purpose. */\nexport async function reserveBudget(\n root: string,\n key: string,\n purpose: \"prompt\" | \"background\",\n estimate: number,\n dailyBudget: number,\n now = new Date(),\n): Promise<boolean> {\n const limit = purpose === \"background\" ? dailyBudget * BACKGROUND_SHARE : dailyBudget;\n return ((await usageToday(root, now))[key] ?? 0) + estimate <= limit;\n}\n\n/** Milliseconds of cooldown left for this provider:model (0 = none). */\nexport async function coolingDown(root: string, key: string, now = new Date()): Promise<number> {\n const until = (await readJson<Record<string, number>>(file(root, \"llm-cooldown.json\"), {}))[key];\n return until ? Math.max(0, until - now.getTime()) : 0;\n}\n\nexport async function startCooldown(\n root: string,\n key: string,\n seconds: number,\n now = new Date(),\n): Promise<void> {\n const path = file(root, \"llm-cooldown.json\");\n const cooldowns = await readJson<Record<string, number>>(path, {});\n cooldowns[key] = now.getTime() + seconds * 1_000;\n await writeJson(path, cooldowns);\n}\n\nexport async function cachedResponse(root: string, key: string): Promise<unknown | undefined> {\n return (await readJson<Record<string, unknown>>(file(root, \"llm-responses.json\"), {}))[key];\n}\n\n/** Insertion-ordered; the oldest entries go once the cache holds CACHE_LIMIT. */\nexport async function storeResponse(root: string, key: string, value: unknown): Promise<void> {\n const path = file(root, \"llm-responses.json\");\n const cache = await readJson<Record<string, unknown>>(path, {});\n delete cache[key];\n cache[key] = value;\n const keys = Object.keys(cache);\n for (const old of keys.slice(0, Math.max(0, keys.length - CACHE_LIMIT))) delete cache[old];\n await writeJson(path, cache);\n}\n","import type { z } from \"zod\";\nimport {\n cachedResponse,\n coolingDown,\n recordUsage,\n reserveBudget,\n startCooldown,\n storeResponse,\n} from \"./budget.js\";\nimport { type EnabledLlmConfig, redactLlmKeys } from \"./config.js\";\n\n/** Seconds to back off after a 429 without a usable retry-after header. */\nconst DEFAULT_COOLDOWN_SECONDS = 60;\n\n/**\n * One OpenAI-compatible chat call that must answer with JSON (Groq and\n * OpenRouter share the API). It never throws: every failure is a reason the\n * caller turns into today's deterministic behaviour plus an explain line.\n */\nexport type LlmFailure =\n | \"disabled\"\n | \"timeout\"\n | \"rate-limited\"\n | \"cooldown\"\n | \"budget\"\n | \"http\"\n | \"invalid\";\n\nexport type LlmResult<T> =\n | { ok: true; value: T; model: string; latencyMs: number; tokens: number; cached: boolean }\n | { ok: false; reason: LlmFailure; detail: string; latencyMs: number };\n\nexport type ChatJsonRequest<T> = {\n system: string;\n user: string;\n schema: z.ZodType<T>;\n maxTokens: number;\n deadlineMs: number;\n /** Prompt-path calls get the reserved budget share; background work stops earlier. */\n purpose: \"prompt\" | \"background\";\n /** Cache identity (inputs + prompt version); omitted = never cached. */\n cacheKey?: string;\n repositoryRoot: string;\n};\n\nexport async function chatJson<T>(\n config: EnabledLlmConfig,\n request: ChatJsonRequest<T>,\n): Promise<LlmResult<T>> {\n const started = Date.now();\n try {\n return await call(config, request, started);\n } catch (error) {\n // Structural guarantee: an unexpected body shape is an invalid answer, not a crash.\n return {\n ok: false,\n reason: \"invalid\",\n detail: redact(config, String((error as Error)?.message ?? error)).slice(0, 300),\n latencyMs: Date.now() - started,\n };\n }\n}\n\nasync function call<T>(\n config: EnabledLlmConfig,\n request: ChatJsonRequest<T>,\n started: number,\n): Promise<LlmResult<T>> {\n const fail = (reason: LlmFailure, detail: string): LlmResult<T> => ({\n ok: false,\n reason,\n detail: redact(config, detail).slice(0, 300),\n latencyMs: Date.now() - started,\n });\n const root = request.repositoryRoot;\n const ledgerKey = `${config.provider}:${config.model}`;\n const cooling = await coolingDown(root, ledgerKey);\n if (cooling > 0) return fail(\"cooldown\", `rate limited; retry in ${Math.ceil(cooling / 1000)} s`);\n const cacheKey = request.cacheKey && `${ledgerKey}:${request.cacheKey}`;\n if (cacheKey) {\n const parsed = request.schema.safeParse(await cachedResponse(root, cacheKey));\n if (parsed.success) {\n return {\n ok: true,\n value: parsed.data,\n model: config.model,\n latencyMs: Date.now() - started,\n tokens: 0,\n cached: true,\n };\n }\n }\n const estimate = estimateTokens(request, \"\") + request.maxTokens;\n if (!(await reserveBudget(root, ledgerKey, request.purpose, estimate, config.dailyTokenBudget))) {\n return fail(\"budget\", `daily ${request.purpose} budget for ${ledgerKey} is spent`);\n }\n let response: Response;\n try {\n response = await fetch(`${config.baseUrl}/chat/completions`, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${config.apiKey}`,\n \"content-type\": \"application/json\",\n ...(config.provider === \"openrouter\" ? { \"x-title\": \"CodeBuddy\" } : {}),\n },\n body: JSON.stringify({\n model: config.model,\n messages: [\n { role: \"system\", content: request.system },\n { role: \"user\", content: request.user },\n ],\n temperature: 0,\n max_tokens: request.maxTokens,\n response_format: { type: \"json_object\" },\n }),\n signal: AbortSignal.timeout(request.deadlineMs),\n });\n } catch (error) {\n const name = (error as Error)?.name;\n if (name === \"TimeoutError\" || name === \"AbortError\") {\n return fail(\"timeout\", `no answer within ${request.deadlineMs} ms`);\n }\n return fail(\"http\", (error as Error)?.message ?? String(error));\n }\n let body: string;\n try {\n body = await response.text();\n } catch (error) {\n const name = (error as Error)?.name;\n return fail(\n name === \"TimeoutError\" || name === \"AbortError\" ? \"timeout\" : \"http\",\n String(error),\n );\n }\n if (response.status === 429) {\n const retryAfter = Number(response.headers.get(\"retry-after\"));\n await startCooldown(\n root,\n ledgerKey,\n Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : DEFAULT_COOLDOWN_SECONDS,\n );\n return fail(\"rate-limited\", body);\n }\n if (!response.ok) return fail(\"http\", `HTTP ${response.status}: ${body}`);\n\n let completion: {\n model?: unknown;\n choices?: Array<{ message?: { content?: unknown } }>;\n usage?: { total_tokens?: unknown };\n } | null;\n try {\n completion = JSON.parse(body);\n } catch {\n return fail(\"invalid\", \"response is not JSON\");\n }\n const raw = completion?.choices?.[0]?.message?.content;\n if (typeof raw !== \"string\") return fail(\"invalid\", \"answer has no text content\");\n const content = raw;\n const reported = completion?.usage?.total_tokens;\n const tokens =\n typeof reported === \"number\" && Number.isFinite(reported)\n ? reported\n : estimateTokens(request, content);\n await recordUsage(root, ledgerKey, tokens);\n const parsed = request.schema.safeParse(parseJsonContent(content));\n if (!parsed.success) return fail(\"invalid\", `output failed validation: ${content.slice(0, 120)}`);\n if (cacheKey) await storeResponse(root, cacheKey, parsed.data);\n return {\n ok: true,\n value: parsed.data,\n model: typeof completion?.model === \"string\" ? completion.model : config.model,\n latencyMs: Date.now() - started,\n tokens,\n cached: false,\n };\n}\n\n/** Free models often wrap JSON in a ```json fence despite json_object mode. */\nfunction parseJsonContent(content: string): unknown {\n const fenced = content.match(/```(?:json)?\\s*([\\s\\S]*?)```/i)?.[1];\n try {\n return JSON.parse((fenced ?? content).trim());\n } catch {\n return undefined;\n }\n}\n\nfunction estimateTokens(request: { system: string; user: string }, content: string): number {\n return Math.ceil((request.system.length + request.user.length + content.length) / 4);\n}\n\nfunction redact(config: EnabledLlmConfig, text: string): string {\n return redactLlmKeys(text).split(config.apiKey).join(\"[REDACTED]\");\n}\n"],"mappings":"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","names":["parsed"]}