@dylanrussell/agent-router 1.1.2 → 2.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/CHANGELOG.md +26 -0
- package/README.md +23 -17
- package/dist/cli.js +77 -22
- package/dist/cli.js.map +1 -1
- package/dist/plugin.d.ts +190 -3
- package/dist/plugin.js +404 -159
- package/dist/plugin.js.map +1 -1
- package/dist/tui.d.ts +3 -1
- package/dist/tui.js +166 -5
- package/dist/tui.js.map +1 -1
- package/index.ts +2 -0
- package/package.json +15 -7
- package/tui.ts +2 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# 2.0.1
|
|
2
|
+
|
|
3
|
+
- Normalize V2's `default` variant so pending next-user-turn fallback is not disabled by an equivalent model reference.
|
|
4
|
+
- Pass the native catalog into `router_back` validation instead of launching the obsolete CLI model command.
|
|
5
|
+
- Verify real isolated 2.0.8 HTTP 429/503 failover without replay, explicit model override, stack switching, undo, history and fixture integrity.
|
|
6
|
+
- Patch development dependencies; the full dependency audit reports no known advisories.
|
|
7
|
+
|
|
8
|
+
# 2.0.0
|
|
9
|
+
|
|
10
|
+
- Target OpenCode 2.0.8 with server and terminal `setup` entrypoints.
|
|
11
|
+
- Include literal root `index.ts`/`tui.ts` wrappers for the 2.0.8 local-directory resolver; published packages retain their exports.
|
|
12
|
+
- Add an isolated real-host integration check for package activation, terminal discovery, and RPC input validation through a disposable test probe.
|
|
13
|
+
- Preserve all six router tools, stack operations, history, sidebar and six slash commands.
|
|
14
|
+
- Register terminal commands under a mounted app slot so OpenCode 2.0.8 provides its Keymap context. Sidebar slots return real renderables with reactive child insertion.
|
|
15
|
+
- Register terminal plugins in `cli.json` and use native `plugins` entries, including package/options objects.
|
|
16
|
+
- Validate advertised model variants using the V2 model catalog. Unsupported `max` variants must be changed explicitly to an advertised variant such as `high`.
|
|
17
|
+
- Preserve legacy frontmatter and support capture/apply of native `model#variant` references.
|
|
18
|
+
- Queue fallback selection for the next explicit user prompt after retryable HTTP 429/500/502/503/504. Veto automatic same-turn retry for routed failures; never submit prompts or replay tools.
|
|
19
|
+
|
|
20
|
+
## Operational limits
|
|
21
|
+
|
|
22
|
+
- Applying/backing out stacks still requires restarting OpenCode to reload agent files.
|
|
23
|
+
- Terminal stack operations access local files; remote-server filesystem management is not supported.
|
|
24
|
+
- Fallback notices are server logs. The sidebar shows configured routing, not session-local fallback state.
|
|
25
|
+
- OpenCode 2.0.8 does not identify request kind on retry hooks. The plugin conservatively tracks the last model-request kind and only handles primary requests. Concurrent auxiliary requests can suppress fallback handling.
|
|
26
|
+
- Session defaults must resolve to explicit agent/model selections before routing. Fallback routing is bounded to 1,024 tracked sessions per plugin instance.
|
package/README.md
CHANGED
|
@@ -68,6 +68,8 @@ Stacks are config, history is state — point `stacksDir` somewhere dotfile-mana
|
|
|
68
68
|
|
|
69
69
|
## Install
|
|
70
70
|
|
|
71
|
+
Version 2 targets **OpenCode 2.0.8**. Version 1 remains the legacy OpenCode release. See [release notes and operational limits](./CHANGELOG.md).
|
|
72
|
+
|
|
71
73
|
```bash
|
|
72
74
|
npx -y @dylanrussell/agent-router init
|
|
73
75
|
```
|
|
@@ -75,13 +77,13 @@ npx -y @dylanrussell/agent-router init
|
|
|
75
77
|
`init` will:
|
|
76
78
|
|
|
77
79
|
1. Capture your agents' current models into a first stack (`default`) and mark it active.
|
|
78
|
-
2. Add `@dylanrussell/agent-router@latest` to the `
|
|
79
|
-
3. Add the same entry to `
|
|
80
|
+
2. Add `@dylanrussell/agent-router@latest` to the `plugins` array in `opencode.json` (backing it up first).
|
|
81
|
+
3. Add the same entry to `cli.json` — this loads the sidebar + `/agent-*` commands.
|
|
80
82
|
4. Remove the legacy `@dylanrussell/omo-router` plugin entry if present.
|
|
81
83
|
|
|
82
84
|
Then **restart opencode** so it picks up the plugin.
|
|
83
85
|
|
|
84
|
-
`@opencode
|
|
86
|
+
The API peer is pinned to `@opencode/plugin@2.0.8`. OpenCode provides the terminal renderer at runtime. Both package entrypoints default-export V2 plugin definitions.
|
|
85
87
|
|
|
86
88
|
## Quickstart
|
|
87
89
|
|
|
@@ -94,7 +96,7 @@ agent-router current # print the agent → model mapping in fro
|
|
|
94
96
|
agent-router status # print active stack name
|
|
95
97
|
agent-router show cheap # print a stack's JSON
|
|
96
98
|
agent-router edit cheap # open a stack in $EDITOR
|
|
97
|
-
agent-router validate --all # check
|
|
99
|
+
agent-router validate --all # check model IDs and variants against the V2 catalog
|
|
98
100
|
agent-router history # list recent switches
|
|
99
101
|
agent-router import my-mix <file> # import a stack from a file
|
|
100
102
|
agent-router export my-mix <file> # export a stack to a file
|
|
@@ -125,7 +127,7 @@ A stack file needs one thing: an `agents` record whose entries carry a `model` s
|
|
|
125
127
|
- Reserved opencode framework keys (`description`, `mode`, `permission`, `tools`, `prompt`, `steps`, `color`, `name`) are never transcribed — a stack entry can't clobber them.
|
|
126
128
|
- Option values serialize to a single frontmatter line: scalars bare (quoted only when needed), objects/arrays as JSON flow. Block-style values an apply replaces are collapsed to flow; `capture` parses them back into typed JSON.
|
|
127
129
|
|
|
128
|
-
`use` is strict by design: if a stack references an agent file that doesn't exist, or one without a `model:` line, the switch fails **before anything is written
|
|
130
|
+
`use` is strict by design: if a stack references an agent file that doesn't exist, or one without a `model:` line, the switch fails **before anything is written**. It validates model IDs and variants against the V2 catalog first (skip with `--no-validate`, override with `--force-invalid`).
|
|
129
131
|
|
|
130
132
|
`capture` is the inverse: it reads the current `model:` line and option keys of every agent file (files without a `model:` line are skipped) and writes a stack. There are no bundled seed stacks — your real setup is the seed.
|
|
131
133
|
|
|
@@ -154,36 +156,40 @@ Model IDs and variants above are illustrative; use models and variants supported
|
|
|
154
156
|
- `fallbacks` is optional, or an array of 0 to 8 objects. Each object contains only `model` (non-whitespace `provider/model`) and optional `variant` (nonempty string). `null`, bare strings, unknown candidate fields, and longer chains are rejected.
|
|
155
157
|
- Candidate order is preserved. Exact `(model, variant)` duplicates, including the primary, are skipped at runtime. An omitted fallback variant clears the previous candidate's variant.
|
|
156
158
|
- Only model and variant change at runtime. Agent prompts, permissions, tools, and other provider options remain unchanged; ensure those options are compatible with every candidate.
|
|
157
|
-
- Validation checks every primary and fallback ID, reporting paths such as `agents.oracle.fallbacks.0.
|
|
159
|
+
- Validation checks every primary and fallback ID and variant, reporting paths such as `agents.oracle.fallbacks.0.variant`. Server tools use the connected host catalog; the standalone CLI uses `opencode api model.list`. Catalog membership does not prove live provider health or quota. `validate --active` includes applied fallback metadata when its primary still matches frontmatter.
|
|
158
160
|
- Failover does not rewrite frontmatter, stacks, history, or router state. `use` stores the applied chains in optional `state.json.fallbackAgents`. Old state files without that field remain valid and have no enabled chains until the stack is reapplied.
|
|
159
161
|
- `capture` and displaced-history snapshots merge those applied chains with live frontmatter only when primary model and variant still match. Editing a stack alone does not alter applied metadata. Applying a stack without chains clears the applied chains, including agents omitted from that stack.
|
|
160
162
|
- `back` retains its existing semantics: reapply a previous **named stack as it exists now**, not restore historical snapshot bytes. Runtime failover never adds a history entry.
|
|
161
163
|
|
|
162
164
|
### Runtime Behavior And Limits
|
|
163
165
|
|
|
164
|
-
The plugin watches
|
|
166
|
+
The V2 plugin watches `retry` hooks correlated with the admitted user turn, agent and selected model. A retryable HTTP **429, 500, 502, 503, or 504** stages the next candidate and vetoes the same-turn retry. Only primary model requests participate.
|
|
165
167
|
|
|
166
168
|
Authentication/permission errors, context overflow, content filtering, output-length errors, unknown errors, transport errors without an HTTP status, and ambiguous `session.error` notifications do not advance the chain. Router never classifies errors by substring matching.
|
|
167
169
|
|
|
168
|
-
After a qualifying failure a
|
|
170
|
+
After a qualifying failure a server log names the next model and asks for an explicit retry/continue instruction. **Review partial output and completed tools before continuing.** The next `prompt` admission hook calls `session.switchModel`; it never submits a prompt. Chains advance monotonically, never wrap, and never replay prompts or tools.
|
|
169
171
|
|
|
170
|
-
Observed
|
|
172
|
+
Observed interruption, explicit model/agent selection events, and an unexpected incoming model disable routing for that session. Router state changes disable startup routing at the next prompt admission. Restart to activate a newly applied stack.
|
|
171
173
|
|
|
172
|
-
Duplicate
|
|
174
|
+
Duplicate failures advance only once per admitted user turn. Routing budgets are isolated by session and agent and bounded to 1,024 sessions. Subagents without user prompt admission are left alone.
|
|
173
175
|
|
|
174
|
-
**Verified boundary:**
|
|
176
|
+
**Verified boundary:** compiled against the published **2.0.8** API types. Deterministic adapter tests cover next-turn selection, retry veto, auxiliary-request exclusion, variants, and no replay or persistent agent writes. These are not real-provider end-to-end tests. An LLM may choose to repeat a tool when explicitly asked to continue.
|
|
175
177
|
|
|
176
|
-
|
|
178
|
+
OpenCode 2.0.8 exposes no atomic compare-and-switch in the prompt hook and no request kind in the retry hook. Concurrent manual selections/admissions or auxiliary requests remain host API limitations. The sidebar displays configured routing, not the session-local runtime candidate. Terminal stack operations require local filesystem access; remote server filesystem management is not supported.
|
|
177
179
|
|
|
178
180
|
### Install This Checkout
|
|
179
181
|
|
|
180
|
-
|
|
182
|
+
To test a local V2 checkout, run `npm run typecheck`, `npm test`, and `npm run build` (a fresh checkout uses `pnpm install --frozen-lockfile`). Replace the registry entry in the server `opencode.json` **plugins** array with the package directory:
|
|
181
183
|
|
|
182
184
|
```json
|
|
183
|
-
"file:///absolute/path/to/agent-router
|
|
185
|
+
"file:///absolute/path/to/agent-router"
|
|
184
186
|
```
|
|
185
187
|
|
|
186
|
-
|
|
188
|
+
Use the same package directory in `cli.json` **plugins** when explicitly registering the terminal half. Do not load both registry and local copies. The local CLI is `node /absolute/path/to/agent-router/dist/cli.js`. Applying a stack still requires restarting OpenCode; inspect server logs for next-turn fallback notices.
|
|
189
|
+
|
|
190
|
+
OpenCode 2.0.8 ignores package exports for local directories. Root `index.ts` and `tui.ts` wrappers load the built files; run `npm run build` before using the source directory.
|
|
191
|
+
|
|
192
|
+
`npm run test:integration` starts an isolated real OpenCode 2.0.8 server, initializes its location, verifies router activation and terminal discovery, and exercises a disposable RPC probe with absent, null, and invalid input. It uses temporary HOME/XDG/router directories and never modifies live configuration. Set `AGENT_ROUTER_TEST_PACKAGE` to an unpacked tarball directory to test release contents. Router itself exposes tools, not RPC methods.
|
|
187
193
|
|
|
188
194
|
## Inside opencode
|
|
189
195
|
|
|
@@ -202,7 +208,7 @@ The plugin exposes six tools the agent (or you, by asking it) can call:
|
|
|
202
208
|
|
|
203
209
|
## In the TUI
|
|
204
210
|
|
|
205
|
-
|
|
211
|
+
The V2 terminal half loads from the package's `./tui` export (`cli.json`, wired up by `init`):
|
|
206
212
|
|
|
207
213
|
- **Sidebar panel** — lists available stacks with the active one checked. Under **Current Stack → Configured routing**, each agent has an indented **Primary** model and ordered **Fallback 1**, **Fallback 2**, … rows. Explicit variants appear in brackets; agents without fallbacks show only their primary. Model IDs are kept in full. A `⟳ restart required` badge appears when the active stack differs from the one at TUI startup. Updates live (≤1.5s), including fallback-only and variant-only edits.
|
|
208
214
|
- **Configuration, not live failover** — sidebar chains come from the active stack file, not session-local routing or the applied `state.json.fallbackAgents` snapshot. Editing a stack changes this preview but does not apply it: use the stack and restart opencode to activate changes. A fallback row is a configured candidate, not a claim that failover is enabled or that the model is currently running.
|
|
@@ -244,7 +250,7 @@ Paths resolve in this order: explicit option → `config.json` → env var → d
|
|
|
244
250
|
|
|
245
251
|
- **Restart required.** opencode reads agent files once at startup. After every `agent-router use`, restart opencode for the new models to take effect. The CLI reminds you.
|
|
246
252
|
- **Hand-edits to frontmatter are not auto-saved into stacks.** If you hand-tune a model or option and want to keep it, `capture` it (or `capture <active> --force`). The next `use` that touches that agent overwrites the hand-edit for keys the stack names; keys the stack omits are preserved (set an option to `null` in a stack entry to clear it).
|
|
247
|
-
- **Validation is
|
|
253
|
+
- **Validation is catalog-dependent.** `agent-router validate` uses the active V2 model catalog, including variants. Catalog membership does not guarantee provider health, valid credentials, or available quota.
|
|
248
254
|
|
|
249
255
|
## FAQ
|
|
250
256
|
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __esm = (fn, res) => function __init() {
|
|
5
|
-
|
|
4
|
+
var __esm = (fn, res, err2) => function __init() {
|
|
5
|
+
if (err2) throw err2[0];
|
|
6
|
+
try {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
} catch (e) {
|
|
9
|
+
throw err2 = [e], e;
|
|
10
|
+
}
|
|
6
11
|
};
|
|
7
12
|
var __export = (target, all) => {
|
|
8
13
|
for (var name in all)
|
|
@@ -15282,7 +15287,13 @@ var init_schema = __esm({
|
|
|
15282
15287
|
stacksDir: external_exports.string().min(1).optional()
|
|
15283
15288
|
}).strict();
|
|
15284
15289
|
OpencodeJsonSchema = external_exports.object({
|
|
15285
|
-
plugin: external_exports.array(external_exports.string()).optional()
|
|
15290
|
+
plugin: external_exports.array(external_exports.string()).optional(),
|
|
15291
|
+
plugins: external_exports.array(
|
|
15292
|
+
external_exports.union([
|
|
15293
|
+
external_exports.string(),
|
|
15294
|
+
external_exports.object({ package: external_exports.string(), options: external_exports.record(external_exports.string(), external_exports.unknown()).optional() }).passthrough()
|
|
15295
|
+
])
|
|
15296
|
+
).optional()
|
|
15286
15297
|
}).passthrough();
|
|
15287
15298
|
}
|
|
15288
15299
|
});
|
|
@@ -16020,7 +16031,7 @@ function resolvePaths(options = {}) {
|
|
|
16020
16031
|
return {
|
|
16021
16032
|
opencodeConfigDir,
|
|
16022
16033
|
opencodeJsonPath: path2.join(opencodeConfigDir, "opencode.json"),
|
|
16023
|
-
tuiJsonPath: path2.join(opencodeConfigDir, "
|
|
16034
|
+
tuiJsonPath: path2.join(opencodeConfigDir, "cli.json"),
|
|
16024
16035
|
agentsDir,
|
|
16025
16036
|
opencodeBackupsDir: path2.join(opencodeConfigDir, ".backups"),
|
|
16026
16037
|
routerHome,
|
|
@@ -16386,6 +16397,11 @@ var RESERVED_AGENT_KEYS = /* @__PURE__ */ new Set([
|
|
|
16386
16397
|
"mode",
|
|
16387
16398
|
"description",
|
|
16388
16399
|
"permission",
|
|
16400
|
+
"permissions",
|
|
16401
|
+
"disabled",
|
|
16402
|
+
"disable",
|
|
16403
|
+
"system",
|
|
16404
|
+
"hidden",
|
|
16389
16405
|
"color",
|
|
16390
16406
|
"tools",
|
|
16391
16407
|
"prompt",
|
|
@@ -16626,31 +16642,35 @@ async function readPluginConfigFile(filePath, label) {
|
|
|
16626
16642
|
return result.data;
|
|
16627
16643
|
}
|
|
16628
16644
|
function ensurePluginEntry(config2, entry = PLUGIN_REGISTRY_ENTRY) {
|
|
16629
|
-
const existing = config2.plugin ?? [];
|
|
16645
|
+
const existing = config2.plugins ?? config2.plugin ?? [];
|
|
16630
16646
|
const already = existing.some(
|
|
16631
|
-
(p) => p === entry || stripVersionTag(p) === stripVersionTag(entry)
|
|
16647
|
+
(p) => pluginPackage(p) === entry || stripVersionTag(pluginPackage(p)) === stripVersionTag(entry)
|
|
16632
16648
|
);
|
|
16633
16649
|
if (already) {
|
|
16634
|
-
return { config: config2, result: { added: false, plugin: existing } };
|
|
16650
|
+
return { config: config2, result: { added: false, plugin: existing.map(pluginPackage) } };
|
|
16635
16651
|
}
|
|
16636
16652
|
const next = [...existing, entry];
|
|
16637
16653
|
return {
|
|
16638
|
-
config: { ...config2,
|
|
16639
|
-
result: { added: true, plugin: next }
|
|
16654
|
+
config: { ...config2, plugins: next },
|
|
16655
|
+
result: { added: true, plugin: next.map(pluginPackage) }
|
|
16640
16656
|
};
|
|
16641
16657
|
}
|
|
16642
16658
|
function removePluginEntry(config2, npmName = LEGACY_PLUGIN_NPM_NAME) {
|
|
16643
|
-
const
|
|
16644
|
-
const
|
|
16659
|
+
const key = config2.plugins ? "plugins" : "plugin";
|
|
16660
|
+
const existing = config2[key] ?? [];
|
|
16661
|
+
const removed = existing.filter((p) => stripVersionTag(pluginPackage(p)) === npmName);
|
|
16645
16662
|
if (removed.length === 0) {
|
|
16646
|
-
return { config: config2, result: { removed: [], plugin: existing } };
|
|
16663
|
+
return { config: config2, result: { removed: [], plugin: existing.map(pluginPackage) } };
|
|
16647
16664
|
}
|
|
16648
|
-
const next = existing.filter((p) => stripVersionTag(p) !== npmName);
|
|
16665
|
+
const next = existing.filter((p) => stripVersionTag(pluginPackage(p)) !== npmName);
|
|
16649
16666
|
return {
|
|
16650
|
-
config: { ...config2,
|
|
16651
|
-
result: { removed, plugin: next }
|
|
16667
|
+
config: { ...config2, [key]: next },
|
|
16668
|
+
result: { removed: removed.map(pluginPackage), plugin: next.map(pluginPackage) }
|
|
16652
16669
|
};
|
|
16653
16670
|
}
|
|
16671
|
+
function pluginPackage(entry) {
|
|
16672
|
+
return typeof entry === "string" ? entry : entry.package;
|
|
16673
|
+
}
|
|
16654
16674
|
function stripVersionTag(entry) {
|
|
16655
16675
|
const lastAt = entry.lastIndexOf("@");
|
|
16656
16676
|
if (lastAt <= 0) return entry;
|
|
@@ -16688,7 +16708,7 @@ import { execFile } from "child_process";
|
|
|
16688
16708
|
import { promisify } from "util";
|
|
16689
16709
|
var execFileAsync = promisify(execFile);
|
|
16690
16710
|
var DEFAULT_RUNNER = async () => {
|
|
16691
|
-
const { stdout } = await execFileAsync("opencode", ["
|
|
16711
|
+
const { stdout } = await execFileAsync("opencode", ["api", "model.list"], {
|
|
16692
16712
|
encoding: "utf8",
|
|
16693
16713
|
timeout: 3e4,
|
|
16694
16714
|
maxBuffer: 4 * 1024 * 1024
|
|
@@ -16697,6 +16717,17 @@ var DEFAULT_RUNNER = async () => {
|
|
|
16697
16717
|
};
|
|
16698
16718
|
function parseModelList(stdout) {
|
|
16699
16719
|
const out = /* @__PURE__ */ new Set();
|
|
16720
|
+
if (/^[\[{]/.test(stdout.trimStart())) {
|
|
16721
|
+
const parsed = JSON.parse(stdout);
|
|
16722
|
+
const models = Array.isArray(parsed) ? parsed : parsed.data;
|
|
16723
|
+
for (const model of models) {
|
|
16724
|
+
if (model.enabled === false) continue;
|
|
16725
|
+
const id = `${model.providerID}/${model.id}`;
|
|
16726
|
+
out.add(id);
|
|
16727
|
+
for (const variant of model.variants ?? []) out.add(`${id}#${variant.id}`);
|
|
16728
|
+
}
|
|
16729
|
+
return out;
|
|
16730
|
+
}
|
|
16700
16731
|
for (const raw of stdout.split(/\r?\n/)) {
|
|
16701
16732
|
const line = raw.trim();
|
|
16702
16733
|
if (!line) continue;
|
|
@@ -16721,6 +16752,19 @@ async function validateStack(stack, options = {}) {
|
|
|
16721
16752
|
const available = parseModelList(stdout);
|
|
16722
16753
|
const refs = collectModelRefs(stack);
|
|
16723
16754
|
const missing = refs.filter((r) => !available.has(r.modelId));
|
|
16755
|
+
if (/^[\[{]/.test(stdout.trimStart())) {
|
|
16756
|
+
for (const [agent, entry] of Object.entries(stack.agents)) {
|
|
16757
|
+
for (const [index, candidate] of [entry, ...entry.fallbacks ?? []].entries()) {
|
|
16758
|
+
if (!candidate.variant) continue;
|
|
16759
|
+
const modelId = `${candidate.model}#${candidate.variant}`;
|
|
16760
|
+
if (!available.has(modelId))
|
|
16761
|
+
missing.push({
|
|
16762
|
+
path: index === 0 ? `agents.${agent}.variant` : `agents.${agent}.fallbacks.${index - 1}.variant`,
|
|
16763
|
+
modelId
|
|
16764
|
+
});
|
|
16765
|
+
}
|
|
16766
|
+
}
|
|
16767
|
+
}
|
|
16724
16768
|
return { ok: missing.length === 0, checked: refs.length, missing, available };
|
|
16725
16769
|
}
|
|
16726
16770
|
async function validateStackOrThrow(stackName, stack, options = {}) {
|
|
@@ -16792,8 +16836,15 @@ async function applyStack(paths, name, options = {}) {
|
|
|
16792
16836
|
for (const [agent, entry] of Object.entries(target.agents)) {
|
|
16793
16837
|
const { filePath, content, model } = await readAgentFileStrict(paths.agentsDir, agent);
|
|
16794
16838
|
const wantOptions = entryOptions(entry);
|
|
16839
|
+
let wantModel = entry.model;
|
|
16840
|
+
if (model.includes("#") || wantModel.includes("#")) {
|
|
16841
|
+
const [base, inline] = wantModel.split("#");
|
|
16842
|
+
const variant = "variant" in entry ? entry.variant : inline ?? model.split("#")[1];
|
|
16843
|
+
wantModel = `${base}${variant ? `#${variant}` : ""}`;
|
|
16844
|
+
wantOptions.variant = null;
|
|
16845
|
+
}
|
|
16795
16846
|
const afterOptions = setFrontmatterOptions(content, wantOptions);
|
|
16796
|
-
const nextContent =
|
|
16847
|
+
const nextContent = wantModel === model ? afterOptions : setFrontmatterModel(afterOptions, wantModel);
|
|
16797
16848
|
pending.push({ agent, filePath, next: nextContent === content ? null : nextContent });
|
|
16798
16849
|
}
|
|
16799
16850
|
const prevState = await readState(paths.statePath);
|
|
@@ -16860,6 +16911,10 @@ function entriesToStackAgents(entries) {
|
|
|
16860
16911
|
for (const [k, v] of Object.entries(options)) {
|
|
16861
16912
|
if (k !== "fallbacks") stackEntry[k] = v;
|
|
16862
16913
|
}
|
|
16914
|
+
if (model.includes("#")) {
|
|
16915
|
+
stackEntry.model = model.slice(0, model.indexOf("#"));
|
|
16916
|
+
stackEntry.variant = model.slice(model.indexOf("#") + 1);
|
|
16917
|
+
}
|
|
16863
16918
|
out[name] = stackEntry;
|
|
16864
16919
|
}
|
|
16865
16920
|
return out;
|
|
@@ -16951,7 +17006,7 @@ async function exportStack(paths, name, toFile) {
|
|
|
16951
17006
|
}
|
|
16952
17007
|
|
|
16953
17008
|
// src/version.ts
|
|
16954
|
-
var VERSION = "
|
|
17009
|
+
var VERSION = "2.0.1";
|
|
16955
17010
|
|
|
16956
17011
|
// src/cli.ts
|
|
16957
17012
|
var log = (...args) => console.log(...args);
|
|
@@ -16995,8 +17050,8 @@ async function cmdInit(paths, opts) {
|
|
|
16995
17050
|
}
|
|
16996
17051
|
if (opts.noEditOpencodeJson) {
|
|
16997
17052
|
log("agent-router: --no-edit-opencode-json passed; not editing opencode.json or tui.json");
|
|
16998
|
-
log(` add this to opencode.json
|
|
16999
|
-
log(` add this to
|
|
17053
|
+
log(` add this to opencode.json plugins[]: "${PLUGIN_REGISTRY_ENTRY}"`);
|
|
17054
|
+
log(` add this to cli.json plugins[] (sidebar): "${PLUGIN_REGISTRY_ENTRY}"`);
|
|
17000
17055
|
return;
|
|
17001
17056
|
}
|
|
17002
17057
|
await editOpencodeJson(paths);
|
|
@@ -17009,7 +17064,7 @@ async function editTuiJson(paths) {
|
|
|
17009
17064
|
}
|
|
17010
17065
|
const result = await ensureTuiJsonPluginEntry(paths.tuiJsonPath);
|
|
17011
17066
|
if (result.added) {
|
|
17012
|
-
log(`agent-router: added "${PLUGIN_REGISTRY_ENTRY}" to
|
|
17067
|
+
log(`agent-router: added "${PLUGIN_REGISTRY_ENTRY}" to cli.json plugins[] (sidebar support)`);
|
|
17013
17068
|
return;
|
|
17014
17069
|
}
|
|
17015
17070
|
log("agent-router: tui.json already up to date");
|
|
@@ -17034,7 +17089,7 @@ async function editOpencodeJson(paths) {
|
|
|
17034
17089
|
);
|
|
17035
17090
|
}
|
|
17036
17091
|
if (ensured.result.added) {
|
|
17037
|
-
log(`agent-router: added "${PLUGIN_REGISTRY_ENTRY}" to opencode.json
|
|
17092
|
+
log(`agent-router: added "${PLUGIN_REGISTRY_ENTRY}" to opencode.json plugins[]`);
|
|
17038
17093
|
}
|
|
17039
17094
|
}
|
|
17040
17095
|
async function cmdList(paths) {
|