@duckmind/dm-windows-x64 0.60.6 → 0.60.9
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/dm.exe +0 -0
- package/extensions/.dm-extensions.json +70 -123
- package/extensions/dm-9router-ext/src/index.js +410 -5
- package/extensions/dm-caveman/extensions/caveman.js +283 -12
- package/extensions/dm-cliproxy/index.js +182 -2
- package/extensions/dm-cliproxy/scripts/check-config-migration.js +66 -8
- package/extensions/dm-cliproxy/src/apply.js +228 -1
- package/extensions/dm-cliproxy/src/cache.js +42 -1
- package/extensions/dm-cliproxy/src/commands.js +50 -2
- package/extensions/dm-cliproxy/src/compat.js +81 -1
- package/extensions/dm-cliproxy/src/config.js +192 -2
- package/extensions/dm-cliproxy/src/conflicts.js +46 -1
- package/extensions/dm-cliproxy/src/fetch-models.js +190 -1
- package/extensions/dm-cliproxy/src/fetch-usage.js +41 -1
- package/extensions/dm-cliproxy/src/log.js +23 -1
- package/extensions/dm-cliproxy/src/status-quota.js +77 -1
- package/extensions/dm-cliproxy/src/ui-frame.js +50 -1
- package/extensions/dm-cliproxy/src/ui-hub/hub.js +199 -2
- package/extensions/dm-cliproxy/src/ui-hub/index.js +26 -2
- package/extensions/dm-cliproxy/src/ui-hub/shell.js +46 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-diagnostics.js +69 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-models.js +379 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-usage.js +106 -1
- package/extensions/dm-cliproxy/src/ui-picker/catalog.js +41 -1
- package/extensions/dm-cliproxy/src/ui-picker/mutate.js +115 -1
- package/extensions/dm-cliproxy/src/ui-picker/prompt-confirm.js +35 -1
- package/extensions/dm-cliproxy/src/ui-picker/prompt-name.js +62 -1
- package/extensions/dm-cliproxy/src/ui-picker/providers.js +45 -1
- package/extensions/dm-cliproxy/src/ui-picker/render-text.js +34 -1
- package/extensions/dm-cliproxy/src/ui-picker/rows.js +57 -1
- package/extensions/dm-cliproxy/src/ui-setup.js +260 -2
- package/extensions/dm-cliproxy/src/ui-usage.js +151 -1
- package/extensions/dm-cliproxy/src/usage-shared-cache.js +97 -1
- package/extensions/dm-context/src/context.js +144 -1
- package/extensions/dm-context/src/index.js +339 -7
- package/extensions/dm-context/src/utils.js +9 -1
- package/extensions/dm-cua/bin/browser-cua.mjs +73 -8
- package/extensions/dm-cua/index.js +75 -6
- package/extensions/dm-cua/src/browser-cua-lib.mjs +490 -6
- package/extensions/dm-cua/src/browser-install.mjs +331 -2
- package/extensions/dm-fff/src/index.js +688 -12
- package/extensions/dm-fff/src/query.js +60 -1
- package/extensions/dm-goal/src/goal.js +894 -23
- package/extensions/dm-image2/index.js +103 -9
- package/extensions/dm-image2/src/image-lib.mjs +1275 -8
- package/extensions/dm-subagents/install.mjs +62 -8
- package/extensions/dm-subagents/src/agents/agent-management.js +1190 -36
- package/extensions/dm-subagents/src/agents/agent-memory.js +216 -6
- package/extensions/dm-subagents/src/agents/agent-scope.js +5 -1
- package/extensions/dm-subagents/src/agents/agent-selection.js +20 -1
- package/extensions/dm-subagents/src/agents/agent-serializer.js +120 -5
- package/extensions/dm-subagents/src/agents/agents.js +1139 -11
- package/extensions/dm-subagents/src/agents/chain-serializer.js +299 -11
- package/extensions/dm-subagents/src/agents/frontmatter.js +65 -6
- package/extensions/dm-subagents/src/agents/identity.js +29 -1
- package/extensions/dm-subagents/src/agents/proactive-skills.js +141 -1
- package/extensions/dm-subagents/src/agents/skills.js +614 -6
- package/extensions/dm-subagents/src/extension/config.js +35 -2
- package/extensions/dm-subagents/src/extension/control-notices.js +69 -4
- package/extensions/dm-subagents/src/extension/doctor.js +172 -15
- package/extensions/dm-subagents/src/extension/fanout-child.js +158 -231
- package/extensions/dm-subagents/src/extension/index.js +521 -359
- package/extensions/dm-subagents/src/extension/rpc.js +266 -7
- package/extensions/dm-subagents/src/extension/schemas.js +275 -1
- package/extensions/dm-subagents/src/extension/tool-description.js +111 -6
- package/extensions/dm-subagents/src/intercom/intercom-bridge.js +126 -4
- package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +452 -5
- package/extensions/dm-subagents/src/intercom/result-intercom.js +319 -3
- package/extensions/dm-subagents/src/profiles/profiles.js +458 -3
- package/extensions/dm-subagents/src/runs/background/async-execution.js +834 -40
- package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +435 -14
- package/extensions/dm-subagents/src/runs/background/async-resume.js +334 -8
- package/extensions/dm-subagents/src/runs/background/async-status.js +313 -12
- package/extensions/dm-subagents/src/runs/background/chain-append.js +245 -2
- package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +136 -1
- package/extensions/dm-subagents/src/runs/background/completion-batcher.js +94 -1
- package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +54 -1
- package/extensions/dm-subagents/src/runs/background/control-channel.js +190 -1
- package/extensions/dm-subagents/src/runs/background/fleet-view.js +483 -17
- package/extensions/dm-subagents/src/runs/background/notify.js +129 -3
- package/extensions/dm-subagents/src/runs/background/parallel-groups.js +34 -1
- package/extensions/dm-subagents/src/runs/background/result-watcher.js +236 -6
- package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +76 -4
- package/extensions/dm-subagents/src/runs/background/run-status.js +427 -23
- package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +487 -4
- package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +306 -9
- package/extensions/dm-subagents/src/runs/background/subagent-runner.js +2849 -73
- package/extensions/dm-subagents/src/runs/background/top-level-async.js +5 -1
- package/extensions/dm-subagents/src/runs/background/wait.js +206 -11
- package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +1013 -12
- package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +980 -101
- package/extensions/dm-subagents/src/runs/foreground/execution.js +1165 -45
- package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +3157 -222
- package/extensions/dm-subagents/src/runs/shared/acceptance.js +835 -3
- package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +104 -1
- package/extensions/dm-subagents/src/runs/shared/completion-guard.js +116 -3
- package/extensions/dm-subagents/src/runs/shared/dm-args.js +208 -1
- package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +90 -1
- package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +282 -1
- package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +148 -1
- package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +305 -1
- package/extensions/dm-subagents/src/runs/shared/model-fallback.js +194 -1
- package/extensions/dm-subagents/src/runs/shared/model-scope.js +65 -1
- package/extensions/dm-subagents/src/runs/shared/nested-events.js +851 -8
- package/extensions/dm-subagents/src/runs/shared/nested-path.js +41 -1
- package/extensions/dm-subagents/src/runs/shared/nested-render.js +105 -1
- package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +81 -4
- package/extensions/dm-subagents/src/runs/shared/run-history.js +51 -4
- package/extensions/dm-subagents/src/runs/shared/single-output.js +149 -8
- package/extensions/dm-subagents/src/runs/shared/structured-output.js +58 -1
- package/extensions/dm-subagents/src/runs/shared/subagent-control.js +166 -5
- package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +329 -13
- package/extensions/dm-subagents/src/runs/shared/tool-budget.js +73 -1
- package/extensions/dm-subagents/src/runs/shared/turn-budget.js +47 -4
- package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +196 -1
- package/extensions/dm-subagents/src/runs/shared/worktree.js +435 -3
- package/extensions/dm-subagents/src/shared/artifacts.js +92 -2
- package/extensions/dm-subagents/src/shared/atomic-json.js +55 -1
- package/extensions/dm-subagents/src/shared/child-transcript.js +167 -5
- package/extensions/dm-subagents/src/shared/file-coalescer.js +25 -1
- package/extensions/dm-subagents/src/shared/fork-context.js +147 -4
- package/extensions/dm-subagents/src/shared/formatters.js +98 -7
- package/extensions/dm-subagents/src/shared/jsonl-writer.js +56 -2
- package/extensions/dm-subagents/src/shared/model-info.js +62 -1
- package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +68 -1
- package/extensions/dm-subagents/src/shared/session-identity.js +6 -1
- package/extensions/dm-subagents/src/shared/session-tokens.js +39 -2
- package/extensions/dm-subagents/src/shared/settings.js +198 -11
- package/extensions/dm-subagents/src/shared/status-format.js +53 -1
- package/extensions/dm-subagents/src/shared/types.js +184 -6
- package/extensions/dm-subagents/src/shared/utils.js +462 -2
- package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +288 -1
- package/extensions/dm-subagents/src/slash/prompt-workflows.js +297 -7
- package/extensions/dm-subagents/src/slash/slash-bridge.js +118 -1
- package/extensions/dm-subagents/src/slash/slash-commands.js +1287 -31
- package/extensions/dm-subagents/src/slash/slash-live-state.js +240 -4
- package/extensions/dm-subagents/src/tui/render-helpers.js +64 -1
- package/extensions/dm-subagents/src/tui/render.js +1542 -4
- package/extensions/dm-usage/index.js +1294 -9
- package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +40 -9
- package/extensions/greedysearch-dm/bin/cdp-headless.mjs +5 -2
- package/extensions/greedysearch-dm/bin/cdp-visible.mjs +5 -2
- package/extensions/greedysearch-dm/bin/cdp.mjs +896 -30
- package/extensions/greedysearch-dm/bin/gschrome.mjs +30 -2
- package/extensions/greedysearch-dm/bin/kill-visible.mjs +7 -2
- package/extensions/greedysearch-dm/bin/launch-visible.mjs +13 -2
- package/extensions/greedysearch-dm/bin/launch.mjs +282 -10
- package/extensions/greedysearch-dm/bin/mcp.mjs +386 -361
- package/extensions/greedysearch-dm/bin/search.mjs +620 -540
- package/extensions/greedysearch-dm/bin/visible.mjs +22 -2
- package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +329 -579
- package/extensions/greedysearch-dm/extractors/chatgpt.mjs +301 -583
- package/extensions/greedysearch-dm/extractors/common.mjs +408 -32
- package/extensions/greedysearch-dm/extractors/consensus.mjs +376 -365
- package/extensions/greedysearch-dm/extractors/consent.mjs +303 -14
- package/extensions/greedysearch-dm/extractors/gemini.mjs +228 -592
- package/extensions/greedysearch-dm/extractors/google-ai.mjs +78 -499
- package/extensions/greedysearch-dm/extractors/logically.mjs +270 -347
- package/extensions/greedysearch-dm/extractors/perplexity.mjs +243 -581
- package/extensions/greedysearch-dm/extractors/selectors.mjs +32 -1
- package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +130 -317
- package/extensions/greedysearch-dm/index.js +123 -23
- package/extensions/greedysearch-dm/src/fetcher.mjs +576 -2
- package/extensions/greedysearch-dm/src/formatters/results.js +95 -10
- package/extensions/greedysearch-dm/src/formatters/sources.js +57 -1
- package/extensions/greedysearch-dm/src/formatters/synthesis.js +49 -1
- package/extensions/greedysearch-dm/src/github.mjs +222 -7
- package/extensions/greedysearch-dm/src/reddit.mjs +145 -14
- package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +340 -9
- package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +112 -4
- package/extensions/greedysearch-dm/src/search/chrome.mjs +486 -285
- package/extensions/greedysearch-dm/src/search/constants.mjs +109 -8
- package/extensions/greedysearch-dm/src/search/defaults.mjs +10 -1
- package/extensions/greedysearch-dm/src/search/engines.mjs +79 -9
- package/extensions/greedysearch-dm/src/search/fetch-source.mjs +441 -349
- package/extensions/greedysearch-dm/src/search/file-sources.mjs +29 -7
- package/extensions/greedysearch-dm/src/search/minimize.mjs +86 -1
- package/extensions/greedysearch-dm/src/search/output.mjs +51 -5
- package/extensions/greedysearch-dm/src/search/paths.mjs +48 -1
- package/extensions/greedysearch-dm/src/search/pdf.mjs +63 -2
- package/extensions/greedysearch-dm/src/search/port-pid.mjs +69 -1
- package/extensions/greedysearch-dm/src/search/progress.mjs +109 -2
- package/extensions/greedysearch-dm/src/search/query.mjs +21 -1
- package/extensions/greedysearch-dm/src/search/recovery.mjs +49 -1
- package/extensions/greedysearch-dm/src/search/research.mjs +2227 -458
- package/extensions/greedysearch-dm/src/search/scale-aware.mjs +61 -11
- package/extensions/greedysearch-dm/src/search/simple-research.mjs +396 -805
- package/extensions/greedysearch-dm/src/search/sources.mjs +412 -1
- package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +127 -12
- package/extensions/greedysearch-dm/src/search/synthesis.mjs +202 -12
- package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +209 -23
- package/extensions/greedysearch-dm/src/tools/shared.js +226 -10
- package/extensions/greedysearch-dm/src/utils/content.mjs +35 -4
- package/extensions/greedysearch-dm/src/utils/helpers.js +22 -1
- package/extensions/greedysearch-dm/src/utils/node-runtime.mjs +10 -1
- package/extensions/greedysearch-dm/src/utils/system-cmds.mjs +61 -1
- package/package.json +1 -1
|
@@ -1,5 +1,410 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
chmodSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
renameSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
statSync,
|
|
10
|
+
writeFileSync
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { Type } from "typebox";
|
|
15
|
+
const DEFAULT_BASE_URL = "http://localhost:20128";
|
|
16
|
+
const ENV_BASE_URL = process.env.NINE_ROUTER_BASE_URL;
|
|
17
|
+
const ENV_API_KEY = process.env.NINE_ROUTER_API_KEY;
|
|
18
|
+
const CONFIG_PATH = join(homedir(), ".dm", "agent", "9router-config.json");
|
|
19
|
+
const CUSTOM_TYPE_CONFIG = "9router-config";
|
|
20
|
+
const CUSTOM_TYPE_LAST_ROUTE = "9router-last-route";
|
|
21
|
+
const ROUTING_HEADERS = [
|
|
22
|
+
"x-9router-model",
|
|
23
|
+
"x-routed-model",
|
|
24
|
+
"x-actual-model",
|
|
25
|
+
"x-upstream-model",
|
|
26
|
+
"x-provider-model"
|
|
27
|
+
];
|
|
28
|
+
function normalizeBaseUrl(url) {
|
|
29
|
+
return url.replace(/\/$/, "");
|
|
30
|
+
}
|
|
31
|
+
function applyEnvOverrides(config) {
|
|
32
|
+
return {
|
|
33
|
+
baseUrl: normalizeBaseUrl(ENV_BASE_URL || config.baseUrl),
|
|
34
|
+
apiKey: ENV_API_KEY || config.apiKey
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function loadConfigFromDisk() {
|
|
38
|
+
try {
|
|
39
|
+
if (!existsSync(CONFIG_PATH))
|
|
40
|
+
return null;
|
|
41
|
+
const data = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
42
|
+
if (typeof data.baseUrl !== "string" || !data.baseUrl.trim())
|
|
43
|
+
return null;
|
|
44
|
+
return {
|
|
45
|
+
baseUrl: normalizeBaseUrl(data.baseUrl.trim()),
|
|
46
|
+
apiKey: typeof data.apiKey === "string" && data.apiKey.trim() ? data.apiKey.trim() : undefined
|
|
47
|
+
};
|
|
48
|
+
} catch {
|
|
49
|
+
console.error("[dm-9router-ext] Failed to load persisted config");
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function serializeConfig(config) {
|
|
54
|
+
const persisted = {
|
|
55
|
+
baseUrl: config.baseUrl
|
|
56
|
+
};
|
|
57
|
+
if (config.apiKey)
|
|
58
|
+
persisted.apiKey = config.apiKey;
|
|
59
|
+
return Buffer.from(`${JSON.stringify(persisted, null, 2)}
|
|
60
|
+
`);
|
|
61
|
+
}
|
|
62
|
+
function captureDiskConfigSnapshot() {
|
|
63
|
+
if (!existsSync(CONFIG_PATH))
|
|
64
|
+
return { existed: false };
|
|
65
|
+
const stat = statSync(CONFIG_PATH);
|
|
66
|
+
return {
|
|
67
|
+
existed: true,
|
|
68
|
+
contents: readFileSync(CONFIG_PATH),
|
|
69
|
+
mode: stat.mode & 511
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function writeConfigTemp(contents, mode = 384) {
|
|
73
|
+
const tempPath = `${CONFIG_PATH}.tmp-${process.pid}-${randomUUID()}`;
|
|
74
|
+
try {
|
|
75
|
+
mkdirSync(dirname(CONFIG_PATH), { recursive: true, mode: 448 });
|
|
76
|
+
writeFileSync(tempPath, contents, { flag: "wx", mode });
|
|
77
|
+
chmodSync(tempPath, mode);
|
|
78
|
+
return tempPath;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
rmSync(tempPath, { force: true });
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function prepareConfigWrite(config) {
|
|
85
|
+
const snapshot = captureDiskConfigSnapshot();
|
|
86
|
+
return {
|
|
87
|
+
tempPath: writeConfigTemp(serializeConfig(config)),
|
|
88
|
+
snapshot
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function commitPreparedConfig(prepared) {
|
|
92
|
+
renameSync(prepared.tempPath, CONFIG_PATH);
|
|
93
|
+
chmodSync(CONFIG_PATH, 384);
|
|
94
|
+
}
|
|
95
|
+
function restoreDiskSnapshot(snapshot) {
|
|
96
|
+
if (!snapshot.existed) {
|
|
97
|
+
rmSync(CONFIG_PATH, { force: true });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const tempPath = writeConfigTemp(snapshot.contents ?? Buffer.alloc(0), snapshot.mode ?? 384);
|
|
101
|
+
renameSync(tempPath, CONFIG_PATH);
|
|
102
|
+
chmodSync(CONFIG_PATH, snapshot.mode ?? 384);
|
|
103
|
+
}
|
|
104
|
+
function loadConfigFromSession(ctx) {
|
|
105
|
+
const sessionManager = ctx.sessionManager;
|
|
106
|
+
const entries = typeof sessionManager.getBranch === "function" ? sessionManager.getBranch() : sessionManager.getEntries();
|
|
107
|
+
for (let index = entries.length - 1;index >= 0; index -= 1) {
|
|
108
|
+
const entry = entries[index];
|
|
109
|
+
if (entry.type !== "custom" || entry.customType !== CUSTOM_TYPE_CONFIG)
|
|
110
|
+
continue;
|
|
111
|
+
const data = entry.data;
|
|
112
|
+
if (typeof data?.baseUrl !== "string" || !data.baseUrl.trim())
|
|
113
|
+
continue;
|
|
114
|
+
return {
|
|
115
|
+
baseUrl: normalizeBaseUrl(data.baseUrl.trim()),
|
|
116
|
+
apiKey: undefined
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
export async function fetchModels(config, signal) {
|
|
122
|
+
const headers = { Accept: "application/json" };
|
|
123
|
+
if (config.apiKey)
|
|
124
|
+
headers.Authorization = `Bearer ${config.apiKey}`;
|
|
125
|
+
const response = await fetch(`${config.baseUrl}/v1/models`, {
|
|
126
|
+
method: "GET",
|
|
127
|
+
headers,
|
|
128
|
+
signal
|
|
129
|
+
});
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
throw new Error(`9router returned ${response.status}: ${response.statusText}`);
|
|
132
|
+
}
|
|
133
|
+
let payload;
|
|
134
|
+
try {
|
|
135
|
+
payload = await response.json();
|
|
136
|
+
} catch {
|
|
137
|
+
throw new Error("9router returned invalid model metadata");
|
|
138
|
+
}
|
|
139
|
+
if (typeof payload !== "object" || payload === null || !Array.isArray(payload.data) || payload.data.some((model) => typeof model !== "object" || model === null || typeof model.id !== "string" || !model.id.trim())) {
|
|
140
|
+
throw new Error("9router returned invalid model metadata");
|
|
141
|
+
}
|
|
142
|
+
return payload.data;
|
|
143
|
+
}
|
|
144
|
+
async function testConnection(config, signal) {
|
|
145
|
+
try {
|
|
146
|
+
await fetchModels(config, signal);
|
|
147
|
+
return { ok: true };
|
|
148
|
+
} catch (error) {
|
|
149
|
+
return {
|
|
150
|
+
ok: false,
|
|
151
|
+
error: error instanceof Error ? error.message : String(error)
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function mapNineRouterModel(model) {
|
|
156
|
+
return {
|
|
157
|
+
id: model.id,
|
|
158
|
+
name: model.owned_by === "combo" ? `\uD83D\uDD00 ${model.id}` : model.id,
|
|
159
|
+
reasoning: false,
|
|
160
|
+
input: ["text"],
|
|
161
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
162
|
+
contextWindow: 128000,
|
|
163
|
+
maxTokens: 4096,
|
|
164
|
+
compat: {
|
|
165
|
+
supportsDeveloperRole: false,
|
|
166
|
+
supportsReasoningEffort: false
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function registerNineRouterProvider(dm, config, models) {
|
|
171
|
+
dm.registerProvider("9router", {
|
|
172
|
+
name: "9router",
|
|
173
|
+
baseUrl: `${config.baseUrl}/v1`,
|
|
174
|
+
apiKey: config.apiKey || "9router-no-api-key",
|
|
175
|
+
api: "openai-completions",
|
|
176
|
+
models: models.map(mapNineRouterModel)
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
function unregisterNineRouterProvider(dm) {
|
|
180
|
+
dm.unregisterProvider("9router");
|
|
181
|
+
}
|
|
182
|
+
export default async function dm9router(dm) {
|
|
183
|
+
const diskConfig = loadConfigFromDisk();
|
|
184
|
+
let persistedConfig = diskConfig || {
|
|
185
|
+
baseUrl: DEFAULT_BASE_URL,
|
|
186
|
+
apiKey: undefined
|
|
187
|
+
};
|
|
188
|
+
let hasPersistedConfig = diskConfig !== null;
|
|
189
|
+
let config = applyEnvOverrides(persistedConfig);
|
|
190
|
+
let discoveredModels = [];
|
|
191
|
+
let lastRoutedModel;
|
|
192
|
+
let activeProvider;
|
|
193
|
+
let isConnected = false;
|
|
194
|
+
const snapshotRuntime = () => ({
|
|
195
|
+
persistedConfig,
|
|
196
|
+
hasPersistedConfig,
|
|
197
|
+
config,
|
|
198
|
+
discoveredModels,
|
|
199
|
+
isConnected
|
|
200
|
+
});
|
|
201
|
+
const restoreProviderSnapshot = (snapshot) => {
|
|
202
|
+
try {
|
|
203
|
+
if (snapshot.isConnected) {
|
|
204
|
+
registerNineRouterProvider(dm, snapshot.config, snapshot.discoveredModels);
|
|
205
|
+
} else {
|
|
206
|
+
unregisterNineRouterProvider(dm);
|
|
207
|
+
}
|
|
208
|
+
} catch {
|
|
209
|
+
console.error("[dm-9router-ext] Failed to restore the previous provider snapshot");
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
const commitConfigTransaction = (nextPersistedConfig, nextConfig, nextModels, appendSessionEntry) => {
|
|
213
|
+
const previous = snapshotRuntime();
|
|
214
|
+
let prepared;
|
|
215
|
+
let providerTouched = false;
|
|
216
|
+
let diskTouched = false;
|
|
217
|
+
try {
|
|
218
|
+
prepared = prepareConfigWrite(nextPersistedConfig);
|
|
219
|
+
providerTouched = true;
|
|
220
|
+
registerNineRouterProvider(dm, nextConfig, nextModels);
|
|
221
|
+
diskTouched = true;
|
|
222
|
+
commitPreparedConfig(prepared);
|
|
223
|
+
if (appendSessionEntry) {
|
|
224
|
+
dm.appendEntry(CUSTOM_TYPE_CONFIG, {
|
|
225
|
+
baseUrl: nextPersistedConfig.baseUrl
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
persistedConfig = nextPersistedConfig;
|
|
229
|
+
hasPersistedConfig = true;
|
|
230
|
+
config = nextConfig;
|
|
231
|
+
discoveredModels = nextModels;
|
|
232
|
+
isConnected = true;
|
|
233
|
+
} catch {
|
|
234
|
+
if (prepared)
|
|
235
|
+
rmSync(prepared.tempPath, { force: true });
|
|
236
|
+
if (diskTouched && prepared) {
|
|
237
|
+
try {
|
|
238
|
+
restoreDiskSnapshot(prepared.snapshot);
|
|
239
|
+
} catch {
|
|
240
|
+
console.error("[dm-9router-ext] Failed to restore the previous config file");
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (providerTouched)
|
|
244
|
+
restoreProviderSnapshot(previous);
|
|
245
|
+
throw new Error("9router configuration update failed; previous configuration remains active");
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
try {
|
|
249
|
+
discoveredModels = await fetchModels(config, AbortSignal.timeout(1500));
|
|
250
|
+
isConnected = true;
|
|
251
|
+
registerNineRouterProvider(dm, config, discoveredModels);
|
|
252
|
+
} catch {
|
|
253
|
+
unregisterNineRouterProvider(dm);
|
|
254
|
+
}
|
|
255
|
+
dm.on("session_start", async (_event, ctx) => {
|
|
256
|
+
const restored = loadConfigFromSession(ctx);
|
|
257
|
+
if (!hasPersistedConfig && restored) {
|
|
258
|
+
const restoredConfig = applyEnvOverrides(restored);
|
|
259
|
+
try {
|
|
260
|
+
const nextModels = await fetchModels(restoredConfig, ctx.signal);
|
|
261
|
+
commitConfigTransaction(restored, restoredConfig, nextModels, false);
|
|
262
|
+
} catch {}
|
|
263
|
+
}
|
|
264
|
+
if (isConnected && discoveredModels.length > 0) {
|
|
265
|
+
ctx.ui.notify(`9router connected — ${discoveredModels.length} models available`, "info");
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
dm.on("after_provider_response", (event) => {
|
|
269
|
+
if (event.status >= 400 || activeProvider !== "9router")
|
|
270
|
+
return;
|
|
271
|
+
for (const header of ROUTING_HEADERS) {
|
|
272
|
+
const value = event.headers[header];
|
|
273
|
+
if (!value || typeof value !== "string")
|
|
274
|
+
continue;
|
|
275
|
+
lastRoutedModel = value;
|
|
276
|
+
dm.appendEntry(CUSTOM_TYPE_LAST_ROUTE, {
|
|
277
|
+
model: value,
|
|
278
|
+
timestamp: Date.now()
|
|
279
|
+
});
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
dm.on("model_select", async (event) => {
|
|
284
|
+
activeProvider = event.model.provider;
|
|
285
|
+
if (activeProvider !== "9router")
|
|
286
|
+
lastRoutedModel = undefined;
|
|
287
|
+
});
|
|
288
|
+
dm.registerCommand("9router-status", {
|
|
289
|
+
description: "Show 9router connection status and configuration",
|
|
290
|
+
handler: async (_args, ctx) => {
|
|
291
|
+
const test = await testConnection(config, ctx.signal);
|
|
292
|
+
const lines = [
|
|
293
|
+
"\uD83D\uDD17 9router Status",
|
|
294
|
+
"",
|
|
295
|
+
`Base URL: ${config.baseUrl}`,
|
|
296
|
+
`API Key: ${config.apiKey ? "set" : "not set"}`,
|
|
297
|
+
`Connection: ${test.ok ? "\uD83D\uDFE2 connected" : `\uD83D\uDD34 ${test.error || "disconnected"}`}`,
|
|
298
|
+
`Models: ${discoveredModels.length} available`
|
|
299
|
+
];
|
|
300
|
+
if (lastRoutedModel)
|
|
301
|
+
lines.push(`Last routed: ${lastRoutedModel}`);
|
|
302
|
+
ctx.ui.notify(lines.join(`
|
|
303
|
+
`), test.ok ? "info" : "warning");
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
dm.registerCommand("9router-models", {
|
|
307
|
+
description: "Browse 9router available models and combos",
|
|
308
|
+
handler: async (_args, ctx) => {
|
|
309
|
+
if (discoveredModels.length === 0) {
|
|
310
|
+
ctx.ui.notify("No 9router models discovered. Check connection with /9router-status", "warning");
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const items = discoveredModels.map((model) => ({
|
|
314
|
+
value: model.id,
|
|
315
|
+
label: model.owned_by === "combo" ? `\uD83D\uDD00 ${model.id}` : model.id
|
|
316
|
+
}));
|
|
317
|
+
const selected = await ctx.ui.select("Select a 9router model to use:", items.map((item) => item.label));
|
|
318
|
+
const modelId = items.find((item) => item.label === selected)?.value;
|
|
319
|
+
if (!modelId)
|
|
320
|
+
return;
|
|
321
|
+
dm.sendUserMessage(`/model 9router/${modelId}`, { deliverAs: "followUp" });
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
dm.registerCommand("9router-config", {
|
|
325
|
+
description: "Configure 9router base URL and API key",
|
|
326
|
+
handler: async (_args, ctx) => {
|
|
327
|
+
const test = await testConnection(config, ctx.signal);
|
|
328
|
+
ctx.ui.notify([
|
|
329
|
+
"Current config:",
|
|
330
|
+
` Base URL: ${config.baseUrl}`,
|
|
331
|
+
` API Key: ${config.apiKey ? "set" : "not set"}`,
|
|
332
|
+
` Status: ${test.ok ? "\uD83D\uDFE2 connected" : "\uD83D\uDD34 disconnected"}`,
|
|
333
|
+
"",
|
|
334
|
+
"Enter new values. Blank keeps the current value."
|
|
335
|
+
].join(`
|
|
336
|
+
`), "info");
|
|
337
|
+
const newBaseUrl = await ctx.ui.input("Base URL", config.baseUrl);
|
|
338
|
+
if (newBaseUrl === undefined)
|
|
339
|
+
return;
|
|
340
|
+
const newApiKey = await ctx.ui.input("API key (blank keeps current, '-' clears):", "");
|
|
341
|
+
if (newApiKey === undefined)
|
|
342
|
+
return;
|
|
343
|
+
const nextPersistedConfig = {
|
|
344
|
+
baseUrl: normalizeBaseUrl(newBaseUrl.trim() || persistedConfig.baseUrl),
|
|
345
|
+
apiKey: newApiKey.trim() === "" ? persistedConfig.apiKey : newApiKey.trim() === "-" ? undefined : newApiKey.trim()
|
|
346
|
+
};
|
|
347
|
+
const nextConfig = applyEnvOverrides(nextPersistedConfig);
|
|
348
|
+
try {
|
|
349
|
+
const nextModels = await fetchModels(nextConfig, ctx.signal);
|
|
350
|
+
commitConfigTransaction(nextPersistedConfig, nextConfig, nextModels, true);
|
|
351
|
+
ctx.ui.notify(`9router updated — ${discoveredModels.length} models at ${config.baseUrl}`, "info");
|
|
352
|
+
} catch {
|
|
353
|
+
ctx.ui.notify("9router update failed; the previous configuration remains active", "error");
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
dm.registerCommand("9router-reload", {
|
|
358
|
+
description: "Reload models from 9router",
|
|
359
|
+
handler: async (_args, ctx) => {
|
|
360
|
+
const previous = snapshotRuntime();
|
|
361
|
+
let providerTouched = false;
|
|
362
|
+
try {
|
|
363
|
+
const nextModels = await fetchModels(config, ctx.signal);
|
|
364
|
+
providerTouched = true;
|
|
365
|
+
registerNineRouterProvider(dm, config, nextModels);
|
|
366
|
+
discoveredModels = nextModels;
|
|
367
|
+
isConnected = true;
|
|
368
|
+
ctx.ui.notify(`9router reloaded — ${discoveredModels.length} models`, "info");
|
|
369
|
+
} catch {
|
|
370
|
+
if (providerTouched)
|
|
371
|
+
restoreProviderSnapshot(previous);
|
|
372
|
+
ctx.ui.notify("9router reload failed; the previous provider and model list remain active", "error");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
dm.registerTool({
|
|
377
|
+
name: "ninerouter_status",
|
|
378
|
+
label: "9router Status",
|
|
379
|
+
description: "Check 9router connection status and list available models",
|
|
380
|
+
parameters: Type.Object({}),
|
|
381
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
382
|
+
const test = await testConnection(config, ctx.signal);
|
|
383
|
+
const combos = discoveredModels.filter((model) => model.owned_by === "combo");
|
|
384
|
+
return {
|
|
385
|
+
content: [
|
|
386
|
+
{
|
|
387
|
+
type: "text",
|
|
388
|
+
text: [
|
|
389
|
+
`9router: ${test.ok ? "connected" : `disconnected (${test.error})`}`,
|
|
390
|
+
`Base URL: ${config.baseUrl}`,
|
|
391
|
+
`Total models: ${discoveredModels.length}`,
|
|
392
|
+
` Regular: ${discoveredModels.length - combos.length}`,
|
|
393
|
+
` Combos: ${combos.length}`,
|
|
394
|
+
lastRoutedModel ? `Last routed model: ${lastRoutedModel}` : ""
|
|
395
|
+
].filter(Boolean).join(`
|
|
396
|
+
`)
|
|
397
|
+
}
|
|
398
|
+
],
|
|
399
|
+
details: {
|
|
400
|
+
connected: test.ok,
|
|
401
|
+
baseUrl: config.baseUrl,
|
|
402
|
+
modelCount: discoveredModels.length,
|
|
403
|
+
comboCount: combos.length,
|
|
404
|
+
lastRoutedModel,
|
|
405
|
+
models: discoveredModels.map((model) => model.id)
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
}
|