@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.
- package/dm +0 -0
- package/extensions/.dm-extensions.json +242 -50
- package/extensions/dm-9router-ext/package.json +2 -19
- package/extensions/dm-9router-ext/src/index.js +5 -0
- package/extensions/dm-ask-user/index.js +10 -0
- package/extensions/dm-ask-user/package.json +1 -21
- package/extensions/dm-cli-anything/package.json +0 -6
- package/extensions/dm-context/package.json +2 -12
- package/extensions/dm-context/src/context.js +1 -0
- package/extensions/dm-context/src/index.js +7 -0
- package/extensions/dm-context/src/utils.js +1 -0
- package/extensions/dm-cua/package.json +0 -7
- package/extensions/dm-fff/package.json +1 -28
- package/extensions/dm-fff/src/index.js +15 -0
- package/extensions/dm-fff/src/query.js +1 -0
- package/extensions/dm-goal/package.json +2 -31
- package/extensions/dm-goal/src/goal.js +43 -0
- package/extensions/dm-grill-me/index.js +174 -0
- package/extensions/dm-grill-me/package.json +1 -26
- package/extensions/dm-image2/package.json +0 -7
- package/extensions/dm-subagents/package.json +1 -32
- package/extensions/dm-subagents/src/agents/agent-management.js +37 -0
- package/extensions/dm-subagents/src/agents/agent-memory.js +6 -0
- package/extensions/dm-subagents/src/agents/agent-scope.js +1 -0
- package/extensions/dm-subagents/src/agents/agent-selection.js +1 -0
- package/extensions/dm-subagents/src/agents/agent-serializer.js +7 -0
- package/extensions/dm-subagents/src/agents/agents.js +11 -0
- package/extensions/dm-subagents/src/agents/chain-serializer.js +12 -0
- package/extensions/dm-subagents/src/agents/frontmatter.js +6 -0
- package/extensions/dm-subagents/src/agents/identity.js +1 -0
- package/extensions/dm-subagents/src/agents/proactive-skills.js +1 -0
- package/extensions/dm-subagents/src/agents/skills.js +6 -0
- package/extensions/dm-subagents/src/extension/config.js +2 -0
- package/extensions/dm-subagents/src/extension/control-notices.js +4 -0
- package/extensions/dm-subagents/src/extension/doctor.js +16 -0
- package/extensions/dm-subagents/src/extension/fanout-child.js +231 -0
- package/extensions/dm-subagents/src/extension/index.js +367 -0
- package/extensions/dm-subagents/src/extension/rpc.js +8 -0
- package/extensions/dm-subagents/src/extension/schemas.js +1 -0
- package/extensions/dm-subagents/src/extension/{tool-description.ts → tool-description.js} +6 -126
- package/extensions/dm-subagents/src/intercom/intercom-bridge.js +13 -0
- package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +5 -0
- package/extensions/dm-subagents/src/intercom/result-intercom.js +3 -0
- package/extensions/dm-subagents/src/profiles/profiles.js +3 -0
- package/extensions/dm-subagents/src/runs/background/async-execution.js +46 -0
- package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +14 -0
- package/extensions/dm-subagents/src/runs/background/async-resume.js +8 -0
- package/extensions/dm-subagents/src/runs/background/async-status.js +12 -0
- package/extensions/dm-subagents/src/runs/background/chain-append.js +2 -0
- package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +1 -0
- package/extensions/dm-subagents/src/runs/background/completion-batcher.js +1 -0
- package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +1 -0
- package/extensions/dm-subagents/src/runs/background/control-channel.js +1 -0
- package/extensions/dm-subagents/src/runs/background/fleet-view.js +17 -0
- package/extensions/dm-subagents/src/runs/background/notify.js +3 -0
- package/extensions/dm-subagents/src/runs/background/parallel-groups.js +1 -0
- package/extensions/dm-subagents/src/runs/background/result-watcher.js +8 -0
- package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +4 -0
- package/extensions/dm-subagents/src/runs/background/run-status.js +23 -0
- package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +4 -0
- package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +11 -0
- package/extensions/dm-subagents/src/runs/background/subagent-runner.js +78 -0
- package/extensions/dm-subagents/src/runs/background/top-level-async.js +1 -0
- package/extensions/dm-subagents/src/runs/background/wait.js +11 -0
- package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +12 -0
- package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +102 -0
- package/extensions/dm-subagents/src/runs/foreground/execution.js +51 -0
- package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +228 -0
- package/extensions/dm-subagents/src/runs/shared/acceptance.js +3 -0
- package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/completion-guard.js +3 -0
- package/extensions/dm-subagents/src/runs/shared/dm-args.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/model-fallback.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/model-scope.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/nested-events.js +8 -0
- package/extensions/dm-subagents/src/runs/shared/nested-path.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/nested-render.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +5 -0
- package/extensions/dm-subagents/src/runs/shared/run-history.js +5 -0
- package/extensions/dm-subagents/src/runs/shared/single-output.js +14 -0
- package/extensions/dm-subagents/src/runs/shared/structured-output.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/subagent-control.js +5 -0
- package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +22 -0
- package/extensions/dm-subagents/src/runs/shared/tool-budget.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/turn-budget.js +7 -0
- package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +1 -0
- package/extensions/dm-subagents/src/runs/shared/worktree.js +3 -0
- package/extensions/dm-subagents/src/shared/artifacts.js +2 -0
- package/extensions/dm-subagents/src/shared/atomic-json.js +1 -0
- package/extensions/dm-subagents/src/shared/child-transcript.js +5 -0
- package/extensions/dm-subagents/src/shared/file-coalescer.js +1 -0
- package/extensions/dm-subagents/src/shared/fork-context.js +6 -0
- package/extensions/dm-subagents/src/shared/formatters.js +9 -0
- package/extensions/dm-subagents/src/shared/jsonl-writer.js +2 -0
- package/extensions/dm-subagents/src/shared/model-info.js +1 -0
- package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +1 -0
- package/extensions/dm-subagents/src/shared/session-identity.js +1 -0
- package/extensions/dm-subagents/src/shared/session-tokens.js +2 -0
- package/extensions/dm-subagents/src/shared/settings.js +23 -0
- package/extensions/dm-subagents/src/shared/status-format.js +1 -0
- package/extensions/dm-subagents/src/shared/types.js +8 -0
- package/extensions/dm-subagents/src/shared/utils.js +2 -0
- package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +1 -0
- package/extensions/dm-subagents/src/slash/prompt-workflows.js +7 -0
- package/extensions/dm-subagents/src/slash/slash-bridge.js +1 -0
- package/extensions/dm-subagents/src/slash/slash-commands.js +38 -0
- package/extensions/dm-subagents/src/slash/slash-live-state.js +6 -0
- package/extensions/dm-subagents/src/tui/render-helpers.js +1 -0
- package/extensions/dm-subagents/src/tui/render.js +4 -0
- package/extensions/dm-tasks/package.json +1 -30
- package/extensions/dm-tasks/src/auto-clear.js +1 -0
- package/extensions/dm-tasks/src/index.js +212 -0
- package/extensions/dm-tasks/src/process-tracker.js +1 -0
- package/extensions/dm-tasks/src/task-store.js +1 -0
- package/extensions/dm-tasks/src/tasks-config.js +1 -0
- package/extensions/dm-tasks/src/types.js +0 -0
- package/extensions/dm-tasks/src/ui/settings-menu.js +1 -0
- package/extensions/dm-tasks/src/ui/task-widget.js +1 -0
- package/extensions/dm-usage/index.js +9 -0
- package/extensions/dm-usage/package.json +1 -18
- package/extensions/greedysearch-dm/bin/search.mjs +51 -51
- package/extensions/greedysearch-dm/index.js +20 -0
- package/extensions/greedysearch-dm/package.json +1 -22
- package/extensions/greedysearch-dm/src/formatters/results.js +8 -0
- package/extensions/greedysearch-dm/src/formatters/sources.js +1 -0
- package/extensions/greedysearch-dm/src/formatters/synthesis.js +1 -0
- package/extensions/greedysearch-dm/src/search/research.mjs +59 -59
- package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +10 -10
- package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +20 -0
- package/extensions/greedysearch-dm/src/tools/shared.js +9 -0
- package/extensions/greedysearch-dm/src/types.js +0 -0
- package/extensions/greedysearch-dm/src/utils/helpers.js +1 -0
- package/package.json +1 -1
- package/extensions/dm-9router-ext/src/index.ts +0 -541
- package/extensions/dm-ask-user/index.ts +0 -857
- package/extensions/dm-context/src/context.ts +0 -158
- package/extensions/dm-context/src/index.ts +0 -439
- package/extensions/dm-context/src/utils.ts +0 -6
- package/extensions/dm-fff/src/index.ts +0 -1023
- package/extensions/dm-fff/src/query.ts +0 -87
- package/extensions/dm-goal/src/goal.ts +0 -1073
- package/extensions/dm-grill-me/index.ts +0 -1085
- package/extensions/dm-subagents/src/agents/agent-management.ts +0 -1052
- package/extensions/dm-subagents/src/agents/agent-memory.ts +0 -254
- package/extensions/dm-subagents/src/agents/agent-scope.ts +0 -6
- package/extensions/dm-subagents/src/agents/agent-selection.ts +0 -25
- package/extensions/dm-subagents/src/agents/agent-serializer.ts +0 -121
- package/extensions/dm-subagents/src/agents/agents.ts +0 -1553
- package/extensions/dm-subagents/src/agents/chain-serializer.ts +0 -277
- package/extensions/dm-subagents/src/agents/frontmatter.ts +0 -93
- package/extensions/dm-subagents/src/agents/identity.ts +0 -30
- package/extensions/dm-subagents/src/agents/proactive-skills.ts +0 -191
- package/extensions/dm-subagents/src/agents/skills.ts +0 -729
- package/extensions/dm-subagents/src/extension/config.ts +0 -39
- package/extensions/dm-subagents/src/extension/control-notices.ts +0 -92
- package/extensions/dm-subagents/src/extension/doctor.ts +0 -214
- package/extensions/dm-subagents/src/extension/fanout-child.ts +0 -172
- package/extensions/dm-subagents/src/extension/index.ts +0 -656
- package/extensions/dm-subagents/src/extension/rpc.ts +0 -369
- package/extensions/dm-subagents/src/extension/schemas.ts +0 -309
- package/extensions/dm-subagents/src/intercom/intercom-bridge.ts +0 -180
- package/extensions/dm-subagents/src/intercom/native-supervisor-channel.ts +0 -519
- package/extensions/dm-subagents/src/intercom/result-intercom.ts +0 -377
- package/extensions/dm-subagents/src/profiles/profiles.ts +0 -637
- package/extensions/dm-subagents/src/runs/background/async-execution.ts +0 -1065
- package/extensions/dm-subagents/src/runs/background/async-job-tracker.ts +0 -441
- package/extensions/dm-subagents/src/runs/background/async-resume.ts +0 -391
- package/extensions/dm-subagents/src/runs/background/async-status.ts +0 -395
- package/extensions/dm-subagents/src/runs/background/chain-append.ts +0 -282
- package/extensions/dm-subagents/src/runs/background/chain-root-attachment.ts +0 -191
- package/extensions/dm-subagents/src/runs/background/completion-batcher.ts +0 -166
- package/extensions/dm-subagents/src/runs/background/completion-dedupe.ts +0 -63
- package/extensions/dm-subagents/src/runs/background/control-channel.ts +0 -332
- package/extensions/dm-subagents/src/runs/background/fleet-view.ts +0 -515
- package/extensions/dm-subagents/src/runs/background/notify.ts +0 -225
- package/extensions/dm-subagents/src/runs/background/parallel-groups.ts +0 -45
- package/extensions/dm-subagents/src/runs/background/result-watcher.ts +0 -315
- package/extensions/dm-subagents/src/runs/background/run-id-resolver.ts +0 -84
- package/extensions/dm-subagents/src/runs/background/run-status.ts +0 -434
- package/extensions/dm-subagents/src/runs/background/scheduled-runs.ts +0 -514
- package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.ts +0 -368
- package/extensions/dm-subagents/src/runs/background/subagent-runner.ts +0 -3171
- package/extensions/dm-subagents/src/runs/background/top-level-async.ts +0 -13
- package/extensions/dm-subagents/src/runs/background/wait.ts +0 -353
- package/extensions/dm-subagents/src/runs/foreground/chain-clarify.ts +0 -1333
- package/extensions/dm-subagents/src/runs/foreground/chain-execution.ts +0 -1313
- package/extensions/dm-subagents/src/runs/foreground/execution.ts +0 -1239
- package/extensions/dm-subagents/src/runs/foreground/subagent-executor.ts +0 -3613
- package/extensions/dm-subagents/src/runs/shared/acceptance.ts +0 -879
- package/extensions/dm-subagents/src/runs/shared/chain-outputs.ts +0 -116
- package/extensions/dm-subagents/src/runs/shared/completion-guard.ts +0 -147
- package/extensions/dm-subagents/src/runs/shared/dm-args.ts +0 -271
- package/extensions/dm-subagents/src/runs/shared/dm-spawn.ts +0 -147
- package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.ts +0 -295
- package/extensions/dm-subagents/src/runs/shared/long-running-guard.ts +0 -175
- package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +0 -365
- package/extensions/dm-subagents/src/runs/shared/model-fallback.ts +0 -292
- package/extensions/dm-subagents/src/runs/shared/model-scope.ts +0 -128
- package/extensions/dm-subagents/src/runs/shared/nested-events.ts +0 -908
- package/extensions/dm-subagents/src/runs/shared/nested-path.ts +0 -52
- package/extensions/dm-subagents/src/runs/shared/nested-render.ts +0 -115
- package/extensions/dm-subagents/src/runs/shared/parallel-utils.ts +0 -198
- package/extensions/dm-subagents/src/runs/shared/run-history.ts +0 -60
- package/extensions/dm-subagents/src/runs/shared/single-output.ts +0 -180
- package/extensions/dm-subagents/src/runs/shared/structured-output.ts +0 -77
- package/extensions/dm-subagents/src/runs/shared/subagent-control.ts +0 -223
- package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.ts +0 -342
- package/extensions/dm-subagents/src/runs/shared/tool-budget.ts +0 -74
- package/extensions/dm-subagents/src/runs/shared/turn-budget.ts +0 -52
- package/extensions/dm-subagents/src/runs/shared/workflow-graph.ts +0 -206
- package/extensions/dm-subagents/src/runs/shared/worktree.ts +0 -600
- package/extensions/dm-subagents/src/shared/artifacts.ts +0 -113
- package/extensions/dm-subagents/src/shared/atomic-json.ts +0 -86
- package/extensions/dm-subagents/src/shared/child-transcript.ts +0 -212
- package/extensions/dm-subagents/src/shared/file-coalescer.ts +0 -40
- package/extensions/dm-subagents/src/shared/fork-context.ts +0 -194
- package/extensions/dm-subagents/src/shared/formatters.ts +0 -133
- package/extensions/dm-subagents/src/shared/jsonl-writer.ts +0 -81
- package/extensions/dm-subagents/src/shared/model-info.ts +0 -78
- package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
- package/extensions/dm-subagents/src/shared/session-identity.ts +0 -10
- package/extensions/dm-subagents/src/shared/session-tokens.ts +0 -44
- package/extensions/dm-subagents/src/shared/settings.ts +0 -450
- package/extensions/dm-subagents/src/shared/status-format.ts +0 -49
- package/extensions/dm-subagents/src/shared/types.ts +0 -1257
- package/extensions/dm-subagents/src/shared/utils.ts +0 -554
- package/extensions/dm-subagents/src/slash/prompt-template-bridge.ts +0 -420
- package/extensions/dm-subagents/src/slash/prompt-workflows.ts +0 -330
- package/extensions/dm-subagents/src/slash/slash-bridge.ts +0 -176
- package/extensions/dm-subagents/src/slash/slash-commands.ts +0 -1296
- package/extensions/dm-subagents/src/slash/slash-live-state.ts +0 -292
- package/extensions/dm-subagents/src/tui/render-helpers.ts +0 -80
- package/extensions/dm-subagents/src/tui/render.ts +0 -1748
- package/extensions/dm-tasks/src/auto-clear.ts +0 -91
- package/extensions/dm-tasks/src/index.ts +0 -1145
- package/extensions/dm-tasks/src/process-tracker.ts +0 -140
- package/extensions/dm-tasks/src/task-store.ts +0 -305
- package/extensions/dm-tasks/src/tasks-config.ts +0 -23
- package/extensions/dm-tasks/src/types.ts +0 -40
- package/extensions/dm-tasks/src/ui/settings-menu.ts +0 -100
- package/extensions/dm-tasks/src/ui/task-widget.ts +0 -265
- package/extensions/dm-usage/index.ts +0 -1718
- package/extensions/greedysearch-dm/index.ts +0 -177
- package/extensions/greedysearch-dm/src/formatters/results.ts +0 -163
- package/extensions/greedysearch-dm/src/formatters/sources.ts +0 -116
- package/extensions/greedysearch-dm/src/formatters/synthesis.ts +0 -87
- package/extensions/greedysearch-dm/src/tools/greedy-search-handler.ts +0 -370
- package/extensions/greedysearch-dm/src/tools/shared.ts +0 -187
- package/extensions/greedysearch-dm/src/types.ts +0 -110
- 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
|
-
}
|