bareloop 0.39.0 → 0.41.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 (76) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/bareloop.context.md +137 -112
  3. package/package.json +2 -2
  4. package/src/authorflow.js +333 -58
  5. package/src/authoring.js +65 -2
  6. package/src/authorjob.js +57 -9
  7. package/src/authorreadout.js +50 -2
  8. package/src/authorrun.js +30 -27
  9. package/src/authorscout.js +9 -6
  10. package/src/bridges.js +1 -1
  11. package/src/bundle.js +7 -4
  12. package/src/bundlerun.js +4 -1
  13. package/src/calibrate.js +1 -1
  14. package/src/cardauthor.js +4 -1
  15. package/src/cli.js +1 -1
  16. package/src/config.js +129 -29
  17. package/src/declaredclose.js +4 -3
  18. package/src/draftspend.js +6 -1
  19. package/src/index.js +2 -4
  20. package/src/interviewrun.js +73 -52
  21. package/src/job.js +50 -11
  22. package/src/jobblock.js +140 -0
  23. package/src/judged.js +12 -44
  24. package/src/keysfile.js +15 -7
  25. package/src/kinds.js +2 -2
  26. package/src/ledger.js +1 -1
  27. package/src/modellist.js +84 -0
  28. package/src/monthly.js +23 -43
  29. package/src/panel/authorroutes.js +34 -19
  30. package/src/panel/authorsession.js +187 -72
  31. package/src/panel/index.html +699 -215
  32. package/src/panel/jobplan.js +107 -0
  33. package/src/panel/runroutes.js +3 -0
  34. package/src/panel/server.js +102 -28
  35. package/src/panel/settingsroutes.js +232 -173
  36. package/src/planrun.js +6 -6
  37. package/src/promptregisters.js +1 -1
  38. package/src/providerrows.js +226 -141
  39. package/src/providers.js +58 -250
  40. package/src/providershapes.js +18 -0
  41. package/src/replay.js +1 -1
  42. package/src/reuse.js +1 -1
  43. package/src/root.js +1 -1
  44. package/src/run.js +3 -3
  45. package/src/selection.js +1 -1
  46. package/src/source.js +64 -1
  47. package/src/text.js +13 -0
  48. package/src/thinking.js +130 -0
  49. package/src/transport.js +1 -1
  50. package/src/userrun.js +53 -76
  51. package/types/authorflow.d.ts +258 -39
  52. package/types/authoring.d.ts +7 -1
  53. package/types/authorjob.d.ts +35 -3
  54. package/types/authorreadout.d.ts +17 -1
  55. package/types/authorscout.d.ts +5 -3
  56. package/types/config.d.ts +23 -6
  57. package/types/declaredclose.d.ts +4 -1
  58. package/types/draftspend.d.ts +8 -2
  59. package/types/index.d.ts +1 -1
  60. package/types/job.d.ts +6 -6
  61. package/types/jobblock.d.ts +102 -0
  62. package/types/judged.d.ts +5 -36
  63. package/types/keysfile.d.ts +15 -4
  64. package/types/modellist.d.ts +50 -0
  65. package/types/monthly.d.ts +4 -3
  66. package/types/panel/authorsession.d.ts +44 -15
  67. package/types/panel/jobplan.d.ts +58 -0
  68. package/types/panel/server.d.ts +34 -4
  69. package/types/planrun.d.ts +1 -1
  70. package/types/providerrows.d.ts +192 -99
  71. package/types/providers.d.ts +35 -116
  72. package/types/providershapes.d.ts +10 -0
  73. package/types/run.d.ts +1 -1
  74. package/types/source.d.ts +26 -0
  75. package/types/thinking.d.ts +90 -0
  76. package/types/userrun.d.ts +6 -1
@@ -7,26 +7,26 @@
7
7
  */
8
8
  export function usableKey<T extends string | undefined>(raw: T): T | string;
9
9
  /**
10
- * What a key with no saved entry shows. Any name not listed is OpenAI-compatible with a
11
- * blank URL and a blank Name.
10
+ * The model lines of a row, pure (no I/O, no rewrite of the file). A row WITH `models.<ENV>` uses
11
+ * that list and ignores `keys.<ENV>.{name,priceInPerM,priceOutPerM}`. A row WITHOUT it whose
12
+ * `keys.<ENV>.name` is a non-empty saved string reads as ONE line: ticked when BOTH older prices
13
+ * are finite numbers >= 0 (the line carries them), else UNTICKED with no price (a ticked line
14
+ * needs both prices). A row with no saved name reads as no lines — nothing is carried from a
15
+ * built-in table.
12
16
  * @param {string} envName
13
- * @returns {{ name: string, shape: string, baseUrl: string }}
17
+ * @param {Record<string, any>} config
18
+ * @returns {ModelLine[]}
14
19
  */
15
- export function defaultsFor(envName: string): {
16
- name: string;
17
- shape: string;
18
- baseUrl: string;
19
- };
20
+ export function effectiveModels(envName: string, config: Record<string, any>): ModelLine[];
20
21
  /**
21
- * @typedef {object} KeyRow
22
- * @property {string} envName the key variable NAME (read-only label; never a value)
23
- * @property {string} name the model id Chat uses ('' = not offered in Chat)
24
- * @property {string} provider the `src/providers.js` table entry (the API shape's id)
25
- * @property {string} baseUrl the saved Base URL ('' = the shape's default host)
26
- * @property {unknown} [priceInPerM] the customer's own price, USD per 1M input tokens — carried RAW
27
- * (present only when the config row names it); {@link ratesFor} is the one place that validates it
28
- * @property {unknown} [priceOutPerM] the same, for output tokens
22
+ * Every ticked-and-priced line of the rows, in keys-file order.
23
+ * @param {readonly KeyRow[]} rows
24
+ * @returns {{ row: KeyRow, line: ModelLine }[]}
29
25
  */
26
+ export function tickedLines(rows: readonly KeyRow[]): {
27
+ row: KeyRow;
28
+ line: ModelLine;
29
+ }[];
30
30
  /**
31
31
  * The rows: one per name in `filled`, saved entry over defaults.
32
32
  * @param {{ filled: readonly string[], config: Record<string, any> }} src
@@ -37,20 +37,33 @@ export function keyRows({ filled, config }: {
37
37
  config: Record<string, any>;
38
38
  }): KeyRow[];
39
39
  /**
40
- * The rows for a keys home. `undefined` home (a door that skipped the keys file) = no
41
- * rows, so nothing resolves through the file and the provider's built-in variable stands.
40
+ * The key names that make rows: every key in the keys file with a value (file order), plus — when `env` is given — a key only the
41
+ * SHELL holds that config already names (`keys.<ENV>` or `models.<ENV>`), sorted after them. The page shows such a row as
42
+ * "found in shell"; the run path must resolve it the same way.
43
+ * @param {readonly string[]} filled the keys file's filled names
44
+ * @param {Record<string, any>} config
45
+ * @param {Record<string, string|undefined>} [env] the shell side
46
+ * @returns {string[]}
47
+ */
48
+ export function rowNamesOf(filled: readonly string[], config: Record<string, any>, env?: Record<string, string | undefined>): string[];
49
+ /**
50
+ * The rows for a keys home. `undefined` home (a door that skipped the keys file) = no rows. With `env`, a shell-only key that
51
+ * config names is a row too ({@link rowNamesOf}).
42
52
  * @param {string|undefined} home
53
+ * @param {Record<string, string|undefined>} [env]
43
54
  * @returns {KeyRow[]}
44
55
  */
45
- export function rowsForHome(home: string | undefined): KeyRow[];
56
+ export function rowsForHome(home: string | undefined, env?: Record<string, string | undefined>): KeyRow[];
46
57
  /**
47
- * @param {string} provider
48
- * @returns {string} the shape's default host ('' = the vendor's own)
58
+ * EVERY row a config home names, keyed or not: the keys file's filled names plus every name `keys.<ENV>` / `models.<ENV>` carries. For
59
+ * a readout that must say which variable a model line WOULD read ("KEY is not set in this shell") before any key exists. Never for a run.
60
+ * @param {string|undefined} home
61
+ * @returns {KeyRow[]}
49
62
  */
50
- export function defaultUrlOf(provider: string): string;
63
+ export function namedRowsForHome(home: string | undefined): KeyRow[];
51
64
  /**
52
- * An endpoint in comparable form: blank = the shape's default host; scheme + host lower-cased,
53
- * trailing slashes dropped.
65
+ * An endpoint in comparable form: scheme + host lower-cased, trailing slashes dropped. A blank
66
+ * Base URL is blank — there is no default host.
54
67
  * @param {string} provider
55
68
  * @param {string|null|undefined} baseUrl
56
69
  * @returns {string}
@@ -58,8 +71,7 @@ export function defaultUrlOf(provider: string): string;
58
71
  export function endpointOf(provider: string, baseUrl: string | null | undefined): string;
59
72
  /**
60
73
  * A job's provider in plain words for a refusal line: the host its Base URL points at (else the
61
- * shape's own default host, else the shape's label), plus the model when the spec names one.
62
- * Never a key, a path or a status code.
74
+ * shape's label), plus the model when the spec names one. Never a key, a path or a status code.
63
75
  * @param {{provider?: string, baseUrl?: string|null, model?: string|null}} spec
64
76
  * @returns {string}
65
77
  */
@@ -68,15 +80,10 @@ export function providerInWords(spec: {
68
80
  baseUrl?: string | null;
69
81
  model?: string | null;
70
82
  }): string;
71
- /**
72
- * The URL a row talks to, for display ('' = the vendor's own host, which has no URL to show).
73
- * @param {KeyRow} row
74
- * @returns {string}
75
- */
76
- export function shownUrl(row: KeyRow): string;
77
83
  /**
78
84
  * Which row a `(provider, baseUrl[, model])` identity belongs to: the rows on that shape and
79
- * endpoint; when several share it, the one whose Name is the model, else the first.
85
+ * endpoint; when several share it, the one that HAS a ticked-and-priced line for `model`, else
86
+ * the first.
80
87
  * @param {readonly KeyRow[]} rows
81
88
  * @param {{ provider?: string|null, baseUrl?: string|null, model?: string|null }} id
82
89
  * @returns {KeyRow|null}
@@ -87,56 +94,97 @@ export function findRow(rows: readonly KeyRow[], { provider, baseUrl, model }: {
87
94
  model?: string | null;
88
95
  }): KeyRow | null;
89
96
  /**
90
- * What Chat's Model menu offers and how a pick resolves: the row whose Name is the model
91
- * (a blank Name is never offered). `baseUrl` is absent when blank, exactly as a spec omits it.
97
+ * How a Model-menu pick resolves: the row named by `provider` (its key's env name) with a ticked-and-priced line for `model`.
98
+ * With no `provider` (an older caller) the first row ticking the model. `baseUrl` is absent when blank, exactly as a spec omits it.
99
+ * A model no row ticks, or a row that does not tick it, is `null` — never offered, never run.
92
100
  * @param {readonly KeyRow[]} rows
93
- * @param {string} model
94
- * @returns {{ provider: string, baseUrl?: string, envName: string, name: string }|null}
101
+ * @param {{ provider?: string|null, model?: string|null }} pick `provider` is the KEY's env name
102
+ * @returns {{ provider: string, baseUrl?: string, envName: string, name: string, legacyMaxTokens: boolean }|null}
95
103
  */
96
- export function modelChoiceFor(rows: readonly KeyRow[], model: string): {
104
+ export function modelChoiceFor(rows: readonly KeyRow[], { provider, model }: {
105
+ provider?: string | null;
106
+ model?: string | null;
107
+ }): {
97
108
  provider: string;
98
109
  baseUrl?: string;
99
110
  envName: string;
100
111
  name: string;
112
+ legacyMaxTokens: boolean;
101
113
  } | null;
102
114
  /**
103
- * The models Chat offers, in keys-file order.
115
+ * The lines Chat's Model menu offers: every ticked-and-priced line of the rows, in keys-file order, labelled `<KEY> · <model>`.
104
116
  * @param {readonly KeyRow[]} rows
105
- * @returns {{ id: string, envName: string, shape: string }[]}
117
+ * @returns {{ slot: string, model: string, label: string }[]}
106
118
  */
107
- export function chatModels(rows: readonly KeyRow[]): {
108
- id: string;
109
- envName: string;
110
- shape: string;
119
+ export function chatLines(rows: readonly KeyRow[]): {
120
+ slot: string;
121
+ model: string;
122
+ label: string;
111
123
  }[];
112
124
  /**
113
- * The key variable NAME a job on `(provider, baseUrl[, model])` reads: the matching row's
114
- * variable, else the provider's built-in one (byte-identical to before for anything no row
115
- * claims).
125
+ * Judge a Model-menu pick `{provider, model}` against the rows, for the doors that start a session. `ok:true` carries the choice;
126
+ * otherwise the plain refusal (one of the three sentences).
127
+ * @param {readonly KeyRow[]} rows
128
+ * @param {{ provider?: string|null, model?: string|null }} pick
129
+ * @returns {{ ok: true, choice: NonNullable<ReturnType<typeof modelChoiceFor>> } | { ok: false, say: string }}
130
+ */
131
+ export function judgePick(rows: readonly KeyRow[], pick: {
132
+ provider?: string | null;
133
+ model?: string | null;
134
+ }): {
135
+ ok: true;
136
+ choice: NonNullable<ReturnType<typeof modelChoiceFor>>;
137
+ } | {
138
+ ok: false;
139
+ say: string;
140
+ };
141
+ /**
142
+ * Which Settings line a RUN works on. A spec's own `model` wins; a spec that names none (signed before models were required)
143
+ * takes `--model <id>`; a spec with no `baseUrl` (the same era) matches a row on any endpoint of its shape. `--provider <KEY>`
144
+ * narrows to one key when two keys tick the same id. Pure; the signed spec is never changed, so its hash is untouched.
145
+ * @param {readonly KeyRow[]} rows
146
+ * @param {{ provider?: string|null, baseUrl?: string|null, model?: string|null }} spec the signed spec's worker fields
147
+ * @param {{ model?: string|null, provider?: string|null }} [flags] the operator's `--model` / `--provider`
148
+ * @returns {{ ok: true, row: KeyRow, line: ModelLine, model: string, baseUrl: string|undefined } | { ok: false, say: string }}
149
+ */
150
+ export function resolveWorker(rows: readonly KeyRow[], spec: {
151
+ provider?: string | null;
152
+ baseUrl?: string | null;
153
+ model?: string | null;
154
+ }, flags?: {
155
+ model?: string | null;
156
+ provider?: string | null;
157
+ }): {
158
+ ok: true;
159
+ row: KeyRow;
160
+ line: ModelLine;
161
+ model: string;
162
+ baseUrl: string | undefined;
163
+ } | {
164
+ ok: false;
165
+ say: string;
166
+ };
167
+ /**
168
+ * The key variable NAME a job on `(provider, baseUrl[, model])` reads: the matching row's own env name. There is NO built-in
169
+ * name: no matching row = no key (`name: null`).
116
170
  * @param {string|null|undefined} provider
117
171
  * @param {string|null|undefined} baseUrl
118
172
  * @param {readonly KeyRow[]} rows
119
173
  * @param {string|null|undefined} [model]
120
- * @returns {{ name: string, builtIn: string, chosen: boolean }}
174
+ * @returns {{ name: string|null, chosen: boolean }}
121
175
  */
122
176
  export function keyNameFor(provider: string | null | undefined, baseUrl: string | null | undefined, rows: readonly KeyRow[], model?: string | null | undefined): {
123
- name: string;
124
- builtIn: string;
177
+ name: string | null;
125
178
  chosen: boolean;
126
179
  };
127
180
  /**
128
- * The env a door hands to provider construction, with the matching row's key variable
129
- * standing in for the provider's built-in one: `env[builtIn] = env[row.envName]` (the `null`
130
- * no-key marker becomes {@link NO_KEY_PLACEHOLDER}). A copy — never mutates the input.
131
- * No matching row and no marker = the env unchanged.
181
+ * The key VALUE for a row's env name out of an env (the `null` no-key marker becomes {@link NO_KEY_PLACEHOLDER}); `undefined`
182
+ * when the row's variable is unset or empty.
132
183
  * @param {Record<string,string|undefined>} env
133
- * @param {string|null|undefined} provider
134
- * @param {string|null|undefined} baseUrl
135
- * @param {readonly KeyRow[]} rows
136
- * @param {string|null|undefined} [model]
137
- * @returns {Record<string,string|undefined>}
184
+ * @param {string|null|undefined} envName
185
+ * @returns {string|undefined}
138
186
  */
139
- export function applyConfiguredKey(env: Record<string, string | undefined>, provider: string | null | undefined, baseUrl: string | null | undefined, rows: readonly KeyRow[], model?: string | null | undefined): Record<string, string | undefined>;
187
+ export function keyValueOf(env: Record<string, string | undefined>, envName: string | null | undefined): string | undefined;
140
188
  /**
141
189
  * The one readout line for a resolved customer price, preview and run tail alike:
142
190
  * `yours: in $0.006 / out $1.20 per 1M tokens (DEEPSEEK_API_KEY row)`. A 0 / 0 price is accepted (the price
@@ -150,12 +198,12 @@ export function priceReadout(price: {
150
198
  envName: string;
151
199
  }): string;
152
200
  /**
153
- * The customer's own price for the row a `(provider, baseUrl[, model])` identity belongs to —
154
- * the SAME row match {@link keyNameFor} uses (`findRow`), so the price and the key always come
155
- * from one row. `null` = no price set (the run keeps bare-agent's built-in guess, byte-identical
156
- * to before). Both fields or neither, each a finite number >= 0, USD per 1M tokens; anything
157
- * else throws `ConfigError` naming the row and field — a bad price never silently falls back to
158
- * the guess. `rates` is bare-agent's `Loop({ rates })` shape (USD per 1K); `inPerM`/`outPerM`
201
+ * The customer's own price for the model LINE of the row a `(provider, baseUrl[, model])` identity
202
+ * belongs to — the SAME row match {@link keyNameFor} uses (`findRow`), so the price and the key
203
+ * always come from one row. With a `model`, the row's line of that name; without one, the row's
204
+ * first ticked line. `null` = no price set (the run keeps bare-agent's built-in guess). A line's
205
+ * price is a pair or nothing ({@link modelsProblem} guards the file), so a half price never
206
+ * reaches here. `rates` is bare-agent's `Loop({ rates })` shape (USD per 1K); `inPerM`/`outPerM`
159
207
  * are what a readout prints.
160
208
  * @param {string|null|undefined} provider
161
209
  * @param {string|null|undefined} baseUrl
@@ -173,13 +221,38 @@ export function ratesFor(provider: string | null | undefined, baseUrl: string |
173
221
  envName: string;
174
222
  } | null;
175
223
  /**
176
- * The price a judge's rounds are booked at. A price belongs to the MODEL's row: when the judge is
177
- * the worker's own provider it calls the worker's endpoint, so a row on that endpoint NAMED for the
178
- * judge's model prices it ({@link ratesFor}); a judge model that is absent or names no row there
179
- * keeps the worker's price (the worker's row, or none = the guess). A different provider resolves
180
- * on its own default endpoint, as before. A bad price on the resolved row throws like `ratesFor`.
181
- * The judge's KEY is chosen elsewhere (`applyConfiguredKey`, by the worker's model), so the two
182
- * can come from different rows — same provider + endpoint, judge model naming another row.
224
+ * A model line's own price in bare-agent's shape, or `null` when the line carries none (a price is a pair or nothing).
225
+ * @param {ModelLine} line
226
+ * @returns {{ rates: { in: number, out: number }, inPerM: number, outPerM: number }|null}
227
+ */
228
+ export function ratesOfLine(line: ModelLine): {
229
+ rates: {
230
+ in: number;
231
+ out: number;
232
+ };
233
+ inPerM: number;
234
+ outPerM: number;
235
+ } | null;
236
+ /**
237
+ * The row a judge's key and endpoint come from: the worker's own row when the judge shares the worker's API shape, else the judge
238
+ * shape's row that ticks the judge's model (else the first row of that shape). `null` when no row is on that shape.
239
+ * @param {readonly KeyRow[]} rows
240
+ * @param {{ provider: string, model?: string|null }} judge
241
+ * @param {string|null|undefined} workerProvider
242
+ * @param {KeyRow|null} workerRow
243
+ * @returns {KeyRow|null}
244
+ */
245
+ export function judgeRowFor(rows: readonly KeyRow[], judge: {
246
+ provider: string;
247
+ model?: string | null;
248
+ }, workerProvider: string | null | undefined, workerRow: KeyRow | null): KeyRow | null;
249
+ /**
250
+ * The price a judge's rounds are booked at. A price belongs to the MODEL's line: when the judge is
251
+ * the worker's own provider it calls the worker's endpoint, so a row on that endpoint with a line
252
+ * NAMED for the judge's model prices it ({@link ratesFor}); a judge model that is absent or names
253
+ * no line there keeps the worker's price (the worker's row, or none = the guess). A different
254
+ * provider resolves with no endpoint. The judge's KEY is chosen elsewhere (`applyConfiguredKey`,
255
+ * by the worker's model), so the two can come from different rows.
183
256
  * @param {{ provider: string, model?: string|null }} judge
184
257
  * @param {string|null|undefined} workerProvider
185
258
  * @param {string|null|undefined} workerBaseUrl
@@ -191,52 +264,72 @@ export function judgeRatesFor(judge: {
191
264
  provider: string;
192
265
  model?: string | null;
193
266
  }, workerProvider: string | null | undefined, workerBaseUrl: string | null | undefined, workerPrice: ReturnType<typeof ratesFor>, rows: readonly KeyRow[]): ReturnType<typeof ratesFor>;
194
- /** DeepSeek's OpenAI-shaped endpoint (the same string the panel's Model menu used). */
195
- export const DEEPSEEK_BASE_URL: "https://api.deepseek.com/v1";
196
- /**
197
- * The API shapes the dropdown offers. `id` is the `src/providers.js` table name;
198
- * `defaultUrl` is the host a blank Base URL means ('' = the vendor's own, unshown).
199
- * @type {readonly {id: string, label: string, defaultUrl: string}[]}
200
- */
201
- export const SHAPES: readonly {
202
- id: string;
203
- label: string;
204
- defaultUrl: string;
205
- }[];
206
- /** The five empty lines a fresh keys file is created with, in this order. */
207
- export const PRESET_KEY_NAMES: readonly string[];
208
- /** A local server's default endpoint: Ollama's OpenAI-compatible host (LM Studio / llama.cpp: change the URL). */
209
- export const LOCAL_BASE_URL: "http://127.0.0.1:11434/v1";
267
+ export { SHAPES };
210
268
  /**
211
269
  * A key line whose value is the word `null` is SET and means "no real key": the row exists and
212
270
  * the provider is handed this harmless stand-in (local servers ignore auth). It is not a
213
271
  * secret and is never shown or logged as a key.
214
272
  */
215
273
  export const NO_KEY_PLACEHOLDER: "no-key-needed";
216
- export type KeyRow = {
274
+ export function isTickedLine(l: ModelLine): boolean;
275
+ /** The plain sentence when a stale Chat pick names a key and a model no row ticks (fwd's wording, bareloop's seam). */
276
+ export const REFUSE_NO_TICK: "Tick a model in Settings > Providers. Nothing was spent.";
277
+ export const REFUSE_NO_PICK: "Pick a model. Nothing was spent.";
278
+ export function refuseNotSetUp(key: string): string;
279
+ /**
280
+ * The CLI sentence when a run names no model and the spec carries none (fwd's `pickTicked` sentence).
281
+ */
282
+ export const REFUSE_NAME_MODEL: "Name the model with --model <id>: one ticked in Settings > Providers with both prices.";
283
+ /**
284
+ * one model line of a row (`models.<ENV>` in config.json)
285
+ */
286
+ export type ModelLine = {
217
287
  /**
218
- * the key variable NAME (read-only label; never a value)
288
+ * the model id
289
+ */
290
+ model: string;
291
+ /**
292
+ * ticked = offered in Chat (needs both prices)
219
293
  */
294
+ use: boolean;
295
+ /**
296
+ * the customer's own price, USD per 1M input tokens
297
+ */
298
+ inPerM?: number | undefined;
299
+ /**
300
+ * the same, for output tokens
301
+ */
302
+ outPerM?: number | undefined;
303
+ };
304
+ export type PriceResult = {
305
+ rates: {
306
+ in: number;
307
+ out: number;
308
+ };
309
+ inPerM: number;
310
+ outPerM: number;
220
311
  envName: string;
312
+ } | null;
313
+ export type KeyRow = {
221
314
  /**
222
- * the model id Chat uses ('' = not offered in Chat)
315
+ * the key variable NAME (read-only label; never a value)
223
316
  */
224
- name: string;
317
+ envName: string;
225
318
  /**
226
319
  * the `src/providers.js` table entry (the API shape's id)
227
320
  */
228
321
  provider: string;
229
322
  /**
230
- * the saved Base URL ('' = the shape's default host)
323
+ * the saved Base URL ('' = none typed yet)
231
324
  */
232
325
  baseUrl: string;
233
326
  /**
234
- * the customer's own price, USD per 1M input tokens — carried RAW
235
- * (present only when the config row names it); {@link ratesFor} is the one place that validates it
327
+ * the "old max_tokens" tick (only meaningful on `openai-api`)
236
328
  */
237
- priceInPerM?: unknown;
329
+ legacyMaxTokens: boolean;
238
330
  /**
239
- * the same, for output tokens
331
+ * the row's model lines
240
332
  */
241
- priceOutPerM?: unknown;
333
+ models: ModelLine[];
242
334
  };
335
+ import { SHAPES } from './providershapes.js';
@@ -1,10 +1,3 @@
1
- /**
2
- * The per-call options the rubric JUDGE's model calls carry for this provider
3
- * instance, from the allow-list above. Empty for any provider/model not listed.
4
- * @param {any} provider a constructed bare-agent provider
5
- * @returns {Record<string, any>}
6
- */
7
- export function judgeCallOptions(provider: any): Record<string, any>;
8
1
  /**
9
2
  * Look up a provider's table entry. Throws a NAMED error on an unrecognized
10
3
  * provider — never a silent default (the `resolveRates`-style-substring-
@@ -18,7 +11,7 @@ export function resolveProvider(providerName: string): ProviderTableEntry;
18
11
  /**
19
12
  * Report whether a resolved API key VALUE (not its presence — every call
20
13
  * site already presence-checks separately) carries a shape an HTTP header
21
- * value cannot: F181 (`docs/logs/FINDINGS.md`) — a two-line secret-store
14
+ * value cannot: F181 (`docs/wiki/FINDINGS.md`) — a two-line secret-store
22
15
  * entry put the key plus a trailing metadata line into `OPENAI_API_KEY`
23
16
  * (`"<key>\nmeta"`), the presence-only check at every door passed, and Node
24
17
  * crashed `TypeError [ERR_INVALID_CHAR]` inside its own header-encode at the
@@ -44,80 +37,67 @@ export function resolveProvider(providerName: string): ProviderTableEntry;
44
37
  */
45
38
  export function apiKeyProblem(value: string): string | null;
46
39
  /**
47
- * Construct ONE provider instance for `providerName`/`model`, applying
48
- * `exposeErrorBody: true` (F153 — every constructed provider, unconditionally)
49
- * and the model's own request-key gating (`paramsFor`). `baseUrl` (PRD item
50
- * 28, ruling (d), 2026-09-09: admitted in v1) is forwarded only when given —
51
- * every provider constructor here defaults it on its own when absent.
40
+ * Construct ONE provider instance for `providerName`/`model`, applying `exposeErrorBody: true` (F153 — every constructed
41
+ * provider, unconditionally). `baseUrl` (PRD item 28, ruling (d), 2026-09-09: admitted in v1) is forwarded only when given.
42
+ * `legacyMaxTokens` is the Settings ROW's "old max_tokens" tick: a server that ignores `max_completion_tokens` (F149) does not
43
+ * bind the output cap until it is on. It is forwarded only for `openai-api` and only when `true` — nothing here sniffs a host
44
+ * or a model id to decide it.
52
45
  * @param {string} providerName
53
- * @param {{apiKey?: string, model?: string, baseUrl?: string}} [options]
46
+ * @param {{apiKey?: string, model?: string, baseUrl?: string, legacyMaxTokens?: boolean}} [options]
54
47
  */
55
- export function makeProvider(providerName: string, { apiKey, model, baseUrl }?: {
48
+ export function makeProvider(providerName: string, { apiKey, model, baseUrl, legacyMaxTokens }?: {
56
49
  apiKey?: string;
57
50
  model?: string;
58
51
  baseUrl?: string;
52
+ legacyMaxTokens?: boolean;
59
53
  }): any;
60
54
  /**
61
- * Build the `{provider, providerFor, judgeProvider}` triple both runners
62
- * need (`src/authorrun.js` and `src/panel/authorsession.js` call it) — ONE seam instead of two copies of the same construction and
63
- * tier-memoization logic. `model`/`tierModels` are already-RESOLVED inputs
64
- * (the caller runs `resolveWorkerModel` itself, exactly as both runners did
65
- * before this factory existed — that decision stays where each runner's own
66
- * `--model` flag/spec precedence lives, not duplicated in here).
67
- *
68
- * THE JUDGE IS NO LONGER PINNED TO ANTHROPIC (PRD item 32.2, hamr 2026-09-10:
69
- * *"what i care about is that judge becomes llm agnostic and not set to one
70
- * model or provider"*). It is constructed through this same factory, from a
71
- * `judgeProviderName`/`judgeModel` pair the CALLER resolved (`resolveJudge`,
72
- * src/judged.js — the spec's signed `judge` override, else the job's own worker
73
- * provider and model). Item 28's pin, and item 28 part (2)'s deferral of a
74
- * signed `judge:{provider,model}` field, both close here.
55
+ * Build the `{provider, providerFor, judgeProvider}` triple both authoring runners need (`src/authorrun.js` and
56
+ * `src/panel/authorsession.js` call it) — ONE seam instead of two copies of the same construction. `model` is the
57
+ * already-RESOLVED worker model id (the caller picked the Settings line).
75
58
  *
76
- * `judgeApiKey` stays a SEPARATE argument rather than being derived: the judge
77
- * may legitimately be a different provider from the worker, and reading its key
78
- * for it would put a second env-var reader in a module whose whole job is to
79
- * have exactly one. The caller reads the resolved judge provider's own `envKey`
80
- * and hands the key in — the same contract the worker key already has here.
59
+ * THE JUDGE IS NOT PINNED (PRD item 32.2): it is constructed through this same factory from a `judgeProviderName`/`judgeModel`
60
+ * pair the CALLER resolved. `judgeApiKey` stays a SEPARATE argument: the judge may be a different provider from the worker,
61
+ * and each key is read by the caller from its own row.
81
62
  * @param {object} args
82
- * @param {string} args.providerName spec.provider (or the anthropic-api default)
83
- * @param {string|undefined} args.apiKey the WORKER's key, read from the provider's own `envKey`
63
+ * @param {string} args.providerName the worker's API shape id
64
+ * @param {string|undefined} args.apiKey the WORKER's key, read from its row's env name
84
65
  * @param {string} args.model the already-resolved worker model id
85
- * @param {Readonly<Record<string, string>>} args.tierModels the already-resolved tier -> model map (spec override folded in by the caller)
86
- * @param {string|undefined} [args.baseUrl] spec.baseUrl, if any
87
- * @param {string|undefined} args.judgeApiKey the judge's own key, read by the caller from the RESOLVED judge provider's `envKey`
66
+ * @param {string|undefined} [args.baseUrl] the worker row's Base URL
67
+ * @param {boolean} [args.legacyMaxTokens] the worker row's old max_tokens tick
68
+ * @param {string|undefined} args.judgeApiKey the judge's own key
88
69
  * @param {string} args.judgeModel the resolved judge model id
89
- * @param {string} [args.judgeProviderName] the resolved judge provider name; defaults to `providerName` (the job's own worker provider), which is `resolveJudge`'s own default
90
- * @param {string|undefined} [args.judgeBaseUrl] the judge provider's endpoint, when it takes one
70
+ * @param {string} [args.judgeProviderName] the resolved judge shape; defaults to `providerName`
71
+ * @param {string|undefined} [args.judgeBaseUrl] the judge row's endpoint
72
+ * @param {boolean} [args.judgeLegacyMaxTokens] the judge row's old max_tokens tick (defaults to the worker's when the judge shares its provider)
91
73
  */
92
- export function buildRunnerProviders({ providerName, apiKey, model, tierModels, baseUrl, judgeApiKey, judgeModel, judgeProviderName, judgeBaseUrl, }: {
74
+ export function buildRunnerProviders({ providerName, apiKey, model, baseUrl, legacyMaxTokens, judgeApiKey, judgeModel, judgeProviderName, judgeBaseUrl, judgeLegacyMaxTokens, }: {
93
75
  providerName: string;
94
76
  apiKey: string | undefined;
95
77
  model: string;
96
- tierModels: Readonly<Record<string, string>>;
97
78
  baseUrl?: string | undefined;
79
+ legacyMaxTokens?: boolean | undefined;
98
80
  judgeApiKey: string | undefined;
99
81
  judgeModel: string;
100
82
  judgeProviderName?: string | undefined;
101
83
  judgeBaseUrl?: string | undefined;
84
+ judgeLegacyMaxTokens?: boolean | undefined;
102
85
  }): {
103
86
  provider: any;
104
- providerFor: (tier: string) => any;
87
+ providerFor: (_tier: string) => any;
105
88
  judgeProvider: any;
106
89
  };
107
90
  /**
108
- * $0 reachability check: a GET against `providerName`'s own models-list
109
- * endpoint, server-side, with the key in a header — NEVER a completion
110
- * request, so it never spends a token (PANEL-BUILD.md P3 build item 7: "the
111
- * person must get feedback BEFORE drafting if the chosen model can't
112
- * work"). Reused, unmodified, by the panel's model-readiness route today
113
- * (`src/panel/authorroutes.js`) and by the future Settings providers
114
- * table's own "test" column (PANEL-BUILD.md P4) — ONE function, not two
115
- * hand-typed copies of the same probe.
91
+ * $0 reachability check: ONE GET of the provider's models list (`src/modellist.js`, the one owner of
92
+ * that call), server-side, with the key in a header — NEVER a completion request, so it never spends
93
+ * a token (PANEL-BUILD.md P3 build item 7: "the person must get feedback BEFORE drafting if the
94
+ * chosen model can't work"). Used by the panel's model-readiness route (`src/panel/authorroutes.js`);
95
+ * Settings > Providers' Test and Refresh call `listModels` directly. A blank `baseUrl` makes no call.
116
96
  *
117
- * NEVER returns the key, never logs it — the key only ever appears in a
118
- * request header sent straight to the provider's own host.
97
+ * NEVER returns the key, never logs it — the key only ever appears in a request header sent
98
+ * straight to the provider's own host.
119
99
  * @param {object} o
120
- * @param {string} o.providerName
100
+ * @param {string} o.providerName an API shape id
121
101
  * @param {string} o.apiKey
122
102
  * @param {string} [o.model] when given, the response is also checked for this model id
123
103
  * @param {string} [o.baseUrl]
@@ -157,55 +137,6 @@ export function checkProviderReachable({ providerName, apiKey, model, baseUrl, f
157
137
  * @returns {string[]|null} lines to print, in order, or null when there is nothing to say
158
138
  */
159
139
  export function probeWarningLines(providerName: string): string[] | null;
160
- /**
161
- * Today's anthropic-api tier table — EXACTLY the `DEFAULT_TIER_MODELS` both
162
- * runners hardcoded before this factory existed. Any change to these two
163
- * values is a change to shipped anthropic-api behaviour, not a refactor.
164
- */
165
- export const ANTHROPIC_TIER_MODELS: Readonly<{
166
- sonnet: "claude-sonnet-5";
167
- haiku: "claude-haiku-4-5-20251001";
168
- }>;
169
- /**
170
- * DeepSeek (F149/F150) is a SINGLE model, not a tiered family — hamr's
171
- * ruling above: one secondary provider, not a menu. Both tiers map to the
172
- * same model id ON PURPOSE: `resolveWorkerModel`/a runner's `providerFor
173
- * (tier)` only ever LOOK UP a tier's model id (src/job.js:818-827,
174
- * scripts/run-u.mjs's `providerFor`) — neither assumes the tiers name
175
- * different models — so repeating the one id is the honest representation
176
- * of a single-model provider, not a second model masquerading as `haiku`.
177
- * If DeepSeek (or a future openai-api entrant) ever ships a genuinely
178
- * cheaper tier model, this is where it would earn its own `haiku` id.
179
- *
180
- * MODEL SWAP (F171, item 34 L16, hamr 2026-09-13): `deepseek-chat` is no
181
- * longer served — a live `/models` call returned only `deepseek-flash` and
182
- * `deepseek-v4-pro`. Switched to `deepseek-flash` (DeepSeek V4.1). F149's
183
- * `legacyMaxTokens` measurement (asked 64 output tokens, got 665/608) was
184
- * taken on `deepseek-chat` and is UNVERIFIED on `deepseek-flash` — kept on
185
- * below as the fail-safe (over-capping direction is safe; see F171).
186
- */
187
- export const OPENAI_TIER_MODELS: Readonly<{
188
- sonnet: "deepseek-flash";
189
- haiku: "deepseek-flash";
190
- }>;
191
- /**
192
- * Gemini (PRD item 31.3, hamr 2026-09-09: "anthropic, openai, gemini drop
193
- * ollama for now"). Two real tiers, unlike DeepSeek's single model: `flash` is
194
- * the cheap tier by design, so `haiku` maps to it honestly rather than
195
- * repeating one id.
196
- *
197
- * ADMITTED-PENDING-PROBE. The probe rule stands (PRD item 28 ruling (d),
198
- * 2026-09-09): no endpoint is in the menu without its own clean paid probe,
199
- * and `gemini-api` has ZERO runs. `anthropic-api` is the original (~167
200
- * archived runs) and `openai-api` was probed green through the shipped runner
201
- * (run mtu12vks, $1.64/$4, F157); gemini owes that fire and has not paid it.
202
- * `PROBE_STATUS` below is the machine-readable form of that debt — nothing in
203
- * here silently pretends the provider is proven.
204
- */
205
- export const GEMINI_TIER_MODELS: Readonly<{
206
- sonnet: "gemini-2.5-pro";
207
- haiku: "gemini-2.5-flash";
208
- }>;
209
140
  /**
210
141
  * Which admitted providers have paid for their own end-to-end probe, and which
211
142
  * have not. Read by the runner to print a loud marker; never read to REFUSE —
@@ -222,20 +153,8 @@ export type ProviderTableEntry = {
222
153
  * the bare-agent provider class
223
154
  */
224
155
  ctor: new (options: any) => any;
225
- /**
226
- * the environment variable this provider reads its key from
227
- */
228
- envKey: string;
229
- /**
230
- * tier name -> model id
231
- */
232
- tiers: Readonly<Record<string, string>>;
233
156
  /**
234
157
  * the CONSTRUCTOR OPTION NAME this provider reads its endpoint from — NOT assumed to be `baseUrl` (see below)
235
158
  */
236
159
  endpointKey: string;
237
- /**
238
- * per-model request-key/param gating, applied on TOP of {exposeErrorBody:true, apiKey, model, <endpointKey>?}
239
- */
240
- paramsFor: (model: string | undefined) => Record<string, any>;
241
160
  };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `id` is the value saved in config.json (and the signed spec's `provider`).
3
+ * @type {readonly {id: string, label: string}[]}
4
+ */
5
+ export const SHAPES: readonly {
6
+ id: string;
7
+ label: string;
8
+ }[];
9
+ /** @type {readonly string[]} */
10
+ export const SHAPE_IDS: readonly string[];
package/types/run.d.ts CHANGED
@@ -158,7 +158,7 @@
158
158
  * frozen A0 baseline arm has to be exactly today, so a guard firing under a disabled
159
159
  * shim would make the baseline a treatment arm. The default-flip belongs to a
160
160
  * paid contrast nobody has approved yet (the `layerRoot` precedent, F41).
161
- * @param {boolean} [opts.scout=true] operator-only switch (docs/product/SCOUT-CONTRAST.md),
161
+ * @param {boolean} [opts.scout=true] operator-only switch (docs/logs/SCOUT-CONTRAST.md),
162
162
  * forwarded verbatim to the plan flow: `false` on a fresh run (no `resumeSeed`) skips the
163
163
  * read-only survey and records `scout-skipped {reason:'operator-off'}` instead — the
164
164
  * planner drafts from `(no scout notes)`. Runner territory exactly like `readShim`: the