@orquentra/codebuddy 2.14.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 (91) hide show
  1. package/ARCHITECTURE.md +98 -0
  2. package/README.md +87 -6
  3. package/dist/{chunk-LW75SKZR.js → chunk-4BSO3HST.js} +126 -18
  4. package/dist/chunk-4BSO3HST.js.map +1 -0
  5. package/dist/{chunk-CAEPHLHC.js → chunk-57642NGU.js} +2 -44
  6. package/dist/chunk-57642NGU.js.map +1 -0
  7. package/dist/{chunk-ESZXCZIN.js → chunk-6YI2D5F5.js} +33 -5
  8. package/dist/chunk-6YI2D5F5.js.map +1 -0
  9. package/dist/{chunk-4DKYKDHI.js → chunk-7IPSDDO5.js} +5 -3
  10. package/dist/{chunk-4DKYKDHI.js.map → chunk-7IPSDDO5.js.map} +1 -1
  11. package/dist/chunk-BTTQWSDS.js +372 -0
  12. package/dist/chunk-BTTQWSDS.js.map +1 -0
  13. package/dist/{chunk-YJPCS2GP.js → chunk-D5E7SGI5.js} +57 -18
  14. package/dist/chunk-D5E7SGI5.js.map +1 -0
  15. package/dist/chunk-EMWGLKPR.js +45 -0
  16. package/dist/chunk-EMWGLKPR.js.map +1 -0
  17. package/dist/chunk-ITXE27SZ.js +358 -0
  18. package/dist/chunk-ITXE27SZ.js.map +1 -0
  19. package/dist/{chunk-LRQNPYCK.js → chunk-JJQDVGF7.js} +13 -7
  20. package/dist/chunk-JJQDVGF7.js.map +1 -0
  21. package/dist/{chunk-XSNW6Y3X.js → chunk-KQCP3ZYH.js} +28 -24
  22. package/dist/chunk-KQCP3ZYH.js.map +1 -0
  23. package/dist/{chunk-YWDH6DQ6.js → chunk-KYRM3HPP.js} +21 -19
  24. package/dist/{chunk-YWDH6DQ6.js.map → chunk-KYRM3HPP.js.map} +1 -1
  25. package/dist/{chunk-KNR2P2UY.js → chunk-M75SN4D4.js} +19 -4
  26. package/dist/chunk-M75SN4D4.js.map +1 -0
  27. package/dist/{chunk-L6BRYZ6G.js → chunk-RZ266NHE.js} +7 -7
  28. package/dist/chunk-U7KDM62P.js +43 -0
  29. package/dist/chunk-U7KDM62P.js.map +1 -0
  30. package/dist/chunk-UATJCD2T.js +368 -0
  31. package/dist/chunk-UATJCD2T.js.map +1 -0
  32. package/dist/{chunk-YNSTIMCF.js → chunk-UR3DF74I.js} +352 -157
  33. package/dist/chunk-UR3DF74I.js.map +1 -0
  34. package/dist/chunk-UTPC66MV.js +128 -0
  35. package/dist/chunk-UTPC66MV.js.map +1 -0
  36. package/dist/chunk-WHKXUXHE.js +156 -0
  37. package/dist/chunk-WHKXUXHE.js.map +1 -0
  38. package/dist/{chunk-7QMHIJBA.js → chunk-YMXFSHXT.js} +969 -968
  39. package/dist/chunk-YMXFSHXT.js.map +1 -0
  40. package/dist/cli/index.js +4 -4
  41. package/dist/config-TXQJCCVI.js +15 -0
  42. package/dist/expand-KWYGNCKZ.js +98 -0
  43. package/dist/expand-KWYGNCKZ.js.map +1 -0
  44. package/dist/file-vectors-MKXTSGGH.js +19 -0
  45. package/dist/index-facts-4MTEHKMN.js +38 -0
  46. package/dist/index-pass-TBDUEZCB.js +14 -0
  47. package/dist/index.js +24 -22
  48. package/dist/{install-EU35ERK5.js → install-E3JWINTN.js} +11 -10
  49. package/dist/{lean-43ADYTBW.js → lean-MLTAFS5L.js} +8 -7
  50. package/dist/local-75DJECVR.js +48 -0
  51. package/dist/local-llm-MECVJTVU.js +19 -0
  52. package/dist/local-llm-MECVJTVU.js.map +1 -0
  53. package/dist/{main-SFC5LV4E.js → main-RLMKQZW4.js} +332 -123
  54. package/dist/main-RLMKQZW4.js.map +1 -0
  55. package/dist/{memory-TGHVTBME.js → memory-PFFVMTCH.js} +9 -6
  56. package/dist/{memory-TGHVTBME.js.map → memory-PFFVMTCH.js.map} +1 -1
  57. package/dist/{operations-6Z6ZDVBP.js → operations-STR7S5UO.js} +8 -7
  58. package/dist/operations-STR7S5UO.js.map +1 -0
  59. package/dist/{rerank-IQAEWLQR.js → rerank-XXKWDI5Q.js} +7 -4
  60. package/dist/{rerank-IQAEWLQR.js.map → rerank-XXKWDI5Q.js.map} +1 -1
  61. package/dist/{runtime-W5AW32SD.js → runtime-SWFWGTQ4.js} +9 -8
  62. package/dist/runtime-SWFWGTQ4.js.map +1 -0
  63. package/dist/summaries-QXWFFDUZ.js +31 -0
  64. package/dist/summaries-QXWFFDUZ.js.map +1 -0
  65. package/dist/{zvec-24HLAFGN.js → zvec-F73IBWO5.js} +13 -5
  66. package/dist/zvec-F73IBWO5.js.map +1 -0
  67. package/package.json +3 -2
  68. package/dist/chunk-7QMHIJBA.js.map +0 -1
  69. package/dist/chunk-CAEPHLHC.js.map +0 -1
  70. package/dist/chunk-ESC4Y3L7.js +0 -204
  71. package/dist/chunk-ESC4Y3L7.js.map +0 -1
  72. package/dist/chunk-ESZXCZIN.js.map +0 -1
  73. package/dist/chunk-KNR2P2UY.js.map +0 -1
  74. package/dist/chunk-LRQNPYCK.js.map +0 -1
  75. package/dist/chunk-LW75SKZR.js.map +0 -1
  76. package/dist/chunk-SFMIMLXR.js +0 -63
  77. package/dist/chunk-SFMIMLXR.js.map +0 -1
  78. package/dist/chunk-XSNW6Y3X.js.map +0 -1
  79. package/dist/chunk-YJPCS2GP.js.map +0 -1
  80. package/dist/chunk-YNSTIMCF.js.map +0 -1
  81. package/dist/config-GEZFLXK6.js +0 -12
  82. package/dist/main-SFC5LV4E.js.map +0 -1
  83. package/dist/summaries-32PHOB6L.js +0 -20
  84. /package/dist/{chunk-L6BRYZ6G.js.map → chunk-RZ266NHE.js.map} +0 -0
  85. /package/dist/{config-GEZFLXK6.js.map → config-TXQJCCVI.js.map} +0 -0
  86. /package/dist/{install-EU35ERK5.js.map → file-vectors-MKXTSGGH.js.map} +0 -0
  87. /package/dist/{lean-43ADYTBW.js.map → index-facts-4MTEHKMN.js.map} +0 -0
  88. /package/dist/{operations-6Z6ZDVBP.js.map → index-pass-TBDUEZCB.js.map} +0 -0
  89. /package/dist/{runtime-W5AW32SD.js.map → install-E3JWINTN.js.map} +0 -0
  90. /package/dist/{summaries-32PHOB6L.js.map → lean-MLTAFS5L.js.map} +0 -0
  91. /package/dist/{zvec-24HLAFGN.js.map → local-75DJECVR.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/local-llm.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { confirm, isCancel, spinner } from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { readConfigFile, setConfigBlock } from \"../../core/config-file.js\";\nimport {\n checkLocalModelResources,\n downloadLocalModel,\n formatBytes,\n freeMemory,\n installedLocalModel,\n LLAMA_RUNTIME,\n LOCAL_MODELS,\n type LocalModelSize,\n localModelPath,\n localRuntimeAvailable,\n removeLocalModels,\n} from \"../../llm/local.js\";\n\nconst ok = (text: string) => `${pc.green(\"✓\")} ${text}`;\nconst warn = (text: string) => `${pc.yellow(\"!\")} ${text}`;\nconst row = (label: string, value: string) => ` ${label.padEnd(10)} ${value}`;\n\nexport const LOCAL_LLM_SIZES = Object.keys(LOCAL_MODELS) as LocalModelSize[];\n\n/**\n * `codebuddy models add local-llm`: the explicit opt-in for the local small\n * model. Shows the size, checks disk and RAM, installs the node-llama-cpp\n * runtime when missing (after confirmation), downloads the pinned GGUF with a\n * checksum check, and records the size in `.codebuddy/config.json`.\n */\nexport async function addLocalLlm(\n options: { size?: LocalModelSize; yes?: boolean; interactive?: boolean } = {},\n): Promise<boolean> {\n const spec = LOCAL_MODELS[options.size ?? \"default\"];\n const interactive = options.interactive ?? process.stdin.isTTY === true;\n console.log(\n `${pc.bold(\"Local LLM\")} ${spec.id} ${pc.dim(`(${formatBytes(spec.bytes)} download, ~${formatBytes(spec.ramBytes)} RAM while carding)`)}`,\n );\n const resources = await checkLocalModelResources(spec);\n if (!resources.ok) {\n console.log(warn(resources.reason));\n return false;\n }\n if (freeMemory() < spec.ramBytes) {\n console.log(warn(`only ${formatBytes(freeMemory())} RAM free right now; carding may be slow`));\n }\n if (!(await localRuntimeAvailable())) {\n const install = options.yes\n ? true\n : interactive\n ? await confirm({\n message: `Install the local runtime (${LLAMA_RUNTIME}, ~20–35 MB prebuilt) with npm -g?`,\n initialValue: true,\n })\n : false;\n if (isCancel(install) || !install) {\n console.log(warn(`Runtime missing. Install it with: npm install -g ${LLAMA_RUNTIME}`));\n return false;\n }\n const code = await new Promise<number>((resolve) => {\n const child = spawn(\"npm\", [\"install\", \"-g\", LLAMA_RUNTIME], { stdio: \"inherit\" });\n child.on(\"error\", () => resolve(1));\n child.on(\"close\", (exit) => resolve(exit ?? 1));\n });\n if (code !== 0 || !(await localRuntimeAvailable())) {\n console.log(warn(`Runtime install failed. Run: npm install -g ${LLAMA_RUNTIME}`));\n return false;\n }\n }\n if ((await installedLocalModel(spec.size))?.id !== spec.id) {\n const progress = spinner();\n progress.start(`Downloading ${spec.file}`);\n let shown = 0;\n try {\n await downloadLocalModel(spec, (received, total) => {\n const percent = Math.floor((received / total) * 100);\n if (percent !== shown) {\n shown = percent;\n progress.message(`Downloading ${spec.file} ${percent}%`);\n }\n });\n progress.stop(ok(`${spec.id} verified ${pc.dim(localModelPath(spec))}`));\n } catch (error) {\n progress.stop(warn((error as Error).message));\n return false;\n }\n } else {\n console.log(ok(`${spec.id} already installed`));\n }\n const llm = (await readConfigFile()).llm;\n // A BYOK block stays as it is (the local model is then unused until it is switched).\n if (llm === undefined || llm.provider === \"local\" || llm.enabled === false) {\n await setConfigBlock(\"llm\", { ...llm, enabled: true, provider: \"local\", size: spec.size });\n } else {\n console.log(\n pc.dim(\n ` .codebuddy/config.json uses ${llm.provider}; set llm.provider to \"local\" to use it.`,\n ),\n );\n }\n console.log(\n pc.dim(\n \" Cards are written in the background by `codebuddy serve` or `codebuddy index --cards`.\",\n ),\n );\n return true;\n}\n\nexport async function removeLocalLlm(): Promise<void> {\n const removed = await removeLocalModels();\n const llm = (await readConfigFile()).llm;\n if (llm?.provider === \"local\") await setConfigBlock(\"llm\", { ...llm, enabled: false });\n console.log(\n removed.length > 0\n ? ok(`removed ${removed.join(\", \")}; CodeBuddy falls back to 2.14 behaviour`)\n : pc.dim(\"no local LLM installed\"),\n );\n}\n\nexport async function printLocalLlmStatus(\n cards: { carded: number; eligible: number } | null,\n): Promise<void> {\n const llm = (await readConfigFile()).llm;\n const installed = await installedLocalModel(llm?.size);\n console.log(pc.bold(\"Local LLM\"));\n if (!installed) {\n console.log(row(\"model\", pc.dim(\"not installed (optional: codebuddy models add local-llm)\")));\n return;\n }\n const runtime = await localRuntimeAvailable();\n console.log(row(\"model\", ok(`${installed.id} ${pc.dim(formatBytes(installed.bytes))}`)));\n console.log(\n row(\"runtime\", runtime ? ok(LLAMA_RUNTIME) : warn(`missing: npm install -g ${LLAMA_RUNTIME}`)),\n );\n if (llm?.enabled === false)\n console.log(row(\"status\", warn(\"disabled in .codebuddy/config.json\")));\n if (cards) console.log(row(\"cards\", `${cards.carded} / ${cards.eligible} source files`));\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,156 @@
1
+ import {
2
+ readConfigFile
3
+ } from "./chunk-M75SN4D4.js";
4
+ import {
5
+ installedLocalModel
6
+ } from "./chunk-BTTQWSDS.js";
7
+
8
+ // src/core/global-config.ts
9
+ import { chmod, mkdir, readFile, writeFile } from "fs/promises";
10
+ import { homedir } from "os";
11
+ import { dirname, join } from "path";
12
+ import { z } from "zod";
13
+ var GLOBAL_DIR = ".codebuddy";
14
+ var GLOBAL_FILE = "global.json";
15
+ var globalConfigSchema = z.object({
16
+ hfToken: z.string().min(1).optional(),
17
+ /** Cloud LLM keys asked for by `codebuddy use` on machines too small for the local model. */
18
+ groqApiKey: z.string().min(1).optional(),
19
+ openrouterApiKey: z.string().min(1).optional(),
20
+ databaseUrl: z.string().min(1).optional(),
21
+ // Where the codebuddy install lives on this machine. Cached so future
22
+ // `codebuddy use` calls in unrelated projects can find the CLI entry
23
+ // without re-resolving via process.argv every time.
24
+ installPath: z.string().optional()
25
+ });
26
+ function globalConfigPath(home = homedir()) {
27
+ return join(home, GLOBAL_DIR, GLOBAL_FILE);
28
+ }
29
+ async function readGlobalConfig(home = homedir()) {
30
+ const path = globalConfigPath(home);
31
+ try {
32
+ const raw = await readFile(path, "utf8");
33
+ return globalConfigSchema.parse(JSON.parse(raw));
34
+ } catch (error) {
35
+ if (error.code === "ENOENT") return {};
36
+ throw error;
37
+ }
38
+ }
39
+ async function writeGlobalConfig(next, home = homedir()) {
40
+ const path = globalConfigPath(home);
41
+ await mkdir(dirname(path), { recursive: true, mode: 448 });
42
+ await writeFile(path, `${JSON.stringify(next, null, 2)}
43
+ `, { mode: 384 });
44
+ await chmod(path, 384);
45
+ return { path };
46
+ }
47
+ async function mergeGlobalConfig(patch, home = homedir()) {
48
+ const current = await readGlobalConfig(home);
49
+ const merged = { ...current, ...patch };
50
+ const { path } = await writeGlobalConfig(merged, home);
51
+ return { path, merged };
52
+ }
53
+
54
+ // src/llm/config.ts
55
+ var BASE_URL = {
56
+ groq: "https://api.groq.com/openai/v1",
57
+ openrouter: "https://openrouter.ai/api/v1"
58
+ };
59
+ var KEY_ENV = {
60
+ groq: "GROQ_API_KEY",
61
+ openrouter: "OPENROUTER_API_KEY"
62
+ };
63
+ var DEFAULT_MODEL = {
64
+ // Qwen everywhere: the cloud path for machines too small for the local
65
+ // Qwen3. Groq shut llama-3.1-8b-instant down on 2026-08-16 and
66
+ // qwen3.6-27b on 2026-09-14; qwen3.8-27b is its replacement (reasoning off
67
+ // by default). OpenRouter lists no free Qwen model (2026-11), so it is paid there.
68
+ groq: "qwen/qwen3.8-27b",
69
+ openrouter: "qwen/qwen3.8-27b"
70
+ };
71
+ var DEFAULTS = { timeoutMs: 700, dailyTokenBudget: 4e5 };
72
+ async function resolveLlmConfig(repositoryRoot, env = process.env) {
73
+ if (env.CODEBUDDY_LLM === "0") return { enabled: false, reason: "CODEBUDDY_LLM=0" };
74
+ const configured = (await readConfigFile(repositoryRoot)).llm;
75
+ if (configured === void 0 || configured.provider === "local") {
76
+ if (configured?.enabled === false) return { enabled: false, reason: "llm not enabled" };
77
+ const local = await installedLocalModel(configured?.size);
78
+ if (local) return localConfig(local, configured);
79
+ if (configured?.provider === "local") {
80
+ return {
81
+ enabled: false,
82
+ reason: "local model not installed (codebuddy models add local-llm)"
83
+ };
84
+ }
85
+ }
86
+ const block = configured ?? (env.CODEBUDDY_LLM === "1" ? { enabled: true } : void 0);
87
+ if (!block?.enabled) return { enabled: false, reason: "llm not enabled" };
88
+ const provider = block.provider === "local" || block.provider === void 0 ? "groq" : block.provider;
89
+ const apiKey = env.CODEBUDDY_LLM_API_KEY || env[KEY_ENV[provider]] || await savedKey(provider, env);
90
+ if (!apiKey) return { enabled: false, reason: `no API key in ${KEY_ENV[provider]}` };
91
+ const fallbackKey = block.fallback && (env[KEY_ENV[block.fallback.provider]] || await savedKey(block.fallback.provider, env));
92
+ return {
93
+ enabled: true,
94
+ provider,
95
+ model: block.model ?? DEFAULT_MODEL[provider],
96
+ // Any OpenAI-compatible endpoint (a proxy, a test stub).
97
+ baseUrl: env.CODEBUDDY_LLM_BASE_URL?.replace(/\/+$/, "") || BASE_URL[provider],
98
+ apiKey,
99
+ timeoutMs: block.timeoutMs ?? DEFAULTS.timeoutMs,
100
+ dailyTokenBudget: block.dailyTokenBudget ?? DEFAULTS.dailyTokenBudget,
101
+ features: {
102
+ summaries: block.features?.summaries ?? true,
103
+ rerank: block.features?.rerank ?? true,
104
+ memory: block.features?.memory ?? false
105
+ },
106
+ fallback: block.fallback && fallbackKey ? {
107
+ provider: block.fallback.provider,
108
+ model: block.fallback.model,
109
+ baseUrl: BASE_URL[block.fallback.provider],
110
+ apiKey: fallbackKey
111
+ } : null
112
+ };
113
+ }
114
+ async function savedKey(provider, env) {
115
+ if (env.CODEBUDDY_GLOBAL_CONFIG === "0") return void 0;
116
+ const global = await readGlobalConfig().catch(
117
+ () => ({})
118
+ );
119
+ return provider === "groq" ? global.groqApiKey : global.openrouterApiKey;
120
+ }
121
+ function localConfig(spec, block) {
122
+ return {
123
+ enabled: true,
124
+ provider: "local",
125
+ model: spec.id,
126
+ baseUrl: "",
127
+ apiKey: "",
128
+ timeoutMs: block?.timeoutMs ?? DEFAULTS.timeoutMs,
129
+ dailyTokenBudget: Number.POSITIVE_INFINITY,
130
+ features: {
131
+ summaries: block?.features?.summaries ?? true,
132
+ rerank: block?.features?.rerank ?? false,
133
+ memory: block?.features?.memory ?? false,
134
+ facts: block?.features?.facts ?? true,
135
+ expansion: block?.features?.expansion ?? true
136
+ },
137
+ fallback: null,
138
+ local: spec
139
+ };
140
+ }
141
+ function redactLlmKeys(text, env = process.env) {
142
+ let redacted = text;
143
+ for (const name of ["CODEBUDDY_LLM_API_KEY", ...Object.values(KEY_ENV)]) {
144
+ const key = env[name];
145
+ if (key) redacted = redacted.split(key).join("[REDACTED]");
146
+ }
147
+ return redacted;
148
+ }
149
+
150
+ export {
151
+ readGlobalConfig,
152
+ mergeGlobalConfig,
153
+ resolveLlmConfig,
154
+ redactLlmKeys
155
+ };
156
+ //# sourceMappingURL=chunk-WHKXUXHE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/global-config.ts","../src/llm/config.ts"],"sourcesContent":["import { chmod, mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { z } from \"zod\";\n\n/**\n * User-level configuration shared across all projects on this machine.\n * Stored at ~/.codebuddy/global.json with 0600 permissions so the HF token\n * is never world-readable. Per-project namespace is NOT stored here — that\n * is implicit in the directory the user runs `codebuddy use` from.\n */\n\nexport const GLOBAL_DIR = \".codebuddy\";\nexport const GLOBAL_FILE = \"global.json\";\n\nconst globalConfigSchema = z.object({\n hfToken: z.string().min(1).optional(),\n /** Cloud LLM keys asked for by `codebuddy use` on machines too small for the local model. */\n groqApiKey: z.string().min(1).optional(),\n openrouterApiKey: z.string().min(1).optional(),\n databaseUrl: z.string().min(1).optional(),\n // Where the codebuddy install lives on this machine. Cached so future\n // `codebuddy use` calls in unrelated projects can find the CLI entry\n // without re-resolving via process.argv every time.\n installPath: z.string().optional(),\n});\n\nexport type GlobalConfig = z.infer<typeof globalConfigSchema>;\n\nexport function globalConfigPath(home = homedir()): string {\n return join(home, GLOBAL_DIR, GLOBAL_FILE);\n}\n\nexport async function readGlobalConfig(home = homedir()): Promise<GlobalConfig> {\n const path = globalConfigPath(home);\n try {\n const raw = await readFile(path, \"utf8\");\n return globalConfigSchema.parse(JSON.parse(raw));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw error;\n }\n}\n\nexport async function writeGlobalConfig(\n next: GlobalConfig,\n home = homedir(),\n): Promise<{ path: string }> {\n const path = globalConfigPath(home);\n await mkdir(dirname(path), { recursive: true, mode: 0o700 });\n await writeFile(path, `${JSON.stringify(next, null, 2)}\\n`, { mode: 0o600 });\n await chmod(path, 0o600);\n return { path };\n}\n\nexport async function mergeGlobalConfig(\n patch: GlobalConfig,\n home = homedir(),\n): Promise<{ path: string; merged: GlobalConfig }> {\n const current = await readGlobalConfig(home);\n const merged: GlobalConfig = { ...current, ...patch };\n const { path } = await writeGlobalConfig(merged, home);\n return { path, merged };\n}\n","import { readConfigFile } from \"../core/config-file.js\";\nimport { readGlobalConfig } from \"../core/global-config.js\";\nimport { installedLocalModel, type LocalModelSize, type LocalModelSpec } from \"./local.js\";\n\n/**\n * Small-model layer: bring-your-own-key (Groq/OpenRouter) or local\n * (`provider: \"local\"`, a GGUF model from `codebuddy models add local-llm`).\n * BYOK is off unless `.codebuddy/config.json` has `llm.enabled` and the\n * environment holds a key; keys are read from the environment only, never\n * stored in config, and redacted from any error text. The local backend is on\n * whenever its model is installed and no `llm` block says otherwise.\n */\nexport type LlmProvider = \"groq\" | \"openrouter\" | \"local\";\nexport type LlmFeatures = {\n summaries: boolean;\n rerank: boolean;\n memory: boolean;\n /** Project facts extracted at index time (`llm/index-facts.ts`). */\n facts?: boolean;\n /** Prompt-time code-term expansion for weak tasks (local backend only). */\n expansion?: boolean;\n};\ntype Endpoint = { provider: LlmProvider; model: string; baseUrl: string; apiKey: string };\nexport type EnabledLlmConfig = Endpoint & {\n enabled: true;\n timeoutMs: number;\n dailyTokenBudget: number;\n features: LlmFeatures;\n /** Background work only; never on the prompt path. */\n fallback: Endpoint | null;\n /** Set for `provider: \"local\"`: the installed GGUF model. */\n local?: LocalModelSpec;\n};\nexport type LlmConfig = EnabledLlmConfig | { enabled: false; reason: string };\n\ntype HostedProvider = Exclude<LlmProvider, \"local\">;\nconst BASE_URL: Record<HostedProvider, string> = {\n groq: \"https://api.groq.com/openai/v1\",\n openrouter: \"https://openrouter.ai/api/v1\",\n};\nconst KEY_ENV: Record<HostedProvider, string> = {\n groq: \"GROQ_API_KEY\",\n openrouter: \"OPENROUTER_API_KEY\",\n};\nconst DEFAULT_MODEL: Record<HostedProvider, string> = {\n // Qwen everywhere: the cloud path for machines too small for the local\n // Qwen3. Groq shut llama-3.1-8b-instant down on 2026-08-16 and\n // qwen3.6-27b on 2026-09-14; qwen3.8-27b is its replacement (reasoning off\n // by default). OpenRouter lists no free Qwen model (2026-11), so it is paid there.\n groq: \"qwen/qwen3.8-27b\",\n openrouter: \"qwen/qwen3.8-27b\",\n};\nconst DEFAULTS = { timeoutMs: 700, dailyTokenBudget: 400_000 } as const;\n\nexport async function resolveLlmConfig(\n repositoryRoot: string,\n env: NodeJS.ProcessEnv = process.env,\n): Promise<LlmConfig> {\n if (env.CODEBUDDY_LLM === \"0\") return { enabled: false, reason: \"CODEBUDDY_LLM=0\" };\n // CODEBUDDY_LLM=1 turns it on with defaults where no config says otherwise\n // (eval copies, one-off runs); an explicit `enabled: false` still wins.\n const configured = (await readConfigFile(repositoryRoot)).llm;\n if (configured === undefined || configured.provider === \"local\") {\n if (configured?.enabled === false) return { enabled: false, reason: \"llm not enabled\" };\n const local = await installedLocalModel(configured?.size);\n if (local) return localConfig(local, configured);\n if (configured?.provider === \"local\") {\n return {\n enabled: false,\n reason: \"local model not installed (codebuddy models add local-llm)\",\n };\n }\n }\n const block = configured ?? (env.CODEBUDDY_LLM === \"1\" ? { enabled: true } : undefined);\n if (!block?.enabled) return { enabled: false, reason: \"llm not enabled\" };\n const provider: HostedProvider =\n block.provider === \"local\" || block.provider === undefined ? \"groq\" : block.provider;\n const apiKey =\n env.CODEBUDDY_LLM_API_KEY || env[KEY_ENV[provider]] || (await savedKey(provider, env));\n if (!apiKey) return { enabled: false, reason: `no API key in ${KEY_ENV[provider]}` };\n const fallbackKey =\n block.fallback &&\n (env[KEY_ENV[block.fallback.provider]] || (await savedKey(block.fallback.provider, env)));\n return {\n enabled: true,\n provider,\n model: block.model ?? DEFAULT_MODEL[provider],\n // Any OpenAI-compatible endpoint (a proxy, a test stub).\n baseUrl: env.CODEBUDDY_LLM_BASE_URL?.replace(/\\/+$/, \"\") || BASE_URL[provider],\n apiKey,\n timeoutMs: block.timeoutMs ?? DEFAULTS.timeoutMs,\n dailyTokenBudget: block.dailyTokenBudget ?? DEFAULTS.dailyTokenBudget,\n features: {\n summaries: block.features?.summaries ?? true,\n rerank: block.features?.rerank ?? true,\n memory: block.features?.memory ?? false,\n },\n fallback:\n block.fallback && fallbackKey\n ? {\n provider: block.fallback.provider,\n model: block.fallback.model,\n baseUrl: BASE_URL[block.fallback.provider],\n apiKey: fallbackKey,\n }\n : null,\n };\n}\n\n/**\n * A key `codebuddy use` asked for and saved in ~/.codebuddy/global.json (0600),\n * the same place as the HF token; the environment always wins. Off with\n * `CODEBUDDY_GLOBAL_CONFIG=0` (tests), so a developer's saved key never leaks in.\n */\nasync function savedKey(\n provider: HostedProvider,\n env: NodeJS.ProcessEnv,\n): Promise<string | undefined> {\n if (env.CODEBUDDY_GLOBAL_CONFIG === \"0\") return undefined;\n const global = await readGlobalConfig().catch(\n () => ({}) as Awaited<ReturnType<typeof readGlobalConfig>>,\n );\n return provider === \"groq\" ? global.groqApiKey : global.openrouterApiKey;\n}\n\ntype LlmBlock = NonNullable<Awaited<ReturnType<typeof readConfigFile>>[\"llm\"]>;\n\n/**\n * The local backend: no key, no daily budget (no cost), no hosted fallback.\n * Cards, facts and expansion default on; rerank stays off until the harness\n * shows it beats dense order (the standing rule for rerankers).\n */\nfunction localConfig(spec: LocalModelSpec, block: LlmBlock | undefined): EnabledLlmConfig {\n return {\n enabled: true,\n provider: \"local\",\n model: spec.id,\n baseUrl: \"\",\n apiKey: \"\",\n timeoutMs: block?.timeoutMs ?? DEFAULTS.timeoutMs,\n dailyTokenBudget: Number.POSITIVE_INFINITY,\n features: {\n summaries: block?.features?.summaries ?? true,\n rerank: block?.features?.rerank ?? false,\n memory: block?.features?.memory ?? false,\n facts: block?.features?.facts ?? true,\n expansion: block?.features?.expansion ?? true,\n },\n fallback: null,\n local: spec,\n };\n}\n\nexport type { LocalModelSize };\n\n/** Replace every LLM key present in the environment with `[REDACTED]`. */\nexport function redactLlmKeys(text: string, env: NodeJS.ProcessEnv = process.env): string {\n let redacted = text;\n // (A saved key is redacted by the client, which always strips `config.apiKey`.)\n for (const name of [\"CODEBUDDY_LLM_API_KEY\", ...Object.values(KEY_ENV)]) {\n const key = env[name];\n if (key) redacted = redacted.split(key).join(\"[REDACTED]\");\n }\n return redacted;\n}\n"],"mappings":"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","names":[]}