@duckmind/dm-darwin-x64 0.43.4 → 0.48.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 (254) hide show
  1. package/dm +0 -0
  2. package/extensions/.dm-extensions.json +242 -50
  3. package/extensions/dm-9router-ext/package.json +2 -19
  4. package/extensions/dm-9router-ext/src/index.js +5 -0
  5. package/extensions/dm-ask-user/index.js +10 -0
  6. package/extensions/dm-ask-user/package.json +1 -21
  7. package/extensions/dm-cli-anything/package.json +0 -6
  8. package/extensions/dm-context/package.json +2 -12
  9. package/extensions/dm-context/src/context.js +1 -0
  10. package/extensions/dm-context/src/index.js +7 -0
  11. package/extensions/dm-context/src/utils.js +1 -0
  12. package/extensions/dm-cua/package.json +0 -7
  13. package/extensions/dm-fff/package.json +1 -28
  14. package/extensions/dm-fff/src/index.js +15 -0
  15. package/extensions/dm-fff/src/query.js +1 -0
  16. package/extensions/dm-goal/package.json +2 -31
  17. package/extensions/dm-goal/src/goal.js +43 -0
  18. package/extensions/dm-grill-me/index.js +174 -0
  19. package/extensions/dm-grill-me/package.json +1 -26
  20. package/extensions/dm-image2/package.json +0 -7
  21. package/extensions/dm-subagents/package.json +1 -32
  22. package/extensions/dm-subagents/src/agents/agent-management.js +37 -0
  23. package/extensions/dm-subagents/src/agents/agent-memory.js +6 -0
  24. package/extensions/dm-subagents/src/agents/agent-scope.js +1 -0
  25. package/extensions/dm-subagents/src/agents/agent-selection.js +1 -0
  26. package/extensions/dm-subagents/src/agents/agent-serializer.js +7 -0
  27. package/extensions/dm-subagents/src/agents/agents.js +11 -0
  28. package/extensions/dm-subagents/src/agents/chain-serializer.js +12 -0
  29. package/extensions/dm-subagents/src/agents/frontmatter.js +6 -0
  30. package/extensions/dm-subagents/src/agents/identity.js +1 -0
  31. package/extensions/dm-subagents/src/agents/proactive-skills.js +1 -0
  32. package/extensions/dm-subagents/src/agents/skills.js +6 -0
  33. package/extensions/dm-subagents/src/extension/config.js +2 -0
  34. package/extensions/dm-subagents/src/extension/control-notices.js +4 -0
  35. package/extensions/dm-subagents/src/extension/doctor.js +16 -0
  36. package/extensions/dm-subagents/src/extension/fanout-child.js +231 -0
  37. package/extensions/dm-subagents/src/extension/index.js +367 -0
  38. package/extensions/dm-subagents/src/extension/rpc.js +8 -0
  39. package/extensions/dm-subagents/src/extension/schemas.js +1 -0
  40. package/extensions/dm-subagents/src/extension/{tool-description.ts → tool-description.js} +6 -126
  41. package/extensions/dm-subagents/src/intercom/intercom-bridge.js +13 -0
  42. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +5 -0
  43. package/extensions/dm-subagents/src/intercom/result-intercom.js +3 -0
  44. package/extensions/dm-subagents/src/profiles/profiles.js +3 -0
  45. package/extensions/dm-subagents/src/runs/background/async-execution.js +46 -0
  46. package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +14 -0
  47. package/extensions/dm-subagents/src/runs/background/async-resume.js +8 -0
  48. package/extensions/dm-subagents/src/runs/background/async-status.js +12 -0
  49. package/extensions/dm-subagents/src/runs/background/chain-append.js +2 -0
  50. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +1 -0
  51. package/extensions/dm-subagents/src/runs/background/completion-batcher.js +1 -0
  52. package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +1 -0
  53. package/extensions/dm-subagents/src/runs/background/control-channel.js +1 -0
  54. package/extensions/dm-subagents/src/runs/background/fleet-view.js +17 -0
  55. package/extensions/dm-subagents/src/runs/background/notify.js +3 -0
  56. package/extensions/dm-subagents/src/runs/background/parallel-groups.js +1 -0
  57. package/extensions/dm-subagents/src/runs/background/result-watcher.js +8 -0
  58. package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +4 -0
  59. package/extensions/dm-subagents/src/runs/background/run-status.js +23 -0
  60. package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +4 -0
  61. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +11 -0
  62. package/extensions/dm-subagents/src/runs/background/subagent-runner.js +78 -0
  63. package/extensions/dm-subagents/src/runs/background/top-level-async.js +1 -0
  64. package/extensions/dm-subagents/src/runs/background/wait.js +11 -0
  65. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +12 -0
  66. package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +102 -0
  67. package/extensions/dm-subagents/src/runs/foreground/execution.js +51 -0
  68. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +228 -0
  69. package/extensions/dm-subagents/src/runs/shared/acceptance.js +3 -0
  70. package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +1 -0
  71. package/extensions/dm-subagents/src/runs/shared/completion-guard.js +3 -0
  72. package/extensions/dm-subagents/src/runs/shared/dm-args.js +1 -0
  73. package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +1 -0
  74. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +1 -0
  75. package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +1 -0
  76. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +1 -0
  77. package/extensions/dm-subagents/src/runs/shared/model-fallback.js +1 -0
  78. package/extensions/dm-subagents/src/runs/shared/model-scope.js +1 -0
  79. package/extensions/dm-subagents/src/runs/shared/nested-events.js +8 -0
  80. package/extensions/dm-subagents/src/runs/shared/nested-path.js +1 -0
  81. package/extensions/dm-subagents/src/runs/shared/nested-render.js +1 -0
  82. package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +5 -0
  83. package/extensions/dm-subagents/src/runs/shared/run-history.js +5 -0
  84. package/extensions/dm-subagents/src/runs/shared/single-output.js +14 -0
  85. package/extensions/dm-subagents/src/runs/shared/structured-output.js +1 -0
  86. package/extensions/dm-subagents/src/runs/shared/subagent-control.js +5 -0
  87. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +22 -0
  88. package/extensions/dm-subagents/src/runs/shared/tool-budget.js +1 -0
  89. package/extensions/dm-subagents/src/runs/shared/turn-budget.js +7 -0
  90. package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +1 -0
  91. package/extensions/dm-subagents/src/runs/shared/worktree.js +3 -0
  92. package/extensions/dm-subagents/src/shared/artifacts.js +2 -0
  93. package/extensions/dm-subagents/src/shared/atomic-json.js +1 -0
  94. package/extensions/dm-subagents/src/shared/child-transcript.js +5 -0
  95. package/extensions/dm-subagents/src/shared/file-coalescer.js +1 -0
  96. package/extensions/dm-subagents/src/shared/fork-context.js +6 -0
  97. package/extensions/dm-subagents/src/shared/formatters.js +9 -0
  98. package/extensions/dm-subagents/src/shared/jsonl-writer.js +2 -0
  99. package/extensions/dm-subagents/src/shared/model-info.js +1 -0
  100. package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +1 -0
  101. package/extensions/dm-subagents/src/shared/session-identity.js +1 -0
  102. package/extensions/dm-subagents/src/shared/session-tokens.js +2 -0
  103. package/extensions/dm-subagents/src/shared/settings.js +23 -0
  104. package/extensions/dm-subagents/src/shared/status-format.js +1 -0
  105. package/extensions/dm-subagents/src/shared/types.js +8 -0
  106. package/extensions/dm-subagents/src/shared/utils.js +2 -0
  107. package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +1 -0
  108. package/extensions/dm-subagents/src/slash/prompt-workflows.js +7 -0
  109. package/extensions/dm-subagents/src/slash/slash-bridge.js +1 -0
  110. package/extensions/dm-subagents/src/slash/slash-commands.js +38 -0
  111. package/extensions/dm-subagents/src/slash/slash-live-state.js +6 -0
  112. package/extensions/dm-subagents/src/tui/render-helpers.js +1 -0
  113. package/extensions/dm-subagents/src/tui/render.js +4 -0
  114. package/extensions/dm-tasks/package.json +1 -30
  115. package/extensions/dm-tasks/src/auto-clear.js +1 -0
  116. package/extensions/dm-tasks/src/index.js +212 -0
  117. package/extensions/dm-tasks/src/process-tracker.js +1 -0
  118. package/extensions/dm-tasks/src/task-store.js +1 -0
  119. package/extensions/dm-tasks/src/tasks-config.js +1 -0
  120. package/extensions/dm-tasks/src/types.js +0 -0
  121. package/extensions/dm-tasks/src/ui/settings-menu.js +1 -0
  122. package/extensions/dm-tasks/src/ui/task-widget.js +1 -0
  123. package/extensions/dm-usage/index.js +9 -0
  124. package/extensions/dm-usage/package.json +1 -18
  125. package/extensions/greedysearch-dm/bin/search.mjs +51 -51
  126. package/extensions/greedysearch-dm/index.js +20 -0
  127. package/extensions/greedysearch-dm/package.json +1 -22
  128. package/extensions/greedysearch-dm/src/formatters/results.js +8 -0
  129. package/extensions/greedysearch-dm/src/formatters/sources.js +1 -0
  130. package/extensions/greedysearch-dm/src/formatters/synthesis.js +1 -0
  131. package/extensions/greedysearch-dm/src/search/research.mjs +59 -59
  132. package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +10 -10
  133. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +20 -0
  134. package/extensions/greedysearch-dm/src/tools/shared.js +9 -0
  135. package/extensions/greedysearch-dm/src/types.js +0 -0
  136. package/extensions/greedysearch-dm/src/utils/helpers.js +1 -0
  137. package/package.json +1 -1
  138. package/extensions/dm-9router-ext/src/index.ts +0 -541
  139. package/extensions/dm-ask-user/index.ts +0 -857
  140. package/extensions/dm-context/src/context.ts +0 -158
  141. package/extensions/dm-context/src/index.ts +0 -439
  142. package/extensions/dm-context/src/utils.ts +0 -6
  143. package/extensions/dm-fff/src/index.ts +0 -1023
  144. package/extensions/dm-fff/src/query.ts +0 -87
  145. package/extensions/dm-goal/src/goal.ts +0 -1073
  146. package/extensions/dm-grill-me/index.ts +0 -1085
  147. package/extensions/dm-subagents/src/agents/agent-management.ts +0 -1052
  148. package/extensions/dm-subagents/src/agents/agent-memory.ts +0 -254
  149. package/extensions/dm-subagents/src/agents/agent-scope.ts +0 -6
  150. package/extensions/dm-subagents/src/agents/agent-selection.ts +0 -25
  151. package/extensions/dm-subagents/src/agents/agent-serializer.ts +0 -121
  152. package/extensions/dm-subagents/src/agents/agents.ts +0 -1553
  153. package/extensions/dm-subagents/src/agents/chain-serializer.ts +0 -277
  154. package/extensions/dm-subagents/src/agents/frontmatter.ts +0 -93
  155. package/extensions/dm-subagents/src/agents/identity.ts +0 -30
  156. package/extensions/dm-subagents/src/agents/proactive-skills.ts +0 -191
  157. package/extensions/dm-subagents/src/agents/skills.ts +0 -729
  158. package/extensions/dm-subagents/src/extension/config.ts +0 -39
  159. package/extensions/dm-subagents/src/extension/control-notices.ts +0 -92
  160. package/extensions/dm-subagents/src/extension/doctor.ts +0 -214
  161. package/extensions/dm-subagents/src/extension/fanout-child.ts +0 -172
  162. package/extensions/dm-subagents/src/extension/index.ts +0 -656
  163. package/extensions/dm-subagents/src/extension/rpc.ts +0 -369
  164. package/extensions/dm-subagents/src/extension/schemas.ts +0 -309
  165. package/extensions/dm-subagents/src/intercom/intercom-bridge.ts +0 -180
  166. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.ts +0 -519
  167. package/extensions/dm-subagents/src/intercom/result-intercom.ts +0 -377
  168. package/extensions/dm-subagents/src/profiles/profiles.ts +0 -637
  169. package/extensions/dm-subagents/src/runs/background/async-execution.ts +0 -1065
  170. package/extensions/dm-subagents/src/runs/background/async-job-tracker.ts +0 -441
  171. package/extensions/dm-subagents/src/runs/background/async-resume.ts +0 -391
  172. package/extensions/dm-subagents/src/runs/background/async-status.ts +0 -395
  173. package/extensions/dm-subagents/src/runs/background/chain-append.ts +0 -282
  174. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.ts +0 -191
  175. package/extensions/dm-subagents/src/runs/background/completion-batcher.ts +0 -166
  176. package/extensions/dm-subagents/src/runs/background/completion-dedupe.ts +0 -63
  177. package/extensions/dm-subagents/src/runs/background/control-channel.ts +0 -332
  178. package/extensions/dm-subagents/src/runs/background/fleet-view.ts +0 -515
  179. package/extensions/dm-subagents/src/runs/background/notify.ts +0 -225
  180. package/extensions/dm-subagents/src/runs/background/parallel-groups.ts +0 -45
  181. package/extensions/dm-subagents/src/runs/background/result-watcher.ts +0 -315
  182. package/extensions/dm-subagents/src/runs/background/run-id-resolver.ts +0 -84
  183. package/extensions/dm-subagents/src/runs/background/run-status.ts +0 -434
  184. package/extensions/dm-subagents/src/runs/background/scheduled-runs.ts +0 -514
  185. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.ts +0 -368
  186. package/extensions/dm-subagents/src/runs/background/subagent-runner.ts +0 -3171
  187. package/extensions/dm-subagents/src/runs/background/top-level-async.ts +0 -13
  188. package/extensions/dm-subagents/src/runs/background/wait.ts +0 -353
  189. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.ts +0 -1333
  190. package/extensions/dm-subagents/src/runs/foreground/chain-execution.ts +0 -1313
  191. package/extensions/dm-subagents/src/runs/foreground/execution.ts +0 -1239
  192. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.ts +0 -3613
  193. package/extensions/dm-subagents/src/runs/shared/acceptance.ts +0 -879
  194. package/extensions/dm-subagents/src/runs/shared/chain-outputs.ts +0 -116
  195. package/extensions/dm-subagents/src/runs/shared/completion-guard.ts +0 -147
  196. package/extensions/dm-subagents/src/runs/shared/dm-args.ts +0 -271
  197. package/extensions/dm-subagents/src/runs/shared/dm-spawn.ts +0 -147
  198. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.ts +0 -295
  199. package/extensions/dm-subagents/src/runs/shared/long-running-guard.ts +0 -175
  200. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +0 -365
  201. package/extensions/dm-subagents/src/runs/shared/model-fallback.ts +0 -292
  202. package/extensions/dm-subagents/src/runs/shared/model-scope.ts +0 -128
  203. package/extensions/dm-subagents/src/runs/shared/nested-events.ts +0 -908
  204. package/extensions/dm-subagents/src/runs/shared/nested-path.ts +0 -52
  205. package/extensions/dm-subagents/src/runs/shared/nested-render.ts +0 -115
  206. package/extensions/dm-subagents/src/runs/shared/parallel-utils.ts +0 -198
  207. package/extensions/dm-subagents/src/runs/shared/run-history.ts +0 -60
  208. package/extensions/dm-subagents/src/runs/shared/single-output.ts +0 -180
  209. package/extensions/dm-subagents/src/runs/shared/structured-output.ts +0 -77
  210. package/extensions/dm-subagents/src/runs/shared/subagent-control.ts +0 -223
  211. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.ts +0 -342
  212. package/extensions/dm-subagents/src/runs/shared/tool-budget.ts +0 -74
  213. package/extensions/dm-subagents/src/runs/shared/turn-budget.ts +0 -52
  214. package/extensions/dm-subagents/src/runs/shared/workflow-graph.ts +0 -206
  215. package/extensions/dm-subagents/src/runs/shared/worktree.ts +0 -600
  216. package/extensions/dm-subagents/src/shared/artifacts.ts +0 -113
  217. package/extensions/dm-subagents/src/shared/atomic-json.ts +0 -86
  218. package/extensions/dm-subagents/src/shared/child-transcript.ts +0 -212
  219. package/extensions/dm-subagents/src/shared/file-coalescer.ts +0 -40
  220. package/extensions/dm-subagents/src/shared/fork-context.ts +0 -194
  221. package/extensions/dm-subagents/src/shared/formatters.ts +0 -133
  222. package/extensions/dm-subagents/src/shared/jsonl-writer.ts +0 -81
  223. package/extensions/dm-subagents/src/shared/model-info.ts +0 -78
  224. package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
  225. package/extensions/dm-subagents/src/shared/session-identity.ts +0 -10
  226. package/extensions/dm-subagents/src/shared/session-tokens.ts +0 -44
  227. package/extensions/dm-subagents/src/shared/settings.ts +0 -450
  228. package/extensions/dm-subagents/src/shared/status-format.ts +0 -49
  229. package/extensions/dm-subagents/src/shared/types.ts +0 -1257
  230. package/extensions/dm-subagents/src/shared/utils.ts +0 -554
  231. package/extensions/dm-subagents/src/slash/prompt-template-bridge.ts +0 -420
  232. package/extensions/dm-subagents/src/slash/prompt-workflows.ts +0 -330
  233. package/extensions/dm-subagents/src/slash/slash-bridge.ts +0 -176
  234. package/extensions/dm-subagents/src/slash/slash-commands.ts +0 -1296
  235. package/extensions/dm-subagents/src/slash/slash-live-state.ts +0 -292
  236. package/extensions/dm-subagents/src/tui/render-helpers.ts +0 -80
  237. package/extensions/dm-subagents/src/tui/render.ts +0 -1748
  238. package/extensions/dm-tasks/src/auto-clear.ts +0 -91
  239. package/extensions/dm-tasks/src/index.ts +0 -1145
  240. package/extensions/dm-tasks/src/process-tracker.ts +0 -140
  241. package/extensions/dm-tasks/src/task-store.ts +0 -305
  242. package/extensions/dm-tasks/src/tasks-config.ts +0 -23
  243. package/extensions/dm-tasks/src/types.ts +0 -40
  244. package/extensions/dm-tasks/src/ui/settings-menu.ts +0 -100
  245. package/extensions/dm-tasks/src/ui/task-widget.ts +0 -265
  246. package/extensions/dm-usage/index.ts +0 -1718
  247. package/extensions/greedysearch-dm/index.ts +0 -177
  248. package/extensions/greedysearch-dm/src/formatters/results.ts +0 -163
  249. package/extensions/greedysearch-dm/src/formatters/sources.ts +0 -116
  250. package/extensions/greedysearch-dm/src/formatters/synthesis.ts +0 -87
  251. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.ts +0 -370
  252. package/extensions/greedysearch-dm/src/tools/shared.ts +0 -187
  253. package/extensions/greedysearch-dm/src/types.ts +0 -110
  254. package/extensions/greedysearch-dm/src/utils/helpers.ts +0 -40
@@ -1,541 +0,0 @@
1
- /**
2
- * dm-9router-ext
3
- *
4
- * DM extension for 9router — an open-source AI routing proxy.
5
- * Connects DM to your 9router instance via its OpenAI-compatible API.
6
- *
7
- * Features:
8
- * - Auto-discovers models and combos from 9router on startup
9
- * - Registers 9router as a DM provider with dynamic base URL and API key
10
- * - Status commands to view connection info and available models
11
- * - User-persisted configuration shared by all DM instances
12
- *
13
- * Environment variables:
14
- * NINE_ROUTER_BASE_URL - 9router endpoint (default: http://localhost:20128)
15
- * NINE_ROUTER_API_KEY - API key if 9router requires authentication
16
- */
17
-
18
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
19
- import { homedir } from "node:os";
20
- import { dirname, join } from "node:path";
21
- import type { ExtensionAPI, ExtensionContext } from "@duckmind/dm-coding-agent";
22
- import { Type } from "typebox";
23
-
24
- // =============================================================================
25
- // Types
26
- // =============================================================================
27
-
28
- interface NineRouterConfig {
29
- baseUrl: string;
30
- apiKey: string | undefined;
31
- }
32
-
33
- interface NineRouterModel {
34
- id: string;
35
- object: string;
36
- owned_by?: string;
37
- kind?: string;
38
- }
39
-
40
- interface NineRouterModelsResponse {
41
- object: string;
42
- data: NineRouterModel[];
43
- }
44
-
45
- // =============================================================================
46
- // Constants
47
- // =============================================================================
48
-
49
- const DEFAULT_BASE_URL = "http://localhost:20128";
50
- const ENV_BASE_URL = process.env.NINE_ROUTER_BASE_URL;
51
- const ENV_API_KEY = process.env.NINE_ROUTER_API_KEY;
52
- const CONFIG_PATH = join(homedir(), ".dm", "agent", "9router-config.json");
53
-
54
- const CUSTOM_TYPE_CONFIG = "9router-config";
55
- const CUSTOM_TYPE_LAST_ROUTE = "9router-last-route";
56
-
57
- // Headers that may indicate the actual upstream model used
58
- const ROUTING_HEADERS = [
59
- "x-9router-model",
60
- "x-routed-model",
61
- "x-actual-model",
62
- "x-upstream-model",
63
- "x-provider-model",
64
- ];
65
-
66
- // =============================================================================
67
- // Config Helpers
68
- // =============================================================================
69
-
70
- function normalizeBaseUrl(url: string): string {
71
- return url.replace(/\/$/, "");
72
- }
73
-
74
- function maskApiKey(key: string): string {
75
- if (key.length <= 8) return "●".repeat(key.length);
76
- return key.slice(0, 4) + "●".repeat(Math.max(0, key.length - 8)) + key.slice(-4);
77
- }
78
-
79
- function applyEnvOverrides(config: NineRouterConfig): NineRouterConfig {
80
- return {
81
- baseUrl: normalizeBaseUrl(ENV_BASE_URL || config.baseUrl),
82
- apiKey: ENV_API_KEY || config.apiKey,
83
- };
84
- }
85
-
86
- function loadConfigFromDisk(): NineRouterConfig | null {
87
- try {
88
- if (!existsSync(CONFIG_PATH)) return null;
89
- const data = JSON.parse(readFileSync(CONFIG_PATH, "utf8")) as Partial<NineRouterConfig>;
90
- if (!data.baseUrl || typeof data.baseUrl !== "string") return null;
91
- return {
92
- baseUrl: normalizeBaseUrl(data.baseUrl),
93
- apiKey: typeof data.apiKey === "string" && data.apiKey.trim()
94
- ? data.apiKey.trim()
95
- : undefined,
96
- };
97
- } catch (err) {
98
- console.error("[dm-9router-ext] Failed to load persisted config:", err);
99
- return null;
100
- }
101
- }
102
-
103
- function saveConfigToDisk(config: NineRouterConfig) {
104
- try {
105
- mkdirSync(dirname(CONFIG_PATH), { recursive: true });
106
- writeFileSync(
107
- CONFIG_PATH,
108
- `${JSON.stringify({ baseUrl: config.baseUrl, apiKey: config.apiKey }, null, 2)}\n`,
109
- { mode: 0o600 },
110
- );
111
- } catch (err) {
112
- console.error("[dm-9router-ext] Failed to persist config:", err);
113
- }
114
- }
115
-
116
- function getInitialConfig(): NineRouterConfig {
117
- return applyEnvOverrides(loadConfigFromDisk() || {
118
- baseUrl: DEFAULT_BASE_URL,
119
- apiKey: undefined,
120
- });
121
- }
122
-
123
- function loadConfigFromSession(ctx: ExtensionContext): NineRouterConfig | null {
124
- for (const entry of ctx.sessionManager.getEntries()) {
125
- if (entry.type === "custom" && entry.customType === CUSTOM_TYPE_CONFIG) {
126
- const data = entry.data as Partial<NineRouterConfig> | undefined;
127
- if (data?.baseUrl) {
128
- return applyEnvOverrides({
129
- baseUrl: normalizeBaseUrl(data.baseUrl),
130
- apiKey: data.apiKey,
131
- });
132
- }
133
- }
134
- }
135
- return null;
136
- }
137
-
138
- function persistConfig(dm: ExtensionAPI, config: NineRouterConfig) {
139
- saveConfigToDisk(config);
140
- dm.appendEntry(CUSTOM_TYPE_CONFIG, {
141
- baseUrl: config.baseUrl,
142
- apiKey: config.apiKey,
143
- });
144
- }
145
-
146
- // =============================================================================
147
- // 9router API Client
148
- // =============================================================================
149
-
150
- async function fetchModels(
151
- config: NineRouterConfig,
152
- signal?: AbortSignal,
153
- ): Promise<NineRouterModel[]> {
154
- const headers: Record<string, string> = {
155
- Accept: "application/json",
156
- };
157
- if (config.apiKey) {
158
- headers.Authorization = `Bearer ${config.apiKey}`;
159
- }
160
-
161
- const response = await fetch(`${config.baseUrl}/v1/models`, {
162
- method: "GET",
163
- headers,
164
- signal,
165
- });
166
-
167
- if (!response.ok) {
168
- const text = await response.text().catch(() => "");
169
- throw new Error(
170
- `9router returned ${response.status}: ${text || response.statusText}`,
171
- );
172
- }
173
-
174
- const payload = (await response.json()) as NineRouterModelsResponse;
175
- return payload.data || [];
176
- }
177
-
178
- async function testConnection(
179
- config: NineRouterConfig,
180
- signal?: AbortSignal,
181
- ): Promise<{ ok: boolean; error?: string }> {
182
- try {
183
- const headers: Record<string, string> = {};
184
- if (config.apiKey) {
185
- headers.Authorization = `Bearer ${config.apiKey}`;
186
- }
187
-
188
- const response = await fetch(`${config.baseUrl}/v1/models`, {
189
- method: "GET",
190
- headers,
191
- signal,
192
- });
193
-
194
- if (response.ok) {
195
- return { ok: true };
196
- }
197
- return { ok: false, error: `HTTP ${response.status}: ${response.statusText}` };
198
- } catch (err) {
199
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
200
- }
201
- }
202
-
203
- // =============================================================================
204
- // Model Mapping
205
- // =============================================================================
206
-
207
- function mapNineRouterModel(model: NineRouterModel) {
208
- const isCombo = model.owned_by === "combo";
209
-
210
- return {
211
- id: model.id,
212
- name: isCombo ? `🔀 ${model.id}` : model.id,
213
- reasoning: false,
214
- input: ["text"] as ("text" | "image")[],
215
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
216
- contextWindow: 128000,
217
- maxTokens: 4096,
218
- compat: {
219
- // 9router is an OpenAI-compatible proxy; keep requests conservative so
220
- // using this extension does not force built-in-provider-specific features.
221
- supportsDeveloperRole: false,
222
- supportsReasoningEffort: false,
223
- },
224
- };
225
- }
226
-
227
- // =============================================================================
228
- // Provider Registration
229
- // =============================================================================
230
-
231
- function registerNineRouterProvider(
232
- dm: ExtensionAPI,
233
- config: NineRouterConfig,
234
- models: NineRouterModel[],
235
- ) {
236
- // Always use a dedicated provider id ("9router") and never override built-in
237
- // providers like ollama-cloud/openrouter. DM requires apiKey for custom
238
- // providers with models; 9router receives the real key only when configured,
239
- // otherwise a harmless placeholder is scoped to the 9router provider.
240
- dm.registerProvider("9router", {
241
- name: "9router",
242
- baseUrl: `${config.baseUrl}/v1`,
243
- apiKey: config.apiKey || "9router-no-api-key",
244
- api: "openai-completions",
245
- models: models.map(mapNineRouterModel),
246
- });
247
- }
248
-
249
- function unregisterNineRouterProvider(dm: ExtensionAPI) {
250
- dm.unregisterProvider("9router");
251
- }
252
-
253
- // =============================================================================
254
- // Extension Factory
255
- // =============================================================================
256
-
257
- export default async function (dm: ExtensionAPI) {
258
- // ---------------------------------------------------------------------------
259
- // Load configuration (env vars are defaults; session config applied later)
260
- // ---------------------------------------------------------------------------
261
- let config: NineRouterConfig = getInitialConfig();
262
-
263
- // State that survives across the extension lifetime
264
- let discoveredModels: NineRouterModel[] = [];
265
- let lastRoutedModel: string | undefined;
266
- let activeProvider: string | undefined;
267
- let isConnected = false;
268
-
269
- // ---------------------------------------------------------------------------
270
- // Provider registration (async factory = models available immediately)
271
- // ---------------------------------------------------------------------------
272
- try {
273
- const models = await fetchModels(config);
274
- discoveredModels = models;
275
- isConnected = true;
276
- registerNineRouterProvider(dm, config, models);
277
- } catch (err) {
278
- // Do not register an empty/broken provider on startup. Leaving the provider
279
- // absent is safer for built-in DM providers and model selection. Commands
280
- // remain available so the user can configure/reload 9router later.
281
- unregisterNineRouterProvider(dm);
282
- }
283
-
284
- // ---------------------------------------------------------------------------
285
- // Session start: rehydrate config from session
286
- // ---------------------------------------------------------------------------
287
- dm.on("session_start", async (_event, ctx) => {
288
- const restored = loadConfigFromSession(ctx);
289
- if (!loadConfigFromDisk() && restored) {
290
- // Migrate old session-persisted config to the new user-wide config file.
291
- config = restored;
292
- persistConfig(dm, config);
293
- try {
294
- const models = await fetchModels(config, ctx.signal);
295
- discoveredModels = models;
296
- isConnected = true;
297
- registerNineRouterProvider(dm, config, models);
298
- } catch (err) {
299
- isConnected = false;
300
- console.error("[dm-9router-ext] Failed to refresh migrated config:", err);
301
- }
302
- }
303
-
304
- if (isConnected && discoveredModels.length > 0) {
305
- ctx.ui.notify(
306
- `9router connected — ${discoveredModels.length} models available`,
307
- "info",
308
- );
309
- }
310
- });
311
-
312
- // ---------------------------------------------------------------------------
313
- // Detect routed model from response headers
314
- // ---------------------------------------------------------------------------
315
- dm.on("after_provider_response", (event) => {
316
- if (event.status >= 400 || activeProvider !== "9router") {
317
- return;
318
- }
319
-
320
- for (const header of ROUTING_HEADERS) {
321
- const value = event.headers[header];
322
- if (value && typeof value === "string") {
323
- lastRoutedModel = value;
324
- dm.appendEntry(CUSTOM_TYPE_LAST_ROUTE, {
325
- model: value,
326
- timestamp: Date.now(),
327
- });
328
- break;
329
- }
330
- }
331
- });
332
-
333
- // ---------------------------------------------------------------------------
334
- // Clear routing info when model changes
335
- // ---------------------------------------------------------------------------
336
- dm.on("model_select", async (event) => {
337
- activeProvider = event.model.provider;
338
- if (event.model.provider !== "9router") {
339
- lastRoutedModel = undefined;
340
- }
341
- });
342
-
343
- // ---------------------------------------------------------------------------
344
- // Command: /9router-status
345
- // ---------------------------------------------------------------------------
346
- dm.registerCommand("9router-status", {
347
- description: "Show 9router connection status and configuration",
348
- handler: async (_args, ctx) => {
349
- const test = await testConnection(config, ctx.signal);
350
- const lines: string[] = [
351
- `🔗 9router Status`,
352
- ``,
353
- `Base URL: ${config.baseUrl}`,
354
- `API Key: ${config.apiKey ? maskApiKey(config.apiKey) : "not set"}`,
355
- `Connection: ${test.ok ? "🟢 connected" : `🔴 ${test.error || "disconnected"}`}`,
356
- `Models: ${discoveredModels.length} available`,
357
- ];
358
-
359
- if (lastRoutedModel) {
360
- lines.push(`Last routed: ${lastRoutedModel}`);
361
- }
362
-
363
- const combos = discoveredModels.filter((m) => m.owned_by === "combo");
364
- const regular = discoveredModels.filter((m) => m.owned_by !== "combo");
365
- if (regular.length > 0) {
366
- lines.push(``, `Regular models: ${regular.length}`);
367
- }
368
- if (combos.length > 0) {
369
- lines.push(`Combos: ${combos.length}`);
370
- }
371
-
372
- ctx.ui.notify(lines.join("\n"), test.ok ? "info" : "warning");
373
- },
374
- });
375
-
376
- // ---------------------------------------------------------------------------
377
- // Command: /9router-models
378
- // ---------------------------------------------------------------------------
379
- dm.registerCommand("9router-models", {
380
- description: "Browse 9router available models and combos",
381
- handler: async (_args, ctx) => {
382
- if (discoveredModels.length === 0) {
383
- ctx.ui.notify(
384
- "No 9router models discovered. Check connection with /9router-status",
385
- "warning",
386
- );
387
- return;
388
- }
389
-
390
- const items = discoveredModels.map((m) => {
391
- const isCombo = m.owned_by === "combo";
392
- return {
393
- value: m.id,
394
- label: isCombo ? `🔀 ${m.id}` : m.id,
395
- };
396
- });
397
-
398
- const selected = await ctx.ui.select(
399
- "Select a 9router model to use:",
400
- items.map((i) => i.label),
401
- );
402
- if (!selected) return;
403
-
404
- const modelId = items.find((i) => i.label === selected)?.value;
405
- if (!modelId) return;
406
-
407
- const fullModelId = `9router/${modelId}`;
408
- ctx.ui.notify(`Switching to ${fullModelId}...`, "info");
409
- dm.sendUserMessage(`/model ${fullModelId}`, { deliverAs: "followUp" });
410
- },
411
- });
412
-
413
- // ---------------------------------------------------------------------------
414
- // Command: /9router-config
415
- // ---------------------------------------------------------------------------
416
- dm.registerCommand("9router-config", {
417
- description: "Configure 9router base URL and API key",
418
- handler: async (_args, ctx) => {
419
- // Show current config first
420
- const test = await testConnection(config, ctx.signal);
421
- const currentStatus = test.ok ? "🟢 connected" : "🔴 disconnected";
422
- const currentApiKeyDisplay = config.apiKey ? "●●●●●●●● (set)" : "not set";
423
-
424
- const currentLines = [
425
- "Current config:",
426
- ` Base URL: ${config.baseUrl}`,
427
- ` API Key: ${config.apiKey ? maskApiKey(config.apiKey) : "not set"}`,
428
- ` Status: ${currentStatus}`,
429
- "",
430
- "Enter new values (press Enter to keep current):",
431
- ].join("\n");
432
-
433
- ctx.ui.notify(currentLines, "info");
434
- const newBaseUrl = await ctx.ui.input("Base URL", config.baseUrl);
435
- if (newBaseUrl === undefined) return; // cancelled
436
-
437
- const newApiKey = await ctx.ui.input(
438
- "API key (press Enter to keep current, leave blank to remove):",
439
- config.apiKey || "",
440
- );
441
- if (newApiKey === undefined) return; // cancelled
442
-
443
- config = {
444
- baseUrl: normalizeBaseUrl(newBaseUrl),
445
- apiKey: newApiKey.trim() || undefined,
446
- };
447
-
448
- persistConfig(dm, config);
449
-
450
- // Try to refresh models and re-register provider
451
- try {
452
- const models = await fetchModels(config, ctx.signal);
453
- discoveredModels = models;
454
- isConnected = true;
455
-
456
- registerNineRouterProvider(dm, config, models);
457
-
458
- ctx.ui.notify(
459
- `9router updated — ${models.length} models at ${config.baseUrl}`,
460
- "info",
461
- );
462
- } catch (err) {
463
- isConnected = false;
464
- unregisterNineRouterProvider(dm);
465
- const msg = err instanceof Error ? err.message : String(err);
466
- ctx.ui.notify(`Failed to connect: ${msg}`, "error");
467
- }
468
- },
469
- });
470
-
471
- // ---------------------------------------------------------------------------
472
- // Command: /9router-reload
473
- // ---------------------------------------------------------------------------
474
- dm.registerCommand("9router-reload", {
475
- description: "Reload models from 9router",
476
- handler: async (_args, ctx) => {
477
- try {
478
- const models = await fetchModels(config, ctx.signal);
479
- discoveredModels = models;
480
- isConnected = true;
481
-
482
- registerNineRouterProvider(dm, config, models);
483
-
484
- ctx.ui.notify(
485
- `9router reloaded — ${models.length} models`,
486
- "info",
487
- );
488
- } catch (err) {
489
- isConnected = false;
490
- unregisterNineRouterProvider(dm);
491
- const msg = err instanceof Error ? err.message : String(err);
492
- ctx.ui.notify(`Reload failed: ${msg}`, "error");
493
- }
494
- },
495
- });
496
-
497
- // ---------------------------------------------------------------------------
498
- // Tool: ninerouter_status
499
- // ---------------------------------------------------------------------------
500
- dm.registerTool({
501
- name: "ninerouter_status",
502
- label: "9router Status",
503
- description:
504
- "Check 9router connection status and list available models",
505
- parameters: Type.Object({}),
506
- async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
507
- const test = await testConnection(config, ctx.signal);
508
- const combos = discoveredModels.filter((m) => m.owned_by === "combo");
509
- const regular = discoveredModels.filter((m) => m.owned_by !== "combo");
510
-
511
- return {
512
- content: [
513
- {
514
- type: "text",
515
- text: [
516
- `9router: ${test.ok ? "connected" : `disconnected (${test.error})`}`,
517
- `Base URL: ${config.baseUrl}`,
518
- `Total models: ${discoveredModels.length}`,
519
- ` Regular: ${regular.length}`,
520
- ` Combos: ${combos.length}`,
521
- lastRoutedModel
522
- ? `Last routed model: ${lastRoutedModel}`
523
- : "",
524
- ]
525
- .filter(Boolean)
526
- .join("\n"),
527
- },
528
- ],
529
- details: {
530
- connected: test.ok,
531
- baseUrl: config.baseUrl,
532
- modelCount: discoveredModels.length,
533
- regularCount: regular.length,
534
- comboCount: combos.length,
535
- lastRoutedModel,
536
- models: discoveredModels.map((m) => m.id),
537
- },
538
- };
539
- },
540
- });
541
- }