acuvo-code 0.3.4 → 0.3.6
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.
- package/ENTERPRISE.md +1 -1
- package/bin/acuvo.mjs +16 -1
- package/lib/acuvo-models.mjs +141 -141
- package/lib/chat.mjs +25 -1
- package/lib/plan.mjs +21 -6
- package/package.json +1 -1
package/ENTERPRISE.md
CHANGED
|
@@ -189,7 +189,7 @@ are the numbers to quote. Counting is the first thing a reviewer does.
|
|
|
189
189
|
|
|
190
190
|
⚠️ **This said "18 shipped files", then "41", then "90", then "101", then "108", and every
|
|
191
191
|
one went stale in turn.** The package ships **119 files — 117 in `lib/`, 2 in
|
|
192
|
-
`bin/` — about
|
|
192
|
+
`bin/` — about 77604 lines**, with **235 test files** beside them (counted 2026-08-22).
|
|
193
193
|
|
|
194
194
|
⭐ **AND THE 108 WENT STALE IN THE MOST INSTRUCTIVE WAY POSSIBLE: THREE OF THE FILES IT
|
|
195
195
|
MISSED WERE REACHABLE FROM NOTHING.** `wiring-reach.test.mjs` was naming
|
package/bin/acuvo.mjs
CHANGED
|
@@ -1489,10 +1489,25 @@ ${formatBoard(listed)}
|
|
|
1489
1489
|
* chat loop owns the invitation, and neither should own both.
|
|
1490
1490
|
*/
|
|
1491
1491
|
const { openingScreen } = await import('../lib/banner.mjs');
|
|
1492
|
+
/**
|
|
1493
|
+
* ── ⭐⭐ THE BRAND NAME, NOT THE VENDOR'S ────────────────────────────────────
|
|
1494
|
+
*
|
|
1495
|
+
* Roman: *"the model shouldn't say deepseek, it should say like Acuvo."*
|
|
1496
|
+
*
|
|
1497
|
+
* ⭐ AND THE WHOLE MAPPING ALREADY EXISTED. `lib/acuvo-models.mjs` has shipped
|
|
1498
|
+
* `Acuvo Flash`, `Acuvo Pro`, `Acuvo Review` and `Acuvo Vision` for days, with
|
|
1499
|
+
* `labelForModelId` written specifically for this — and the banner, the first
|
|
1500
|
+
* thing every user reads, asked nothing and printed the raw vendor slug. One
|
|
1501
|
+
* caller in the whole package used it. Built, correct, and unreached.
|
|
1502
|
+
*
|
|
1503
|
+
* ⚠️ It falls back to the raw id for a model we did not ship, deliberately:
|
|
1504
|
+
* printing "Acuvo Something" over a model somebody chose themselves would be
|
|
1505
|
+
* a lie in the one place that has to be true.
|
|
1506
|
+
*/
|
|
1492
1507
|
const banner = openingScreen({
|
|
1493
1508
|
version: pkgVersion,
|
|
1494
1509
|
workspace: shortenRoot(executor.root),
|
|
1495
|
-
model: config.model,
|
|
1510
|
+
model: labelForModelId(config.model),
|
|
1496
1511
|
billing,
|
|
1497
1512
|
canRun: mode,
|
|
1498
1513
|
interactive: false,
|
package/lib/acuvo-models.mjs
CHANGED
|
@@ -1,141 +1,141 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ── ⭐⭐ OUR MODELS HAVE OUR NAMES ───────────────────────────────────────────
|
|
3
|
-
*
|
|
4
|
-
* Everywhere a user could see a model, they saw `deepseek/deepseek-v4-flash-0731`.
|
|
5
|
-
* That is somebody else's product name in the middle of ours, and it is wrong
|
|
6
|
-
* for three separate reasons:
|
|
7
|
-
*
|
|
8
|
-
* 1. **It sells the wrong thing.** A buyer comparing us to Claude Code sees
|
|
9
|
-
* "Sonnet vs Opus" against "deepseek-v4-flash-0731", and concludes we are
|
|
10
|
-
* a wrapper. The work that makes this good — the harness, the pin, the
|
|
11
|
-
* independent reviewer, the budget — is ours and is invisible in that name.
|
|
12
|
-
* 2. **It leaks a decision we must be free to change.** The day a better or
|
|
13
|
-
* cheaper model appears, `--model deepseek/...` is in scripts, CI configs
|
|
14
|
-
* and muscle memory. A name we own is a name we can re-point.
|
|
15
|
-
* 3. **It exposes a supplier to be approached directly.** Our margin comes
|
|
16
|
-
* from the harness and the routing, not from secrecy — but there is no
|
|
17
|
-
* reason to print the supplier list on the product.
|
|
18
|
-
*
|
|
19
|
-
* ⚠️ AND IT IS NOT A LIE. The underlying id is always one command away
|
|
20
|
-
* (`acuvo doctor` prints it, the audit log records it, `--json` carries it), and
|
|
21
|
-
* `resolveModelName` accepts a raw vendor id unchanged so nothing existing
|
|
22
|
-
* breaks. Renaming is branding; hiding would be dishonesty, and this package
|
|
23
|
-
* does not get to have a `refusedCommitPath` and also a secret supplier.
|
|
24
|
-
*
|
|
25
|
-
* ── ⚠️ TWO OF THESE ARE NOT USER-SELECTABLE, ON PURPOSE ─────────────────────
|
|
26
|
-
*
|
|
27
|
-
* The reviewer and the vision model are INFRASTRUCTURE. A user choosing the
|
|
28
|
-
* model that reviews their work can (accidentally or otherwise) pick the same
|
|
29
|
-
* one that wrote it, which turns an independent check into self-review — the
|
|
30
|
-
* exact defect `chooseRefuteModel` exists to prevent. They are listed here so
|
|
31
|
-
* the catalogue is complete and honest, and marked `internal` so no menu offers
|
|
32
|
-
* them.
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* ⭐ THE ONE MAPPING. Every other module asks this file rather than embedding a
|
|
37
|
-
* vendor id, so re-pointing a name is a one-line change here.
|
|
38
|
-
*/
|
|
39
|
-
export const ACUVO_MODELS = Object.freeze({
|
|
40
|
-
'acuvo-flash': Object.freeze({
|
|
41
|
-
name: 'acuvo-flash',
|
|
42
|
-
label: 'Acuvo Flash',
|
|
43
|
-
id: 'deepseek/deepseek-v4-flash-0731',
|
|
44
|
-
role: 'build',
|
|
45
|
-
internal: false,
|
|
46
|
-
/** Measured on our own 13-task bench, 2026-08-15. */
|
|
47
|
-
blurb: 'The default. 12 of 13 on our bench for 1.5 cents. Fast, and cheap enough that verifying everything is affordable.',
|
|
48
|
-
}),
|
|
49
|
-
'acuvo-pro': Object.freeze({
|
|
50
|
-
name: 'acuvo-pro',
|
|
51
|
-
label: 'Acuvo Pro',
|
|
52
|
-
id: 'deepseek/deepseek-v4-pro-0813',
|
|
53
|
-
role: 'build',
|
|
54
|
-
internal: false,
|
|
55
|
-
blurb: 'The strong model. Costs 1.2x Flash on a long warm session and 6x on a short cold one — worth it for hard, sustained work.',
|
|
56
|
-
}),
|
|
57
|
-
/**
|
|
58
|
-
* ⚠️ INTERNAL. See the header: a user who could point the reviewer at their
|
|
59
|
-
* own builder would silently convert an independent check into self-review.
|
|
60
|
-
*/
|
|
61
|
-
'acuvo-review': Object.freeze({
|
|
62
|
-
name: 'acuvo-review',
|
|
63
|
-
label: 'Acuvo Review',
|
|
64
|
-
id: 'qwen/qwen3.7-flash',
|
|
65
|
-
role: 'review',
|
|
66
|
-
internal: true,
|
|
67
|
-
blurb: 'Reviews the builder\'s work from a different model family, so its blind spots are not the same ones.',
|
|
68
|
-
}),
|
|
69
|
-
'acuvo-vision': Object.freeze({
|
|
70
|
-
name: 'acuvo-vision',
|
|
71
|
-
label: 'Acuvo Vision',
|
|
72
|
-
id: 'qwen/qwen3.7-flash',
|
|
73
|
-
role: 'vision',
|
|
74
|
-
internal: true,
|
|
75
|
-
blurb: 'Looks at rendered pages and images.',
|
|
76
|
-
}),
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
/** The names a user may choose between — the `/model` menu. */
|
|
80
|
-
export function selectableModels() {
|
|
81
|
-
return Object.values(ACUVO_MODELS).filter((m) => !m.internal);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Resolve whatever the user typed into a provider model id.
|
|
86
|
-
*
|
|
87
|
-
* ⚠️ A RAW VENDOR ID PASSES THROUGH UNCHANGED, and that is deliberate rather
|
|
88
|
-
* than lazy: every existing script, CI file and test that names
|
|
89
|
-
* `deepseek/deepseek-v4-flash-0731` keeps working, and anyone who wants a model
|
|
90
|
-
* we have never heard of can still use it. Renaming must not become a gate.
|
|
91
|
-
*
|
|
92
|
-
* @param {string} input `acuvo-pro`, `Acuvo Pro`, or a raw vendor id
|
|
93
|
-
* @returns {{ ok: true, id: string, model: object|null } | { ok: false, error: string }}
|
|
94
|
-
*/
|
|
95
|
-
export function resolveModelName(input) {
|
|
96
|
-
const raw = String(input ?? '').trim();
|
|
97
|
-
if (!raw) return { ok: false, error: 'no model named' };
|
|
98
|
-
|
|
99
|
-
const key = raw.toLowerCase().replace(/\s+/g, '-');
|
|
100
|
-
const hit = ACUVO_MODELS[key];
|
|
101
|
-
if (hit) {
|
|
102
|
-
if (hit.internal) {
|
|
103
|
-
return {
|
|
104
|
-
ok: false,
|
|
105
|
-
error: `${hit.label} is chosen for you — it ${hit.role === 'review' ? 'reviews the builder\'s work, and letting you point it at the builder\'s own model would turn an independent check into self-review' : 'is used internally'}. Pick from: ${selectableModels().map((m) => m.name).join(', ')}.`,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
return { ok: true, id: hit.id, model: hit };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* ⚠️ A vendor id is recognised by its slash. Anything else that is not a
|
|
113
|
-
* known name is a TYPO, and a typo must not be silently posted to a provider
|
|
114
|
-
* as a model id — that costs a round trip to learn "no endpoints found".
|
|
115
|
-
*/
|
|
116
|
-
if (raw.includes('/')) {
|
|
117
|
-
const known = Object.values(ACUVO_MODELS).find((m) => m.id === raw);
|
|
118
|
-
return { ok: true, id: raw, model: known ?? null };
|
|
119
|
-
}
|
|
120
|
-
return {
|
|
121
|
-
ok: false,
|
|
122
|
-
error: `"${raw}" is not a model this understands. Choose ${selectableModels().map((m) => m.name).join(' or ')}, or give a full provider id like deepseek/deepseek-v4-flash-0731.`,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* The Acuvo name for a provider id, for anything a user reads.
|
|
128
|
-
*
|
|
129
|
-
* ⚠️ FALLS BACK TO THE RAW ID rather than inventing a name. A model we did not
|
|
130
|
-
* ship is still a model somebody is running, and printing "Acuvo Something" over
|
|
131
|
-
* it would be a lie in the one place — the receipt — that has to be true.
|
|
132
|
-
*/
|
|
133
|
-
export function labelForModelId(id) {
|
|
134
|
-
const hit = Object.values(ACUVO_MODELS).find((m) => m.id === String(id ?? ''));
|
|
135
|
-
return hit ? hit.label : String(id ?? '');
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** One line per selectable model, for `--help` and the `/model` menu. */
|
|
139
|
-
export function formatModelMenu() {
|
|
140
|
-
return selectableModels().map((m) => ` ${m.name.padEnd(12)} ${m.label.padEnd(12)} ${m.blurb}`);
|
|
141
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* ── ⭐⭐ OUR MODELS HAVE OUR NAMES ───────────────────────────────────────────
|
|
3
|
+
*
|
|
4
|
+
* Everywhere a user could see a model, they saw `deepseek/deepseek-v4-flash-0731`.
|
|
5
|
+
* That is somebody else's product name in the middle of ours, and it is wrong
|
|
6
|
+
* for three separate reasons:
|
|
7
|
+
*
|
|
8
|
+
* 1. **It sells the wrong thing.** A buyer comparing us to Claude Code sees
|
|
9
|
+
* "Sonnet vs Opus" against "deepseek-v4-flash-0731", and concludes we are
|
|
10
|
+
* a wrapper. The work that makes this good — the harness, the pin, the
|
|
11
|
+
* independent reviewer, the budget — is ours and is invisible in that name.
|
|
12
|
+
* 2. **It leaks a decision we must be free to change.** The day a better or
|
|
13
|
+
* cheaper model appears, `--model deepseek/...` is in scripts, CI configs
|
|
14
|
+
* and muscle memory. A name we own is a name we can re-point.
|
|
15
|
+
* 3. **It exposes a supplier to be approached directly.** Our margin comes
|
|
16
|
+
* from the harness and the routing, not from secrecy — but there is no
|
|
17
|
+
* reason to print the supplier list on the product.
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ AND IT IS NOT A LIE. The underlying id is always one command away
|
|
20
|
+
* (`acuvo doctor` prints it, the audit log records it, `--json` carries it), and
|
|
21
|
+
* `resolveModelName` accepts a raw vendor id unchanged so nothing existing
|
|
22
|
+
* breaks. Renaming is branding; hiding would be dishonesty, and this package
|
|
23
|
+
* does not get to have a `refusedCommitPath` and also a secret supplier.
|
|
24
|
+
*
|
|
25
|
+
* ── ⚠️ TWO OF THESE ARE NOT USER-SELECTABLE, ON PURPOSE ─────────────────────
|
|
26
|
+
*
|
|
27
|
+
* The reviewer and the vision model are INFRASTRUCTURE. A user choosing the
|
|
28
|
+
* model that reviews their work can (accidentally or otherwise) pick the same
|
|
29
|
+
* one that wrote it, which turns an independent check into self-review — the
|
|
30
|
+
* exact defect `chooseRefuteModel` exists to prevent. They are listed here so
|
|
31
|
+
* the catalogue is complete and honest, and marked `internal` so no menu offers
|
|
32
|
+
* them.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* ⭐ THE ONE MAPPING. Every other module asks this file rather than embedding a
|
|
37
|
+
* vendor id, so re-pointing a name is a one-line change here.
|
|
38
|
+
*/
|
|
39
|
+
export const ACUVO_MODELS = Object.freeze({
|
|
40
|
+
'acuvo-flash': Object.freeze({
|
|
41
|
+
name: 'acuvo-flash',
|
|
42
|
+
label: 'Acuvo Flash 1',
|
|
43
|
+
id: 'deepseek/deepseek-v4-flash-0731',
|
|
44
|
+
role: 'build',
|
|
45
|
+
internal: false,
|
|
46
|
+
/** Measured on our own 13-task bench, 2026-08-15. */
|
|
47
|
+
blurb: 'The default. 12 of 13 on our bench for 1.5 cents. Fast, and cheap enough that verifying everything is affordable.',
|
|
48
|
+
}),
|
|
49
|
+
'acuvo-pro': Object.freeze({
|
|
50
|
+
name: 'acuvo-pro',
|
|
51
|
+
label: 'Acuvo Pro 1',
|
|
52
|
+
id: 'deepseek/deepseek-v4-pro-0813',
|
|
53
|
+
role: 'build',
|
|
54
|
+
internal: false,
|
|
55
|
+
blurb: 'The strong model. Costs 1.2x Flash on a long warm session and 6x on a short cold one — worth it for hard, sustained work.',
|
|
56
|
+
}),
|
|
57
|
+
/**
|
|
58
|
+
* ⚠️ INTERNAL. See the header: a user who could point the reviewer at their
|
|
59
|
+
* own builder would silently convert an independent check into self-review.
|
|
60
|
+
*/
|
|
61
|
+
'acuvo-review': Object.freeze({
|
|
62
|
+
name: 'acuvo-review',
|
|
63
|
+
label: 'Acuvo Review 1',
|
|
64
|
+
id: 'qwen/qwen3.7-flash',
|
|
65
|
+
role: 'review',
|
|
66
|
+
internal: true,
|
|
67
|
+
blurb: 'Reviews the builder\'s work from a different model family, so its blind spots are not the same ones.',
|
|
68
|
+
}),
|
|
69
|
+
'acuvo-vision': Object.freeze({
|
|
70
|
+
name: 'acuvo-vision',
|
|
71
|
+
label: 'Acuvo Vision 1',
|
|
72
|
+
id: 'qwen/qwen3.7-flash',
|
|
73
|
+
role: 'vision',
|
|
74
|
+
internal: true,
|
|
75
|
+
blurb: 'Looks at rendered pages and images.',
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/** The names a user may choose between — the `/model` menu. */
|
|
80
|
+
export function selectableModels() {
|
|
81
|
+
return Object.values(ACUVO_MODELS).filter((m) => !m.internal);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Resolve whatever the user typed into a provider model id.
|
|
86
|
+
*
|
|
87
|
+
* ⚠️ A RAW VENDOR ID PASSES THROUGH UNCHANGED, and that is deliberate rather
|
|
88
|
+
* than lazy: every existing script, CI file and test that names
|
|
89
|
+
* `deepseek/deepseek-v4-flash-0731` keeps working, and anyone who wants a model
|
|
90
|
+
* we have never heard of can still use it. Renaming must not become a gate.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} input `acuvo-pro`, `Acuvo Pro`, or a raw vendor id
|
|
93
|
+
* @returns {{ ok: true, id: string, model: object|null } | { ok: false, error: string }}
|
|
94
|
+
*/
|
|
95
|
+
export function resolveModelName(input) {
|
|
96
|
+
const raw = String(input ?? '').trim();
|
|
97
|
+
if (!raw) return { ok: false, error: 'no model named' };
|
|
98
|
+
|
|
99
|
+
const key = raw.toLowerCase().replace(/\s+/g, '-');
|
|
100
|
+
const hit = ACUVO_MODELS[key];
|
|
101
|
+
if (hit) {
|
|
102
|
+
if (hit.internal) {
|
|
103
|
+
return {
|
|
104
|
+
ok: false,
|
|
105
|
+
error: `${hit.label} is chosen for you — it ${hit.role === 'review' ? 'reviews the builder\'s work, and letting you point it at the builder\'s own model would turn an independent check into self-review' : 'is used internally'}. Pick from: ${selectableModels().map((m) => m.name).join(', ')}.`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return { ok: true, id: hit.id, model: hit };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* ⚠️ A vendor id is recognised by its slash. Anything else that is not a
|
|
113
|
+
* known name is a TYPO, and a typo must not be silently posted to a provider
|
|
114
|
+
* as a model id — that costs a round trip to learn "no endpoints found".
|
|
115
|
+
*/
|
|
116
|
+
if (raw.includes('/')) {
|
|
117
|
+
const known = Object.values(ACUVO_MODELS).find((m) => m.id === raw);
|
|
118
|
+
return { ok: true, id: raw, model: known ?? null };
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
ok: false,
|
|
122
|
+
error: `"${raw}" is not a model this understands. Choose ${selectableModels().map((m) => m.name).join(' or ')}, or give a full provider id like deepseek/deepseek-v4-flash-0731.`,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The Acuvo name for a provider id, for anything a user reads.
|
|
128
|
+
*
|
|
129
|
+
* ⚠️ FALLS BACK TO THE RAW ID rather than inventing a name. A model we did not
|
|
130
|
+
* ship is still a model somebody is running, and printing "Acuvo Something" over
|
|
131
|
+
* it would be a lie in the one place — the receipt — that has to be true.
|
|
132
|
+
*/
|
|
133
|
+
export function labelForModelId(id) {
|
|
134
|
+
const hit = Object.values(ACUVO_MODELS).find((m) => m.id === String(id ?? ''));
|
|
135
|
+
return hit ? hit.label : String(id ?? '');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** One line per selectable model, for `--help` and the `/model` menu. */
|
|
139
|
+
export function formatModelMenu() {
|
|
140
|
+
return selectableModels().map((m) => ` ${m.name.padEnd(12)} ${m.label.padEnd(12)} ${m.blurb}`);
|
|
141
|
+
}
|
package/lib/chat.mjs
CHANGED
|
@@ -353,9 +353,33 @@ export async function runChat({
|
|
|
353
353
|
|
|
354
354
|
try {
|
|
355
355
|
for (;;) {
|
|
356
|
+
/**
|
|
357
|
+
* ── ⭐⭐ THE INPUT IS A BOX, BECAUSE A BARE `› ` IS NOT A PLACE TO TYPE ──
|
|
358
|
+
*
|
|
359
|
+
* Roman, comparing against a real Claude Code screenshot: *"you can see
|
|
360
|
+
* the box where you type, acuvo doesn't have that."* He is right — the
|
|
361
|
+
* prompt was two characters floating in the scrollback, which reads as
|
|
362
|
+
* output rather than as somewhere input goes.
|
|
363
|
+
*
|
|
364
|
+
* ⚠️ THE BOX OPENS BEFORE THE LINE AND CLOSES AFTER IT, deliberately.
|
|
365
|
+
* Drawing all four sides up front needs absolute cursor control, and
|
|
366
|
+
* readline owns the cursor once `question()` starts — every version of
|
|
367
|
+
* that fights the line editor the moment input wraps, a history entry is
|
|
368
|
+
* recalled, or the window is resized. Opening on entry and closing on
|
|
369
|
+
* submit needs no cursor math at all, survives all three, and leaves a
|
|
370
|
+
* transcript where each turn is visibly a closed unit.
|
|
371
|
+
*
|
|
372
|
+
* ⚠️ WIDTH IS READ FRESH EACH TURN — a terminal resized mid-session would
|
|
373
|
+
* otherwise draw rules at the old width for the rest of the run. Clamped,
|
|
374
|
+
* because `columns` is `undefined` when stdout is not a TTY and enormous
|
|
375
|
+
* when someone maximises on an ultrawide.
|
|
376
|
+
*/
|
|
377
|
+
const width = Math.max(40, Math.min(100, (output.columns ?? process.stdout.columns ?? 80) - 1));
|
|
378
|
+
if (interactive) output.write(`\n╭${'─'.repeat(width - 1)}\n`);
|
|
356
379
|
const line = interactive
|
|
357
|
-
? await ask(rl, '› ', state)
|
|
380
|
+
? await ask(rl, '│ › ', state)
|
|
358
381
|
: (queueIndex < queued.length ? queued[queueIndex++] : null);
|
|
382
|
+
if (interactive && line !== null) output.write(`╰${'─'.repeat(width - 1)}\n`);
|
|
359
383
|
// Echo a piped prompt so a scripted transcript reads like a session.
|
|
360
384
|
if (!interactive && line !== null && line.trim()) output.write(`› ${line.trim()}
|
|
361
385
|
`);
|
package/lib/plan.mjs
CHANGED
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
* context is coldest, and it is measured rather than reasoned.
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
+
import { labelForModelId } from './acuvo-models.mjs';
|
|
37
|
+
|
|
36
38
|
/**
|
|
37
39
|
* Per-million prices, USD, from OpenRouter's per-model endpoint feed on
|
|
38
40
|
* 2026-08-15, for the endpoint each model is PINNED to.
|
|
@@ -422,12 +424,25 @@ export function planGate({ plan, usedByModel = {}, model, projectedTokens = 0, l
|
|
|
422
424
|
return { allowed: true, reason: 'ok', remaining, message: null };
|
|
423
425
|
}
|
|
424
426
|
|
|
425
|
-
/**
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
427
|
+
/**
|
|
428
|
+
* ── ⚠️⚠️ THIS WAS A SECOND COPY OF THE BRAND MAP, AND IT DRIFTED ────────────
|
|
429
|
+
*
|
|
430
|
+
* It read: *"Acuvo's name for a provider id, without importing the catalogue
|
|
431
|
+
* into the math"* — and hardcoded `Acuvo Flash` / `Acuvo Pro`. The moment the
|
|
432
|
+
* labels were versioned in `acuvo-models.mjs`, this file kept printing the old
|
|
433
|
+
* names, in the messages a user sees when they run out of allowance. Two places
|
|
434
|
+
* naming the same product, one of them silently a version behind.
|
|
435
|
+
*
|
|
436
|
+
* ⭐ THE STATED REASON DID NOT HOLD. `acuvo-models.mjs` is a LEAF — it imports
|
|
437
|
+
* nothing — so pulling it in adds no dependency chain to "the math"; it is a
|
|
438
|
+
* frozen object. Avoiding the import bought nothing and cost a divergence.
|
|
439
|
+
*
|
|
440
|
+
* This is the fourth instance of the same shape found today: a mapping copied
|
|
441
|
+
* rather than imported, correct on the day it was written, wrong the first time
|
|
442
|
+
* the original changed.
|
|
443
|
+
*/
|
|
444
|
+
|
|
445
|
+
const labelFor = labelForModelId;
|
|
431
446
|
|
|
432
447
|
/**
|
|
433
448
|
* Tokens used per PROVIDER id, from audit records.
|