@lawaty/lacode 0.1.3 → 0.1.5
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/agents.js +7 -2
- package/dist/index.js +53 -6
- package/dist/pool.js +48 -7
- package/package.json +1 -1
- package/presets/README.md +22 -3
- package/presets/free-tier.json +2 -2
package/dist/agents.js
CHANGED
|
@@ -33,6 +33,7 @@ var PRESETS_DIR = join(PACKAGE_ROOT, "presets");
|
|
|
33
33
|
var DEFAULT_PRESET = join(PRESETS_DIR, "free-tier.json");
|
|
34
34
|
var USER_CONFIG_DIR = join(homedir(), ".config", "lacode");
|
|
35
35
|
var USER_MODELS_PATH = join(USER_CONFIG_DIR, "pool.json");
|
|
36
|
+
var USER_MODELS_TXT = join(USER_CONFIG_DIR, "pool.txt");
|
|
36
37
|
|
|
37
38
|
// src/agents.ts
|
|
38
39
|
var POOLED_BASES = ["explore", "implement", "context"];
|
|
@@ -116,7 +117,9 @@ missing). Never paste context file contents into the report.`,
|
|
|
116
117
|
"task": {
|
|
117
118
|
"*": "deny",
|
|
118
119
|
"explore*": "allow",
|
|
119
|
-
"implement*": "allow"
|
|
120
|
+
"implement*": "allow",
|
|
121
|
+
"basic": "allow",
|
|
122
|
+
"expert": "allow"
|
|
120
123
|
}
|
|
121
124
|
}
|
|
122
125
|
},
|
|
@@ -186,7 +189,9 @@ Once the plan is complete, present it clearly so the caller can implement it ste
|
|
|
186
189
|
"task": {
|
|
187
190
|
"*": "deny",
|
|
188
191
|
"explore*": "allow",
|
|
189
|
-
"implement*": "allow"
|
|
192
|
+
"implement*": "allow",
|
|
193
|
+
"basic": "allow",
|
|
194
|
+
"expert": "allow"
|
|
190
195
|
}
|
|
191
196
|
}
|
|
192
197
|
},
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,7 @@ var PRESETS_DIR = join(PACKAGE_ROOT, "presets");
|
|
|
35
35
|
var DEFAULT_PRESET = join(PRESETS_DIR, "free-tier.json");
|
|
36
36
|
var USER_CONFIG_DIR = join(homedir(), ".config", "lacode");
|
|
37
37
|
var USER_MODELS_PATH = join(USER_CONFIG_DIR, "pool.json");
|
|
38
|
+
var USER_MODELS_TXT = join(USER_CONFIG_DIR, "pool.txt");
|
|
38
39
|
var FALLBACK_SLOTS = [
|
|
39
40
|
{ index: 1, model: "opencode/space-bunny-free", weight: 3 },
|
|
40
41
|
{ index: 2, model: "openrouter/nvidia/nemotron-3-ultra-550b-a55b:free", weight: 2 },
|
|
@@ -66,6 +67,26 @@ function parseSlots(parsed, origin) {
|
|
|
66
67
|
}
|
|
67
68
|
return { slots, problems };
|
|
68
69
|
}
|
|
70
|
+
function parseTextSlots(text, origin) {
|
|
71
|
+
const entries = [];
|
|
72
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
73
|
+
const line = raw.split("#")[0].trim();
|
|
74
|
+
if (!line) continue;
|
|
75
|
+
const [model, weight] = line.split(/\s+/);
|
|
76
|
+
entries.push(weight === void 0 ? { model } : { model, weight });
|
|
77
|
+
}
|
|
78
|
+
return parseSlots({ slots: entries }, origin);
|
|
79
|
+
}
|
|
80
|
+
function parseSlotText(text, origin) {
|
|
81
|
+
if (text.trimStart().startsWith("{")) {
|
|
82
|
+
try {
|
|
83
|
+
return parseSlots(JSON.parse(text), origin);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return { slots: FALLBACK_SLOTS, problems: [`${origin} is not valid JSON (${String(error)}); using built-in defaults`] };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return parseTextSlots(text, origin);
|
|
89
|
+
}
|
|
69
90
|
var readJSON = (path4) => {
|
|
70
91
|
try {
|
|
71
92
|
return { ok: true, value: JSON.parse(readFileSync(path4, "utf8")) };
|
|
@@ -81,6 +102,15 @@ function readOptional(path4, origin, problems) {
|
|
|
81
102
|
}
|
|
82
103
|
return result.value;
|
|
83
104
|
}
|
|
105
|
+
function readTextOptional(path4, origin, problems) {
|
|
106
|
+
if (!existsSync(path4)) return void 0;
|
|
107
|
+
try {
|
|
108
|
+
return readFileSync(path4, "utf8");
|
|
109
|
+
} catch (error) {
|
|
110
|
+
problems.push(`${origin} unreadable (${String(error)}); trying the next source`);
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
84
114
|
function resolveModels(input = {}) {
|
|
85
115
|
const problems = [];
|
|
86
116
|
if (input.models !== void 0) {
|
|
@@ -92,11 +122,15 @@ function resolveModels(input = {}) {
|
|
|
92
122
|
return { slots: parsed.slots, problems: [...problems, ...parsed.problems], origin };
|
|
93
123
|
}
|
|
94
124
|
if (input.modelsFile) {
|
|
95
|
-
const
|
|
96
|
-
if (!result.ok) return { slots: FALLBACK_SLOTS, problems: [`${input.modelsFile} unreadable (${result.error}); using built-in defaults`], origin: input.modelsFile };
|
|
97
|
-
const parsed = parseSlots(result.value, input.modelsFile);
|
|
125
|
+
const parsed = readSlotFile(input.modelsFile);
|
|
98
126
|
return { slots: parsed.slots, problems: parsed.problems, origin: input.modelsFile };
|
|
99
127
|
}
|
|
128
|
+
const text = readTextOptional(USER_MODELS_TXT, USER_MODELS_TXT, problems);
|
|
129
|
+
if (text !== void 0) {
|
|
130
|
+
const parsed = parseSlotText(text, USER_MODELS_TXT);
|
|
131
|
+
if (parsed.slots !== FALLBACK_SLOTS) return { slots: parsed.slots, problems: [...problems, ...parsed.problems], origin: USER_MODELS_TXT };
|
|
132
|
+
problems.push(...parsed.problems);
|
|
133
|
+
}
|
|
100
134
|
const user = readOptional(USER_MODELS_PATH, USER_MODELS_PATH, problems);
|
|
101
135
|
if (user !== void 0) {
|
|
102
136
|
const parsed = parseSlots(user, USER_MODELS_PATH);
|
|
@@ -108,7 +142,16 @@ function resolveModels(input = {}) {
|
|
|
108
142
|
const parsed = parseSlots(preset, "presets/free-tier.json");
|
|
109
143
|
return { slots: parsed.slots, problems: [...problems, ...parsed.problems], origin: "presets/free-tier.json" };
|
|
110
144
|
}
|
|
111
|
-
return { slots: FALLBACK_SLOTS, problems: [...problems, `no pool source found (${USER_MODELS_PATH}, ${DEFAULT_PRESET}); using built-in defaults`], origin: "built-in defaults" };
|
|
145
|
+
return { slots: FALLBACK_SLOTS, problems: [...problems, `no pool source found (${USER_MODELS_TXT}, ${USER_MODELS_PATH}, ${DEFAULT_PRESET}); using built-in defaults`], origin: "built-in defaults" };
|
|
146
|
+
}
|
|
147
|
+
function readSlotFile(path4) {
|
|
148
|
+
let text;
|
|
149
|
+
try {
|
|
150
|
+
text = readFileSync(path4, "utf8");
|
|
151
|
+
} catch (error) {
|
|
152
|
+
return { slots: FALLBACK_SLOTS, problems: [`${path4} unreadable (${String(error)}); using built-in defaults`] };
|
|
153
|
+
}
|
|
154
|
+
return parseSlotText(text, path4);
|
|
112
155
|
}
|
|
113
156
|
function ensureUserConfigDir() {
|
|
114
157
|
try {
|
|
@@ -528,7 +571,9 @@ missing). Never paste context file contents into the report.`,
|
|
|
528
571
|
"task": {
|
|
529
572
|
"*": "deny",
|
|
530
573
|
"explore*": "allow",
|
|
531
|
-
"implement*": "allow"
|
|
574
|
+
"implement*": "allow",
|
|
575
|
+
"basic": "allow",
|
|
576
|
+
"expert": "allow"
|
|
532
577
|
}
|
|
533
578
|
}
|
|
534
579
|
},
|
|
@@ -598,7 +643,9 @@ Once the plan is complete, present it clearly so the caller can implement it ste
|
|
|
598
643
|
"task": {
|
|
599
644
|
"*": "deny",
|
|
600
645
|
"explore*": "allow",
|
|
601
|
-
"implement*": "allow"
|
|
646
|
+
"implement*": "allow",
|
|
647
|
+
"basic": "allow",
|
|
648
|
+
"expert": "allow"
|
|
602
649
|
}
|
|
603
650
|
}
|
|
604
651
|
},
|
package/dist/pool.js
CHANGED
|
@@ -35,6 +35,7 @@ var PRESETS_DIR = join(PACKAGE_ROOT, "presets");
|
|
|
35
35
|
var DEFAULT_PRESET = join(PRESETS_DIR, "free-tier.json");
|
|
36
36
|
var USER_CONFIG_DIR = join(homedir(), ".config", "lacode");
|
|
37
37
|
var USER_MODELS_PATH = join(USER_CONFIG_DIR, "pool.json");
|
|
38
|
+
var USER_MODELS_TXT = join(USER_CONFIG_DIR, "pool.txt");
|
|
38
39
|
var FALLBACK_SLOTS = [
|
|
39
40
|
{ index: 1, model: "opencode/space-bunny-free", weight: 3 },
|
|
40
41
|
{ index: 2, model: "openrouter/nvidia/nemotron-3-ultra-550b-a55b:free", weight: 2 },
|
|
@@ -66,6 +67,26 @@ function parseSlots(parsed, origin) {
|
|
|
66
67
|
}
|
|
67
68
|
return { slots, problems };
|
|
68
69
|
}
|
|
70
|
+
function parseTextSlots(text, origin) {
|
|
71
|
+
const entries = [];
|
|
72
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
73
|
+
const line = raw.split("#")[0].trim();
|
|
74
|
+
if (!line) continue;
|
|
75
|
+
const [model, weight] = line.split(/\s+/);
|
|
76
|
+
entries.push(weight === void 0 ? { model } : { model, weight });
|
|
77
|
+
}
|
|
78
|
+
return parseSlots({ slots: entries }, origin);
|
|
79
|
+
}
|
|
80
|
+
function parseSlotText(text, origin) {
|
|
81
|
+
if (text.trimStart().startsWith("{")) {
|
|
82
|
+
try {
|
|
83
|
+
return parseSlots(JSON.parse(text), origin);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return { slots: FALLBACK_SLOTS, problems: [`${origin} is not valid JSON (${String(error)}); using built-in defaults`] };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return parseTextSlots(text, origin);
|
|
89
|
+
}
|
|
69
90
|
var readJSON = (path) => {
|
|
70
91
|
try {
|
|
71
92
|
return { ok: true, value: JSON.parse(readFileSync(path, "utf8")) };
|
|
@@ -81,6 +102,15 @@ function readOptional(path, origin, problems) {
|
|
|
81
102
|
}
|
|
82
103
|
return result.value;
|
|
83
104
|
}
|
|
105
|
+
function readTextOptional(path, origin, problems) {
|
|
106
|
+
if (!existsSync(path)) return void 0;
|
|
107
|
+
try {
|
|
108
|
+
return readFileSync(path, "utf8");
|
|
109
|
+
} catch (error) {
|
|
110
|
+
problems.push(`${origin} unreadable (${String(error)}); trying the next source`);
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
84
114
|
function resolveModels(input = {}) {
|
|
85
115
|
const problems = [];
|
|
86
116
|
if (input.models !== void 0) {
|
|
@@ -92,11 +122,15 @@ function resolveModels(input = {}) {
|
|
|
92
122
|
return { slots: parsed.slots, problems: [...problems, ...parsed.problems], origin };
|
|
93
123
|
}
|
|
94
124
|
if (input.modelsFile) {
|
|
95
|
-
const
|
|
96
|
-
if (!result.ok) return { slots: FALLBACK_SLOTS, problems: [`${input.modelsFile} unreadable (${result.error}); using built-in defaults`], origin: input.modelsFile };
|
|
97
|
-
const parsed = parseSlots(result.value, input.modelsFile);
|
|
125
|
+
const parsed = readSlotFile(input.modelsFile);
|
|
98
126
|
return { slots: parsed.slots, problems: parsed.problems, origin: input.modelsFile };
|
|
99
127
|
}
|
|
128
|
+
const text = readTextOptional(USER_MODELS_TXT, USER_MODELS_TXT, problems);
|
|
129
|
+
if (text !== void 0) {
|
|
130
|
+
const parsed = parseSlotText(text, USER_MODELS_TXT);
|
|
131
|
+
if (parsed.slots !== FALLBACK_SLOTS) return { slots: parsed.slots, problems: [...problems, ...parsed.problems], origin: USER_MODELS_TXT };
|
|
132
|
+
problems.push(...parsed.problems);
|
|
133
|
+
}
|
|
100
134
|
const user = readOptional(USER_MODELS_PATH, USER_MODELS_PATH, problems);
|
|
101
135
|
if (user !== void 0) {
|
|
102
136
|
const parsed = parseSlots(user, USER_MODELS_PATH);
|
|
@@ -108,12 +142,16 @@ function resolveModels(input = {}) {
|
|
|
108
142
|
const parsed = parseSlots(preset, "presets/free-tier.json");
|
|
109
143
|
return { slots: parsed.slots, problems: [...problems, ...parsed.problems], origin: "presets/free-tier.json" };
|
|
110
144
|
}
|
|
111
|
-
return { slots: FALLBACK_SLOTS, problems: [...problems, `no pool source found (${USER_MODELS_PATH}, ${DEFAULT_PRESET}); using built-in defaults`], origin: "built-in defaults" };
|
|
145
|
+
return { slots: FALLBACK_SLOTS, problems: [...problems, `no pool source found (${USER_MODELS_TXT}, ${USER_MODELS_PATH}, ${DEFAULT_PRESET}); using built-in defaults`], origin: "built-in defaults" };
|
|
112
146
|
}
|
|
113
147
|
function readSlotFile(path) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
148
|
+
let text;
|
|
149
|
+
try {
|
|
150
|
+
text = readFileSync(path, "utf8");
|
|
151
|
+
} catch (error) {
|
|
152
|
+
return { slots: FALLBACK_SLOTS, problems: [`${path} unreadable (${String(error)}); using built-in defaults`] };
|
|
153
|
+
}
|
|
154
|
+
return parseSlotText(text, path);
|
|
117
155
|
}
|
|
118
156
|
function ensureUserConfigDir() {
|
|
119
157
|
try {
|
|
@@ -471,6 +509,7 @@ export {
|
|
|
471
509
|
UNAVAILABLE_COOLDOWN_MS,
|
|
472
510
|
USER_CONFIG_DIR,
|
|
473
511
|
USER_MODELS_PATH,
|
|
512
|
+
USER_MODELS_TXT,
|
|
474
513
|
acquire,
|
|
475
514
|
agentFor,
|
|
476
515
|
bindChild,
|
|
@@ -486,7 +525,9 @@ export {
|
|
|
486
525
|
isLimitText,
|
|
487
526
|
isUnavailableText,
|
|
488
527
|
noteActivity,
|
|
528
|
+
parseSlotText,
|
|
489
529
|
parseSlots,
|
|
530
|
+
parseTextSlots,
|
|
490
531
|
pickSlot,
|
|
491
532
|
providerOf,
|
|
492
533
|
pruneOwn,
|
package/package.json
CHANGED
package/presets/README.md
CHANGED
|
@@ -10,7 +10,7 @@ LaCode reads the first source that yields usable slots, in this order:
|
|
|
10
10
|
| # | Source | Where |
|
|
11
11
|
|---|--------|-------|
|
|
12
12
|
| 1 | plugin options | `["@lawaty/lacode", { "models": { "slots": [...] } }]` |
|
|
13
|
-
| 2 | user config | `~/.config/lacode/pool.json` |
|
|
13
|
+
| 2 | user config | `~/.config/lacode/pool.txt` (flat, preferred) or `~/.config/lacode/pool.json` (JSON) |
|
|
14
14
|
| 3 | bundled preset | `presets/free-tier.json` |
|
|
15
15
|
| 4 | built-in fallback | hardcoded in `src/lib/pool.ts`, so routing always works |
|
|
16
16
|
|
|
@@ -29,6 +29,25 @@ Source 3 is a default, not a floor.
|
|
|
29
29
|
Nothing is hot-reloaded. Change a preset, then restart every running opencode
|
|
30
30
|
process.
|
|
31
31
|
|
|
32
|
+
## The flat pool file (recommended)
|
|
33
|
+
|
|
34
|
+
`~/.config/lacode/pool.txt` is one model per line — edit a model id or a weight
|
|
35
|
+
in place, no JSON and no objects:
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
# provider/model-id weight
|
|
39
|
+
opencode-go/longcat-2.5-preview-free 3
|
|
40
|
+
opencode/space-bunny-free 2
|
|
41
|
+
opencode/big-pickle 1
|
|
42
|
+
opencode/longcat-2.5-preview-free 1
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`#` starts a comment (whole-line or trailing), blank lines are ignored, and the
|
|
46
|
+
weight is optional (default 1). The rules are exactly the same as the JSON form
|
|
47
|
+
below: up to 4 slots, distinct models, split on the first slash only. The loader
|
|
48
|
+
accepts either shape in the same file, so a leading `{` means JSON and anything
|
|
49
|
+
else means the flat form.
|
|
50
|
+
|
|
32
51
|
## Schema
|
|
33
52
|
|
|
34
53
|
```jsonc
|
|
@@ -89,8 +108,8 @@ provider-wide rate limit takes out every slot on that provider at once.
|
|
|
89
108
|
|
|
90
109
|
## Writing your own
|
|
91
110
|
|
|
92
|
-
|
|
93
|
-
`~/.config/lacode/pool.json
|
|
111
|
+
Write `~/.config/lacode/pool.txt` (the flat form above) — that is the whole job.
|
|
112
|
+
Or put the same slots in a JSON `~/.config/lacode/pool.json`, or pass `models`
|
|
94
113
|
inline from `opencode.jsonc` when you want the list version-controlled beside
|
|
95
114
|
the rest of your config:
|
|
96
115
|
|
package/presets/free-tier.json
CHANGED
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"Changing this file requires restarting every opencode process: plugins are not hot-reloaded."
|
|
53
53
|
],
|
|
54
54
|
"slots": [
|
|
55
|
-
{ "model": "opencode
|
|
56
|
-
{ "model": "opencode/
|
|
55
|
+
{ "model": "opencode/space-bunny-free", "weight": 3 },
|
|
56
|
+
{ "model": "opencode-go/longcat-2.5-preview-free", "weight": 2 },
|
|
57
57
|
{ "model": "opencode/big-pickle", "weight": 1 },
|
|
58
58
|
{ "model": "opencode/longcat-2.5-preview-free", "weight": 1 }
|
|
59
59
|
]
|