@s-berdia/opencode-router 0.0.0-stage → 1.0.1
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/LICENSE +21 -0
- package/README.md +202 -2
- package/USAGE.md +513 -0
- package/dist/bin.js +2951 -0
- package/dist/bin.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2010 -0
- package/dist/index.js.map +1 -0
- package/model-scores.json +181 -0
- package/package.json +71 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,2010 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var PLAN_PROFILES = ["go", "go-plus", "free"];
|
|
3
|
+
function isPlan(value) {
|
|
4
|
+
return typeof value === "string" && PLAN_PROFILES.includes(value);
|
|
5
|
+
}
|
|
6
|
+
var AGENT_ROLES = ["plan", "build", "general", "explore"];
|
|
7
|
+
var INTERNAL_ROLES = ["small_model", "title", "summary", "compaction"];
|
|
8
|
+
var ROLE_NAMES = [...AGENT_ROLES, ...INTERNAL_ROLES];
|
|
9
|
+
function isAgentRole(value) {
|
|
10
|
+
return value !== void 0 && AGENT_ROLES.includes(value);
|
|
11
|
+
}
|
|
12
|
+
function isRoleName(value) {
|
|
13
|
+
return ROLE_NAMES.includes(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/catalog.ts
|
|
17
|
+
import fs2 from "fs";
|
|
18
|
+
|
|
19
|
+
// src/paths.ts
|
|
20
|
+
import os from "os";
|
|
21
|
+
import path from "path";
|
|
22
|
+
import fs from "fs";
|
|
23
|
+
|
|
24
|
+
// node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
25
|
+
function createScanner(text, ignoreTrivia = false) {
|
|
26
|
+
const len = text.length;
|
|
27
|
+
let pos = 0, value = "", tokenOffset = 0, token = 16, lineNumber = 0, lineStartOffset = 0, tokenLineStartOffset = 0, prevTokenLineStartOffset = 0, scanError = 0;
|
|
28
|
+
function scanHexDigits(count, exact) {
|
|
29
|
+
let digits = 0;
|
|
30
|
+
let value2 = 0;
|
|
31
|
+
while (digits < count || !exact) {
|
|
32
|
+
let ch = text.charCodeAt(pos);
|
|
33
|
+
if (ch >= 48 && ch <= 57) {
|
|
34
|
+
value2 = value2 * 16 + ch - 48;
|
|
35
|
+
} else if (ch >= 65 && ch <= 70) {
|
|
36
|
+
value2 = value2 * 16 + ch - 65 + 10;
|
|
37
|
+
} else if (ch >= 97 && ch <= 102) {
|
|
38
|
+
value2 = value2 * 16 + ch - 97 + 10;
|
|
39
|
+
} else {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
pos++;
|
|
43
|
+
digits++;
|
|
44
|
+
}
|
|
45
|
+
if (digits < count) {
|
|
46
|
+
value2 = -1;
|
|
47
|
+
}
|
|
48
|
+
return value2;
|
|
49
|
+
}
|
|
50
|
+
function setPosition(newPosition) {
|
|
51
|
+
pos = newPosition;
|
|
52
|
+
value = "";
|
|
53
|
+
tokenOffset = 0;
|
|
54
|
+
token = 16;
|
|
55
|
+
scanError = 0;
|
|
56
|
+
}
|
|
57
|
+
function scanNumber() {
|
|
58
|
+
let start = pos;
|
|
59
|
+
if (text.charCodeAt(pos) === 48) {
|
|
60
|
+
pos++;
|
|
61
|
+
} else {
|
|
62
|
+
pos++;
|
|
63
|
+
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
64
|
+
pos++;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (pos < text.length && text.charCodeAt(pos) === 46) {
|
|
68
|
+
pos++;
|
|
69
|
+
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
70
|
+
pos++;
|
|
71
|
+
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
72
|
+
pos++;
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
scanError = 3;
|
|
76
|
+
return text.substring(start, pos);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
let end = pos;
|
|
80
|
+
if (pos < text.length && (text.charCodeAt(pos) === 69 || text.charCodeAt(pos) === 101)) {
|
|
81
|
+
pos++;
|
|
82
|
+
if (pos < text.length && text.charCodeAt(pos) === 43 || text.charCodeAt(pos) === 45) {
|
|
83
|
+
pos++;
|
|
84
|
+
}
|
|
85
|
+
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
86
|
+
pos++;
|
|
87
|
+
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
|
88
|
+
pos++;
|
|
89
|
+
}
|
|
90
|
+
end = pos;
|
|
91
|
+
} else {
|
|
92
|
+
scanError = 3;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return text.substring(start, end);
|
|
96
|
+
}
|
|
97
|
+
function scanString() {
|
|
98
|
+
let result = "", start = pos;
|
|
99
|
+
while (true) {
|
|
100
|
+
if (pos >= len) {
|
|
101
|
+
result += text.substring(start, pos);
|
|
102
|
+
scanError = 2;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
const ch = text.charCodeAt(pos);
|
|
106
|
+
if (ch === 34) {
|
|
107
|
+
result += text.substring(start, pos);
|
|
108
|
+
pos++;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
if (ch === 92) {
|
|
112
|
+
result += text.substring(start, pos);
|
|
113
|
+
pos++;
|
|
114
|
+
if (pos >= len) {
|
|
115
|
+
scanError = 2;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
const ch2 = text.charCodeAt(pos++);
|
|
119
|
+
switch (ch2) {
|
|
120
|
+
case 34:
|
|
121
|
+
result += '"';
|
|
122
|
+
break;
|
|
123
|
+
case 92:
|
|
124
|
+
result += "\\";
|
|
125
|
+
break;
|
|
126
|
+
case 47:
|
|
127
|
+
result += "/";
|
|
128
|
+
break;
|
|
129
|
+
case 98:
|
|
130
|
+
result += "\b";
|
|
131
|
+
break;
|
|
132
|
+
case 102:
|
|
133
|
+
result += "\f";
|
|
134
|
+
break;
|
|
135
|
+
case 110:
|
|
136
|
+
result += "\n";
|
|
137
|
+
break;
|
|
138
|
+
case 114:
|
|
139
|
+
result += "\r";
|
|
140
|
+
break;
|
|
141
|
+
case 116:
|
|
142
|
+
result += " ";
|
|
143
|
+
break;
|
|
144
|
+
case 117:
|
|
145
|
+
const ch3 = scanHexDigits(4, true);
|
|
146
|
+
if (ch3 >= 0) {
|
|
147
|
+
result += String.fromCharCode(ch3);
|
|
148
|
+
} else {
|
|
149
|
+
scanError = 4;
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
default:
|
|
153
|
+
scanError = 5;
|
|
154
|
+
}
|
|
155
|
+
start = pos;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (ch >= 0 && ch <= 31) {
|
|
159
|
+
if (isLineBreak(ch)) {
|
|
160
|
+
result += text.substring(start, pos);
|
|
161
|
+
scanError = 2;
|
|
162
|
+
break;
|
|
163
|
+
} else {
|
|
164
|
+
scanError = 6;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
pos++;
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
function scanNext() {
|
|
172
|
+
value = "";
|
|
173
|
+
scanError = 0;
|
|
174
|
+
tokenOffset = pos;
|
|
175
|
+
lineStartOffset = lineNumber;
|
|
176
|
+
prevTokenLineStartOffset = tokenLineStartOffset;
|
|
177
|
+
if (pos >= len) {
|
|
178
|
+
tokenOffset = len;
|
|
179
|
+
return token = 17;
|
|
180
|
+
}
|
|
181
|
+
let code = text.charCodeAt(pos);
|
|
182
|
+
if (isWhiteSpace(code)) {
|
|
183
|
+
do {
|
|
184
|
+
pos++;
|
|
185
|
+
value += String.fromCharCode(code);
|
|
186
|
+
code = text.charCodeAt(pos);
|
|
187
|
+
} while (isWhiteSpace(code));
|
|
188
|
+
return token = 15;
|
|
189
|
+
}
|
|
190
|
+
if (isLineBreak(code)) {
|
|
191
|
+
pos++;
|
|
192
|
+
value += String.fromCharCode(code);
|
|
193
|
+
if (code === 13 && text.charCodeAt(pos) === 10) {
|
|
194
|
+
pos++;
|
|
195
|
+
value += "\n";
|
|
196
|
+
}
|
|
197
|
+
lineNumber++;
|
|
198
|
+
tokenLineStartOffset = pos;
|
|
199
|
+
return token = 14;
|
|
200
|
+
}
|
|
201
|
+
switch (code) {
|
|
202
|
+
// tokens: []{}:,
|
|
203
|
+
case 123:
|
|
204
|
+
pos++;
|
|
205
|
+
return token = 1;
|
|
206
|
+
case 125:
|
|
207
|
+
pos++;
|
|
208
|
+
return token = 2;
|
|
209
|
+
case 91:
|
|
210
|
+
pos++;
|
|
211
|
+
return token = 3;
|
|
212
|
+
case 93:
|
|
213
|
+
pos++;
|
|
214
|
+
return token = 4;
|
|
215
|
+
case 58:
|
|
216
|
+
pos++;
|
|
217
|
+
return token = 6;
|
|
218
|
+
case 44:
|
|
219
|
+
pos++;
|
|
220
|
+
return token = 5;
|
|
221
|
+
// strings
|
|
222
|
+
case 34:
|
|
223
|
+
pos++;
|
|
224
|
+
value = scanString();
|
|
225
|
+
return token = 10;
|
|
226
|
+
// comments
|
|
227
|
+
case 47:
|
|
228
|
+
const start = pos - 1;
|
|
229
|
+
if (text.charCodeAt(pos + 1) === 47) {
|
|
230
|
+
pos += 2;
|
|
231
|
+
while (pos < len) {
|
|
232
|
+
if (isLineBreak(text.charCodeAt(pos))) {
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
pos++;
|
|
236
|
+
}
|
|
237
|
+
value = text.substring(start, pos);
|
|
238
|
+
return token = 12;
|
|
239
|
+
}
|
|
240
|
+
if (text.charCodeAt(pos + 1) === 42) {
|
|
241
|
+
pos += 2;
|
|
242
|
+
const safeLength = len - 1;
|
|
243
|
+
let commentClosed = false;
|
|
244
|
+
while (pos < safeLength) {
|
|
245
|
+
const ch = text.charCodeAt(pos);
|
|
246
|
+
if (ch === 42 && text.charCodeAt(pos + 1) === 47) {
|
|
247
|
+
pos += 2;
|
|
248
|
+
commentClosed = true;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
pos++;
|
|
252
|
+
if (isLineBreak(ch)) {
|
|
253
|
+
if (ch === 13 && text.charCodeAt(pos) === 10) {
|
|
254
|
+
pos++;
|
|
255
|
+
}
|
|
256
|
+
lineNumber++;
|
|
257
|
+
tokenLineStartOffset = pos;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (!commentClosed) {
|
|
261
|
+
pos++;
|
|
262
|
+
scanError = 1;
|
|
263
|
+
}
|
|
264
|
+
value = text.substring(start, pos);
|
|
265
|
+
return token = 13;
|
|
266
|
+
}
|
|
267
|
+
value += String.fromCharCode(code);
|
|
268
|
+
pos++;
|
|
269
|
+
return token = 16;
|
|
270
|
+
// numbers
|
|
271
|
+
case 45:
|
|
272
|
+
value += String.fromCharCode(code);
|
|
273
|
+
pos++;
|
|
274
|
+
if (pos === len || !isDigit(text.charCodeAt(pos))) {
|
|
275
|
+
return token = 16;
|
|
276
|
+
}
|
|
277
|
+
// found a minus, followed by a number so
|
|
278
|
+
// we fall through to proceed with scanning
|
|
279
|
+
// numbers
|
|
280
|
+
case 48:
|
|
281
|
+
case 49:
|
|
282
|
+
case 50:
|
|
283
|
+
case 51:
|
|
284
|
+
case 52:
|
|
285
|
+
case 53:
|
|
286
|
+
case 54:
|
|
287
|
+
case 55:
|
|
288
|
+
case 56:
|
|
289
|
+
case 57:
|
|
290
|
+
value += scanNumber();
|
|
291
|
+
return token = 11;
|
|
292
|
+
// literals and unknown symbols
|
|
293
|
+
default:
|
|
294
|
+
while (pos < len && isUnknownContentCharacter(code)) {
|
|
295
|
+
pos++;
|
|
296
|
+
code = text.charCodeAt(pos);
|
|
297
|
+
}
|
|
298
|
+
if (tokenOffset !== pos) {
|
|
299
|
+
value = text.substring(tokenOffset, pos);
|
|
300
|
+
switch (value) {
|
|
301
|
+
case "true":
|
|
302
|
+
return token = 8;
|
|
303
|
+
case "false":
|
|
304
|
+
return token = 9;
|
|
305
|
+
case "null":
|
|
306
|
+
return token = 7;
|
|
307
|
+
}
|
|
308
|
+
return token = 16;
|
|
309
|
+
}
|
|
310
|
+
value += String.fromCharCode(code);
|
|
311
|
+
pos++;
|
|
312
|
+
return token = 16;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function isUnknownContentCharacter(code) {
|
|
316
|
+
if (isWhiteSpace(code) || isLineBreak(code)) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
switch (code) {
|
|
320
|
+
case 125:
|
|
321
|
+
case 93:
|
|
322
|
+
case 123:
|
|
323
|
+
case 91:
|
|
324
|
+
case 34:
|
|
325
|
+
case 58:
|
|
326
|
+
case 44:
|
|
327
|
+
case 47:
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
function scanNextNonTrivia() {
|
|
333
|
+
let result;
|
|
334
|
+
do {
|
|
335
|
+
result = scanNext();
|
|
336
|
+
} while (result >= 12 && result <= 15);
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
setPosition,
|
|
341
|
+
getPosition: () => pos,
|
|
342
|
+
scan: ignoreTrivia ? scanNextNonTrivia : scanNext,
|
|
343
|
+
getToken: () => token,
|
|
344
|
+
getTokenValue: () => value,
|
|
345
|
+
getTokenOffset: () => tokenOffset,
|
|
346
|
+
getTokenLength: () => pos - tokenOffset,
|
|
347
|
+
getTokenStartLine: () => lineStartOffset,
|
|
348
|
+
getTokenStartCharacter: () => tokenOffset - prevTokenLineStartOffset,
|
|
349
|
+
getTokenError: () => scanError
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function isWhiteSpace(ch) {
|
|
353
|
+
return ch === 32 || ch === 9;
|
|
354
|
+
}
|
|
355
|
+
function isLineBreak(ch) {
|
|
356
|
+
return ch === 10 || ch === 13;
|
|
357
|
+
}
|
|
358
|
+
function isDigit(ch) {
|
|
359
|
+
return ch >= 48 && ch <= 57;
|
|
360
|
+
}
|
|
361
|
+
var CharacterCodes;
|
|
362
|
+
(function(CharacterCodes2) {
|
|
363
|
+
CharacterCodes2[CharacterCodes2["lineFeed"] = 10] = "lineFeed";
|
|
364
|
+
CharacterCodes2[CharacterCodes2["carriageReturn"] = 13] = "carriageReturn";
|
|
365
|
+
CharacterCodes2[CharacterCodes2["space"] = 32] = "space";
|
|
366
|
+
CharacterCodes2[CharacterCodes2["_0"] = 48] = "_0";
|
|
367
|
+
CharacterCodes2[CharacterCodes2["_1"] = 49] = "_1";
|
|
368
|
+
CharacterCodes2[CharacterCodes2["_2"] = 50] = "_2";
|
|
369
|
+
CharacterCodes2[CharacterCodes2["_3"] = 51] = "_3";
|
|
370
|
+
CharacterCodes2[CharacterCodes2["_4"] = 52] = "_4";
|
|
371
|
+
CharacterCodes2[CharacterCodes2["_5"] = 53] = "_5";
|
|
372
|
+
CharacterCodes2[CharacterCodes2["_6"] = 54] = "_6";
|
|
373
|
+
CharacterCodes2[CharacterCodes2["_7"] = 55] = "_7";
|
|
374
|
+
CharacterCodes2[CharacterCodes2["_8"] = 56] = "_8";
|
|
375
|
+
CharacterCodes2[CharacterCodes2["_9"] = 57] = "_9";
|
|
376
|
+
CharacterCodes2[CharacterCodes2["a"] = 97] = "a";
|
|
377
|
+
CharacterCodes2[CharacterCodes2["b"] = 98] = "b";
|
|
378
|
+
CharacterCodes2[CharacterCodes2["c"] = 99] = "c";
|
|
379
|
+
CharacterCodes2[CharacterCodes2["d"] = 100] = "d";
|
|
380
|
+
CharacterCodes2[CharacterCodes2["e"] = 101] = "e";
|
|
381
|
+
CharacterCodes2[CharacterCodes2["f"] = 102] = "f";
|
|
382
|
+
CharacterCodes2[CharacterCodes2["g"] = 103] = "g";
|
|
383
|
+
CharacterCodes2[CharacterCodes2["h"] = 104] = "h";
|
|
384
|
+
CharacterCodes2[CharacterCodes2["i"] = 105] = "i";
|
|
385
|
+
CharacterCodes2[CharacterCodes2["j"] = 106] = "j";
|
|
386
|
+
CharacterCodes2[CharacterCodes2["k"] = 107] = "k";
|
|
387
|
+
CharacterCodes2[CharacterCodes2["l"] = 108] = "l";
|
|
388
|
+
CharacterCodes2[CharacterCodes2["m"] = 109] = "m";
|
|
389
|
+
CharacterCodes2[CharacterCodes2["n"] = 110] = "n";
|
|
390
|
+
CharacterCodes2[CharacterCodes2["o"] = 111] = "o";
|
|
391
|
+
CharacterCodes2[CharacterCodes2["p"] = 112] = "p";
|
|
392
|
+
CharacterCodes2[CharacterCodes2["q"] = 113] = "q";
|
|
393
|
+
CharacterCodes2[CharacterCodes2["r"] = 114] = "r";
|
|
394
|
+
CharacterCodes2[CharacterCodes2["s"] = 115] = "s";
|
|
395
|
+
CharacterCodes2[CharacterCodes2["t"] = 116] = "t";
|
|
396
|
+
CharacterCodes2[CharacterCodes2["u"] = 117] = "u";
|
|
397
|
+
CharacterCodes2[CharacterCodes2["v"] = 118] = "v";
|
|
398
|
+
CharacterCodes2[CharacterCodes2["w"] = 119] = "w";
|
|
399
|
+
CharacterCodes2[CharacterCodes2["x"] = 120] = "x";
|
|
400
|
+
CharacterCodes2[CharacterCodes2["y"] = 121] = "y";
|
|
401
|
+
CharacterCodes2[CharacterCodes2["z"] = 122] = "z";
|
|
402
|
+
CharacterCodes2[CharacterCodes2["A"] = 65] = "A";
|
|
403
|
+
CharacterCodes2[CharacterCodes2["B"] = 66] = "B";
|
|
404
|
+
CharacterCodes2[CharacterCodes2["C"] = 67] = "C";
|
|
405
|
+
CharacterCodes2[CharacterCodes2["D"] = 68] = "D";
|
|
406
|
+
CharacterCodes2[CharacterCodes2["E"] = 69] = "E";
|
|
407
|
+
CharacterCodes2[CharacterCodes2["F"] = 70] = "F";
|
|
408
|
+
CharacterCodes2[CharacterCodes2["G"] = 71] = "G";
|
|
409
|
+
CharacterCodes2[CharacterCodes2["H"] = 72] = "H";
|
|
410
|
+
CharacterCodes2[CharacterCodes2["I"] = 73] = "I";
|
|
411
|
+
CharacterCodes2[CharacterCodes2["J"] = 74] = "J";
|
|
412
|
+
CharacterCodes2[CharacterCodes2["K"] = 75] = "K";
|
|
413
|
+
CharacterCodes2[CharacterCodes2["L"] = 76] = "L";
|
|
414
|
+
CharacterCodes2[CharacterCodes2["M"] = 77] = "M";
|
|
415
|
+
CharacterCodes2[CharacterCodes2["N"] = 78] = "N";
|
|
416
|
+
CharacterCodes2[CharacterCodes2["O"] = 79] = "O";
|
|
417
|
+
CharacterCodes2[CharacterCodes2["P"] = 80] = "P";
|
|
418
|
+
CharacterCodes2[CharacterCodes2["Q"] = 81] = "Q";
|
|
419
|
+
CharacterCodes2[CharacterCodes2["R"] = 82] = "R";
|
|
420
|
+
CharacterCodes2[CharacterCodes2["S"] = 83] = "S";
|
|
421
|
+
CharacterCodes2[CharacterCodes2["T"] = 84] = "T";
|
|
422
|
+
CharacterCodes2[CharacterCodes2["U"] = 85] = "U";
|
|
423
|
+
CharacterCodes2[CharacterCodes2["V"] = 86] = "V";
|
|
424
|
+
CharacterCodes2[CharacterCodes2["W"] = 87] = "W";
|
|
425
|
+
CharacterCodes2[CharacterCodes2["X"] = 88] = "X";
|
|
426
|
+
CharacterCodes2[CharacterCodes2["Y"] = 89] = "Y";
|
|
427
|
+
CharacterCodes2[CharacterCodes2["Z"] = 90] = "Z";
|
|
428
|
+
CharacterCodes2[CharacterCodes2["asterisk"] = 42] = "asterisk";
|
|
429
|
+
CharacterCodes2[CharacterCodes2["backslash"] = 92] = "backslash";
|
|
430
|
+
CharacterCodes2[CharacterCodes2["closeBrace"] = 125] = "closeBrace";
|
|
431
|
+
CharacterCodes2[CharacterCodes2["closeBracket"] = 93] = "closeBracket";
|
|
432
|
+
CharacterCodes2[CharacterCodes2["colon"] = 58] = "colon";
|
|
433
|
+
CharacterCodes2[CharacterCodes2["comma"] = 44] = "comma";
|
|
434
|
+
CharacterCodes2[CharacterCodes2["dot"] = 46] = "dot";
|
|
435
|
+
CharacterCodes2[CharacterCodes2["doubleQuote"] = 34] = "doubleQuote";
|
|
436
|
+
CharacterCodes2[CharacterCodes2["minus"] = 45] = "minus";
|
|
437
|
+
CharacterCodes2[CharacterCodes2["openBrace"] = 123] = "openBrace";
|
|
438
|
+
CharacterCodes2[CharacterCodes2["openBracket"] = 91] = "openBracket";
|
|
439
|
+
CharacterCodes2[CharacterCodes2["plus"] = 43] = "plus";
|
|
440
|
+
CharacterCodes2[CharacterCodes2["slash"] = 47] = "slash";
|
|
441
|
+
CharacterCodes2[CharacterCodes2["formFeed"] = 12] = "formFeed";
|
|
442
|
+
CharacterCodes2[CharacterCodes2["tab"] = 9] = "tab";
|
|
443
|
+
})(CharacterCodes || (CharacterCodes = {}));
|
|
444
|
+
|
|
445
|
+
// node_modules/jsonc-parser/lib/esm/impl/string-intern.js
|
|
446
|
+
var cachedSpaces = new Array(20).fill(0).map((_, index) => {
|
|
447
|
+
return " ".repeat(index);
|
|
448
|
+
});
|
|
449
|
+
var maxCachedValues = 200;
|
|
450
|
+
var cachedBreakLinesWithSpaces = {
|
|
451
|
+
" ": {
|
|
452
|
+
"\n": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
453
|
+
return "\n" + " ".repeat(index);
|
|
454
|
+
}),
|
|
455
|
+
"\r": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
456
|
+
return "\r" + " ".repeat(index);
|
|
457
|
+
}),
|
|
458
|
+
"\r\n": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
459
|
+
return "\r\n" + " ".repeat(index);
|
|
460
|
+
})
|
|
461
|
+
},
|
|
462
|
+
" ": {
|
|
463
|
+
"\n": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
464
|
+
return "\n" + " ".repeat(index);
|
|
465
|
+
}),
|
|
466
|
+
"\r": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
467
|
+
return "\r" + " ".repeat(index);
|
|
468
|
+
}),
|
|
469
|
+
"\r\n": new Array(maxCachedValues).fill(0).map((_, index) => {
|
|
470
|
+
return "\r\n" + " ".repeat(index);
|
|
471
|
+
})
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// node_modules/jsonc-parser/lib/esm/impl/parser.js
|
|
476
|
+
var ParseOptions;
|
|
477
|
+
(function(ParseOptions2) {
|
|
478
|
+
ParseOptions2.DEFAULT = {
|
|
479
|
+
allowTrailingComma: false
|
|
480
|
+
};
|
|
481
|
+
})(ParseOptions || (ParseOptions = {}));
|
|
482
|
+
function parse(text, errors = [], options = ParseOptions.DEFAULT) {
|
|
483
|
+
let currentProperty = null;
|
|
484
|
+
let currentParent = [];
|
|
485
|
+
const previousParents = [];
|
|
486
|
+
function onValue(value) {
|
|
487
|
+
if (Array.isArray(currentParent)) {
|
|
488
|
+
currentParent.push(value);
|
|
489
|
+
} else if (currentProperty !== null) {
|
|
490
|
+
currentParent[currentProperty] = value;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const visitor = {
|
|
494
|
+
onObjectBegin: () => {
|
|
495
|
+
const object = {};
|
|
496
|
+
onValue(object);
|
|
497
|
+
previousParents.push(currentParent);
|
|
498
|
+
currentParent = object;
|
|
499
|
+
currentProperty = null;
|
|
500
|
+
},
|
|
501
|
+
onObjectProperty: (name) => {
|
|
502
|
+
currentProperty = name;
|
|
503
|
+
},
|
|
504
|
+
onObjectEnd: () => {
|
|
505
|
+
currentParent = previousParents.pop();
|
|
506
|
+
},
|
|
507
|
+
onArrayBegin: () => {
|
|
508
|
+
const array = [];
|
|
509
|
+
onValue(array);
|
|
510
|
+
previousParents.push(currentParent);
|
|
511
|
+
currentParent = array;
|
|
512
|
+
currentProperty = null;
|
|
513
|
+
},
|
|
514
|
+
onArrayEnd: () => {
|
|
515
|
+
currentParent = previousParents.pop();
|
|
516
|
+
},
|
|
517
|
+
onLiteralValue: onValue,
|
|
518
|
+
onError: (error, offset, length) => {
|
|
519
|
+
errors.push({ error, offset, length });
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
visit(text, visitor, options);
|
|
523
|
+
return currentParent[0];
|
|
524
|
+
}
|
|
525
|
+
function visit(text, visitor, options = ParseOptions.DEFAULT) {
|
|
526
|
+
const _scanner = createScanner(text, false);
|
|
527
|
+
const _jsonPath = [];
|
|
528
|
+
let suppressedCallbacks = 0;
|
|
529
|
+
function toNoArgVisit(visitFunction) {
|
|
530
|
+
return visitFunction ? () => suppressedCallbacks === 0 && visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()) : () => true;
|
|
531
|
+
}
|
|
532
|
+
function toOneArgVisit(visitFunction) {
|
|
533
|
+
return visitFunction ? (arg) => suppressedCallbacks === 0 && visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()) : () => true;
|
|
534
|
+
}
|
|
535
|
+
function toOneArgVisitWithPath(visitFunction) {
|
|
536
|
+
return visitFunction ? (arg) => suppressedCallbacks === 0 && visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter(), () => _jsonPath.slice()) : () => true;
|
|
537
|
+
}
|
|
538
|
+
function toBeginVisit(visitFunction) {
|
|
539
|
+
return visitFunction ? () => {
|
|
540
|
+
if (suppressedCallbacks > 0) {
|
|
541
|
+
suppressedCallbacks++;
|
|
542
|
+
} else {
|
|
543
|
+
let cbReturn = visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter(), () => _jsonPath.slice());
|
|
544
|
+
if (cbReturn === false) {
|
|
545
|
+
suppressedCallbacks = 1;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
} : () => true;
|
|
549
|
+
}
|
|
550
|
+
function toEndVisit(visitFunction) {
|
|
551
|
+
return visitFunction ? () => {
|
|
552
|
+
if (suppressedCallbacks > 0) {
|
|
553
|
+
suppressedCallbacks--;
|
|
554
|
+
}
|
|
555
|
+
if (suppressedCallbacks === 0) {
|
|
556
|
+
visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter());
|
|
557
|
+
}
|
|
558
|
+
} : () => true;
|
|
559
|
+
}
|
|
560
|
+
const onObjectBegin = toBeginVisit(visitor.onObjectBegin), onObjectProperty = toOneArgVisitWithPath(visitor.onObjectProperty), onObjectEnd = toEndVisit(visitor.onObjectEnd), onArrayBegin = toBeginVisit(visitor.onArrayBegin), onArrayEnd = toEndVisit(visitor.onArrayEnd), onLiteralValue = toOneArgVisitWithPath(visitor.onLiteralValue), onSeparator = toOneArgVisit(visitor.onSeparator), onComment = toNoArgVisit(visitor.onComment), onError = toOneArgVisit(visitor.onError);
|
|
561
|
+
const disallowComments = options && options.disallowComments;
|
|
562
|
+
const allowTrailingComma = options && options.allowTrailingComma;
|
|
563
|
+
function scanNext() {
|
|
564
|
+
while (true) {
|
|
565
|
+
const token = _scanner.scan();
|
|
566
|
+
switch (_scanner.getTokenError()) {
|
|
567
|
+
case 4:
|
|
568
|
+
handleError(
|
|
569
|
+
14
|
|
570
|
+
/* ParseErrorCode.InvalidUnicode */
|
|
571
|
+
);
|
|
572
|
+
break;
|
|
573
|
+
case 5:
|
|
574
|
+
handleError(
|
|
575
|
+
15
|
|
576
|
+
/* ParseErrorCode.InvalidEscapeCharacter */
|
|
577
|
+
);
|
|
578
|
+
break;
|
|
579
|
+
case 3:
|
|
580
|
+
handleError(
|
|
581
|
+
13
|
|
582
|
+
/* ParseErrorCode.UnexpectedEndOfNumber */
|
|
583
|
+
);
|
|
584
|
+
break;
|
|
585
|
+
case 1:
|
|
586
|
+
if (!disallowComments) {
|
|
587
|
+
handleError(
|
|
588
|
+
11
|
|
589
|
+
/* ParseErrorCode.UnexpectedEndOfComment */
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
break;
|
|
593
|
+
case 2:
|
|
594
|
+
handleError(
|
|
595
|
+
12
|
|
596
|
+
/* ParseErrorCode.UnexpectedEndOfString */
|
|
597
|
+
);
|
|
598
|
+
break;
|
|
599
|
+
case 6:
|
|
600
|
+
handleError(
|
|
601
|
+
16
|
|
602
|
+
/* ParseErrorCode.InvalidCharacter */
|
|
603
|
+
);
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
switch (token) {
|
|
607
|
+
case 12:
|
|
608
|
+
case 13:
|
|
609
|
+
if (disallowComments) {
|
|
610
|
+
handleError(
|
|
611
|
+
10
|
|
612
|
+
/* ParseErrorCode.InvalidCommentToken */
|
|
613
|
+
);
|
|
614
|
+
} else {
|
|
615
|
+
onComment();
|
|
616
|
+
}
|
|
617
|
+
break;
|
|
618
|
+
case 16:
|
|
619
|
+
handleError(
|
|
620
|
+
1
|
|
621
|
+
/* ParseErrorCode.InvalidSymbol */
|
|
622
|
+
);
|
|
623
|
+
break;
|
|
624
|
+
case 15:
|
|
625
|
+
case 14:
|
|
626
|
+
break;
|
|
627
|
+
default:
|
|
628
|
+
return token;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function handleError(error, skipUntilAfter = [], skipUntil = []) {
|
|
633
|
+
onError(error);
|
|
634
|
+
if (skipUntilAfter.length + skipUntil.length > 0) {
|
|
635
|
+
let token = _scanner.getToken();
|
|
636
|
+
while (token !== 17) {
|
|
637
|
+
if (skipUntilAfter.indexOf(token) !== -1) {
|
|
638
|
+
scanNext();
|
|
639
|
+
break;
|
|
640
|
+
} else if (skipUntil.indexOf(token) !== -1) {
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
token = scanNext();
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
function parseString(isValue) {
|
|
648
|
+
const value = _scanner.getTokenValue();
|
|
649
|
+
if (isValue) {
|
|
650
|
+
onLiteralValue(value);
|
|
651
|
+
} else {
|
|
652
|
+
onObjectProperty(value);
|
|
653
|
+
_jsonPath.push(value);
|
|
654
|
+
}
|
|
655
|
+
scanNext();
|
|
656
|
+
return true;
|
|
657
|
+
}
|
|
658
|
+
function parseLiteral() {
|
|
659
|
+
switch (_scanner.getToken()) {
|
|
660
|
+
case 11:
|
|
661
|
+
const tokenValue = _scanner.getTokenValue();
|
|
662
|
+
let value = Number(tokenValue);
|
|
663
|
+
if (isNaN(value)) {
|
|
664
|
+
handleError(
|
|
665
|
+
2
|
|
666
|
+
/* ParseErrorCode.InvalidNumberFormat */
|
|
667
|
+
);
|
|
668
|
+
value = 0;
|
|
669
|
+
}
|
|
670
|
+
onLiteralValue(value);
|
|
671
|
+
break;
|
|
672
|
+
case 7:
|
|
673
|
+
onLiteralValue(null);
|
|
674
|
+
break;
|
|
675
|
+
case 8:
|
|
676
|
+
onLiteralValue(true);
|
|
677
|
+
break;
|
|
678
|
+
case 9:
|
|
679
|
+
onLiteralValue(false);
|
|
680
|
+
break;
|
|
681
|
+
default:
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
scanNext();
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
function parseProperty() {
|
|
688
|
+
if (_scanner.getToken() !== 10) {
|
|
689
|
+
handleError(3, [], [
|
|
690
|
+
2,
|
|
691
|
+
5
|
|
692
|
+
/* SyntaxKind.CommaToken */
|
|
693
|
+
]);
|
|
694
|
+
return false;
|
|
695
|
+
}
|
|
696
|
+
parseString(false);
|
|
697
|
+
if (_scanner.getToken() === 6) {
|
|
698
|
+
onSeparator(":");
|
|
699
|
+
scanNext();
|
|
700
|
+
if (!parseValue()) {
|
|
701
|
+
handleError(4, [], [
|
|
702
|
+
2,
|
|
703
|
+
5
|
|
704
|
+
/* SyntaxKind.CommaToken */
|
|
705
|
+
]);
|
|
706
|
+
}
|
|
707
|
+
} else {
|
|
708
|
+
handleError(5, [], [
|
|
709
|
+
2,
|
|
710
|
+
5
|
|
711
|
+
/* SyntaxKind.CommaToken */
|
|
712
|
+
]);
|
|
713
|
+
}
|
|
714
|
+
_jsonPath.pop();
|
|
715
|
+
return true;
|
|
716
|
+
}
|
|
717
|
+
function parseObject() {
|
|
718
|
+
onObjectBegin();
|
|
719
|
+
scanNext();
|
|
720
|
+
let needsComma = false;
|
|
721
|
+
while (_scanner.getToken() !== 2 && _scanner.getToken() !== 17) {
|
|
722
|
+
if (_scanner.getToken() === 5) {
|
|
723
|
+
if (!needsComma) {
|
|
724
|
+
handleError(4, [], []);
|
|
725
|
+
}
|
|
726
|
+
onSeparator(",");
|
|
727
|
+
scanNext();
|
|
728
|
+
if (_scanner.getToken() === 2 && allowTrailingComma) {
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
} else if (needsComma) {
|
|
732
|
+
handleError(6, [], []);
|
|
733
|
+
}
|
|
734
|
+
if (!parseProperty()) {
|
|
735
|
+
handleError(4, [], [
|
|
736
|
+
2,
|
|
737
|
+
5
|
|
738
|
+
/* SyntaxKind.CommaToken */
|
|
739
|
+
]);
|
|
740
|
+
}
|
|
741
|
+
needsComma = true;
|
|
742
|
+
}
|
|
743
|
+
onObjectEnd();
|
|
744
|
+
if (_scanner.getToken() !== 2) {
|
|
745
|
+
handleError(7, [
|
|
746
|
+
2
|
|
747
|
+
/* SyntaxKind.CloseBraceToken */
|
|
748
|
+
], []);
|
|
749
|
+
} else {
|
|
750
|
+
scanNext();
|
|
751
|
+
}
|
|
752
|
+
return true;
|
|
753
|
+
}
|
|
754
|
+
function parseArray() {
|
|
755
|
+
onArrayBegin();
|
|
756
|
+
scanNext();
|
|
757
|
+
let isFirstElement = true;
|
|
758
|
+
let needsComma = false;
|
|
759
|
+
while (_scanner.getToken() !== 4 && _scanner.getToken() !== 17) {
|
|
760
|
+
if (_scanner.getToken() === 5) {
|
|
761
|
+
if (!needsComma) {
|
|
762
|
+
handleError(4, [], []);
|
|
763
|
+
}
|
|
764
|
+
onSeparator(",");
|
|
765
|
+
scanNext();
|
|
766
|
+
if (_scanner.getToken() === 4 && allowTrailingComma) {
|
|
767
|
+
break;
|
|
768
|
+
}
|
|
769
|
+
} else if (needsComma) {
|
|
770
|
+
handleError(6, [], []);
|
|
771
|
+
}
|
|
772
|
+
if (isFirstElement) {
|
|
773
|
+
_jsonPath.push(0);
|
|
774
|
+
isFirstElement = false;
|
|
775
|
+
} else {
|
|
776
|
+
_jsonPath[_jsonPath.length - 1]++;
|
|
777
|
+
}
|
|
778
|
+
if (!parseValue()) {
|
|
779
|
+
handleError(4, [], [
|
|
780
|
+
4,
|
|
781
|
+
5
|
|
782
|
+
/* SyntaxKind.CommaToken */
|
|
783
|
+
]);
|
|
784
|
+
}
|
|
785
|
+
needsComma = true;
|
|
786
|
+
}
|
|
787
|
+
onArrayEnd();
|
|
788
|
+
if (!isFirstElement) {
|
|
789
|
+
_jsonPath.pop();
|
|
790
|
+
}
|
|
791
|
+
if (_scanner.getToken() !== 4) {
|
|
792
|
+
handleError(8, [
|
|
793
|
+
4
|
|
794
|
+
/* SyntaxKind.CloseBracketToken */
|
|
795
|
+
], []);
|
|
796
|
+
} else {
|
|
797
|
+
scanNext();
|
|
798
|
+
}
|
|
799
|
+
return true;
|
|
800
|
+
}
|
|
801
|
+
function parseValue() {
|
|
802
|
+
switch (_scanner.getToken()) {
|
|
803
|
+
case 3:
|
|
804
|
+
return parseArray();
|
|
805
|
+
case 1:
|
|
806
|
+
return parseObject();
|
|
807
|
+
case 10:
|
|
808
|
+
return parseString(true);
|
|
809
|
+
default:
|
|
810
|
+
return parseLiteral();
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
scanNext();
|
|
814
|
+
if (_scanner.getToken() === 17) {
|
|
815
|
+
if (options.allowEmptyContent) {
|
|
816
|
+
return true;
|
|
817
|
+
}
|
|
818
|
+
handleError(4, [], []);
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
if (!parseValue()) {
|
|
822
|
+
handleError(4, [], []);
|
|
823
|
+
return false;
|
|
824
|
+
}
|
|
825
|
+
if (_scanner.getToken() !== 17) {
|
|
826
|
+
handleError(9, [], []);
|
|
827
|
+
}
|
|
828
|
+
return true;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// node_modules/jsonc-parser/lib/esm/main.js
|
|
832
|
+
var ScanError;
|
|
833
|
+
(function(ScanError2) {
|
|
834
|
+
ScanError2[ScanError2["None"] = 0] = "None";
|
|
835
|
+
ScanError2[ScanError2["UnexpectedEndOfComment"] = 1] = "UnexpectedEndOfComment";
|
|
836
|
+
ScanError2[ScanError2["UnexpectedEndOfString"] = 2] = "UnexpectedEndOfString";
|
|
837
|
+
ScanError2[ScanError2["UnexpectedEndOfNumber"] = 3] = "UnexpectedEndOfNumber";
|
|
838
|
+
ScanError2[ScanError2["InvalidUnicode"] = 4] = "InvalidUnicode";
|
|
839
|
+
ScanError2[ScanError2["InvalidEscapeCharacter"] = 5] = "InvalidEscapeCharacter";
|
|
840
|
+
ScanError2[ScanError2["InvalidCharacter"] = 6] = "InvalidCharacter";
|
|
841
|
+
})(ScanError || (ScanError = {}));
|
|
842
|
+
var SyntaxKind;
|
|
843
|
+
(function(SyntaxKind2) {
|
|
844
|
+
SyntaxKind2[SyntaxKind2["OpenBraceToken"] = 1] = "OpenBraceToken";
|
|
845
|
+
SyntaxKind2[SyntaxKind2["CloseBraceToken"] = 2] = "CloseBraceToken";
|
|
846
|
+
SyntaxKind2[SyntaxKind2["OpenBracketToken"] = 3] = "OpenBracketToken";
|
|
847
|
+
SyntaxKind2[SyntaxKind2["CloseBracketToken"] = 4] = "CloseBracketToken";
|
|
848
|
+
SyntaxKind2[SyntaxKind2["CommaToken"] = 5] = "CommaToken";
|
|
849
|
+
SyntaxKind2[SyntaxKind2["ColonToken"] = 6] = "ColonToken";
|
|
850
|
+
SyntaxKind2[SyntaxKind2["NullKeyword"] = 7] = "NullKeyword";
|
|
851
|
+
SyntaxKind2[SyntaxKind2["TrueKeyword"] = 8] = "TrueKeyword";
|
|
852
|
+
SyntaxKind2[SyntaxKind2["FalseKeyword"] = 9] = "FalseKeyword";
|
|
853
|
+
SyntaxKind2[SyntaxKind2["StringLiteral"] = 10] = "StringLiteral";
|
|
854
|
+
SyntaxKind2[SyntaxKind2["NumericLiteral"] = 11] = "NumericLiteral";
|
|
855
|
+
SyntaxKind2[SyntaxKind2["LineCommentTrivia"] = 12] = "LineCommentTrivia";
|
|
856
|
+
SyntaxKind2[SyntaxKind2["BlockCommentTrivia"] = 13] = "BlockCommentTrivia";
|
|
857
|
+
SyntaxKind2[SyntaxKind2["LineBreakTrivia"] = 14] = "LineBreakTrivia";
|
|
858
|
+
SyntaxKind2[SyntaxKind2["Trivia"] = 15] = "Trivia";
|
|
859
|
+
SyntaxKind2[SyntaxKind2["Unknown"] = 16] = "Unknown";
|
|
860
|
+
SyntaxKind2[SyntaxKind2["EOF"] = 17] = "EOF";
|
|
861
|
+
})(SyntaxKind || (SyntaxKind = {}));
|
|
862
|
+
var parse2 = parse;
|
|
863
|
+
var ParseErrorCode;
|
|
864
|
+
(function(ParseErrorCode2) {
|
|
865
|
+
ParseErrorCode2[ParseErrorCode2["InvalidSymbol"] = 1] = "InvalidSymbol";
|
|
866
|
+
ParseErrorCode2[ParseErrorCode2["InvalidNumberFormat"] = 2] = "InvalidNumberFormat";
|
|
867
|
+
ParseErrorCode2[ParseErrorCode2["PropertyNameExpected"] = 3] = "PropertyNameExpected";
|
|
868
|
+
ParseErrorCode2[ParseErrorCode2["ValueExpected"] = 4] = "ValueExpected";
|
|
869
|
+
ParseErrorCode2[ParseErrorCode2["ColonExpected"] = 5] = "ColonExpected";
|
|
870
|
+
ParseErrorCode2[ParseErrorCode2["CommaExpected"] = 6] = "CommaExpected";
|
|
871
|
+
ParseErrorCode2[ParseErrorCode2["CloseBraceExpected"] = 7] = "CloseBraceExpected";
|
|
872
|
+
ParseErrorCode2[ParseErrorCode2["CloseBracketExpected"] = 8] = "CloseBracketExpected";
|
|
873
|
+
ParseErrorCode2[ParseErrorCode2["EndOfFileExpected"] = 9] = "EndOfFileExpected";
|
|
874
|
+
ParseErrorCode2[ParseErrorCode2["InvalidCommentToken"] = 10] = "InvalidCommentToken";
|
|
875
|
+
ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfComment"] = 11] = "UnexpectedEndOfComment";
|
|
876
|
+
ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfString"] = 12] = "UnexpectedEndOfString";
|
|
877
|
+
ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfNumber"] = 13] = "UnexpectedEndOfNumber";
|
|
878
|
+
ParseErrorCode2[ParseErrorCode2["InvalidUnicode"] = 14] = "InvalidUnicode";
|
|
879
|
+
ParseErrorCode2[ParseErrorCode2["InvalidEscapeCharacter"] = 15] = "InvalidEscapeCharacter";
|
|
880
|
+
ParseErrorCode2[ParseErrorCode2["InvalidCharacter"] = 16] = "InvalidCharacter";
|
|
881
|
+
})(ParseErrorCode || (ParseErrorCode = {}));
|
|
882
|
+
function printParseErrorCode(code) {
|
|
883
|
+
switch (code) {
|
|
884
|
+
case 1:
|
|
885
|
+
return "InvalidSymbol";
|
|
886
|
+
case 2:
|
|
887
|
+
return "InvalidNumberFormat";
|
|
888
|
+
case 3:
|
|
889
|
+
return "PropertyNameExpected";
|
|
890
|
+
case 4:
|
|
891
|
+
return "ValueExpected";
|
|
892
|
+
case 5:
|
|
893
|
+
return "ColonExpected";
|
|
894
|
+
case 6:
|
|
895
|
+
return "CommaExpected";
|
|
896
|
+
case 7:
|
|
897
|
+
return "CloseBraceExpected";
|
|
898
|
+
case 8:
|
|
899
|
+
return "CloseBracketExpected";
|
|
900
|
+
case 9:
|
|
901
|
+
return "EndOfFileExpected";
|
|
902
|
+
case 10:
|
|
903
|
+
return "InvalidCommentToken";
|
|
904
|
+
case 11:
|
|
905
|
+
return "UnexpectedEndOfComment";
|
|
906
|
+
case 12:
|
|
907
|
+
return "UnexpectedEndOfString";
|
|
908
|
+
case 13:
|
|
909
|
+
return "UnexpectedEndOfNumber";
|
|
910
|
+
case 14:
|
|
911
|
+
return "InvalidUnicode";
|
|
912
|
+
case 15:
|
|
913
|
+
return "InvalidEscapeCharacter";
|
|
914
|
+
case 16:
|
|
915
|
+
return "InvalidCharacter";
|
|
916
|
+
}
|
|
917
|
+
return "<unknown ParseErrorCode>";
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// src/paths.ts
|
|
921
|
+
function globalConfigDir() {
|
|
922
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
923
|
+
const base = xdg && xdg.length > 0 ? xdg : path.join(os.homedir(), ".config");
|
|
924
|
+
return path.join(base, "opencode");
|
|
925
|
+
}
|
|
926
|
+
function catalogCacheFile() {
|
|
927
|
+
const xdg = process.env.XDG_CACHE_HOME;
|
|
928
|
+
const base = xdg && xdg.length > 0 ? xdg : path.join(os.homedir(), ".cache");
|
|
929
|
+
return path.join(base, "opencode", "models.json");
|
|
930
|
+
}
|
|
931
|
+
function globalRouterConfigFile() {
|
|
932
|
+
return path.join(globalConfigDir(), "router.config.json");
|
|
933
|
+
}
|
|
934
|
+
function projectRouterConfigFile(dir) {
|
|
935
|
+
return path.join(dir, ".opencode", "router.config.json");
|
|
936
|
+
}
|
|
937
|
+
function ensureDir(dir) {
|
|
938
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
939
|
+
}
|
|
940
|
+
function parseJsonc(text) {
|
|
941
|
+
const errors = [];
|
|
942
|
+
const value = parse2(text, errors, { allowTrailingComma: true });
|
|
943
|
+
const first = errors[0];
|
|
944
|
+
if (first) return { error: `${printParseErrorCode(first.error)} at line ${lineAt(text, first.offset)}` };
|
|
945
|
+
return { value };
|
|
946
|
+
}
|
|
947
|
+
function lineAt(text, offset) {
|
|
948
|
+
return text.slice(0, offset).split("\n").length;
|
|
949
|
+
}
|
|
950
|
+
function readText(file) {
|
|
951
|
+
try {
|
|
952
|
+
return fs.readFileSync(file, "utf8").replace(/^/, "");
|
|
953
|
+
} catch {
|
|
954
|
+
return void 0;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function readJsonFile(file) {
|
|
958
|
+
const text = readText(file);
|
|
959
|
+
if (text === void 0 || text.trim() === "") return { status: "absent" };
|
|
960
|
+
const parsed = parseJsonc(text);
|
|
961
|
+
if (parsed.error !== void 0) return { status: "invalid", error: parsed.error };
|
|
962
|
+
return { status: "ok", value: parsed.value };
|
|
963
|
+
}
|
|
964
|
+
function isPlainObject(value) {
|
|
965
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
966
|
+
}
|
|
967
|
+
function writeTextAtomic(file, text) {
|
|
968
|
+
ensureDir(path.dirname(file));
|
|
969
|
+
const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
970
|
+
fs.writeFileSync(temp, text, "utf8");
|
|
971
|
+
try {
|
|
972
|
+
fs.renameSync(temp, file);
|
|
973
|
+
} catch {
|
|
974
|
+
fs.writeFileSync(file, text, "utf8");
|
|
975
|
+
fs.rmSync(temp, { force: true });
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
function writeJson(file, value) {
|
|
979
|
+
writeTextAtomic(file, JSON.stringify(value, null, 2) + "\n");
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// src/catalog.ts
|
|
983
|
+
var ROUTED_PROVIDERS = ["opencode-go", "opencode"];
|
|
984
|
+
function readCatalogFile(file = catalogCacheFile()) {
|
|
985
|
+
try {
|
|
986
|
+
const raw = fs2.readFileSync(file, "utf8");
|
|
987
|
+
return JSON.parse(raw);
|
|
988
|
+
} catch {
|
|
989
|
+
return void 0;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
function toNumber(value, fallback = 0) {
|
|
993
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
994
|
+
}
|
|
995
|
+
function mapTiers(raw) {
|
|
996
|
+
const tiers = raw.cost?.tiers;
|
|
997
|
+
if (!Array.isArray(tiers)) return [];
|
|
998
|
+
const out = [];
|
|
999
|
+
for (const t of tiers) {
|
|
1000
|
+
const size = t?.tier?.size;
|
|
1001
|
+
if (typeof size !== "number") continue;
|
|
1002
|
+
out.push({
|
|
1003
|
+
size,
|
|
1004
|
+
input: toNumber(t.input),
|
|
1005
|
+
output: toNumber(t.output),
|
|
1006
|
+
cacheRead: t.cache_read !== void 0 ? toNumber(t.cache_read) : void 0,
|
|
1007
|
+
cacheWrite: t.cache_write !== void 0 ? toNumber(t.cache_write) : void 0
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
return out.sort((a, b) => a.size - b.size);
|
|
1011
|
+
}
|
|
1012
|
+
function isFreeModel(cost, tiers) {
|
|
1013
|
+
const baseFree = cost.input === 0 && cost.output === 0;
|
|
1014
|
+
const tiersFree = tiers.every((t) => t.input === 0 && t.output === 0);
|
|
1015
|
+
return baseFree && tiersFree;
|
|
1016
|
+
}
|
|
1017
|
+
function mapModel(providerID, raw) {
|
|
1018
|
+
const cost = {
|
|
1019
|
+
input: toNumber(raw.cost?.input),
|
|
1020
|
+
output: toNumber(raw.cost?.output),
|
|
1021
|
+
cacheRead: raw.cost?.cache_read !== void 0 ? toNumber(raw.cost.cache_read) : void 0,
|
|
1022
|
+
cacheWrite: raw.cost?.cache_write !== void 0 ? toNumber(raw.cost.cache_write) : void 0
|
|
1023
|
+
};
|
|
1024
|
+
const tiers = mapTiers(raw);
|
|
1025
|
+
const rawInputs = raw.modalities?.input;
|
|
1026
|
+
const inputModalities = Array.isArray(rawInputs) ? rawInputs.map(String) : ["text"];
|
|
1027
|
+
return {
|
|
1028
|
+
providerID,
|
|
1029
|
+
modelID: raw.id,
|
|
1030
|
+
id: `${providerID}/${raw.id}`,
|
|
1031
|
+
name: raw.name ?? raw.id,
|
|
1032
|
+
family: raw.family,
|
|
1033
|
+
releaseDate: raw.release_date ?? raw.last_updated,
|
|
1034
|
+
reasoning: raw.reasoning === true,
|
|
1035
|
+
toolCall: raw.tool_call === true,
|
|
1036
|
+
attachment: raw.attachment === true,
|
|
1037
|
+
openWeights: raw.open_weights === true,
|
|
1038
|
+
limit: {
|
|
1039
|
+
context: toNumber(raw.limit?.context),
|
|
1040
|
+
input: raw.limit?.input !== void 0 ? toNumber(raw.limit.input) : void 0,
|
|
1041
|
+
output: toNumber(raw.limit?.output)
|
|
1042
|
+
},
|
|
1043
|
+
cost,
|
|
1044
|
+
tiers,
|
|
1045
|
+
inputModalities,
|
|
1046
|
+
free: isFreeModel(cost, tiers)
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
function loadCatalog(file = catalogCacheFile()) {
|
|
1050
|
+
const raw = readCatalogFile(file);
|
|
1051
|
+
if (!raw) return [];
|
|
1052
|
+
const out = [];
|
|
1053
|
+
for (const providerID of ROUTED_PROVIDERS) {
|
|
1054
|
+
const provider = raw[providerID];
|
|
1055
|
+
if (!provider?.models) continue;
|
|
1056
|
+
for (const rawModel of Object.values(provider.models)) {
|
|
1057
|
+
if (rawModel && typeof rawModel.id === "string") out.push(mapModel(providerID, rawModel));
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
return out;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// src/scoring.ts
|
|
1064
|
+
var FIVE_HOUR_SHARE = 0.2;
|
|
1065
|
+
var FREE_ROLE_BONUS = 6;
|
|
1066
|
+
var DEFAULT_UNVETTED_PENALTY = 8;
|
|
1067
|
+
function modelClass(model) {
|
|
1068
|
+
if (model.free) return "free";
|
|
1069
|
+
if (model.providerID === "opencode-go") return "subscription";
|
|
1070
|
+
return "paid";
|
|
1071
|
+
}
|
|
1072
|
+
function emptyFeedScores() {
|
|
1073
|
+
return { byFamily: /* @__PURE__ */ new Map(), byId: /* @__PURE__ */ new Map() };
|
|
1074
|
+
}
|
|
1075
|
+
function clamp(value, min = 0, max = 100) {
|
|
1076
|
+
return Math.max(min, Math.min(max, value));
|
|
1077
|
+
}
|
|
1078
|
+
function resolveMetric(model, table, feeds, metric) {
|
|
1079
|
+
const override = table.models[model.id]?.[metric];
|
|
1080
|
+
if (override !== void 0) return { value: clamp(override), source: "override" };
|
|
1081
|
+
const fromId = feeds.byId.get(model.id.toLowerCase())?.[metric];
|
|
1082
|
+
if (fromId !== void 0) return { value: clamp(fromId), source: "feed" };
|
|
1083
|
+
const fromFamily = model.family ? feeds.byFamily.get(model.family.toLowerCase())?.[metric] : void 0;
|
|
1084
|
+
if (fromFamily !== void 0) return { value: clamp(fromFamily), source: "feed" };
|
|
1085
|
+
const id = model.modelID.toLowerCase();
|
|
1086
|
+
const pattern = table.heuristics.patterns.find((p) => id.includes(p.match.toLowerCase()));
|
|
1087
|
+
if (pattern) return { value: clamp(pattern[metric]), source: "heuristic" };
|
|
1088
|
+
const fallback = metric === "intelligence" ? table.heuristics.defaultIntelligence : table.heuristics.defaultSpeed;
|
|
1089
|
+
return { value: clamp(fallback), source: "default" };
|
|
1090
|
+
}
|
|
1091
|
+
function resolveIntelligence(model, table, feeds) {
|
|
1092
|
+
return resolveMetric(model, table, feeds, "intelligence");
|
|
1093
|
+
}
|
|
1094
|
+
function resolveSpeed(model, table, feeds) {
|
|
1095
|
+
return resolveMetric(model, table, feeds, "speed");
|
|
1096
|
+
}
|
|
1097
|
+
function blendedCost(model, cfg) {
|
|
1098
|
+
const { input, output, cacheRead } = cfg.blend;
|
|
1099
|
+
const c = model.cost;
|
|
1100
|
+
return c.input * input + c.output * output + (c.cacheRead ?? 0) * cacheRead;
|
|
1101
|
+
}
|
|
1102
|
+
function monthlyCapFor(modelId, table, plan) {
|
|
1103
|
+
if (plan === "free") return null;
|
|
1104
|
+
const entry = table.monthlyCaps[modelId];
|
|
1105
|
+
if (!entry) return null;
|
|
1106
|
+
return plan === "go-plus" ? entry.goPlus : entry.go;
|
|
1107
|
+
}
|
|
1108
|
+
function logScale(value, min, max) {
|
|
1109
|
+
const lo = Math.log10(Math.max(min, 1e-6));
|
|
1110
|
+
const hi = Math.log10(Math.max(max, min * 1.001));
|
|
1111
|
+
const v = Math.log10(Math.max(value, 1e-6));
|
|
1112
|
+
return clamp((v - lo) / (hi - lo) * 100);
|
|
1113
|
+
}
|
|
1114
|
+
function resolveEndurance(model, table, cfg) {
|
|
1115
|
+
const bc = blendedCost(model, cfg);
|
|
1116
|
+
if (model.free) {
|
|
1117
|
+
return { score: 100, source: "cap", tokensPer5h: null, monthlyCapUSD: null, blendedCost: bc };
|
|
1118
|
+
}
|
|
1119
|
+
const cap = monthlyCapFor(model.id, table, cfg.plan);
|
|
1120
|
+
if (cap === null) {
|
|
1121
|
+
return { score: 100, source: "cap", tokensPer5h: null, monthlyCapUSD: null, blendedCost: bc };
|
|
1122
|
+
}
|
|
1123
|
+
if (bc <= 0) {
|
|
1124
|
+
return { score: 100, source: "cap", tokensPer5h: null, monthlyCapUSD: cap, blendedCost: bc };
|
|
1125
|
+
}
|
|
1126
|
+
const tokensPer5h = cap * FIVE_HOUR_SHARE / bc;
|
|
1127
|
+
const score = logScale(tokensPer5h, cfg.enduranceScale.minTokensPer5h, cfg.enduranceScale.maxTokensPer5h);
|
|
1128
|
+
return { score, source: "cap", tokensPer5h, monthlyCapUSD: cap, blendedCost: bc };
|
|
1129
|
+
}
|
|
1130
|
+
function scoreModel(model, role, table, cfg, feeds) {
|
|
1131
|
+
const intel = resolveIntelligence(model, table, feeds);
|
|
1132
|
+
const speed = resolveSpeed(model, table, feeds);
|
|
1133
|
+
const end = resolveEndurance(model, table, cfg);
|
|
1134
|
+
const w = role.weights;
|
|
1135
|
+
let score = w.intelligence * intel.value + w.speed * speed.value + w.endurance * end.score;
|
|
1136
|
+
const klass = modelClass(model);
|
|
1137
|
+
if (role.preferFree) {
|
|
1138
|
+
if (klass === "free") score += FREE_ROLE_BONUS;
|
|
1139
|
+
} else {
|
|
1140
|
+
score += table.classPreference?.[cfg.plan]?.[klass] ?? 0;
|
|
1141
|
+
}
|
|
1142
|
+
if (table.models[model.id] === void 0) score -= table.heuristics.unvettedPenalty ?? DEFAULT_UNVETTED_PENALTY;
|
|
1143
|
+
score = clamp(score);
|
|
1144
|
+
return {
|
|
1145
|
+
model,
|
|
1146
|
+
intelligence: intel.value,
|
|
1147
|
+
speed: speed.value,
|
|
1148
|
+
endurance: end.score,
|
|
1149
|
+
score,
|
|
1150
|
+
intelligenceSource: intel.source,
|
|
1151
|
+
speedSource: speed.source,
|
|
1152
|
+
enduranceSource: end.source,
|
|
1153
|
+
blendedCost: end.blendedCost,
|
|
1154
|
+
tokensPer5h: end.tokensPer5h,
|
|
1155
|
+
monthlyCapUSD: end.monthlyCapUSD
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// src/feeds.ts
|
|
1160
|
+
async function loadFeedScores(cfg) {
|
|
1161
|
+
const feeds = emptyFeedScores();
|
|
1162
|
+
if (!cfg.feeds?.enabled || !cfg.feeds.url) return { feeds, note: null };
|
|
1163
|
+
const controller = new AbortController();
|
|
1164
|
+
const timeout = setTimeout(() => controller.abort(), cfg.feeds.timeoutMs ?? 5e3);
|
|
1165
|
+
try {
|
|
1166
|
+
const res = await fetch(cfg.feeds.url, { signal: controller.signal });
|
|
1167
|
+
if (!res.ok) return { feeds, note: `feed ${cfg.feeds.url} returned ${res.status}; using table + heuristics` };
|
|
1168
|
+
const payload = await res.json();
|
|
1169
|
+
const records = Array.isArray(payload.models) ? payload.models : [];
|
|
1170
|
+
for (const rec of records) {
|
|
1171
|
+
if (!rec) continue;
|
|
1172
|
+
const value = {};
|
|
1173
|
+
if (typeof rec.intelligence === "number") value.intelligence = rec.intelligence;
|
|
1174
|
+
if (typeof rec.speed === "number") value.speed = rec.speed;
|
|
1175
|
+
if (value.intelligence === void 0 && value.speed === void 0) continue;
|
|
1176
|
+
if (rec.id) feeds.byId.set(rec.id.toLowerCase(), value);
|
|
1177
|
+
if (rec.family) feeds.byFamily.set(rec.family.toLowerCase(), value);
|
|
1178
|
+
}
|
|
1179
|
+
return { feeds, note: `feed ${cfg.feeds.url}: ${records.length} records` };
|
|
1180
|
+
} catch (error) {
|
|
1181
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1182
|
+
return { feeds, note: `feed unavailable (${message}); using table + heuristics` };
|
|
1183
|
+
} finally {
|
|
1184
|
+
clearTimeout(timeout);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// src/config.ts
|
|
1189
|
+
var DEFAULT_SOFT_THRESHOLD = 0.85;
|
|
1190
|
+
var DEFAULT_CHAIN_DEPTH = 4;
|
|
1191
|
+
function loadUserConfig(projectDir) {
|
|
1192
|
+
const notes = [];
|
|
1193
|
+
const global = readUserConfigFile(globalRouterConfigFile(), notes);
|
|
1194
|
+
const project = projectDir ? readUserConfigFile(projectRouterConfigFile(projectDir), notes) : {};
|
|
1195
|
+
return { config: mergeUserConfig(global, project), notes };
|
|
1196
|
+
}
|
|
1197
|
+
function readUserConfigFile(file, notes) {
|
|
1198
|
+
const result = readJsonFile(file);
|
|
1199
|
+
if (result.status === "absent") return {};
|
|
1200
|
+
if (result.status === "invalid") {
|
|
1201
|
+
notes.push(`${file} has a JSON error (${result.error}); it was ignored.`);
|
|
1202
|
+
return {};
|
|
1203
|
+
}
|
|
1204
|
+
if (!isPlainObject(result.value)) {
|
|
1205
|
+
notes.push(`${file} does not contain a JSON object; it was ignored.`);
|
|
1206
|
+
return {};
|
|
1207
|
+
}
|
|
1208
|
+
return sanitizeUserConfig(result.value, file, notes);
|
|
1209
|
+
}
|
|
1210
|
+
function sanitizeUserConfig(raw, file, notes) {
|
|
1211
|
+
const config = { ...raw };
|
|
1212
|
+
const drop = (key, rule) => {
|
|
1213
|
+
notes.push(`${file}: "${key}" ${rule}, not ${JSON.stringify(config[key])}; it was ignored.`);
|
|
1214
|
+
delete config[key];
|
|
1215
|
+
};
|
|
1216
|
+
if (config.plan !== void 0 && !isPlan(config.plan)) drop("plan", `must be one of ${PLAN_PROFILES.join(" | ")}`);
|
|
1217
|
+
const depth = config.chainDepth;
|
|
1218
|
+
if (depth !== void 0 && !(typeof depth === "number" && Number.isInteger(depth) && depth >= 1)) {
|
|
1219
|
+
drop("chainDepth", "must be a whole number of 1 or more");
|
|
1220
|
+
}
|
|
1221
|
+
const threshold = config.softThreshold;
|
|
1222
|
+
if (threshold !== void 0 && !(typeof threshold === "number" && threshold > 0 && threshold <= 1)) {
|
|
1223
|
+
drop("softThreshold", "must be a number above 0 and at most 1");
|
|
1224
|
+
}
|
|
1225
|
+
for (const key of ["denyModels", "allowModels"]) {
|
|
1226
|
+
const list = config[key];
|
|
1227
|
+
if (list !== void 0 && !(Array.isArray(list) && list.every((item) => typeof item === "string"))) {
|
|
1228
|
+
drop(key, "must be a list of model IDs");
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
for (const key of ["roles", "weights"]) {
|
|
1232
|
+
const perRole = config[key];
|
|
1233
|
+
if (perRole === void 0) continue;
|
|
1234
|
+
if (!isPlainObject(perRole)) {
|
|
1235
|
+
drop(key, "must be an object keyed by role");
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1238
|
+
const known = {};
|
|
1239
|
+
for (const [role, value] of Object.entries(perRole)) {
|
|
1240
|
+
if (isRoleName(role)) known[role] = value;
|
|
1241
|
+
else notes.push(`${file}: unknown role "${role}" in "${key}"; it was ignored.`);
|
|
1242
|
+
}
|
|
1243
|
+
config[key] = known;
|
|
1244
|
+
}
|
|
1245
|
+
return config;
|
|
1246
|
+
}
|
|
1247
|
+
function mergeByKey(base = {}, override = {}) {
|
|
1248
|
+
const out = { ...base };
|
|
1249
|
+
for (const key of Object.keys(override)) {
|
|
1250
|
+
out[key] = { ...base[key], ...override[key] };
|
|
1251
|
+
}
|
|
1252
|
+
return out;
|
|
1253
|
+
}
|
|
1254
|
+
function mergeUserConfig(base, override) {
|
|
1255
|
+
return {
|
|
1256
|
+
...base,
|
|
1257
|
+
...override,
|
|
1258
|
+
blend: { ...base.blend, ...override.blend },
|
|
1259
|
+
enduranceScale: { ...base.enduranceScale, ...override.enduranceScale },
|
|
1260
|
+
weights: mergeByKey(base.weights, override.weights),
|
|
1261
|
+
roles: mergeByKey(base.roles, override.roles),
|
|
1262
|
+
scores: mergeByKey(base.scores, override.scores),
|
|
1263
|
+
privacy: { ...base.privacy, ...override.privacy },
|
|
1264
|
+
feeds: { ...base.feeds, ...override.feeds },
|
|
1265
|
+
denyModels: override.denyModels ?? base.denyModels,
|
|
1266
|
+
allowModels: override.allowModels ?? base.allowModels
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
function detectPlan(catalog) {
|
|
1270
|
+
const hasGo = catalog.some((m) => m.providerID === "opencode-go" && !m.free);
|
|
1271
|
+
if (hasGo) return "go";
|
|
1272
|
+
const hasFree = catalog.some((m) => m.free);
|
|
1273
|
+
if (hasFree) return "free";
|
|
1274
|
+
return "go";
|
|
1275
|
+
}
|
|
1276
|
+
function resolveConfig(user, catalog, cliPlan) {
|
|
1277
|
+
const plan = cliPlan ?? user.plan ?? detectPlan(catalog);
|
|
1278
|
+
return {
|
|
1279
|
+
...user,
|
|
1280
|
+
plan,
|
|
1281
|
+
chainDepth: user.chainDepth ?? DEFAULT_CHAIN_DEPTH,
|
|
1282
|
+
softThreshold: user.softThreshold ?? DEFAULT_SOFT_THRESHOLD,
|
|
1283
|
+
blend: {
|
|
1284
|
+
input: user.blend?.input ?? 0.75,
|
|
1285
|
+
output: user.blend?.output ?? 0.25,
|
|
1286
|
+
cacheRead: user.blend?.cacheRead ?? 0.1
|
|
1287
|
+
},
|
|
1288
|
+
enduranceScale: {
|
|
1289
|
+
minTokensPer5h: user.enduranceScale?.minTokensPer5h ?? 0.25,
|
|
1290
|
+
maxTokensPer5h: user.enduranceScale?.maxTokensPer5h ?? 40
|
|
1291
|
+
},
|
|
1292
|
+
denyModels: user.denyModels ?? [],
|
|
1293
|
+
allowModels: user.allowModels ?? [],
|
|
1294
|
+
privacy: { freeModelsOk: user.privacy?.freeModelsOk ?? true }
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// model-scores.json
|
|
1299
|
+
var model_scores_default = {
|
|
1300
|
+
$comment: "Committed default score table for opencode-router. Safe to edit; user config (router.config.json) overrides these values.",
|
|
1301
|
+
version: 1,
|
|
1302
|
+
updated: "2026-10-18",
|
|
1303
|
+
roles: {
|
|
1304
|
+
plan: {
|
|
1305
|
+
weights: { intelligence: 0.65, speed: 0.1, endurance: 0.25 },
|
|
1306
|
+
variant: "high",
|
|
1307
|
+
chainDepth: 4,
|
|
1308
|
+
requires: { toolCall: true, minContext: 2e5 },
|
|
1309
|
+
diversifyFamily: true
|
|
1310
|
+
},
|
|
1311
|
+
build: {
|
|
1312
|
+
weights: { intelligence: 0.4, speed: 0.25, endurance: 0.35 },
|
|
1313
|
+
variant: "medium",
|
|
1314
|
+
chainDepth: 4,
|
|
1315
|
+
requires: { toolCall: true, minContext: 2e5 },
|
|
1316
|
+
diversifyFamily: true
|
|
1317
|
+
},
|
|
1318
|
+
general: {
|
|
1319
|
+
weights: { intelligence: 0.35, speed: 0.25, endurance: 0.4 },
|
|
1320
|
+
variant: "medium",
|
|
1321
|
+
chainDepth: 4,
|
|
1322
|
+
requires: { toolCall: true, minContext: 2e5 },
|
|
1323
|
+
diversifyFamily: true
|
|
1324
|
+
},
|
|
1325
|
+
explore: {
|
|
1326
|
+
weights: { intelligence: 0.15, speed: 0.45, endurance: 0.4 },
|
|
1327
|
+
variant: "low",
|
|
1328
|
+
chainDepth: 4,
|
|
1329
|
+
requires: { toolCall: true, minContext: 128e3 },
|
|
1330
|
+
preferFree: true
|
|
1331
|
+
},
|
|
1332
|
+
small_model: {
|
|
1333
|
+
weights: { intelligence: 0.05, speed: 0.55, endurance: 0.4 },
|
|
1334
|
+
variant: "low",
|
|
1335
|
+
chainDepth: 3,
|
|
1336
|
+
requires: { toolCall: true, minContext: 1e5 },
|
|
1337
|
+
preferFree: true
|
|
1338
|
+
},
|
|
1339
|
+
title: {
|
|
1340
|
+
weights: { intelligence: 0.05, speed: 0.55, endurance: 0.4 },
|
|
1341
|
+
variant: "low",
|
|
1342
|
+
chainDepth: 3,
|
|
1343
|
+
requires: { toolCall: true, minContext: 1e5 },
|
|
1344
|
+
preferFree: true
|
|
1345
|
+
},
|
|
1346
|
+
summary: {
|
|
1347
|
+
weights: { intelligence: 0.3, speed: 0.3, endurance: 0.4 },
|
|
1348
|
+
variant: "low",
|
|
1349
|
+
chainDepth: 3,
|
|
1350
|
+
requires: { toolCall: true, minContext: 2e5 },
|
|
1351
|
+
preferFree: true
|
|
1352
|
+
},
|
|
1353
|
+
compaction: {
|
|
1354
|
+
weights: { intelligence: 0.3, speed: 0.2, endurance: 0.5 },
|
|
1355
|
+
variant: "none",
|
|
1356
|
+
chainDepth: 3,
|
|
1357
|
+
requires: { toolCall: true, minContext: 1e6 },
|
|
1358
|
+
preferFree: true
|
|
1359
|
+
}
|
|
1360
|
+
},
|
|
1361
|
+
models: {
|
|
1362
|
+
"opencode-go/gemini-4-argon-contributor": { intelligence: 95, speed: 25 },
|
|
1363
|
+
"opencode-go/kimi-k3": { intelligence: 93, speed: 20 },
|
|
1364
|
+
"opencode-go/qwen3.8-max": { intelligence: 92, speed: 35 },
|
|
1365
|
+
"opencode-go/grok-4.7": { intelligence: 91, speed: 40 },
|
|
1366
|
+
"opencode-go/deepseek-v4-pro": { intelligence: 90, speed: 30 },
|
|
1367
|
+
"opencode-go/glm-5.3": { intelligence: 90, speed: 45 },
|
|
1368
|
+
"opencode-go/glm-5.2": { intelligence: 88, speed: 45 },
|
|
1369
|
+
"opencode-go/step-5-preview": { intelligence: 87, speed: 40 },
|
|
1370
|
+
"opencode-go/qwen3.7-plus": { intelligence: 86, speed: 45 },
|
|
1371
|
+
"opencode-go/kimi-k2.7-code": { intelligence: 85, speed: 50 },
|
|
1372
|
+
"opencode-go/gpt-6-luna": { intelligence: 85, speed: 55 },
|
|
1373
|
+
"opencode-go/gpt-5.6-luna": { intelligence: 84, speed: 55 },
|
|
1374
|
+
"opencode-go/minimax-m3": { intelligence: 84, speed: 55 },
|
|
1375
|
+
"opencode-go/hy4-preview": { intelligence: 82, speed: 40 },
|
|
1376
|
+
"opencode-go/qwen3.8-flash": { intelligence: 80, speed: 70 },
|
|
1377
|
+
"opencode-go/deepseek-v4.1-flash": { intelligence: 80, speed: 65 },
|
|
1378
|
+
"opencode-go/space-bunny": { intelligence: 79, speed: 60 },
|
|
1379
|
+
"opencode-go/glm-5.3-flash": { intelligence: 78, speed: 72 },
|
|
1380
|
+
"opencode-go/mimo-v2.6-pro": { intelligence: 78, speed: 58 },
|
|
1381
|
+
"opencode-go/muse-spark-1.3-contributor": { intelligence: 77, speed: 62 },
|
|
1382
|
+
"opencode-go/mimo-v2.5-pro": { intelligence: 76, speed: 58 },
|
|
1383
|
+
"opencode-go/longcat-2.0": { intelligence: 76, speed: 55 },
|
|
1384
|
+
"opencode-go/muse-spark-1.2-contributor": { intelligence: 75, speed: 62 },
|
|
1385
|
+
"opencode-go/longcat-2.5-preview-free": { intelligence: 75, speed: 60 },
|
|
1386
|
+
"opencode-go/hy3": { intelligence: 74, speed: 60 },
|
|
1387
|
+
"opencode-go/claude-haiku-5-5": { intelligence: 74, speed: 78 },
|
|
1388
|
+
"opencode-go/mimo-v2.6-flash": { intelligence: 73, speed: 75 },
|
|
1389
|
+
"opencode-go/mimo-v2.5": { intelligence: 72, speed: 72 },
|
|
1390
|
+
"opencode-go/beam-free": { intelligence: 72, speed: 80 },
|
|
1391
|
+
"opencode/deepseek-v4.1-flash-free": { intelligence: 80, speed: 65 },
|
|
1392
|
+
"opencode/space-bunny-free": { intelligence: 79, speed: 60 },
|
|
1393
|
+
"opencode/glm-5.3-flash-free": { intelligence: 78, speed: 72 },
|
|
1394
|
+
"opencode/longcat-2.5-preview-free": { intelligence: 75, speed: 60 },
|
|
1395
|
+
"opencode/nemotron-3-ultra-free": { intelligence: 74, speed: 62 },
|
|
1396
|
+
"opencode/mimo-v2.6-flash-free": { intelligence: 73, speed: 75 },
|
|
1397
|
+
"opencode/beam-free": { intelligence: 72, speed: 80 },
|
|
1398
|
+
"opencode/nemotron-3.5-super-vl-free": { intelligence: 72, speed: 70 },
|
|
1399
|
+
"opencode/nemotron-3.5-lightning-free": { intelligence: 68, speed: 88 },
|
|
1400
|
+
"opencode/big-pickle": { intelligence: 66, speed: 70 }
|
|
1401
|
+
},
|
|
1402
|
+
heuristics: {
|
|
1403
|
+
patterns: [
|
|
1404
|
+
{ match: "gemini-4-argon", intelligence: 95, speed: 25 },
|
|
1405
|
+
{ match: "kimi-k3", intelligence: 93, speed: 20 },
|
|
1406
|
+
{ match: "qwen3.8-max", intelligence: 92, speed: 35 },
|
|
1407
|
+
{ match: "opus", intelligence: 94, speed: 30 },
|
|
1408
|
+
{ match: "grok", intelligence: 91, speed: 40 },
|
|
1409
|
+
{ match: "deepseek-v4-pro", intelligence: 90, speed: 30 },
|
|
1410
|
+
{ match: "sonnet", intelligence: 88, speed: 50 },
|
|
1411
|
+
{ match: "step-5", intelligence: 87, speed: 40 },
|
|
1412
|
+
{ match: "gpt-6", intelligence: 85, speed: 55 },
|
|
1413
|
+
{ match: "kimi", intelligence: 85, speed: 45 },
|
|
1414
|
+
{ match: "gpt-5", intelligence: 84, speed: 55 },
|
|
1415
|
+
{ match: "minimax", intelligence: 84, speed: 55 },
|
|
1416
|
+
{ match: "hy4", intelligence: 82, speed: 40 },
|
|
1417
|
+
{ match: "claude", intelligence: 82, speed: 60 },
|
|
1418
|
+
{ match: "gpt", intelligence: 82, speed: 55 },
|
|
1419
|
+
{ match: "qwen", intelligence: 80, speed: 55 },
|
|
1420
|
+
{ match: "deepseek", intelligence: 80, speed: 65 },
|
|
1421
|
+
{ match: "glm", intelligence: 80, speed: 55 },
|
|
1422
|
+
{ match: "space-bunny", intelligence: 79, speed: 60 },
|
|
1423
|
+
{ match: "muse", intelligence: 77, speed: 62 },
|
|
1424
|
+
{ match: "longcat", intelligence: 76, speed: 58 },
|
|
1425
|
+
{ match: "hy3", intelligence: 74, speed: 60 },
|
|
1426
|
+
{ match: "haiku", intelligence: 74, speed: 78 },
|
|
1427
|
+
{ match: "mimo", intelligence: 74, speed: 72 },
|
|
1428
|
+
{ match: "nemotron", intelligence: 72, speed: 75 },
|
|
1429
|
+
{ match: "big-pickle", intelligence: 66, speed: 70 },
|
|
1430
|
+
{ match: "-max", intelligence: 90, speed: 35 },
|
|
1431
|
+
{ match: "-pro", intelligence: 88, speed: 42 },
|
|
1432
|
+
{ match: "-flash", intelligence: 78, speed: 74 },
|
|
1433
|
+
{ match: "-mini", intelligence: 74, speed: 80 },
|
|
1434
|
+
{ match: "-lite", intelligence: 70, speed: 84 },
|
|
1435
|
+
{ match: "-free", intelligence: 72, speed: 70 },
|
|
1436
|
+
{ match: "contributor", intelligence: 74, speed: 62 }
|
|
1437
|
+
],
|
|
1438
|
+
defaultIntelligence: 60,
|
|
1439
|
+
defaultSpeed: 60,
|
|
1440
|
+
unvettedPenalty: 8
|
|
1441
|
+
},
|
|
1442
|
+
classPreference: {
|
|
1443
|
+
go: { subscription: 0, paid: -6, free: -12 },
|
|
1444
|
+
"go-plus": { subscription: 0, paid: -6, free: -12 },
|
|
1445
|
+
free: { subscription: 0, paid: 0, free: 0 }
|
|
1446
|
+
},
|
|
1447
|
+
monthlyCaps: {
|
|
1448
|
+
"opencode-go/longcat-2.5-preview-free": { go: null, goPlus: null },
|
|
1449
|
+
"opencode-go/beam-free": { go: null, goPlus: null },
|
|
1450
|
+
"opencode-go/deepseek-v4.1-flash": { go: 80, goPlus: 160 },
|
|
1451
|
+
"opencode-go/longcat-2.0": { go: 60, goPlus: 240 },
|
|
1452
|
+
"opencode-go/hy3": { go: 60, goPlus: 240 },
|
|
1453
|
+
"opencode-go/step-5-preview": { go: 60, goPlus: 240 },
|
|
1454
|
+
"opencode-go/gemini-4-argon-contributor": { go: 60, goPlus: 240 },
|
|
1455
|
+
"opencode-go/glm-5.3-flash": { go: 60, goPlus: 180 },
|
|
1456
|
+
"opencode-go/glm-5.2": { go: 60, goPlus: 180 },
|
|
1457
|
+
"opencode-go/kimi-k2.7-code": { go: 60, goPlus: 180 },
|
|
1458
|
+
"opencode-go/minimax-m3": { go: 60, goPlus: 180 },
|
|
1459
|
+
"opencode-go/qwen3.7-plus": { go: 60, goPlus: 180 },
|
|
1460
|
+
"opencode-go/mimo-v2.6-flash": { go: 60, goPlus: 120 },
|
|
1461
|
+
"opencode-go/mimo-v2.5": { go: 60, goPlus: 120 },
|
|
1462
|
+
"opencode-go/muse-spark-1.3-contributor": { go: 60, goPlus: 120 },
|
|
1463
|
+
"opencode-go/muse-spark-1.2-contributor": { go: 60, goPlus: 120 },
|
|
1464
|
+
"opencode-go/hy4-preview": { go: 30, goPlus: 120 },
|
|
1465
|
+
"opencode-go/space-bunny": { go: 30, goPlus: 120 },
|
|
1466
|
+
"opencode-go/qwen3.8-flash": { go: 30, goPlus: 90 },
|
|
1467
|
+
"opencode-go/glm-5.3": { go: 15, goPlus: 120 },
|
|
1468
|
+
"opencode-go/kimi-k3": { go: 15, goPlus: 60 },
|
|
1469
|
+
"opencode-go/mimo-v2.6-pro": { go: 15, goPlus: 60 },
|
|
1470
|
+
"opencode-go/mimo-v2.5-pro": { go: 15, goPlus: 60 },
|
|
1471
|
+
"opencode-go/qwen3.8-max": { go: 15, goPlus: 60 },
|
|
1472
|
+
"opencode-go/deepseek-v4-pro": { go: 15, goPlus: 60 },
|
|
1473
|
+
"opencode-go/grok-4.7": { go: 15, goPlus: 60 },
|
|
1474
|
+
"opencode-go/gpt-6-luna": { go: 15, goPlus: 60 },
|
|
1475
|
+
"opencode-go/gpt-5.6-luna": { go: 15, goPlus: 60 },
|
|
1476
|
+
"opencode-go/claude-haiku-5-5": { go: 15, goPlus: 60 }
|
|
1477
|
+
}
|
|
1478
|
+
};
|
|
1479
|
+
|
|
1480
|
+
// src/score-table.ts
|
|
1481
|
+
var DEFAULT_TABLE = model_scores_default;
|
|
1482
|
+
function loadDefaultTable() {
|
|
1483
|
+
return structuredClone(DEFAULT_TABLE);
|
|
1484
|
+
}
|
|
1485
|
+
function mergeRole(base, override) {
|
|
1486
|
+
return {
|
|
1487
|
+
...base,
|
|
1488
|
+
...override,
|
|
1489
|
+
weights: { ...base.weights, ...override.weights },
|
|
1490
|
+
requires: { ...base.requires, ...override.requires }
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
function resolveTable(user) {
|
|
1494
|
+
const table = loadDefaultTable();
|
|
1495
|
+
for (const role of ROLE_NAMES) {
|
|
1496
|
+
let spec = table.roles[role];
|
|
1497
|
+
if (!spec) continue;
|
|
1498
|
+
if (user.chainDepth !== void 0) spec = { ...spec, chainDepth: user.chainDepth };
|
|
1499
|
+
const override = user.roles?.[role];
|
|
1500
|
+
if (override) spec = mergeRole(spec, override);
|
|
1501
|
+
const weights = user.weights?.[role];
|
|
1502
|
+
if (weights) spec = { ...spec, weights: { ...spec.weights, ...weights } };
|
|
1503
|
+
table.roles[role] = spec;
|
|
1504
|
+
}
|
|
1505
|
+
for (const [id, score] of Object.entries(user.scores ?? {})) {
|
|
1506
|
+
table.models[id] = { ...table.models[id], ...score };
|
|
1507
|
+
}
|
|
1508
|
+
return table;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
// src/rank.ts
|
|
1512
|
+
function globToRegex(glob) {
|
|
1513
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1514
|
+
return new RegExp(`^${escaped}$`, "i");
|
|
1515
|
+
}
|
|
1516
|
+
function matchesAny(id, globs) {
|
|
1517
|
+
return globs.some((g) => globToRegex(g).test(id));
|
|
1518
|
+
}
|
|
1519
|
+
function providerAllowed(model, plan) {
|
|
1520
|
+
if (plan === "free") {
|
|
1521
|
+
return model.providerID === "opencode" && model.free;
|
|
1522
|
+
}
|
|
1523
|
+
if (plan === "go" || plan === "go-plus") {
|
|
1524
|
+
if (model.providerID === "opencode-go") return true;
|
|
1525
|
+
if (model.providerID === "opencode") return model.free;
|
|
1526
|
+
return false;
|
|
1527
|
+
}
|
|
1528
|
+
return false;
|
|
1529
|
+
}
|
|
1530
|
+
function requirementOk(model, role) {
|
|
1531
|
+
const req = role.requires;
|
|
1532
|
+
if (!req) return true;
|
|
1533
|
+
if (req.toolCall && !model.toolCall) return false;
|
|
1534
|
+
if (req.reasoning && !model.reasoning) return false;
|
|
1535
|
+
if (req.minContext !== void 0 && model.limit.context < req.minContext) return false;
|
|
1536
|
+
if (req.minOutput !== void 0 && model.limit.output < req.minOutput) return false;
|
|
1537
|
+
if (req.inputModalities && req.inputModalities.length > 0) {
|
|
1538
|
+
const ok = req.inputModalities.every((m) => model.inputModalities.includes(m));
|
|
1539
|
+
if (!ok) return false;
|
|
1540
|
+
}
|
|
1541
|
+
return true;
|
|
1542
|
+
}
|
|
1543
|
+
function eligibleModels(catalog, role, table, cfg) {
|
|
1544
|
+
return catalog.filter((model) => {
|
|
1545
|
+
if (!providerAllowed(model, cfg.plan)) return false;
|
|
1546
|
+
if (!requirementOk(model, role)) return false;
|
|
1547
|
+
if ((cfg.plan === "go" || cfg.plan === "go-plus") && !model.free && !table.monthlyCaps[model.id]) return false;
|
|
1548
|
+
if (cfg.denyModels.length > 0 && matchesAny(model.id, cfg.denyModels)) return false;
|
|
1549
|
+
if (cfg.allowModels.length > 0 && !matchesAny(model.id, cfg.allowModels)) return false;
|
|
1550
|
+
if (!cfg.privacy.freeModelsOk && model.free && !role.preferFree) return false;
|
|
1551
|
+
return true;
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
function rankRole(catalog, roleName, table, cfg, feeds) {
|
|
1555
|
+
const role = table.roles[roleName];
|
|
1556
|
+
if (!role) return [];
|
|
1557
|
+
const scored = eligibleModels(catalog, role, table, cfg).map((model) => scoreModel(model, role, table, cfg, feeds)).sort((a, b) => b.score - a.score || a.blendedCost - b.blendedCost);
|
|
1558
|
+
const depth = role.chainDepth ?? cfg.chainDepth;
|
|
1559
|
+
const diversify = role.diversifyFamily ?? false;
|
|
1560
|
+
const chosen = [];
|
|
1561
|
+
const seenFamilies = /* @__PURE__ */ new Set();
|
|
1562
|
+
for (const entry of scored) {
|
|
1563
|
+
if (chosen.length >= depth) break;
|
|
1564
|
+
const family = (entry.model.family ?? entry.model.modelID).toLowerCase();
|
|
1565
|
+
if (diversify && seenFamilies.has(family) && chosen.length < depth) continue;
|
|
1566
|
+
chosen.push(entry);
|
|
1567
|
+
seenFamilies.add(family);
|
|
1568
|
+
}
|
|
1569
|
+
if (chosen.length < depth) {
|
|
1570
|
+
for (const entry of scored) {
|
|
1571
|
+
if (chosen.length >= depth) break;
|
|
1572
|
+
if (chosen.includes(entry)) continue;
|
|
1573
|
+
chosen.push(entry);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
return chosen.map((entry, index) => ({
|
|
1577
|
+
rank: index + 1,
|
|
1578
|
+
providerID: entry.model.providerID,
|
|
1579
|
+
modelID: entry.model.modelID,
|
|
1580
|
+
id: entry.model.id,
|
|
1581
|
+
variant: role.variant,
|
|
1582
|
+
score: round(entry.score),
|
|
1583
|
+
intelligence: round(entry.intelligence),
|
|
1584
|
+
speed: round(entry.speed),
|
|
1585
|
+
endurance: round(entry.endurance),
|
|
1586
|
+
tokensPer5h: entry.tokensPer5h === null ? null : round(entry.tokensPer5h, 3),
|
|
1587
|
+
monthlyCapUSD: entry.monthlyCapUSD,
|
|
1588
|
+
sources: {
|
|
1589
|
+
intelligence: entry.intelligenceSource,
|
|
1590
|
+
speed: entry.speedSource,
|
|
1591
|
+
endurance: entry.enduranceSource
|
|
1592
|
+
},
|
|
1593
|
+
class: modelClass(entry.model)
|
|
1594
|
+
}));
|
|
1595
|
+
}
|
|
1596
|
+
function round(value, digits = 1) {
|
|
1597
|
+
const f = Math.pow(10, digits);
|
|
1598
|
+
return Math.round(value * f) / f;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
// src/engine.ts
|
|
1602
|
+
async function buildRoutingTable(opts = {}) {
|
|
1603
|
+
const catalogFile = opts.catalogFile ?? catalogCacheFile();
|
|
1604
|
+
const catalog = opts.catalog ?? loadCatalog(catalogFile);
|
|
1605
|
+
const user = loadUserConfig(opts.projectDir);
|
|
1606
|
+
const notes = [...opts.notes ?? [], ...user.notes];
|
|
1607
|
+
if (catalog.length === 0) {
|
|
1608
|
+
notes.push(`No models found in ${catalogFile}. Start OpenCode one time and sign in, then run this command again.`);
|
|
1609
|
+
}
|
|
1610
|
+
const cfg = resolveConfig(user.config, catalog, opts.plan);
|
|
1611
|
+
const scoreTable = resolveTable(user.config);
|
|
1612
|
+
let feeds = opts.feeds;
|
|
1613
|
+
if (!feeds) {
|
|
1614
|
+
const loaded = await loadFeedScores(cfg);
|
|
1615
|
+
feeds = loaded.feeds;
|
|
1616
|
+
if (loaded.note) notes.push(loaded.note);
|
|
1617
|
+
}
|
|
1618
|
+
const profiles = {};
|
|
1619
|
+
for (const plan of PLAN_PROFILES) {
|
|
1620
|
+
const planCfg = { ...cfg, plan };
|
|
1621
|
+
const chains = {};
|
|
1622
|
+
for (const role of ROLE_NAMES) {
|
|
1623
|
+
chains[role] = rankRole(catalog, role, scoreTable, planCfg, feeds);
|
|
1624
|
+
}
|
|
1625
|
+
profiles[plan] = chains;
|
|
1626
|
+
}
|
|
1627
|
+
const table = {
|
|
1628
|
+
version: 1,
|
|
1629
|
+
createdAt: (opts.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
1630
|
+
plan: cfg.plan,
|
|
1631
|
+
active: profiles[cfg.plan] ?? {},
|
|
1632
|
+
profiles,
|
|
1633
|
+
notes
|
|
1634
|
+
};
|
|
1635
|
+
return { table, cfg, scoreTable, catalog };
|
|
1636
|
+
}
|
|
1637
|
+
function pickForRole(table, role, isHealthy) {
|
|
1638
|
+
const chain = table.active[role];
|
|
1639
|
+
if (!chain || chain.length === 0) return void 0;
|
|
1640
|
+
return chain.find((entry) => isHealthy(entry.id)) ?? chain[0];
|
|
1641
|
+
}
|
|
1642
|
+
function healthCheck(result, ledger) {
|
|
1643
|
+
return (modelId) => ledger.isHealthy(modelId, monthlyCapFor(modelId, result.scoreTable, result.cfg.plan), result.cfg.softThreshold);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
// src/ledger.ts
|
|
1647
|
+
import fs3 from "fs";
|
|
1648
|
+
var HOUR = 36e5;
|
|
1649
|
+
var DAY = 24 * HOUR;
|
|
1650
|
+
var WINDOW_5H = 5 * HOUR;
|
|
1651
|
+
var WINDOW_WEEK = 7 * DAY;
|
|
1652
|
+
var WINDOW_MONTH = 30 * DAY;
|
|
1653
|
+
var MONTHLY_LIMIT = 0.95;
|
|
1654
|
+
function emptyState(now) {
|
|
1655
|
+
return {
|
|
1656
|
+
cost5h: 0,
|
|
1657
|
+
costWeek: 0,
|
|
1658
|
+
costMonth: 0,
|
|
1659
|
+
tokens5h: 0,
|
|
1660
|
+
tokensWeek: 0,
|
|
1661
|
+
tokensMonth: 0,
|
|
1662
|
+
window5hStart: now,
|
|
1663
|
+
windowWeekStart: now,
|
|
1664
|
+
windowMonthStart: now
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
function emptyLedger(now) {
|
|
1668
|
+
return { version: 1, updatedAt: new Date(now).toISOString(), models: {} };
|
|
1669
|
+
}
|
|
1670
|
+
function isLedger(value) {
|
|
1671
|
+
return isPlainObject(value) && value.version === 1 && isPlainObject(value.models);
|
|
1672
|
+
}
|
|
1673
|
+
function mtimeOf(file) {
|
|
1674
|
+
try {
|
|
1675
|
+
return fs3.statSync(file).mtimeMs;
|
|
1676
|
+
} catch {
|
|
1677
|
+
return -1;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
var LedgerStore = class {
|
|
1681
|
+
constructor(file, now = Date.now) {
|
|
1682
|
+
this.file = file;
|
|
1683
|
+
this.now = now;
|
|
1684
|
+
this.data = this.load();
|
|
1685
|
+
}
|
|
1686
|
+
file;
|
|
1687
|
+
now;
|
|
1688
|
+
data;
|
|
1689
|
+
loadedMtime = -1;
|
|
1690
|
+
/** Read the file. A corrupt file is kept as `<file>.corrupt-<time>` and a new ledger starts. */
|
|
1691
|
+
load() {
|
|
1692
|
+
this.loadedMtime = mtimeOf(this.file);
|
|
1693
|
+
const result = readJsonFile(this.file);
|
|
1694
|
+
if (result.status === "ok" && isLedger(result.value)) return result.value;
|
|
1695
|
+
if (result.status !== "absent") {
|
|
1696
|
+
try {
|
|
1697
|
+
fs3.renameSync(this.file, `${this.file}.corrupt-${this.now()}`);
|
|
1698
|
+
} catch {
|
|
1699
|
+
}
|
|
1700
|
+
this.loadedMtime = -1;
|
|
1701
|
+
}
|
|
1702
|
+
return emptyLedger(this.now());
|
|
1703
|
+
}
|
|
1704
|
+
sync() {
|
|
1705
|
+
if (mtimeOf(this.file) !== this.loadedMtime) this.data = this.load();
|
|
1706
|
+
}
|
|
1707
|
+
/** Reset each window that has ended. */
|
|
1708
|
+
roll(state, now) {
|
|
1709
|
+
if (now - state.window5hStart >= WINDOW_5H) {
|
|
1710
|
+
state.cost5h = 0;
|
|
1711
|
+
state.tokens5h = 0;
|
|
1712
|
+
state.window5hStart = now;
|
|
1713
|
+
}
|
|
1714
|
+
if (now - state.windowWeekStart >= WINDOW_WEEK) {
|
|
1715
|
+
state.costWeek = 0;
|
|
1716
|
+
state.tokensWeek = 0;
|
|
1717
|
+
state.windowWeekStart = now;
|
|
1718
|
+
}
|
|
1719
|
+
if (now - state.windowMonthStart >= WINDOW_MONTH) {
|
|
1720
|
+
state.costMonth = 0;
|
|
1721
|
+
state.tokensMonth = 0;
|
|
1722
|
+
state.windowMonthStart = now;
|
|
1723
|
+
}
|
|
1724
|
+
return state;
|
|
1725
|
+
}
|
|
1726
|
+
state(modelId) {
|
|
1727
|
+
this.sync();
|
|
1728
|
+
const now = this.now();
|
|
1729
|
+
let entry = this.data.models[modelId];
|
|
1730
|
+
if (!entry) {
|
|
1731
|
+
entry = emptyState(now);
|
|
1732
|
+
this.data.models[modelId] = entry;
|
|
1733
|
+
}
|
|
1734
|
+
return this.roll(entry, now);
|
|
1735
|
+
}
|
|
1736
|
+
record(modelId, costUSD, tokens) {
|
|
1737
|
+
const state = this.state(modelId);
|
|
1738
|
+
state.cost5h += costUSD;
|
|
1739
|
+
state.costWeek += costUSD;
|
|
1740
|
+
state.costMonth += costUSD;
|
|
1741
|
+
state.tokens5h += tokens;
|
|
1742
|
+
state.tokensWeek += tokens;
|
|
1743
|
+
state.tokensMonth += tokens;
|
|
1744
|
+
state.lastUsedAt = this.now();
|
|
1745
|
+
this.save();
|
|
1746
|
+
}
|
|
1747
|
+
cooldown(modelId, durationMs, reason) {
|
|
1748
|
+
const state = this.state(modelId);
|
|
1749
|
+
state.cooldownUntil = this.now() + durationMs;
|
|
1750
|
+
if (reason) state.lastError = reason;
|
|
1751
|
+
this.save();
|
|
1752
|
+
}
|
|
1753
|
+
clearCooldown(modelId) {
|
|
1754
|
+
const state = this.state(modelId);
|
|
1755
|
+
state.cooldownUntil = void 0;
|
|
1756
|
+
this.save();
|
|
1757
|
+
}
|
|
1758
|
+
usage(modelId, monthlyCapUSD) {
|
|
1759
|
+
return usageOf(this.state(modelId), monthlyCapUSD);
|
|
1760
|
+
}
|
|
1761
|
+
/** Healthy = not cooling down, under the soft threshold for 5 hours, and under the monthly limit. */
|
|
1762
|
+
isHealthy(modelId, monthlyCapUSD, softThreshold) {
|
|
1763
|
+
const state = this.state(modelId);
|
|
1764
|
+
if (state.cooldownUntil && this.now() < state.cooldownUntil) return false;
|
|
1765
|
+
const u = usageOf(state, monthlyCapUSD);
|
|
1766
|
+
return u.pct5h < softThreshold && u.pctMonth < MONTHLY_LIMIT;
|
|
1767
|
+
}
|
|
1768
|
+
snapshot() {
|
|
1769
|
+
return structuredClone(this.data);
|
|
1770
|
+
}
|
|
1771
|
+
save() {
|
|
1772
|
+
this.data.updatedAt = new Date(this.now()).toISOString();
|
|
1773
|
+
writeJson(this.file, this.data);
|
|
1774
|
+
this.loadedMtime = mtimeOf(this.file);
|
|
1775
|
+
}
|
|
1776
|
+
};
|
|
1777
|
+
function usageOf(state, monthlyCapUSD) {
|
|
1778
|
+
const cap5h = monthlyCapUSD === null ? null : monthlyCapUSD * FIVE_HOUR_SHARE;
|
|
1779
|
+
return {
|
|
1780
|
+
cost5h: state.cost5h,
|
|
1781
|
+
costMonth: state.costMonth,
|
|
1782
|
+
tokens5h: state.tokens5h,
|
|
1783
|
+
tokensMonth: state.tokensMonth,
|
|
1784
|
+
pct5h: cap5h === null || cap5h <= 0 ? 0 : state.cost5h / cap5h,
|
|
1785
|
+
pctMonth: monthlyCapUSD === null || monthlyCapUSD <= 0 ? 0 : state.costMonth / monthlyCapUSD,
|
|
1786
|
+
cooldownUntil: state.cooldownUntil ?? null
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
// src/apply.ts
|
|
1791
|
+
import path2 from "path";
|
|
1792
|
+
var INTERNAL_AGENTS = ["compaction", "title", "summary"];
|
|
1793
|
+
function routingConfigBody(table) {
|
|
1794
|
+
const agent = {};
|
|
1795
|
+
for (const role of AGENT_ROLES) {
|
|
1796
|
+
const entry = table.active[role]?.[0];
|
|
1797
|
+
if (!entry) continue;
|
|
1798
|
+
agent[role] = entry.variant && entry.variant !== "none" ? { model: entry.id, variant: entry.variant } : { model: entry.id };
|
|
1799
|
+
}
|
|
1800
|
+
for (const role of INTERNAL_AGENTS) {
|
|
1801
|
+
const entry = table.active[role]?.[0];
|
|
1802
|
+
if (!entry) continue;
|
|
1803
|
+
agent[role] = { model: entry.id };
|
|
1804
|
+
}
|
|
1805
|
+
const body = { agent };
|
|
1806
|
+
const build = table.active.build?.[0];
|
|
1807
|
+
const small = table.active.small_model?.[0];
|
|
1808
|
+
if (build) body.model = build.id;
|
|
1809
|
+
if (small) body.small_model = small.id;
|
|
1810
|
+
return body;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
// src/commands.ts
|
|
1814
|
+
import { fileURLToPath } from "url";
|
|
1815
|
+
var SLASH_COMMANDS = [
|
|
1816
|
+
{ name: "refresh-routing", description: "Recompute and apply OpenCode model routing", args: "refresh" },
|
|
1817
|
+
{ name: "routing-status", description: "Show the model in use per role and its 5-hour quota use", args: "status" },
|
|
1818
|
+
{ name: "routing-plan", description: "Switch the routing plan (go | go-plus | free) and rebuild", args: "plan $1" },
|
|
1819
|
+
{ name: "routing-eval", description: "Run the router scoring stress test", args: "eval" }
|
|
1820
|
+
];
|
|
1821
|
+
var OUTPUT_BEGIN = "---BEGIN ROUTER OUTPUT---";
|
|
1822
|
+
function isRouterCommandPrompt(parts) {
|
|
1823
|
+
return parts.some((part) => part.type === "text" && part.text?.includes(OUTPUT_BEGIN) === true);
|
|
1824
|
+
}
|
|
1825
|
+
function cliScriptPath() {
|
|
1826
|
+
return fileURLToPath(new URL("./bin.js", import.meta.url)).replace(/\\/g, "/");
|
|
1827
|
+
}
|
|
1828
|
+
function commandTemplate(args) {
|
|
1829
|
+
return [
|
|
1830
|
+
"opencode-router already ran. Its output is between the markers below.",
|
|
1831
|
+
"",
|
|
1832
|
+
"Rules:",
|
|
1833
|
+
"1. Do not run any command, script, or tool. Do not search for opencode-router on PATH.",
|
|
1834
|
+
"2. Present the text between the markers to the user exactly as written.",
|
|
1835
|
+
"3. Do not summarize, reformat, shorten, or reorganize it.",
|
|
1836
|
+
"",
|
|
1837
|
+
OUTPUT_BEGIN,
|
|
1838
|
+
`!\`node "${cliScriptPath()}" ${args} --scope global\``,
|
|
1839
|
+
"---END ROUTER OUTPUT---"
|
|
1840
|
+
].join("\n");
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
// src/state.ts
|
|
1844
|
+
import fs4 from "fs";
|
|
1845
|
+
import path3 from "path";
|
|
1846
|
+
function stateDir() {
|
|
1847
|
+
return path3.join(globalConfigDir(), "router");
|
|
1848
|
+
}
|
|
1849
|
+
function ledgerFile() {
|
|
1850
|
+
return path3.join(stateDir(), "ledger.json");
|
|
1851
|
+
}
|
|
1852
|
+
function routingTableFile() {
|
|
1853
|
+
return path3.join(stateDir(), "routing-table.json");
|
|
1854
|
+
}
|
|
1855
|
+
function applyMarkerFile() {
|
|
1856
|
+
return path3.join(stateDir(), "last-apply.json");
|
|
1857
|
+
}
|
|
1858
|
+
function writeApplyMarker(marker) {
|
|
1859
|
+
writeJson(applyMarkerFile(), { at: (/* @__PURE__ */ new Date()).toISOString(), ...marker });
|
|
1860
|
+
}
|
|
1861
|
+
function clearApplyMarker() {
|
|
1862
|
+
fs4.rmSync(applyMarkerFile(), { force: true });
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
// src/index.ts
|
|
1866
|
+
var RATE_LIMIT_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
1867
|
+
var AUTH_COOLDOWN_MS = 24 * 60 * 60 * 1e3;
|
|
1868
|
+
var MAX_TRACKED_MESSAGES = 500;
|
|
1869
|
+
var TTL_MS = 15e3;
|
|
1870
|
+
function errorMessage(error) {
|
|
1871
|
+
return error instanceof Error ? error.message : String(error);
|
|
1872
|
+
}
|
|
1873
|
+
function isRateLimitError(error) {
|
|
1874
|
+
if (!error || typeof error !== "object") return false;
|
|
1875
|
+
const rec = error;
|
|
1876
|
+
const data = rec.data ?? {};
|
|
1877
|
+
const status = Number(data.statusCode ?? data.status ?? 0);
|
|
1878
|
+
const message = String(data.message ?? data.responseBody ?? "").toLowerCase();
|
|
1879
|
+
if (status === 429 || status === 402) return true;
|
|
1880
|
+
return /rate.?limit|quota|too many requests|insufficient|capacity|overloaded/.test(message);
|
|
1881
|
+
}
|
|
1882
|
+
function isAuthError(error) {
|
|
1883
|
+
return typeof error === "object" && error !== null && String(error.name ?? "").includes("ProviderAuthError");
|
|
1884
|
+
}
|
|
1885
|
+
var OpencodeRouter = async (input) => {
|
|
1886
|
+
const dir = input.directory ?? input.worktree ?? process.cwd();
|
|
1887
|
+
const ledger = new LedgerStore(ledgerFile(), () => Date.now());
|
|
1888
|
+
const recordedMessages = /* @__PURE__ */ new Set();
|
|
1889
|
+
const sessionModels = /* @__PURE__ */ new Map();
|
|
1890
|
+
let cached = null;
|
|
1891
|
+
let cachedAt = 0;
|
|
1892
|
+
let blendedById = /* @__PURE__ */ new Map();
|
|
1893
|
+
async function ensure(force = false, plan) {
|
|
1894
|
+
const now = Date.now();
|
|
1895
|
+
if (!force && cached && now - cachedAt < TTL_MS) return cached;
|
|
1896
|
+
const result = await buildRoutingTable({ projectDir: dir, plan });
|
|
1897
|
+
cached = result;
|
|
1898
|
+
cachedAt = now;
|
|
1899
|
+
blendedById = new Map(result.catalog.map((m) => [m.id, blendedCost(m, result.cfg)]));
|
|
1900
|
+
try {
|
|
1901
|
+
writeJson(routingTableFile(), result.table);
|
|
1902
|
+
} catch {
|
|
1903
|
+
}
|
|
1904
|
+
return result;
|
|
1905
|
+
}
|
|
1906
|
+
async function applyToSession(result) {
|
|
1907
|
+
try {
|
|
1908
|
+
await input.client.config.update({ body: routingConfigBody(result.table) });
|
|
1909
|
+
writeApplyMarker({ ok: true, detail: "hot-applied to the running session by the opencode-router plugin" });
|
|
1910
|
+
} catch (error) {
|
|
1911
|
+
writeApplyMarker({ ok: false, detail: `hot-apply failed: ${errorMessage(error)}` });
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
function rememberMessage(id) {
|
|
1915
|
+
recordedMessages.add(id);
|
|
1916
|
+
if (recordedMessages.size > MAX_TRACKED_MESSAGES) {
|
|
1917
|
+
const oldest = recordedMessages.values().next().value;
|
|
1918
|
+
if (oldest !== void 0) recordedMessages.delete(oldest);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
return {
|
|
1922
|
+
config: async (cfg) => {
|
|
1923
|
+
try {
|
|
1924
|
+
const result = await ensure();
|
|
1925
|
+
const body = routingConfigBody(result.table);
|
|
1926
|
+
const agents = { ...cfg.agent };
|
|
1927
|
+
for (const [role, spec] of Object.entries(body.agent)) agents[role] = { ...agents[role], ...spec };
|
|
1928
|
+
cfg.agent = agents;
|
|
1929
|
+
if (body.model) cfg.model = body.model;
|
|
1930
|
+
if (body.small_model) cfg.small_model = body.small_model;
|
|
1931
|
+
const commands = { ...cfg.command };
|
|
1932
|
+
for (const command of SLASH_COMMANDS) {
|
|
1933
|
+
commands[command.name] = {
|
|
1934
|
+
description: command.description,
|
|
1935
|
+
agent: "build",
|
|
1936
|
+
model: body.small_model,
|
|
1937
|
+
template: commandTemplate(command.args)
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
cfg.command = commands;
|
|
1941
|
+
} catch {
|
|
1942
|
+
}
|
|
1943
|
+
},
|
|
1944
|
+
// The CLI that the command template starts prints the result. This hook
|
|
1945
|
+
// applies the same table to the running session.
|
|
1946
|
+
"command.execute.before": async (cmd) => {
|
|
1947
|
+
const name = cmd.command.replace(/^\//, "");
|
|
1948
|
+
try {
|
|
1949
|
+
if (name === "refresh-routing") {
|
|
1950
|
+
clearApplyMarker();
|
|
1951
|
+
await applyToSession(await ensure(true));
|
|
1952
|
+
} else if (name === "routing-plan") {
|
|
1953
|
+
const plan = cmd.arguments.trim().split(/\s+/)[0];
|
|
1954
|
+
if (!isPlan(plan)) return;
|
|
1955
|
+
clearApplyMarker();
|
|
1956
|
+
await applyToSession(await ensure(true, plan));
|
|
1957
|
+
}
|
|
1958
|
+
} catch (error) {
|
|
1959
|
+
writeApplyMarker({ ok: false, detail: `router command failed: ${errorMessage(error)}` });
|
|
1960
|
+
}
|
|
1961
|
+
},
|
|
1962
|
+
"chat.message": async (msgInput, output) => {
|
|
1963
|
+
try {
|
|
1964
|
+
const agent = msgInput.agent ?? output.message.agent;
|
|
1965
|
+
if (!isAgentRole(agent)) return;
|
|
1966
|
+
if (isRouterCommandPrompt(output.parts)) return;
|
|
1967
|
+
const result = await ensure();
|
|
1968
|
+
const entry = pickForRole(result.table, agent, healthCheck(result, ledger));
|
|
1969
|
+
if (!entry) return;
|
|
1970
|
+
sessionModels.set(msgInput.sessionID, entry.id);
|
|
1971
|
+
const current = output.message.model;
|
|
1972
|
+
if (current?.providerID === entry.providerID && current.modelID === entry.modelID) return;
|
|
1973
|
+
output.message.model = { providerID: entry.providerID, modelID: entry.modelID };
|
|
1974
|
+
} catch {
|
|
1975
|
+
}
|
|
1976
|
+
},
|
|
1977
|
+
event: async ({ event }) => {
|
|
1978
|
+
try {
|
|
1979
|
+
if (event.type === "message.updated") {
|
|
1980
|
+
const info = event.properties.info;
|
|
1981
|
+
if (info.role !== "assistant" || !info.time.completed || recordedMessages.has(info.id)) return;
|
|
1982
|
+
rememberMessage(info.id);
|
|
1983
|
+
const id = `${info.providerID}/${info.modelID}`;
|
|
1984
|
+
sessionModels.set(info.sessionID, id);
|
|
1985
|
+
const tokens = (info.tokens?.input ?? 0) + (info.tokens?.output ?? 0) + (info.tokens?.cache?.read ?? 0) + (info.tokens?.cache?.write ?? 0);
|
|
1986
|
+
let cost = info.cost ?? 0;
|
|
1987
|
+
if (cost === 0 && tokens > 0) {
|
|
1988
|
+
await ensure();
|
|
1989
|
+
const blended = blendedById.get(id) ?? 0;
|
|
1990
|
+
if (blended > 0) cost = tokens / 1e6 * blended;
|
|
1991
|
+
}
|
|
1992
|
+
ledger.record(id, cost, tokens);
|
|
1993
|
+
if (isRateLimitError(info.error)) ledger.cooldown(id, RATE_LIMIT_COOLDOWN_MS, "rate limit / quota");
|
|
1994
|
+
else if (isAuthError(info.error)) ledger.cooldown(id, AUTH_COOLDOWN_MS, "auth error");
|
|
1995
|
+
} else if (event.type === "session.error") {
|
|
1996
|
+
const { sessionID, error } = event.properties;
|
|
1997
|
+
const id = sessionID ? sessionModels.get(sessionID) : void 0;
|
|
1998
|
+
if (id && isRateLimitError(error)) ledger.cooldown(id, RATE_LIMIT_COOLDOWN_MS, "session rate limit");
|
|
1999
|
+
}
|
|
2000
|
+
} catch {
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
};
|
|
2004
|
+
};
|
|
2005
|
+
var src_default = OpencodeRouter;
|
|
2006
|
+
export {
|
|
2007
|
+
OpencodeRouter,
|
|
2008
|
+
src_default as default
|
|
2009
|
+
};
|
|
2010
|
+
//# sourceMappingURL=index.js.map
|