@dylanrussell/agent-router 1.1.2 → 2.0.0
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 +19 -0
- package/README.md +23 -17
- package/dist/cli.js +70 -20
- package/dist/cli.js.map +1 -1
- package/dist/plugin.d.ts +190 -3
- package/dist/plugin.js +382 -153
- 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 +7 -3
- package/tui.ts +2 -0
package/dist/tui.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Context } from '@opencode/plugin/tui/context';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Structural slice of opencode's TuiPluginApi.
|
|
3
5
|
*
|
|
@@ -93,7 +95,7 @@ interface RouterTuiApi {
|
|
|
93
95
|
declare const tui: (api: RouterTuiApi) => Promise<void>;
|
|
94
96
|
declare const agentRouterTui: {
|
|
95
97
|
id: string;
|
|
96
|
-
|
|
98
|
+
setup(ctx: Context): Promise<(() => Promise<void>) | undefined>;
|
|
97
99
|
};
|
|
98
100
|
|
|
99
101
|
export { agentRouterTui as default, tui };
|
package/dist/tui.js
CHANGED
|
@@ -90,7 +90,7 @@ function resolvePaths(options = {}) {
|
|
|
90
90
|
return {
|
|
91
91
|
opencodeConfigDir,
|
|
92
92
|
opencodeJsonPath: path.join(opencodeConfigDir, "opencode.json"),
|
|
93
|
-
tuiJsonPath: path.join(opencodeConfigDir, "
|
|
93
|
+
tuiJsonPath: path.join(opencodeConfigDir, "cli.json"),
|
|
94
94
|
agentsDir,
|
|
95
95
|
opencodeBackupsDir: path.join(opencodeConfigDir, ".backups"),
|
|
96
96
|
routerHome,
|
|
@@ -14648,7 +14648,13 @@ var ConfigFileSchema = external_exports.object({
|
|
|
14648
14648
|
stacksDir: external_exports.string().min(1).optional()
|
|
14649
14649
|
}).strict();
|
|
14650
14650
|
var OpencodeJsonSchema = external_exports.object({
|
|
14651
|
-
plugin: external_exports.array(external_exports.string()).optional()
|
|
14651
|
+
plugin: external_exports.array(external_exports.string()).optional(),
|
|
14652
|
+
plugins: external_exports.array(
|
|
14653
|
+
external_exports.union([
|
|
14654
|
+
external_exports.string(),
|
|
14655
|
+
external_exports.object({ package: external_exports.string(), options: external_exports.record(external_exports.string(), external_exports.unknown()).optional() }).passthrough()
|
|
14656
|
+
])
|
|
14657
|
+
).optional()
|
|
14652
14658
|
}).passthrough();
|
|
14653
14659
|
|
|
14654
14660
|
// src/core/config.ts
|
|
@@ -15046,6 +15052,11 @@ var RESERVED_AGENT_KEYS = /* @__PURE__ */ new Set([
|
|
|
15046
15052
|
"mode",
|
|
15047
15053
|
"description",
|
|
15048
15054
|
"permission",
|
|
15055
|
+
"permissions",
|
|
15056
|
+
"disabled",
|
|
15057
|
+
"disable",
|
|
15058
|
+
"system",
|
|
15059
|
+
"hidden",
|
|
15049
15060
|
"color",
|
|
15050
15061
|
"tools",
|
|
15051
15062
|
"prompt",
|
|
@@ -15294,7 +15305,7 @@ import { execFile } from "child_process";
|
|
|
15294
15305
|
import { promisify } from "util";
|
|
15295
15306
|
var execFileAsync = promisify(execFile);
|
|
15296
15307
|
var DEFAULT_RUNNER = async () => {
|
|
15297
|
-
const { stdout } = await execFileAsync("opencode", ["
|
|
15308
|
+
const { stdout } = await execFileAsync("opencode", ["api", "model.list"], {
|
|
15298
15309
|
encoding: "utf8",
|
|
15299
15310
|
timeout: 3e4,
|
|
15300
15311
|
maxBuffer: 4 * 1024 * 1024
|
|
@@ -15303,6 +15314,17 @@ var DEFAULT_RUNNER = async () => {
|
|
|
15303
15314
|
};
|
|
15304
15315
|
function parseModelList(stdout) {
|
|
15305
15316
|
const out = /* @__PURE__ */ new Set();
|
|
15317
|
+
if (/^[\[{]/.test(stdout.trimStart())) {
|
|
15318
|
+
const parsed = JSON.parse(stdout);
|
|
15319
|
+
const models = Array.isArray(parsed) ? parsed : parsed.data;
|
|
15320
|
+
for (const model of models) {
|
|
15321
|
+
if (model.enabled === false) continue;
|
|
15322
|
+
const id = `${model.providerID}/${model.id}`;
|
|
15323
|
+
out.add(id);
|
|
15324
|
+
for (const variant of model.variants ?? []) out.add(`${id}#${variant.id}`);
|
|
15325
|
+
}
|
|
15326
|
+
return out;
|
|
15327
|
+
}
|
|
15306
15328
|
for (const raw of stdout.split(/\r?\n/)) {
|
|
15307
15329
|
const line = raw.trim();
|
|
15308
15330
|
if (!line) continue;
|
|
@@ -15327,6 +15349,19 @@ async function validateStack(stack, options = {}) {
|
|
|
15327
15349
|
const available = parseModelList(stdout);
|
|
15328
15350
|
const refs = collectModelRefs(stack);
|
|
15329
15351
|
const missing = refs.filter((r) => !available.has(r.modelId));
|
|
15352
|
+
if (/^[\[{]/.test(stdout.trimStart())) {
|
|
15353
|
+
for (const [agent, entry] of Object.entries(stack.agents)) {
|
|
15354
|
+
for (const [index, candidate] of [entry, ...entry.fallbacks ?? []].entries()) {
|
|
15355
|
+
if (!candidate.variant) continue;
|
|
15356
|
+
const modelId = `${candidate.model}#${candidate.variant}`;
|
|
15357
|
+
if (!available.has(modelId))
|
|
15358
|
+
missing.push({
|
|
15359
|
+
path: index === 0 ? `agents.${agent}.variant` : `agents.${agent}.fallbacks.${index - 1}.variant`,
|
|
15360
|
+
modelId
|
|
15361
|
+
});
|
|
15362
|
+
}
|
|
15363
|
+
}
|
|
15364
|
+
}
|
|
15330
15365
|
return { ok: missing.length === 0, checked: refs.length, missing, available };
|
|
15331
15366
|
}
|
|
15332
15367
|
async function validateStackOrThrow(stackName, stack, options = {}) {
|
|
@@ -15391,8 +15426,15 @@ async function applyStack(paths, name, options = {}) {
|
|
|
15391
15426
|
for (const [agent, entry] of Object.entries(target.agents)) {
|
|
15392
15427
|
const { filePath, content, model } = await readAgentFileStrict(paths.agentsDir, agent);
|
|
15393
15428
|
const wantOptions = entryOptions(entry);
|
|
15429
|
+
let wantModel = entry.model;
|
|
15430
|
+
if (model.includes("#") || wantModel.includes("#")) {
|
|
15431
|
+
const [base, inline] = wantModel.split("#");
|
|
15432
|
+
const variant = "variant" in entry ? entry.variant : inline ?? model.split("#")[1];
|
|
15433
|
+
wantModel = `${base}${variant ? `#${variant}` : ""}`;
|
|
15434
|
+
wantOptions.variant = null;
|
|
15435
|
+
}
|
|
15394
15436
|
const afterOptions = setFrontmatterOptions(content, wantOptions);
|
|
15395
|
-
const nextContent =
|
|
15437
|
+
const nextContent = wantModel === model ? afterOptions : setFrontmatterModel(afterOptions, wantModel);
|
|
15396
15438
|
pending.push({ agent, filePath, next: nextContent === content ? null : nextContent });
|
|
15397
15439
|
}
|
|
15398
15440
|
const prevState = await readState(paths.statePath);
|
|
@@ -15459,6 +15501,10 @@ function entriesToStackAgents(entries) {
|
|
|
15459
15501
|
for (const [k, v] of Object.entries(options)) {
|
|
15460
15502
|
if (k !== "fallbacks") stackEntry[k] = v;
|
|
15461
15503
|
}
|
|
15504
|
+
if (model.includes("#")) {
|
|
15505
|
+
stackEntry.model = model.slice(0, model.indexOf("#"));
|
|
15506
|
+
stackEntry.variant = model.slice(model.indexOf("#") + 1);
|
|
15507
|
+
}
|
|
15462
15508
|
out[name] = stackEntry;
|
|
15463
15509
|
}
|
|
15464
15510
|
return out;
|
|
@@ -16014,7 +16060,122 @@ var tui = async (api) => {
|
|
|
16014
16060
|
};
|
|
16015
16061
|
var agentRouterTui = {
|
|
16016
16062
|
id: "agent-router:tui",
|
|
16017
|
-
|
|
16063
|
+
async setup(ctx) {
|
|
16064
|
+
const solid = await importHostSolid();
|
|
16065
|
+
if (!solid) return;
|
|
16066
|
+
const cleanups = [];
|
|
16067
|
+
const [revision, updateRevision] = ctx.storage.memory("sidebar-revision", {
|
|
16068
|
+
initial: { value: 0 }
|
|
16069
|
+
});
|
|
16070
|
+
const api = {
|
|
16071
|
+
slots: {
|
|
16072
|
+
register: ({ slots }) => {
|
|
16073
|
+
for (const render of Object.values(slots)) {
|
|
16074
|
+
cleanups.push(
|
|
16075
|
+
ctx.ui.slot({
|
|
16076
|
+
append: "sidebar.content",
|
|
16077
|
+
render: () => {
|
|
16078
|
+
const node = solid.createElement("box");
|
|
16079
|
+
solid.setProp(node, "flexDirection", "column");
|
|
16080
|
+
solid.insert(node, () => {
|
|
16081
|
+
void revision.value;
|
|
16082
|
+
return render();
|
|
16083
|
+
});
|
|
16084
|
+
return node;
|
|
16085
|
+
}
|
|
16086
|
+
})
|
|
16087
|
+
);
|
|
16088
|
+
}
|
|
16089
|
+
}
|
|
16090
|
+
},
|
|
16091
|
+
renderer: {
|
|
16092
|
+
requestRender: () => {
|
|
16093
|
+
updateRevision((draft) => {
|
|
16094
|
+
draft.value++;
|
|
16095
|
+
});
|
|
16096
|
+
ctx.renderer.requestRender();
|
|
16097
|
+
}
|
|
16098
|
+
},
|
|
16099
|
+
ui: {
|
|
16100
|
+
toast: (input) => ctx.ui.toast.show(input),
|
|
16101
|
+
dialog: {
|
|
16102
|
+
replace: (render) => {
|
|
16103
|
+
render();
|
|
16104
|
+
},
|
|
16105
|
+
clear: () => ctx.ui.dialog.clear()
|
|
16106
|
+
},
|
|
16107
|
+
DialogSelect: (props) => {
|
|
16108
|
+
void ctx.ui.dialog.select({
|
|
16109
|
+
title: props.title,
|
|
16110
|
+
options: props.options.map((option) => ({
|
|
16111
|
+
title: option.title,
|
|
16112
|
+
value: option.value,
|
|
16113
|
+
...option.description ? { description: option.description } : {},
|
|
16114
|
+
...option.category ? { category: option.category } : {},
|
|
16115
|
+
...option.disabled !== void 0 ? { disabled: option.disabled } : {}
|
|
16116
|
+
})),
|
|
16117
|
+
current: props.current
|
|
16118
|
+
}).then((value) => {
|
|
16119
|
+
const option = props.options.find((option2) => option2.value === value);
|
|
16120
|
+
if (option) {
|
|
16121
|
+
option.onSelect?.();
|
|
16122
|
+
props.onSelect?.(option);
|
|
16123
|
+
}
|
|
16124
|
+
});
|
|
16125
|
+
return null;
|
|
16126
|
+
},
|
|
16127
|
+
DialogConfirm: (props) => {
|
|
16128
|
+
void ctx.ui.dialog.confirm({ title: props.title, message: props.message }).then((yes) => {
|
|
16129
|
+
if (yes) props.onConfirm?.();
|
|
16130
|
+
else props.onCancel?.();
|
|
16131
|
+
});
|
|
16132
|
+
return null;
|
|
16133
|
+
}
|
|
16134
|
+
},
|
|
16135
|
+
command: {
|
|
16136
|
+
register: (build) => cleanups.push(
|
|
16137
|
+
ctx.ui.slot({
|
|
16138
|
+
append: "app",
|
|
16139
|
+
render: () => {
|
|
16140
|
+
ctx.keymap.layer(() => ({
|
|
16141
|
+
mode: "global",
|
|
16142
|
+
commands: build().map((command) => ({
|
|
16143
|
+
id: command.value,
|
|
16144
|
+
title: command.title,
|
|
16145
|
+
group: command.category ?? "agent-router",
|
|
16146
|
+
palette: true,
|
|
16147
|
+
...command.slash ? {
|
|
16148
|
+
slash: {
|
|
16149
|
+
name: command.slash.name,
|
|
16150
|
+
aliases: [...command.slash.aliases ?? []]
|
|
16151
|
+
}
|
|
16152
|
+
} : {},
|
|
16153
|
+
run: () => command.onSelect?.()
|
|
16154
|
+
}))
|
|
16155
|
+
}));
|
|
16156
|
+
return null;
|
|
16157
|
+
}
|
|
16158
|
+
})
|
|
16159
|
+
)
|
|
16160
|
+
},
|
|
16161
|
+
lifecycle: { onDispose: (cleanup) => cleanups.push(cleanup) },
|
|
16162
|
+
state: {
|
|
16163
|
+
get provider() {
|
|
16164
|
+
const models = ctx.data.location.model.list(ctx.location) ?? [];
|
|
16165
|
+
return [...new Set(models.map((model) => model.providerID))].map((id) => ({
|
|
16166
|
+
id,
|
|
16167
|
+
models: Object.fromEntries(
|
|
16168
|
+
models.filter((model) => model.providerID === id).map((model) => [model.id, model])
|
|
16169
|
+
)
|
|
16170
|
+
}));
|
|
16171
|
+
}
|
|
16172
|
+
}
|
|
16173
|
+
};
|
|
16174
|
+
await tui(api);
|
|
16175
|
+
return async () => {
|
|
16176
|
+
for (const cleanup of cleanups.reverse()) await cleanup();
|
|
16177
|
+
};
|
|
16178
|
+
}
|
|
16018
16179
|
};
|
|
16019
16180
|
var tui_default = agentRouterTui;
|
|
16020
16181
|
export {
|