@code-yeongyu/senpi 2026.5.16 → 2026.5.18

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 (94) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/dist/cli/config-selector.d.ts.map +1 -1
  3. package/dist/cli/config-selector.js +1 -1
  4. package/dist/cli/config-selector.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +5 -1
  7. package/dist/cli.js.map +1 -1
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +12 -3
  10. package/dist/config.js.map +1 -1
  11. package/dist/core/agent-session.d.ts +2 -0
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +47 -6
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/compaction/compaction.d.ts +5 -3
  16. package/dist/core/compaction/compaction.d.ts.map +1 -1
  17. package/dist/core/compaction/compaction.js +22 -14
  18. package/dist/core/compaction/compaction.js.map +1 -1
  19. package/dist/core/extensions/builtin/gpt-apply-patch/preview-format.d.ts.map +1 -1
  20. package/dist/core/extensions/builtin/gpt-apply-patch/preview-format.js +5 -128
  21. package/dist/core/extensions/builtin/gpt-apply-patch/preview-format.js.map +1 -1
  22. package/dist/core/model-registry.d.ts +1 -0
  23. package/dist/core/model-registry.d.ts.map +1 -1
  24. package/dist/core/model-registry.js +64 -9
  25. package/dist/core/model-registry.js.map +1 -1
  26. package/dist/core/package-manager.d.ts +5 -0
  27. package/dist/core/package-manager.d.ts.map +1 -1
  28. package/dist/core/package-manager.js +72 -31
  29. package/dist/core/package-manager.js.map +1 -1
  30. package/dist/core/prompt-templates.d.ts.map +1 -1
  31. package/dist/core/prompt-templates.js +6 -4
  32. package/dist/core/prompt-templates.js.map +1 -1
  33. package/dist/core/session-manager.d.ts.map +1 -1
  34. package/dist/core/session-manager.js +38 -8
  35. package/dist/core/session-manager.js.map +1 -1
  36. package/dist/core/skills.d.ts.map +1 -1
  37. package/dist/core/skills.js +2 -5
  38. package/dist/core/skills.js.map +1 -1
  39. package/dist/core/system-prompt.d.ts.map +1 -1
  40. package/dist/core/system-prompt.js +3 -2
  41. package/dist/core/system-prompt.js.map +1 -1
  42. package/dist/core/tools/diff-render.d.ts +13 -0
  43. package/dist/core/tools/diff-render.d.ts.map +1 -0
  44. package/dist/core/tools/diff-render.js +130 -0
  45. package/dist/core/tools/diff-render.js.map +1 -0
  46. package/dist/core/tools/edit.d.ts.map +1 -1
  47. package/dist/core/tools/edit.js +8 -3
  48. package/dist/core/tools/edit.js.map +1 -1
  49. package/dist/core/tools/write.d.ts.map +1 -1
  50. package/dist/core/tools/write.js +28 -7
  51. package/dist/core/tools/write.js.map +1 -1
  52. package/dist/modes/interactive/components/config-selector.d.ts +2 -2
  53. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  54. package/dist/modes/interactive/components/config-selector.js +7 -4
  55. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  56. package/dist/modes/interactive/components/footer.d.ts +0 -1
  57. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  58. package/dist/modes/interactive/components/footer.js +42 -44
  59. package/dist/modes/interactive/components/footer.js.map +1 -1
  60. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  61. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  62. package/dist/modes/interactive/interactive-mode.js +55 -48
  63. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  64. package/dist/modes/interactive/session-info-format.d.ts +3 -0
  65. package/dist/modes/interactive/session-info-format.d.ts.map +1 -0
  66. package/dist/modes/interactive/session-info-format.js +44 -0
  67. package/dist/modes/interactive/session-info-format.js.map +1 -0
  68. package/dist/modes/interactive/working-status.d.ts +6 -0
  69. package/dist/modes/interactive/working-status.d.ts.map +1 -1
  70. package/dist/modes/interactive/working-status.js +11 -0
  71. package/dist/modes/interactive/working-status.js.map +1 -1
  72. package/dist/package-manager-cli.d.ts.map +1 -1
  73. package/dist/package-manager-cli.js +3 -4
  74. package/dist/package-manager-cli.js.map +1 -1
  75. package/dist/senpi +5 -1
  76. package/dist/utils/child-process.d.ts +7 -1
  77. package/dist/utils/child-process.d.ts.map +1 -1
  78. package/dist/utils/child-process.js +60 -7
  79. package/dist/utils/child-process.js.map +1 -1
  80. package/dist/utils/tools-manager.d.ts.map +1 -1
  81. package/dist/utils/tools-manager.js +4 -1
  82. package/dist/utils/tools-manager.js.map +1 -1
  83. package/docs/custom-provider.md +55 -0
  84. package/docs/extensions.md +1 -1
  85. package/docs/settings.md +1 -3
  86. package/docs/skills.md +3 -4
  87. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  88. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  89. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  90. package/examples/extensions/sandbox/package-lock.json +2 -2
  91. package/examples/extensions/sandbox/package.json +1 -1
  92. package/examples/extensions/with-deps/package-lock.json +2 -2
  93. package/examples/extensions/with-deps/package.json +1 -1
  94. package/package.json +6 -6
@@ -1,6 +1,5 @@
1
1
  import path from "node:path";
2
- import * as Diff from "diff";
3
- import { getLanguageFromPath, highlightCode } from "../../../../modes/interactive/theme/theme.js";
2
+ import { renderToolDiff } from "../../../tools/diff-render.js";
4
3
  import { parsePatch } from "./parser.js";
5
4
  import { extractPatchedPaths } from "./text.js";
6
5
  export const PATCH_PREVIEW_MAX_LINES = 16;
@@ -137,131 +136,6 @@ export function formatInFlightCallText(patchText) {
137
136
  const count = paths.length > 1 ? ` (${paths.length} ${noun})` : "";
138
137
  return `Patching${count}: ${paths.join(", ")}`;
139
138
  }
140
- function parseRenderableDiffLine(line) {
141
- const match = line.match(/^([+\- ])(\s*\d+)\s(.*)$/);
142
- if (!match)
143
- return { kind: "meta", text: line };
144
- const sign = match[1];
145
- const lineNumber = match[2];
146
- if ((sign !== "+" && sign !== "-" && sign !== " ") || lineNumber === undefined)
147
- return { kind: "meta", text: line };
148
- const content = match[3] ?? "";
149
- if (sign === "+")
150
- return { content, kind: "added", lineNumber, sign };
151
- if (sign === "-")
152
- return { content, kind: "removed", lineNumber, sign };
153
- return { content, kind: "context", lineNumber, sign };
154
- }
155
- function replaceTabs(text) {
156
- return text.replace(/\t/g, " ");
157
- }
158
- function highlightDiffContent(content, filePath) {
159
- const plainContent = replaceTabs(content);
160
- const language = getLanguageFromPath(filePath);
161
- try {
162
- return highlightCode(plainContent, language)[0] ?? plainContent;
163
- }
164
- catch {
165
- return plainContent;
166
- }
167
- }
168
- function renderInlineDiff(oldContent, newContent, theme) {
169
- const parts = Diff.diffWords(replaceTabs(oldContent), replaceTabs(newContent));
170
- let added = "";
171
- let removed = "";
172
- let firstAdded = true;
173
- let firstRemoved = true;
174
- for (const part of parts) {
175
- if (part.added) {
176
- let value = part.value;
177
- if (firstAdded) {
178
- const leadingWhitespace = value.match(/^(\s*)/)?.[1] ?? "";
179
- added += leadingWhitespace;
180
- value = value.slice(leadingWhitespace.length);
181
- firstAdded = false;
182
- }
183
- if (value)
184
- added += theme.inverse(value);
185
- continue;
186
- }
187
- if (part.removed) {
188
- let value = part.value;
189
- if (firstRemoved) {
190
- const leadingWhitespace = value.match(/^(\s*)/)?.[1] ?? "";
191
- removed += leadingWhitespace;
192
- value = value.slice(leadingWhitespace.length);
193
- firstRemoved = false;
194
- }
195
- if (value)
196
- removed += theme.inverse(value);
197
- continue;
198
- }
199
- added += part.value;
200
- removed += part.value;
201
- }
202
- return { added, removed };
203
- }
204
- function renderOpenCodeLikeDiffLine(line, filePath, theme, contentOverride) {
205
- const lineNumber = theme.fg("muted", line.lineNumber);
206
- if (line.kind === "context") {
207
- return `${theme.fg("toolDiffContext", line.sign)}${lineNumber} ${highlightDiffContent(line.content, filePath)}`;
208
- }
209
- const diffColor = line.kind === "added" ? "toolDiffAdded" : "toolDiffRemoved";
210
- const background = line.kind === "added" ? "toolSuccessBg" : "toolErrorBg";
211
- const content = contentOverride === undefined
212
- ? highlightDiffContent(line.content, filePath)
213
- : theme.fg(diffColor, replaceTabs(contentOverride));
214
- const rendered = `${theme.fg(diffColor, line.sign)}${lineNumber} ${content}`;
215
- return theme.bg(background, rendered);
216
- }
217
- function renderOpenCodeLikeDiff(diffText, filePath, theme) {
218
- const parsedLines = diffText.split("\n").map(parseRenderableDiffLine);
219
- const rendered = [];
220
- let index = 0;
221
- while (index < parsedLines.length) {
222
- const line = parsedLines[index];
223
- if (!line) {
224
- index++;
225
- continue;
226
- }
227
- if (line.kind !== "removed") {
228
- rendered.push(line.kind === "meta"
229
- ? theme.fg("toolDiffContext", line.text)
230
- : renderOpenCodeLikeDiffLine(line, filePath, theme));
231
- index++;
232
- continue;
233
- }
234
- const removedLines = [];
235
- while (parsedLines[index]?.kind === "removed") {
236
- const removedLine = parsedLines[index];
237
- if (removedLine?.kind === "removed")
238
- removedLines.push(removedLine);
239
- index++;
240
- }
241
- const addedLines = [];
242
- while (parsedLines[index]?.kind === "added") {
243
- const addedLine = parsedLines[index];
244
- if (addedLine?.kind === "added")
245
- addedLines.push(addedLine);
246
- index++;
247
- }
248
- const pairedCount = Math.min(removedLines.length, addedLines.length);
249
- for (let pairIndex = 0; pairIndex < pairedCount; pairIndex++) {
250
- const removedLine = removedLines[pairIndex];
251
- const addedLine = addedLines[pairIndex];
252
- if (!removedLine || !addedLine)
253
- continue;
254
- const inline = renderInlineDiff(removedLine.content, addedLine.content, theme);
255
- rendered.push(renderOpenCodeLikeDiffLine(removedLine, filePath, theme, inline.removed));
256
- rendered.push(renderOpenCodeLikeDiffLine(addedLine, filePath, theme, inline.added));
257
- }
258
- for (const removedLine of removedLines.slice(pairedCount))
259
- rendered.push(renderOpenCodeLikeDiffLine(removedLine, filePath, theme));
260
- for (const addedLine of addedLines.slice(pairedCount))
261
- rendered.push(renderOpenCodeLikeDiffLine(addedLine, filePath, theme));
262
- }
263
- return rendered.join("\n");
264
- }
265
139
  export function getApplyPatchRenderState(toolCallId, cwd, patchText) {
266
140
  const existing = applyPatchRenderStates.get(toolCallId);
267
141
  if (existing && existing.cwd === cwd && existing.patchText === patchText)
@@ -305,7 +179,10 @@ export function renderPatchPreview(preview, cwd, theme, expanded) {
305
179
  ? `${headerPrefix}${formatPatchFilePath(file, cwd)} ${formatLineCountSummary(file.added, file.removed)}`
306
180
  : header;
307
181
  }
308
- const renderedDiff = renderOpenCodeLikeDiff(truncatePreview(file.diff), file.movePath ?? file.filePath, theme);
182
+ const renderedDiff = renderToolDiff(truncatePreview(file.diff), {
183
+ filePath: file.movePath ?? file.filePath,
184
+ theme,
185
+ });
309
186
  if (headerPrefix.length > 0) {
310
187
  const nestedHeader = `${headerPrefix}${formatPatchFilePath(file, cwd)} ${formatLineCountSummary(file.added, file.removed)}`;
311
188
  return `${nestedHeader}\n${renderedDiff
@@ -1 +1 @@
1
- {"version":3,"file":"preview-format.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/gpt-apply-patch/preview-format.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\nimport * as Diff from \"diff\";\nimport { getLanguageFromPath, highlightCode } from \"../../../../modes/interactive/theme/theme.js\";\nimport { parsePatch } from \"./parser.js\";\nimport { extractPatchedPaths } from \"./text.js\";\nimport type {\n\tApplyPatchOperation,\n\tApplyPatchPreview,\n\tApplyPatchPreviewFile,\n\tApplyPatchRenderState,\n\tApplyPatchTheme,\n} from \"./types.js\";\n\nexport const PATCH_PREVIEW_MAX_LINES = 16;\nexport const PATCH_PREVIEW_MAX_CHARS = 4000;\nconst PATCH_PREVIEW_HEAD_LINES = 8;\nconst PATCH_PREVIEW_TAIL_LINES = PATCH_PREVIEW_MAX_LINES - PATCH_PREVIEW_HEAD_LINES - 1;\nconst PATCH_PREVIEW_TRUNCATION_MARKER = \"…\";\nconst applyPatchRenderStates = new Map<string, ApplyPatchRenderState>();\n\nfunction isChangedPreviewLine(line: string): boolean {\n\treturn /^[+-]\\s*\\d+\\s/.test(line);\n}\n\nfunction countWindowLines(lines: string[], start: number, end: number): number {\n\treturn end - start + (start > 0 ? 1 : 0) + (end < lines.length ? 1 : 0);\n}\n\nfunction formatPreviewWindow(lines: string[], start: number, end: number): string {\n\tconst previewLines = lines.slice(start, end);\n\tif (start > 0) previewLines.unshift(\"…\");\n\tif (end < lines.length) previewLines.push(\"…\");\n\treturn previewLines.join(\"\\n\");\n}\n\nfunction createChangedHunkPreview(lines: string[]): string | undefined {\n\tconst firstChangedLine = lines.findIndex(isChangedPreviewLine);\n\tif (firstChangedLine === -1) return undefined;\n\n\tlet start = firstChangedLine;\n\tlet end = firstChangedLine + 1;\n\twhile (end < lines.length) {\n\t\tconst line = lines[end];\n\t\tif (line === undefined || !isChangedPreviewLine(line)) break;\n\t\tend++;\n\t}\n\n\tconst changedHunkEnd = end;\n\twhile (end > start && countWindowLines(lines, start, end) > PATCH_PREVIEW_MAX_LINES) end--;\n\n\twhile (countWindowLines(lines, start, end) < PATCH_PREVIEW_MAX_LINES) {\n\t\tconst canAddBefore = start > 0;\n\t\tconst canAddAfter = end < lines.length;\n\t\tif (!canAddBefore && !canAddAfter) break;\n\n\t\tconst beforeContextLines = firstChangedLine - start;\n\t\tconst afterContextLines = end - changedHunkEnd;\n\t\tif (canAddBefore && (!canAddAfter || beforeContextLines <= afterContextLines)) {\n\t\t\tstart--;\n\t\t} else {\n\t\t\tend++;\n\t\t}\n\t}\n\n\treturn formatPreviewWindow(lines, start, end);\n}\n\nfunction formatLineCountSummary(added: number, removed: number): string {\n\treturn `(+${added} -${removed})`;\n}\n\nfunction countLines(text: string): number {\n\tif (text.length === 0) return 0;\n\tlet lines = 1;\n\tfor (let index = 0; index < text.length; index++) {\n\t\tif (text.charCodeAt(index) === 10) lines += 1;\n\t}\n\treturn lines;\n}\n\nfunction enforcePreviewCharLimit(preview: string): string {\n\tif (preview.length <= PATCH_PREVIEW_MAX_CHARS) return preview;\n\treturn `${preview.slice(0, PATCH_PREVIEW_MAX_CHARS - PATCH_PREVIEW_TRUNCATION_MARKER.length).trimEnd()}${PATCH_PREVIEW_TRUNCATION_MARKER}`;\n}\n\nexport function truncatePreview(text: string): string {\n\tif (text.length <= PATCH_PREVIEW_MAX_CHARS && countLines(text) <= PATCH_PREVIEW_MAX_LINES) return text;\n\tconst lines = text.split(\"\\n\");\n\tconst changedHunkPreview = createChangedHunkPreview(lines);\n\tconst preview =\n\t\tchangedHunkPreview ??\n\t\t[...lines.slice(0, PATCH_PREVIEW_HEAD_LINES), \"…\", ...lines.slice(-PATCH_PREVIEW_TAIL_LINES)].join(\"\\n\");\n\treturn enforcePreviewCharLimit(preview);\n}\n\nexport function displayPath(filePath: string, cwd: string): string {\n\tif (!path.isAbsolute(filePath)) return filePath;\n\tconst absoluteCwd = path.resolve(cwd);\n\tconst relativePath = path.relative(absoluteCwd, filePath);\n\tif (\n\t\trelativePath === \"\" ||\n\t\t(!relativePath.startsWith(`..${path.sep}`) && relativePath !== \"..\" && !path.isAbsolute(relativePath))\n\t) {\n\t\treturn relativePath || \".\";\n\t}\n\treturn filePath;\n}\n\nfunction formatPatchFilePath(file: ApplyPatchPreviewFile, cwd: string = process.cwd()): string {\n\tconst filePath = displayPath(file.filePath, cwd);\n\tif (!file.movePath) return filePath;\n\treturn `${filePath} → ${displayPath(file.movePath, cwd)}`;\n}\n\nfunction formatPatchOperation(operation: ApplyPatchOperation): string {\n\tif (operation === \"add\") return \"Added\";\n\tif (operation === \"delete\") return \"Deleted\";\n\treturn \"Edited\";\n}\n\nexport function formatPatchPreview(\n\tpreview: ApplyPatchPreview,\n\tcwd: string = process.cwd(),\n\texpanded: boolean = true,\n): string {\n\tconst lines: string[] = [];\n\tif (preview.files.length === 1) {\n\t\tconst file = preview.files[0];\n\t\tif (file) {\n\t\t\tlines.push(\n\t\t\t\t`• ${formatPatchOperation(file.operation)} ${formatPatchFilePath(file, cwd)} ${formatLineCountSummary(file.added, file.removed)}`,\n\t\t\t);\n\t\t\tif (expanded && file.diff)\n\t\t\t\tlines.push(\n\t\t\t\t\t...truncatePreview(file.diff)\n\t\t\t\t\t\t.split(\"\\n\")\n\t\t\t\t\t\t.map((line) => ` ${line}`),\n\t\t\t\t);\n\t\t}\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\tconst noun = preview.files.length === 1 ? \"file\" : \"files\";\n\tlines.push(`• Edited ${preview.files.length} ${noun} ${formatLineCountSummary(preview.added, preview.removed)}`);\n\tfor (const file of preview.files) {\n\t\tlines.push(` └ ${formatPatchFilePath(file, cwd)} ${formatLineCountSummary(file.added, file.removed)}`);\n\t\tif (expanded && file.diff)\n\t\t\tlines.push(\n\t\t\t\t...truncatePreview(file.diff)\n\t\t\t\t\t.split(\"\\n\")\n\t\t\t\t\t.map((line) => ` ${line}`),\n\t\t\t);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nexport function formatInFlightCallText(patchText: string): string {\n\tconst paths = extractPatchedPaths(patchText);\n\tif (paths.length === 0) return \"Patching\";\n\tconst noun = paths.length === 1 ? \"file\" : \"files\";\n\tconst count = paths.length > 1 ? ` (${paths.length} ${noun})` : \"\";\n\treturn `Patching${count}: ${paths.join(\", \")}`;\n}\n\ntype RenderableAddedDiffLine = { content: string; kind: \"added\"; lineNumber: string; sign: \"+\" };\ntype RenderableRemovedDiffLine = { content: string; kind: \"removed\"; lineNumber: string; sign: \"-\" };\ntype RenderableContextDiffLine = { content: string; kind: \"context\"; lineNumber: string; sign: \" \" };\ntype RenderableContentDiffLine = RenderableAddedDiffLine | RenderableContextDiffLine | RenderableRemovedDiffLine;\ntype RenderableDiffLine = RenderableContentDiffLine | { kind: \"meta\"; text: string };\n\nfunction parseRenderableDiffLine(line: string): RenderableDiffLine {\n\tconst match = line.match(/^([+\\- ])(\\s*\\d+)\\s(.*)$/);\n\tif (!match) return { kind: \"meta\", text: line };\n\n\tconst sign = match[1];\n\tconst lineNumber = match[2];\n\tif ((sign !== \"+\" && sign !== \"-\" && sign !== \" \") || lineNumber === undefined) return { kind: \"meta\", text: line };\n\n\tconst content = match[3] ?? \"\";\n\tif (sign === \"+\") return { content, kind: \"added\", lineNumber, sign };\n\tif (sign === \"-\") return { content, kind: \"removed\", lineNumber, sign };\n\treturn { content, kind: \"context\", lineNumber, sign };\n}\n\nfunction replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\nfunction highlightDiffContent(content: string, filePath: string): string {\n\tconst plainContent = replaceTabs(content);\n\tconst language = getLanguageFromPath(filePath);\n\ttry {\n\t\treturn highlightCode(plainContent, language)[0] ?? plainContent;\n\t} catch {\n\t\treturn plainContent;\n\t}\n}\n\nfunction renderInlineDiff(\n\toldContent: string,\n\tnewContent: string,\n\ttheme: ApplyPatchTheme,\n): { added: string; removed: string } {\n\tconst parts = Diff.diffWords(replaceTabs(oldContent), replaceTabs(newContent));\n\tlet added = \"\";\n\tlet removed = \"\";\n\tlet firstAdded = true;\n\tlet firstRemoved = true;\n\n\tfor (const part of parts) {\n\t\tif (part.added) {\n\t\t\tlet value = part.value;\n\t\t\tif (firstAdded) {\n\t\t\t\tconst leadingWhitespace = value.match(/^(\\s*)/)?.[1] ?? \"\";\n\t\t\t\tadded += leadingWhitespace;\n\t\t\t\tvalue = value.slice(leadingWhitespace.length);\n\t\t\t\tfirstAdded = false;\n\t\t\t}\n\t\t\tif (value) added += theme.inverse(value);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (part.removed) {\n\t\t\tlet value = part.value;\n\t\t\tif (firstRemoved) {\n\t\t\t\tconst leadingWhitespace = value.match(/^(\\s*)/)?.[1] ?? \"\";\n\t\t\t\tremoved += leadingWhitespace;\n\t\t\t\tvalue = value.slice(leadingWhitespace.length);\n\t\t\t\tfirstRemoved = false;\n\t\t\t}\n\t\t\tif (value) removed += theme.inverse(value);\n\t\t\tcontinue;\n\t\t}\n\n\t\tadded += part.value;\n\t\tremoved += part.value;\n\t}\n\n\treturn { added, removed };\n}\n\nfunction renderOpenCodeLikeDiffLine(\n\tline: RenderableContentDiffLine,\n\tfilePath: string,\n\ttheme: ApplyPatchTheme,\n\tcontentOverride?: string,\n): string {\n\tconst lineNumber = theme.fg(\"muted\", line.lineNumber);\n\tif (line.kind === \"context\") {\n\t\treturn `${theme.fg(\"toolDiffContext\", line.sign)}${lineNumber} ${highlightDiffContent(line.content, filePath)}`;\n\t}\n\n\tconst diffColor = line.kind === \"added\" ? \"toolDiffAdded\" : \"toolDiffRemoved\";\n\tconst background = line.kind === \"added\" ? \"toolSuccessBg\" : \"toolErrorBg\";\n\tconst content =\n\t\tcontentOverride === undefined\n\t\t\t? highlightDiffContent(line.content, filePath)\n\t\t\t: theme.fg(diffColor, replaceTabs(contentOverride));\n\tconst rendered = `${theme.fg(diffColor, line.sign)}${lineNumber} ${content}`;\n\treturn theme.bg(background, rendered);\n}\n\nfunction renderOpenCodeLikeDiff(diffText: string, filePath: string, theme: ApplyPatchTheme): string {\n\tconst parsedLines = diffText.split(\"\\n\").map(parseRenderableDiffLine);\n\tconst rendered: string[] = [];\n\tlet index = 0;\n\n\twhile (index < parsedLines.length) {\n\t\tconst line = parsedLines[index];\n\t\tif (!line) {\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (line.kind !== \"removed\") {\n\t\t\trendered.push(\n\t\t\t\tline.kind === \"meta\"\n\t\t\t\t\t? theme.fg(\"toolDiffContext\", line.text)\n\t\t\t\t\t: renderOpenCodeLikeDiffLine(line, filePath, theme),\n\t\t\t);\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst removedLines: RenderableRemovedDiffLine[] = [];\n\t\twhile (parsedLines[index]?.kind === \"removed\") {\n\t\t\tconst removedLine = parsedLines[index];\n\t\t\tif (removedLine?.kind === \"removed\") removedLines.push(removedLine);\n\t\t\tindex++;\n\t\t}\n\n\t\tconst addedLines: RenderableAddedDiffLine[] = [];\n\t\twhile (parsedLines[index]?.kind === \"added\") {\n\t\t\tconst addedLine = parsedLines[index];\n\t\t\tif (addedLine?.kind === \"added\") addedLines.push(addedLine);\n\t\t\tindex++;\n\t\t}\n\n\t\tconst pairedCount = Math.min(removedLines.length, addedLines.length);\n\t\tfor (let pairIndex = 0; pairIndex < pairedCount; pairIndex++) {\n\t\t\tconst removedLine = removedLines[pairIndex];\n\t\t\tconst addedLine = addedLines[pairIndex];\n\t\t\tif (!removedLine || !addedLine) continue;\n\n\t\t\tconst inline = renderInlineDiff(removedLine.content, addedLine.content, theme);\n\t\t\trendered.push(renderOpenCodeLikeDiffLine(removedLine, filePath, theme, inline.removed));\n\t\t\trendered.push(renderOpenCodeLikeDiffLine(addedLine, filePath, theme, inline.added));\n\t\t}\n\n\t\tfor (const removedLine of removedLines.slice(pairedCount))\n\t\t\trendered.push(renderOpenCodeLikeDiffLine(removedLine, filePath, theme));\n\t\tfor (const addedLine of addedLines.slice(pairedCount))\n\t\t\trendered.push(renderOpenCodeLikeDiffLine(addedLine, filePath, theme));\n\t}\n\n\treturn rendered.join(\"\\n\");\n}\n\nexport function getApplyPatchRenderState(toolCallId: string, cwd: string, patchText: string): ApplyPatchRenderState {\n\tconst existing = applyPatchRenderStates.get(toolCallId);\n\tif (existing && existing.cwd === cwd && existing.patchText === patchText) return existing;\n\n\tconst callText = formatInFlightCallText(patchText);\n\tlet collapsed = \"\";\n\tlet expanded = \"\";\n\ttry {\n\t\tconst hunks = parsePatch(patchText);\n\t\tif (hunks.length > 0) {\n\t\t\tconst files = hunks.map((hunk) => ({\n\t\t\t\tfilePath: hunk.filePath,\n\t\t\t\tmovePath: hunk.type === \"update\" ? hunk.movePath : undefined,\n\t\t\t\toperation: hunk.type,\n\t\t\t\tdiff: \"\",\n\t\t\t\tadded: 0,\n\t\t\t\tremoved: 0,\n\t\t\t})) satisfies ApplyPatchPreviewFile[];\n\t\t\tconst preview: ApplyPatchPreview = { files, added: 0, removed: 0 };\n\t\t\tcollapsed = formatPatchPreview(preview, cwd, false);\n\t\t\texpanded = formatPatchPreview(preview, cwd, true);\n\t\t}\n\t} catch {\n\t\t// ignore incomplete patch text\n\t}\n\n\tconst nextState: ApplyPatchRenderState = { ...existing, cwd, patchText, callText, collapsed, expanded };\n\tapplyPatchRenderStates.set(toolCallId, nextState);\n\treturn nextState;\n}\n\nexport function clearApplyPatchRenderState(): void {\n\tapplyPatchRenderStates.clear();\n}\n\nexport function renderPatchPreview(\n\tpreview: ApplyPatchPreview,\n\tcwd: string,\n\ttheme: ApplyPatchTheme,\n\texpanded: boolean,\n): string {\n\tif (expanded) {\n\t\ttry {\n\t\t\tconst renderFile = (file: ApplyPatchPreviewFile, headerPrefix: string): string => {\n\t\t\t\tconst header = `• ${formatPatchOperation(file.operation)} ${formatPatchFilePath(file, cwd)} ${formatLineCountSummary(file.added, file.removed)}`;\n\t\t\t\tif (!file.diff) {\n\t\t\t\t\treturn headerPrefix.length > 0\n\t\t\t\t\t\t? `${headerPrefix}${formatPatchFilePath(file, cwd)} ${formatLineCountSummary(file.added, file.removed)}`\n\t\t\t\t\t\t: header;\n\t\t\t\t}\n\n\t\t\t\tconst renderedDiff = renderOpenCodeLikeDiff(\n\t\t\t\t\ttruncatePreview(file.diff),\n\t\t\t\t\tfile.movePath ?? file.filePath,\n\t\t\t\t\ttheme,\n\t\t\t\t);\n\t\t\t\tif (headerPrefix.length > 0) {\n\t\t\t\t\tconst nestedHeader = `${headerPrefix}${formatPatchFilePath(file, cwd)} ${formatLineCountSummary(file.added, file.removed)}`;\n\t\t\t\t\treturn `${nestedHeader}\\n${renderedDiff\n\t\t\t\t\t\t.split(\"\\n\")\n\t\t\t\t\t\t.map((line) => ` ${line}`)\n\t\t\t\t\t\t.join(\"\\n\")}`;\n\t\t\t\t}\n\t\t\t\treturn `${header}\\n${renderedDiff}`;\n\t\t\t};\n\n\t\t\tif (preview.files.length === 1) {\n\t\t\t\tconst file = preview.files[0];\n\t\t\t\treturn file ? renderFile(file, \"\") : \"\";\n\t\t\t}\n\n\t\t\tconst noun = preview.files.length === 1 ? \"file\" : \"files\";\n\t\t\tconst renderedFiles = preview.files.map((file) => renderFile(file, \" └ \")).join(\"\\n\");\n\t\t\tif (renderedFiles.length > 0) {\n\t\t\t\treturn `• Edited ${preview.files.length} ${noun} ${formatLineCountSummary(preview.added, preview.removed)}\\n${renderedFiles}`;\n\t\t\t}\n\t\t} catch {\n\t\t\t// fall back to manual themed line rendering\n\t\t}\n\t}\n\n\treturn formatPatchPreview(preview, cwd, expanded)\n\t\t.split(\"\\n\")\n\t\t.map((line) => renderPatchLine(line, theme))\n\t\t.join(\"\\n\");\n}\n\nexport function formatPendingPatchPaths(patchText: string): string {\n\tconst paths = extractPatchedPaths(patchText);\n\tif (paths.length === 0) return \"Applying patch...\";\n\treturn `Applying patch...\\n${paths.map((filePath) => `• ${filePath}`).join(\"\\n\")}`;\n}\n\nexport function renderPatchLine(line: string, theme: ApplyPatchTheme): string {\n\tconst trimmed = line.trimStart();\n\tif (trimmed.startsWith(\"+\")) return theme.fg(\"toolDiffAdded\", line);\n\tif (trimmed.startsWith(\"-\")) return theme.fg(\"toolDiffRemoved\", line);\n\tif (trimmed.startsWith(\"•\")) return theme.fg(\"toolTitle\", theme.bold(line));\n\tif (trimmed.startsWith(\"└\")) return theme.fg(\"accent\", line);\n\treturn theme.fg(\"toolDiffContext\", line);\n}\n"]}
1
+ {"version":3,"file":"preview-format.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/gpt-apply-patch/preview-format.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\nimport { renderToolDiff } from \"../../../tools/diff-render.js\";\nimport { parsePatch } from \"./parser.js\";\nimport { extractPatchedPaths } from \"./text.js\";\nimport type {\n\tApplyPatchOperation,\n\tApplyPatchPreview,\n\tApplyPatchPreviewFile,\n\tApplyPatchRenderState,\n\tApplyPatchTheme,\n} from \"./types.js\";\n\nexport const PATCH_PREVIEW_MAX_LINES = 16;\nexport const PATCH_PREVIEW_MAX_CHARS = 4000;\nconst PATCH_PREVIEW_HEAD_LINES = 8;\nconst PATCH_PREVIEW_TAIL_LINES = PATCH_PREVIEW_MAX_LINES - PATCH_PREVIEW_HEAD_LINES - 1;\nconst PATCH_PREVIEW_TRUNCATION_MARKER = \"…\";\nconst applyPatchRenderStates = new Map<string, ApplyPatchRenderState>();\n\nfunction isChangedPreviewLine(line: string): boolean {\n\treturn /^[+-]\\s*\\d+\\s/.test(line);\n}\n\nfunction countWindowLines(lines: string[], start: number, end: number): number {\n\treturn end - start + (start > 0 ? 1 : 0) + (end < lines.length ? 1 : 0);\n}\n\nfunction formatPreviewWindow(lines: string[], start: number, end: number): string {\n\tconst previewLines = lines.slice(start, end);\n\tif (start > 0) previewLines.unshift(\"…\");\n\tif (end < lines.length) previewLines.push(\"…\");\n\treturn previewLines.join(\"\\n\");\n}\n\nfunction createChangedHunkPreview(lines: string[]): string | undefined {\n\tconst firstChangedLine = lines.findIndex(isChangedPreviewLine);\n\tif (firstChangedLine === -1) return undefined;\n\n\tlet start = firstChangedLine;\n\tlet end = firstChangedLine + 1;\n\twhile (end < lines.length) {\n\t\tconst line = lines[end];\n\t\tif (line === undefined || !isChangedPreviewLine(line)) break;\n\t\tend++;\n\t}\n\n\tconst changedHunkEnd = end;\n\twhile (end > start && countWindowLines(lines, start, end) > PATCH_PREVIEW_MAX_LINES) end--;\n\n\twhile (countWindowLines(lines, start, end) < PATCH_PREVIEW_MAX_LINES) {\n\t\tconst canAddBefore = start > 0;\n\t\tconst canAddAfter = end < lines.length;\n\t\tif (!canAddBefore && !canAddAfter) break;\n\n\t\tconst beforeContextLines = firstChangedLine - start;\n\t\tconst afterContextLines = end - changedHunkEnd;\n\t\tif (canAddBefore && (!canAddAfter || beforeContextLines <= afterContextLines)) {\n\t\t\tstart--;\n\t\t} else {\n\t\t\tend++;\n\t\t}\n\t}\n\n\treturn formatPreviewWindow(lines, start, end);\n}\n\nfunction formatLineCountSummary(added: number, removed: number): string {\n\treturn `(+${added} -${removed})`;\n}\n\nfunction countLines(text: string): number {\n\tif (text.length === 0) return 0;\n\tlet lines = 1;\n\tfor (let index = 0; index < text.length; index++) {\n\t\tif (text.charCodeAt(index) === 10) lines += 1;\n\t}\n\treturn lines;\n}\n\nfunction enforcePreviewCharLimit(preview: string): string {\n\tif (preview.length <= PATCH_PREVIEW_MAX_CHARS) return preview;\n\treturn `${preview.slice(0, PATCH_PREVIEW_MAX_CHARS - PATCH_PREVIEW_TRUNCATION_MARKER.length).trimEnd()}${PATCH_PREVIEW_TRUNCATION_MARKER}`;\n}\n\nexport function truncatePreview(text: string): string {\n\tif (text.length <= PATCH_PREVIEW_MAX_CHARS && countLines(text) <= PATCH_PREVIEW_MAX_LINES) return text;\n\tconst lines = text.split(\"\\n\");\n\tconst changedHunkPreview = createChangedHunkPreview(lines);\n\tconst preview =\n\t\tchangedHunkPreview ??\n\t\t[...lines.slice(0, PATCH_PREVIEW_HEAD_LINES), \"…\", ...lines.slice(-PATCH_PREVIEW_TAIL_LINES)].join(\"\\n\");\n\treturn enforcePreviewCharLimit(preview);\n}\n\nexport function displayPath(filePath: string, cwd: string): string {\n\tif (!path.isAbsolute(filePath)) return filePath;\n\tconst absoluteCwd = path.resolve(cwd);\n\tconst relativePath = path.relative(absoluteCwd, filePath);\n\tif (\n\t\trelativePath === \"\" ||\n\t\t(!relativePath.startsWith(`..${path.sep}`) && relativePath !== \"..\" && !path.isAbsolute(relativePath))\n\t) {\n\t\treturn relativePath || \".\";\n\t}\n\treturn filePath;\n}\n\nfunction formatPatchFilePath(file: ApplyPatchPreviewFile, cwd: string = process.cwd()): string {\n\tconst filePath = displayPath(file.filePath, cwd);\n\tif (!file.movePath) return filePath;\n\treturn `${filePath} → ${displayPath(file.movePath, cwd)}`;\n}\n\nfunction formatPatchOperation(operation: ApplyPatchOperation): string {\n\tif (operation === \"add\") return \"Added\";\n\tif (operation === \"delete\") return \"Deleted\";\n\treturn \"Edited\";\n}\n\nexport function formatPatchPreview(\n\tpreview: ApplyPatchPreview,\n\tcwd: string = process.cwd(),\n\texpanded: boolean = true,\n): string {\n\tconst lines: string[] = [];\n\tif (preview.files.length === 1) {\n\t\tconst file = preview.files[0];\n\t\tif (file) {\n\t\t\tlines.push(\n\t\t\t\t`• ${formatPatchOperation(file.operation)} ${formatPatchFilePath(file, cwd)} ${formatLineCountSummary(file.added, file.removed)}`,\n\t\t\t);\n\t\t\tif (expanded && file.diff)\n\t\t\t\tlines.push(\n\t\t\t\t\t...truncatePreview(file.diff)\n\t\t\t\t\t\t.split(\"\\n\")\n\t\t\t\t\t\t.map((line) => ` ${line}`),\n\t\t\t\t);\n\t\t}\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\tconst noun = preview.files.length === 1 ? \"file\" : \"files\";\n\tlines.push(`• Edited ${preview.files.length} ${noun} ${formatLineCountSummary(preview.added, preview.removed)}`);\n\tfor (const file of preview.files) {\n\t\tlines.push(` └ ${formatPatchFilePath(file, cwd)} ${formatLineCountSummary(file.added, file.removed)}`);\n\t\tif (expanded && file.diff)\n\t\t\tlines.push(\n\t\t\t\t...truncatePreview(file.diff)\n\t\t\t\t\t.split(\"\\n\")\n\t\t\t\t\t.map((line) => ` ${line}`),\n\t\t\t);\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nexport function formatInFlightCallText(patchText: string): string {\n\tconst paths = extractPatchedPaths(patchText);\n\tif (paths.length === 0) return \"Patching\";\n\tconst noun = paths.length === 1 ? \"file\" : \"files\";\n\tconst count = paths.length > 1 ? ` (${paths.length} ${noun})` : \"\";\n\treturn `Patching${count}: ${paths.join(\", \")}`;\n}\n\nexport function getApplyPatchRenderState(toolCallId: string, cwd: string, patchText: string): ApplyPatchRenderState {\n\tconst existing = applyPatchRenderStates.get(toolCallId);\n\tif (existing && existing.cwd === cwd && existing.patchText === patchText) return existing;\n\n\tconst callText = formatInFlightCallText(patchText);\n\tlet collapsed = \"\";\n\tlet expanded = \"\";\n\ttry {\n\t\tconst hunks = parsePatch(patchText);\n\t\tif (hunks.length > 0) {\n\t\t\tconst files = hunks.map((hunk) => ({\n\t\t\t\tfilePath: hunk.filePath,\n\t\t\t\tmovePath: hunk.type === \"update\" ? hunk.movePath : undefined,\n\t\t\t\toperation: hunk.type,\n\t\t\t\tdiff: \"\",\n\t\t\t\tadded: 0,\n\t\t\t\tremoved: 0,\n\t\t\t})) satisfies ApplyPatchPreviewFile[];\n\t\t\tconst preview: ApplyPatchPreview = { files, added: 0, removed: 0 };\n\t\t\tcollapsed = formatPatchPreview(preview, cwd, false);\n\t\t\texpanded = formatPatchPreview(preview, cwd, true);\n\t\t}\n\t} catch {\n\t\t// ignore incomplete patch text\n\t}\n\n\tconst nextState: ApplyPatchRenderState = { ...existing, cwd, patchText, callText, collapsed, expanded };\n\tapplyPatchRenderStates.set(toolCallId, nextState);\n\treturn nextState;\n}\n\nexport function clearApplyPatchRenderState(): void {\n\tapplyPatchRenderStates.clear();\n}\n\nexport function renderPatchPreview(\n\tpreview: ApplyPatchPreview,\n\tcwd: string,\n\ttheme: ApplyPatchTheme,\n\texpanded: boolean,\n): string {\n\tif (expanded) {\n\t\ttry {\n\t\t\tconst renderFile = (file: ApplyPatchPreviewFile, headerPrefix: string): string => {\n\t\t\t\tconst header = `• ${formatPatchOperation(file.operation)} ${formatPatchFilePath(file, cwd)} ${formatLineCountSummary(file.added, file.removed)}`;\n\t\t\t\tif (!file.diff) {\n\t\t\t\t\treturn headerPrefix.length > 0\n\t\t\t\t\t\t? `${headerPrefix}${formatPatchFilePath(file, cwd)} ${formatLineCountSummary(file.added, file.removed)}`\n\t\t\t\t\t\t: header;\n\t\t\t\t}\n\n\t\t\t\tconst renderedDiff = renderToolDiff(truncatePreview(file.diff), {\n\t\t\t\t\tfilePath: file.movePath ?? file.filePath,\n\t\t\t\t\ttheme,\n\t\t\t\t});\n\t\t\t\tif (headerPrefix.length > 0) {\n\t\t\t\t\tconst nestedHeader = `${headerPrefix}${formatPatchFilePath(file, cwd)} ${formatLineCountSummary(file.added, file.removed)}`;\n\t\t\t\t\treturn `${nestedHeader}\\n${renderedDiff\n\t\t\t\t\t\t.split(\"\\n\")\n\t\t\t\t\t\t.map((line) => ` ${line}`)\n\t\t\t\t\t\t.join(\"\\n\")}`;\n\t\t\t\t}\n\t\t\t\treturn `${header}\\n${renderedDiff}`;\n\t\t\t};\n\n\t\t\tif (preview.files.length === 1) {\n\t\t\t\tconst file = preview.files[0];\n\t\t\t\treturn file ? renderFile(file, \"\") : \"\";\n\t\t\t}\n\n\t\t\tconst noun = preview.files.length === 1 ? \"file\" : \"files\";\n\t\t\tconst renderedFiles = preview.files.map((file) => renderFile(file, \" └ \")).join(\"\\n\");\n\t\t\tif (renderedFiles.length > 0) {\n\t\t\t\treturn `• Edited ${preview.files.length} ${noun} ${formatLineCountSummary(preview.added, preview.removed)}\\n${renderedFiles}`;\n\t\t\t}\n\t\t} catch {\n\t\t\t// fall back to manual themed line rendering\n\t\t}\n\t}\n\n\treturn formatPatchPreview(preview, cwd, expanded)\n\t\t.split(\"\\n\")\n\t\t.map((line) => renderPatchLine(line, theme))\n\t\t.join(\"\\n\");\n}\n\nexport function formatPendingPatchPaths(patchText: string): string {\n\tconst paths = extractPatchedPaths(patchText);\n\tif (paths.length === 0) return \"Applying patch...\";\n\treturn `Applying patch...\\n${paths.map((filePath) => `• ${filePath}`).join(\"\\n\")}`;\n}\n\nexport function renderPatchLine(line: string, theme: ApplyPatchTheme): string {\n\tconst trimmed = line.trimStart();\n\tif (trimmed.startsWith(\"+\")) return theme.fg(\"toolDiffAdded\", line);\n\tif (trimmed.startsWith(\"-\")) return theme.fg(\"toolDiffRemoved\", line);\n\tif (trimmed.startsWith(\"•\")) return theme.fg(\"toolTitle\", theme.bold(line));\n\tif (trimmed.startsWith(\"└\")) return theme.fg(\"accent\", line);\n\treturn theme.fg(\"toolDiffContext\", line);\n}\n"]}
@@ -87,6 +87,7 @@ export declare class ModelRegistry {
87
87
  private storeModelExtraBody;
88
88
  private storeModelUpstreamId;
89
89
  private storeModelServiceTier;
90
+ private clearModelRequestConfigForProvider;
90
91
  /**
91
92
  * Get API key and request headers for a model.
92
93
  */
@@ -1 +1 @@
1
- {"version":3,"file":"model-registry.d.ts","sourceRoot":"","sources":["../../src/core/model-registry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Model registry - manages built-in and custom models, provides API key resolution.\n */\n\nimport {\n\ttype AnthropicMessagesCompat,\n\ttype Api,\n\ttype AssistantMessageEventStream,\n\ttype Context,\n\tgetModels,\n\tgetProviders,\n\ttype KnownProvider,\n\ttype Model,\n\ttype OAuthProviderInterface,\n\ttype OpenAICompletionsCompat,\n\ttype OpenAIResponsesCompat,\n\tregisterApiProvider,\n\tresetApiProviders,\n\ttype SimpleStreamOptions,\n} from \"@earendil-works/pi-ai\";\nimport { registerOAuthProvider, resetOAuthProviders } from \"@earendil-works/pi-ai/oauth\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { Compile } from \"typebox/compile\";\nimport type { TLocalizedValidationError } from \"typebox/error\";\nimport { getAgentDir } from \"../config.js\";\nimport type { AuthStatus, AuthStorage } from \"./auth-storage.js\";\nimport { BUILT_IN_PROVIDER_DISPLAY_NAMES } from \"./provider-display-names.js\";\nimport {\n\tclearConfigValueCache,\n\tresolveConfigValueOrThrow,\n\tresolveConfigValueUncached,\n\tresolveHeadersOrThrow,\n} from \"./resolve-config-value.js\";\n\n// Schema for OpenRouter routing preferences\nconst PercentileCutoffsSchema = Type.Object({\n\tp50: Type.Optional(Type.Number()),\n\tp75: Type.Optional(Type.Number()),\n\tp90: Type.Optional(Type.Number()),\n\tp99: Type.Optional(Type.Number()),\n});\n\nconst OpenRouterRoutingSchema = Type.Object({\n\tallow_fallbacks: Type.Optional(Type.Boolean()),\n\trequire_parameters: Type.Optional(Type.Boolean()),\n\tdata_collection: Type.Optional(Type.Union([Type.Literal(\"deny\"), Type.Literal(\"allow\")])),\n\tzdr: Type.Optional(Type.Boolean()),\n\tenforce_distillable_text: Type.Optional(Type.Boolean()),\n\torder: Type.Optional(Type.Array(Type.String())),\n\tonly: Type.Optional(Type.Array(Type.String())),\n\tignore: Type.Optional(Type.Array(Type.String())),\n\tquantizations: Type.Optional(Type.Array(Type.String())),\n\tsort: Type.Optional(\n\t\tType.Union([\n\t\t\tType.String(),\n\t\t\tType.Object({\n\t\t\t\tby: Type.Optional(Type.String()),\n\t\t\t\tpartition: Type.Optional(Type.Union([Type.String(), Type.Null()])),\n\t\t\t}),\n\t\t]),\n\t),\n\tmax_price: Type.Optional(\n\t\tType.Object({\n\t\t\tprompt: Type.Optional(Type.Union([Type.Number(), Type.String()])),\n\t\t\tcompletion: Type.Optional(Type.Union([Type.Number(), Type.String()])),\n\t\t\timage: Type.Optional(Type.Union([Type.Number(), Type.String()])),\n\t\t\taudio: Type.Optional(Type.Union([Type.Number(), Type.String()])),\n\t\t\trequest: Type.Optional(Type.Union([Type.Number(), Type.String()])),\n\t\t}),\n\t),\n\tpreferred_min_throughput: Type.Optional(Type.Union([Type.Number(), PercentileCutoffsSchema])),\n\tpreferred_max_latency: Type.Optional(Type.Union([Type.Number(), PercentileCutoffsSchema])),\n});\n\n// Schema for Vercel AI Gateway routing preferences\nconst VercelGatewayRoutingSchema = Type.Object({\n\tonly: Type.Optional(Type.Array(Type.String())),\n\torder: Type.Optional(Type.Array(Type.String())),\n});\n\n// Schema for thinking level support and provider-specific values\nconst ThinkingLevelMapValueSchema = Type.Union([Type.String(), Type.Null()]);\nconst ThinkingLevelMapSchema = Type.Object({\n\toff: Type.Optional(ThinkingLevelMapValueSchema),\n\tminimal: Type.Optional(ThinkingLevelMapValueSchema),\n\tlow: Type.Optional(ThinkingLevelMapValueSchema),\n\tmedium: Type.Optional(ThinkingLevelMapValueSchema),\n\thigh: Type.Optional(ThinkingLevelMapValueSchema),\n\txhigh: Type.Optional(ThinkingLevelMapValueSchema),\n\tmax: Type.Optional(ThinkingLevelMapValueSchema),\n});\n\nconst OpenAICompletionsCompatSchema = Type.Object({\n\tsupportsStore: Type.Optional(Type.Boolean()),\n\tsupportsDeveloperRole: Type.Optional(Type.Boolean()),\n\tsupportsReasoningEffort: Type.Optional(Type.Boolean()),\n\tsupportsUsageInStreaming: Type.Optional(Type.Boolean()),\n\tmaxTokensField: Type.Optional(Type.Union([Type.Literal(\"max_completion_tokens\"), Type.Literal(\"max_tokens\")])),\n\trequiresToolResultName: Type.Optional(Type.Boolean()),\n\trequiresAssistantAfterToolResult: Type.Optional(Type.Boolean()),\n\trequiresThinkingAsText: Type.Optional(Type.Boolean()),\n\trequiresReasoningContentOnAssistantMessages: Type.Optional(Type.Boolean()),\n\tthinkingFormat: Type.Optional(\n\t\tType.Union([\n\t\t\tType.Literal(\"openai\"),\n\t\t\tType.Literal(\"openrouter\"),\n\t\t\tType.Literal(\"together\"),\n\t\t\tType.Literal(\"deepseek\"),\n\t\t\tType.Literal(\"zai\"),\n\t\t\tType.Literal(\"qwen\"),\n\t\t\tType.Literal(\"qwen-chat-template\"),\n\t\t]),\n\t),\n\tcacheControlFormat: Type.Optional(Type.Literal(\"anthropic\")),\n\topenRouterRouting: Type.Optional(OpenRouterRoutingSchema),\n\tvercelGatewayRouting: Type.Optional(VercelGatewayRoutingSchema),\n\tsupportsStrictMode: Type.Optional(Type.Boolean()),\n\ttoolCallFormat: Type.Optional(\n\t\tType.Union([\n\t\t\tType.Literal(\"hermes\"),\n\t\t\tType.Literal(\"gemma4\"),\n\t\t\tType.Literal(\"gemma4-delimiter\"),\n\t\t\tType.Literal(\"xml\"),\n\t\t\tType.Literal(\"yaml-xml\"),\n\t\t]),\n\t),\n\tsupportsLongCacheRetention: Type.Optional(Type.Boolean()),\n});\n\nconst OpenAIResponsesCompatSchema = Type.Object({\n\tsendSessionIdHeader: Type.Optional(Type.Boolean()),\n\tsupportsLongCacheRetention: Type.Optional(Type.Boolean()),\n\tsupportsWebSocket: Type.Optional(Type.Boolean()),\n\tsupportsWebSearchPreview: Type.Optional(Type.Boolean()),\n});\n\nconst AnthropicMessagesCompatSchema = Type.Object({\n\tsupportsEagerToolInputStreaming: Type.Optional(Type.Boolean()),\n\tsupportsLongCacheRetention: Type.Optional(Type.Boolean()),\n\tsupportsDisabledThinking: Type.Optional(Type.Boolean()),\n});\n\nconst ProviderCompatSchema = Type.Union([\n\tOpenAICompletionsCompatSchema,\n\tOpenAIResponsesCompatSchema,\n\tAnthropicMessagesCompatSchema,\n]);\n\n// Schema for custom model definition\n// Most fields are optional with sensible defaults for local models (Ollama, LM Studio, etc.)\nconst ExtraBodySchema = Type.Record(Type.String(), Type.Unknown());\nconst ServiceTierSchema = Type.Union([Type.Literal(\"auto\"), Type.Literal(\"flex\"), Type.Literal(\"priority\")]);\ntype ModelServiceTier = Static<typeof ServiceTierSchema>;\n\nconst ModelDefinitionSchema = Type.Object({\n\tid: Type.String({ minLength: 1 }),\n\tname: Type.Optional(Type.String({ minLength: 1 })),\n\tupstreamModelId: Type.Optional(Type.String({ minLength: 1 })),\n\tserviceTier: Type.Optional(ServiceTierSchema),\n\tpromptPreset: Type.Optional(Type.String({ minLength: 1 })),\n\tapi: Type.Optional(Type.String({ minLength: 1 })),\n\tbaseUrl: Type.Optional(Type.String({ minLength: 1 })),\n\treasoning: Type.Optional(Type.Boolean()),\n\tthinkingLevelMap: Type.Optional(ThinkingLevelMapSchema),\n\tinput: Type.Optional(Type.Array(Type.Union([Type.Literal(\"text\"), Type.Literal(\"image\"), Type.Literal(\"video\")]))),\n\tcost: Type.Optional(\n\t\tType.Object({\n\t\t\tinput: Type.Number(),\n\t\t\toutput: Type.Number(),\n\t\t\tcacheRead: Type.Number(),\n\t\t\tcacheWrite: Type.Number(),\n\t\t}),\n\t),\n\tcontextWindow: Type.Optional(Type.Number()),\n\tmaxTokens: Type.Optional(Type.Number()),\n\theaders: Type.Optional(Type.Record(Type.String(), Type.String())),\n\textraBody: Type.Optional(ExtraBodySchema),\n\tcompat: Type.Optional(ProviderCompatSchema),\n});\n\n// Schema for per-model overrides (all fields optional, merged with built-in model)\nconst ModelOverrideSchema = Type.Object({\n\tname: Type.Optional(Type.String({ minLength: 1 })),\n\tpromptPreset: Type.Optional(Type.String({ minLength: 1 })),\n\treasoning: Type.Optional(Type.Boolean()),\n\tthinkingLevelMap: Type.Optional(ThinkingLevelMapSchema),\n\tthinkingLevelMapMode: Type.Optional(Type.Union([Type.Literal(\"merge\"), Type.Literal(\"replace\")])),\n\tinput: Type.Optional(Type.Array(Type.Union([Type.Literal(\"text\"), Type.Literal(\"image\"), Type.Literal(\"video\")]))),\n\tcost: Type.Optional(\n\t\tType.Object({\n\t\t\tinput: Type.Optional(Type.Number()),\n\t\t\toutput: Type.Optional(Type.Number()),\n\t\t\tcacheRead: Type.Optional(Type.Number()),\n\t\t\tcacheWrite: Type.Optional(Type.Number()),\n\t\t}),\n\t),\n\tcontextWindow: Type.Optional(Type.Number()),\n\tmaxTokens: Type.Optional(Type.Number()),\n\theaders: Type.Optional(Type.Record(Type.String(), Type.String())),\n\textraBody: Type.Optional(ExtraBodySchema),\n\tcompat: Type.Optional(ProviderCompatSchema),\n});\n\ntype ModelOverride = Static<typeof ModelOverrideSchema>;\ntype ThinkingLevelMap = Static<typeof ThinkingLevelMapSchema>;\ntype ModelWithConfigMetadata = Model<Api> & { promptPreset?: string; thinkingLevelMap?: ThinkingLevelMap };\n\nconst ProviderConfigSchema = Type.Object({\n\tname: Type.Optional(Type.String({ minLength: 1 })),\n\tdisabled: Type.Optional(Type.Boolean()),\n\tbaseUrl: Type.Optional(Type.String({ minLength: 1 })),\n\tapiKey: Type.Optional(Type.String({ minLength: 1 })),\n\tapi: Type.Optional(Type.String({ minLength: 1 })),\n\theaders: Type.Optional(Type.Record(Type.String(), Type.String())),\n\textraBody: Type.Optional(ExtraBodySchema),\n\tcompat: Type.Optional(ProviderCompatSchema),\n\tauthHeader: Type.Optional(Type.Boolean()),\n\twhitelist: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),\n\tblacklist: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),\n\tmodels: Type.Optional(Type.Array(ModelDefinitionSchema)),\n\tmodelOverrides: Type.Optional(Type.Record(Type.String(), ModelOverrideSchema)),\n});\n\nconst ModelsConfigSchema = Type.Object({\n\tdisabledProviders: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),\n\tproviders: Type.Record(Type.String(), ProviderConfigSchema),\n});\n\nconst validateModelsConfig = Compile(ModelsConfigSchema);\n\ntype ModelsConfig = Static<typeof ModelsConfigSchema>;\n\nfunction formatValidationPath(error: TLocalizedValidationError): string {\n\tif (error.keyword === \"required\") {\n\t\tconst requiredProperties = (error.params as { requiredProperties?: string[] }).requiredProperties;\n\t\tconst requiredProperty = requiredProperties?.[0];\n\t\tif (requiredProperty) {\n\t\t\tconst basePath = error.instancePath.replace(/^\\//, \"\").replace(/\\//g, \".\");\n\t\t\treturn basePath ? `${basePath}.${requiredProperty}` : requiredProperty;\n\t\t}\n\t}\n\tconst path = error.instancePath.replace(/^\\//, \"\").replace(/\\//g, \".\");\n\treturn path || \"root\";\n}\n\n/** Strip `//` line comments and trailing commas from JSON, leaving string literals untouched. */\nfunction stripJsonComments(input: string): string {\n\treturn input\n\t\t.replace(/\"(?:\\\\.|[^\"\\\\])*\"|\\/\\/[^\\n]*/g, (m) => (m[0] === '\"' ? m : \"\"))\n\t\t.replace(/\"(?:\\\\.|[^\"\\\\])*\"|,(\\s*[}\\]])/g, (m, tail) => tail ?? (m[0] === '\"' ? m : \"\"));\n}\n\n/** Provider override config (baseUrl, compat) without request auth/headers */\ninterface ProviderOverride {\n\tbaseUrl?: string;\n\tcompat?: Model<Api>[\"compat\"];\n}\n\ninterface ProviderRequestConfig {\n\tapiKey?: string;\n\theaders?: Record<string, string>;\n\textraBody?: Record<string, unknown>;\n\tauthHeader?: boolean;\n}\n\nexport type ResolvedRequestAuth =\n\t| {\n\t\t\tok: true;\n\t\t\tapiKey?: string;\n\t\t\theaders?: Record<string, string>;\n\t\t\textraBody?: Record<string, unknown>;\n\t\t\tupstreamModelId?: string;\n\t\t\tserviceTier?: ModelServiceTier;\n\t }\n\t| {\n\t\t\tok: false;\n\t\t\terror: string;\n\t };\n\n/** Result of loading custom models from models.json */\ninterface CustomModelsResult {\n\tmodels: Model<Api>[];\n\t/** Providers with baseUrl/headers/apiKey overrides for built-in models */\n\toverrides: Map<string, ProviderOverride>;\n\t/** Per-model overrides: provider -> modelId -> override */\n\tmodelOverrides: Map<string, Map<string, ModelOverride>>;\n\tdisabledProviders: Set<string>;\n\tmodelWhitelists: Map<string, Set<string>>;\n\tmodelBlacklists: Map<string, Set<string>>;\n\terror: string | undefined;\n}\n\nfunction emptyCustomModelsResult(error?: string): CustomModelsResult {\n\treturn {\n\t\tmodels: [],\n\t\toverrides: new Map(),\n\t\tmodelOverrides: new Map(),\n\t\tdisabledProviders: new Set(),\n\t\tmodelWhitelists: new Map(),\n\t\tmodelBlacklists: new Map(),\n\t\terror,\n\t};\n}\n\nfunction mergeCompat(\n\tbaseCompat: Model<Api>[\"compat\"],\n\toverrideCompat: ModelOverride[\"compat\"],\n): Model<Api>[\"compat\"] | undefined {\n\tif (!overrideCompat) return baseCompat;\n\n\tconst base = baseCompat as OpenAICompletionsCompat | OpenAIResponsesCompat | AnthropicMessagesCompat | undefined;\n\tconst override = overrideCompat as OpenAICompletionsCompat | OpenAIResponsesCompat | AnthropicMessagesCompat;\n\tconst merged = { ...base, ...override } as OpenAICompletionsCompat | OpenAIResponsesCompat | AnthropicMessagesCompat;\n\n\tconst baseCompletions = base as OpenAICompletionsCompat | undefined;\n\tconst overrideCompletions = override as OpenAICompletionsCompat;\n\tconst mergedCompletions = merged as OpenAICompletionsCompat;\n\n\tif (baseCompletions?.openRouterRouting || overrideCompletions.openRouterRouting) {\n\t\tmergedCompletions.openRouterRouting = {\n\t\t\t...baseCompletions?.openRouterRouting,\n\t\t\t...overrideCompletions.openRouterRouting,\n\t\t};\n\t}\n\n\tif (baseCompletions?.vercelGatewayRouting || overrideCompletions.vercelGatewayRouting) {\n\t\tmergedCompletions.vercelGatewayRouting = {\n\t\t\t...baseCompletions?.vercelGatewayRouting,\n\t\t\t...overrideCompletions.vercelGatewayRouting,\n\t\t};\n\t}\n\n\treturn merged as Model<Api>[\"compat\"];\n}\n\n/**\n * Deep merge a model override into a model.\n * Handles nested objects (cost, compat) by merging rather than replacing.\n */\nfunction applyModelOverride(model: Model<Api>, override: ModelOverride): Model<Api> {\n\tconst modelWithConfigMetadata = model as ModelWithConfigMetadata;\n\tconst result: ModelWithConfigMetadata = { ...modelWithConfigMetadata };\n\n\t// Simple field overrides\n\tif (override.name !== undefined) result.name = override.name;\n\tif (override.promptPreset !== undefined) result.promptPreset = override.promptPreset;\n\tif (override.reasoning !== undefined) result.reasoning = override.reasoning;\n\tif (override.thinkingLevelMap !== undefined) {\n\t\tresult.thinkingLevelMap =\n\t\t\toverride.thinkingLevelMapMode === \"replace\"\n\t\t\t\t? { ...override.thinkingLevelMap }\n\t\t\t\t: { ...modelWithConfigMetadata.thinkingLevelMap, ...override.thinkingLevelMap };\n\t}\n\tif (override.input !== undefined) result.input = override.input as Model<Api>[\"input\"];\n\tif (override.contextWindow !== undefined) result.contextWindow = override.contextWindow;\n\tif (override.maxTokens !== undefined) result.maxTokens = override.maxTokens;\n\n\t// Merge cost (partial override)\n\tif (override.cost) {\n\t\tresult.cost = {\n\t\t\tinput: override.cost.input ?? model.cost.input,\n\t\t\toutput: override.cost.output ?? model.cost.output,\n\t\t\tcacheRead: override.cost.cacheRead ?? model.cost.cacheRead,\n\t\t\tcacheWrite: override.cost.cacheWrite ?? model.cost.cacheWrite,\n\t\t};\n\t}\n\n\t// Deep merge compat\n\tresult.compat = mergeCompat(model.compat, override.compat);\n\n\treturn result;\n}\n\n/** Clear the config value command cache. Exported for testing. */\nexport const clearApiKeyCache = clearConfigValueCache;\n\n/**\n * Model registry - loads and manages models, resolves API keys via AuthStorage.\n */\nexport class ModelRegistry {\n\tprivate models: Model<Api>[] = [];\n\tprivate providerRequestConfigs: Map<string, ProviderRequestConfig> = new Map();\n\tprivate modelRequestHeaders: Map<string, Record<string, string>> = new Map();\n\tprivate modelRequestExtraBody: Map<string, Record<string, unknown>> = new Map();\n\tprivate modelRequestUpstreamIds: Map<string, string> = new Map();\n\tprivate modelRequestServiceTiers: Map<string, ModelServiceTier> = new Map();\n\tprivate registeredProviders: Map<string, ProviderConfigInput> = new Map();\n\tprivate loadError: string | undefined = undefined;\n\n\tprivate constructor(\n\t\treadonly authStorage: AuthStorage,\n\t\tprivate modelsJsonPath: string | undefined,\n\t) {\n\t\tthis.loadModels();\n\t}\n\n\tstatic create(authStorage: AuthStorage, modelsJsonPath: string = join(getAgentDir(), \"models.json\")): ModelRegistry {\n\t\treturn new ModelRegistry(authStorage, modelsJsonPath);\n\t}\n\n\tstatic inMemory(authStorage: AuthStorage): ModelRegistry {\n\t\treturn new ModelRegistry(authStorage, undefined);\n\t}\n\n\t/**\n\t * Reload models from disk (built-in + custom from models.json).\n\t */\n\trefresh(): void {\n\t\tthis.providerRequestConfigs.clear();\n\t\tthis.modelRequestHeaders.clear();\n\t\tthis.modelRequestExtraBody.clear();\n\t\tthis.modelRequestUpstreamIds.clear();\n\t\tthis.modelRequestServiceTiers.clear();\n\t\tthis.loadError = undefined;\n\n\t\t// Ensure dynamic API/OAuth registrations are rebuilt from current provider state.\n\t\tresetApiProviders();\n\t\tresetOAuthProviders();\n\n\t\tthis.loadModels();\n\n\t\tfor (const [providerName, config] of this.registeredProviders.entries()) {\n\t\t\tthis.applyProviderConfig(providerName, config);\n\t\t}\n\t}\n\n\t/**\n\t * Get any error from loading models.json (undefined if no error).\n\t */\n\tgetError(): string | undefined {\n\t\treturn this.loadError;\n\t}\n\n\tprivate loadModels(): void {\n\t\t// Load custom models and overrides from models.json\n\t\tconst {\n\t\t\tmodels: customModels,\n\t\t\toverrides,\n\t\t\tmodelOverrides,\n\t\t\tdisabledProviders,\n\t\t\tmodelWhitelists,\n\t\t\tmodelBlacklists,\n\t\t\terror,\n\t\t} = this.modelsJsonPath ? this.loadCustomModels(this.modelsJsonPath) : emptyCustomModelsResult();\n\n\t\tif (error) {\n\t\t\tthis.loadError = error;\n\t\t\t// Keep built-in models even if custom models failed to load\n\t\t}\n\n\t\tconst builtInModels = this.loadBuiltInModels(\n\t\t\toverrides,\n\t\t\tmodelOverrides,\n\t\t\tdisabledProviders,\n\t\t\tmodelWhitelists,\n\t\t\tmodelBlacklists,\n\t\t);\n\t\tlet combined = this.mergeCustomModels(builtInModels, customModels);\n\n\t\t// Let OAuth providers modify their models (e.g., update baseUrl)\n\t\tfor (const oauthProvider of this.authStorage.getOAuthProviders()) {\n\t\t\tconst cred = this.authStorage.get(oauthProvider.id);\n\t\t\tif (cred?.type === \"oauth\" && oauthProvider.modifyModels) {\n\t\t\t\tcombined = oauthProvider.modifyModels(combined, cred);\n\t\t\t}\n\t\t}\n\n\t\tthis.models = combined;\n\t}\n\n\t/** Load built-in models and apply provider/model overrides */\n\tprivate loadBuiltInModels(\n\t\toverrides: Map<string, ProviderOverride>,\n\t\tmodelOverrides: Map<string, Map<string, ModelOverride>>,\n\t\tdisabledProviders: Set<string>,\n\t\tmodelWhitelists: Map<string, Set<string>>,\n\t\tmodelBlacklists: Map<string, Set<string>>,\n\t): Model<Api>[] {\n\t\treturn getProviders().flatMap((provider) => {\n\t\t\tif (disabledProviders.has(provider)) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\tconst models = getModels(provider as KnownProvider) as Model<Api>[];\n\t\t\tconst providerOverride = overrides.get(provider);\n\t\t\tconst perModelOverrides = modelOverrides.get(provider);\n\t\t\tconst whitelist = modelWhitelists.get(provider);\n\t\t\tconst blacklist = modelBlacklists.get(provider);\n\n\t\t\treturn models.flatMap((m) => {\n\t\t\t\tif (whitelist && !whitelist.has(m.id)) return [];\n\t\t\t\tif (blacklist?.has(m.id)) return [];\n\n\t\t\t\tlet model = m;\n\n\t\t\t\t// Apply provider-level baseUrl/headers/compat override\n\t\t\t\tif (providerOverride) {\n\t\t\t\t\tmodel = {\n\t\t\t\t\t\t...model,\n\t\t\t\t\t\tbaseUrl: providerOverride.baseUrl ?? model.baseUrl,\n\t\t\t\t\t\tcompat: mergeCompat(model.compat, providerOverride.compat),\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\t// Apply per-model override\n\t\t\t\tconst modelOverride = perModelOverrides?.get(m.id);\n\t\t\t\tif (modelOverride) {\n\t\t\t\t\tmodel = applyModelOverride(model, modelOverride);\n\t\t\t\t}\n\n\t\t\t\treturn [model];\n\t\t\t});\n\t\t});\n\t}\n\n\t/** Merge custom models into built-in list by provider+id (custom wins on conflicts). */\n\tprivate mergeCustomModels(builtInModels: Model<Api>[], customModels: Model<Api>[]): Model<Api>[] {\n\t\tconst merged = [...builtInModels];\n\t\tfor (const customModel of customModels) {\n\t\t\tconst existingIndex = merged.findIndex((m) => m.provider === customModel.provider && m.id === customModel.id);\n\t\t\tif (existingIndex >= 0) {\n\t\t\t\tmerged[existingIndex] = customModel;\n\t\t\t} else {\n\t\t\t\tmerged.push(customModel);\n\t\t\t}\n\t\t}\n\t\treturn merged;\n\t}\n\n\tprivate loadCustomModels(modelsJsonPath: string): CustomModelsResult {\n\t\tif (!existsSync(modelsJsonPath)) {\n\t\t\treturn emptyCustomModelsResult();\n\t\t}\n\n\t\ttry {\n\t\t\tconst content = readFileSync(modelsJsonPath, \"utf-8\");\n\t\t\tconst parsed: unknown = JSON.parse(stripJsonComments(content));\n\n\t\t\tif (!validateModelsConfig.Check(parsed)) {\n\t\t\t\tconst errors =\n\t\t\t\t\tvalidateModelsConfig\n\t\t\t\t\t\t.Errors(parsed)\n\t\t\t\t\t\t.map((error) => ` - ${formatValidationPath(error)}: ${error.message}`)\n\t\t\t\t\t\t.join(\"\\n\") || \"Unknown schema error\";\n\t\t\t\treturn emptyCustomModelsResult(`Invalid models.json schema:\\n${errors}\\n\\nFile: ${modelsJsonPath}`);\n\t\t\t}\n\n\t\t\tconst config = parsed as ModelsConfig;\n\n\t\t\t// Additional validation\n\t\t\tthis.validateConfig(config);\n\n\t\t\tconst overrides = new Map<string, ProviderOverride>();\n\t\t\tconst modelOverrides = new Map<string, Map<string, ModelOverride>>();\n\t\t\tconst disabledProviders = new Set(config.disabledProviders ?? []);\n\t\t\tconst modelWhitelists = new Map<string, Set<string>>();\n\t\t\tconst modelBlacklists = new Map<string, Set<string>>();\n\n\t\t\tfor (const [providerName, providerConfig] of Object.entries(config.providers)) {\n\t\t\t\tif (providerConfig.disabled) {\n\t\t\t\t\tdisabledProviders.add(providerName);\n\t\t\t\t}\n\n\t\t\t\tif (providerConfig.whitelist) {\n\t\t\t\t\tmodelWhitelists.set(providerName, new Set(providerConfig.whitelist));\n\t\t\t\t}\n\t\t\t\tif (providerConfig.blacklist) {\n\t\t\t\t\tmodelBlacklists.set(providerName, new Set(providerConfig.blacklist));\n\t\t\t\t}\n\n\t\t\t\tif (providerConfig.baseUrl || providerConfig.compat) {\n\t\t\t\t\toverrides.set(providerName, {\n\t\t\t\t\t\tbaseUrl: providerConfig.baseUrl,\n\t\t\t\t\t\tcompat: providerConfig.compat,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tthis.storeProviderRequestConfig(providerName, providerConfig);\n\n\t\t\t\tif (providerConfig.modelOverrides) {\n\t\t\t\t\tmodelOverrides.set(providerName, new Map(Object.entries(providerConfig.modelOverrides)));\n\t\t\t\t\tfor (const [modelId, modelOverride] of Object.entries(providerConfig.modelOverrides)) {\n\t\t\t\t\t\tthis.storeModelHeaders(providerName, modelId, modelOverride.headers);\n\t\t\t\t\t\tthis.storeModelExtraBody(providerName, modelId, modelOverride.extraBody);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tmodels: this.parseModels(config, disabledProviders, modelWhitelists, modelBlacklists),\n\t\t\t\toverrides,\n\t\t\t\tmodelOverrides,\n\t\t\t\tdisabledProviders,\n\t\t\t\tmodelWhitelists,\n\t\t\t\tmodelBlacklists,\n\t\t\t\terror: undefined,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tif (error instanceof SyntaxError) {\n\t\t\t\treturn emptyCustomModelsResult(`Failed to parse models.json: ${error.message}\\n\\nFile: ${modelsJsonPath}`);\n\t\t\t}\n\t\t\treturn emptyCustomModelsResult(\n\t\t\t\t`Failed to load models.json: ${error instanceof Error ? error.message : error}\\n\\nFile: ${modelsJsonPath}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate validateConfig(config: ModelsConfig): void {\n\t\tconst builtInProviders = new Set<string>(getProviders());\n\n\t\tfor (const [providerName, providerConfig] of Object.entries(config.providers)) {\n\t\t\tif (providerConfig.disabled) continue;\n\n\t\t\tconst isBuiltIn = builtInProviders.has(providerName);\n\t\t\tconst hasProviderApi = !!providerConfig.api;\n\t\t\tconst models = providerConfig.models ?? [];\n\t\t\tconst hasModelOverrides =\n\t\t\t\tproviderConfig.modelOverrides && Object.keys(providerConfig.modelOverrides).length > 0;\n\n\t\t\tif (models.length === 0) {\n\t\t\t\t// Override-only config: needs baseUrl, headers, compat, modelOverrides, extraBody, or some combination.\n\t\t\t\tif (\n\t\t\t\t\t!providerConfig.baseUrl &&\n\t\t\t\t\t!providerConfig.headers &&\n\t\t\t\t\t!providerConfig.compat &&\n\t\t\t\t\t!hasModelOverrides &&\n\t\t\t\t\t!providerConfig.extraBody &&\n\t\t\t\t\t!providerConfig.whitelist &&\n\t\t\t\t\t!providerConfig.blacklist\n\t\t\t\t) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Provider ${providerName}: must specify \"baseUrl\", \"headers\", \"compat\", \"modelOverrides\", \"extraBody\", or \"models\".`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else if (!isBuiltIn) {\n\t\t\t\t// Non-built-in providers with custom models require endpoint + auth.\n\t\t\t\tif (!providerConfig.baseUrl) {\n\t\t\t\t\tthrow new Error(`Provider ${providerName}: \"baseUrl\" is required when defining custom models.`);\n\t\t\t\t}\n\t\t\t\tif (!providerConfig.apiKey) {\n\t\t\t\t\tthrow new Error(`Provider ${providerName}: \"apiKey\" is required when defining custom models.`);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Built-in providers with custom models: baseUrl/apiKey/api are optional,\n\t\t\t// inherited from built-in models. Auth comes from env vars / auth storage.\n\n\t\t\tfor (const modelDef of models) {\n\t\t\t\tconst hasModelApi = !!modelDef.api;\n\n\t\t\t\tif (!hasProviderApi && !hasModelApi && !isBuiltIn) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Provider ${providerName}, model ${modelDef.id}: no \"api\" specified. Set at provider or model level.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t// For built-in providers, api is optional — inherited from built-in models.\n\n\t\t\t\tif (!modelDef.id) throw new Error(`Provider ${providerName}: model missing \"id\"`);\n\t\t\t\t// Validate contextWindow/maxTokens only if provided (they have defaults)\n\t\t\t\tif (modelDef.contextWindow !== undefined && modelDef.contextWindow <= 0)\n\t\t\t\t\tthrow new Error(`Provider ${providerName}, model ${modelDef.id}: invalid contextWindow`);\n\t\t\t\tif (modelDef.maxTokens !== undefined && modelDef.maxTokens <= 0)\n\t\t\t\t\tthrow new Error(`Provider ${providerName}, model ${modelDef.id}: invalid maxTokens`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate parseModels(\n\t\tconfig: ModelsConfig,\n\t\tdisabledProviders: Set<string>,\n\t\tmodelWhitelists: Map<string, Set<string>>,\n\t\tmodelBlacklists: Map<string, Set<string>>,\n\t): Model<Api>[] {\n\t\tconst models: Model<Api>[] = [];\n\t\tconst builtInProviders = new Set<string>(getProviders());\n\n\t\t// Cache built-in defaults (api, baseUrl) per provider, extracted from first model.\n\t\tconst builtInDefaultsCache = new Map<string, { api: string; baseUrl: string }>();\n\t\tconst getBuiltInDefaults = (providerName: string): { api: string; baseUrl: string } | undefined => {\n\t\t\tif (!builtInProviders.has(providerName)) return undefined;\n\t\t\tif (builtInDefaultsCache.has(providerName)) return builtInDefaultsCache.get(providerName);\n\t\t\tconst builtIn = getModels(providerName as KnownProvider) as Model<Api>[];\n\t\t\tif (builtIn.length === 0) return undefined;\n\t\t\tconst defaults = { api: builtIn[0].api, baseUrl: builtIn[0].baseUrl };\n\t\t\tbuiltInDefaultsCache.set(providerName, defaults);\n\t\t\treturn defaults;\n\t\t};\n\n\t\tfor (const [providerName, providerConfig] of Object.entries(config.providers)) {\n\t\t\tif (disabledProviders.has(providerName)) continue;\n\n\t\t\tconst modelDefs = providerConfig.models ?? [];\n\t\t\tif (modelDefs.length === 0) continue; // Override-only, no custom models\n\n\t\t\tconst builtInDefaults = getBuiltInDefaults(providerName);\n\t\t\tconst whitelist = modelWhitelists.get(providerName);\n\t\t\tconst blacklist = modelBlacklists.get(providerName);\n\n\t\t\tfor (const modelDef of modelDefs) {\n\t\t\t\tif (whitelist && !whitelist.has(modelDef.id)) continue;\n\t\t\t\tif (blacklist?.has(modelDef.id)) continue;\n\n\t\t\t\tconst api = modelDef.api ?? providerConfig.api ?? builtInDefaults?.api;\n\t\t\t\tif (!api) continue;\n\n\t\t\t\tconst baseUrl = modelDef.baseUrl ?? providerConfig.baseUrl ?? builtInDefaults?.baseUrl;\n\t\t\t\tif (!baseUrl) continue;\n\n\t\t\t\tconst compat = mergeCompat(providerConfig.compat, modelDef.compat);\n\t\t\t\tthis.storeModelHeaders(providerName, modelDef.id, modelDef.headers);\n\t\t\t\tthis.storeModelExtraBody(providerName, modelDef.id, modelDef.extraBody);\n\t\t\t\tthis.storeModelUpstreamId(providerName, modelDef.id, modelDef.upstreamModelId);\n\t\t\t\tthis.storeModelServiceTier(providerName, modelDef.id, modelDef.serviceTier);\n\n\t\t\t\tconst defaultCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelDef.id,\n\t\t\t\t\tname: modelDef.name ?? modelDef.id,\n\t\t\t\t\tpromptPreset: modelDef.promptPreset,\n\t\t\t\t\tapi: api as Api,\n\t\t\t\t\tprovider: providerName,\n\t\t\t\t\tbaseUrl,\n\t\t\t\t\treasoning: modelDef.reasoning ?? false,\n\t\t\t\t\tthinkingLevelMap: modelDef.thinkingLevelMap,\n\t\t\t\t\tinput: (modelDef.input ?? [\"text\"]) as Model<Api>[\"input\"],\n\t\t\t\t\tcost: modelDef.cost ?? defaultCost,\n\t\t\t\t\tcontextWindow: modelDef.contextWindow ?? 128000,\n\t\t\t\t\tmaxTokens: modelDef.maxTokens ?? 16384,\n\t\t\t\t\theaders: undefined,\n\t\t\t\t\tcompat,\n\t\t\t\t} as Model<Api>);\n\t\t\t}\n\t\t}\n\n\t\treturn models;\n\t}\n\n\t/**\n\t * Get all models (built-in + custom).\n\t * If models.json had errors, returns only built-in models.\n\t */\n\tgetAll(): Model<Api>[] {\n\t\treturn this.models;\n\t}\n\n\t/**\n\t * Get only models that have auth configured.\n\t * This is a fast check that doesn't refresh OAuth tokens.\n\t */\n\tgetAvailable(): Model<Api>[] {\n\t\treturn this.models.filter((m) => this.hasConfiguredAuth(m));\n\t}\n\n\t/**\n\t * Find a model by provider and ID.\n\t */\n\tfind(provider: string, modelId: string): Model<Api> | undefined {\n\t\treturn this.models.find((m) => m.provider === provider && m.id === modelId);\n\t}\n\n\t/**\n\t * Get API key for a model.\n\t */\n\thasConfiguredAuth(model: Model<Api>): boolean {\n\t\treturn (\n\t\t\tthis.authStorage.hasAuth(model.provider) ||\n\t\t\tthis.providerRequestConfigs.get(model.provider)?.apiKey !== undefined\n\t\t);\n\t}\n\n\tprivate getModelRequestKey(provider: string, modelId: string): string {\n\t\treturn `${provider}:${modelId}`;\n\t}\n\n\tprivate storeProviderRequestConfig(\n\t\tproviderName: string,\n\t\tconfig: {\n\t\t\tapiKey?: string;\n\t\t\theaders?: Record<string, string>;\n\t\t\textraBody?: Record<string, unknown>;\n\t\t\tauthHeader?: boolean;\n\t\t},\n\t): void {\n\t\tif (!config.apiKey && !config.headers && !config.extraBody && !config.authHeader) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.providerRequestConfigs.set(providerName, {\n\t\t\tapiKey: config.apiKey,\n\t\t\theaders: config.headers,\n\t\t\textraBody: config.extraBody,\n\t\t\tauthHeader: config.authHeader,\n\t\t});\n\t}\n\n\tprivate storeModelHeaders(providerName: string, modelId: string, headers?: Record<string, string>): void {\n\t\tconst key = this.getModelRequestKey(providerName, modelId);\n\t\tif (!headers || Object.keys(headers).length === 0) {\n\t\t\tthis.modelRequestHeaders.delete(key);\n\t\t\treturn;\n\t\t}\n\t\tthis.modelRequestHeaders.set(key, headers);\n\t}\n\n\tprivate storeModelExtraBody(providerName: string, modelId: string, extraBody?: Record<string, unknown>): void {\n\t\tconst key = this.getModelRequestKey(providerName, modelId);\n\t\tif (!extraBody || Object.keys(extraBody).length === 0) {\n\t\t\tthis.modelRequestExtraBody.delete(key);\n\t\t\treturn;\n\t\t}\n\t\tthis.modelRequestExtraBody.set(key, extraBody);\n\t}\n\n\tprivate storeModelUpstreamId(providerName: string, modelId: string, upstreamModelId?: string): void {\n\t\tconst key = this.getModelRequestKey(providerName, modelId);\n\t\tif (!upstreamModelId) {\n\t\t\tthis.modelRequestUpstreamIds.delete(key);\n\t\t\treturn;\n\t\t}\n\t\tthis.modelRequestUpstreamIds.set(key, upstreamModelId);\n\t}\n\n\tprivate storeModelServiceTier(providerName: string, modelId: string, serviceTier?: ModelServiceTier): void {\n\t\tconst key = this.getModelRequestKey(providerName, modelId);\n\t\tif (!serviceTier) {\n\t\t\tthis.modelRequestServiceTiers.delete(key);\n\t\t\treturn;\n\t\t}\n\t\tthis.modelRequestServiceTiers.set(key, serviceTier);\n\t}\n\n\t/**\n\t * Get API key and request headers for a model.\n\t */\n\tasync getApiKeyAndHeaders(model: Model<Api>): Promise<ResolvedRequestAuth> {\n\t\ttry {\n\t\t\tconst providerConfig = this.providerRequestConfigs.get(model.provider);\n\t\t\tconst apiKeyFromAuthStorage = await this.authStorage.getApiKey(model.provider, { includeFallback: false });\n\t\t\tconst apiKey =\n\t\t\t\tapiKeyFromAuthStorage ??\n\t\t\t\t(providerConfig?.apiKey\n\t\t\t\t\t? resolveConfigValueOrThrow(providerConfig.apiKey, `API key for provider \"${model.provider}\"`)\n\t\t\t\t\t: undefined);\n\n\t\t\tconst providerHeaders = resolveHeadersOrThrow(providerConfig?.headers, `provider \"${model.provider}\"`);\n\t\t\tconst modelRequestKey = this.getModelRequestKey(model.provider, model.id);\n\t\t\tconst modelHeaders = resolveHeadersOrThrow(\n\t\t\t\tthis.modelRequestHeaders.get(modelRequestKey),\n\t\t\t\t`model \"${model.provider}/${model.id}\"`,\n\t\t\t);\n\n\t\t\tlet headers =\n\t\t\t\tmodel.headers || providerHeaders || modelHeaders\n\t\t\t\t\t? { ...model.headers, ...providerHeaders, ...modelHeaders }\n\t\t\t\t\t: undefined;\n\n\t\t\tif (providerConfig?.authHeader) {\n\t\t\t\tif (!apiKey) {\n\t\t\t\t\treturn { ok: false, error: `No API key found for \"${model.provider}\"` };\n\t\t\t\t}\n\t\t\t\theaders = { ...headers, Authorization: `Bearer ${apiKey}` };\n\t\t\t}\n\n\t\t\tconst providerExtraBody = providerConfig?.extraBody;\n\t\t\tconst modelExtraBody = this.modelRequestExtraBody.get(modelRequestKey);\n\t\t\tconst extraBody =\n\t\t\t\tproviderExtraBody || modelExtraBody ? { ...providerExtraBody, ...modelExtraBody } : undefined;\n\t\t\tconst upstreamModelId = this.modelRequestUpstreamIds.get(modelRequestKey);\n\t\t\tconst serviceTier = this.modelRequestServiceTiers.get(modelRequestKey);\n\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tapiKey,\n\t\t\t\theaders: headers && Object.keys(headers).length > 0 ? headers : undefined,\n\t\t\t\textraBody: extraBody && Object.keys(extraBody).length > 0 ? extraBody : undefined,\n\t\t\t\tupstreamModelId,\n\t\t\t\tserviceTier,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Return auth status for a provider, including request auth configured in models.json.\n\t * This intentionally does not execute command-backed config values.\n\t */\n\tgetProviderAuthStatus(provider: string): AuthStatus {\n\t\tconst authStatus = this.authStorage.getAuthStatus(provider);\n\t\tif (authStatus.source) {\n\t\t\treturn authStatus;\n\t\t}\n\n\t\tconst providerApiKey = this.providerRequestConfigs.get(provider)?.apiKey;\n\t\tif (!providerApiKey) {\n\t\t\treturn authStatus;\n\t\t}\n\n\t\tif (providerApiKey.startsWith(\"!\")) {\n\t\t\treturn { configured: true, source: \"models_json_command\" };\n\t\t}\n\n\t\tif (process.env[providerApiKey]) {\n\t\t\treturn { configured: true, source: \"environment\", label: providerApiKey };\n\t\t}\n\n\t\treturn { configured: true, source: \"models_json_key\" };\n\t}\n\n\t/**\n\t * Get display name for a provider.\n\t */\n\tgetProviderDisplayName(provider: string): string {\n\t\tconst registeredProvider = this.registeredProviders.get(provider);\n\t\tconst oauthProvider = this.authStorage.getOAuthProviders().find((p) => p.id === provider);\n\n\t\treturn (\n\t\t\tregisteredProvider?.name ??\n\t\t\tregisteredProvider?.oauth?.name ??\n\t\t\toauthProvider?.name ??\n\t\t\tBUILT_IN_PROVIDER_DISPLAY_NAMES[provider] ??\n\t\t\tprovider\n\t\t);\n\t}\n\n\t/**\n\t * Get API key for a provider.\n\t */\n\tasync getApiKeyForProvider(provider: string): Promise<string | undefined> {\n\t\tconst apiKey = await this.authStorage.getApiKey(provider, { includeFallback: false });\n\t\tif (apiKey !== undefined) {\n\t\t\treturn apiKey;\n\t\t}\n\n\t\tconst providerApiKey = this.providerRequestConfigs.get(provider)?.apiKey;\n\t\treturn providerApiKey ? resolveConfigValueUncached(providerApiKey) : undefined;\n\t}\n\n\t/**\n\t * Check if a model is using OAuth credentials (subscription).\n\t */\n\tisUsingOAuth(model: Model<Api>): boolean {\n\t\tconst cred = this.authStorage.get(model.provider);\n\t\treturn cred?.type === \"oauth\";\n\t}\n\n\t/**\n\t * Register a provider dynamically (from extensions).\n\t *\n\t * If provider has models: replaces all existing models for this provider.\n\t * If provider has only baseUrl/headers: overrides existing models' URLs.\n\t * If provider has oauth: registers OAuth provider for /login support.\n\t */\n\tregisterProvider(providerName: string, config: ProviderConfigInput): void {\n\t\tthis.validateProviderConfig(providerName, config);\n\t\tthis.applyProviderConfig(providerName, config);\n\t\tthis.upsertRegisteredProvider(providerName, config);\n\t}\n\n\t/**\n\t * Unregister a previously registered provider.\n\t *\n\t * Removes the provider from the registry and reloads models from disk so that\n\t * built-in models overridden by this provider are restored to their original state.\n\t * Also resets dynamic OAuth and API stream registrations before reapplying\n\t * remaining dynamic providers.\n\t * Has no effect if the provider was never registered.\n\t */\n\tunregisterProvider(providerName: string): void {\n\t\tif (!this.registeredProviders.has(providerName)) return;\n\t\tthis.registeredProviders.delete(providerName);\n\t\tthis.refresh();\n\t}\n\n\t/**\n\t * Upsert a provider config into registeredProviders.\n\t * If the provider is already registered, defined values in the incoming config\n\t * override existing ones; undefined values are preserved from the stored config.\n\t * If the provider is not registered, the incoming config is stored as-is.\n\t */\n\tprivate upsertRegisteredProvider(providerName: string, config: ProviderConfigInput): void {\n\t\tconst existing = this.registeredProviders.get(providerName);\n\t\tif (!existing) {\n\t\t\tthis.registeredProviders.set(providerName, config);\n\t\t\treturn;\n\t\t}\n\t\tfor (const k of Object.keys(config) as (keyof ProviderConfigInput)[]) {\n\t\t\tif (config[k] !== undefined) {\n\t\t\t\t(existing as Record<string, unknown>)[k] = config[k];\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate validateProviderConfig(providerName: string, config: ProviderConfigInput): void {\n\t\tif (config.streamSimple && !config.api) {\n\t\t\tthrow new Error(`Provider ${providerName}: \"api\" is required when registering streamSimple.`);\n\t\t}\n\n\t\tif (!config.models || config.models.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!config.baseUrl) {\n\t\t\tthrow new Error(`Provider ${providerName}: \"baseUrl\" is required when defining models.`);\n\t\t}\n\t\tif (!config.apiKey && !config.oauth) {\n\t\t\tthrow new Error(`Provider ${providerName}: \"apiKey\" or \"oauth\" is required when defining models.`);\n\t\t}\n\n\t\tfor (const modelDef of config.models) {\n\t\t\tconst api = modelDef.api || config.api;\n\t\t\tif (!api) {\n\t\t\t\tthrow new Error(`Provider ${providerName}, model ${modelDef.id}: no \"api\" specified.`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate applyProviderConfig(providerName: string, config: ProviderConfigInput): void {\n\t\t// Register OAuth provider if provided\n\t\tif (config.oauth) {\n\t\t\t// Ensure the OAuth provider ID matches the provider name\n\t\t\tconst oauthProvider: OAuthProviderInterface = {\n\t\t\t\t...config.oauth,\n\t\t\t\tid: providerName,\n\t\t\t};\n\t\t\tregisterOAuthProvider(oauthProvider);\n\t\t}\n\n\t\tif (config.streamSimple) {\n\t\t\tconst streamSimple = config.streamSimple;\n\t\t\tregisterApiProvider(\n\t\t\t\t{\n\t\t\t\t\tapi: config.api!,\n\t\t\t\t\tstream: (model, context, options) => streamSimple(model, context, options as SimpleStreamOptions),\n\t\t\t\t\tstreamSimple,\n\t\t\t\t},\n\t\t\t\t`provider:${providerName}`,\n\t\t\t);\n\t\t}\n\n\t\tthis.storeProviderRequestConfig(providerName, config);\n\n\t\tif (config.models && config.models.length > 0) {\n\t\t\t// Full replacement: remove existing models for this provider\n\t\t\tthis.models = this.models.filter((m) => m.provider !== providerName);\n\n\t\t\t// Parse and add new models\n\t\t\tfor (const modelDef of config.models) {\n\t\t\t\tconst api = modelDef.api || config.api;\n\t\t\t\tthis.storeModelHeaders(providerName, modelDef.id, modelDef.headers);\n\t\t\t\tthis.storeModelExtraBody(providerName, modelDef.id, modelDef.extraBody);\n\t\t\t\tthis.storeModelUpstreamId(providerName, modelDef.id, modelDef.upstreamModelId);\n\t\t\t\tthis.storeModelServiceTier(providerName, modelDef.id, modelDef.serviceTier);\n\n\t\t\t\tthis.models.push({\n\t\t\t\t\tid: modelDef.id,\n\t\t\t\t\tname: modelDef.name,\n\t\t\t\t\tapi: api as Api,\n\t\t\t\t\tprovider: providerName,\n\t\t\t\t\tbaseUrl: modelDef.baseUrl ?? config.baseUrl!,\n\t\t\t\t\treasoning: modelDef.reasoning,\n\t\t\t\t\tthinkingLevelMap: modelDef.thinkingLevelMap,\n\t\t\t\t\tinput: modelDef.input as Model<Api>[\"input\"],\n\t\t\t\t\tcost: modelDef.cost,\n\t\t\t\t\tcontextWindow: modelDef.contextWindow,\n\t\t\t\t\tmaxTokens: modelDef.maxTokens,\n\t\t\t\t\theaders: undefined,\n\t\t\t\t\tcompat: modelDef.compat,\n\t\t\t\t} as Model<Api>);\n\t\t\t}\n\n\t\t\t// Apply OAuth modifyModels if credentials exist (e.g., to update baseUrl)\n\t\t\tif (config.oauth?.modifyModels) {\n\t\t\t\tconst cred = this.authStorage.get(providerName);\n\t\t\t\tif (cred?.type === \"oauth\") {\n\t\t\t\t\tthis.models = config.oauth.modifyModels(this.models, cred);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (config.baseUrl || config.headers || config.extraBody) {\n\t\t\t// Override-only: update baseUrl for existing models. Request headers and extraBody are resolved per request.\n\t\t\tthis.models = this.models.map((m) => {\n\t\t\t\tif (m.provider !== providerName) return m;\n\t\t\t\treturn {\n\t\t\t\t\t...m,\n\t\t\t\t\tbaseUrl: config.baseUrl ?? m.baseUrl,\n\t\t\t\t};\n\t\t\t});\n\t\t}\n\t}\n}\n\n/**\n * Input type for registerProvider API.\n */\nexport interface ProviderConfigInput {\n\tname?: string;\n\tbaseUrl?: string;\n\tapiKey?: string;\n\tapi?: Api;\n\tstreamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;\n\theaders?: Record<string, string>;\n\textraBody?: Record<string, unknown>;\n\tauthHeader?: boolean;\n\t/** OAuth provider for /login support */\n\toauth?: Omit<OAuthProviderInterface, \"id\">;\n\tmodels?: Array<{\n\t\tid: string;\n\t\tname: string;\n\t\tupstreamModelId?: string;\n\t\tserviceTier?: ModelServiceTier;\n\t\tapi?: Api;\n\t\tbaseUrl?: string;\n\t\treasoning: boolean;\n\t\tthinkingLevelMap?: ThinkingLevelMap;\n\t\tinput: Model<Api>[\"input\"];\n\t\tcost: { input: number; output: number; cacheRead: number; cacheWrite: number };\n\t\tcontextWindow: number;\n\t\tmaxTokens: number;\n\t\theaders?: Record<string, string>;\n\t\textraBody?: Record<string, unknown>;\n\t\tcompat?: Model<Api>[\"compat\"];\n\t}>;\n}\n"]}
1
+ {"version":3,"file":"model-registry.d.ts","sourceRoot":"","sources":["../../src/core/model-registry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Model registry - manages built-in and custom models, provides API key resolution.\n */\n\nimport {\n\ttype AnthropicMessagesCompat,\n\ttype Api,\n\ttype AssistantMessageEventStream,\n\ttype Context,\n\tgetModels,\n\tgetProviders,\n\ttype KnownProvider,\n\ttype Model,\n\ttype OAuthProviderInterface,\n\ttype OpenAICompletionsCompat,\n\ttype OpenAIResponsesCompat,\n\tregisterApiProvider,\n\tresetApiProviders,\n\ttype SimpleStreamOptions,\n} from \"@earendil-works/pi-ai\";\nimport { registerOAuthProvider, resetOAuthProviders } from \"@earendil-works/pi-ai/oauth\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { Compile } from \"typebox/compile\";\nimport type { TLocalizedValidationError } from \"typebox/error\";\nimport { getAgentDir } from \"../config.js\";\nimport type { AuthStatus, AuthStorage } from \"./auth-storage.js\";\nimport { BUILT_IN_PROVIDER_DISPLAY_NAMES } from \"./provider-display-names.js\";\nimport {\n\tclearConfigValueCache,\n\tresolveConfigValueOrThrow,\n\tresolveConfigValueUncached,\n\tresolveHeadersOrThrow,\n} from \"./resolve-config-value.js\";\n\n// Schema for OpenRouter routing preferences\nconst PercentileCutoffsSchema = Type.Object({\n\tp50: Type.Optional(Type.Number()),\n\tp75: Type.Optional(Type.Number()),\n\tp90: Type.Optional(Type.Number()),\n\tp99: Type.Optional(Type.Number()),\n});\n\nconst OpenRouterRoutingSchema = Type.Object({\n\tallow_fallbacks: Type.Optional(Type.Boolean()),\n\trequire_parameters: Type.Optional(Type.Boolean()),\n\tdata_collection: Type.Optional(Type.Union([Type.Literal(\"deny\"), Type.Literal(\"allow\")])),\n\tzdr: Type.Optional(Type.Boolean()),\n\tenforce_distillable_text: Type.Optional(Type.Boolean()),\n\torder: Type.Optional(Type.Array(Type.String())),\n\tonly: Type.Optional(Type.Array(Type.String())),\n\tignore: Type.Optional(Type.Array(Type.String())),\n\tquantizations: Type.Optional(Type.Array(Type.String())),\n\tsort: Type.Optional(\n\t\tType.Union([\n\t\t\tType.String(),\n\t\t\tType.Object({\n\t\t\t\tby: Type.Optional(Type.String()),\n\t\t\t\tpartition: Type.Optional(Type.Union([Type.String(), Type.Null()])),\n\t\t\t}),\n\t\t]),\n\t),\n\tmax_price: Type.Optional(\n\t\tType.Object({\n\t\t\tprompt: Type.Optional(Type.Union([Type.Number(), Type.String()])),\n\t\t\tcompletion: Type.Optional(Type.Union([Type.Number(), Type.String()])),\n\t\t\timage: Type.Optional(Type.Union([Type.Number(), Type.String()])),\n\t\t\taudio: Type.Optional(Type.Union([Type.Number(), Type.String()])),\n\t\t\trequest: Type.Optional(Type.Union([Type.Number(), Type.String()])),\n\t\t}),\n\t),\n\tpreferred_min_throughput: Type.Optional(Type.Union([Type.Number(), PercentileCutoffsSchema])),\n\tpreferred_max_latency: Type.Optional(Type.Union([Type.Number(), PercentileCutoffsSchema])),\n});\n\n// Schema for Vercel AI Gateway routing preferences\nconst VercelGatewayRoutingSchema = Type.Object({\n\tonly: Type.Optional(Type.Array(Type.String())),\n\torder: Type.Optional(Type.Array(Type.String())),\n});\n\n// Schema for thinking level support and provider-specific values\nconst ThinkingLevelMapValueSchema = Type.Union([Type.String(), Type.Null()]);\nconst ThinkingLevelMapSchema = Type.Object({\n\toff: Type.Optional(ThinkingLevelMapValueSchema),\n\tminimal: Type.Optional(ThinkingLevelMapValueSchema),\n\tlow: Type.Optional(ThinkingLevelMapValueSchema),\n\tmedium: Type.Optional(ThinkingLevelMapValueSchema),\n\thigh: Type.Optional(ThinkingLevelMapValueSchema),\n\txhigh: Type.Optional(ThinkingLevelMapValueSchema),\n\tmax: Type.Optional(ThinkingLevelMapValueSchema),\n});\n\nconst OpenAICompletionsCompatSchema = Type.Object({\n\tsupportsStore: Type.Optional(Type.Boolean()),\n\tsupportsDeveloperRole: Type.Optional(Type.Boolean()),\n\tsupportsReasoningEffort: Type.Optional(Type.Boolean()),\n\tsupportsUsageInStreaming: Type.Optional(Type.Boolean()),\n\tmaxTokensField: Type.Optional(Type.Union([Type.Literal(\"max_completion_tokens\"), Type.Literal(\"max_tokens\")])),\n\trequiresToolResultName: Type.Optional(Type.Boolean()),\n\trequiresAssistantAfterToolResult: Type.Optional(Type.Boolean()),\n\trequiresThinkingAsText: Type.Optional(Type.Boolean()),\n\trequiresReasoningContentOnAssistantMessages: Type.Optional(Type.Boolean()),\n\tthinkingFormat: Type.Optional(\n\t\tType.Union([\n\t\t\tType.Literal(\"openai\"),\n\t\t\tType.Literal(\"openrouter\"),\n\t\t\tType.Literal(\"together\"),\n\t\t\tType.Literal(\"deepseek\"),\n\t\t\tType.Literal(\"zai\"),\n\t\t\tType.Literal(\"qwen\"),\n\t\t\tType.Literal(\"qwen-chat-template\"),\n\t\t]),\n\t),\n\tcacheControlFormat: Type.Optional(Type.Literal(\"anthropic\")),\n\topenRouterRouting: Type.Optional(OpenRouterRoutingSchema),\n\tvercelGatewayRouting: Type.Optional(VercelGatewayRoutingSchema),\n\tsupportsStrictMode: Type.Optional(Type.Boolean()),\n\ttoolCallFormat: Type.Optional(\n\t\tType.Union([\n\t\t\tType.Literal(\"hermes\"),\n\t\t\tType.Literal(\"gemma4\"),\n\t\t\tType.Literal(\"gemma4-delimiter\"),\n\t\t\tType.Literal(\"xml\"),\n\t\t\tType.Literal(\"yaml-xml\"),\n\t\t]),\n\t),\n\tsupportsLongCacheRetention: Type.Optional(Type.Boolean()),\n});\n\nconst OpenAIResponsesCompatSchema = Type.Object({\n\tsendSessionIdHeader: Type.Optional(Type.Boolean()),\n\tsupportsLongCacheRetention: Type.Optional(Type.Boolean()),\n\tsupportsWebSocket: Type.Optional(Type.Boolean()),\n\tsupportsWebSearchPreview: Type.Optional(Type.Boolean()),\n});\n\nconst AnthropicMessagesCompatSchema = Type.Object({\n\tsupportsEagerToolInputStreaming: Type.Optional(Type.Boolean()),\n\tsupportsLongCacheRetention: Type.Optional(Type.Boolean()),\n\tsupportsDisabledThinking: Type.Optional(Type.Boolean()),\n});\n\nconst ProviderCompatSchema = Type.Union([\n\tOpenAICompletionsCompatSchema,\n\tOpenAIResponsesCompatSchema,\n\tAnthropicMessagesCompatSchema,\n]);\n\n// Schema for custom model definition\n// Most fields are optional with sensible defaults for local models (Ollama, LM Studio, etc.)\nconst ExtraBodySchema = Type.Record(Type.String(), Type.Unknown());\nconst ServiceTierSchema = Type.Union([Type.Literal(\"auto\"), Type.Literal(\"flex\"), Type.Literal(\"priority\")]);\ntype ModelServiceTier = Static<typeof ServiceTierSchema>;\n\nconst ModelDefinitionSchema = Type.Object({\n\tid: Type.String({ minLength: 1 }),\n\tname: Type.Optional(Type.String({ minLength: 1 })),\n\tupstreamModelId: Type.Optional(Type.String({ minLength: 1 })),\n\tserviceTier: Type.Optional(ServiceTierSchema),\n\tpromptPreset: Type.Optional(Type.String({ minLength: 1 })),\n\tapi: Type.Optional(Type.String({ minLength: 1 })),\n\tbaseUrl: Type.Optional(Type.String({ minLength: 1 })),\n\treasoning: Type.Optional(Type.Boolean()),\n\tthinkingLevelMap: Type.Optional(ThinkingLevelMapSchema),\n\tinput: Type.Optional(Type.Array(Type.Union([Type.Literal(\"text\"), Type.Literal(\"image\"), Type.Literal(\"video\")]))),\n\tcost: Type.Optional(\n\t\tType.Object({\n\t\t\tinput: Type.Number(),\n\t\t\toutput: Type.Number(),\n\t\t\tcacheRead: Type.Number(),\n\t\t\tcacheWrite: Type.Number(),\n\t\t}),\n\t),\n\tcontextWindow: Type.Optional(Type.Number()),\n\tmaxTokens: Type.Optional(Type.Number()),\n\theaders: Type.Optional(Type.Record(Type.String(), Type.String())),\n\textraBody: Type.Optional(ExtraBodySchema),\n\tcompat: Type.Optional(ProviderCompatSchema),\n});\n\n// Schema for per-model overrides (all fields optional, merged with built-in model)\nconst ModelOverrideSchema = Type.Object({\n\tname: Type.Optional(Type.String({ minLength: 1 })),\n\tpromptPreset: Type.Optional(Type.String({ minLength: 1 })),\n\treasoning: Type.Optional(Type.Boolean()),\n\tthinkingLevelMap: Type.Optional(ThinkingLevelMapSchema),\n\tthinkingLevelMapMode: Type.Optional(Type.Union([Type.Literal(\"merge\"), Type.Literal(\"replace\")])),\n\tinput: Type.Optional(Type.Array(Type.Union([Type.Literal(\"text\"), Type.Literal(\"image\"), Type.Literal(\"video\")]))),\n\tcost: Type.Optional(\n\t\tType.Object({\n\t\t\tinput: Type.Optional(Type.Number()),\n\t\t\toutput: Type.Optional(Type.Number()),\n\t\t\tcacheRead: Type.Optional(Type.Number()),\n\t\t\tcacheWrite: Type.Optional(Type.Number()),\n\t\t}),\n\t),\n\tcontextWindow: Type.Optional(Type.Number()),\n\tmaxTokens: Type.Optional(Type.Number()),\n\theaders: Type.Optional(Type.Record(Type.String(), Type.String())),\n\textraBody: Type.Optional(ExtraBodySchema),\n\tcompat: Type.Optional(ProviderCompatSchema),\n});\n\ntype ModelOverride = Static<typeof ModelOverrideSchema>;\ntype ThinkingLevelMap = Static<typeof ThinkingLevelMapSchema>;\ntype ModelWithConfigMetadata = Model<Api> & { promptPreset?: string; thinkingLevelMap?: ThinkingLevelMap };\n\nconst ProviderConfigSchema = Type.Object({\n\tname: Type.Optional(Type.String({ minLength: 1 })),\n\tdisabled: Type.Optional(Type.Boolean()),\n\tbaseUrl: Type.Optional(Type.String({ minLength: 1 })),\n\tapiKey: Type.Optional(Type.String({ minLength: 1 })),\n\tapi: Type.Optional(Type.String({ minLength: 1 })),\n\theaders: Type.Optional(Type.Record(Type.String(), Type.String())),\n\textraBody: Type.Optional(ExtraBodySchema),\n\tcompat: Type.Optional(ProviderCompatSchema),\n\tauthHeader: Type.Optional(Type.Boolean()),\n\twhitelist: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),\n\tblacklist: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),\n\tmodels: Type.Optional(Type.Array(ModelDefinitionSchema)),\n\tmodelOverrides: Type.Optional(Type.Record(Type.String(), ModelOverrideSchema)),\n});\n\nconst ModelsConfigSchema = Type.Object({\n\tdisabledProviders: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),\n\tproviders: Type.Record(Type.String(), ProviderConfigSchema),\n});\n\nconst validateModelsConfig = Compile(ModelsConfigSchema);\n\ntype ModelsConfig = Static<typeof ModelsConfigSchema>;\n\nfunction formatValidationPath(error: TLocalizedValidationError): string {\n\tif (error.keyword === \"required\") {\n\t\tconst requiredProperties = (error.params as { requiredProperties?: string[] }).requiredProperties;\n\t\tconst requiredProperty = requiredProperties?.[0];\n\t\tif (requiredProperty) {\n\t\t\tconst basePath = error.instancePath.replace(/^\\//, \"\").replace(/\\//g, \".\");\n\t\t\treturn basePath ? `${basePath}.${requiredProperty}` : requiredProperty;\n\t\t}\n\t}\n\tconst path = error.instancePath.replace(/^\\//, \"\").replace(/\\//g, \".\");\n\treturn path || \"root\";\n}\n\n/** Strip `//` line comments and trailing commas from JSON, leaving string literals untouched. */\nfunction stripJsonComments(input: string): string {\n\treturn input\n\t\t.replace(/\"(?:\\\\.|[^\"\\\\])*\"|\\/\\/[^\\n]*/g, (m) => (m[0] === '\"' ? m : \"\"))\n\t\t.replace(/\"(?:\\\\.|[^\"\\\\])*\"|,(\\s*[}\\]])/g, (m, tail) => tail ?? (m[0] === '\"' ? m : \"\"));\n}\n\n/** Provider override config (baseUrl, compat) without request auth/headers */\ninterface ProviderOverride {\n\tbaseUrl?: string;\n\tcompat?: Model<Api>[\"compat\"];\n}\n\ninterface ProviderRequestConfig {\n\tapiKey?: string;\n\theaders?: Record<string, string>;\n\textraBody?: Record<string, unknown>;\n\tauthHeader?: boolean;\n}\n\nexport type ResolvedRequestAuth =\n\t| {\n\t\t\tok: true;\n\t\t\tapiKey?: string;\n\t\t\theaders?: Record<string, string>;\n\t\t\textraBody?: Record<string, unknown>;\n\t\t\tupstreamModelId?: string;\n\t\t\tserviceTier?: ModelServiceTier;\n\t }\n\t| {\n\t\t\tok: false;\n\t\t\terror: string;\n\t };\n\n/** Result of loading custom models from models.json */\ninterface CustomModelsResult {\n\tmodels: Model<Api>[];\n\t/** Providers with baseUrl/headers/apiKey overrides for built-in models */\n\toverrides: Map<string, ProviderOverride>;\n\t/** Per-model overrides: provider -> modelId -> override */\n\tmodelOverrides: Map<string, Map<string, ModelOverride>>;\n\tdisabledProviders: Set<string>;\n\tmodelWhitelists: Map<string, Set<string>>;\n\tmodelBlacklists: Map<string, Set<string>>;\n\terror: string | undefined;\n}\n\nfunction emptyCustomModelsResult(error?: string): CustomModelsResult {\n\treturn {\n\t\tmodels: [],\n\t\toverrides: new Map(),\n\t\tmodelOverrides: new Map(),\n\t\tdisabledProviders: new Set(),\n\t\tmodelWhitelists: new Map(),\n\t\tmodelBlacklists: new Map(),\n\t\terror,\n\t};\n}\n\nfunction mergeCompat(\n\tbaseCompat: Model<Api>[\"compat\"],\n\toverrideCompat: ModelOverride[\"compat\"],\n): Model<Api>[\"compat\"] | undefined {\n\tif (!overrideCompat) return baseCompat;\n\n\tconst base = baseCompat as OpenAICompletionsCompat | OpenAIResponsesCompat | AnthropicMessagesCompat | undefined;\n\tconst override = overrideCompat as OpenAICompletionsCompat | OpenAIResponsesCompat | AnthropicMessagesCompat;\n\tconst merged = { ...base, ...override } as OpenAICompletionsCompat | OpenAIResponsesCompat | AnthropicMessagesCompat;\n\n\tconst baseCompletions = base as OpenAICompletionsCompat | undefined;\n\tconst overrideCompletions = override as OpenAICompletionsCompat;\n\tconst mergedCompletions = merged as OpenAICompletionsCompat;\n\n\tif (baseCompletions?.openRouterRouting || overrideCompletions.openRouterRouting) {\n\t\tmergedCompletions.openRouterRouting = {\n\t\t\t...baseCompletions?.openRouterRouting,\n\t\t\t...overrideCompletions.openRouterRouting,\n\t\t};\n\t}\n\n\tif (baseCompletions?.vercelGatewayRouting || overrideCompletions.vercelGatewayRouting) {\n\t\tmergedCompletions.vercelGatewayRouting = {\n\t\t\t...baseCompletions?.vercelGatewayRouting,\n\t\t\t...overrideCompletions.vercelGatewayRouting,\n\t\t};\n\t}\n\n\treturn merged as Model<Api>[\"compat\"];\n}\n\n/**\n * Deep merge a model override into a model.\n * Handles nested objects (cost, compat) by merging rather than replacing.\n */\nfunction applyModelOverride(model: Model<Api>, override: ModelOverride): Model<Api> {\n\tconst modelWithConfigMetadata = model as ModelWithConfigMetadata;\n\tconst result: ModelWithConfigMetadata = { ...modelWithConfigMetadata };\n\n\t// Simple field overrides\n\tif (override.name !== undefined) result.name = override.name;\n\tif (override.promptPreset !== undefined) result.promptPreset = override.promptPreset;\n\tif (override.reasoning !== undefined) result.reasoning = override.reasoning;\n\tif (override.thinkingLevelMap !== undefined) {\n\t\tresult.thinkingLevelMap =\n\t\t\toverride.thinkingLevelMapMode === \"replace\"\n\t\t\t\t? { ...override.thinkingLevelMap }\n\t\t\t\t: { ...modelWithConfigMetadata.thinkingLevelMap, ...override.thinkingLevelMap };\n\t}\n\tif (override.input !== undefined) result.input = override.input as Model<Api>[\"input\"];\n\tif (override.contextWindow !== undefined) result.contextWindow = override.contextWindow;\n\tif (override.maxTokens !== undefined) result.maxTokens = override.maxTokens;\n\n\t// Merge cost (partial override)\n\tif (override.cost) {\n\t\tresult.cost = {\n\t\t\tinput: override.cost.input ?? model.cost.input,\n\t\t\toutput: override.cost.output ?? model.cost.output,\n\t\t\tcacheRead: override.cost.cacheRead ?? model.cost.cacheRead,\n\t\t\tcacheWrite: override.cost.cacheWrite ?? model.cost.cacheWrite,\n\t\t};\n\t}\n\n\t// Deep merge compat\n\tresult.compat = mergeCompat(model.compat, override.compat);\n\n\treturn result;\n}\n\n/** Clear the config value command cache. Exported for testing. */\nexport const clearApiKeyCache = clearConfigValueCache;\n\n/**\n * Model registry - loads and manages models, resolves API keys via AuthStorage.\n */\nexport class ModelRegistry {\n\tprivate models: Model<Api>[] = [];\n\tprivate providerRequestConfigs: Map<string, ProviderRequestConfig> = new Map();\n\tprivate modelRequestHeaders: Map<string, Record<string, string>> = new Map();\n\tprivate modelRequestExtraBody: Map<string, Record<string, unknown>> = new Map();\n\tprivate modelRequestUpstreamIds: Map<string, string> = new Map();\n\tprivate modelRequestServiceTiers: Map<string, ModelServiceTier> = new Map();\n\tprivate registeredProviders: Map<string, ProviderConfigInput> = new Map();\n\tprivate loadError: string | undefined = undefined;\n\n\tprivate constructor(\n\t\treadonly authStorage: AuthStorage,\n\t\tprivate modelsJsonPath: string | undefined,\n\t) {\n\t\tthis.loadModels();\n\t}\n\n\tstatic create(authStorage: AuthStorage, modelsJsonPath: string = join(getAgentDir(), \"models.json\")): ModelRegistry {\n\t\treturn new ModelRegistry(authStorage, modelsJsonPath);\n\t}\n\n\tstatic inMemory(authStorage: AuthStorage): ModelRegistry {\n\t\treturn new ModelRegistry(authStorage, undefined);\n\t}\n\n\t/**\n\t * Reload models from disk (built-in + custom from models.json).\n\t */\n\trefresh(): void {\n\t\tthis.providerRequestConfigs.clear();\n\t\tthis.modelRequestHeaders.clear();\n\t\tthis.modelRequestExtraBody.clear();\n\t\tthis.modelRequestUpstreamIds.clear();\n\t\tthis.modelRequestServiceTiers.clear();\n\t\tthis.loadError = undefined;\n\n\t\t// Ensure dynamic API/OAuth registrations are rebuilt from current provider state.\n\t\tresetApiProviders();\n\t\tresetOAuthProviders();\n\n\t\tthis.loadModels();\n\n\t\tfor (const [providerName, config] of this.registeredProviders.entries()) {\n\t\t\tthis.applyProviderConfig(providerName, config);\n\t\t}\n\t}\n\n\t/**\n\t * Get any error from loading models.json (undefined if no error).\n\t */\n\tgetError(): string | undefined {\n\t\treturn this.loadError;\n\t}\n\n\tprivate loadModels(): void {\n\t\t// Load custom models and overrides from models.json\n\t\tconst {\n\t\t\tmodels: customModels,\n\t\t\toverrides,\n\t\t\tmodelOverrides,\n\t\t\tdisabledProviders,\n\t\t\tmodelWhitelists,\n\t\t\tmodelBlacklists,\n\t\t\terror,\n\t\t} = this.modelsJsonPath ? this.loadCustomModels(this.modelsJsonPath) : emptyCustomModelsResult();\n\n\t\tif (error) {\n\t\t\tthis.loadError = error;\n\t\t\t// Keep built-in models even if custom models failed to load\n\t\t}\n\n\t\tconst builtInModels = this.loadBuiltInModels(\n\t\t\toverrides,\n\t\t\tmodelOverrides,\n\t\t\tdisabledProviders,\n\t\t\tmodelWhitelists,\n\t\t\tmodelBlacklists,\n\t\t);\n\t\tlet combined = this.mergeCustomModels(builtInModels, customModels);\n\n\t\t// Let OAuth providers modify their models (e.g., update baseUrl)\n\t\tfor (const oauthProvider of this.authStorage.getOAuthProviders()) {\n\t\t\tconst cred = this.authStorage.get(oauthProvider.id);\n\t\t\tif (cred?.type === \"oauth\" && oauthProvider.modifyModels) {\n\t\t\t\tcombined = oauthProvider.modifyModels(combined, cred);\n\t\t\t}\n\t\t}\n\n\t\tthis.models = combined;\n\t}\n\n\t/** Load built-in models and apply provider/model overrides */\n\tprivate loadBuiltInModels(\n\t\toverrides: Map<string, ProviderOverride>,\n\t\tmodelOverrides: Map<string, Map<string, ModelOverride>>,\n\t\tdisabledProviders: Set<string>,\n\t\tmodelWhitelists: Map<string, Set<string>>,\n\t\tmodelBlacklists: Map<string, Set<string>>,\n\t): Model<Api>[] {\n\t\treturn getProviders().flatMap((provider) => {\n\t\t\tif (disabledProviders.has(provider)) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\tconst models = getModels(provider as KnownProvider) as Model<Api>[];\n\t\t\tconst providerOverride = overrides.get(provider);\n\t\t\tconst perModelOverrides = modelOverrides.get(provider);\n\t\t\tconst whitelist = modelWhitelists.get(provider);\n\t\t\tconst blacklist = modelBlacklists.get(provider);\n\n\t\t\treturn models.flatMap((m) => {\n\t\t\t\tif (whitelist && !whitelist.has(m.id)) return [];\n\t\t\t\tif (blacklist?.has(m.id)) return [];\n\n\t\t\t\tlet model = m;\n\n\t\t\t\t// Apply provider-level baseUrl/headers/compat override\n\t\t\t\tif (providerOverride) {\n\t\t\t\t\tmodel = {\n\t\t\t\t\t\t...model,\n\t\t\t\t\t\tbaseUrl: providerOverride.baseUrl ?? model.baseUrl,\n\t\t\t\t\t\tcompat: mergeCompat(model.compat, providerOverride.compat),\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\t// Apply per-model override\n\t\t\t\tconst modelOverride = perModelOverrides?.get(m.id);\n\t\t\t\tif (modelOverride) {\n\t\t\t\t\tmodel = applyModelOverride(model, modelOverride);\n\t\t\t\t}\n\n\t\t\t\treturn [model];\n\t\t\t});\n\t\t});\n\t}\n\n\t/** Merge custom models into built-in list by provider+id (custom wins on conflicts). */\n\tprivate mergeCustomModels(builtInModels: Model<Api>[], customModels: Model<Api>[]): Model<Api>[] {\n\t\tconst merged = [...builtInModels];\n\t\tfor (const customModel of customModels) {\n\t\t\tconst existingIndex = merged.findIndex((m) => m.provider === customModel.provider && m.id === customModel.id);\n\t\t\tif (existingIndex >= 0) {\n\t\t\t\tmerged[existingIndex] = customModel;\n\t\t\t} else {\n\t\t\t\tmerged.push(customModel);\n\t\t\t}\n\t\t}\n\t\treturn merged;\n\t}\n\n\tprivate loadCustomModels(modelsJsonPath: string): CustomModelsResult {\n\t\tif (!existsSync(modelsJsonPath)) {\n\t\t\treturn emptyCustomModelsResult();\n\t\t}\n\n\t\ttry {\n\t\t\tconst content = readFileSync(modelsJsonPath, \"utf-8\");\n\t\t\tconst parsed: unknown = JSON.parse(stripJsonComments(content));\n\n\t\t\tif (!validateModelsConfig.Check(parsed)) {\n\t\t\t\tconst errors =\n\t\t\t\t\tvalidateModelsConfig\n\t\t\t\t\t\t.Errors(parsed)\n\t\t\t\t\t\t.map((error) => ` - ${formatValidationPath(error)}: ${error.message}`)\n\t\t\t\t\t\t.join(\"\\n\") || \"Unknown schema error\";\n\t\t\t\treturn emptyCustomModelsResult(`Invalid models.json schema:\\n${errors}\\n\\nFile: ${modelsJsonPath}`);\n\t\t\t}\n\n\t\t\tconst config = parsed as ModelsConfig;\n\n\t\t\t// Additional validation\n\t\t\tthis.validateConfig(config);\n\n\t\t\tconst overrides = new Map<string, ProviderOverride>();\n\t\t\tconst modelOverrides = new Map<string, Map<string, ModelOverride>>();\n\t\t\tconst disabledProviders = new Set(config.disabledProviders ?? []);\n\t\t\tconst modelWhitelists = new Map<string, Set<string>>();\n\t\t\tconst modelBlacklists = new Map<string, Set<string>>();\n\n\t\t\tfor (const [providerName, providerConfig] of Object.entries(config.providers)) {\n\t\t\t\tif (providerConfig.disabled) {\n\t\t\t\t\tdisabledProviders.add(providerName);\n\t\t\t\t}\n\n\t\t\t\tif (providerConfig.whitelist) {\n\t\t\t\t\tmodelWhitelists.set(providerName, new Set(providerConfig.whitelist));\n\t\t\t\t}\n\t\t\t\tif (providerConfig.blacklist) {\n\t\t\t\t\tmodelBlacklists.set(providerName, new Set(providerConfig.blacklist));\n\t\t\t\t}\n\n\t\t\t\tif (providerConfig.baseUrl || providerConfig.compat) {\n\t\t\t\t\toverrides.set(providerName, {\n\t\t\t\t\t\tbaseUrl: providerConfig.baseUrl,\n\t\t\t\t\t\tcompat: providerConfig.compat,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tthis.storeProviderRequestConfig(providerName, providerConfig);\n\n\t\t\t\tif (providerConfig.modelOverrides) {\n\t\t\t\t\tmodelOverrides.set(providerName, new Map(Object.entries(providerConfig.modelOverrides)));\n\t\t\t\t\tfor (const [modelId, modelOverride] of Object.entries(providerConfig.modelOverrides)) {\n\t\t\t\t\t\tthis.storeModelHeaders(providerName, modelId, modelOverride.headers);\n\t\t\t\t\t\tthis.storeModelExtraBody(providerName, modelId, modelOverride.extraBody);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tmodels: this.parseModels(config, disabledProviders, modelWhitelists, modelBlacklists),\n\t\t\t\toverrides,\n\t\t\t\tmodelOverrides,\n\t\t\t\tdisabledProviders,\n\t\t\t\tmodelWhitelists,\n\t\t\t\tmodelBlacklists,\n\t\t\t\terror: undefined,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tif (error instanceof SyntaxError) {\n\t\t\t\treturn emptyCustomModelsResult(`Failed to parse models.json: ${error.message}\\n\\nFile: ${modelsJsonPath}`);\n\t\t\t}\n\t\t\treturn emptyCustomModelsResult(\n\t\t\t\t`Failed to load models.json: ${error instanceof Error ? error.message : error}\\n\\nFile: ${modelsJsonPath}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate validateConfig(config: ModelsConfig): void {\n\t\tconst builtInProviders = new Set<string>(getProviders());\n\n\t\tfor (const [providerName, providerConfig] of Object.entries(config.providers)) {\n\t\t\tif (providerConfig.disabled) continue;\n\n\t\t\tconst isBuiltIn = builtInProviders.has(providerName);\n\t\t\tconst hasProviderApi = !!providerConfig.api;\n\t\t\tconst models = providerConfig.models ?? [];\n\t\t\tconst hasModelOverrides =\n\t\t\t\tproviderConfig.modelOverrides && Object.keys(providerConfig.modelOverrides).length > 0;\n\n\t\t\tif (models.length === 0) {\n\t\t\t\t// Override-only config: needs baseUrl, headers, compat, modelOverrides, extraBody, or some combination.\n\t\t\t\tif (\n\t\t\t\t\t!providerConfig.baseUrl &&\n\t\t\t\t\t!providerConfig.headers &&\n\t\t\t\t\t!providerConfig.compat &&\n\t\t\t\t\t!hasModelOverrides &&\n\t\t\t\t\t!providerConfig.extraBody &&\n\t\t\t\t\t!providerConfig.whitelist &&\n\t\t\t\t\t!providerConfig.blacklist\n\t\t\t\t) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Provider ${providerName}: must specify \"baseUrl\", \"headers\", \"compat\", \"modelOverrides\", \"extraBody\", or \"models\".`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else if (!isBuiltIn) {\n\t\t\t\t// Non-built-in providers with custom models require endpoint + auth.\n\t\t\t\tif (!providerConfig.baseUrl) {\n\t\t\t\t\tthrow new Error(`Provider ${providerName}: \"baseUrl\" is required when defining custom models.`);\n\t\t\t\t}\n\t\t\t\tif (!providerConfig.apiKey) {\n\t\t\t\t\tthrow new Error(`Provider ${providerName}: \"apiKey\" is required when defining custom models.`);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Built-in providers with custom models: baseUrl/apiKey/api are optional,\n\t\t\t// inherited from built-in models. Auth comes from env vars / auth storage.\n\n\t\t\tfor (const modelDef of models) {\n\t\t\t\tconst hasModelApi = !!modelDef.api;\n\n\t\t\t\tif (!hasProviderApi && !hasModelApi && !isBuiltIn) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Provider ${providerName}, model ${modelDef.id}: no \"api\" specified. Set at provider or model level.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t// For built-in providers, api is optional — inherited from built-in models.\n\n\t\t\t\tif (!modelDef.id) throw new Error(`Provider ${providerName}: model missing \"id\"`);\n\t\t\t\t// Validate contextWindow/maxTokens only if provided (they have defaults)\n\t\t\t\tif (modelDef.contextWindow !== undefined && modelDef.contextWindow <= 0)\n\t\t\t\t\tthrow new Error(`Provider ${providerName}, model ${modelDef.id}: invalid contextWindow`);\n\t\t\t\tif (modelDef.maxTokens !== undefined && modelDef.maxTokens <= 0)\n\t\t\t\t\tthrow new Error(`Provider ${providerName}, model ${modelDef.id}: invalid maxTokens`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate parseModels(\n\t\tconfig: ModelsConfig,\n\t\tdisabledProviders: Set<string>,\n\t\tmodelWhitelists: Map<string, Set<string>>,\n\t\tmodelBlacklists: Map<string, Set<string>>,\n\t): Model<Api>[] {\n\t\tconst models: Model<Api>[] = [];\n\t\tconst builtInProviders = new Set<string>(getProviders());\n\n\t\t// Cache built-in defaults (api, baseUrl) per provider, extracted from first model.\n\t\tconst builtInDefaultsCache = new Map<string, { api: string; baseUrl: string }>();\n\t\tconst getBuiltInDefaults = (providerName: string): { api: string; baseUrl: string } | undefined => {\n\t\t\tif (!builtInProviders.has(providerName)) return undefined;\n\t\t\tif (builtInDefaultsCache.has(providerName)) return builtInDefaultsCache.get(providerName);\n\t\t\tconst builtIn = getModels(providerName as KnownProvider) as Model<Api>[];\n\t\t\tif (builtIn.length === 0) return undefined;\n\t\t\tconst defaults = { api: builtIn[0].api, baseUrl: builtIn[0].baseUrl };\n\t\t\tbuiltInDefaultsCache.set(providerName, defaults);\n\t\t\treturn defaults;\n\t\t};\n\n\t\tfor (const [providerName, providerConfig] of Object.entries(config.providers)) {\n\t\t\tif (disabledProviders.has(providerName)) continue;\n\n\t\t\tconst modelDefs = providerConfig.models ?? [];\n\t\t\tif (modelDefs.length === 0) continue; // Override-only, no custom models\n\n\t\t\tconst builtInDefaults = getBuiltInDefaults(providerName);\n\t\t\tconst whitelist = modelWhitelists.get(providerName);\n\t\t\tconst blacklist = modelBlacklists.get(providerName);\n\n\t\t\tfor (const modelDef of modelDefs) {\n\t\t\t\tif (whitelist && !whitelist.has(modelDef.id)) continue;\n\t\t\t\tif (blacklist?.has(modelDef.id)) continue;\n\n\t\t\t\tconst api = modelDef.api ?? providerConfig.api ?? builtInDefaults?.api;\n\t\t\t\tif (!api) continue;\n\n\t\t\t\tconst baseUrl = modelDef.baseUrl ?? providerConfig.baseUrl ?? builtInDefaults?.baseUrl;\n\t\t\t\tif (!baseUrl) continue;\n\n\t\t\t\tconst compat = mergeCompat(providerConfig.compat, modelDef.compat);\n\t\t\t\tthis.storeModelHeaders(providerName, modelDef.id, modelDef.headers);\n\t\t\t\tthis.storeModelExtraBody(providerName, modelDef.id, modelDef.extraBody);\n\t\t\t\tthis.storeModelUpstreamId(providerName, modelDef.id, modelDef.upstreamModelId);\n\t\t\t\tthis.storeModelServiceTier(providerName, modelDef.id, modelDef.serviceTier);\n\n\t\t\t\tconst defaultCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };\n\t\t\t\tmodels.push({\n\t\t\t\t\tid: modelDef.id,\n\t\t\t\t\tname: modelDef.name ?? modelDef.id,\n\t\t\t\t\tpromptPreset: modelDef.promptPreset,\n\t\t\t\t\tapi: api as Api,\n\t\t\t\t\tprovider: providerName,\n\t\t\t\t\tbaseUrl,\n\t\t\t\t\treasoning: modelDef.reasoning ?? false,\n\t\t\t\t\tthinkingLevelMap: modelDef.thinkingLevelMap,\n\t\t\t\t\tinput: (modelDef.input ?? [\"text\"]) as Model<Api>[\"input\"],\n\t\t\t\t\tcost: modelDef.cost ?? defaultCost,\n\t\t\t\t\tcontextWindow: modelDef.contextWindow ?? 128000,\n\t\t\t\t\tmaxTokens: modelDef.maxTokens ?? 16384,\n\t\t\t\t\theaders: undefined,\n\t\t\t\t\tcompat,\n\t\t\t\t} as Model<Api>);\n\t\t\t}\n\t\t}\n\n\t\treturn models;\n\t}\n\n\t/**\n\t * Get all models (built-in + custom).\n\t * If models.json had errors, returns only built-in models.\n\t */\n\tgetAll(): Model<Api>[] {\n\t\treturn this.models;\n\t}\n\n\t/**\n\t * Get only models that have auth configured.\n\t * This is a fast check that doesn't refresh OAuth tokens.\n\t */\n\tgetAvailable(): Model<Api>[] {\n\t\treturn this.models.filter((m) => this.hasConfiguredAuth(m));\n\t}\n\n\t/**\n\t * Find a model by provider and ID.\n\t */\n\tfind(provider: string, modelId: string): Model<Api> | undefined {\n\t\treturn this.models.find((m) => m.provider === provider && m.id === modelId);\n\t}\n\n\t/**\n\t * Get API key for a model.\n\t */\n\thasConfiguredAuth(model: Model<Api>): boolean {\n\t\treturn (\n\t\t\tthis.authStorage.hasAuth(model.provider) ||\n\t\t\tthis.providerRequestConfigs.get(model.provider)?.apiKey !== undefined\n\t\t);\n\t}\n\n\tprivate getModelRequestKey(provider: string, modelId: string): string {\n\t\treturn `${provider}:${modelId}`;\n\t}\n\n\tprivate storeProviderRequestConfig(\n\t\tproviderName: string,\n\t\tconfig: {\n\t\t\tapiKey?: string;\n\t\t\theaders?: Record<string, string>;\n\t\t\textraBody?: Record<string, unknown>;\n\t\t\tauthHeader?: boolean;\n\t\t},\n\t\tpreservedApiKey?: string,\n\t): void {\n\t\tconst apiKey = config.apiKey ?? preservedApiKey;\n\t\tif (!apiKey && !config.headers && !config.extraBody && config.authHeader !== true) {\n\t\t\tthis.providerRequestConfigs.delete(providerName);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.providerRequestConfigs.set(providerName, {\n\t\t\tapiKey,\n\t\t\theaders: config.headers,\n\t\t\textraBody: config.extraBody,\n\t\t\tauthHeader: config.authHeader,\n\t\t});\n\t}\n\n\tprivate storeModelHeaders(providerName: string, modelId: string, headers?: Record<string, string>): void {\n\t\tconst key = this.getModelRequestKey(providerName, modelId);\n\t\tif (!headers || Object.keys(headers).length === 0) {\n\t\t\tthis.modelRequestHeaders.delete(key);\n\t\t\treturn;\n\t\t}\n\t\tthis.modelRequestHeaders.set(key, headers);\n\t}\n\n\tprivate storeModelExtraBody(providerName: string, modelId: string, extraBody?: Record<string, unknown>): void {\n\t\tconst key = this.getModelRequestKey(providerName, modelId);\n\t\tif (!extraBody || Object.keys(extraBody).length === 0) {\n\t\t\tthis.modelRequestExtraBody.delete(key);\n\t\t\treturn;\n\t\t}\n\t\tthis.modelRequestExtraBody.set(key, extraBody);\n\t}\n\n\tprivate storeModelUpstreamId(providerName: string, modelId: string, upstreamModelId?: string): void {\n\t\tconst key = this.getModelRequestKey(providerName, modelId);\n\t\tif (!upstreamModelId) {\n\t\t\tthis.modelRequestUpstreamIds.delete(key);\n\t\t\treturn;\n\t\t}\n\t\tthis.modelRequestUpstreamIds.set(key, upstreamModelId);\n\t}\n\n\tprivate storeModelServiceTier(providerName: string, modelId: string, serviceTier?: ModelServiceTier): void {\n\t\tconst key = this.getModelRequestKey(providerName, modelId);\n\t\tif (!serviceTier) {\n\t\t\tthis.modelRequestServiceTiers.delete(key);\n\t\t\treturn;\n\t\t}\n\t\tthis.modelRequestServiceTiers.set(key, serviceTier);\n\t}\n\n\tprivate clearModelRequestConfigForProvider(providerName: string): void {\n\t\tconst prefix = `${providerName}:`;\n\t\tfor (const key of this.modelRequestHeaders.keys()) {\n\t\t\tif (key.startsWith(prefix)) {\n\t\t\t\tthis.modelRequestHeaders.delete(key);\n\t\t\t}\n\t\t}\n\t\tfor (const key of this.modelRequestExtraBody.keys()) {\n\t\t\tif (key.startsWith(prefix)) {\n\t\t\t\tthis.modelRequestExtraBody.delete(key);\n\t\t\t}\n\t\t}\n\t\tfor (const key of this.modelRequestUpstreamIds.keys()) {\n\t\t\tif (key.startsWith(prefix)) {\n\t\t\t\tthis.modelRequestUpstreamIds.delete(key);\n\t\t\t}\n\t\t}\n\t\tfor (const key of this.modelRequestServiceTiers.keys()) {\n\t\t\tif (key.startsWith(prefix)) {\n\t\t\t\tthis.modelRequestServiceTiers.delete(key);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get API key and request headers for a model.\n\t */\n\tasync getApiKeyAndHeaders(model: Model<Api>): Promise<ResolvedRequestAuth> {\n\t\ttry {\n\t\t\tconst providerConfig = this.providerRequestConfigs.get(model.provider);\n\t\t\tconst apiKeyFromAuthStorage = await this.authStorage.getApiKey(model.provider, { includeFallback: false });\n\t\t\tconst apiKey =\n\t\t\t\tapiKeyFromAuthStorage ??\n\t\t\t\t(providerConfig?.apiKey\n\t\t\t\t\t? resolveConfigValueOrThrow(providerConfig.apiKey, `API key for provider \"${model.provider}\"`)\n\t\t\t\t\t: undefined);\n\n\t\t\tconst providerHeaders = resolveHeadersOrThrow(providerConfig?.headers, `provider \"${model.provider}\"`);\n\t\t\tconst modelRequestKey = this.getModelRequestKey(model.provider, model.id);\n\t\t\tconst modelHeaders = resolveHeadersOrThrow(\n\t\t\t\tthis.modelRequestHeaders.get(modelRequestKey),\n\t\t\t\t`model \"${model.provider}/${model.id}\"`,\n\t\t\t);\n\n\t\t\tlet headers =\n\t\t\t\tmodel.headers || providerHeaders || modelHeaders\n\t\t\t\t\t? { ...model.headers, ...providerHeaders, ...modelHeaders }\n\t\t\t\t\t: undefined;\n\n\t\t\tif (providerConfig?.authHeader) {\n\t\t\t\tif (!apiKey) {\n\t\t\t\t\treturn { ok: false, error: `No API key found for \"${model.provider}\"` };\n\t\t\t\t}\n\t\t\t\theaders = { ...headers, Authorization: `Bearer ${apiKey}` };\n\t\t\t}\n\n\t\t\tconst providerExtraBody = providerConfig?.extraBody;\n\t\t\tconst modelExtraBody = this.modelRequestExtraBody.get(modelRequestKey);\n\t\t\tconst extraBody =\n\t\t\t\tproviderExtraBody || modelExtraBody ? { ...providerExtraBody, ...modelExtraBody } : undefined;\n\t\t\tconst upstreamModelId = this.modelRequestUpstreamIds.get(modelRequestKey);\n\t\t\tconst serviceTier = this.modelRequestServiceTiers.get(modelRequestKey);\n\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tapiKey,\n\t\t\t\theaders: headers && Object.keys(headers).length > 0 ? headers : undefined,\n\t\t\t\textraBody: extraBody && Object.keys(extraBody).length > 0 ? extraBody : undefined,\n\t\t\t\tupstreamModelId,\n\t\t\t\tserviceTier,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Return auth status for a provider, including request auth configured in models.json.\n\t * This intentionally does not execute command-backed config values.\n\t */\n\tgetProviderAuthStatus(provider: string): AuthStatus {\n\t\tconst authStatus = this.authStorage.getAuthStatus(provider);\n\t\tif (authStatus.source) {\n\t\t\treturn authStatus;\n\t\t}\n\n\t\tconst providerApiKey = this.providerRequestConfigs.get(provider)?.apiKey;\n\t\tif (!providerApiKey) {\n\t\t\treturn authStatus;\n\t\t}\n\n\t\tif (providerApiKey.startsWith(\"!\")) {\n\t\t\treturn { configured: true, source: \"models_json_command\" };\n\t\t}\n\n\t\tif (process.env[providerApiKey]) {\n\t\t\treturn { configured: true, source: \"environment\", label: providerApiKey };\n\t\t}\n\n\t\treturn { configured: true, source: \"models_json_key\" };\n\t}\n\n\t/**\n\t * Get display name for a provider.\n\t */\n\tgetProviderDisplayName(provider: string): string {\n\t\tconst registeredProvider = this.registeredProviders.get(provider);\n\t\tconst oauthProvider = this.authStorage.getOAuthProviders().find((p) => p.id === provider);\n\n\t\treturn (\n\t\t\tregisteredProvider?.name ??\n\t\t\tregisteredProvider?.oauth?.name ??\n\t\t\toauthProvider?.name ??\n\t\t\tBUILT_IN_PROVIDER_DISPLAY_NAMES[provider] ??\n\t\t\tprovider\n\t\t);\n\t}\n\n\t/**\n\t * Get API key for a provider.\n\t */\n\tasync getApiKeyForProvider(provider: string): Promise<string | undefined> {\n\t\tconst apiKey = await this.authStorage.getApiKey(provider, { includeFallback: false });\n\t\tif (apiKey !== undefined) {\n\t\t\treturn apiKey;\n\t\t}\n\n\t\tconst providerApiKey = this.providerRequestConfigs.get(provider)?.apiKey;\n\t\treturn providerApiKey ? resolveConfigValueUncached(providerApiKey) : undefined;\n\t}\n\n\t/**\n\t * Check if a model is using OAuth credentials (subscription).\n\t */\n\tisUsingOAuth(model: Model<Api>): boolean {\n\t\tconst cred = this.authStorage.get(model.provider);\n\t\treturn cred?.type === \"oauth\";\n\t}\n\n\t/**\n\t * Register a provider dynamically (from extensions).\n\t *\n\t * If provider has models: replaces all existing models for this provider.\n\t * If provider has only baseUrl/headers: overrides existing models' URLs.\n\t * If provider has oauth: registers OAuth provider for /login support.\n\t */\n\tregisterProvider(providerName: string, config: ProviderConfigInput): void {\n\t\tthis.validateProviderConfig(providerName, config);\n\t\tthis.applyProviderConfig(providerName, config);\n\t\tthis.upsertRegisteredProvider(providerName, config);\n\t}\n\n\t/**\n\t * Unregister a previously registered provider.\n\t *\n\t * Removes the provider from the registry and reloads models from disk so that\n\t * built-in models overridden by this provider are restored to their original state.\n\t * Also resets dynamic OAuth and API stream registrations before reapplying\n\t * remaining dynamic providers.\n\t * Has no effect if the provider was never registered.\n\t */\n\tunregisterProvider(providerName: string): void {\n\t\tif (!this.registeredProviders.has(providerName)) return;\n\t\tthis.registeredProviders.delete(providerName);\n\t\tthis.refresh();\n\t}\n\n\t/**\n\t * Upsert a provider config into registeredProviders.\n\t * If the provider is already registered, defined values in the incoming config\n\t * override existing ones; undefined values are preserved from the stored config.\n\t * If the provider is not registered, the incoming config is stored as-is.\n\t */\n\tprivate upsertRegisteredProvider(providerName: string, config: ProviderConfigInput): void {\n\t\tconst existing = this.registeredProviders.get(providerName);\n\t\tif (!existing) {\n\t\t\tthis.registeredProviders.set(providerName, config);\n\t\t\treturn;\n\t\t}\n\t\tconst next: ProviderConfigInput = { ...existing };\n\n\t\tif (config.name !== undefined) next.name = config.name;\n\t\tif (config.baseUrl !== undefined) next.baseUrl = config.baseUrl;\n\t\tif (config.apiKey !== undefined) next.apiKey = config.apiKey;\n\t\tif (config.api !== undefined) next.api = config.api;\n\t\tif (config.streamSimple !== undefined) next.streamSimple = config.streamSimple;\n\t\tif (config.oauth !== undefined) next.oauth = config.oauth;\n\t\tif (config.models !== undefined) next.models = config.models;\n\n\t\tif (config.headers !== undefined) {\n\t\t\tnext.headers = config.headers;\n\t\t} else {\n\t\t\tdelete next.headers;\n\t\t}\n\t\tif (config.extraBody !== undefined) {\n\t\t\tnext.extraBody = config.extraBody;\n\t\t} else {\n\t\t\tdelete next.extraBody;\n\t\t}\n\t\tif (config.authHeader !== undefined) {\n\t\t\tnext.authHeader = config.authHeader;\n\t\t} else {\n\t\t\tdelete next.authHeader;\n\t\t}\n\n\t\tthis.registeredProviders.set(providerName, next);\n\t}\n\n\tprivate validateProviderConfig(providerName: string, config: ProviderConfigInput): void {\n\t\tif (config.streamSimple && !config.api) {\n\t\t\tthrow new Error(`Provider ${providerName}: \"api\" is required when registering streamSimple.`);\n\t\t}\n\n\t\tif (!config.models || config.models.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!config.baseUrl) {\n\t\t\tthrow new Error(`Provider ${providerName}: \"baseUrl\" is required when defining models.`);\n\t\t}\n\t\tif (!config.apiKey && !config.oauth) {\n\t\t\tthrow new Error(`Provider ${providerName}: \"apiKey\" or \"oauth\" is required when defining models.`);\n\t\t}\n\n\t\tfor (const modelDef of config.models) {\n\t\t\tconst api = modelDef.api || config.api;\n\t\t\tif (!api) {\n\t\t\t\tthrow new Error(`Provider ${providerName}, model ${modelDef.id}: no \"api\" specified.`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate applyProviderConfig(providerName: string, config: ProviderConfigInput): void {\n\t\t// Register OAuth provider if provided\n\t\tif (config.oauth) {\n\t\t\t// Ensure the OAuth provider ID matches the provider name\n\t\t\tconst oauthProvider: OAuthProviderInterface = {\n\t\t\t\t...config.oauth,\n\t\t\t\tid: providerName,\n\t\t\t};\n\t\t\tregisterOAuthProvider(oauthProvider);\n\t\t}\n\n\t\tif (config.streamSimple) {\n\t\t\tconst streamSimple = config.streamSimple;\n\t\t\tregisterApiProvider(\n\t\t\t\t{\n\t\t\t\t\tapi: config.api!,\n\t\t\t\t\tstream: (model, context, options) => streamSimple(model, context, options as SimpleStreamOptions),\n\t\t\t\t\tstreamSimple,\n\t\t\t\t},\n\t\t\t\t`provider:${providerName}`,\n\t\t\t);\n\t\t}\n\n\t\tthis.storeProviderRequestConfig(providerName, config, this.registeredProviders.get(providerName)?.apiKey);\n\n\t\tif (config.models && config.models.length > 0) {\n\t\t\t// Full replacement: remove existing models for this provider\n\t\t\tthis.models = this.models.filter((m) => m.provider !== providerName);\n\t\t\tthis.clearModelRequestConfigForProvider(providerName);\n\n\t\t\t// Parse and add new models\n\t\t\tfor (const modelDef of config.models) {\n\t\t\t\tconst api = modelDef.api || config.api;\n\t\t\t\tthis.storeModelHeaders(providerName, modelDef.id, modelDef.headers);\n\t\t\t\tthis.storeModelExtraBody(providerName, modelDef.id, modelDef.extraBody);\n\t\t\t\tthis.storeModelUpstreamId(providerName, modelDef.id, modelDef.upstreamModelId);\n\t\t\t\tthis.storeModelServiceTier(providerName, modelDef.id, modelDef.serviceTier);\n\n\t\t\t\tthis.models.push({\n\t\t\t\t\tid: modelDef.id,\n\t\t\t\t\tname: modelDef.name,\n\t\t\t\t\tapi: api as Api,\n\t\t\t\t\tprovider: providerName,\n\t\t\t\t\tbaseUrl: modelDef.baseUrl ?? config.baseUrl!,\n\t\t\t\t\treasoning: modelDef.reasoning,\n\t\t\t\t\tthinkingLevelMap: modelDef.thinkingLevelMap,\n\t\t\t\t\tinput: modelDef.input as Model<Api>[\"input\"],\n\t\t\t\t\tcost: modelDef.cost,\n\t\t\t\t\tcontextWindow: modelDef.contextWindow,\n\t\t\t\t\tmaxTokens: modelDef.maxTokens,\n\t\t\t\t\theaders: undefined,\n\t\t\t\t\tcompat: modelDef.compat,\n\t\t\t\t} as Model<Api>);\n\t\t\t}\n\n\t\t\t// Apply OAuth modifyModels if credentials exist (e.g., to update baseUrl)\n\t\t\tif (config.oauth?.modifyModels) {\n\t\t\t\tconst cred = this.authStorage.get(providerName);\n\t\t\t\tif (cred?.type === \"oauth\") {\n\t\t\t\t\tthis.models = config.oauth.modifyModels(this.models, cred);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (config.baseUrl || config.headers || config.extraBody) {\n\t\t\t// Override-only: update baseUrl for existing models. Request headers and extraBody are resolved per request.\n\t\t\tthis.models = this.models.map((m) => {\n\t\t\t\tif (m.provider !== providerName) return m;\n\t\t\t\treturn {\n\t\t\t\t\t...m,\n\t\t\t\t\tbaseUrl: config.baseUrl ?? m.baseUrl,\n\t\t\t\t};\n\t\t\t});\n\t\t}\n\t}\n}\n\n/**\n * Input type for registerProvider API.\n */\nexport interface ProviderConfigInput {\n\tname?: string;\n\tbaseUrl?: string;\n\tapiKey?: string;\n\tapi?: Api;\n\tstreamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;\n\theaders?: Record<string, string>;\n\textraBody?: Record<string, unknown>;\n\tauthHeader?: boolean;\n\t/** OAuth provider for /login support */\n\toauth?: Omit<OAuthProviderInterface, \"id\">;\n\tmodels?: Array<{\n\t\tid: string;\n\t\tname: string;\n\t\tupstreamModelId?: string;\n\t\tserviceTier?: ModelServiceTier;\n\t\tapi?: Api;\n\t\tbaseUrl?: string;\n\t\treasoning: boolean;\n\t\tthinkingLevelMap?: ThinkingLevelMap;\n\t\tinput: Model<Api>[\"input\"];\n\t\tcost: { input: number; output: number; cacheRead: number; cacheWrite: number };\n\t\tcontextWindow: number;\n\t\tmaxTokens: number;\n\t\theaders?: Record<string, string>;\n\t\textraBody?: Record<string, unknown>;\n\t\tcompat?: Model<Api>[\"compat\"];\n\t}>;\n}\n"]}
@@ -588,12 +588,14 @@ export class ModelRegistry {
588
588
  getModelRequestKey(provider, modelId) {
589
589
  return `${provider}:${modelId}`;
590
590
  }
591
- storeProviderRequestConfig(providerName, config) {
592
- if (!config.apiKey && !config.headers && !config.extraBody && !config.authHeader) {
591
+ storeProviderRequestConfig(providerName, config, preservedApiKey) {
592
+ const apiKey = config.apiKey ?? preservedApiKey;
593
+ if (!apiKey && !config.headers && !config.extraBody && config.authHeader !== true) {
594
+ this.providerRequestConfigs.delete(providerName);
593
595
  return;
594
596
  }
595
597
  this.providerRequestConfigs.set(providerName, {
596
- apiKey: config.apiKey,
598
+ apiKey,
597
599
  headers: config.headers,
598
600
  extraBody: config.extraBody,
599
601
  authHeader: config.authHeader,
@@ -631,6 +633,29 @@ export class ModelRegistry {
631
633
  }
632
634
  this.modelRequestServiceTiers.set(key, serviceTier);
633
635
  }
636
+ clearModelRequestConfigForProvider(providerName) {
637
+ const prefix = `${providerName}:`;
638
+ for (const key of this.modelRequestHeaders.keys()) {
639
+ if (key.startsWith(prefix)) {
640
+ this.modelRequestHeaders.delete(key);
641
+ }
642
+ }
643
+ for (const key of this.modelRequestExtraBody.keys()) {
644
+ if (key.startsWith(prefix)) {
645
+ this.modelRequestExtraBody.delete(key);
646
+ }
647
+ }
648
+ for (const key of this.modelRequestUpstreamIds.keys()) {
649
+ if (key.startsWith(prefix)) {
650
+ this.modelRequestUpstreamIds.delete(key);
651
+ }
652
+ }
653
+ for (const key of this.modelRequestServiceTiers.keys()) {
654
+ if (key.startsWith(prefix)) {
655
+ this.modelRequestServiceTiers.delete(key);
656
+ }
657
+ }
658
+ }
634
659
  /**
635
660
  * Get API key and request headers for a model.
636
661
  */
@@ -765,11 +790,40 @@ export class ModelRegistry {
765
790
  this.registeredProviders.set(providerName, config);
766
791
  return;
767
792
  }
768
- for (const k of Object.keys(config)) {
769
- if (config[k] !== undefined) {
770
- existing[k] = config[k];
771
- }
772
- }
793
+ const next = { ...existing };
794
+ if (config.name !== undefined)
795
+ next.name = config.name;
796
+ if (config.baseUrl !== undefined)
797
+ next.baseUrl = config.baseUrl;
798
+ if (config.apiKey !== undefined)
799
+ next.apiKey = config.apiKey;
800
+ if (config.api !== undefined)
801
+ next.api = config.api;
802
+ if (config.streamSimple !== undefined)
803
+ next.streamSimple = config.streamSimple;
804
+ if (config.oauth !== undefined)
805
+ next.oauth = config.oauth;
806
+ if (config.models !== undefined)
807
+ next.models = config.models;
808
+ if (config.headers !== undefined) {
809
+ next.headers = config.headers;
810
+ }
811
+ else {
812
+ delete next.headers;
813
+ }
814
+ if (config.extraBody !== undefined) {
815
+ next.extraBody = config.extraBody;
816
+ }
817
+ else {
818
+ delete next.extraBody;
819
+ }
820
+ if (config.authHeader !== undefined) {
821
+ next.authHeader = config.authHeader;
822
+ }
823
+ else {
824
+ delete next.authHeader;
825
+ }
826
+ this.registeredProviders.set(providerName, next);
773
827
  }
774
828
  validateProviderConfig(providerName, config) {
775
829
  if (config.streamSimple && !config.api) {
@@ -809,10 +863,11 @@ export class ModelRegistry {
809
863
  streamSimple,
810
864
  }, `provider:${providerName}`);
811
865
  }
812
- this.storeProviderRequestConfig(providerName, config);
866
+ this.storeProviderRequestConfig(providerName, config, this.registeredProviders.get(providerName)?.apiKey);
813
867
  if (config.models && config.models.length > 0) {
814
868
  // Full replacement: remove existing models for this provider
815
869
  this.models = this.models.filter((m) => m.provider !== providerName);
870
+ this.clearModelRequestConfigForProvider(providerName);
816
871
  // Parse and add new models
817
872
  for (const modelDef of config.models) {
818
873
  const api = modelDef.api || config.api;