@duckmind/dm-darwin-x64 0.43.2 → 0.43.8

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 (256) hide show
  1. package/dm +0 -0
  2. package/extensions/.dm-extensions.json +242 -49
  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 +135 -135
  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/engines.mjs +9 -9
  132. package/extensions/greedysearch-dm/src/search/paths.mjs +1 -0
  133. package/extensions/greedysearch-dm/src/search/research.mjs +100 -100
  134. package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +10 -10
  135. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +20 -0
  136. package/extensions/greedysearch-dm/src/tools/shared.js +9 -0
  137. package/extensions/greedysearch-dm/src/types.js +0 -0
  138. package/extensions/greedysearch-dm/src/utils/helpers.js +1 -0
  139. package/package.json +1 -1
  140. package/extensions/dm-9router-ext/src/index.ts +0 -541
  141. package/extensions/dm-ask-user/index.ts +0 -857
  142. package/extensions/dm-context/src/context.ts +0 -158
  143. package/extensions/dm-context/src/index.ts +0 -439
  144. package/extensions/dm-context/src/utils.ts +0 -6
  145. package/extensions/dm-fff/src/index.ts +0 -1023
  146. package/extensions/dm-fff/src/query.ts +0 -87
  147. package/extensions/dm-goal/src/goal.ts +0 -1073
  148. package/extensions/dm-grill-me/index.ts +0 -1085
  149. package/extensions/dm-subagents/src/agents/agent-management.ts +0 -1052
  150. package/extensions/dm-subagents/src/agents/agent-memory.ts +0 -254
  151. package/extensions/dm-subagents/src/agents/agent-scope.ts +0 -6
  152. package/extensions/dm-subagents/src/agents/agent-selection.ts +0 -25
  153. package/extensions/dm-subagents/src/agents/agent-serializer.ts +0 -121
  154. package/extensions/dm-subagents/src/agents/agents.ts +0 -1553
  155. package/extensions/dm-subagents/src/agents/chain-serializer.ts +0 -277
  156. package/extensions/dm-subagents/src/agents/frontmatter.ts +0 -93
  157. package/extensions/dm-subagents/src/agents/identity.ts +0 -30
  158. package/extensions/dm-subagents/src/agents/proactive-skills.ts +0 -191
  159. package/extensions/dm-subagents/src/agents/skills.ts +0 -729
  160. package/extensions/dm-subagents/src/extension/config.ts +0 -39
  161. package/extensions/dm-subagents/src/extension/control-notices.ts +0 -92
  162. package/extensions/dm-subagents/src/extension/doctor.ts +0 -214
  163. package/extensions/dm-subagents/src/extension/fanout-child.ts +0 -172
  164. package/extensions/dm-subagents/src/extension/index.ts +0 -656
  165. package/extensions/dm-subagents/src/extension/rpc.ts +0 -369
  166. package/extensions/dm-subagents/src/extension/schemas.ts +0 -309
  167. package/extensions/dm-subagents/src/intercom/intercom-bridge.ts +0 -180
  168. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.ts +0 -519
  169. package/extensions/dm-subagents/src/intercom/result-intercom.ts +0 -377
  170. package/extensions/dm-subagents/src/profiles/profiles.ts +0 -637
  171. package/extensions/dm-subagents/src/runs/background/async-execution.ts +0 -1065
  172. package/extensions/dm-subagents/src/runs/background/async-job-tracker.ts +0 -441
  173. package/extensions/dm-subagents/src/runs/background/async-resume.ts +0 -391
  174. package/extensions/dm-subagents/src/runs/background/async-status.ts +0 -395
  175. package/extensions/dm-subagents/src/runs/background/chain-append.ts +0 -282
  176. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.ts +0 -191
  177. package/extensions/dm-subagents/src/runs/background/completion-batcher.ts +0 -166
  178. package/extensions/dm-subagents/src/runs/background/completion-dedupe.ts +0 -63
  179. package/extensions/dm-subagents/src/runs/background/control-channel.ts +0 -332
  180. package/extensions/dm-subagents/src/runs/background/fleet-view.ts +0 -515
  181. package/extensions/dm-subagents/src/runs/background/notify.ts +0 -225
  182. package/extensions/dm-subagents/src/runs/background/parallel-groups.ts +0 -45
  183. package/extensions/dm-subagents/src/runs/background/result-watcher.ts +0 -315
  184. package/extensions/dm-subagents/src/runs/background/run-id-resolver.ts +0 -84
  185. package/extensions/dm-subagents/src/runs/background/run-status.ts +0 -434
  186. package/extensions/dm-subagents/src/runs/background/scheduled-runs.ts +0 -514
  187. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.ts +0 -368
  188. package/extensions/dm-subagents/src/runs/background/subagent-runner.ts +0 -3171
  189. package/extensions/dm-subagents/src/runs/background/top-level-async.ts +0 -13
  190. package/extensions/dm-subagents/src/runs/background/wait.ts +0 -353
  191. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.ts +0 -1333
  192. package/extensions/dm-subagents/src/runs/foreground/chain-execution.ts +0 -1313
  193. package/extensions/dm-subagents/src/runs/foreground/execution.ts +0 -1239
  194. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.ts +0 -3613
  195. package/extensions/dm-subagents/src/runs/shared/acceptance.ts +0 -879
  196. package/extensions/dm-subagents/src/runs/shared/chain-outputs.ts +0 -116
  197. package/extensions/dm-subagents/src/runs/shared/completion-guard.ts +0 -147
  198. package/extensions/dm-subagents/src/runs/shared/dm-args.ts +0 -271
  199. package/extensions/dm-subagents/src/runs/shared/dm-spawn.ts +0 -147
  200. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.ts +0 -295
  201. package/extensions/dm-subagents/src/runs/shared/long-running-guard.ts +0 -175
  202. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +0 -365
  203. package/extensions/dm-subagents/src/runs/shared/model-fallback.ts +0 -292
  204. package/extensions/dm-subagents/src/runs/shared/model-scope.ts +0 -128
  205. package/extensions/dm-subagents/src/runs/shared/nested-events.ts +0 -908
  206. package/extensions/dm-subagents/src/runs/shared/nested-path.ts +0 -52
  207. package/extensions/dm-subagents/src/runs/shared/nested-render.ts +0 -115
  208. package/extensions/dm-subagents/src/runs/shared/parallel-utils.ts +0 -198
  209. package/extensions/dm-subagents/src/runs/shared/run-history.ts +0 -60
  210. package/extensions/dm-subagents/src/runs/shared/single-output.ts +0 -180
  211. package/extensions/dm-subagents/src/runs/shared/structured-output.ts +0 -77
  212. package/extensions/dm-subagents/src/runs/shared/subagent-control.ts +0 -223
  213. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.ts +0 -342
  214. package/extensions/dm-subagents/src/runs/shared/tool-budget.ts +0 -74
  215. package/extensions/dm-subagents/src/runs/shared/turn-budget.ts +0 -52
  216. package/extensions/dm-subagents/src/runs/shared/workflow-graph.ts +0 -206
  217. package/extensions/dm-subagents/src/runs/shared/worktree.ts +0 -600
  218. package/extensions/dm-subagents/src/shared/artifacts.ts +0 -113
  219. package/extensions/dm-subagents/src/shared/atomic-json.ts +0 -86
  220. package/extensions/dm-subagents/src/shared/child-transcript.ts +0 -212
  221. package/extensions/dm-subagents/src/shared/file-coalescer.ts +0 -40
  222. package/extensions/dm-subagents/src/shared/fork-context.ts +0 -194
  223. package/extensions/dm-subagents/src/shared/formatters.ts +0 -133
  224. package/extensions/dm-subagents/src/shared/jsonl-writer.ts +0 -81
  225. package/extensions/dm-subagents/src/shared/model-info.ts +0 -78
  226. package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
  227. package/extensions/dm-subagents/src/shared/session-identity.ts +0 -10
  228. package/extensions/dm-subagents/src/shared/session-tokens.ts +0 -44
  229. package/extensions/dm-subagents/src/shared/settings.ts +0 -450
  230. package/extensions/dm-subagents/src/shared/status-format.ts +0 -49
  231. package/extensions/dm-subagents/src/shared/types.ts +0 -1257
  232. package/extensions/dm-subagents/src/shared/utils.ts +0 -554
  233. package/extensions/dm-subagents/src/slash/prompt-template-bridge.ts +0 -420
  234. package/extensions/dm-subagents/src/slash/prompt-workflows.ts +0 -330
  235. package/extensions/dm-subagents/src/slash/slash-bridge.ts +0 -176
  236. package/extensions/dm-subagents/src/slash/slash-commands.ts +0 -1296
  237. package/extensions/dm-subagents/src/slash/slash-live-state.ts +0 -292
  238. package/extensions/dm-subagents/src/tui/render-helpers.ts +0 -80
  239. package/extensions/dm-subagents/src/tui/render.ts +0 -1748
  240. package/extensions/dm-tasks/src/auto-clear.ts +0 -91
  241. package/extensions/dm-tasks/src/index.ts +0 -1145
  242. package/extensions/dm-tasks/src/process-tracker.ts +0 -140
  243. package/extensions/dm-tasks/src/task-store.ts +0 -305
  244. package/extensions/dm-tasks/src/tasks-config.ts +0 -23
  245. package/extensions/dm-tasks/src/types.ts +0 -40
  246. package/extensions/dm-tasks/src/ui/settings-menu.ts +0 -100
  247. package/extensions/dm-tasks/src/ui/task-widget.ts +0 -265
  248. package/extensions/dm-usage/index.ts +0 -1718
  249. package/extensions/greedysearch-dm/index.ts +0 -177
  250. package/extensions/greedysearch-dm/src/formatters/results.ts +0 -163
  251. package/extensions/greedysearch-dm/src/formatters/sources.ts +0 -116
  252. package/extensions/greedysearch-dm/src/formatters/synthesis.ts +0 -87
  253. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.ts +0 -370
  254. package/extensions/greedysearch-dm/src/tools/shared.ts +0 -187
  255. package/extensions/greedysearch-dm/src/types.ts +0 -110
  256. 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
- }