@duckmind/dm-windows-x64 0.60.4 → 0.60.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.
- package/dm.exe +0 -0
- package/extensions/.dm-extensions.json +70 -123
- package/extensions/dm-9router-ext/src/index.js +410 -5
- package/extensions/dm-caveman/extensions/caveman.js +283 -12
- package/extensions/dm-cliproxy/index.js +182 -2
- package/extensions/dm-cliproxy/scripts/check-config-migration.js +66 -8
- package/extensions/dm-cliproxy/src/apply.js +228 -1
- package/extensions/dm-cliproxy/src/cache.js +42 -1
- package/extensions/dm-cliproxy/src/commands.js +50 -2
- package/extensions/dm-cliproxy/src/compat.js +81 -1
- package/extensions/dm-cliproxy/src/config.js +192 -2
- package/extensions/dm-cliproxy/src/conflicts.js +46 -1
- package/extensions/dm-cliproxy/src/fetch-models.js +190 -1
- package/extensions/dm-cliproxy/src/fetch-usage.js +41 -1
- package/extensions/dm-cliproxy/src/log.js +23 -1
- package/extensions/dm-cliproxy/src/status-quota.js +77 -1
- package/extensions/dm-cliproxy/src/ui-frame.js +50 -1
- package/extensions/dm-cliproxy/src/ui-hub/hub.js +199 -2
- package/extensions/dm-cliproxy/src/ui-hub/index.js +26 -2
- package/extensions/dm-cliproxy/src/ui-hub/shell.js +46 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-diagnostics.js +69 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-models.js +379 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-usage.js +106 -1
- package/extensions/dm-cliproxy/src/ui-picker/catalog.js +41 -1
- package/extensions/dm-cliproxy/src/ui-picker/mutate.js +115 -1
- package/extensions/dm-cliproxy/src/ui-picker/prompt-confirm.js +35 -1
- package/extensions/dm-cliproxy/src/ui-picker/prompt-name.js +62 -1
- package/extensions/dm-cliproxy/src/ui-picker/providers.js +45 -1
- package/extensions/dm-cliproxy/src/ui-picker/render-text.js +34 -1
- package/extensions/dm-cliproxy/src/ui-picker/rows.js +57 -1
- package/extensions/dm-cliproxy/src/ui-setup.js +260 -2
- package/extensions/dm-cliproxy/src/ui-usage.js +151 -1
- package/extensions/dm-cliproxy/src/usage-shared-cache.js +97 -1
- package/extensions/dm-context/src/context.js +144 -1
- package/extensions/dm-context/src/index.js +339 -7
- package/extensions/dm-context/src/utils.js +9 -1
- package/extensions/dm-cua/bin/browser-cua.mjs +73 -8
- package/extensions/dm-cua/index.js +75 -6
- package/extensions/dm-cua/src/browser-cua-lib.mjs +490 -6
- package/extensions/dm-cua/src/browser-install.mjs +331 -2
- package/extensions/dm-fff/src/index.js +688 -12
- package/extensions/dm-fff/src/query.js +60 -1
- package/extensions/dm-goal/src/goal.js +894 -23
- package/extensions/dm-image2/index.js +103 -9
- package/extensions/dm-image2/src/image-lib.mjs +1275 -8
- package/extensions/dm-subagents/install.mjs +62 -8
- package/extensions/dm-subagents/src/agents/agent-management.js +1190 -36
- package/extensions/dm-subagents/src/agents/agent-memory.js +216 -6
- package/extensions/dm-subagents/src/agents/agent-scope.js +5 -1
- package/extensions/dm-subagents/src/agents/agent-selection.js +20 -1
- package/extensions/dm-subagents/src/agents/agent-serializer.js +120 -5
- package/extensions/dm-subagents/src/agents/agents.js +1139 -11
- package/extensions/dm-subagents/src/agents/chain-serializer.js +299 -11
- package/extensions/dm-subagents/src/agents/frontmatter.js +65 -6
- package/extensions/dm-subagents/src/agents/identity.js +29 -1
- package/extensions/dm-subagents/src/agents/proactive-skills.js +141 -1
- package/extensions/dm-subagents/src/agents/skills.js +614 -6
- package/extensions/dm-subagents/src/extension/config.js +35 -2
- package/extensions/dm-subagents/src/extension/control-notices.js +69 -4
- package/extensions/dm-subagents/src/extension/doctor.js +172 -15
- package/extensions/dm-subagents/src/extension/fanout-child.js +158 -231
- package/extensions/dm-subagents/src/extension/index.js +521 -359
- package/extensions/dm-subagents/src/extension/rpc.js +266 -7
- package/extensions/dm-subagents/src/extension/schemas.js +275 -1
- package/extensions/dm-subagents/src/extension/tool-description.js +111 -6
- package/extensions/dm-subagents/src/intercom/intercom-bridge.js +126 -4
- package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +452 -5
- package/extensions/dm-subagents/src/intercom/result-intercom.js +319 -3
- package/extensions/dm-subagents/src/profiles/profiles.js +458 -3
- package/extensions/dm-subagents/src/runs/background/async-execution.js +834 -40
- package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +435 -14
- package/extensions/dm-subagents/src/runs/background/async-resume.js +334 -8
- package/extensions/dm-subagents/src/runs/background/async-status.js +313 -12
- package/extensions/dm-subagents/src/runs/background/chain-append.js +245 -2
- package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +136 -1
- package/extensions/dm-subagents/src/runs/background/completion-batcher.js +94 -1
- package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +54 -1
- package/extensions/dm-subagents/src/runs/background/control-channel.js +190 -1
- package/extensions/dm-subagents/src/runs/background/fleet-view.js +483 -17
- package/extensions/dm-subagents/src/runs/background/notify.js +129 -3
- package/extensions/dm-subagents/src/runs/background/parallel-groups.js +34 -1
- package/extensions/dm-subagents/src/runs/background/result-watcher.js +236 -6
- package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +76 -4
- package/extensions/dm-subagents/src/runs/background/run-status.js +427 -23
- package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +487 -4
- package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +306 -9
- package/extensions/dm-subagents/src/runs/background/subagent-runner.js +2849 -73
- package/extensions/dm-subagents/src/runs/background/top-level-async.js +5 -1
- package/extensions/dm-subagents/src/runs/background/wait.js +206 -11
- package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +1013 -12
- package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +980 -101
- package/extensions/dm-subagents/src/runs/foreground/execution.js +1165 -45
- package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +3157 -222
- package/extensions/dm-subagents/src/runs/shared/acceptance.js +835 -3
- package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +104 -1
- package/extensions/dm-subagents/src/runs/shared/completion-guard.js +116 -3
- package/extensions/dm-subagents/src/runs/shared/dm-args.js +208 -1
- package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +90 -1
- package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +282 -1
- package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +148 -1
- package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +305 -1
- package/extensions/dm-subagents/src/runs/shared/model-fallback.js +194 -1
- package/extensions/dm-subagents/src/runs/shared/model-scope.js +65 -1
- package/extensions/dm-subagents/src/runs/shared/nested-events.js +851 -8
- package/extensions/dm-subagents/src/runs/shared/nested-path.js +41 -1
- package/extensions/dm-subagents/src/runs/shared/nested-render.js +105 -1
- package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +81 -4
- package/extensions/dm-subagents/src/runs/shared/run-history.js +51 -4
- package/extensions/dm-subagents/src/runs/shared/single-output.js +149 -8
- package/extensions/dm-subagents/src/runs/shared/structured-output.js +58 -1
- package/extensions/dm-subagents/src/runs/shared/subagent-control.js +166 -5
- package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +329 -13
- package/extensions/dm-subagents/src/runs/shared/tool-budget.js +73 -1
- package/extensions/dm-subagents/src/runs/shared/turn-budget.js +47 -4
- package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +196 -1
- package/extensions/dm-subagents/src/runs/shared/worktree.js +435 -3
- package/extensions/dm-subagents/src/shared/artifacts.js +92 -2
- package/extensions/dm-subagents/src/shared/atomic-json.js +55 -1
- package/extensions/dm-subagents/src/shared/child-transcript.js +167 -5
- package/extensions/dm-subagents/src/shared/file-coalescer.js +25 -1
- package/extensions/dm-subagents/src/shared/fork-context.js +147 -4
- package/extensions/dm-subagents/src/shared/formatters.js +98 -7
- package/extensions/dm-subagents/src/shared/jsonl-writer.js +56 -2
- package/extensions/dm-subagents/src/shared/model-info.js +62 -1
- package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +68 -1
- package/extensions/dm-subagents/src/shared/session-identity.js +6 -1
- package/extensions/dm-subagents/src/shared/session-tokens.js +39 -2
- package/extensions/dm-subagents/src/shared/settings.js +198 -11
- package/extensions/dm-subagents/src/shared/status-format.js +53 -1
- package/extensions/dm-subagents/src/shared/types.js +184 -6
- package/extensions/dm-subagents/src/shared/utils.js +462 -2
- package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +288 -1
- package/extensions/dm-subagents/src/slash/prompt-workflows.js +297 -7
- package/extensions/dm-subagents/src/slash/slash-bridge.js +118 -1
- package/extensions/dm-subagents/src/slash/slash-commands.js +1287 -31
- package/extensions/dm-subagents/src/slash/slash-live-state.js +240 -4
- package/extensions/dm-subagents/src/tui/render-helpers.js +64 -1
- package/extensions/dm-subagents/src/tui/render.js +1542 -4
- package/extensions/dm-usage/index.js +1294 -9
- package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +40 -9
- package/extensions/greedysearch-dm/bin/cdp-headless.mjs +5 -2
- package/extensions/greedysearch-dm/bin/cdp-visible.mjs +5 -2
- package/extensions/greedysearch-dm/bin/cdp.mjs +896 -30
- package/extensions/greedysearch-dm/bin/gschrome.mjs +30 -2
- package/extensions/greedysearch-dm/bin/kill-visible.mjs +7 -2
- package/extensions/greedysearch-dm/bin/launch-visible.mjs +13 -2
- package/extensions/greedysearch-dm/bin/launch.mjs +282 -10
- package/extensions/greedysearch-dm/bin/mcp.mjs +386 -361
- package/extensions/greedysearch-dm/bin/search.mjs +620 -540
- package/extensions/greedysearch-dm/bin/visible.mjs +22 -2
- package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +329 -579
- package/extensions/greedysearch-dm/extractors/chatgpt.mjs +301 -583
- package/extensions/greedysearch-dm/extractors/common.mjs +408 -32
- package/extensions/greedysearch-dm/extractors/consensus.mjs +376 -365
- package/extensions/greedysearch-dm/extractors/consent.mjs +303 -14
- package/extensions/greedysearch-dm/extractors/gemini.mjs +228 -592
- package/extensions/greedysearch-dm/extractors/google-ai.mjs +78 -499
- package/extensions/greedysearch-dm/extractors/logically.mjs +270 -347
- package/extensions/greedysearch-dm/extractors/perplexity.mjs +243 -581
- package/extensions/greedysearch-dm/extractors/selectors.mjs +32 -1
- package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +130 -317
- package/extensions/greedysearch-dm/index.js +123 -23
- package/extensions/greedysearch-dm/src/fetcher.mjs +576 -2
- package/extensions/greedysearch-dm/src/formatters/results.js +95 -10
- package/extensions/greedysearch-dm/src/formatters/sources.js +57 -1
- package/extensions/greedysearch-dm/src/formatters/synthesis.js +49 -1
- package/extensions/greedysearch-dm/src/github.mjs +222 -7
- package/extensions/greedysearch-dm/src/reddit.mjs +145 -14
- package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +340 -9
- package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +112 -4
- package/extensions/greedysearch-dm/src/search/chrome.mjs +486 -285
- package/extensions/greedysearch-dm/src/search/constants.mjs +109 -8
- package/extensions/greedysearch-dm/src/search/defaults.mjs +10 -1
- package/extensions/greedysearch-dm/src/search/engines.mjs +79 -9
- package/extensions/greedysearch-dm/src/search/fetch-source.mjs +441 -349
- package/extensions/greedysearch-dm/src/search/file-sources.mjs +29 -7
- package/extensions/greedysearch-dm/src/search/minimize.mjs +86 -1
- package/extensions/greedysearch-dm/src/search/output.mjs +51 -5
- package/extensions/greedysearch-dm/src/search/paths.mjs +48 -1
- package/extensions/greedysearch-dm/src/search/pdf.mjs +63 -2
- package/extensions/greedysearch-dm/src/search/port-pid.mjs +69 -1
- package/extensions/greedysearch-dm/src/search/progress.mjs +109 -2
- package/extensions/greedysearch-dm/src/search/query.mjs +21 -1
- package/extensions/greedysearch-dm/src/search/recovery.mjs +49 -1
- package/extensions/greedysearch-dm/src/search/research.mjs +2227 -458
- package/extensions/greedysearch-dm/src/search/scale-aware.mjs +61 -11
- package/extensions/greedysearch-dm/src/search/simple-research.mjs +396 -805
- package/extensions/greedysearch-dm/src/search/sources.mjs +412 -1
- package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +127 -12
- package/extensions/greedysearch-dm/src/search/synthesis.mjs +202 -12
- package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +209 -23
- package/extensions/greedysearch-dm/src/tools/shared.js +226 -10
- package/extensions/greedysearch-dm/src/utils/content.mjs +35 -4
- package/extensions/greedysearch-dm/src/utils/helpers.js +22 -1
- package/extensions/greedysearch-dm/src/utils/node-runtime.mjs +10 -1
- package/extensions/greedysearch-dm/src/utils/system-cmds.mjs +61 -1
- package/package.json +1 -1
|
@@ -1,8 +1,66 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import {
|
|
3
|
+
chmodSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdtempSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
statSync,
|
|
10
|
+
writeFileSync
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { migrateLegacyConfig } from "../src/config.js";
|
|
15
|
+
import { setLogQuiet } from "../src/log.js";
|
|
16
|
+
setLogQuiet(true);
|
|
17
|
+
const root = mkdtempSync(join(tmpdir(), "dm-cliproxy-migration-"));
|
|
18
|
+
try {
|
|
19
|
+
for (const localCheckout of [true, false]) {
|
|
20
|
+
const legacy = join(root, String(localCheckout), "legacy", "config.json");
|
|
21
|
+
const missing = join(root, String(localCheckout), "missing", "config.json");
|
|
22
|
+
const current = join(root, String(localCheckout), "current", "config.json");
|
|
23
|
+
const config = Buffer.from(JSON.stringify({
|
|
24
|
+
proxy: {
|
|
25
|
+
apiKey: `!cat ${join(dirname(legacy), "key")}`,
|
|
26
|
+
usageKey: `!cat ${join(dirname(legacy), "usage-key")}`
|
|
27
|
+
}
|
|
28
|
+
}, null, 2) + `
|
|
29
|
+
`);
|
|
30
|
+
mkdirSync(dirname(legacy), { recursive: true });
|
|
31
|
+
writeFileSync(legacy, config);
|
|
32
|
+
chmodSync(legacy, 416);
|
|
33
|
+
for (const sidecar of ["key", "usage-key"]) {
|
|
34
|
+
writeFileSync(join(dirname(legacy), sidecar), `${sidecar}
|
|
35
|
+
`);
|
|
36
|
+
}
|
|
37
|
+
migrateLegacyConfig([missing, legacy], current, localCheckout);
|
|
38
|
+
assert.deepEqual(readFileSync(current), config);
|
|
39
|
+
assert.equal(statSync(current).mode & 511, 384);
|
|
40
|
+
assert.equal(statSync(dirname(current)).mode & 511, 448);
|
|
41
|
+
assert.equal(existsSync(legacy), true);
|
|
42
|
+
assert.equal(statSync(legacy).mode & 511, 384);
|
|
43
|
+
for (const sidecar of ["key", "usage-key"]) {
|
|
44
|
+
assert.equal(readFileSync(join(dirname(legacy), sidecar), "utf8"), `${sidecar}
|
|
45
|
+
`);
|
|
46
|
+
assert.equal(statSync(join(dirname(legacy), sidecar)).mode & 511, 384);
|
|
47
|
+
assert.equal(existsSync(join(dirname(current), sidecar)), false);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const legacy = join(root, "no-overwrite", "legacy", "config.json");
|
|
51
|
+
const current = join(root, "no-overwrite", "current", "config.json");
|
|
52
|
+
mkdirSync(dirname(legacy), { recursive: true });
|
|
53
|
+
mkdirSync(dirname(current), { recursive: true });
|
|
54
|
+
writeFileSync(legacy, `legacy
|
|
55
|
+
`);
|
|
56
|
+
writeFileSync(current, `current
|
|
57
|
+
`);
|
|
58
|
+
migrateLegacyConfig(legacy, current, false);
|
|
59
|
+
assert.equal(readFileSync(current, "utf8"), `current
|
|
60
|
+
`);
|
|
61
|
+
assert.equal(readFileSync(legacy, "utf8"), `legacy
|
|
62
|
+
`);
|
|
63
|
+
console.log("dm-cliproxy config migration check: ok");
|
|
64
|
+
} finally {
|
|
65
|
+
rmSync(root, { recursive: true, force: true });
|
|
66
|
+
}
|
|
@@ -1 +1,228 @@
|
|
|
1
|
-
import{getModels
|
|
1
|
+
import { getModels } from "@duckmind/dm-ai/compat";
|
|
2
|
+
import { ALLOWED_APIS, baseUrlFor, modelDefaults } from "./compat.js";
|
|
3
|
+
import { resolveConfigValue } from "./config.js";
|
|
4
|
+
import { discoveryToIdSet } from "./fetch-models.js";
|
|
5
|
+
import { log } from "./log.js";
|
|
6
|
+
const ownedProviders = new WeakMap;
|
|
7
|
+
const providerGenerations = new WeakMap;
|
|
8
|
+
export function beginProviderRefresh(dm) {
|
|
9
|
+
const generation = (providerGenerations.get(dm) ?? 0) + 1;
|
|
10
|
+
providerGenerations.set(dm, generation);
|
|
11
|
+
return generation;
|
|
12
|
+
}
|
|
13
|
+
function isCurrentProviderRefresh(dm, generation) {
|
|
14
|
+
return generation === undefined || generation === (providerGenerations.get(dm) ?? 0);
|
|
15
|
+
}
|
|
16
|
+
function reconcileOwnedProviders(dm, nextProviders, report) {
|
|
17
|
+
const previous = ownedProviders.get(dm) ?? new Set;
|
|
18
|
+
for (const name of previous) {
|
|
19
|
+
if (nextProviders.has(name))
|
|
20
|
+
continue;
|
|
21
|
+
dm.unregisterProvider(name);
|
|
22
|
+
report.unregistered.push(name);
|
|
23
|
+
}
|
|
24
|
+
ownedProviders.set(dm, new Set(nextProviders));
|
|
25
|
+
}
|
|
26
|
+
export function unregisterOwnedProviders(dm) {
|
|
27
|
+
const report = { registered: [], skipped: [], unregistered: [] };
|
|
28
|
+
reconcileOwnedProviders(dm, new Set, report);
|
|
29
|
+
return report.unregistered;
|
|
30
|
+
}
|
|
31
|
+
export function invalidateProviderLifecycle(dm) {
|
|
32
|
+
beginProviderRefresh(dm);
|
|
33
|
+
return unregisterOwnedProviders(dm);
|
|
34
|
+
}
|
|
35
|
+
export async function applyAll(dm, cfg, discovery, refreshGeneration) {
|
|
36
|
+
const report = { registered: [], skipped: [], unregistered: [] };
|
|
37
|
+
if (!isCurrentProviderRefresh(dm, refreshGeneration)) {
|
|
38
|
+
report.skipped.push({
|
|
39
|
+
provider: "*",
|
|
40
|
+
reason: "stale refresh generation"
|
|
41
|
+
});
|
|
42
|
+
return report;
|
|
43
|
+
}
|
|
44
|
+
const nextProviders = new Set;
|
|
45
|
+
const proxyIds = discoveryToIdSet(discovery);
|
|
46
|
+
const resolvedKey = resolveConfigValue(cfg.proxy.apiKey);
|
|
47
|
+
if (!resolvedKey) {
|
|
48
|
+
log.warn("proxy apiKey is empty — dm.registerProvider calls will be skipped");
|
|
49
|
+
reconcileOwnedProviders(dm, nextProviders, report);
|
|
50
|
+
return report;
|
|
51
|
+
}
|
|
52
|
+
const builtinByName = new Map;
|
|
53
|
+
for (const p of discovery.builtinProviders) {
|
|
54
|
+
builtinByName.set(p.name, p.models.map((m) => ({
|
|
55
|
+
id: m.id,
|
|
56
|
+
name: m.name,
|
|
57
|
+
reasoning: m.reasoning,
|
|
58
|
+
contextWindow: m.contextWindow,
|
|
59
|
+
maxTokens: m.maxTokens,
|
|
60
|
+
cost: m.cost
|
|
61
|
+
})));
|
|
62
|
+
}
|
|
63
|
+
for (const [name, p] of Object.entries(cfg.builtinProviders)) {
|
|
64
|
+
if (!isCurrentProviderRefresh(dm, refreshGeneration))
|
|
65
|
+
return report;
|
|
66
|
+
if (!p?.enabled) {
|
|
67
|
+
report.skipped.push({ provider: name, reason: "disabled" });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (!Array.isArray(p.models) || p.models.length === 0) {
|
|
71
|
+
report.skipped.push({ provider: name, reason: "empty whitelist" });
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
let catalog = [];
|
|
75
|
+
try {
|
|
76
|
+
catalog = getModels(name);
|
|
77
|
+
} catch {}
|
|
78
|
+
const catalogById = new Map(catalog.map((m) => [m.id, m]));
|
|
79
|
+
const proxyById = new Map((builtinByName.get(name) ?? []).map((m) => [m.id, m]));
|
|
80
|
+
const selected = [];
|
|
81
|
+
for (const id of p.models) {
|
|
82
|
+
if (!proxyIds.has(id))
|
|
83
|
+
continue;
|
|
84
|
+
const c = catalogById.get(id);
|
|
85
|
+
const px = proxyById.get(id);
|
|
86
|
+
if (c) {
|
|
87
|
+
selected.push({
|
|
88
|
+
id: c.id,
|
|
89
|
+
name: c.name,
|
|
90
|
+
reasoning: c.reasoning,
|
|
91
|
+
contextWindow: c.contextWindow,
|
|
92
|
+
maxTokens: c.maxTokens,
|
|
93
|
+
cost: c.cost,
|
|
94
|
+
input: c.input,
|
|
95
|
+
api: c.api,
|
|
96
|
+
thinkingLevelMap: c.thinkingLevelMap
|
|
97
|
+
});
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (!px)
|
|
101
|
+
continue;
|
|
102
|
+
const api = name === "anthropic" ? "anthropic-messages" : p.apiOverride ?? "openai-responses";
|
|
103
|
+
selected.push({
|
|
104
|
+
id: px.id,
|
|
105
|
+
name: px.name,
|
|
106
|
+
reasoning: px.reasoning,
|
|
107
|
+
contextWindow: px.contextWindow,
|
|
108
|
+
maxTokens: px.maxTokens,
|
|
109
|
+
cost: px.cost,
|
|
110
|
+
input: ["text"],
|
|
111
|
+
api
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (selected.length === 0) {
|
|
115
|
+
report.skipped.push({
|
|
116
|
+
provider: name,
|
|
117
|
+
reason: "no whitelisted models present on proxy"
|
|
118
|
+
});
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const api = p.apiOverride ?? selected[0].api;
|
|
122
|
+
if (!ALLOWED_APIS.has(api)) {
|
|
123
|
+
report.skipped.push({
|
|
124
|
+
provider: name,
|
|
125
|
+
reason: `api "${api}" not in allowlist`
|
|
126
|
+
});
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const modelDefs = selected.map((m) => {
|
|
130
|
+
const ov = cfg.overrides[m.id] ?? {};
|
|
131
|
+
const base = {
|
|
132
|
+
id: m.id,
|
|
133
|
+
name: typeof ov.name === "string" ? ov.name : m.name,
|
|
134
|
+
api,
|
|
135
|
+
reasoning: typeof ov.reasoning === "boolean" ? ov.reasoning : m.reasoning,
|
|
136
|
+
input: m.input,
|
|
137
|
+
cost: ov.cost ?? m.cost,
|
|
138
|
+
contextWindow: typeof ov.contextWindow === "number" ? ov.contextWindow : m.contextWindow,
|
|
139
|
+
maxTokens: typeof ov.maxTokens === "number" ? ov.maxTokens : m.maxTokens
|
|
140
|
+
};
|
|
141
|
+
if (m.thinkingLevelMap)
|
|
142
|
+
base.thinkingLevelMap = m.thinkingLevelMap;
|
|
143
|
+
return base;
|
|
144
|
+
});
|
|
145
|
+
const providerConfig = {
|
|
146
|
+
name,
|
|
147
|
+
baseUrl: baseUrlFor(api, cfg.proxy.endpoint),
|
|
148
|
+
apiKey: resolvedKey,
|
|
149
|
+
authHeader: true,
|
|
150
|
+
api,
|
|
151
|
+
models: modelDefs
|
|
152
|
+
};
|
|
153
|
+
dm.registerProvider(name, providerConfig);
|
|
154
|
+
nextProviders.add(name);
|
|
155
|
+
report.registered.push({
|
|
156
|
+
provider: name,
|
|
157
|
+
modelCount: modelDefs.length,
|
|
158
|
+
api
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
const proxyCustomById = new Map(discovery.customPool.map((m) => [m.id, m]));
|
|
162
|
+
for (const [name, c] of Object.entries(cfg.customProviders)) {
|
|
163
|
+
if (!isCurrentProviderRefresh(dm, refreshGeneration))
|
|
164
|
+
return report;
|
|
165
|
+
if (!ALLOWED_APIS.has(c.api)) {
|
|
166
|
+
report.skipped.push({
|
|
167
|
+
provider: name,
|
|
168
|
+
reason: `api "${c.api}" not in allowlist`
|
|
169
|
+
});
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const present = c.models.filter((m) => proxyIds.has(m.id));
|
|
173
|
+
if (present.length === 0) {
|
|
174
|
+
report.skipped.push({
|
|
175
|
+
provider: name,
|
|
176
|
+
reason: "no configured models present on proxy"
|
|
177
|
+
});
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const modelDefs = present.map((m) => {
|
|
181
|
+
const fromPool = proxyCustomById.get(m.id);
|
|
182
|
+
const base = modelDefaults(m.id);
|
|
183
|
+
const ov = cfg.overrides[m.id] ?? {};
|
|
184
|
+
return {
|
|
185
|
+
id: m.id,
|
|
186
|
+
name: m.name ?? fromPool?.name ?? base.name ?? m.id,
|
|
187
|
+
api: c.api,
|
|
188
|
+
reasoning: pickBool(m.reasoning, fromPool?.reasoning, base.reasoning, ov.reasoning) ?? false,
|
|
189
|
+
input: ["text"],
|
|
190
|
+
cost: m.cost ?? fromPool?.cost ?? base.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
191
|
+
contextWindow: pickNum(m.contextWindow, fromPool?.contextWindow, base.contextWindow, ov.contextWindow) ?? 128000,
|
|
192
|
+
maxTokens: pickNum(m.maxTokens, fromPool?.maxTokens, base.maxTokens, ov.maxTokens) ?? 16000
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
const providerConfig = {
|
|
196
|
+
name,
|
|
197
|
+
baseUrl: baseUrlFor(c.api, cfg.proxy.endpoint),
|
|
198
|
+
apiKey: resolvedKey,
|
|
199
|
+
authHeader: true,
|
|
200
|
+
api: c.api,
|
|
201
|
+
models: modelDefs
|
|
202
|
+
};
|
|
203
|
+
dm.registerProvider(name, providerConfig);
|
|
204
|
+
nextProviders.add(name);
|
|
205
|
+
report.registered.push({
|
|
206
|
+
provider: name,
|
|
207
|
+
modelCount: modelDefs.length,
|
|
208
|
+
api: c.api
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
if (isCurrentProviderRefresh(dm, refreshGeneration)) {
|
|
212
|
+
reconcileOwnedProviders(dm, nextProviders, report);
|
|
213
|
+
}
|
|
214
|
+
log.debug(`applyAll: registered ${report.registered.length} providers, skipped ${report.skipped.length}, unregistered ${report.unregistered.length}`);
|
|
215
|
+
return report;
|
|
216
|
+
}
|
|
217
|
+
function pickNum(...vals) {
|
|
218
|
+
for (const v of vals)
|
|
219
|
+
if (typeof v === "number")
|
|
220
|
+
return v;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
function pickBool(...vals) {
|
|
224
|
+
for (const v of vals)
|
|
225
|
+
if (typeof v === "boolean")
|
|
226
|
+
return v;
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
@@ -1 +1,42 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
CONFIG_DIR,
|
|
6
|
+
ensurePrivateDirectory,
|
|
7
|
+
ensurePrivateFile
|
|
8
|
+
} from "./config.js";
|
|
9
|
+
import { log } from "./log.js";
|
|
10
|
+
export const DISCOVERY_CACHE_PATH = join(CONFIG_DIR, "discovery-cache.json");
|
|
11
|
+
export function discoveryCacheScope(endpoint, resolvedKey) {
|
|
12
|
+
return createHash("sha256").update(endpoint.trim().replace(/\/+$/, "")).update("\x00").update(resolvedKey).digest("hex");
|
|
13
|
+
}
|
|
14
|
+
export function readDiscoveryCache(expectedScope, cachePath = DISCOVERY_CACHE_PATH) {
|
|
15
|
+
if (!existsSync(cachePath))
|
|
16
|
+
return null;
|
|
17
|
+
try {
|
|
18
|
+
ensurePrivateDirectory(dirname(cachePath));
|
|
19
|
+
ensurePrivateFile(cachePath);
|
|
20
|
+
const env = JSON.parse(readFileSync(cachePath, "utf8"));
|
|
21
|
+
if (!env || typeof env.savedAt !== "number" || env.scope !== expectedScope || !env.discovery || !Array.isArray(env.discovery.builtinProviders) || !Array.isArray(env.discovery.customPool)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return { discovery: env.discovery, ageMs: Date.now() - env.savedAt };
|
|
25
|
+
} catch (err) {
|
|
26
|
+
log.warn("failed to read discovery cache:", err.message);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function writeDiscoveryCache(discovery, scope, cachePath = DISCOVERY_CACHE_PATH) {
|
|
31
|
+
try {
|
|
32
|
+
ensurePrivateDirectory(dirname(cachePath));
|
|
33
|
+
const env = { savedAt: Date.now(), scope, discovery };
|
|
34
|
+
writeFileSync(cachePath, JSON.stringify(env), {
|
|
35
|
+
encoding: "utf8",
|
|
36
|
+
mode: 384
|
|
37
|
+
});
|
|
38
|
+
ensurePrivateFile(cachePath);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
log.warn("failed to write discovery cache:", err.message);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -1,2 +1,50 @@
|
|
|
1
|
-
import
|
|
2
|
-
`,{encoding:"utf8"}),kj(Z),A0(Z),Z=void 0,Kj(J,j),Q$(j)}finally{if(Z!==void 0)A0(Z);c$(J,{force:!0})}}function I$($){yj($),F.info("config saved to",l)}function wj($){if(!$||typeof $!=="object")return structuredClone(K$);let j=$,J=structuredClone(K$),Z=j.proxy??{};if(J.proxy.endpoint=typeof Z.endpoint==="string"?Z.endpoint:J.proxy.endpoint,J.proxy.apiKey=typeof Z.apiKey==="string"?Z.apiKey:"",typeof Z.usageKey==="string")J.proxy.usageKey=Z.usageKey;if(typeof Z.providerPrefix==="string"&&Z.providerPrefix.trim())J.proxy.providerPrefix=Z.providerPrefix.trim();if(j.builtinProviders&&typeof j.builtinProviders==="object")J.builtinProviders=j.builtinProviders;if(j.customProviders&&typeof j.customProviders==="object")J.customProviders=j.customProviders;if(Array.isArray(j.discoveryExcludes))J.discoveryExcludes=j.discoveryExcludes.filter((Y)=>typeof Y==="string");if(j.overrides&&typeof j.overrides==="object")J.overrides=j.overrides;if(typeof j.refreshIntervalMinutes==="number")J.refreshIntervalMinutes=j.refreshIntervalMinutes;if(typeof j.usageCacheTtlMs==="number")J.usageCacheTtlMs=j.usageCacheTtlMs;return J}function x($){if(!$)return"";let j=$.trim();if(j.startsWith("!"))try{return Nj(j.slice(1),{encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim()}catch(J){let Z=J?.status,Y=typeof Z==="number"?` (exit ${Z})`:"";return F.warn(`failed to resolve "!" config value${Y}`),""}if(j.startsWith("$"))return process.env[j.slice(1)]??"";return j}import{createHash as Cj}from"node:crypto";import{existsSync as i1,readFileSync as r1,writeFileSync as vj}from"node:fs";import{dirname as uj,join as gj}from"node:path";var fj=gj(m$,"discovery-cache.json");function G0($,j){return Cj("sha256").update($.trim().replace(/\/+$/,"")).update("\x00").update(j).digest("hex")}function s$($,j,J=fj){try{R$(uj(J));let Z={savedAt:Date.now(),scope:j,discovery:$};vj(J,JSON.stringify(Z),{encoding:"utf8",mode:384}),Q$(J)}catch(Z){F.warn("failed to write discovery cache:",Z.message)}}var z$="dm-cliproxy/0.3.5",hj=5000;async function T0($,j){let J=new AbortController,Z=setTimeout(()=>J.abort(Error("timeout")),hj);try{return await fetch($,{...j,signal:J.signal})}finally{clearTimeout(Z)}}function pj($){return new URL("/.well-known/pi",new URL($).origin).toString()}async function cj($){let j=pj($.proxy.endpoint),J;try{J=await T0(j,{headers:{"User-Agent":z$,Accept:"application/json"}})}catch(Q){return F.debug("well-known fetch failed:",Q.message,"— falling back to /v1/models"),null}if(!J.ok)return F.debug(`well-known returned ${J.status} — falling back to /v1/models`),null;let Z;try{Z=await J.json()}catch{return F.debug("well-known returned non-JSON — falling back to /v1/models"),null}if(!Z||Z.schemaVersion!==1)return F.debug("well-known schemaVersion != 1 — falling back to /v1/models"),null;let Y=[],X=Z.builtinProviders??{};for(let[Q,q]of Object.entries(X)){if(!q||!Array.isArray(q.models))continue;let E=q.models.map((M)=>({id:String(M.id),name:typeof M.name==="string"?M.name:String(M.id),reasoning:Boolean(M.reasoning??L$(String(M.id))),contextWindow:typeof M.contextWindow==="number"?M.contextWindow:200000,maxTokens:typeof M.maxTokens==="number"?M.maxTokens:16000,cost:M.cost??{input:0,output:0,cacheRead:0,cacheWrite:0}}));Y.push({name:Q,api:q.api??"openai-responses",models:E})}let z=(Array.isArray(Z.customModelPool)?Z.customModelPool:[]).map((Q)=>({id:String(Q.id),name:typeof Q.name==="string"?Q.name:String(Q.id),reasoning:Boolean(Q.reasoning??L$(String(Q.id))),contextWindow:typeof Q.contextWindow==="number"?Q.contextWindow:128000,maxTokens:typeof Q.maxTokens==="number"?Q.maxTokens:16000,cost:Q.cost??{input:0,output:0,cacheRead:0,cacheWrite:0},api:Q.api??"openai-completions",suggestedProvider:R0(typeof Q.suggestedProviderName==="string"?Q.suggestedProviderName:"misc",$.proxy.providerPrefix),ownedBy:typeof Q.owned_by==="string"?Q.owned_by:""}));return{source:"well-known",upstreamVersion:typeof Z?.upstream?.upstreamVersion==="string"?Z.upstream.upstreamVersion:null,builtinProviders:Y,customPool:z,serverDiscoveryExcludes:Array.isArray(Z.discoveryExcludes)?Z.discoveryExcludes.filter((Q)=>typeof Q==="string"):[],upstreamTotal:typeof Z?.counts?.upstreamTotal==="number"?Z.counts.upstreamTotal:0}}async function lj($,j){let J=new URL("/v1/models",new URL($.proxy.endpoint).origin).toString(),Z=await T0(J,{headers:{Authorization:`Bearer ${j}`,Accept:"application/json","User-Agent":z$}});if(!Z.ok)throw Error(`/v1/models returned ${Z.status}`);let Y=await Z.json();if(!Y?.data||!Array.isArray(Y.data))return[];return Y.data.map((X)=>({id:typeof X.id==="string"?X.id:"",owned_by:typeof X.owned_by==="string"?X.owned_by:""})).filter((X)=>X.id)}function mj($,j){let J=j.discoveryExcludes,Z=new Map,Y=[],X=0;for(let z of $){if(X++,D0(z.id,J))continue;if(z.owned_by==="openai"){let M=X$(z.id);N0(Z,"openai","openai-responses",W0(M));continue}if(z.owned_by==="anthropic"){let M=X$(z.id);N0(Z,"anthropic","anthropic-messages",W0(M));continue}let{slug:Q,api:q}=_0(z.owned_by,j.proxy.providerPrefix),E=X$(z.id);Y.push({id:z.id,name:E.name??z.id,reasoning:E.reasoning??!1,contextWindow:E.contextWindow??128000,maxTokens:E.maxTokens??16000,cost:E.cost??{input:0,output:0,cacheRead:0,cacheWrite:0},api:q,suggestedProvider:Q,ownedBy:z.owned_by})}return{source:"v1-models",upstreamVersion:null,builtinProviders:Array.from(Z.values()),customPool:Y,serverDiscoveryExcludes:[],upstreamTotal:X}}function N0($,j,J,Z){let Y=$.get(j);if(!Y)Y={name:j,api:J,models:[]},$.set(j,Y);Y.models.push(Z)}function W0($){return{id:$.id,name:$.name??$.id,reasoning:$.reasoning??!1,contextWindow:$.contextWindow??128000,maxTokens:$.maxTokens??16000,cost:$.cost??{input:0,output:0,cacheRead:0,cacheWrite:0}}}async function _$($,j){let J=G0($.proxy.endpoint,j),Z=await cj($);if(Z)return F.debug(`discovery via /.well-known/pi: ${Z.builtinProviders.length} builtin, ${Z.customPool.length} custom`),s$(Z,J),Z;if(!j)throw Error("well-known unavailable AND proxy apiKey is empty — cannot fall back to /v1/models. Set proxy.apiKey in config.");let Y=await lj($,j),X=mj(Y,$);return F.debug(`discovery via /v1/models: ${X.builtinProviders.length} builtin, ${X.customPool.length} custom`),s$(X,J),X}function k0($){let j=new Set;for(let J of $.builtinProviders)for(let Z of J.models)j.add(Z.id);for(let J of $.customPool)j.add(J.id);return j}var B0=new WeakMap,o$=new WeakMap;function A$($){let j=(o$.get($)??0)+1;return o$.set($,j),j}function b$($,j){return j===void 0||j===(o$.get($)??0)}function L0($,j,J){let Z=B0.get($)??new Set;for(let Y of Z){if(j.has(Y))continue;$.unregisterProvider(Y),J.unregistered.push(Y)}B0.set($,new Set(j))}async function O$($,j,J,Z){let Y={registered:[],skipped:[],unregistered:[]};if(!b$($,Z))return Y.skipped.push({provider:"*",reason:"stale refresh generation"}),Y;let X=new Set,z=k0(J),Q=x(j.proxy.apiKey);if(!Q)return F.warn("proxy apiKey is empty — dm.registerProvider calls will be skipped"),L0($,X,Y),Y;let q=new Map;for(let M of J.builtinProviders)q.set(M.name,M.models.map((U)=>({id:U.id,name:U.name,reasoning:U.reasoning,contextWindow:U.contextWindow,maxTokens:U.maxTokens,cost:U.cost})));for(let[M,U]of Object.entries(j.builtinProviders)){if(!b$($,Z))return Y;if(!U?.enabled){Y.skipped.push({provider:M,reason:"disabled"});continue}if(!Array.isArray(U.models)||U.models.length===0){Y.skipped.push({provider:M,reason:"empty whitelist"});continue}let K=[];try{K=sj(M)}catch{}let k=new Map(K.map((N)=>[N.id,N])),O=new Map((q.get(M)??[]).map((N)=>[N.id,N])),D=[];for(let N of U.models){if(!z.has(N))continue;let G=k.get(N),n=O.get(N);if(G){D.push({id:G.id,name:G.name,reasoning:G.reasoning,contextWindow:G.contextWindow,maxTokens:G.maxTokens,cost:G.cost,input:G.input,api:G.api,thinkingLevelMap:G.thinkingLevelMap});continue}if(!n)continue;let J$=M==="anthropic"?"anthropic-messages":U.apiOverride??"openai-responses";D.push({id:n.id,name:n.name,reasoning:n.reasoning,contextWindow:n.contextWindow,maxTokens:n.maxTokens,cost:n.cost,input:["text"],api:J$})}if(D.length===0){Y.skipped.push({provider:M,reason:"no whitelisted models present on proxy"});continue}let A=U.apiOverride??D[0].api;if(!f$.has(A)){Y.skipped.push({provider:M,reason:`api "${A}" not in allowlist`});continue}let B=D.map((N)=>{let G=j.overrides[N.id]??{},n={id:N.id,name:typeof G.name==="string"?G.name:N.name,api:A,reasoning:typeof G.reasoning==="boolean"?G.reasoning:N.reasoning,input:N.input,cost:G.cost??N.cost,contextWindow:typeof G.contextWindow==="number"?G.contextWindow:N.contextWindow,maxTokens:typeof G.maxTokens==="number"?G.maxTokens:N.maxTokens};if(N.thinkingLevelMap)n.thinkingLevelMap=N.thinkingLevelMap;return n}),C={name:M,baseUrl:h$(A,j.proxy.endpoint),apiKey:Q,authHeader:!0,api:A,models:B};$.registerProvider(M,C),X.add(M),Y.registered.push({provider:M,modelCount:B.length,api:A})}let E=new Map(J.customPool.map((M)=>[M.id,M]));for(let[M,U]of Object.entries(j.customProviders)){if(!b$($,Z))return Y;if(!f$.has(U.api)){Y.skipped.push({provider:M,reason:`api "${U.api}" not in allowlist`});continue}let K=U.models.filter((D)=>z.has(D.id));if(K.length===0){Y.skipped.push({provider:M,reason:"no configured models present on proxy"});continue}let k=K.map((D)=>{let A=E.get(D.id),B=X$(D.id),C=j.overrides[D.id]??{};return{id:D.id,name:D.name??A?.name??B.name??D.id,api:U.api,reasoning:oj(D.reasoning,A?.reasoning,B.reasoning,C.reasoning)??!1,input:["text"],cost:D.cost??A?.cost??B.cost??{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:F0(D.contextWindow,A?.contextWindow,B.contextWindow,C.contextWindow)??128000,maxTokens:F0(D.maxTokens,A?.maxTokens,B.maxTokens,C.maxTokens)??16000}}),O={name:M,baseUrl:h$(U.api,j.proxy.endpoint),apiKey:Q,authHeader:!0,api:U.api,models:k};$.registerProvider(M,O),X.add(M),Y.registered.push({provider:M,modelCount:k.length,api:U.api})}if(b$($,Z))L0($,X,Y);return F.debug(`applyAll: registered ${Y.registered.length} providers, skipped ${Y.skipped.length}, unregistered ${Y.unregistered.length}`),Y}function F0(...$){for(let j of $)if(typeof j==="number")return j;return}function oj(...$){for(let j of $)if(typeof j==="boolean")return j;return}var ij=15000,H$=null;function q$(){H$=null}async function K0($,j,J={}){if(!J.force&&H$&&Date.now()-H$.fetchedAt<$.usageCacheTtlMs)return H$.doc;if(!j)throw Error("usage key not configured; set proxy.usageKey in config and rerun");let Z=new URL("/api/usage",new URL($.proxy.endpoint).origin).toString(),Y=new AbortController,X=setTimeout(()=>Y.abort(Error("timeout")),ij),z;try{z=await fetch(Z,{headers:{"X-Plugin-Key":j,Accept:"application/json","User-Agent":z$},signal:Y.signal})}finally{clearTimeout(X)}if(!z.ok)throw Error(`/api/usage returned ${z.status}`);let Q=await z.json();if(!Q||Q.schemaVersion!==1||!Array.isArray(Q.accounts))throw Error("/api/usage returned unexpected payload shape");return H$={fetchedAt:Date.now(),doc:Q},F.debug("usage fetched, accounts:",Q.accounts.length),Q}import{getKeybindings as n1,matchesKey as u}from"@duckmind/dm-tui";import{visibleWidth as r$}from"@duckmind/dm-tui";import{visibleWidth as S0}from"@duckmind/dm-tui";function S($,j){let J=S0($);if(J===j)return $;if(J<j)return $+" ".repeat(j-J);return i$($,j)}function i$($,j){if(j<=0)return"\x1B[0m";let J="",Z=0,Y=0;while(Y<$.length){let X=$[Y];if(X==="\x1B"&&$[Y+1]==="["){let Q=$.indexOf("m",Y+2);if(Q>=0){J+=$.slice(Y,Q+1),Y=Q+1;continue}}let z=S0(X);if(Z+z>j)break;J+=X,Z+=z,Y++}return`${J}\x1B[0m`}var rj="│",dj="╭",tj="╮",aj="╰",ej="╯",G$="─";function d($,j){let J=Math.max(4,j.width);return[$1($,j.title,j.titleColor??"accent",J),...j.lines.map((Z)=>j1($,Z,J)),J1($,j.footer,J)]}function $1($,j,J,Z){let Y=j??"",X=r$(Y),z=Math.max(0,Z-3-X),Q=Y?$.bold($.fg(J,Y)):"",q=`${dj}${G$}${Q}${G$.repeat(z)}${tj}`;return $.fg("borderAccent",q)}function j1($,j,J){let Z=Math.max(0,J-2),Y=$.fg("borderAccent",rj);return`${Y}${S(j,Z)}${Y}`}function J1($,j,J){let Z=`${aj}${G$}`,Y=`${ej}`,X=3;if(!j||!j.hint&&!j.badge){let O=Math.max(0,J-3);return $.fg("borderAccent",`${Z}${G$.repeat(O)}${Y}`)}let z=j.hint??"",Q=j.badge??"",q=r$(z),E=r$(Q),M=Math.max(0,J-3-q-E),U=$.fg("dim",z),K=Q?$.fg("muted",Q):"",k=G$.repeat(M);return`${$.fg("borderAccent",Z)}${U}${$.fg("borderAccent",k)}${K}${$.fg("borderAccent",Y)}`}function P$($){return Math.max(0,$-2)}import{existsSync as I0}from"node:fs";import{homedir as Y1}from"node:os";import{getKeybindings as Z1,Input as d$,matchesKey as X1}from"@duckmind/dm-tui";var Q1=[{label:"endpoint",hint:"OpenAI-style base URL of your CliProxyAPI deployment, must include /v1",required:!0,validate:($)=>{try{if(!new URL($).pathname.endsWith("/v1"))return"must end with /v1";return null}catch{return"not a valid URL"}}},{label:"apiKey",hint:"CliProxyAPI bearer key. Input is masked; blank keeps an existing key",required:!0,secret:!0},{label:"providerPrefix",hint:"Prefix for your custom provider names. Suggested groups become <prefix>-glm, <prefix>-gemini, etc. Use any short slug (letters/digits/dashes).",required:!0,validate:($)=>/^[a-z0-9][a-z0-9-]*$/i.test($)?null:"letters/digits/dashes only"},{label:"usageKey",hint:"Optional X-Plugin-Key for /api/usage. Input is masked; blank keeps it, '-' clears it",required:!1,secret:!0}];async function N$($,j=!1){let J=r(),Z={endpoint:J.proxy.endpoint??"",apiKey:J.proxy.apiKey??"",providerPrefix:J.proxy.providerPrefix??"",usageKey:J.proxy.usageKey??""},Y=!1;for(let z of Q1){let Q=Z[z.label]??"",q=z.secret?"":j?Z[z.label]??z.initialValue??"":Z[z.label]??"";if(!j&&q)continue;let E=z.secret&&Q?{...z,required:!1}:z,M=await M1($,E,q||z.initialValue||"");if(M===void 0){Y=!0;break}let U=q1(z,Q,M);if(U===null){if(z.required){$.ui.notify(`${z.label} is required — aborted`,"warning"),Y=!0;break}}Z[z.label]=U??""}if(Y)return!1;let X={...J};if(X.proxy={...J.proxy,endpoint:Z.endpoint??"",apiKey:Z.apiKey??"",providerPrefix:Z.providerPrefix??""},Z.usageKey)X.proxy.usageKey=Z.usageKey;else delete X.proxy.usageKey;return I$(X),$.ui.notify(`saved to ${l}`,"info"),!0}function z1($){if($.startsWith("!")||$.startsWith("$"))return $;if($.startsWith("~/")){let j=$.replace(/^~/,Y1());return I0(j)?`!cat ${$}`:$}if($.startsWith("/"))return I0($)?`!cat ${$}`:$;return $}function q1($,j,J){let Z=J.trim();if(!Z){if($.secret&&j)return j;return $.required?null:""}if($.secret&&!$.required&&Z==="-")return"";return $.label==="providerPrefix"||$.label==="endpoint"?Z:z1(Z)}async function M1($,j,J){return $.ui.custom((Z,Y,X,z)=>E1(Z,Y,j,J,z),{overlay:!0,overlayOptions:{width:100,maxHeight:"60%"}})}class b0{rawInput=new d$;displayInput=new d$;pasteActive=!1;get focused(){return this.rawInput.focused}set focused($){this.rawInput.focused=$,this.displayInput.focused=$}set onSubmit($){this.rawInput.onSubmit=$}set onEscape($){this.rawInput.onEscape=$}getValue(){return this.rawInput.getValue()}setValue($){this.rawInput.setValue($),this.displayInput.setValue("•".repeat($.length))}handleInput($){this.rawInput.handleInput($),this.displayInput.handleInput(this.maskInput($)),this.displayInput.setValue("•".repeat(this.rawInput.getValue().length))}render($){return this.displayInput.render($)}invalidate(){this.rawInput.invalidate(),this.displayInput.invalidate()}maskInput($){let j="";for(let J=0;J<$.length;){if($.startsWith("\x1B[200~",J)){this.pasteActive=!0,j+="\x1B[200~",J+=6;continue}if($.startsWith("\x1B[201~",J)){this.pasteActive=!1,j+="\x1B[201~",J+=6;continue}if(!this.pasteActive&&$.charCodeAt(J)===27)return $;let Z=$.codePointAt(J),Y=String.fromCodePoint(Z);j+=Z<32||Z===127?Y:"•",J+=Y.length}return j}}function E1($,j,J,Z,Y){let X=J.secret?new b0:new d$;X.setValue(Z),X.focused=!0;let z=null,Q=(q)=>{if(!q&&!J.required){Y("");return}if(!q&&J.required){z="required field",$.requestRender();return}if(J.validate){let E=q.trim();if(E&&!E.startsWith("!")&&!E.startsWith("$")){let M=J.validate(E);if(M){z=M,$.requestRender();return}}}Y(q)};return X.onSubmit=Q,X.onEscape=()=>Y(void 0),{render(q){let E=P$(q),M=X.render(E-4),U=["",` ${j.fg("dim",J.hint)}`,"",...M.map((K)=>` ${j.fg("accent",`❯ ${K}`)}`),"",z?` ${j.fg("error",`! ${z}`)}`:` ${j.fg("dim","enter = save · esc = cancel")}`];return d(j,{width:q,title:` setup: ${J.label} `,lines:U,footer:{hint:" enter = save · esc = cancel "}})},invalidate(){X.invalidate()},handleInput(q){if(Z1().matches(q,"tui.select.cancel")||X1(q,"escape")){Y(void 0);return}z=null,X.handleInput(q),$.requestRender()}}}import{visibleWidth as x$}from"@duckmind/dm-tui";function P0($,j,J,Z){let Y=$.fg("borderAccent","│"),X=j.map((z,Q)=>{let q=`${Q+1} ${z.label}`;return Q===J?$.bold($.fg("accent",` ${q} `)):$.fg("dim",` ${q} `)});return S(` ${X.join(Y)}`,Z)}function x0($,j,J){let Z=j.join($.fg("dim"," "));return S(` ${Z}`,J)}function n0($,j){return $.fg("borderAccent","─".repeat(Math.max(0,j)))}function t($,j,J){let Z=$.slice(j,j+J);while(Z.length<J)Z.push("");return Z}function n$($,j,J,Z,Y,X){if(Y<=Z)return 0;let z=Math.max(0,Y-Z);if(j<X)return 0;let Q=Math.max(0,Math.min(J,z)),q=Q+X;if($<q)Q=Math.max(0,$-X);let E=Q+Z-1;if(j>E)Q=Math.min(z,j-(Z-1));return Q}function y$($,j,J){let Z=Math.max(0,J-j);return Math.max(0,Math.min($,Z))}import{matchesKey as a}from"@duckmind/dm-tui";import{existsSync as U1,readFileSync as V1}from"node:fs";import{homedir as w0}from"node:os";import{join as C0}from"node:path";var D1=C0(w0(),".dm","models.json"),R1=C0(w0(),".dm","auth.json");function y0($){if(!U1($))return null;try{return JSON.parse(V1($,"utf8"))}catch{return null}}function v0($){let j=[],J=new Set([...Object.keys($.builtinProviders),...Object.keys($.customProviders)]),Z=y0(D1);if(Z&&typeof Z==="object"){for(let X of Object.keys(Z))if(J.has(X))j.push({kind:"models.json",provider:X,detail:`provider "${X}" has user overrides in ~/.dm/models.json which DM merges on top of our registration`})}let Y=y0(R1);if(Y&&typeof Y==="object"){for(let X of Object.keys(Y))if(J.has(X))j.push({kind:"auth.json",provider:X,detail:`provider "${X}" has user-set credentials in ~/.dm/auth.json which take priority over our apiKey`})}return j}function u0($){let{theme:j,cfg:J}=$,Z=0,Y=()=>{let Q=$.getDiscovery(),q=(O)=>j.fg("success",O),E=(O)=>j.fg("error",O),M=(O)=>j.fg("dim",O),U=[];U.push(`${M("endpoint")} ${J.proxy.endpoint||E("(unset)")}`),U.push(`${M("apiKey")} ${x(J.proxy.apiKey)?q("resolves"):E("empty after resolution")}`),U.push(`${M("usageKey")} ${J.proxy.usageKey?x(J.proxy.usageKey)?q("resolves"):E("set but empty"):M("not configured")}`),U.push(`${M("user-agent")} ${z$}`),U.push(""),U.push(`${M("discovery")} source=${Q.source}`),U.push(`${M("upstream")} v=${Q.upstreamVersion??"(unknown)"} · ${Q.upstreamTotal} ids`);let K=Q.builtinProviders.map((O)=>`${O.name}=${O.models.length}`).join(", ")||M("(none)");U.push(`${M("built-in")} ${K}`),U.push(`${M("custom pool")} ${Q.customPool.length} models`);let k=v0(J);if(U.push(""),k.length===0)U.push(`${M("conflicts")} ${q("none")}`);else{U.push(`${M("conflicts")}`);for(let O of k)U.push(` ${j.fg("warning",`[${O.kind}]`)} ${O.detail}`)}return U};return{id:"diagnostics",label:"Diagnostics",render:(Q,q)=>{let E=Y();return Z=y$(Z,q,E.length),t(E,Z,q).map((M)=>S(` ${M}`,Q))},handleInput:(Q)=>{let q=Y().length;if(a(Q,"up")||a(Q,"k"))return Z=Math.max(0,Z-1),!0;if(a(Q,"down")||a(Q,"j"))return Z=Math.min(Math.max(0,q-1),Z+1),!0;if(a(Q,"pageUp")||a(Q,"b"))return Z=Math.max(0,Z-10),!0;if(a(Q,"pageDown")||a(Q,"f"))return Z=Math.min(Math.max(0,q-1),Z+10),!0;return!1},footerHint:()=>" ↑↓ scroll · r refresh discovery "}}import{matchesKey as T}from"@duckmind/dm-tui";function t$($){let j=new Map,J=new Map;for(let Y of $.builtinProviders){let X=[];for(let z of Y.models)j.set(z.id,{id:z.id,name:z.name,suggestedApi:Y.api,subtitle:z.name&&z.name!==z.id?z.name:void 0,ownedBy:Y.name,reasoning:z.reasoning,contextWindow:z.contextWindow,maxTokens:z.maxTokens,cost:z.cost}),X.push(z.id);J.set(Y.name,X)}let Z=[];for(let Y of $.customPool){if(!j.has(Y.id))j.set(Y.id,{id:Y.id,name:Y.name,suggestedApi:Y.api,subtitle:Y.name&&Y.name!==Y.id?Y.name:void 0,origin:Y.suggestedProvider,ownedBy:Y.ownedBy,reasoning:Y.reasoning,contextWindow:Y.contextWindow,maxTokens:Y.maxTokens,cost:Y.cost});Z.push(Y.id)}return{byId:j,builtinModelIds:J,customPoolIds:Z}}function _1($){let j=new Map;for(let[J,Z]of Object.entries($.customProviders))for(let Y of Z.models)j.set(Y.id,J);return j}function j$($,j){if(j.kind==="builtin")return[...$.builtinProviders[j.name]?.models??[]];return $.customProviders[j.name]?.models.map((J)=>J.id)??[]}function g0($,j,J){if(j.kind==="builtin"){let X=J.builtinModelIds.get(j.name)??[],z=new Set(j$($,j));return X.filter((Q)=>!z.has(Q))}let Z=_1($),Y=new Set(j$($,j));return J.customPoolIds.filter((X)=>{if(Y.has(X))return!1;let z=Z.get(X);return z===void 0||z===j.name})}function A1($){return{id:$.id,name:$.name,contextWindow:$.contextWindow,maxTokens:$.maxTokens,reasoning:$.reasoning,cost:$.cost}}function f0($,j,J){if(j.kind==="builtin"){let Y=$.builtinProviders[j.name]??{enabled:!0,models:[]},X=new Set(Y.models);X.add(J.id),$.builtinProviders[j.name]={enabled:!0,apiOverride:Y.apiOverride??null,models:Array.from(X).sort()};return}for(let[Y,X]of Object.entries($.customProviders)){if(Y===j.name)continue;let z=X.models.findIndex((Q)=>Q.id===J.id);if(z>=0)X.models.splice(z,1)}let Z=$.customProviders[j.name]??{api:j.api,models:[]};if(!Z.models.some((Y)=>Y.id===J.id))Z.models.push(A1(J));$.customProviders[j.name]=Z}function h0($,j,J){if(j.kind==="builtin"){let Y=$.builtinProviders[j.name];if(!Y)return;Y.models=Y.models.filter((X)=>X!==J),Y.enabled=Y.models.length>0;return}let Z=$.customProviders[j.name];if(!Z)return;Z.models=Z.models.filter((Y)=>Y.id!==J)}function a$($,j){if($===j)return!0;let J=["openai-completions","openai-responses"];if(J.includes($)&&J.includes(j))return!0;return!1}function p0($,j){let J=new Map;for(let Z of $){let Y=j.byId.get(Z),X=Y?.ownedBy||Y?.origin||"misc",z=J.get(X)??[];z.push(Z),J.set(X,z)}return Array.from(J.entries()).sort((Z,Y)=>Z[0].localeCompare(Y[0])).map(([Z,Y])=>({label:Z,ids:Y}))}function c0($,j,J){let Z=J.trim().toLowerCase();if(!Z)return $;return $.filter((Y)=>{if(Y.toLowerCase().includes(Z))return!0;let X=j.byId.get(Y)?.name;return X?X.toLowerCase().includes(Z):!1})}import{getModels as O1,getProviders as H1}from"@duckmind/dm-ai/compat";function e$($,j){let J=[],Z=new Set(j.builtinModelIds.keys()),Y=new Set(H1()),X=new Set([...Z,...Y]);for(let z of Array.from(X).sort()){let Q=j.builtinModelIds.get(z);if(!Q||Q.length===0)continue;let q;try{let M=O1(z).find((U)=>Q.includes(U.id));if(M)q=M.api}catch{}if(!q){let E=Q[0];q=j.byId.get(E)?.suggestedApi??"openai-responses"}if(J.push({kind:"builtin",name:z,api:q,subtitle:`built-in · ${q}`}),!$.builtinProviders[z])$.builtinProviders[z]={enabled:!1,models:[]}}for(let z of Object.keys($.customProviders).sort()){let Q=$.customProviders[z];J.push({kind:"custom",name:z,api:Q.api,subtitle:`custom · ${Q.api}`})}return J}import{getKeybindings as G1,matchesKey as M$}from"@duckmind/dm-tui";async function l0($,j){return $.ui.custom((J,Z,Y,X)=>N1(Z,`Remove custom group “${j}”?`,"All model assignments in this group will be discarded.",X),{overlay:!0,overlayOptions:{width:80,maxHeight:"40%"}})}function N1($,j,J,Z){return{render(Y){return d($,{width:Y,title:" confirm ",titleColor:"error",lines:["",` ${$.bold(j)}`,` ${$.fg("dim",J)}`,""],footer:{hint:" y / enter = remove · n / esc = cancel "}})},invalidate(){},handleInput(Y){if(G1().matches(Y,"tui.select.cancel")||M$(Y,"escape")||M$(Y,"n")||M$(Y,"q")){Z(!1);return}if(M$(Y,"y")||M$(Y,"enter")||M$(Y,"return")){Z(!0);return}}}}import{getKeybindings as W1,Input as T1,matchesKey as k1}from"@duckmind/dm-tui";async function m0($,j){let J=B$(j,"group");return $.ui.custom((Z,Y,X,z)=>B1(Z,Y,J,z),{overlay:!0,overlayOptions:{width:80,maxHeight:"40%"}})}function B1($,j,J,Z){let Y=new T1;if(J)Y.setValue(J);Y.focused=!0;let X=null;return Y.onSubmit=(z)=>{let Q=z.trim();if(!/^[a-z0-9][a-z0-9._-]*$/i.test(Q)){X="letters / digits / dot / dash / underscore only",$.requestRender();return}Z(Q)},Y.onEscape=()=>Z(null),{render(z){let Q=P$(z),q=J?`name shown in /model picker, e.g. ${J}`:"letters / digits / dot / dash / underscore",E=Y.render(Q-4),M=["",` ${j.fg("dim",q)}`,"",...E.map((U)=>` ${j.fg("accent",`❯ ${U}`)}`),"",X?` ${j.fg("error",`! ${X}`)}`:""];return d(j,{width:z,title:" new custom group ",lines:M,footer:{hint:" enter = create · esc = cancel "}})},invalidate(){Y.invalidate()},handleInput(z){if(W1().matches(z,"tui.select.cancel")||k1(z,"escape")){Z(null);return}X=null,Y.handleInput(z),$.requestRender()}}}import{visibleWidth as L1}from"@duckmind/dm-tui";function $0($,j,J){if(!j)return" ";if(J)return $.fg("accent","▶ ");return $.fg("muted","▸ ")}function s0($,j){let J=L1($);if(J===j)return $;if(J>j)return i$($,j);return S($,j)}function o0($,j,J){let{theme:Z,width:Y,isCursor:X,isFocused:z}=J,Q=$0(Z,X,z),q=$.kind==="builtin"?j.builtinProviders[$.name]?.models.length??0:j.customProviders[$.name]?.models.length??0,E=$.kind==="builtin"?Z.fg("accent","B"):Z.fg("warning","C"),M=`${Z.fg("dim","[")}${E}${Z.fg("dim","]")}`,U=q>0?Z.fg("success","●"):Z.fg("dim","○"),K=q>0?Z.fg("success",String(q).padStart(2)):Z.fg("dim"," 0"),k=X&&z?Z.bold(Z.fg("accent",$.name)):X?Z.bold($.name):$.name,O=Z.fg("dim",$.api),D=`${Q}${M} ${k} ${U}${K} ${O}`;return s0(D,Y)}function i0($,j,J,Z){let Y=$0($,j,J),X=j&&J?$.bold($.fg("accent","+ new custom group…")):$.fg("accent","+ new custom group…");return S(`${Y}${X}`,Z)}function j0($,j,J,Z,Y){let{theme:X,width:z,isCursor:Q,isFocused:q}=Y,E=$0(X,Q,q),M=J==="assigned"?X.fg("success","☑"):X.fg("dim","☐"),U=Z?` ${X.fg("warning","⚠")}`:"",K=Q&&q?X.fg("accent",$):$,k=j?.reasoning?` ${X.fg("accent","✨")}`:"",O=j?X.fg("muted",` · ${j.suggestedApi}`):X.fg("error"," · not on proxy"),D=`${E}${M} ${K}${U}${k}${O}`;return s0(D,z)}function r0($,j,J){let Z=`${$.fg("borderAccent","──")} ${$.fg("warning",j)} ${$.fg("borderAccent","──")}`;return S(` ${Z}`,J)}function J0($,j,J){return S(` ${$.fg("dim",j)}`,J)}function d0($,j,J,Z){let Y=Z?$.bold($.fg("accent","❚ ")):$.fg("dim"," ");return S(`${Y}${j}`,J)}function t0($){let{tui:j,theme:J,ctx:Z,cfg:Y}=$,X=$.onChange??(()=>{}),z=t$($.getDiscovery()),Q=e$(Y,z),q="providers",E=0,M=0,U=0,K=0,k=0,O=0,D="",A=!1,B=()=>{if(Q.length===0)return null;let V=Math.max(0,Math.min(E,Q.length-1));return Q[V]??null},C=(V)=>{let H=c0(g0(Y,V,z),z,D);return p0(H,z)},N=(V)=>C(V).flatMap((H)=>H.ids),G=()=>{Q=e$(Y,z);let V=Math.max(0,Q.length);if(E>V)E=V;let H=B();if(H){let P=j$(Y,H).length,W=N(H).length;if(M>=P)M=Math.max(0,P-1);if(U>=W)U=Math.max(0,W-1)}},n=()=>{z=t$($.getDiscovery()),G()},J$=(V)=>{let H=["providers","assigned","pool"],P=H.indexOf(q);q=V?H[(P-1+H.length)%H.length]:H[(P+1)%H.length]},_=(V)=>{if(q==="providers"){let W=Q.length+1;E=Math.max(0,Math.min(E+V,W-1));return}let H=B();if(!H)return;if(q==="assigned"){let W=j$(Y,H).length;if(W===0)return;M=Math.max(0,Math.min(M+V,W-1));return}let P=N(H).length;if(P===0)return;U=Math.max(0,Math.min(U+V,P-1))},b=async()=>{if(q==="providers"){if(E===Q.length){let W=await m0(Z,Y.proxy.providerPrefix);if(W&&!Y.customProviders[W]){if(Y.customProviders[W]={api:"openai-completions",models:[]},X(),G(),E=Q.findIndex((I)=>I.kind==="custom"&&I.name===W),E<0)E=Q.length-1;q="pool"}j.requestRender();return}q="pool";return}let V=B();if(!V)return;if(q==="assigned"){let W=j$(Y,V)[M];if(!W)return;h0(Y,V,W),X(),G();return}let H=N(V)[U];if(!H)return;let P=z.byId.get(H);if(!P)return;f0(Y,V,P),X(),G()},p=async()=>{if(q!=="providers")return;let V=B();if(!V||V.kind!=="custom")return;if(!await l0(Z,V.name)){j.requestRender();return}if(delete Y.customProviders[V.name],X(),G(),E>=Q.length)E=Math.max(0,Q.length-1);j.requestRender()},g=(V,H)=>c(V,H);function c(V,H){let P=Math.max(70,V),W=Math.min(56,Math.max(34,Math.floor(P*0.4))),I=P-W-1,s=Math.max(8,H),o=Math.max(5,Math.floor((s-1)/2)),Y0=s-o,m=B(),E$=0,w$=0,C$=0,Z0=0,v$=0,X0=0,e=[];e.push(Y$(" providers ",W,q==="providers"));for(let L=0;L<Q.length;L++){let f=Q[L],v=q==="providers"&&L===E;if(v)E$=e.length,w$=E$;e.push(o0(f,Y,{theme:J,width:W,isCursor:v,isFocused:q==="providers"}))}{let L=q==="providers"&&E===Q.length;if(L)E$=e.length,w$=E$;e.push(i0(J,L,q==="providers",W))}let $$=[],Mj=m?`assigned to ${J.bold(m.name)} ${J.fg("dim",`· ${m.api}`)}`:J.fg("dim","no provider selected");if($$.push(Y$(" assigned ",I,q==="assigned")),$$.push(d0(J,Mj,I,q==="assigned")),m){let L=j$(Y,m);if(L.length===0)$$.push(J0(J,"(nothing assigned yet)",I));for(let f=0;f<L.length;f++){let v=L[f],h=z.byId.get(v),k$=q==="assigned"&&f===M;if(k$)C$=$$.length,Z0=C$;let Z$=h?!a$(m.api,h.suggestedApi):!1;$$.push(j0(v,h,"assigned",Z$,{theme:J,width:I,isCursor:k$,isFocused:q==="assigned"}))}}let i=[];if(i.push(y(I,q==="pool")),m){let L=C(m);if(L.reduce((v,h)=>v+h.ids.length,0)===0)i.push(J0(J,D?`(no matches for "${D}")`:"(no models available)",I));else{let v=0;for(let h of L){let k$=i.length;i.push(r0(J,h.label,I));for(let Z$=0;Z$<h.ids.length;Z$++){let U0=h.ids[Z$],g$=z.byId.get(U0),V0=q==="pool"&&v===U;if(V0)v$=i.length,X0=Z$===0?k$:v$;let Dj=g$?!a$(m.api,g$.suggestedApi):!1;i.push(j0(U0,g$,"pool",Dj,{theme:J,width:I,isCursor:V0,isFocused:q==="pool"})),v++}}}}let Ej=Math.max(1,Y0-1),Q0=n$(w$,E$,K,s,e.length,1),z0=n$(q==="assigned"?Z0:0,q==="assigned"?C$:0,k,o,$$.length,2),q0=n$(q==="pool"?X0:0,q==="pool"?v$:0,O,Ej,i.length,1);K=Q0,k=z0,O=q0;let M0=t(e,Q0,s),Uj=t($$,z0,o),Vj=t(i,q0,Y0),E0=J.fg("borderAccent","│"),u$=[];for(let L=0;L<s;L++){let f=S(M0[L]??"",W),v=L<o?Uj[L]:Vj[L-o],h=S(v,I);u$.push(`${f}${E0}${h}`)}let T$=o;if(T$>0&&T$<s){let L=S(M0[T$]??"",W),f=J.fg("borderAccent","─".repeat(I));u$[T$]=`${L}${E0}${f}`}return u$}function Y$(V,H,P){let W=P?J.bold(J.fg("accent",V)):J.fg("muted",V),I=J.fg("borderAccent","─".repeat(Math.max(0,H-x$(V)-1)));return S(`${W}${I}`,H)}function y(V,H){let W=H?J.bold(J.fg("accent"," available pool ")):J.fg("muted"," available pool "),I="";if(A)I=`${J.fg("dim"," /")}${J.fg("accent",D)}${J.fg("accent","█")} `;else if(D)I=`${J.fg("dim"," /")}${J.fg("warning",D)} `;let s=x$(" available pool ")+x$(I),o=J.fg("borderAccent","─".repeat(Math.max(0,V-s-1)));return S(`${W}${I}${o}`,V)}function W$(V){if(A){if(T(V,"escape"))return D="",A=!1,U=0,!0;if(T(V,"enter")||T(V,"return"))return A=!1,!0;if(T(V,"backspace")||T(V,"delete"))return D=D.slice(0,-1),U=0,!0;if(V.length===1&&V>=" "&&V!=="")return D+=V,U=0,!0;return!0}if(T(V,"tab"))return J$(!1),!0;if(T(V,"shift+tab"))return J$(!0),!0;if(T(V,"/"))return q="pool",A=!0,!0;if(T(V,"enter")||T(V,"return")||T(V,"space"))return b().then(()=>!0);if(q==="providers"&&(T(V,"d")||T(V,"delete")||T(V,"backspace")))return p().then(()=>!0);if(T(V,"up")||T(V,"k"))return _(-1),!0;if(T(V,"down")||T(V,"j"))return _(1),!0;if(T(V,"pageUp")||T(V,"b"))return _(-8),!0;if(T(V,"pageDown")||T(V,"f"))return _(8),!0;if(T(V,"left")||T(V,"h"))return q="providers",!0;if(T(V,"right")||T(V,"l")){if(q==="providers")q="assigned";else if(q==="assigned")q="pool";return!0}return!1}function qj(){if(A)return" type to filter · ↵ apply · esc clear ";return" tab ↔ panel · ↑↓ nav · ↵ move · / filter · d remove group "}return{id:"models",label:"Models",render:g,handleInput:W$,footerHint:qj,rebuild:n}}import{matchesKey as w}from"@duckmind/dm-tui";var R={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",accent:"\x1B[38;5;111m",muted:"\x1B[38;5;245m",dim2:"\x1B[38;5;240m",green:"\x1B[38;5;114m",yellow:"\x1B[38;5;179m",red:"\x1B[38;5;167m",greenBg:"\x1B[48;5;22m",yellowBg:"\x1B[48;5;94m",redBg:"\x1B[48;5;52m"};function a0($,j={}){let J=[],Z=new Map,Y=0;for(let X of $.accounts){if(!j.showDisabled&&X.disabled){Y++;continue}let z=Z.get(X.provider)??[];z.push(X),Z.set(X.provider,z)}if(J.push(`${R.dim}generated ${$.generatedAt}${R.reset}`),$.unsupportedProviders.length>0)J.push(`${R.dim}providers without quota lookup:${R.reset} ${$.unsupportedProviders.join(", ")}`);if(Y>0)J.push(`${R.dim}hidden disabled accounts:${R.reset} ${Y} ${R.dim2}(press d to show)${R.reset}`);for(let[X,z]of Array.from(Z.entries()).sort()){J.push(""),J.push(`${R.bold}${R.accent}▌ ${X}${R.reset}`);for(let Q of z){if(J.push(K1(Q)),!Q.supported){J.push(` ${R.dim2}(provider not quota-aware)${R.reset}`);continue}if(Q.error){if(j.verbose)for(let E of F1(Q.error,88))J.push(` ${R.red}${E}${R.reset}`);continue}let q=Q.groups??[];if(q.length===0){J.push(` ${R.dim2}(no active quota windows)${R.reset}`);continue}for(let E of q)J.push(I1(E))}}return J.push(""),J.push(`${R.dim}legend:${R.reset} ${e0} ok ${$j} error/unavailable ${jj} disabled ${R.green}✓N${R.reset}/${R.red}✗N${R.reset} request counters ${R.dim}v = verbose, d = show disabled${R.reset}`),J}function F1($,j){if($.length<=j)return[$];let J=[],Z=$;while(Z.length>j){let Y=Z.lastIndexOf(" ",j);if(Y<j*0.6)Y=j;J.push(Z.slice(0,Y)),Z=Z.slice(Y).replace(/^\s+/,"")}if(Z)J.push(Z);return J}function K1($){let j=S1($),J=`${R.green}✓${$.success}${R.reset} ${R.red}✗${$.failed}${R.reset}`,Z=Zj($.label,36);return` ${j} ${R.bold}${Z}${R.reset} ${J}`}var e0=`${R.green}●${R.reset}`,$j=`${R.yellow}⚠${R.reset}`,jj=`${R.dim2}⊘${R.reset}`;function S1($){if($.disabled)return jj;if($.unavailable||$.error||$.status&&$.status!=="active")return $j;return e0}function I1($){let j=Yj($.remainingFraction),J=Math.round(j*100),Z=b1(j),Y=$.resetTime?` ${R.dim}reset ${x1($.resetTime)}${R.reset}`:"",X=Zj($.label,28).padEnd(28," ");return` ${Z} ${P1(J,j)} ${X}${Y}`}function b1($){let j=Yj($),J=Math.round(j*18),Z=Jj(j),Y=R.dim2,X="█".repeat(J),z="░".repeat(18-J);return`${Y}[${Z}${X}${Y}${z}]${R.reset}`}function P1($,j){return`${Jj(j)}${String($).padStart(3)}%${R.reset}`}function Jj($){if($>=0.4)return R.green;if($>=0.15)return R.yellow;return R.red}function x1($){let j=Date.parse($);if(!Number.isFinite(j))return $;let J=j-Date.now();if(J<=0)return"now";let Z=Math.round(J/60000);if(Z<60)return`${Z}m`;let Y=Math.round(Z/60);if(Y<48)return`${Y}h`;return`${Math.round(Y/24)}d`}function Yj($){return Math.max(0,Math.min(1,$))}function Zj($,j){if($.length<=j)return $;return`${$.slice(0,j-1)}…`}function Xj($){let{tui:j,theme:J,cfg:Z}=$,Y="idle",X=null,z="",Q=0,q=!1,E=!1,M=(D)=>{Y="loading",j.requestRender();let A=x(Z.proxy.usageKey);K0(Z,A,{force:D}).then((B)=>{X=B,Y="ready",Q=0,j.requestRender()}).catch((B)=>{z=B.message,Y="error",j.requestRender()})},U=()=>{if(Y==="idle"||Y==="loading")return[J.fg("dim"," loading usage…")];if(Y==="error")return[J.fg("error",` usage failed: ${z}`)];if(!X)return[J.fg("dim"," (no data)")];return a0(X,{showDisabled:q,verbose:E})};return{id:"usage",label:"Usage",render:(D,A)=>{let B=U();return Q=y$(Q,A,B.length),t(B,Q,A).map((C)=>S(` ${C}`,D))},handleInput:(D)=>{if(w(D,"d"))return q=!q,Q=0,!0;if(w(D,"v"))return E=!E,Q=0,!0;let A=U().length;if(w(D,"up")||w(D,"k"))return Q=Math.max(0,Q-1),!0;if(w(D,"down")||w(D,"j"))return Q=Math.min(Math.max(0,A-1),Q+1),!0;if(w(D,"pageUp")||w(D,"b"))return Q=Math.max(0,Q-10),!0;if(w(D,"pageDown")||w(D,"f"))return Q=Math.min(Math.max(0,A-1),Q+10),!0;if(w(D,"home")||w(D,"g"))return Q=0,!0;if(w(D,"end")||w(D,"shift+g"))return Q=Math.max(0,A-1),!0;return!1},footerHint:()=>{let D=q?J.fg("success","[d disabled]"):"d disabled",A=E?J.fg("success","[v verbose]"):"v verbose";return` ↑↓ scroll · ${D} · ${A} `},onActivate:()=>{if(Y==="idle")M(!1)},reload:()=>{if(q$(),Y!=="idle")M(!0)}}}function Qj($){let{dm:j,ctx:J,tui:Z,theme:Y,cfg:X,done:z}=$,Q=$.discovery,q=!1,E="",M=t0({tui:Z,theme:Y,ctx:J,cfg:X,getDiscovery:()=>Q,onChange:()=>{q=!0,E=""}}),U=Xj({tui:Z,theme:Y,cfg:X}),K=u0({theme:Y,cfg:X,getDiscovery:()=>Q}),k=[M,U,K],O=0,D=()=>k[O],A=(_)=>{if(_<0||_>=k.length||_===O)return;O=_,D().onActivate?.(),Z.requestRender()},B=()=>{E=Y.fg("dim","saving…"),I$(X),Z.requestRender();let _=A$(j);O$(j,X,Q,_).then((b)=>{q=!1,E=Y.fg("success",`✓ settings saved · ${b.registered.length} registered, ${b.skipped.length} skipped`),Z.requestRender()}).catch((b)=>{E=Y.fg("error",`save failed: ${b.message}`),Z.requestRender()})},C=async()=>{E=Y.fg("dim","refreshing…"),Z.requestRender();try{let _=A$(j),b=x(X.proxy.apiKey);Q=await _$(X,b),q$(),M.rebuild(),U.reload();let p=await O$(j,X,Q,_);E=Y.fg("success",`✓ refreshed · ${p.registered.length} providers (${Q.source})`)}catch(_){E=Y.fg("error",`refresh failed: ${_.message}`)}Z.requestRender()},N=async()=>{if(!await N$(J,!0)){Z.requestRender();return}let b=r();X.proxy={...X.proxy,...b.proxy},await C()},G=()=>{let _=X.proxy.endpoint||"(unset)",b=_.length>40?`…${_.slice(-39)}`:_,p=Boolean(x(X.proxy.apiKey)),g=0,c=0;for(let y of Object.values(X.builtinProviders)){if(y.enabled&&y.models.length>0)g++;c+=y.models.length}for(let y of Object.values(X.customProviders)){if(y.models.length>0)g++;c+=y.models.length}let Y$=E?E:q?Y.fg("warning","● unsaved — press s to save"):Y.fg("dim","✓ saved");return[`${Y.fg("dim","endpoint")} ${b}`,`${Y.fg("dim","key")} ${p?Y.fg("success","✓"):Y.fg("error","✗")}`,`${Y.fg("dim","providers")} ${g}`,`${Y.fg("dim","models")} ${c}`,Y$]},n=(_)=>{let b=Z.rows??40,g=Math.max(16,Math.min(b-6,38))-2,c=Math.max(72,_-2),Y$=Math.max(6,g-3),y=[];return y.push(x0(Y,G(),c)),y.push(P0(Y,k.map((W$)=>({id:W$.id,label:W$.label})),O,c)),y.push(n0(Y,c)),y.push(...D().render(c,Y$)),d(Y,{width:_,title:" /cliproxy ",lines:y,footer:{hint:D().footerHint(),badge:" [ ] tab · r refresh · e setup · s save · q close "}})},J$=(_)=>{if(n1().matches(_,"tui.select.cancel")||u(_,"q")||u(_,"escape"))return z(),!0;if(u(_,"]")||u(_,"tab"))return A((O+1)%k.length),!0;if(u(_,"[")||u(_,"shift+tab"))return A((O-1+k.length)%k.length),!0;if(u(_,"1"))return A(0),!0;if(u(_,"2"))return A(1),!0;if(u(_,"3"))return A(2),!0;if(u(_,"r"))return C().then(()=>!0);if(u(_,"e"))return N().then(()=>!0);if(u(_,"s"))return B(),!0;return!1};return{render:n,invalidate(){},handleInput(_){let b=D().handleInput(_);if(b instanceof Promise){E="",b.then((g)=>{if(g)Z.requestRender()});return}if(b){E="",Z.requestRender();return}let p=J$(_);if(p instanceof Promise)p.then((g)=>{if(g)Z.requestRender()});else if(p)Z.requestRender()}}}async function zj($,j,J,Z){if(!j.hasUI){j.ui.notify("interactive UI required for /cliproxy","warning");return}let Y=structuredClone(J);p$(!0);try{await j.ui.custom((X,z,Q,q)=>Qj({dm:$,ctx:j,tui:X,theme:z,cfg:Y,discovery:Z,done:q}),{overlay:!0,overlayOptions:{width:170,maxHeight:"94%"}})}finally{p$(!1)}}function vJ($){$.registerCommand("cliproxy",{description:"Manage proxy models, usage, and diagnostics in one hub overlay",handler:y1.bind(null,$)}),$.registerCommand("cliproxy-setup",{description:"Set endpoint, API key, and (optional) usage key for the proxy",handler:w1.bind(null,$)})}async function y1($,j,J){let Z=r();if(!Z.proxy.endpoint||!x(Z.proxy.apiKey)){if(J.ui.notify("endpoint or API key not set — launching setup first","info"),!await N$(J,!0))return}let Y=r(),X=x(Y.proxy.apiKey),z;try{z=await _$(Y,X)}catch(Q){J.ui.notify(`discovery failed: ${Q.message}`,"error");return}await zj($,J,Y,z)}async function w1($,j,J){if(!await N$(J,!0))return;let Y=r();try{let X=A$($),z=await _$(Y,x(Y.proxy.apiKey)),Q=await O$($,Y,z,X);q$(),J.ui.notify(`setup ok · ${Q.registered.length} providers registered (source=${z.source})`,"info")}catch(X){J.ui.notify(`setup saved, but apply failed: ${X.message}`,"warning")}}export{vJ as registerCommands};
|
|
1
|
+
import { applyAll, beginProviderRefresh } from "./apply.js";
|
|
2
|
+
import { loadConfig, resolveConfigValue } from "./config.js";
|
|
3
|
+
import { fetchDiscovery } from "./fetch-models.js";
|
|
4
|
+
import { clearUsageCache } from "./fetch-usage.js";
|
|
5
|
+
import { runHub } from "./ui-hub/index.js";
|
|
6
|
+
import { runSetup } from "./ui-setup.js";
|
|
7
|
+
export function registerCommands(dm) {
|
|
8
|
+
dm.registerCommand("cliproxy", {
|
|
9
|
+
description: "Manage proxy models, usage, and diagnostics in one hub overlay",
|
|
10
|
+
handler: handleCliproxy.bind(null, dm)
|
|
11
|
+
});
|
|
12
|
+
dm.registerCommand("cliproxy-setup", {
|
|
13
|
+
description: "Set endpoint, API key, and (optional) usage key for the proxy",
|
|
14
|
+
handler: handleSetup.bind(null, dm)
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
async function handleCliproxy(dm, _args, ctx) {
|
|
18
|
+
const cfg = loadConfig();
|
|
19
|
+
if (!cfg.proxy.endpoint || !resolveConfigValue(cfg.proxy.apiKey)) {
|
|
20
|
+
ctx.ui.notify("endpoint or API key not set — launching setup first", "info");
|
|
21
|
+
const ok = await runSetup(ctx, true);
|
|
22
|
+
if (!ok)
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const current = loadConfig();
|
|
26
|
+
const resolvedKey = resolveConfigValue(current.proxy.apiKey);
|
|
27
|
+
let discovery;
|
|
28
|
+
try {
|
|
29
|
+
discovery = await fetchDiscovery(current, resolvedKey);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
ctx.ui.notify(`discovery failed: ${err.message}`, "error");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
await runHub(dm, ctx, current, discovery);
|
|
35
|
+
}
|
|
36
|
+
async function handleSetup(dm, _args, ctx) {
|
|
37
|
+
const ok = await runSetup(ctx, true);
|
|
38
|
+
if (!ok)
|
|
39
|
+
return;
|
|
40
|
+
const cfg = loadConfig();
|
|
41
|
+
try {
|
|
42
|
+
const refreshGeneration = beginProviderRefresh(dm);
|
|
43
|
+
const discovery = await fetchDiscovery(cfg, resolveConfigValue(cfg.proxy.apiKey));
|
|
44
|
+
const rep = await applyAll(dm, cfg, discovery, refreshGeneration);
|
|
45
|
+
clearUsageCache();
|
|
46
|
+
ctx.ui.notify(`setup ok · ${rep.registered.length} providers registered (source=${discovery.source})`, "info");
|
|
47
|
+
} catch (err) {
|
|
48
|
+
ctx.ui.notify(`setup saved, but apply failed: ${err.message}`, "warning");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -1 +1,81 @@
|
|
|
1
|
-
|
|
1
|
+
export const ALLOWED_APIS = new Set([
|
|
2
|
+
"openai-completions",
|
|
3
|
+
"openai-responses",
|
|
4
|
+
"anthropic-messages"
|
|
5
|
+
]);
|
|
6
|
+
export function baseUrlFor(api, endpoint) {
|
|
7
|
+
const trimmed = endpoint.replace(/\/+$/, "");
|
|
8
|
+
if (api === "anthropic-messages") {
|
|
9
|
+
return trimmed.replace(/\/v1$/, "");
|
|
10
|
+
}
|
|
11
|
+
return trimmed;
|
|
12
|
+
}
|
|
13
|
+
export function isExcluded(id, patterns) {
|
|
14
|
+
if (!id)
|
|
15
|
+
return true;
|
|
16
|
+
for (const pat of patterns) {
|
|
17
|
+
const rx = new RegExp("^" + pat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$");
|
|
18
|
+
if (rx.test(id))
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
export const DEFAULT_CONTEXT_WINDOW = 128000;
|
|
24
|
+
export const DEFAULT_MAX_TOKENS = 16000;
|
|
25
|
+
export const DEFAULT_COST = {
|
|
26
|
+
input: 0,
|
|
27
|
+
output: 0,
|
|
28
|
+
cacheRead: 0,
|
|
29
|
+
cacheWrite: 0
|
|
30
|
+
};
|
|
31
|
+
const REASONING_RX = [
|
|
32
|
+
/.*-thinking$/,
|
|
33
|
+
/^gpt-5(\.\d+)?(-.*)?$/,
|
|
34
|
+
/^gemini-3(\.\d+)?(-.*)?$/
|
|
35
|
+
];
|
|
36
|
+
export function reasoningFromId(id) {
|
|
37
|
+
return REASONING_RX.some((rx) => rx.test(id));
|
|
38
|
+
}
|
|
39
|
+
const SUGGESTED_GROUPS = [
|
|
40
|
+
[new Set(["zai"]), "glm", "openai-completions"],
|
|
41
|
+
[new Set(["Mistral"]), "mistral", "openai-completions"],
|
|
42
|
+
[new Set(["google", "antigravity"]), "gemini", "openai-completions"],
|
|
43
|
+
[new Set(["Ollama", "Ollama pay"]), "ollama", "openai-completions"],
|
|
44
|
+
[new Set(["Xiaomi"]), "mimo", "openai-completions"],
|
|
45
|
+
[new Set(["OpenRouter"]), "openrouter", "openai-completions"],
|
|
46
|
+
[new Set(["cerebras"]), "cerebras", "openai-completions"]
|
|
47
|
+
];
|
|
48
|
+
export function withProviderPrefix(prefix, suffix) {
|
|
49
|
+
const p = (prefix ?? "").trim();
|
|
50
|
+
return p ? `${p}-${suffix}` : suffix;
|
|
51
|
+
}
|
|
52
|
+
export function normalizeSuggestedProvider(suggestedProvider, prefix) {
|
|
53
|
+
const p = (prefix ?? "").trim();
|
|
54
|
+
const m = suggestedProvider.match(/^([a-z0-9]+-)(.*)$/);
|
|
55
|
+
if (m)
|
|
56
|
+
return p ? `${p}-${m[1]}` : m[1];
|
|
57
|
+
return suggestedProvider;
|
|
58
|
+
}
|
|
59
|
+
export function classifyCustom(ownedBy, prefix) {
|
|
60
|
+
for (const [owners, suffix, api] of SUGGESTED_GROUPS) {
|
|
61
|
+
if (owners.has(ownedBy))
|
|
62
|
+
return { slug: withProviderPrefix(prefix, suffix), api };
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
slug: withProviderPrefix(prefix, "misc"),
|
|
66
|
+
api: "openai-completions"
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function prettifyName(id) {
|
|
70
|
+
return id.replace(/[/:]/g, " ").split(/[-_]/).filter(Boolean).map((p) => p[0].toUpperCase() + p.slice(1)).join(" ");
|
|
71
|
+
}
|
|
72
|
+
export function modelDefaults(id) {
|
|
73
|
+
return {
|
|
74
|
+
id,
|
|
75
|
+
name: prettifyName(id),
|
|
76
|
+
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
77
|
+
maxTokens: DEFAULT_MAX_TOKENS,
|
|
78
|
+
reasoning: reasoningFromId(id),
|
|
79
|
+
cost: { ...DEFAULT_COST }
|
|
80
|
+
};
|
|
81
|
+
}
|