@sagmans/dsh-tui 0.7.0 → 0.9.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 (242) hide show
  1. package/.agents/skills/dsh-tui-dogfood/SKILL.md +5 -1
  2. package/.agents/skills/dsh-tui-dogfood/scripts/run-plugin-from-worktree.sh +26 -2
  3. package/.agents/skills/dsh-tui-update-models/SKILL.md +95 -17
  4. package/.agents/skills/dsh-tui-update-models/references/model-wiring.md +158 -28
  5. package/.agents/skills/dsh-tui-update-models/scripts/dump-model-catalog.mjs +152 -16
  6. package/README.md +117 -53
  7. package/lib/agent/history.d.ts +0 -2
  8. package/lib/agent/history.d.ts.map +1 -1
  9. package/lib/agent/history.js +1 -1
  10. package/lib/agent/history.js.map +1 -1
  11. package/lib/agent/host.d.ts +0 -25
  12. package/lib/agent/host.d.ts.map +1 -1
  13. package/lib/agent/host.js +2 -2
  14. package/lib/agent/host.js.map +1 -1
  15. package/lib/agent/model.d.ts +38 -6
  16. package/lib/agent/model.d.ts.map +1 -1
  17. package/lib/agent/model.js +38 -14
  18. package/lib/agent/model.js.map +1 -1
  19. package/lib/agent/prompt-history.d.ts +7 -12
  20. package/lib/agent/prompt-history.d.ts.map +1 -1
  21. package/lib/agent/prompt-history.js +10 -31
  22. package/lib/agent/prompt-history.js.map +1 -1
  23. package/lib/cards/composition.d.ts +1 -1
  24. package/lib/cards/composition.d.ts.map +1 -1
  25. package/lib/cards/composition.js +7 -0
  26. package/lib/cards/composition.js.map +1 -1
  27. package/lib/cards/presenter.d.ts.map +1 -1
  28. package/lib/cards/presenter.js +32 -2
  29. package/lib/cards/presenter.js.map +1 -1
  30. package/lib/cards.d.ts +27 -2
  31. package/lib/cards.d.ts.map +1 -1
  32. package/lib/cards.js +17 -3
  33. package/lib/cards.js.map +1 -1
  34. package/lib/compat/probe.js +1 -1
  35. package/lib/compat/probe.js.map +1 -1
  36. package/lib/config.d.ts +10 -0
  37. package/lib/config.d.ts.map +1 -1
  38. package/lib/config.js +18 -0
  39. package/lib/config.js.map +1 -1
  40. package/lib/contracts.d.ts +2 -0
  41. package/lib/contracts.d.ts.map +1 -1
  42. package/lib/export.d.ts +12 -0
  43. package/lib/export.d.ts.map +1 -1
  44. package/lib/export.js +34 -3
  45. package/lib/export.js.map +1 -1
  46. package/lib/herdr/client.d.ts +8 -9
  47. package/lib/herdr/client.d.ts.map +1 -1
  48. package/lib/herdr/client.js +14 -2
  49. package/lib/herdr/client.js.map +1 -1
  50. package/lib/herdr/constants.d.ts +18 -0
  51. package/lib/herdr/constants.d.ts.map +1 -1
  52. package/lib/herdr/constants.js +18 -0
  53. package/lib/herdr/constants.js.map +1 -1
  54. package/lib/herdr/reporter.d.ts +20 -4
  55. package/lib/herdr/reporter.d.ts.map +1 -1
  56. package/lib/herdr/reporter.js +70 -20
  57. package/lib/herdr/reporter.js.map +1 -1
  58. package/lib/herdr/state.d.ts +10 -10
  59. package/lib/herdr/state.d.ts.map +1 -1
  60. package/lib/herdr/state.js +22 -5
  61. package/lib/herdr/state.js.map +1 -1
  62. package/lib/identity.d.ts +0 -2
  63. package/lib/identity.d.ts.map +1 -1
  64. package/lib/identity.js +0 -1
  65. package/lib/identity.js.map +1 -1
  66. package/lib/index.d.ts +1 -4
  67. package/lib/index.d.ts.map +1 -1
  68. package/lib/index.js +15 -5
  69. package/lib/index.js.map +1 -1
  70. package/lib/input/keymap.d.ts +1 -7
  71. package/lib/input/keymap.d.ts.map +1 -1
  72. package/lib/input/keymap.js +3 -11
  73. package/lib/input/keymap.js.map +1 -1
  74. package/lib/input/submission.js +2 -2
  75. package/lib/input/submission.js.map +1 -1
  76. package/lib/install-skills.d.ts +20 -1
  77. package/lib/install-skills.d.ts.map +1 -1
  78. package/lib/install-skills.js +84 -9
  79. package/lib/install-skills.js.map +1 -1
  80. package/lib/model-list.d.ts.map +1 -1
  81. package/lib/model-list.js +17 -5
  82. package/lib/model-list.js.map +1 -1
  83. package/lib/queue.d.ts +0 -2
  84. package/lib/queue.d.ts.map +1 -1
  85. package/lib/queue.js +1 -1
  86. package/lib/queue.js.map +1 -1
  87. package/lib/settings-notice.d.ts +10 -1
  88. package/lib/settings-notice.d.ts.map +1 -1
  89. package/lib/settings-notice.js +17 -7
  90. package/lib/settings-notice.js.map +1 -1
  91. package/lib/spacing.d.ts +40 -0
  92. package/lib/spacing.d.ts.map +1 -0
  93. package/lib/spacing.js +54 -0
  94. package/lib/spacing.js.map +1 -0
  95. package/lib/stash/paths.d.ts +19 -10
  96. package/lib/stash/paths.d.ts.map +1 -1
  97. package/lib/stash/paths.js +52 -23
  98. package/lib/stash/paths.js.map +1 -1
  99. package/lib/stash/schema.d.ts +13 -12
  100. package/lib/stash/schema.d.ts.map +1 -1
  101. package/lib/stash/schema.js +11 -9
  102. package/lib/stash/schema.js.map +1 -1
  103. package/lib/stash/store.d.ts.map +1 -1
  104. package/lib/stash/store.js +9 -19
  105. package/lib/stash/store.js.map +1 -1
  106. package/lib/stash.d.ts +17 -10
  107. package/lib/stash.d.ts.map +1 -1
  108. package/lib/stash.js +31 -20
  109. package/lib/stash.js.map +1 -1
  110. package/lib/subagents.d.ts +0 -4
  111. package/lib/subagents.d.ts.map +1 -1
  112. package/lib/subagents.js +2 -2
  113. package/lib/subagents.js.map +1 -1
  114. package/lib/surface/appearance.d.ts +3 -0
  115. package/lib/surface/appearance.d.ts.map +1 -1
  116. package/lib/surface/appearance.js +5 -0
  117. package/lib/surface/appearance.js.map +1 -1
  118. package/lib/surface/background-work.d.ts +2 -0
  119. package/lib/surface/background-work.d.ts.map +1 -1
  120. package/lib/surface/background-work.js +45 -3
  121. package/lib/surface/background-work.js.map +1 -1
  122. package/lib/surface/commands.d.ts +2 -0
  123. package/lib/surface/commands.d.ts.map +1 -1
  124. package/lib/surface/commands.js +32 -6
  125. package/lib/surface/commands.js.map +1 -1
  126. package/lib/surface/modal-input.d.ts +3 -2
  127. package/lib/surface/modal-input.d.ts.map +1 -1
  128. package/lib/surface/modal-input.js +25 -8
  129. package/lib/surface/modal-input.js.map +1 -1
  130. package/lib/surface/model-choice.d.ts.map +1 -1
  131. package/lib/surface/model-choice.js +18 -18
  132. package/lib/surface/model-choice.js.map +1 -1
  133. package/lib/surface/preset-choice.d.ts.map +1 -1
  134. package/lib/surface/preset-choice.js +18 -10
  135. package/lib/surface/preset-choice.js.map +1 -1
  136. package/lib/surface/prompt-memory.d.ts +3 -1
  137. package/lib/surface/prompt-memory.d.ts.map +1 -1
  138. package/lib/surface/prompt-memory.js +10 -11
  139. package/lib/surface/prompt-memory.js.map +1 -1
  140. package/lib/surface/session-lifecycle.d.ts +6 -1
  141. package/lib/surface/session-lifecycle.d.ts.map +1 -1
  142. package/lib/surface/session-lifecycle.js +30 -8
  143. package/lib/surface/session-lifecycle.js.map +1 -1
  144. package/lib/surface/session-view.d.ts.map +1 -1
  145. package/lib/surface/session-view.js +1 -3
  146. package/lib/surface/session-view.js.map +1 -1
  147. package/lib/surface/staged-turns.d.ts +0 -2
  148. package/lib/surface/staged-turns.d.ts.map +1 -1
  149. package/lib/surface/staged-turns.js +37 -6
  150. package/lib/surface/staged-turns.js.map +1 -1
  151. package/lib/terminal/external-editor.d.ts +16 -0
  152. package/lib/terminal/external-editor.d.ts.map +1 -1
  153. package/lib/terminal/external-editor.js +38 -6
  154. package/lib/terminal/external-editor.js.map +1 -1
  155. package/lib/terminal/host-writes.d.ts +15 -2
  156. package/lib/terminal/host-writes.d.ts.map +1 -1
  157. package/lib/terminal/host-writes.js +10 -1
  158. package/lib/terminal/host-writes.js.map +1 -1
  159. package/lib/terminal/restore.d.ts +1 -2
  160. package/lib/terminal/restore.d.ts.map +1 -1
  161. package/lib/terminal/restore.js +1 -9
  162. package/lib/terminal/restore.js.map +1 -1
  163. package/lib/terminal/signals.d.ts +0 -1
  164. package/lib/terminal/signals.d.ts.map +1 -1
  165. package/lib/terminal/signals.js +1 -1
  166. package/lib/terminal/signals.js.map +1 -1
  167. package/lib/terminal/title.d.ts.map +1 -1
  168. package/lib/terminal/title.js +14 -3
  169. package/lib/terminal/title.js.map +1 -1
  170. package/lib/terminal/warning-screen.d.ts.map +1 -1
  171. package/lib/terminal/warning-screen.js +12 -1
  172. package/lib/terminal/warning-screen.js.map +1 -1
  173. package/lib/theme-capability.d.ts +0 -2
  174. package/lib/theme-capability.d.ts.map +1 -1
  175. package/lib/theme-capability.js +1 -1
  176. package/lib/theme-capability.js.map +1 -1
  177. package/lib/theme-defaults.d.ts.map +1 -1
  178. package/lib/theme-defaults.js +7 -0
  179. package/lib/theme-defaults.js.map +1 -1
  180. package/lib/theme-files.d.ts +1 -1
  181. package/lib/theme-files.d.ts.map +1 -1
  182. package/lib/theme-files.js +1 -1
  183. package/lib/theme-files.js.map +1 -1
  184. package/lib/theme-schema.d.ts +6 -6
  185. package/lib/theme-settings.d.ts +13 -8
  186. package/lib/theme-settings.d.ts.map +1 -1
  187. package/lib/theme-settings.js +17 -3
  188. package/lib/theme-settings.js.map +1 -1
  189. package/lib/theme-tokens.d.ts +1 -1
  190. package/lib/theme-tokens.d.ts.map +1 -1
  191. package/lib/theme-tokens.js +7 -2
  192. package/lib/theme-tokens.js.map +1 -1
  193. package/lib/transcript.d.ts +2 -2
  194. package/lib/transcript.d.ts.map +1 -1
  195. package/lib/transcript.js +9 -4
  196. package/lib/transcript.js.map +1 -1
  197. package/lib/ui/dock.d.ts +2 -0
  198. package/lib/ui/dock.d.ts.map +1 -1
  199. package/lib/ui/dock.js +18 -3
  200. package/lib/ui/dock.js.map +1 -1
  201. package/lib/ui/gap.d.ts +28 -0
  202. package/lib/ui/gap.d.ts.map +1 -0
  203. package/lib/ui/gap.js +47 -0
  204. package/lib/ui/gap.js.map +1 -0
  205. package/lib/ui/gutter.d.ts +25 -0
  206. package/lib/ui/gutter.d.ts.map +1 -0
  207. package/lib/ui/gutter.js +31 -0
  208. package/lib/ui/gutter.js.map +1 -0
  209. package/lib/ui/layout.d.ts +13 -1
  210. package/lib/ui/layout.d.ts.map +1 -1
  211. package/lib/ui/layout.js +32 -6
  212. package/lib/ui/layout.js.map +1 -1
  213. package/lib/ui/queue.d.ts +0 -4
  214. package/lib/ui/queue.d.ts.map +1 -1
  215. package/lib/ui/queue.js +2 -2
  216. package/lib/ui/queue.js.map +1 -1
  217. package/lib/ui/rows.d.ts +0 -8
  218. package/lib/ui/rows.d.ts.map +1 -1
  219. package/lib/ui/rows.js +1 -9
  220. package/lib/ui/rows.js.map +1 -1
  221. package/lib/ui/stash-picker.d.ts +2 -2
  222. package/lib/ui/stash-picker.d.ts.map +1 -1
  223. package/lib/ui/stash-picker.js +3 -3
  224. package/lib/ui/stash-picker.js.map +1 -1
  225. package/lib/ui/view/gate-card.d.ts.map +1 -1
  226. package/lib/ui/view/gate-card.js +4 -1
  227. package/lib/ui/view/gate-card.js.map +1 -1
  228. package/lib/ui/view/tool-card.d.ts +17 -2
  229. package/lib/ui/view/tool-card.d.ts.map +1 -1
  230. package/lib/ui/view/tool-card.js +40 -8
  231. package/lib/ui/view/tool-card.js.map +1 -1
  232. package/lib/ui/view/transcript-message.d.ts +3 -0
  233. package/lib/ui/view/transcript-message.d.ts.map +1 -1
  234. package/lib/ui/view/transcript-message.js +8 -1
  235. package/lib/ui/view/transcript-message.js.map +1 -1
  236. package/lib/ui/view.d.ts +36 -9
  237. package/lib/ui/view.d.ts.map +1 -1
  238. package/lib/ui/view.js +90 -32
  239. package/lib/ui/view.js.map +1 -1
  240. package/package.json +3 -1
  241. package/themes/deepseek-blue.yaml +3 -0
  242. package/themes/violet-orbit.yaml +16 -9
@@ -8,11 +8,23 @@
8
8
  * invisible in the UI. This reads the same two inputs the resolver does - the
9
9
  * owned catalog in the profile patch, and the installed pi-ai provider data -
10
10
  * and names where each number came from: "installed" for the source's own
11
- * entry, "template:<id>" for a clone, "metadata" for what the catalog declares.
11
+ * entry, "template:<id>" for a clone, "route" for the protocol a sourceless
12
+ * route declares once for every model it serves, "metadata" for what the
13
+ * catalog declares.
14
+ *
12
15
  * An entry the catalog spells out completely needs no installed entry at all,
13
- * so a missing harness install must not hide it.
16
+ * so a missing harness install must not hide it. A `filter` route is the
17
+ * exception: its membership is the installed catalog's, so it cannot be read
18
+ * without that install. A declaration the plugin's resolver would refuse is
19
+ * reported as a refusal here too, because a catalog that cannot compose serves
20
+ * nothing.
21
+ *
22
+ * Usage: dump-model-catalog.mjs [--home <dsh home>] [--profile <name>]
23
+ * [--data <pi-ai data dir>] [--json]
14
24
  *
15
- * Usage: dump-model-catalog.mjs [--home <dsh home>] [--profile <name>] [--json]
25
+ * `--data` names the installed provider-data directory to inherit from, which
26
+ * is how a reading is checked where no harness is installed - a `filter` route
27
+ * and every template clone need one.
16
28
  */
17
29
  import { execFileSync } from 'node:child_process'
18
30
  import { readFileSync, realpathSync } from 'node:fs'
@@ -23,9 +35,15 @@ import { fileURLToPath } from 'node:url'
23
35
  const LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']
24
36
  /** Fields a catalog entry may inherit, and the layer each value came from. */
25
37
  const INHERITED_FIELDS = ['contextWindow', 'maxTokens', 'cost', 'input', 'reasoning', 'thinkingLevelMap', 'api']
38
+ /** What a model with no installed sibling and no template must state itself. */
39
+ const REQUIRED_METADATA = ['api', 'reasoning', 'input', 'cost', 'contextWindow', 'maxTokens']
40
+ const COST_FIELDS = ['input', 'output', 'cacheRead', 'cacheWrite']
26
41
  const INSTALLED = 'installed'
27
42
  const METADATA = 'metadata'
43
+ const ROUTE = 'route'
28
44
  const ABSENT = 'none'
45
+ /** The only wildcard a `filter` pattern carries; every other character is itself. */
46
+ const WILDCARD = '*'
29
47
 
30
48
  /** The levels pi-ai offers: a null mapping removes one, and xhigh/max need one. */
31
49
  export function supportedLevels(levelMap) {
@@ -38,12 +56,13 @@ export function supportedLevels(levelMap) {
38
56
  }
39
57
 
40
58
  function parseArgs(argv) {
41
- const options = { home: process.env.DSH_HOME ?? join(homedir(), '.dsh'), profile: 'tui', json: false }
59
+ const options = { home: process.env.DSH_HOME ?? join(homedir(), '.dsh'), profile: 'tui', data: undefined, json: false }
42
60
  for (let index = 0; index < argv.length; index += 1) {
43
61
  const flag = argv[index]
44
62
  if (flag === '--json') options.json = true
45
63
  else if (flag === '--home') options.home = argv[++index]
46
64
  else if (flag === '--profile') options.profile = argv[++index]
65
+ else if (flag === '--data') options.data = argv[++index]
47
66
  else throw new Error('unknown argument ' + flag)
48
67
  }
49
68
  return options
@@ -74,7 +93,7 @@ export function catalogConfig(patch) {
74
93
  }
75
94
 
76
95
  export function providerData(directory, source) {
77
- if (directory === undefined) return undefined
96
+ if (directory === undefined || source === undefined) return undefined
78
97
  try {
79
98
  return JSON.parse(readFileSync(join(directory, source + '.json'), 'utf8'))
80
99
  } catch {
@@ -82,6 +101,28 @@ export function providerData(directory, source) {
82
101
  }
83
102
  }
84
103
 
104
+ /** Installed models in the order the source publishes them, one api map at a time. */
105
+ function installedModels(data) {
106
+ if (data === undefined) return []
107
+ return Object.entries(data).flatMap(([api, models]) => Object.entries(models)
108
+ .map(([id, model]) => ({ ...model, id, api: model.api ?? api })))
109
+ }
110
+
111
+ /** Mirror the resolver's pattern rule so a filter reads the same here and there. */
112
+ function patternToRegExp(pattern) {
113
+ const escaped = pattern.split(WILDCARD).map(part => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
114
+ return new RegExp('^' + escaped.join('.*') + '$')
115
+ }
116
+
117
+ /** A filter keeps installed facts and order, then drops what exclude names. */
118
+ export function expandFilter(data, filter) {
119
+ const include = filter?.include === undefined ? undefined : filter.include.map(patternToRegExp)
120
+ const exclude = filter?.exclude === undefined ? [] : filter.exclude.map(patternToRegExp)
121
+ return installedModels(data)
122
+ .filter(model => (include === undefined || include.some(pattern => pattern.test(model.id)))
123
+ && !exclude.some(pattern => pattern.test(model.id)))
124
+ }
125
+
85
126
  /** Look one id up across a provider's api maps; an api in the spec disambiguates. */
86
127
  function lookup(data, id, api) {
87
128
  if (data === undefined) return undefined
@@ -91,8 +132,13 @@ function lookup(data, id, api) {
91
132
  return undefined
92
133
  }
93
134
 
135
+ function costOf(cost) {
136
+ if (cost === undefined) return undefined
137
+ return '$' + COST_FIELDS.map(field => cost[field]).join('/') + ' per 1M'
138
+ }
139
+
94
140
  /** Resolve one catalog entry against the installed data it may inherit from. */
95
- export function resolve(spec, data) {
141
+ export function resolve(spec, data, routeApi) {
96
142
  const metadata = spec.metadata ?? {}
97
143
  const declared = Object.keys(metadata).length > 0
98
144
  const cloneOf = spec.template ?? spec.id
@@ -101,37 +147,88 @@ export function resolve(spec, data) {
101
147
  return { error: 'no source entry for ' + cloneOf + '; declare a template or metadata' }
102
148
  }
103
149
  const merged = { ...base, ...metadata, id: spec.id, name: spec.name ?? base?.name ?? spec.id }
104
- merged.maxTokens = metadata.maxTokens ?? spec.defaultMaxTokens ?? base?.maxTokens
150
+ // A route that names no source states its protocol once, for every model.
151
+ if (merged.api === undefined && routeApi !== undefined) merged.api = routeApi
152
+ // Capacity and the request default are different numbers, and the resolver
153
+ // caps one by the other: folding them would hide the mistake it refuses.
154
+ const capacity = metadata.maxTokens ?? base?.maxTokens
155
+ const missing = base === undefined
156
+ ? REQUIRED_METADATA.filter(field => field === 'api' ? merged.api === undefined : metadata[field] === undefined)
157
+ : []
158
+ if (missing.length > 0) return { error: 'unknown model requires complete metadata: missing ' + missing.join(', ') }
159
+ if (spec.defaultMaxTokens !== undefined && capacity !== undefined && spec.defaultMaxTokens > capacity) {
160
+ return { error: 'defaultMaxTokens ' + spec.defaultMaxTokens + ' exceeds model maxTokens ' + capacity }
161
+ }
105
162
  const origins = {}
106
163
  for (const field of INHERITED_FIELDS) {
107
- const declaredValue = field === 'maxTokens' ? metadata.maxTokens ?? spec.defaultMaxTokens : metadata[field]
108
- if (declaredValue !== undefined) origins[field] = METADATA
164
+ if (metadata[field] !== undefined) origins[field] = METADATA
165
+ else if (field === 'api' && base === undefined && routeApi !== undefined) origins[field] = ROUTE
109
166
  else if (base === undefined) origins[field] = ABSENT
110
167
  else origins[field] = spec.template === undefined ? INSTALLED : 'template:' + spec.template
111
168
  }
112
- return { model: merged, origins, levels: supportedLevels(merged.thinkingLevelMap) }
169
+ return {
170
+ model: { ...merged, maxTokens: capacity },
171
+ origins,
172
+ levels: supportedLevels(merged.thinkingLevelMap),
173
+ ...(spec.defaultMaxTokens === undefined ? {} : { requestMax: spec.defaultMaxTokens }),
174
+ ...(spec.aliases === undefined ? {} : { aliases: spec.aliases.join(',') }),
175
+ ...(metadata.cost?.tiers === undefined ? {} : {
176
+ tiers: metadata.cost.tiers.map(tier => 'above ' + tier.inputTokensAbove + ': ' + costOf(tier)).join('; '),
177
+ }),
178
+ }
113
179
  }
114
180
 
115
181
  function rowsOf(catalog, directory) {
116
182
  const rows = []
117
183
  for (const provider of catalog.providers ?? []) {
118
184
  const data = providerData(directory, provider.source)
185
+ const hasModels = 'models' in provider
186
+ const hasFilter = 'filter' in provider
187
+ // The resolver refuses both and neither; saying so here beats serving half.
188
+ if (hasModels === hasFilter) {
189
+ rows.push({ route: provider.id, error: 'declare exactly one of models or filter' })
190
+ continue
191
+ }
192
+ if (hasFilter) {
193
+ if (data === undefined) {
194
+ rows.push({
195
+ route: provider.id,
196
+ error: 'filter expands the installed catalog of source "' + provider.source + '"; no harness install found',
197
+ })
198
+ continue
199
+ }
200
+ for (const model of expandFilter(data, provider.filter)) {
201
+ rows.push({
202
+ route: provider.id + '/' + model.id,
203
+ name: model.name ?? model.id,
204
+ api: model.api,
205
+ contextWindow: model.contextWindow,
206
+ maxTokens: model.maxTokens,
207
+ cost: costOf(model.cost),
208
+ levels: supportedLevels(model.thinkingLevelMap).join(','),
209
+ origins: 'filter:' + (provider.filter.include ?? [WILDCARD]).join(',') + ' all installed',
210
+ })
211
+ }
212
+ continue
213
+ }
119
214
  for (const spec of provider.models ?? []) {
120
- const result = resolve(spec, data)
215
+ const result = resolve(spec, data, provider.api)
121
216
  const route = provider.id + '/' + spec.id
122
217
  if (result.error !== undefined) {
123
218
  rows.push({ route, error: result.error })
124
219
  continue
125
220
  }
126
- const cost = result.model.cost ?? {}
127
221
  rows.push({
128
222
  route,
129
223
  name: result.model.name,
130
224
  api: result.model.api,
131
225
  contextWindow: result.model.contextWindow,
132
226
  maxTokens: result.model.maxTokens,
133
- cost: '$' + [cost.input, cost.output, cost.cacheRead, cost.cacheWrite].join('/') + ' per 1M',
227
+ cost: costOf(result.model.cost),
134
228
  levels: result.levels.join(','),
229
+ ...(result.requestMax === undefined ? {} : { requestMax: result.requestMax }),
230
+ ...(result.aliases === undefined ? {} : { aliases: result.aliases }),
231
+ ...(result.tiers === undefined ? {} : { tiers: result.tiers }),
135
232
  origins: INHERITED_FIELDS
136
233
  .map(field => [field, result.origins[field]])
137
234
  .filter(([, origin]) => origin !== INSTALLED)
@@ -143,6 +240,39 @@ function rowsOf(catalog, directory) {
143
240
  return rows
144
241
  }
145
242
 
243
+ /**
244
+ * The default is part of the catalog, not a comment beside it: the resolver
245
+ * refuses a selection nothing serves, so a reading that skipped it would call a
246
+ * broken catalog readable.
247
+ */
248
+ export function checkDefault(catalog, rows) {
249
+ const served = rows.filter(row => row.error === undefined)
250
+ const selection = catalog.default ?? null
251
+ if (served.length === 0) {
252
+ return selection === null ? [] : [{ route: 'default', error: 'an empty catalog requires default null' }]
253
+ }
254
+ if (selection === null || typeof selection !== 'object') {
255
+ return [{ route: 'default', error: 'a served catalog requires a default selection' }]
256
+ }
257
+ const alias = new Map()
258
+ for (const provider of catalog.providers ?? []) {
259
+ for (const model of provider.models ?? []) {
260
+ for (const name of [model.id, ...(model.aliases ?? [])]) alias.set(provider.id + '/' + name, model.id)
261
+ }
262
+ }
263
+ const route = selection.provider + '/' + selection.model
264
+ const canonical = alias.get(route)
265
+ const row = served.find(candidate => candidate.route === selection.provider + '/' + (canonical ?? selection.model))
266
+ if (row === undefined) {
267
+ return [{ route: 'default', error: 'provider/model is outside the managed selection: ' + route }]
268
+ }
269
+ const effort = selection.reasoningEffort
270
+ if (effort !== undefined && !String(row.levels).split(',').includes(effort)) {
271
+ return [{ route: 'default', error: 'effort is not supported by the selected model: ' + effort }]
272
+ }
273
+ return []
274
+ }
275
+
146
276
  function main() {
147
277
  const options = parseArgs(process.argv.slice(2))
148
278
  const patch = join(options.home, 'profiles', options.profile, 'cordis.patch.yml')
@@ -152,11 +282,12 @@ function main() {
152
282
  + 'this profile wires models another way; see references/model-wiring.md\n')
153
283
  process.exit(1)
154
284
  }
155
- const directory = dataDirectory()
285
+ const directory = options.data ?? dataDirectory()
156
286
  if (directory === undefined) {
157
287
  process.stderr.write('no harness install found; entries that inherit from it cannot be checked\n')
158
288
  }
159
- const rows = rowsOf(catalog, directory)
289
+ const served = rowsOf(catalog, directory)
290
+ const rows = [...served, ...checkDefault(catalog, served)]
160
291
  const unresolved = rows.filter(row => row.error !== undefined).length
161
292
  if (options.json) {
162
293
  process.stdout.write(JSON.stringify({ default: catalog.default ?? null, rows }, null, 2) + '\n')
@@ -167,7 +298,12 @@ function main() {
167
298
  continue
168
299
  }
169
300
  process.stdout.write(row.route + '\n ' + row.name + ' | ' + row.api + ' | ctx ' + row.contextWindow
170
- + ' | max ' + row.maxTokens + ' | ' + row.cost + ' | efforts ' + row.levels + '\n inherited: ' + row.origins + '\n')
301
+ + ' | max ' + row.maxTokens
302
+ + (row.requestMax === undefined ? '' : ' | req ' + row.requestMax)
303
+ + ' | ' + row.cost
304
+ + (row.tiers === undefined ? '' : ' | tiers ' + row.tiers)
305
+ + ' | efforts ' + row.levels
306
+ + (row.aliases === undefined ? '' : ' | aliases ' + row.aliases) + '\n inherited: ' + row.origins + '\n')
171
307
  }
172
308
  process.stdout.write('default: ' + JSON.stringify(catalog.default ?? null) + '\n')
173
309
  }