@dylanrussell/agent-router 2.0.0 → 2.0.2
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 +14 -1
- package/README.md +3 -3
- package/dist/cli.js +8 -3
- package/dist/cli.js.map +1 -1
- package/dist/plugin.js +29 -13
- package/dist/plugin.js.map +1 -1
- package/dist/tui.d.ts +38 -1
- package/dist/tui.js +56 -20
- package/dist/tui.js.map +1 -1
- package/package.json +9 -5
package/dist/tui.d.ts
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
import { Context } from '@opencode/plugin/tui/context';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Snapshot + polling layer for the TUI sidebar.
|
|
5
|
+
*
|
|
6
|
+
* Polling (not fs.watch): agent-router writes state.json atomically via
|
|
7
|
+
* temp-file + rename, and Bun's fs.watch drops the rename-to-target event
|
|
8
|
+
* (verified: Bun 1.3.14 reports only the temp file, Node reports both), so a
|
|
9
|
+
* watcher filtered on "state.json" never fires inside opencode. A 1.5s poll
|
|
10
|
+
* of two tiny reads is the reliable alternative.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
interface ModelAssignment {
|
|
14
|
+
readonly model: string;
|
|
15
|
+
readonly variant?: string | null | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Pure view model for the sidebar — no opentui imports, fully unit-testable.
|
|
20
|
+
* `materialize` in render.ts turns these nodes into real opentui elements.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
interface SidebarTheme {
|
|
24
|
+
readonly text?: unknown;
|
|
25
|
+
readonly textMuted?: unknown;
|
|
26
|
+
readonly warning?: unknown;
|
|
27
|
+
readonly success?: unknown;
|
|
28
|
+
}
|
|
29
|
+
interface SidebarContext {
|
|
30
|
+
/** Active stack when the TUI booted — differing means a restart is due. */
|
|
31
|
+
readonly bootActive: string | null;
|
|
32
|
+
readonly theme?: SidebarTheme | undefined;
|
|
33
|
+
/** Only the agent/model from this sidebar's live session may be marked current. */
|
|
34
|
+
readonly current?: (ModelAssignment & {
|
|
35
|
+
readonly agent: string;
|
|
36
|
+
}) | undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
3
39
|
/**
|
|
4
40
|
* Structural slice of opencode's TuiPluginApi.
|
|
5
41
|
*
|
|
@@ -54,7 +90,7 @@ interface RouterTuiApi {
|
|
|
54
90
|
readonly slots: {
|
|
55
91
|
register(plugin: {
|
|
56
92
|
order?: number;
|
|
57
|
-
slots: Record<string, () => unknown>;
|
|
93
|
+
slots: Record<string, (sessionID?: string) => unknown>;
|
|
58
94
|
}): unknown;
|
|
59
95
|
};
|
|
60
96
|
readonly renderer: {
|
|
@@ -78,6 +114,7 @@ interface RouterTuiApi {
|
|
|
78
114
|
readonly state?: {
|
|
79
115
|
readonly provider?: unknown;
|
|
80
116
|
};
|
|
117
|
+
readonly currentModel?: (sessionID: string) => SidebarContext["current"];
|
|
81
118
|
}
|
|
82
119
|
|
|
83
120
|
/**
|
package/dist/tui.js
CHANGED
|
@@ -15863,7 +15863,14 @@ function restartRequired(snapshot, ctx) {
|
|
|
15863
15863
|
return snapshot.active !== ctx.bootActive;
|
|
15864
15864
|
}
|
|
15865
15865
|
function modelLabel(assignment) {
|
|
15866
|
-
|
|
15866
|
+
const variant = normalizeVariant(assignment.variant);
|
|
15867
|
+
return `${assignment.model}${variant ? ` [${variant}]` : ""}`;
|
|
15868
|
+
}
|
|
15869
|
+
function normalizeVariant(variant) {
|
|
15870
|
+
return variant && variant !== "default" ? variant : void 0;
|
|
15871
|
+
}
|
|
15872
|
+
function sameModel(a, b) {
|
|
15873
|
+
return a.model === b.model && normalizeVariant(a.variant) === normalizeVariant(b.variant);
|
|
15867
15874
|
}
|
|
15868
15875
|
function buildSidebarNodes(snapshot, ctx) {
|
|
15869
15876
|
const theme = ctx.theme ?? {};
|
|
@@ -15880,32 +15887,38 @@ function buildSidebarNodes(snapshot, ctx) {
|
|
|
15880
15887
|
);
|
|
15881
15888
|
}
|
|
15882
15889
|
}
|
|
15883
|
-
nodes.push(
|
|
15890
|
+
nodes.push(
|
|
15891
|
+
text(`Current Stack \xB7 ${snapshot.active ?? "(none)"}`, {
|
|
15892
|
+
fg: theme.text,
|
|
15893
|
+
attributes: TEXT_ATTR_BOLD,
|
|
15894
|
+
marginTop: 1
|
|
15895
|
+
})
|
|
15896
|
+
);
|
|
15884
15897
|
if (snapshot.agents.length === 0) {
|
|
15885
15898
|
nodes.push(text("\u2022 (none)", { fg: theme.textMuted }));
|
|
15886
15899
|
} else {
|
|
15887
|
-
nodes.push(text("
|
|
15900
|
+
nodes.push(text("Precedence \u2193 \xB7 \u25CF current", { fg: theme.textMuted }));
|
|
15888
15901
|
for (const assignment of snapshot.agents) {
|
|
15902
|
+
const chain = [assignment, ...assignment.fallbacks ?? []];
|
|
15903
|
+
const current = ctx.current?.agent === assignment.agent ? ctx.current : void 0;
|
|
15904
|
+
const selected = current ? chain.findIndex((model) => sameModel(model, current)) : -1;
|
|
15905
|
+
const modelRow = (model, active, external = false) => text(`${active ? "\u25CF " : " "}${external ? "Current \xB7 " : ""}${modelLabel(model)}`, {
|
|
15906
|
+
fg: active ? theme.success : theme.textMuted,
|
|
15907
|
+
attributes: active ? TEXT_ATTR_BOLD : 0,
|
|
15908
|
+
wrapMode: "char",
|
|
15909
|
+
width: "100%"
|
|
15910
|
+
});
|
|
15889
15911
|
nodes.push({
|
|
15890
15912
|
kind: "box",
|
|
15891
|
-
props: { flexDirection: "column" },
|
|
15913
|
+
props: { flexDirection: "column", marginTop: 1 },
|
|
15892
15914
|
children: [
|
|
15893
|
-
{
|
|
15894
|
-
kind: "box",
|
|
15895
|
-
props: { flexDirection: "row", gap: 1 },
|
|
15896
|
-
children: [
|
|
15897
|
-
text("\u2022", { flexShrink: 0, style: { fg: theme.success } }),
|
|
15898
|
-
text(assignment.agent, { fg: theme.text, attributes: TEXT_ATTR_BOLD })
|
|
15899
|
-
]
|
|
15900
|
-
},
|
|
15915
|
+
text(assignment.agent, { fg: theme.text, attributes: TEXT_ATTR_BOLD }),
|
|
15901
15916
|
{
|
|
15902
15917
|
kind: "box",
|
|
15903
15918
|
props: { flexDirection: "column", paddingLeft: 2 },
|
|
15904
15919
|
children: [
|
|
15905
|
-
|
|
15906
|
-
...(
|
|
15907
|
-
(fallback, index) => text(`Fallback ${index + 1} \u2192 ${modelLabel(fallback)}`, { fg: theme.textMuted })
|
|
15908
|
-
)
|
|
15920
|
+
...chain.map((model, index) => modelRow(model, index === selected)),
|
|
15921
|
+
...current && selected === -1 ? [modelRow(current, true, true)] : []
|
|
15909
15922
|
]
|
|
15910
15923
|
}
|
|
15911
15924
|
]
|
|
@@ -16005,12 +16018,16 @@ var tui = async (api) => {
|
|
|
16005
16018
|
if (!paths) return;
|
|
16006
16019
|
let snapshot = await readStackSnapshot(paths);
|
|
16007
16020
|
const bootActive = snapshot.active;
|
|
16008
|
-
const viewContext = () => ({
|
|
16021
|
+
const viewContext = (sessionID) => ({
|
|
16022
|
+
bootActive,
|
|
16023
|
+
theme: api.theme?.current,
|
|
16024
|
+
current: sessionID ? api.currentModel?.(sessionID) : void 0
|
|
16025
|
+
});
|
|
16009
16026
|
try {
|
|
16010
16027
|
api.slots.register({
|
|
16011
16028
|
order: SIDEBAR_ORDER,
|
|
16012
16029
|
slots: {
|
|
16013
|
-
sidebar_content: () => materialize(buildSidebarNodes(snapshot, viewContext()), solid)
|
|
16030
|
+
sidebar_content: (sessionID) => materialize(buildSidebarNodes(snapshot, viewContext(sessionID)), solid)
|
|
16014
16031
|
}
|
|
16015
16032
|
});
|
|
16016
16033
|
debugLog("slots.register ok");
|
|
@@ -16074,12 +16091,12 @@ var agentRouterTui = {
|
|
|
16074
16091
|
cleanups.push(
|
|
16075
16092
|
ctx.ui.slot({
|
|
16076
16093
|
append: "sidebar.content",
|
|
16077
|
-
render: () => {
|
|
16094
|
+
render: (input) => {
|
|
16078
16095
|
const node = solid.createElement("box");
|
|
16079
16096
|
solid.setProp(node, "flexDirection", "column");
|
|
16080
16097
|
solid.insert(node, () => {
|
|
16081
16098
|
void revision.value;
|
|
16082
|
-
return render();
|
|
16099
|
+
return render(input.sessionID);
|
|
16083
16100
|
});
|
|
16084
16101
|
return node;
|
|
16085
16102
|
}
|
|
@@ -16159,6 +16176,25 @@ var agentRouterTui = {
|
|
|
16159
16176
|
)
|
|
16160
16177
|
},
|
|
16161
16178
|
lifecycle: { onDispose: (cleanup) => cleanups.push(cleanup) },
|
|
16179
|
+
theme: {
|
|
16180
|
+
get current() {
|
|
16181
|
+
return {
|
|
16182
|
+
text: ctx.theme.text.default,
|
|
16183
|
+
textMuted: ctx.theme.text.subdued,
|
|
16184
|
+
warning: ctx.theme.text.feedback.warning.default,
|
|
16185
|
+
success: ctx.theme.text.feedback.success.default
|
|
16186
|
+
};
|
|
16187
|
+
}
|
|
16188
|
+
},
|
|
16189
|
+
currentModel: (sessionID) => {
|
|
16190
|
+
const session = ctx.data.session.get(sessionID);
|
|
16191
|
+
if (!session?.agent || !session.model) return void 0;
|
|
16192
|
+
return {
|
|
16193
|
+
agent: session.agent,
|
|
16194
|
+
model: `${session.model.providerID}/${session.model.id}`,
|
|
16195
|
+
variant: session.model.variant ?? null
|
|
16196
|
+
};
|
|
16197
|
+
},
|
|
16162
16198
|
state: {
|
|
16163
16199
|
get provider() {
|
|
16164
16200
|
const models = ctx.data.location.model.list(ctx.location) ?? [];
|