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