@oh-my-pi/pi-coding-agent 16.1.22 → 16.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/CHANGELOG.md +99 -1
  2. package/dist/cli.js +4387 -4164
  3. package/dist/types/advisor/__tests__/emission-guard.test.d.ts +1 -0
  4. package/dist/types/advisor/emission-guard.d.ts +73 -0
  5. package/dist/types/advisor/index.d.ts +1 -0
  6. package/dist/types/advisor/runtime.d.ts +7 -0
  7. package/dist/types/advisor/watchdog.d.ts +2 -0
  8. package/dist/types/cli/flag-tables.d.ts +1 -0
  9. package/dist/types/cli/gallery-fixtures/search.d.ts +1 -1
  10. package/dist/types/cli/gc-cli.d.ts +58 -0
  11. package/dist/types/collab/display-name.d.ts +3 -0
  12. package/dist/types/collab/host.d.ts +0 -2
  13. package/dist/types/commands/gc.d.ts +37 -0
  14. package/dist/types/commands/worktree.d.ts +0 -3
  15. package/dist/types/config/inline-tool-descriptors-mode.d.ts +13 -0
  16. package/dist/types/config/model-discovery.d.ts +6 -1
  17. package/dist/types/config/model-registry.d.ts +6 -2
  18. package/dist/types/config/model-resolver.d.ts +12 -0
  19. package/dist/types/config/models-config-schema.d.ts +29 -2
  20. package/dist/types/config/models-config.d.ts +22 -1
  21. package/dist/types/config/settings-schema.d.ts +145 -21
  22. package/dist/types/config/settings.d.ts +13 -0
  23. package/dist/types/dap/config.d.ts +1 -1
  24. package/dist/types/edit/hashline/filesystem.d.ts +4 -2
  25. package/dist/types/edit/renderer.d.ts +4 -0
  26. package/dist/types/extensibility/extensions/types.d.ts +17 -17
  27. package/dist/types/extensibility/hooks/types.d.ts +10 -10
  28. package/dist/types/extensibility/plugins/marketplace/types.d.ts +1 -0
  29. package/dist/types/internal-urls/__tests__/ssh-protocol.test.d.ts +1 -0
  30. package/dist/types/internal-urls/index.d.ts +1 -0
  31. package/dist/types/internal-urls/registry-helpers.d.ts +2 -0
  32. package/dist/types/internal-urls/router.d.ts +1 -1
  33. package/dist/types/internal-urls/ssh-protocol.d.ts +10 -0
  34. package/dist/types/internal-urls/types.d.ts +19 -2
  35. package/dist/types/irc/bus.d.ts +6 -0
  36. package/dist/types/mcp/transports/stdio.d.ts +25 -1
  37. package/dist/types/modes/components/__tests__/move-overlay.test.d.ts +1 -0
  38. package/dist/types/modes/components/custom-editor.d.ts +7 -0
  39. package/dist/types/modes/components/move-overlay.d.ts +23 -0
  40. package/dist/types/modes/components/plugin-selector.d.ts +2 -1
  41. package/dist/types/modes/components/queue-mode-selector.d.ts +2 -1
  42. package/dist/types/modes/components/select-list-mouse-routing.d.ts +2 -0
  43. package/dist/types/modes/components/settings-defs.d.ts +4 -1
  44. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  45. package/dist/types/modes/components/show-images-selector.d.ts +2 -1
  46. package/dist/types/modes/components/status-line/component.d.ts +2 -0
  47. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  48. package/dist/types/modes/components/theme-selector.d.ts +2 -1
  49. package/dist/types/modes/components/thinking-selector.d.ts +2 -1
  50. package/dist/types/modes/controllers/command-controller.d.ts +10 -1
  51. package/dist/types/modes/controllers/streaming-reveal.d.ts +6 -0
  52. package/dist/types/modes/interactive-mode.d.ts +10 -1
  53. package/dist/types/modes/internal-url-autocomplete.d.ts +1 -1
  54. package/dist/types/modes/running-subagent-badge.d.ts +6 -0
  55. package/dist/types/modes/theme/mermaid-rendering.test.d.ts +1 -0
  56. package/dist/types/modes/theme/theme.d.ts +2 -1
  57. package/dist/types/modes/types.d.ts +9 -1
  58. package/dist/types/sdk.d.ts +2 -2
  59. package/dist/types/session/agent-session.d.ts +2 -1
  60. package/dist/types/session/session-listing.d.ts +10 -1
  61. package/dist/types/session/session-manager.d.ts +13 -0
  62. package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
  63. package/dist/types/ssh/__tests__/connection-manager-args.test.d.ts +1 -0
  64. package/dist/types/ssh/__tests__/file-transfer-posix-guard.test.d.ts +1 -0
  65. package/dist/types/ssh/connection-manager.d.ts +2 -0
  66. package/dist/types/ssh/file-transfer.d.ts +79 -0
  67. package/dist/types/ssh/utils.d.ts +6 -0
  68. package/dist/types/system-prompt.d.ts +5 -0
  69. package/dist/types/task/executor.d.ts +16 -0
  70. package/dist/types/tiny/text.d.ts +1 -1
  71. package/dist/types/tools/builtin-names.d.ts +5 -1
  72. package/dist/types/tools/{find.d.ts → glob.d.ts} +15 -15
  73. package/dist/types/tools/{search.d.ts → grep.d.ts} +14 -14
  74. package/dist/types/tools/index.d.ts +3 -3
  75. package/dist/types/tools/path-utils.d.ts +29 -0
  76. package/dist/types/tools/plan-mode-guard.d.ts +7 -0
  77. package/dist/types/tools/read.d.ts +2 -2
  78. package/dist/types/tools/render-utils.d.ts +8 -0
  79. package/dist/types/tools/renderers.d.ts +11 -0
  80. package/dist/types/tools/ssh.d.ts +2 -0
  81. package/dist/types/tools/todo.d.ts +0 -16
  82. package/dist/types/tools/write.d.ts +2 -2
  83. package/dist/types/utils/active-repo-context.d.ts +8 -0
  84. package/dist/types/utils/image-resize.d.ts +1 -0
  85. package/dist/types/utils/markit-cache.d.ts +23 -0
  86. package/dist/types/utils/markit.d.ts +5 -1
  87. package/dist/types/utils/prompt-path.d.ts +1 -0
  88. package/dist/types/web/search/providers/duckduckgo.d.ts +14 -0
  89. package/dist/types/web/search/providers/firecrawl.d.ts +28 -0
  90. package/dist/types/web/search/providers/tinyfish.d.ts +29 -0
  91. package/dist/types/web/search/providers/xai.d.ts +13 -0
  92. package/dist/types/web/search/types.d.ts +18 -2
  93. package/package.json +30 -15
  94. package/scripts/bench-guard.ts +1 -1
  95. package/scripts/build-binary.ts +9 -9
  96. package/scripts/bundle-dist.ts +2 -2
  97. package/src/advisor/__tests__/advisor.test.ts +40 -4
  98. package/src/advisor/__tests__/emission-guard.test.ts +147 -0
  99. package/src/advisor/advise-tool.ts +1 -1
  100. package/src/advisor/emission-guard.ts +172 -0
  101. package/src/advisor/index.ts +1 -0
  102. package/src/advisor/runtime.ts +11 -0
  103. package/src/advisor/watchdog.ts +12 -1
  104. package/src/cli/args.ts +5 -2
  105. package/src/cli/auth-broker-cli.ts +17 -0
  106. package/src/cli/config-cli.ts +4 -0
  107. package/src/cli/flag-tables.ts +7 -4
  108. package/src/cli/gallery-cli.ts +14 -2
  109. package/src/cli/gallery-fixtures/edit.ts +60 -0
  110. package/src/cli/gallery-fixtures/fs.ts +17 -17
  111. package/src/cli/gallery-fixtures/search.ts +4 -4
  112. package/src/cli/gc-cli.ts +939 -0
  113. package/src/cli/usage-cli.ts +20 -1
  114. package/src/cli/web-search-cli.ts +1 -1
  115. package/src/cli-commands.ts +1 -0
  116. package/src/collab/display-name.ts +13 -0
  117. package/src/collab/guest.ts +5 -1
  118. package/src/collab/host.ts +2 -13
  119. package/src/commands/gc.ts +46 -0
  120. package/src/commands/worktree.ts +6 -0
  121. package/src/config/inline-tool-descriptors-mode.ts +27 -0
  122. package/src/config/model-discovery.ts +77 -8
  123. package/src/config/model-registry.ts +89 -11
  124. package/src/config/model-resolver.ts +39 -1
  125. package/src/config/models-config-schema.ts +41 -5
  126. package/src/config/models-config.ts +4 -1
  127. package/src/config/settings-schema.ts +130 -27
  128. package/src/config/settings.ts +216 -7
  129. package/src/cursor.ts +1 -1
  130. package/src/dap/config.ts +152 -8
  131. package/src/dap/session.ts +1 -1
  132. package/src/discovery/helpers.ts +3 -1
  133. package/src/edit/hashline/diff.ts +14 -3
  134. package/src/edit/hashline/execute.ts +42 -6
  135. package/src/edit/hashline/filesystem.ts +49 -8
  136. package/src/edit/index.ts +1 -0
  137. package/src/edit/modes/patch.ts +11 -1
  138. package/src/edit/renderer.ts +140 -13
  139. package/src/eval/__tests__/agent-bridge.test.ts +101 -0
  140. package/src/eval/__tests__/julia-prelude.test.ts +18 -0
  141. package/src/eval/agent-bridge.ts +26 -3
  142. package/src/eval/jl/runner.jl +7 -1
  143. package/src/eval/js/tool-bridge.ts +2 -2
  144. package/src/export/html/index.ts +1 -1
  145. package/src/export/html/template.js +3 -1
  146. package/src/export/html/tool-views.generated.js +20 -20
  147. package/src/extensibility/extensions/types.ts +22 -22
  148. package/src/extensibility/hooks/types.ts +11 -11
  149. package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +10 -3
  150. package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +31 -10
  151. package/src/extensibility/plugins/marketplace/manager.ts +22 -0
  152. package/src/extensibility/plugins/marketplace/types.ts +1 -0
  153. package/src/internal-urls/__tests__/ssh-protocol.test.ts +331 -0
  154. package/src/internal-urls/docs-index.generated.txt +2 -2
  155. package/src/internal-urls/docs-index.ts +2 -3
  156. package/src/internal-urls/index.ts +1 -0
  157. package/src/internal-urls/registry-helpers.ts +19 -4
  158. package/src/internal-urls/router.ts +5 -3
  159. package/src/internal-urls/ssh-protocol.ts +367 -0
  160. package/src/internal-urls/types.ts +19 -2
  161. package/src/irc/bus.ts +11 -3
  162. package/src/lsp/index.ts +8 -1
  163. package/src/mcp/oauth-discovery.ts +20 -20
  164. package/src/mcp/tool-bridge.ts +32 -2
  165. package/src/mcp/transports/stdio.test.ts +20 -3
  166. package/src/mcp/transports/stdio.ts +45 -14
  167. package/src/memories/index.ts +1 -1
  168. package/src/modes/acp/acp-event-mapper.ts +2 -2
  169. package/src/modes/components/__tests__/move-overlay.test.ts +166 -0
  170. package/src/modes/components/agent-hub.ts +3 -0
  171. package/src/modes/components/agent-transcript-viewer.ts +9 -7
  172. package/src/modes/components/custom-editor.ts +90 -5
  173. package/src/modes/components/move-overlay.ts +282 -0
  174. package/src/modes/components/plan-review-overlay.ts +35 -35
  175. package/src/modes/components/plugin-selector.ts +6 -1
  176. package/src/modes/components/queue-mode-selector.ts +6 -1
  177. package/src/modes/components/select-list-mouse-routing.ts +35 -0
  178. package/src/modes/components/session-selector.ts +11 -10
  179. package/src/modes/components/settings-defs.ts +14 -1
  180. package/src/modes/components/settings-selector.ts +196 -29
  181. package/src/modes/components/show-images-selector.ts +6 -1
  182. package/src/modes/components/status-line/component.ts +108 -28
  183. package/src/modes/components/status-line/segments.ts +5 -1
  184. package/src/modes/components/status-line/types.ts +2 -0
  185. package/src/modes/components/theme-selector.ts +6 -1
  186. package/src/modes/components/thinking-selector.ts +6 -1
  187. package/src/modes/components/tool-execution.ts +25 -9
  188. package/src/modes/components/tree-selector.ts +5 -5
  189. package/src/modes/controllers/command-controller.ts +140 -47
  190. package/src/modes/controllers/event-controller.ts +59 -3
  191. package/src/modes/controllers/input-controller.ts +70 -4
  192. package/src/modes/controllers/selector-controller.ts +15 -2
  193. package/src/modes/controllers/streaming-reveal.ts +17 -0
  194. package/src/modes/controllers/tool-args-reveal.ts +1 -1
  195. package/src/modes/interactive-mode.ts +142 -59
  196. package/src/modes/internal-url-autocomplete.ts +17 -2
  197. package/src/modes/prompt-action-autocomplete.ts +3 -1
  198. package/src/modes/running-subagent-badge.ts +13 -0
  199. package/src/modes/setup-wizard/scenes/glyph.ts +2 -13
  200. package/src/modes/setup-wizard/scenes/providers.ts +2 -1
  201. package/src/modes/setup-wizard/scenes/theme.ts +6 -12
  202. package/src/modes/setup-wizard/scenes/web-search.ts +8 -13
  203. package/src/modes/setup-wizard/wizard-overlay.ts +6 -5
  204. package/src/modes/theme/mermaid-rendering.test.ts +53 -0
  205. package/src/modes/theme/theme.ts +42 -15
  206. package/src/modes/types.ts +9 -1
  207. package/src/modes/utils/interactive-context-helpers.ts +1 -1
  208. package/src/priority.json +15 -0
  209. package/src/prompts/advisor/active-repo-watchdog.md +6 -0
  210. package/src/prompts/advisor/system.md +21 -7
  211. package/src/prompts/agents/designer.md +1 -1
  212. package/src/prompts/agents/explore.md +1 -1
  213. package/src/prompts/agents/librarian.md +2 -2
  214. package/src/prompts/agents/plan.md +2 -2
  215. package/src/prompts/agents/reviewer.md +1 -1
  216. package/src/prompts/agents/task.md +2 -2
  217. package/src/prompts/system/active-repo-context.md +4 -0
  218. package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
  219. package/src/prompts/system/plan-mode-active.md +12 -3
  220. package/src/prompts/system/project-prompt.md +2 -2
  221. package/src/prompts/system/system-prompt.md +8 -6
  222. package/src/prompts/tools/bash.md +2 -2
  223. package/src/prompts/tools/checkpoint.md +1 -1
  224. package/src/prompts/tools/{find.md → glob.md} +1 -1
  225. package/src/prompts/tools/{search.md → grep.md} +3 -3
  226. package/src/prompts/tools/read.md +4 -2
  227. package/src/sdk.ts +98 -29
  228. package/src/session/agent-session.ts +828 -191
  229. package/src/session/session-history-format.ts +2 -2
  230. package/src/session/session-listing.ts +35 -2
  231. package/src/session/session-manager.ts +46 -0
  232. package/src/slash-commands/builtin-registry.ts +158 -22
  233. package/src/slash-commands/helpers/usage-report.ts +23 -2
  234. package/src/ssh/__tests__/connection-manager-args.test.ts +69 -0
  235. package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +68 -0
  236. package/src/ssh/connection-manager.ts +12 -15
  237. package/src/ssh/file-transfer.ts +209 -0
  238. package/src/ssh/utils.ts +24 -0
  239. package/src/system-prompt.ts +60 -28
  240. package/src/task/executor.ts +96 -36
  241. package/src/task/index.ts +3 -3
  242. package/src/task/render.ts +14 -13
  243. package/src/task/worktree.ts +38 -1
  244. package/src/tiny/text.ts +49 -4
  245. package/src/tiny/worker.ts +3 -3
  246. package/src/tools/acp-bridge.ts +6 -1
  247. package/src/tools/bash.ts +2 -2
  248. package/src/tools/builtin-names.ts +26 -2
  249. package/src/tools/{find.ts → glob.ts} +48 -42
  250. package/src/tools/{search.ts → grep.ts} +311 -129
  251. package/src/tools/index.ts +13 -14
  252. package/src/tools/irc.ts +6 -2
  253. package/src/tools/path-utils.ts +64 -1
  254. package/src/tools/plan-mode-guard.ts +26 -13
  255. package/src/tools/read.ts +35 -6
  256. package/src/tools/render-utils.ts +14 -0
  257. package/src/tools/renderers.ts +15 -4
  258. package/src/tools/ssh.ts +17 -2
  259. package/src/tools/todo.ts +4 -26
  260. package/src/tools/write.ts +25 -9
  261. package/src/utils/active-repo-context.ts +143 -0
  262. package/src/utils/edit-mode.ts +19 -2
  263. package/src/utils/image-resize.ts +88 -7
  264. package/src/utils/lang-from-path.ts +3 -3
  265. package/src/utils/markit-cache.ts +166 -0
  266. package/src/utils/markit.ts +86 -18
  267. package/src/utils/prompt-path.ts +3 -0
  268. package/src/utils/shell-snapshot.ts +1 -1
  269. package/src/utils/title-generator.ts +1 -1
  270. package/src/web/search/provider.ts +54 -34
  271. package/src/web/search/providers/duckduckgo.ts +140 -0
  272. package/src/web/search/providers/firecrawl.ts +144 -0
  273. package/src/web/search/providers/tinyfish.ts +159 -0
  274. package/src/web/search/providers/xai.ts +292 -0
  275. package/src/web/search/providers/zai.ts +142 -56
  276. package/src/web/search/types.ts +6 -2
@@ -1,7 +1,15 @@
1
1
  import { execSync } from "node:child_process";
2
2
  import * as path from "node:path";
3
3
  import { registerCustomApi, unregisterCustomApis } from "@oh-my-pi/pi-ai/api-registry";
4
- import type { Api, Context, Model, ModelSpec, SimpleStreamOptions, ThinkingConfig } from "@oh-my-pi/pi-ai/types";
4
+ import type {
5
+ Api,
6
+ Context,
7
+ Model,
8
+ ModelSpec,
9
+ RemoteCompactionConfig,
10
+ SimpleStreamOptions,
11
+ ThinkingConfig,
12
+ } from "@oh-my-pi/pi-ai/types";
5
13
  import type { AssistantMessageEventStream } from "@oh-my-pi/pi-ai/utils/event-stream";
6
14
  import { buildModel } from "@oh-my-pi/pi-catalog/build";
7
15
  import { isVertexExpressOpenAIUrl } from "@oh-my-pi/pi-catalog/hosts";
@@ -71,6 +79,7 @@ import {
71
79
  discoverModelsByProviderType,
72
80
  getImplicitOllamaBaseUrl,
73
81
  getOllamaContextLengthOverride,
82
+ normalizeLiteLLMDiscoveryBaseUrl,
74
83
  } from "./model-discovery";
75
84
  import { ModelsConfigFile, type ProviderValidationModel, validateProviderConfiguration } from "./models-config";
76
85
  import type { ModelOverride, ModelsConfig, ProviderAuthMode } from "./models-config-schema";
@@ -95,6 +104,7 @@ interface ProviderOverride {
95
104
  apiKey?: string;
96
105
  authHeader?: boolean;
97
106
  compat?: ModelSpec<Api>["compat"];
107
+ remoteCompaction?: RemoteCompactionConfig<Api>;
98
108
  transport?: Model<Api>["transport"];
99
109
  }
100
110
 
@@ -127,7 +137,7 @@ interface ProviderOverride {
127
137
  export function mergeDiscoveredModel<TApi extends Api>(
128
138
  model: Model<TApi>,
129
139
  existing: Model<Api> | undefined,
130
- providerOverride?: Pick<ProviderOverride, "baseUrl" | "headers" | "transport">,
140
+ providerOverride?: Pick<ProviderOverride, "baseUrl" | "headers" | "remoteCompaction" | "transport">,
131
141
  ): Model<TApi> {
132
142
  if (existing) {
133
143
  const supportsTools = model.supportsTools ?? existing.supportsTools;
@@ -136,6 +146,10 @@ export function mergeDiscoveredModel<TApi extends Api>(
136
146
  baseUrl: providerOverride?.baseUrl ?? model.baseUrl ?? existing.baseUrl,
137
147
  headers: existing.headers ? { ...existing.headers, ...model.headers } : model.headers,
138
148
  transport: providerOverride?.transport ?? existing.transport ?? model.transport,
149
+ remoteCompaction: mergeProviderRemoteCompactionConfig(
150
+ mergeRemoteCompactionConfig(existing.remoteCompaction, model.remoteCompaction),
151
+ providerOverride?.remoteCompaction,
152
+ ),
139
153
  ...(supportsTools !== undefined ? { supportsTools } : {}),
140
154
  compat: model.compatConfig,
141
155
  } as ModelSpec<TApi>);
@@ -146,6 +160,10 @@ export function mergeDiscoveredModel<TApi extends Api>(
146
160
  baseUrl: providerOverride.baseUrl ?? model.baseUrl,
147
161
  headers: providerOverride.headers ? { ...model.headers, ...providerOverride.headers } : model.headers,
148
162
  ...(providerOverride.transport !== undefined ? { transport: providerOverride.transport } : {}),
163
+ remoteCompaction: mergeProviderRemoteCompactionConfig(
164
+ model.remoteCompaction,
165
+ providerOverride.remoteCompaction,
166
+ ),
149
167
  compat: model.compatConfig,
150
168
  } as ModelSpec<TApi>);
151
169
  }
@@ -353,6 +371,22 @@ function mergeCompat<TBase extends object, TOverride extends object>(
353
371
  return merged as TBase & TOverride;
354
372
  }
355
373
 
374
+ function mergeRemoteCompactionConfig(
375
+ baseConfig: RemoteCompactionConfig<Api> | undefined,
376
+ overrideConfig: RemoteCompactionConfig<Api> | undefined,
377
+ ): RemoteCompactionConfig<Api> | undefined {
378
+ if (!baseConfig) return overrideConfig;
379
+ if (!overrideConfig) return baseConfig;
380
+ return { ...baseConfig, ...overrideConfig };
381
+ }
382
+
383
+ function mergeProviderRemoteCompactionConfig(
384
+ modelConfig: RemoteCompactionConfig<Api> | undefined,
385
+ providerConfig: RemoteCompactionConfig<Api> | undefined,
386
+ ): RemoteCompactionConfig<Api> | undefined {
387
+ return mergeRemoteCompactionConfig(providerConfig, modelConfig);
388
+ }
389
+
356
390
  /**
357
391
  * Project a built model back to spec shape for the model-manager/cache
358
392
  * boundary: sparse compat comes from `compatConfig`, never from the resolved
@@ -380,6 +414,8 @@ interface ModelPatch {
380
414
  headers?: Record<string, string>;
381
415
  compat?: ModelSpec<Api>["compat"];
382
416
  contextPromotionTarget?: string;
417
+ compactionModel?: string;
418
+ remoteCompaction?: RemoteCompactionConfig<Api>;
383
419
  premiumMultiplier?: number;
384
420
  }
385
421
 
@@ -403,6 +439,10 @@ function applyModelPatch(base: Model<Api>, patch: ModelPatch, transport: ModelTr
403
439
  if (patch.maxTokens !== undefined) result.maxTokens = patch.maxTokens;
404
440
  if (patch.omitMaxOutputTokens !== undefined) result.omitMaxOutputTokens = patch.omitMaxOutputTokens;
405
441
  if (patch.contextPromotionTarget !== undefined) result.contextPromotionTarget = patch.contextPromotionTarget;
442
+ if (patch.compactionModel !== undefined) result.compactionModel = patch.compactionModel;
443
+ if (patch.remoteCompaction !== undefined) {
444
+ result.remoteCompaction = mergeRemoteCompactionConfig(base.remoteCompaction, patch.remoteCompaction);
445
+ }
406
446
  if (patch.premiumMultiplier !== undefined) result.premiumMultiplier = patch.premiumMultiplier;
407
447
  if (patch.cost) {
408
448
  result.cost = {
@@ -475,8 +515,6 @@ function mergeAuthHeader(
475
515
  /**
476
516
  * Decide whether a custom-yaml model should force OAuth-style request shaping.
477
517
  * - Explicit `auth: oauth` → force on.
478
- * - Explicit `auth: apiKey` / `auth: none` → leave unset (auto-detect by key prefix).
479
- * - No `auth` specified and `api: anthropic-messages` → default on. Custom Anthropic
480
518
  * endpoints are typically Claude-Code-style proxies (e.g. CLIProxyAPI) that expect
481
519
  * the cloaked request shape regardless of how the proxy itself is authenticated.
482
520
  * - Otherwise → unset.
@@ -497,6 +535,7 @@ function buildCustomModelOverlay(
497
535
  authHeader: boolean | undefined,
498
536
  providerCompat: ModelSpec<Api>["compat"] | undefined,
499
537
  providerAuth: ProviderAuthMode | undefined,
538
+ providerRemoteCompaction: RemoteCompactionConfig<Api> | undefined,
500
539
  modelDef: CustomModelDefinitionLike,
501
540
  ): CustomModelOverlay | undefined {
502
541
  const api = modelDef.api ?? providerApi;
@@ -518,6 +557,8 @@ function buildCustomModelOverlay(
518
557
  headers: mergeCustomModelHeaders(providerHeaders, modelDef.headers, authHeader, providerApiKey),
519
558
  compat: mergeCompat(providerCompat, modelDef.compat),
520
559
  contextPromotionTarget: modelDef.contextPromotionTarget,
560
+ compactionModel: modelDef.compactionModel,
561
+ remoteCompaction: mergeRemoteCompactionConfig(providerRemoteCompaction, modelDef.remoteCompaction),
521
562
  premiumMultiplier: modelDef.premiumMultiplier,
522
563
  isOAuth: resolveCustomModelIsOAuth(api, providerAuth),
523
564
  };
@@ -558,6 +599,8 @@ function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuil
558
599
  omitMaxOutputTokens: resolvedModel.omitMaxOutputTokens ?? reference?.omitMaxOutputTokens,
559
600
  compat: mergeCompat(reference?.compatConfig, resolvedModel.compat),
560
601
  contextPromotionTarget: resolvedModel.contextPromotionTarget,
602
+ compactionModel: resolvedModel.compactionModel,
603
+ remoteCompaction: resolvedModel.remoteCompaction,
561
604
  premiumMultiplier: resolvedModel.premiumMultiplier,
562
605
  isOAuth: resolvedModel.isOAuth,
563
606
  } as ModelSpec<Api>);
@@ -1078,12 +1121,25 @@ export class ModelRegistry {
1078
1121
  )
1079
1122
  : models;
1080
1123
 
1124
+ const withRemoteCompaction = providerConfig.remoteCompaction
1125
+ ? withDecoderMetadata.map(model =>
1126
+ buildModel({
1127
+ ...model,
1128
+ remoteCompaction: mergeProviderRemoteCompactionConfig(
1129
+ model.remoteCompaction,
1130
+ providerConfig.remoteCompaction,
1131
+ ),
1132
+ compat: model.compatConfig,
1133
+ } as ModelSpec<Api>),
1134
+ )
1135
+ : withDecoderMetadata;
1136
+
1081
1137
  if (providerConfig.provider !== "ollama" || providerConfig.api !== "openai-responses") {
1082
- return withDecoderMetadata;
1138
+ return withRemoteCompaction;
1083
1139
  }
1084
1140
 
1085
1141
  const contextLengthOverride = getOllamaContextLengthOverride();
1086
- return withDecoderMetadata.map(model => {
1142
+ return withRemoteCompaction.map(model => {
1087
1143
  const normalized =
1088
1144
  model.api === "openai-completions"
1089
1145
  ? buildModel({
@@ -1172,7 +1228,6 @@ export class ModelRegistry {
1172
1228
  const discoverableProviders: DiscoveryProviderConfig[] = [];
1173
1229
  const providerEntries = Object.entries(value.providers ?? {});
1174
1230
  const configuredProviders = new Set(Object.keys(value.providers ?? {}));
1175
-
1176
1231
  for (const [providerName, providerConfig] of providerEntries) {
1177
1232
  const resolvedProviderHeaders = resolveConfigHeaders(providerConfig.headers);
1178
1233
  // Always set overrides when baseUrl/headers/apiKey/authHeader/compat/disableStrictTools/transport are present
@@ -1183,15 +1238,20 @@ export class ModelRegistry {
1183
1238
  providerConfig.authHeader !== undefined ||
1184
1239
  providerConfig.compat ||
1185
1240
  providerConfig.disableStrictTools ||
1241
+ providerConfig.remoteCompaction ||
1186
1242
  providerConfig.transport
1187
1243
  ) {
1188
1244
  const disableStrictCompat = providerConfig.disableStrictTools ? { disableStrictTools: true } : undefined;
1189
1245
  overrides.set(providerName, {
1190
- baseUrl: providerConfig.baseUrl,
1246
+ baseUrl:
1247
+ providerConfig.discovery?.type === "litellm"
1248
+ ? normalizeLiteLLMDiscoveryBaseUrl(providerConfig.baseUrl)
1249
+ : providerConfig.baseUrl,
1191
1250
  headers: resolvedProviderHeaders,
1192
1251
  apiKey: providerConfig.apiKey,
1193
1252
  authHeader: providerConfig.authHeader,
1194
1253
  compat: mergeCompat(providerConfig.compat, disableStrictCompat),
1254
+ remoteCompaction: providerConfig.remoteCompaction,
1195
1255
  transport: providerConfig.transport,
1196
1256
  });
1197
1257
  }
@@ -1212,6 +1272,7 @@ export class ModelRegistry {
1212
1272
  baseUrl: providerConfig.baseUrl,
1213
1273
  headers: resolvedProviderHeaders,
1214
1274
  compat: mergeCompat(providerConfig.compat, disableStrictCompat),
1275
+ remoteCompaction: providerConfig.remoteCompaction,
1215
1276
  discovery: providerConfig.discovery,
1216
1277
  optional: false,
1217
1278
  });
@@ -1302,6 +1363,9 @@ export class ModelRegistry {
1302
1363
  if (providerConfig.discovery.type === "openai-models-list") {
1303
1364
  return `${providerConfig.provider}:openai-models-list-context-v2`;
1304
1365
  }
1366
+ if (providerConfig.discovery.type === "litellm") {
1367
+ return `${providerConfig.provider}:litellm-rich-v1`;
1368
+ }
1305
1369
  return providerConfig.provider;
1306
1370
  }
1307
1371
 
@@ -1599,12 +1663,18 @@ export class ModelRegistry {
1599
1663
  authHeader: override.authHeader ?? baseOverride?.authHeader,
1600
1664
  headers: override.headers ? { ...(baseOverride?.headers ?? {}), ...override.headers } : baseOverride?.headers,
1601
1665
  compat: override.compat ? mergeCompat(baseOverride?.compat, override.compat) : baseOverride?.compat,
1666
+ remoteCompaction: mergeRemoteCompactionConfig(baseOverride?.remoteCompaction, override.remoteCompaction),
1602
1667
  transport: override.transport ?? baseOverride?.transport,
1603
1668
  };
1604
1669
  }
1605
- #applyProviderTransportOverride<T extends { baseUrl?: string; headers?: Record<string, string> }>(
1670
+ #applyProviderTransportOverride<
1671
+ T extends { baseUrl?: string; headers?: Record<string, string>; remoteCompaction?: RemoteCompactionConfig<Api> },
1672
+ >(
1606
1673
  entry: T,
1607
- override: Pick<ProviderOverride, "baseUrl" | "headers" | "authHeader" | "apiKey" | "transport">,
1674
+ override: Pick<
1675
+ ProviderOverride,
1676
+ "baseUrl" | "headers" | "authHeader" | "apiKey" | "remoteCompaction" | "transport"
1677
+ >,
1608
1678
  ): T {
1609
1679
  const headers = mergeAuthHeader(
1610
1680
  override.headers ? { ...entry.headers, ...override.headers } : entry.headers,
@@ -1618,6 +1688,7 @@ export class ModelRegistry {
1618
1688
  // Preserve the model's existing transport when the override omits one;
1619
1689
  // providers without a `transport` field keep the default per-API dispatch.
1620
1690
  ...(override.transport !== undefined ? { transport: override.transport } : {}),
1691
+ remoteCompaction: mergeProviderRemoteCompactionConfig(entry.remoteCompaction, override.remoteCompaction),
1621
1692
  };
1622
1693
  }
1623
1694
  #applyRuntimeProviderOverrides(models: Model<Api>[]): Model<Api>[] {
@@ -1719,7 +1790,6 @@ export class ModelRegistry {
1719
1790
 
1720
1791
  #parseModels(config: ModelsConfig): CustomModelOverlay[] {
1721
1792
  const models: CustomModelOverlay[] = [];
1722
-
1723
1793
  for (const [providerName, providerConfig] of Object.entries(config.providers ?? {})) {
1724
1794
  const modelDefs = providerConfig.models ?? [];
1725
1795
  if (modelDefs.length === 0) continue; // Override-only, no custom models
@@ -1740,6 +1810,7 @@ export class ModelRegistry {
1740
1810
  providerConfig.authHeader,
1741
1811
  providerCompat,
1742
1812
  (providerConfig.auth as ProviderAuthMode | undefined) ?? undefined,
1813
+ providerConfig.remoteCompaction,
1743
1814
  modelDef as CustomModelDefinitionLike,
1744
1815
  );
1745
1816
  if (!model) continue;
@@ -2147,6 +2218,7 @@ export class ModelRegistry {
2147
2218
  config.authHeader,
2148
2219
  config.compat,
2149
2220
  undefined,
2221
+ config.remoteCompaction,
2150
2222
  modelDef as CustomModelDefinitionLike,
2151
2223
  );
2152
2224
  if (!overlay) {
@@ -2214,6 +2286,7 @@ export class ModelRegistry {
2214
2286
  providerAuthHeader,
2215
2287
  providerCompat,
2216
2288
  undefined,
2289
+ config.remoteCompaction,
2217
2290
  modelDef as CustomModelDefinitionLike,
2218
2291
  );
2219
2292
  if (overlay) results.push(finalizeCustomModel(overlay, { useDefaults: true }));
@@ -2231,6 +2304,7 @@ export class ModelRegistry {
2231
2304
  config.headers ||
2232
2305
  config.apiKey ||
2233
2306
  config.authHeader !== undefined ||
2307
+ config.remoteCompaction !== undefined ||
2234
2308
  config.transport !== undefined
2235
2309
  ) {
2236
2310
  const transportOverride = {
@@ -2238,6 +2312,7 @@ export class ModelRegistry {
2238
2312
  headers: config.headers,
2239
2313
  apiKey: config.apiKey,
2240
2314
  authHeader: config.authHeader,
2315
+ remoteCompaction: config.remoteCompaction,
2241
2316
  transport: config.transport,
2242
2317
  };
2243
2318
  const nextRuntimeOverride = this.#mergeProviderOverride(
@@ -2299,6 +2374,7 @@ export interface ProviderConfigInput {
2299
2374
  streamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
2300
2375
  headers?: Record<string, string>;
2301
2376
  compat?: ModelSpec<Api>["compat"];
2377
+ remoteCompaction?: RemoteCompactionConfig<Api>;
2302
2378
  authHeader?: boolean;
2303
2379
  /** Streaming transport override — see {@link Model.transport}. */
2304
2380
  transport?: Model<Api>["transport"];
@@ -2333,6 +2409,8 @@ export interface ProviderConfigInput {
2333
2409
  headers?: Record<string, string>;
2334
2410
  compat?: ModelSpec<Api>["compat"];
2335
2411
  contextPromotionTarget?: string;
2412
+ compactionModel?: string;
2413
+ remoteCompaction?: RemoteCompactionConfig<Api>;
2336
2414
  premiumMultiplier?: number;
2337
2415
  }>;
2338
2416
  }
@@ -877,6 +877,23 @@ function shouldInheritDefaultBeforePriority(role: ModelRole): boolean {
877
877
  return role === "smol" || role === "slow" || role === "designer";
878
878
  }
879
879
 
880
+ /**
881
+ * Roles that have no priority.json chain of their own reuse another role's
882
+ * list. The advisor — a second-opinion reviewer — defaults to the `slow`
883
+ * reasoning chain, but (unlike the `slow` role, see
884
+ * {@link shouldInheritDefaultBeforePriority}) never inherits the primary's
885
+ * model, so it stays a distinct strong model out of the box.
886
+ */
887
+ const ROLE_PRIORITY_ALIAS: Partial<Record<ModelRole, keyof typeof MODEL_PRIO>> = {
888
+ advisor: "slow",
889
+ };
890
+
891
+ /** Built-in priority patterns for a role, following {@link ROLE_PRIORITY_ALIAS}. */
892
+ function rolePriorityDefaults(role: ModelRole): string[] {
893
+ const key = ROLE_PRIORITY_ALIAS[role] ?? (role as keyof typeof MODEL_PRIO);
894
+ return normalizeModelPatternList(MODEL_PRIO[key]);
895
+ }
896
+
880
897
  function resolveDefaultInheritedPatterns(
881
898
  role: ModelRole,
882
899
  configuredDefault: string | undefined,
@@ -939,7 +956,7 @@ function resolveConfiguredRolePattern(
939
956
 
940
957
  const configured = settings?.getModelRole(role)?.trim();
941
958
  const configuredDefault = settings?.getModelRole(DEFAULT_MODEL_ROLE)?.trim();
942
- const roleDefaults = normalizeModelPatternList(MODEL_PRIO[role as keyof typeof MODEL_PRIO]);
959
+ const roleDefaults = rolePriorityDefaults(role);
943
960
  const resolved = configured
944
961
  ? normalizeModelPatternList(configured)
945
962
  : resolveDefaultInheritedPatterns(role, configuredDefault, roleDefaults, settings, visited);
@@ -1245,6 +1262,27 @@ export function resolveRoleSelection(
1245
1262
  return undefined;
1246
1263
  }
1247
1264
 
1265
+ /**
1266
+ * Resolve the model for the `advisor` role. A configured `modelRoles.advisor`
1267
+ * wins outright (a bad override surfaces as no model rather than silently
1268
+ * running something else); when unset it falls back to the `slow` priority
1269
+ * chain via {@link ROLE_PRIORITY_ALIAS} — a strong reasoning model that, unlike
1270
+ * the `slow` role itself, never inherits the primary's model. Returns undefined
1271
+ * only when no candidate in the resolved chain is available.
1272
+ */
1273
+ export function resolveAdvisorRoleSelection(
1274
+ settings: Settings,
1275
+ availableModels: Model<Api>[],
1276
+ modelRegistry?: CanonicalModelRegistry,
1277
+ ): { model: Model<Api>; thinkingLevel?: ThinkingLevel } | undefined {
1278
+ const resolved = resolveModelRoleValue(`${PREFIX_MODEL_ROLE}advisor`, availableModels, {
1279
+ settings,
1280
+ matchPreferences: getModelMatchPreferences(settings),
1281
+ modelRegistry,
1282
+ });
1283
+ return resolved.model ? { model: resolved.model, thinkingLevel: resolved.thinkingLevel } : undefined;
1284
+ }
1285
+
1248
1286
  function resolveExactCanonicalScopePattern(
1249
1287
  pattern: string,
1250
1288
  modelRegistry: Pick<ModelRegistry, "getCanonicalVariants">,
@@ -70,6 +70,10 @@ export const OpenAICompatSchema = type({
70
70
  "whenThinking?": OpenAICompatFieldsSchema,
71
71
  });
72
72
 
73
+ const ApiSchema = type(
74
+ '"openai-completions" | "openai-responses" | "openai-codex-responses" | "azure-openai-responses" | "anthropic-messages" | "google-generative-ai" | "google-gemini-cli" | "google-vertex"',
75
+ );
76
+
73
77
  const EffortSchema = type('"minimal" | "low" | "medium" | "high" | "xhigh"');
74
78
 
75
79
  const ThinkingControlModeSchema = type(
@@ -118,11 +122,25 @@ const ModelThinkingSchema = type({
118
122
  };
119
123
  });
120
124
 
125
+ const RemoteCompactionSchema = type({
126
+ "enabled?": "boolean",
127
+ "api?": ApiSchema,
128
+ "endpoint?": "string",
129
+ "model?": "string",
130
+ }).narrow((value, ctx) => {
131
+ if (value.endpoint !== undefined && typeof value.endpoint === "string" && value.endpoint.length === 0) {
132
+ return ctx.mustBe("remoteCompaction.endpoint a non-empty string");
133
+ }
134
+ if (value.model !== undefined && typeof value.model === "string" && value.model.length === 0) {
135
+ return ctx.mustBe("remoteCompaction.model a non-empty string");
136
+ }
137
+ return true;
138
+ });
139
+
121
140
  const ModelDefinitionSchema = type({
122
141
  id: "string",
123
142
  "name?": "string",
124
- "api?":
125
- '"openai-completions" | "openai-responses" | "openai-codex-responses" | "azure-openai-responses" | "anthropic-messages" | "google-generative-ai" | "google-gemini-cli" | "google-vertex"',
143
+ "api?": ApiSchema,
126
144
  "baseUrl?": "string",
127
145
  "reasoning?": "boolean",
128
146
  "thinking?": ModelThinkingSchema,
@@ -141,6 +159,8 @@ const ModelDefinitionSchema = type({
141
159
  "headers?": { "[string]": "string" },
142
160
  "compat?": OpenAICompatSchema,
143
161
  "contextPromotionTarget?": "string",
162
+ "compactionModel?": "string",
163
+ "remoteCompaction?": RemoteCompactionSchema,
144
164
  }).narrow((value, ctx) => {
145
165
  // Enforce id non-empty
146
166
  if (typeof value.id === "string" && value.id.length === 0) {
@@ -159,6 +179,13 @@ const ModelDefinitionSchema = type({
159
179
  ) {
160
180
  return ctx.mustBe("contextPromotionTarget a non-empty string");
161
181
  }
182
+ if (
183
+ value.compactionModel !== undefined &&
184
+ typeof value.compactionModel === "string" &&
185
+ value.compactionModel.length === 0
186
+ ) {
187
+ return ctx.mustBe("compactionModel a non-empty string");
188
+ }
162
189
  return true;
163
190
  });
164
191
 
@@ -181,6 +208,8 @@ export const ModelOverrideSchema = type({
181
208
  "headers?": { "[string]": "string" },
182
209
  "compat?": OpenAICompatSchema,
183
210
  "contextPromotionTarget?": "string",
211
+ "compactionModel?": "string",
212
+ "remoteCompaction?": RemoteCompactionSchema,
184
213
  }).narrow((value, ctx) => {
185
214
  if (value.name !== undefined && typeof value.name === "string" && value.name.length === 0) {
186
215
  return ctx.mustBe("name a non-empty string");
@@ -192,13 +221,20 @@ export const ModelOverrideSchema = type({
192
221
  ) {
193
222
  return ctx.mustBe("contextPromotionTarget a non-empty string");
194
223
  }
224
+ if (
225
+ value.compactionModel !== undefined &&
226
+ typeof value.compactionModel === "string" &&
227
+ value.compactionModel.length === 0
228
+ ) {
229
+ return ctx.mustBe("compactionModel a non-empty string");
230
+ }
195
231
  return true;
196
232
  });
197
233
 
198
234
  export type ModelOverride = typeof ModelOverrideSchema.infer;
199
235
 
200
236
  export const ProviderDiscoverySchema = type({
201
- type: '"ollama" | "llama.cpp" | "lm-studio" | "openai-models-list" | "proxy"',
237
+ type: '"ollama" | "llama.cpp" | "lm-studio" | "openai-models-list" | "proxy" | "litellm"',
202
238
  });
203
239
 
204
240
  export const ProviderAuthSchema = type('"apiKey" | "none" | "oauth"');
@@ -209,10 +245,10 @@ export type ProviderDiscovery = typeof ProviderDiscoverySchema.infer;
209
245
  const ProviderConfigSchema = type({
210
246
  "baseUrl?": "string",
211
247
  "apiKey?": "string",
212
- "api?":
213
- '"openai-completions" | "openai-responses" | "openai-codex-responses" | "azure-openai-responses" | "anthropic-messages" | "google-generative-ai" | "google-gemini-cli" | "google-vertex"',
248
+ "api?": ApiSchema,
214
249
  "headers?": { "[string]": "string" },
215
250
  "compat?": OpenAICompatSchema,
251
+ "remoteCompaction?": RemoteCompactionSchema,
216
252
  "authHeader?": "boolean",
217
253
  "auth?": ProviderAuthSchema,
218
254
  "discovery?": ProviderDiscoverySchema,
@@ -30,6 +30,7 @@ export interface ProviderValidationConfig {
30
30
  oauthConfigured?: boolean;
31
31
  discovery?: ProviderDiscovery;
32
32
  compat?: ModelSpec<Api>["compat"];
33
+ remoteCompaction?: unknown;
33
34
  disableStrictTools?: boolean;
34
35
  modelOverrides?: Record<string, unknown>;
35
36
  models: ProviderValidationModel[];
@@ -53,11 +54,12 @@ export function validateProviderConfiguration(
53
54
  !config.apiKey &&
54
55
  config.auth !== "none" &&
55
56
  !config.disableStrictTools &&
57
+ !config.remoteCompaction &&
56
58
  !hasModelOverrides &&
57
59
  !config.discovery
58
60
  ) {
59
61
  throw new Error(
60
- `Provider ${providerName}: must specify "baseUrl", "headers", "apiKey", "auth: none", "compat", "disableStrictTools", "modelOverrides", "discovery", or "models"`,
62
+ `Provider ${providerName}: must specify "baseUrl", "headers", "apiKey", "auth: none", "compat", "disableStrictTools", "remoteCompaction", "modelOverrides", "discovery", or "models"`,
61
63
  );
62
64
  }
63
65
  }
@@ -120,6 +122,7 @@ export const ModelsConfigFile = new ConfigFile<ModelsConfig>("models", ModelsCon
120
122
  auth: (providerConfig.auth ?? "apiKey") as ProviderAuthMode,
121
123
  discovery: providerConfig.discovery as ProviderDiscovery | undefined,
122
124
  compat: providerConfig.compat,
125
+ remoteCompaction: providerConfig.remoteCompaction,
123
126
  disableStrictTools: providerConfig.disableStrictTools,
124
127
  modelOverrides: providerConfig.modelOverrides,
125
128
  models: (providerConfig.models ?? []) as ProviderValidationModel[],