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.
Files changed (3) hide show
  1. package/README.md +11 -1
  2. package/package.json +1 -1
  3. 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@1.0.0"
38
+ "opencode-model-router@latest"
29
39
  ]
30
40
  }
31
41
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-model-router",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "OpenCode plugin that routes tasks to tiered subagents (fast/medium/heavy) based on complexity",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
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
- return JSON.parse(readFileSync(configPath(), "utf-8")) as RouterConfig;
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
- cfg.activePreset = presetName;
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
- if (cfg.presets[requestedPreset]) {
184
- saveActivePreset(requestedPreset);
185
- const tiers = cfg.presets[requestedPreset]!;
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 **${requestedPreset}**.`,
243
+ `Preset switched to **${resolvedPreset}**.`,
191
244
  "",
192
245
  models,
193
246
  "",
194
- "Restart OpenCode for agent registration to take effect.",
195
- "System prompt delegation rules will update immediately.",
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