opencode-model-router 1.0.3 → 1.0.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/README.md +11 -1
- package/package.json +1 -1
- package/src/index.ts +63 -9
package/README.md
CHANGED
|
@@ -16,6 +16,16 @@ The agent automatically delegates via the Task tool when it recognizes the task
|
|
|
16
16
|
|
|
17
17
|
This applies both to plan-driven execution and direct ad-hoc requests. For every new user message, the orchestrator performs an intent gate, splits multi-task requests into atomic units, and routes each unit to `@fast`, `@medium`, or `@heavy`.
|
|
18
18
|
|
|
19
|
+
### Token overhead disclaimer
|
|
20
|
+
|
|
21
|
+
The injected protocol is compact, but it still adds tokens on every iteration.
|
|
22
|
+
|
|
23
|
+
- Estimated average injection: ~208 tokens per iteration
|
|
24
|
+
- Preset breakdown (default `tiers.json`): `anthropic` ~209, `openai` ~206
|
|
25
|
+
- Estimation method: `prompt_characters / 4` (rough heuristic)
|
|
26
|
+
|
|
27
|
+
Real token usage varies by tokenizer/model and any custom changes you make to `tiers.json`.
|
|
28
|
+
|
|
19
29
|
## Installation
|
|
20
30
|
|
|
21
31
|
### Option A: npm package (recommended)
|
|
@@ -25,7 +35,7 @@ Add the plugin package in your `opencode.json`:
|
|
|
25
35
|
```json
|
|
26
36
|
{
|
|
27
37
|
"plugin": [
|
|
28
|
-
"opencode-model-router@
|
|
38
|
+
"opencode-model-router@latest"
|
|
29
39
|
]
|
|
30
40
|
}
|
|
31
41
|
```
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Plugin, PluginInput } from "@opencode-ai/plugin";
|
|
2
|
-
import { readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
3
4
|
import { dirname, join } from "path";
|
|
4
5
|
import { fileURLToPath } from "url";
|
|
5
6
|
|
|
@@ -37,6 +38,10 @@ interface RouterConfig {
|
|
|
37
38
|
defaultTier: string;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
interface RouterState {
|
|
42
|
+
activePreset?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
// ---------------------------------------------------------------------------
|
|
41
46
|
// Config loader
|
|
42
47
|
// ---------------------------------------------------------------------------
|
|
@@ -50,13 +55,59 @@ function configPath(): string {
|
|
|
50
55
|
return join(getPluginRoot(), "tiers.json");
|
|
51
56
|
}
|
|
52
57
|
|
|
58
|
+
function statePath(): string {
|
|
59
|
+
return join(homedir(), ".config", "opencode", "opencode-model-router.state.json");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function resolvePresetName(cfg: RouterConfig, requestedPreset: string): string | undefined {
|
|
63
|
+
if (cfg.presets[requestedPreset]) {
|
|
64
|
+
return requestedPreset;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const normalized = requestedPreset.trim().toLowerCase();
|
|
68
|
+
if (!normalized) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return Object.keys(cfg.presets).find((name) => name.toLowerCase() === normalized);
|
|
73
|
+
}
|
|
74
|
+
|
|
53
75
|
function loadConfig(): RouterConfig {
|
|
54
|
-
|
|
76
|
+
const cfg = JSON.parse(readFileSync(configPath(), "utf-8")) as RouterConfig;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
if (existsSync(statePath())) {
|
|
80
|
+
const state = JSON.parse(readFileSync(statePath(), "utf-8")) as RouterState;
|
|
81
|
+
if (state.activePreset) {
|
|
82
|
+
const resolved = resolvePresetName(cfg, state.activePreset);
|
|
83
|
+
if (resolved) {
|
|
84
|
+
cfg.activePreset = resolved;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
// Ignore state read errors and keep tiers.json active preset
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return cfg;
|
|
55
93
|
}
|
|
56
94
|
|
|
57
95
|
function saveActivePreset(presetName: string): void {
|
|
58
96
|
const cfg = loadConfig();
|
|
59
|
-
|
|
97
|
+
const resolved = resolvePresetName(cfg, presetName);
|
|
98
|
+
if (!resolved) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
cfg.activePreset = resolved;
|
|
103
|
+
|
|
104
|
+
// Persist user-selected preset outside package cache so it survives npm updates
|
|
105
|
+
const presetState: RouterState = { activePreset: resolved };
|
|
106
|
+
const p = statePath();
|
|
107
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
108
|
+
writeFileSync(p, JSON.stringify(presetState, null, 2) + "\n", "utf-8");
|
|
109
|
+
|
|
110
|
+
// Keep local tiers.json in sync as best effort
|
|
60
111
|
writeFileSync(configPath(), JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
61
112
|
}
|
|
62
113
|
|
|
@@ -180,19 +231,22 @@ function buildPresetOutput(cfg: RouterConfig, args: string): string {
|
|
|
180
231
|
}
|
|
181
232
|
|
|
182
233
|
// Switch preset
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
234
|
+
const resolvedPreset = resolvePresetName(cfg, requestedPreset);
|
|
235
|
+
if (resolvedPreset) {
|
|
236
|
+
saveActivePreset(resolvedPreset);
|
|
237
|
+
cfg.activePreset = resolvedPreset;
|
|
238
|
+
const tiers = cfg.presets[resolvedPreset]!;
|
|
186
239
|
const models = Object.entries(tiers)
|
|
187
240
|
.map(([tier, t]) => ` @${tier} -> ${t.model}`)
|
|
188
241
|
.join("\n");
|
|
189
242
|
return [
|
|
190
|
-
`Preset switched to **${
|
|
243
|
+
`Preset switched to **${resolvedPreset}**.`,
|
|
191
244
|
"",
|
|
192
245
|
models,
|
|
193
246
|
"",
|
|
194
|
-
"
|
|
195
|
-
"
|
|
247
|
+
"Selection is now persisted in ~/.config/opencode/opencode-model-router.state.json.",
|
|
248
|
+
"Restart OpenCode for subagent model registration to take effect.",
|
|
249
|
+
"System prompt delegation rules update immediately.",
|
|
196
250
|
].join("\n");
|
|
197
251
|
}
|
|
198
252
|
|