ftown 0.25.1 → 0.25.3
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/dist/command-rpc.d.ts +3 -0
- package/dist/command-rpc.js +41 -0
- package/dist/command-rpc.js.map +1 -1
- package/dist/ftown-cli.js +5 -2
- package/dist/ftown-cli.js.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/quota/agy.d.ts +16 -0
- package/dist/quota/agy.js +287 -0
- package/dist/quota/agy.js.map +1 -0
- package/dist/quota/annotate.d.ts +5 -0
- package/dist/quota/annotate.js +56 -0
- package/dist/quota/annotate.js.map +1 -0
- package/dist/quota/claude.d.ts +27 -0
- package/dist/quota/claude.js +265 -0
- package/dist/quota/claude.js.map +1 -0
- package/dist/quota/codex.d.ts +25 -0
- package/dist/quota/codex.js +378 -0
- package/dist/quota/codex.js.map +1 -0
- package/dist/quota/format.d.ts +6 -0
- package/dist/quota/format.js +113 -0
- package/dist/quota/format.js.map +1 -0
- package/dist/quota/grok.d.ts +97 -0
- package/dist/quota/grok.js +563 -0
- package/dist/quota/grok.js.map +1 -0
- package/dist/quota/models-agy.d.ts +10 -0
- package/dist/quota/models-agy.js +144 -0
- package/dist/quota/models-agy.js.map +1 -0
- package/dist/quota/models-claude.d.ts +10 -0
- package/dist/quota/models-claude.js +227 -0
- package/dist/quota/models-claude.js.map +1 -0
- package/dist/quota/models-codex.d.ts +18 -0
- package/dist/quota/models-codex.js +305 -0
- package/dist/quota/models-codex.js.map +1 -0
- package/dist/quota/models-grok.d.ts +19 -0
- package/dist/quota/models-grok.js +272 -0
- package/dist/quota/models-grok.js.map +1 -0
- package/dist/quota/models-muse.d.ts +13 -0
- package/dist/quota/models-muse.js +200 -0
- package/dist/quota/models-muse.js.map +1 -0
- package/dist/quota/models-opencode.d.ts +51 -0
- package/dist/quota/models-opencode.js +400 -0
- package/dist/quota/models-opencode.js.map +1 -0
- package/dist/quota/muse.d.ts +38 -0
- package/dist/quota/muse.js +225 -0
- package/dist/quota/muse.js.map +1 -0
- package/dist/quota/opencode.d.ts +7 -0
- package/dist/quota/opencode.js +47 -0
- package/dist/quota/opencode.js.map +1 -0
- package/dist/quota/routing-config.d.ts +18 -0
- package/dist/quota/routing-config.js +438 -0
- package/dist/quota/routing-config.js.map +1 -0
- package/dist/quota/routing-service.d.ts +24 -0
- package/dist/quota/routing-service.js +118 -0
- package/dist/quota/routing-service.js.map +1 -0
- package/dist/quota/routing.d.ts +10 -0
- package/dist/quota/routing.js +180 -0
- package/dist/quota/routing.js.map +1 -0
- package/dist/quota/types.d.ts +304 -0
- package/dist/quota/types.js +45 -0
- package/dist/quota/types.js.map +1 -0
- package/dist/quota-cli.d.ts +80 -0
- package/dist/quota-cli.js +462 -0
- package/dist/quota-cli.js.map +1 -0
- package/dist/security/bridge/permissions.js +4 -2
- package/dist/security/bridge/permissions.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +3 -2
- package/skills/ftown/SKILL.md +6 -4
- package/skills/ftown/references/orchestrator.md +5 -0
- package/skills/ftown/references/quota.md +177 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { mkdir, open, rename, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { dirname, isAbsolute, join } from 'node:path';
|
|
5
|
+
import { DEFAULT_ROUTING_TIERS, MODEL_ROUTING_LIMITS, MODEL_ROUTING_RELATIVE_PATH, QUOTA_PROVIDER_IDS, ROUTING_TIER_NAME_PATTERN, } from './types.js';
|
|
6
|
+
const MAX_FILE_BYTES = 1024 * 1024;
|
|
7
|
+
const READ_CHUNK_BYTES = 64 * 1024;
|
|
8
|
+
const MAX_MODEL_LENGTH = 200;
|
|
9
|
+
const MAX_ERRORS = 50;
|
|
10
|
+
const EFFORT_PATTERN = /^[a-z0-9][a-z0-9_-]{0,30}$/;
|
|
11
|
+
const TEMP_PREFIX = '.model-routing.';
|
|
12
|
+
const CONFIG_UNREADABLE = 'could not read the config';
|
|
13
|
+
const HOME_NOT_SET = 'home directory is not set';
|
|
14
|
+
/**
|
|
15
|
+
* Control and invisible characters, as code point ranges: C0 and DEL, C1, bidi controls
|
|
16
|
+
* (LRM, RLM, LRE..RLO, LRI..PDI), zero-width characters, word joiner, BOM, and the line
|
|
17
|
+
* and paragraph separators.
|
|
18
|
+
*/
|
|
19
|
+
const INVISIBLE_RANGES = [
|
|
20
|
+
[0x0000, 0x001f],
|
|
21
|
+
[0x007f, 0x009f],
|
|
22
|
+
[0x200b, 0x200f],
|
|
23
|
+
[0x202a, 0x202e],
|
|
24
|
+
[0x2028, 0x2029],
|
|
25
|
+
[0x2060, 0x2060],
|
|
26
|
+
[0x2066, 0x2069],
|
|
27
|
+
[0xfeff, 0xfeff],
|
|
28
|
+
];
|
|
29
|
+
/** Same set, but tab and newline are kept (used for steering). */
|
|
30
|
+
const STEERING_INVISIBLE_RANGES = [
|
|
31
|
+
[0x0000, 0x0008],
|
|
32
|
+
[0x000b, 0x001f],
|
|
33
|
+
...INVISIBLE_RANGES.slice(1),
|
|
34
|
+
];
|
|
35
|
+
function characterClass(ranges) {
|
|
36
|
+
const hex = (code) => `\\u${code.toString(16).padStart(4, "0")}`;
|
|
37
|
+
return `[${ranges.map(([from, to]) => `${hex(from)}-${hex(to)}`).join("")}]`;
|
|
38
|
+
}
|
|
39
|
+
const CONTROL_OR_INVISIBLE = new RegExp(characterClass(INVISIBLE_RANGES));
|
|
40
|
+
const CONTROL_OR_INVISIBLE_GLOBAL = new RegExp(characterClass(INVISIBLE_RANGES), "g");
|
|
41
|
+
const STEERING_INVISIBLE_GLOBAL = new RegExp(characterClass(STEERING_INVISIBLE_RANGES), "g");
|
|
42
|
+
/** Collects validation errors, keeping at most MAX_ERRORS of them. */
|
|
43
|
+
class ErrorCollector {
|
|
44
|
+
messages = [];
|
|
45
|
+
total = 0;
|
|
46
|
+
add(message) {
|
|
47
|
+
this.total += 1;
|
|
48
|
+
if (this.messages.length < MAX_ERRORS)
|
|
49
|
+
this.messages.push(message);
|
|
50
|
+
}
|
|
51
|
+
get count() {
|
|
52
|
+
return this.total;
|
|
53
|
+
}
|
|
54
|
+
list() {
|
|
55
|
+
const messages = this.messages.slice();
|
|
56
|
+
if (this.total > MAX_ERRORS)
|
|
57
|
+
messages[MAX_ERRORS - 1] = '... and more';
|
|
58
|
+
return messages;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function isPlainObject(value) {
|
|
62
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
63
|
+
return false;
|
|
64
|
+
const proto = Object.getPrototypeOf(value);
|
|
65
|
+
return proto === Object.prototype || proto === null;
|
|
66
|
+
}
|
|
67
|
+
function isProviderId(value) {
|
|
68
|
+
return QUOTA_PROVIDER_IDS.includes(value);
|
|
69
|
+
}
|
|
70
|
+
function homeIsSet(homeDir) {
|
|
71
|
+
return typeof homeDir === 'string' && homeDir !== '' && isAbsolute(homeDir);
|
|
72
|
+
}
|
|
73
|
+
/** Short rendering of an arbitrary input value for an error message. */
|
|
74
|
+
function show(value) {
|
|
75
|
+
let text;
|
|
76
|
+
if (typeof value === 'string')
|
|
77
|
+
text = JSON.stringify(value);
|
|
78
|
+
else if (typeof value === 'object' && value !== null)
|
|
79
|
+
text = Array.isArray(value) ? 'array' : 'object';
|
|
80
|
+
else
|
|
81
|
+
text = String(value);
|
|
82
|
+
return text.length > 60 ? `${text.slice(0, 60)}...` : text;
|
|
83
|
+
}
|
|
84
|
+
function errorCode(error) {
|
|
85
|
+
if (typeof error === 'object' && error !== null && 'code' in error) {
|
|
86
|
+
const code = error.code;
|
|
87
|
+
if (typeof code === 'string' && code !== '')
|
|
88
|
+
return code;
|
|
89
|
+
}
|
|
90
|
+
return 'UNKNOWN';
|
|
91
|
+
}
|
|
92
|
+
export function parseModelRoutingConfig(json) {
|
|
93
|
+
try {
|
|
94
|
+
if (!isPlainObject(json)) {
|
|
95
|
+
return { ok: false, errors: ['the routing file must contain a JSON object'] };
|
|
96
|
+
}
|
|
97
|
+
const errors = new ErrorCollector();
|
|
98
|
+
if (json.version !== 1)
|
|
99
|
+
errors.add('version must be 1');
|
|
100
|
+
const tiers = parseTiers(json.tiers, errors);
|
|
101
|
+
const steering = parseSteering(json.steering, errors);
|
|
102
|
+
if (errors.count > 0)
|
|
103
|
+
return { ok: false, errors: errors.list() };
|
|
104
|
+
return { ok: true, config: { version: 1, tiers, steering } };
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return { ok: false, errors: [CONFIG_UNREADABLE] };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function parseTiers(value, errors) {
|
|
111
|
+
if (!Array.isArray(value)) {
|
|
112
|
+
errors.add('tiers must be an array');
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
const { maxTiers } = MODEL_ROUTING_LIMITS;
|
|
116
|
+
if (value.length > maxTiers) {
|
|
117
|
+
errors.add(`tiers has ${value.length} entries; the limit is ${maxTiers}`);
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
const namesToIndex = new Map();
|
|
121
|
+
const tiers = [];
|
|
122
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
123
|
+
const tier = parseTier(value[index], index, namesToIndex, errors);
|
|
124
|
+
if (tier)
|
|
125
|
+
tiers.push(tier);
|
|
126
|
+
}
|
|
127
|
+
return tiers;
|
|
128
|
+
}
|
|
129
|
+
function parseTier(entry, index, namesToIndex, errors) {
|
|
130
|
+
const at = `tiers[${index}]`;
|
|
131
|
+
if (!isPlainObject(entry)) {
|
|
132
|
+
errors.add(`${at} must be an object`);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const name = parseTierName(entry.name, at, index, namesToIndex, errors);
|
|
136
|
+
const description = parseDescription(entry.description, at, errors);
|
|
137
|
+
const picks = parsePicks(entry.picks, at, errors);
|
|
138
|
+
const guidance = parseGuidance(entry.guidance, at, errors);
|
|
139
|
+
if (name === null)
|
|
140
|
+
return null;
|
|
141
|
+
return { name, description, picks, guidance };
|
|
142
|
+
}
|
|
143
|
+
function parseTierName(value, at, index, namesToIndex, errors) {
|
|
144
|
+
if (typeof value !== 'string') {
|
|
145
|
+
errors.add(`${at}.name must be a string`);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const name = value.trim();
|
|
149
|
+
const { maxTierNameLength } = MODEL_ROUTING_LIMITS;
|
|
150
|
+
if (name === '') {
|
|
151
|
+
errors.add(`${at}.name must not be empty`);
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
if (name.length > maxTierNameLength) {
|
|
155
|
+
errors.add(`${at}.name is longer than ${maxTierNameLength} characters`);
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
if (!ROUTING_TIER_NAME_PATTERN.test(name)) {
|
|
159
|
+
errors.add(`${at}.name ${show(name)} must use only lowercase letters, digits, "-" and "_", and start with a letter or digit`);
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const firstIndex = namesToIndex.get(name);
|
|
163
|
+
if (firstIndex !== undefined) {
|
|
164
|
+
errors.add(`${at}.name ${show(name)} is already used by tiers[${firstIndex}]`);
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
namesToIndex.set(name, index);
|
|
168
|
+
return name;
|
|
169
|
+
}
|
|
170
|
+
function parseDescription(value, at, errors) {
|
|
171
|
+
if (value === undefined)
|
|
172
|
+
return '';
|
|
173
|
+
if (typeof value !== 'string') {
|
|
174
|
+
errors.add(`${at}.description must be a string`);
|
|
175
|
+
return '';
|
|
176
|
+
}
|
|
177
|
+
const description = value
|
|
178
|
+
.replace(/\s+/g, ' ')
|
|
179
|
+
.replace(CONTROL_OR_INVISIBLE_GLOBAL, '')
|
|
180
|
+
.replace(/\s+/g, ' ')
|
|
181
|
+
.trim();
|
|
182
|
+
const { maxTierDescriptionLength } = MODEL_ROUTING_LIMITS;
|
|
183
|
+
if (description.length > maxTierDescriptionLength) {
|
|
184
|
+
errors.add(`${at}.description is longer than ${maxTierDescriptionLength} characters`);
|
|
185
|
+
}
|
|
186
|
+
return description;
|
|
187
|
+
}
|
|
188
|
+
/** Same cleaning as steering: invisible characters stripped (newlines and tabs kept), then trimmed. */
|
|
189
|
+
function parseGuidance(value, at, errors) {
|
|
190
|
+
if (value === undefined || value === null)
|
|
191
|
+
return '';
|
|
192
|
+
if (typeof value !== 'string') {
|
|
193
|
+
errors.add(`${at}.guidance must be a string`);
|
|
194
|
+
return '';
|
|
195
|
+
}
|
|
196
|
+
const guidance = value.replace(STEERING_INVISIBLE_GLOBAL, '').trim();
|
|
197
|
+
const { maxTierGuidanceLength } = MODEL_ROUTING_LIMITS;
|
|
198
|
+
if (guidance.length > maxTierGuidanceLength) {
|
|
199
|
+
errors.add(`${at}.guidance is longer than ${maxTierGuidanceLength} characters`);
|
|
200
|
+
}
|
|
201
|
+
return guidance;
|
|
202
|
+
}
|
|
203
|
+
function parsePicks(value, at, errors) {
|
|
204
|
+
if (!Array.isArray(value)) {
|
|
205
|
+
errors.add(`${at}.picks must be an array`);
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
const { maxPicksPerTier } = MODEL_ROUTING_LIMITS;
|
|
209
|
+
if (value.length > maxPicksPerTier) {
|
|
210
|
+
errors.add(`${at}.picks has ${value.length} entries; the limit is ${maxPicksPerTier}`);
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
const seen = new Set();
|
|
214
|
+
const picks = [];
|
|
215
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
216
|
+
const pickAt = `${at}.picks[${index}]`;
|
|
217
|
+
const entry = value[index];
|
|
218
|
+
if (!isPlainObject(entry)) {
|
|
219
|
+
errors.add(`${pickAt} must be an object`);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const pick = parsePick(entry, pickAt, errors);
|
|
223
|
+
if (!pick)
|
|
224
|
+
continue;
|
|
225
|
+
const key = JSON.stringify([pick.provider, pick.model, pick.effort]);
|
|
226
|
+
if (seen.has(key)) {
|
|
227
|
+
errors.add(`${pickAt} repeats an earlier pick for the same provider, model and effort`);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
seen.add(key);
|
|
231
|
+
picks.push(pick);
|
|
232
|
+
}
|
|
233
|
+
return picks;
|
|
234
|
+
}
|
|
235
|
+
function parsePick(entry, at, errors) {
|
|
236
|
+
const before = errors.count;
|
|
237
|
+
const provider = parseProvider(entry.provider, at, errors);
|
|
238
|
+
const model = parseModel(entry.model, at, errors);
|
|
239
|
+
const effort = parseEffort(entry.effort, at, errors);
|
|
240
|
+
if (errors.count > before || provider === null || model === null)
|
|
241
|
+
return null;
|
|
242
|
+
return { provider, model, effort };
|
|
243
|
+
}
|
|
244
|
+
function parseProvider(value, at, errors) {
|
|
245
|
+
if (isProviderId(value))
|
|
246
|
+
return value;
|
|
247
|
+
errors.add(`${at}.provider ${show(value)} is not a known CLI`);
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
function parseModel(value, at, errors) {
|
|
251
|
+
if (typeof value !== 'string') {
|
|
252
|
+
errors.add(`${at}.model must be a string`);
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
if (CONTROL_OR_INVISIBLE.test(value)) {
|
|
256
|
+
errors.add(`${at}.model contains control or invisible characters`);
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
const model = value.trim();
|
|
260
|
+
if (model === '') {
|
|
261
|
+
errors.add(`${at}.model must not be empty`);
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
if (model.length > MAX_MODEL_LENGTH) {
|
|
265
|
+
errors.add(`${at}.model is longer than ${MAX_MODEL_LENGTH} characters`);
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
return model;
|
|
269
|
+
}
|
|
270
|
+
function parseEffort(value, at, errors) {
|
|
271
|
+
if (value === undefined || value === null)
|
|
272
|
+
return null;
|
|
273
|
+
if (typeof value !== 'string') {
|
|
274
|
+
errors.add(`${at}.effort must be a string or null`);
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
if (!EFFORT_PATTERN.test(value)) {
|
|
278
|
+
errors.add(`${at}.effort ${show(value)} must be 1 to 31 characters: lowercase letters, digits, "-" or "_", starting with a letter or digit`);
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
return value;
|
|
282
|
+
}
|
|
283
|
+
function parseSteering(value, errors) {
|
|
284
|
+
if (value === undefined)
|
|
285
|
+
return '';
|
|
286
|
+
if (typeof value !== 'string') {
|
|
287
|
+
errors.add('steering must be a string');
|
|
288
|
+
return '';
|
|
289
|
+
}
|
|
290
|
+
const steering = value.replace(STEERING_INVISIBLE_GLOBAL, '').trim();
|
|
291
|
+
const { maxSteeringLength } = MODEL_ROUTING_LIMITS;
|
|
292
|
+
if (steering.length > maxSteeringLength) {
|
|
293
|
+
errors.add(`steering is longer than ${maxSteeringLength} characters`);
|
|
294
|
+
}
|
|
295
|
+
return steering;
|
|
296
|
+
}
|
|
297
|
+
export function validatePicksAgainstModels(config, models) {
|
|
298
|
+
const listByProvider = new Map();
|
|
299
|
+
for (const list of models) {
|
|
300
|
+
if (!listByProvider.has(list.provider))
|
|
301
|
+
listByProvider.set(list.provider, list);
|
|
302
|
+
}
|
|
303
|
+
const errors = [];
|
|
304
|
+
for (const tier of config.tiers) {
|
|
305
|
+
for (const pick of tier.picks) {
|
|
306
|
+
const list = listByProvider.get(pick.provider);
|
|
307
|
+
if (!list || list.status !== 'ok')
|
|
308
|
+
continue;
|
|
309
|
+
const where = `tiers.${tier.name}`;
|
|
310
|
+
const model = list.models.find((candidate) => candidate.id === pick.model);
|
|
311
|
+
if (!model) {
|
|
312
|
+
errors.push(`${where}: ${pick.provider} has no model "${pick.model}"`);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (pick.effort === null)
|
|
316
|
+
continue;
|
|
317
|
+
if (model.efforts.length === 0) {
|
|
318
|
+
errors.push(`${where}: ${pick.provider} model "${pick.model}" takes no effort setting`);
|
|
319
|
+
}
|
|
320
|
+
else if (!model.efforts.includes(pick.effort)) {
|
|
321
|
+
errors.push(`${where}: ${pick.provider} model "${pick.model}" does not support effort "${pick.effort}" (supported: ${model.efforts.join(', ')})`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return errors;
|
|
326
|
+
}
|
|
327
|
+
export function starterRoutingConfig() {
|
|
328
|
+
return {
|
|
329
|
+
version: 1,
|
|
330
|
+
tiers: DEFAULT_ROUTING_TIERS.map((tier) => ({
|
|
331
|
+
name: tier.name,
|
|
332
|
+
description: tier.description,
|
|
333
|
+
picks: [],
|
|
334
|
+
guidance: '',
|
|
335
|
+
})),
|
|
336
|
+
steering: '',
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
/** The routing file path, or "" when homeDir is not an absolute path (never relative to the working directory). */
|
|
340
|
+
export function resolveRoutingPath(homeDir) {
|
|
341
|
+
return homeIsSet(homeDir) ? join(homeDir, MODEL_ROUTING_RELATIVE_PATH) : '';
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Opens without blocking (a FIFO or device at the path cannot hang the read),
|
|
345
|
+
* then checks the opened handle is a regular file and reads at most
|
|
346
|
+
* MAX_FILE_BYTES + 1 bytes.
|
|
347
|
+
*/
|
|
348
|
+
async function readRoutingFile(filePath) {
|
|
349
|
+
let handle;
|
|
350
|
+
try {
|
|
351
|
+
handle = await open(filePath, constants.O_RDONLY | constants.O_NONBLOCK);
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
if (errorCode(error) === 'ENOENT')
|
|
355
|
+
return { kind: 'missing' };
|
|
356
|
+
return { kind: 'error', errors: [`could not read the file (${errorCode(error)})`] };
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
const info = await handle.stat();
|
|
360
|
+
if (!info.isFile())
|
|
361
|
+
return { kind: 'error', errors: ['routing file is not a regular file'] };
|
|
362
|
+
if (info.size > MAX_FILE_BYTES)
|
|
363
|
+
return { kind: 'error', errors: ['file is too large'] };
|
|
364
|
+
const chunks = [];
|
|
365
|
+
let total = 0;
|
|
366
|
+
while (total <= MAX_FILE_BYTES) {
|
|
367
|
+
const chunk = Buffer.alloc(Math.min(READ_CHUNK_BYTES, MAX_FILE_BYTES + 1 - total));
|
|
368
|
+
const { bytesRead } = await handle.read(chunk, 0, chunk.length, null);
|
|
369
|
+
if (bytesRead === 0)
|
|
370
|
+
break;
|
|
371
|
+
chunks.push(chunk.subarray(0, bytesRead));
|
|
372
|
+
total += bytesRead;
|
|
373
|
+
}
|
|
374
|
+
if (total > MAX_FILE_BYTES)
|
|
375
|
+
return { kind: 'error', errors: ['file is too large'] };
|
|
376
|
+
return { kind: 'text', text: Buffer.concat(chunks, total).toString('utf8') };
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
return { kind: 'error', errors: [`could not read the file (${errorCode(error)})`] };
|
|
380
|
+
}
|
|
381
|
+
finally {
|
|
382
|
+
await handle.close().catch(() => undefined);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
export async function loadRoutingConfig(homeDir) {
|
|
386
|
+
const filePath = resolveRoutingPath(homeDir);
|
|
387
|
+
const invalid = (errors) => ({
|
|
388
|
+
state: { status: 'invalid', path: filePath, errors },
|
|
389
|
+
config: null,
|
|
390
|
+
});
|
|
391
|
+
if (filePath === '')
|
|
392
|
+
return invalid([HOME_NOT_SET]);
|
|
393
|
+
try {
|
|
394
|
+
const outcome = await readRoutingFile(filePath);
|
|
395
|
+
if (outcome.kind === 'missing') {
|
|
396
|
+
return { state: { status: 'missing', path: filePath, errors: [] }, config: null };
|
|
397
|
+
}
|
|
398
|
+
if (outcome.kind === 'error')
|
|
399
|
+
return invalid(outcome.errors);
|
|
400
|
+
let json;
|
|
401
|
+
try {
|
|
402
|
+
json = JSON.parse(outcome.text);
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
return invalid(['not valid JSON']);
|
|
406
|
+
}
|
|
407
|
+
const result = parseModelRoutingConfig(json);
|
|
408
|
+
if (!result.ok)
|
|
409
|
+
return invalid(result.errors);
|
|
410
|
+
return { state: { status: 'ok', path: filePath, errors: [] }, config: result.config };
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
return invalid([`could not read the file (${errorCode(error)})`]);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
export async function saveRoutingConfig(homeDir, config) {
|
|
417
|
+
const target = resolveRoutingPath(homeDir);
|
|
418
|
+
if (target === '')
|
|
419
|
+
return { ok: false, error: HOME_NOT_SET };
|
|
420
|
+
let temp = '';
|
|
421
|
+
try {
|
|
422
|
+
const result = parseModelRoutingConfig(config);
|
|
423
|
+
if (!result.ok)
|
|
424
|
+
return { ok: false, error: result.errors[0] ?? CONFIG_UNREADABLE };
|
|
425
|
+
const directory = dirname(target);
|
|
426
|
+
temp = join(directory, `${TEMP_PREFIX}${process.pid}.${randomBytes(6).toString('hex')}.tmp`);
|
|
427
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
428
|
+
await writeFile(temp, `${JSON.stringify(result.config, null, 2)}\n`, { mode: 0o600, flag: 'wx' });
|
|
429
|
+
await rename(temp, target);
|
|
430
|
+
return { ok: true };
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
if (temp !== '')
|
|
434
|
+
await rm(temp, { force: true }).catch(() => undefined);
|
|
435
|
+
return { ok: false, error: `could not write the routing file (${errorCode(error)})` };
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
//# sourceMappingURL=routing-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing-config.js","sourceRoot":"","sources":["../../src/quota/routing-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type GetModelRoutingPayload, type ModelRoutingSnapshot, type QuotaReport, type SetModelRoutingPayload, type SetModelRoutingResult } from './types.js';
|
|
2
|
+
/** How long a collected report serves `get` without `refresh`. */
|
|
3
|
+
export declare const MODEL_ROUTING_CACHE_MS = 60000;
|
|
4
|
+
/** How old a cached report may be and still validate a `set`. */
|
|
5
|
+
export declare const MODEL_ROUTING_VALIDATION_MAX_AGE_MS: number;
|
|
6
|
+
/** Per-provider fetch timeout used by the real service. */
|
|
7
|
+
export declare const MODEL_ROUTING_COLLECT_TIMEOUT_MS = 15000;
|
|
8
|
+
export interface ModelRoutingServiceOptions {
|
|
9
|
+
homeDir: string;
|
|
10
|
+
now: () => number;
|
|
11
|
+
/** Collects a full report; `refresh` is true when the caller asked to bypass the cache. */
|
|
12
|
+
collect: (refresh: boolean) => Promise<QuotaReport>;
|
|
13
|
+
cacheMs?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface ModelRoutingService {
|
|
16
|
+
get(payload: GetModelRoutingPayload): Promise<ModelRoutingSnapshot>;
|
|
17
|
+
set(payload: SetModelRoutingPayload): Promise<SetModelRoutingResult>;
|
|
18
|
+
}
|
|
19
|
+
export declare function createModelRoutingService(options: ModelRoutingServiceOptions): ModelRoutingService;
|
|
20
|
+
/**
|
|
21
|
+
* The bridge's service: every provider, model lists included. The quota modules
|
|
22
|
+
* load on the first collect, so creating the service costs nothing at start-up.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createRealModelRoutingService(homeDir: string): ModelRoutingService;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Bridge-side model routing for the dashboard: the stored routing config joined
|
|
2
|
+
// with live quota, behind a short cache so opening the screen does not re-run
|
|
3
|
+
// every quota and model fetch. The routing file itself is never cached.
|
|
4
|
+
import { annotateTiers } from './annotate.js';
|
|
5
|
+
import { loadRoutingConfig, parseModelRoutingConfig, saveRoutingConfig, starterRoutingConfig, validatePicksAgainstModels, } from './routing-config.js';
|
|
6
|
+
import { MODEL_ROUTING_MIN_REFRESH_MS, MODEL_ROUTING_SCHEMA, QUOTA_PROVIDER_IDS, } from './types.js';
|
|
7
|
+
/** How long a collected report serves `get` without `refresh`. */
|
|
8
|
+
export const MODEL_ROUTING_CACHE_MS = 60_000;
|
|
9
|
+
/** How old a cached report may be and still validate a `set`. */
|
|
10
|
+
export const MODEL_ROUTING_VALIDATION_MAX_AGE_MS = 10 * 60_000;
|
|
11
|
+
/** Per-provider fetch timeout used by the real service. */
|
|
12
|
+
export const MODEL_ROUTING_COLLECT_TIMEOUT_MS = 15_000;
|
|
13
|
+
const INVALID_FILE_BLOCKED = 'The existing routing file is invalid; confirm to replace it.';
|
|
14
|
+
function shortReason(error) {
|
|
15
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
16
|
+
const line = (text.split('\n')[0] ?? '').trim();
|
|
17
|
+
return line.length > 160 ? `${line.slice(0, 157)}...` : line || 'unknown error';
|
|
18
|
+
}
|
|
19
|
+
export function createModelRoutingService(options) {
|
|
20
|
+
const cacheMs = options.cacheMs ?? MODEL_ROUTING_CACHE_MS;
|
|
21
|
+
let cache = null;
|
|
22
|
+
let inFlight = null;
|
|
23
|
+
let setQueue = Promise.resolve();
|
|
24
|
+
const fresh = (maxAgeMs) => cache !== null && options.now() - cache.collectedAt < maxAgeMs ? cache : null;
|
|
25
|
+
/**
|
|
26
|
+
* One collect at a time; callers arriving while it runs share it. Failures are not cached.
|
|
27
|
+
* `options.collect` is invoked on a microtask so a synchronous throw becomes a rejection of
|
|
28
|
+
* `run`, and the slot is cleared only by the run that owns it.
|
|
29
|
+
*/
|
|
30
|
+
const collect = (refresh) => {
|
|
31
|
+
if (inFlight)
|
|
32
|
+
return inFlight;
|
|
33
|
+
const startedAt = options.now();
|
|
34
|
+
const run = Promise.resolve()
|
|
35
|
+
.then(() => options.collect(refresh))
|
|
36
|
+
.then(report => {
|
|
37
|
+
cache = { report, collectedAt: startedAt, completedAt: options.now() };
|
|
38
|
+
return report;
|
|
39
|
+
}, (error) => {
|
|
40
|
+
throw new Error(`quota could not be collected: ${shortReason(error)}`);
|
|
41
|
+
});
|
|
42
|
+
inFlight = run;
|
|
43
|
+
// Registered before any caller can await `run`, so the slot is free when they resume.
|
|
44
|
+
const clear = () => { if (inFlight === run)
|
|
45
|
+
inFlight = null; };
|
|
46
|
+
run.then(clear, clear);
|
|
47
|
+
return run;
|
|
48
|
+
};
|
|
49
|
+
const get = async (payload) => {
|
|
50
|
+
// The floor applies only when nothing is collecting. A refresh that arrives during a collect
|
|
51
|
+
// joins it. Otherwise a refresh inside the floor is the last completed report, even when
|
|
52
|
+
// cacheMs has elapsed or a slow collect has aged the report past the normal cache window.
|
|
53
|
+
const refreshRequested = payload.refresh === true;
|
|
54
|
+
const insideFloor = inFlight === null && cache !== null && options.now() - cache.completedAt < MODEL_ROUTING_MIN_REFRESH_MS;
|
|
55
|
+
const refresh = refreshRequested && !insideFloor;
|
|
56
|
+
const hit = refreshRequested && insideFloor ? cache : refresh ? null : fresh(cacheMs);
|
|
57
|
+
const base = hit ? hit.report : await collect(refresh);
|
|
58
|
+
const loaded = await loadRoutingConfig(options.homeDir);
|
|
59
|
+
const config = loaded.state.status === 'ok' ? loaded.config : null;
|
|
60
|
+
const report = {
|
|
61
|
+
...base,
|
|
62
|
+
routingConfig: loaded.state,
|
|
63
|
+
tiers: config ? annotateTiers(config, base.providers, base.routing, base.models) : [],
|
|
64
|
+
steering: config ? config.steering : '',
|
|
65
|
+
};
|
|
66
|
+
return { schema: MODEL_ROUTING_SCHEMA, config: config ?? starterRoutingConfig(), report, cached: hit !== null };
|
|
67
|
+
};
|
|
68
|
+
const saveOne = async (payload) => {
|
|
69
|
+
const parsed = parseModelRoutingConfig(payload.config);
|
|
70
|
+
if (!parsed.ok)
|
|
71
|
+
return { schema: MODEL_ROUTING_SCHEMA, saved: false, errors: parsed.errors };
|
|
72
|
+
if (payload.overwriteInvalid !== true && (await loadRoutingConfig(options.homeDir)).state.status === 'invalid') {
|
|
73
|
+
return { schema: MODEL_ROUTING_SCHEMA, saved: false, errors: [INVALID_FILE_BLOCKED], invalidFileBlocked: true };
|
|
74
|
+
}
|
|
75
|
+
const recent = fresh(MODEL_ROUTING_VALIDATION_MAX_AGE_MS);
|
|
76
|
+
const report = recent ? recent.report : await collect(false);
|
|
77
|
+
const pickErrors = validatePicksAgainstModels(parsed.config, report.models);
|
|
78
|
+
if (pickErrors.length > 0)
|
|
79
|
+
return { schema: MODEL_ROUTING_SCHEMA, saved: false, errors: pickErrors };
|
|
80
|
+
const written = await saveRoutingConfig(options.homeDir, parsed.config);
|
|
81
|
+
if (!written.ok)
|
|
82
|
+
return { schema: MODEL_ROUTING_SCHEMA, saved: false, errors: [written.error] };
|
|
83
|
+
return { schema: MODEL_ROUTING_SCHEMA, saved: true, errors: [] };
|
|
84
|
+
};
|
|
85
|
+
const set = (payload) => {
|
|
86
|
+
const run = setQueue.then(() => saveOne(payload));
|
|
87
|
+
setQueue = run.catch(() => undefined);
|
|
88
|
+
return run;
|
|
89
|
+
};
|
|
90
|
+
return { get, set };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The bridge's service: every provider, model lists included. The quota modules
|
|
94
|
+
* load on the first collect, so creating the service costs nothing at start-up.
|
|
95
|
+
*/
|
|
96
|
+
export function createRealModelRoutingService(homeDir) {
|
|
97
|
+
let loading = null;
|
|
98
|
+
const load = () => {
|
|
99
|
+
if (!loading) {
|
|
100
|
+
loading = (async () => {
|
|
101
|
+
const cli = await import('../quota-cli.js');
|
|
102
|
+
return { collect: cli.collectQuotaReport, deps: await cli.loadRealQuotaDeps() };
|
|
103
|
+
})();
|
|
104
|
+
loading.catch(() => { loading = null; });
|
|
105
|
+
}
|
|
106
|
+
return loading;
|
|
107
|
+
};
|
|
108
|
+
return createModelRoutingService({
|
|
109
|
+
homeDir,
|
|
110
|
+
now: () => Date.now(),
|
|
111
|
+
collect: async () => {
|
|
112
|
+
const { collect, deps } = await load();
|
|
113
|
+
const { report } = await collect({ providers: [...QUOTA_PROVIDER_IDS], timeoutMs: MODEL_ROUTING_COLLECT_TIMEOUT_MS, includeModels: true }, { ...deps, homeDir });
|
|
114
|
+
return report;
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=routing-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing-service.js","sourceRoot":"","sources":["../../src/quota/routing-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { QuotaAvailability, QuotaRoutingAdvisor, QuotaWindow } from './types.js';
|
|
2
|
+
/** Assesses one window against `now`. Pure: never reads the clock. */
|
|
3
|
+
export declare function assessWindow(window: QuotaWindow, now: Date): {
|
|
4
|
+
availability: QuotaAvailability;
|
|
5
|
+
leftPercent: number | null;
|
|
6
|
+
resetsInMinutes: number | null;
|
|
7
|
+
runway: number | null;
|
|
8
|
+
resetting: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare const adviseQuotaRouting: QuotaRoutingAdvisor;
|