@toddzheng024/dscode-bundle 0.7.2 → 0.7.4
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/THIRD_PARTY_NOTICES.md +3 -0
- package/cordis.patch.yml +4 -0
- package/package.json +4 -2
- package/plugins/auto-review/index.mjs +2 -1
- package/plugins/code-review/git.mjs +24 -2
- package/plugins/code-review/index.mjs +13 -6
- package/plugins/credentials/index.mjs +7 -5
- package/plugins/exec/cli.mjs +63 -0
- package/plugins/exec/index.mjs +1 -1
- package/plugins/memory/index.mjs +17 -12
- package/plugins/providers/catalog.mjs +134 -0
- package/plugins/session-cards/index.mjs +11 -8
- package/plugins/session-cards/manager.mjs +1 -1
- package/plugins/session-metrics/attribution.mjs +19 -0
- package/plugins/session-metrics/balance.mjs +41 -22
- package/plugins/session-metrics/index.mjs +26 -14
- package/plugins/session-metrics/pricing.mjs +24 -4
- package/plugins/session-metrics/view.mjs +5 -3
- package/plugins/tui-tools/doctor.mjs +9 -6
- package/plugins/tui-tools/index.mjs +2 -2
- package/plugins/ultra/policy.mjs +16 -0
- package/vendor/pi-ai/LICENSE +21 -0
- package/vendor/pi-ai/index.js +2702 -0
- package/vendor/pi-ai/types/adapter.d.ts +105 -0
- package/vendor/pi-ai/types/auth.d.ts +60 -0
- package/vendor/pi-ai/types/catalog.d.ts +355 -0
- package/vendor/pi-ai/types/config.d.ts +208 -0
- package/vendor/pi-ai/types/context.d.ts +42 -0
- package/vendor/pi-ai/types/discovery.d.ts +43 -0
- package/vendor/pi-ai/types/index.d.ts +69 -0
- package/vendor/pi-ai/types/login.d.ts +21 -0
- package/vendor/pi-ai/types/provider.d.ts +59 -0
- package/vendor/pi-ai/types/replay.d.ts +63 -0
- package/vendor/pi-ai/types/stream.d.ts +43 -0
- package/vendor/tui/dscode-providers/catalog.mjs +134 -0
- package/vendor/tui/index.mjs +146 -59
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Model providers `/provider` switches between. DeepSeek's official API is the
|
|
2
|
+
// native `llm-deepseek` route; OpenRouter reaches the same DeepSeek models
|
|
3
|
+
// through pi-ai's catalog route, which the base composition mounts dormant until
|
|
4
|
+
// a `llm-pi-ai:` settings section declares it.
|
|
5
|
+
|
|
6
|
+
export const PROVIDERS = Object.freeze([
|
|
7
|
+
{ id: 'deepseek-official', name: 'DeepSeek', aliases: ['deepseek', 'deepseek-official', 'official'], credentialRef: 'DEEPSEEK_API_KEY', defaultModel: 'deepseek-flash' },
|
|
8
|
+
{ id: 'openrouter', name: 'OpenRouter', aliases: ['openrouter', 'open-router'], credentialRef: 'OPENROUTER_API_KEY', defaultModel: 'deepseek/deepseek-v4-flash' },
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
const PI_AI_NS = 'llm-pi-ai';
|
|
12
|
+
|
|
13
|
+
// OpenRouter serves DeepSeek V4 thinking as none/high/xhigh. DeepSeek itself
|
|
14
|
+
// answers `low` as high and `max` as xhigh, so the route offers the official
|
|
15
|
+
// low/high/max detents (and Ultra on top of max) with the wire spelling OpenRouter
|
|
16
|
+
// accepts; session cards and delegated children that ask for `low` keep working.
|
|
17
|
+
const OPENROUTER_EFFORTS = Object.freeze({ off: 'none', low: 'high', high: 'high', max: 'xhigh' });
|
|
18
|
+
|
|
19
|
+
/** The DeepSeek models the OpenRouter route declares, with their official-route counterparts. */
|
|
20
|
+
export const OPENROUTER_MODELS = Object.freeze([
|
|
21
|
+
{ id: 'deepseek/deepseek-v4-flash', name: 'DeepSeek V4 Flash', official: ['deepseek-flash', 'deepseek-v4-flash'] },
|
|
22
|
+
{ id: 'deepseek/deepseek-v4-pro', name: 'DeepSeek V4 Pro', official: ['deepseek-v4-pro'] },
|
|
23
|
+
{ id: 'deepseek/deepseek-v4-flash-vision-exp', name: 'DeepSeek V4 Flash Vision Exp', official: ['deepseek-v4-flash-vision-exp'] },
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
/** The `llm-pi-ai` profile `/provider openrouter` writes: installed-catalog models narrowed to DeepSeek. */
|
|
27
|
+
export function openRouterProfile() {
|
|
28
|
+
return {
|
|
29
|
+
displayName: 'OpenRouter',
|
|
30
|
+
apiKeyEnv: 'OPENROUTER_API_KEY',
|
|
31
|
+
// Like the official route, requests default to high; it also gives /effort the DSCODE detent bar.
|
|
32
|
+
reasoning: 'high',
|
|
33
|
+
models: OPENROUTER_MODELS.map(({ id, name }) => ({ id, name, reasoningEfforts: { ...OPENROUTER_EFFORTS } })),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function providerSpec(id) {
|
|
38
|
+
return PROVIDERS.find(provider => provider.id === id);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a `/provider` or `/login` argument.
|
|
43
|
+
* @param raw - text after the command name.
|
|
44
|
+
* @returns a provider id, `undefined` for no argument, or `null` when unrecognized. Callers must not echo the text: it may be a pasted key.
|
|
45
|
+
*/
|
|
46
|
+
export function providerArgument(raw) {
|
|
47
|
+
const value = String(raw ?? '').trim().toLowerCase();
|
|
48
|
+
if (value === '') return undefined;
|
|
49
|
+
return PROVIDERS.find(provider => provider.aliases.includes(value))?.id ?? null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Split a `provider/model` label at the first slash: OpenRouter model ids carry their own `vendor/` segment. */
|
|
53
|
+
export function splitModelLabel(label) {
|
|
54
|
+
const text = typeof label === 'string' ? label : '';
|
|
55
|
+
const cut = text.indexOf('/');
|
|
56
|
+
return cut > 0 ? { provider: text.slice(0, cut), model: text.slice(cut + 1) } : { provider: '', model: text };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The switchable provider a label names, defaulting to DeepSeek for unknown or bare labels. */
|
|
60
|
+
export function providerOfLabel(label) {
|
|
61
|
+
return providerSpec(splitModelLabel(label).provider)?.id ?? PROVIDERS[0].id;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Provider id leading a footer header (`provider: model @ effort`), if any. */
|
|
65
|
+
export function providerOfHeader(header) {
|
|
66
|
+
return typeof header === 'string' ? header.match(/^([^\s:/]+): /)?.[1] : undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function counterpart(from, model, to) {
|
|
70
|
+
if (from === to) return model;
|
|
71
|
+
if (from === 'deepseek-official' && to === 'openrouter') return OPENROUTER_MODELS.find(entry => entry.official.includes(model))?.id;
|
|
72
|
+
if (from === 'openrouter' && to === 'deepseek-official') return OPENROUTER_MODELS.find(entry => entry.id === model)?.official[0];
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The model a provider switch lands on: the current model's counterpart, else the
|
|
78
|
+
* provider default, else its first model. The effort carries over only when the
|
|
79
|
+
* target offers it; otherwise the model's own default applies.
|
|
80
|
+
* @param rows - model directory rows (`provider`, `model`, `reasoning`).
|
|
81
|
+
* @returns `{ row, effort }`, or `undefined` when the provider serves no model yet.
|
|
82
|
+
*/
|
|
83
|
+
export function pickModel(rows, provider, currentLabel, effort) {
|
|
84
|
+
const candidates = rows.filter(row => row.provider === provider);
|
|
85
|
+
if (candidates.length === 0) return undefined;
|
|
86
|
+
const current = splitModelLabel(currentLabel);
|
|
87
|
+
const wanted = counterpart(current.provider, current.model, provider);
|
|
88
|
+
const row = candidates.find(candidate => candidate.model === wanted)
|
|
89
|
+
?? candidates.find(candidate => candidate.model === providerSpec(provider)?.defaultModel)
|
|
90
|
+
?? candidates[0];
|
|
91
|
+
const offered = row.reasoning?.efforts.map(level => level.id) ?? [];
|
|
92
|
+
return { row, effort: effort && offered.includes(effort) ? effort : undefined };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Credential status of a provider-settings row.
|
|
97
|
+
* @returns `saved`, `env`, `missing`, `readonly` (an empty read-only source), `error`, or `unavailable` (no row).
|
|
98
|
+
*/
|
|
99
|
+
export function credentialState(row) {
|
|
100
|
+
if (!row) return 'unavailable';
|
|
101
|
+
const credential = row.credential;
|
|
102
|
+
if (credential?.kind === 'error') return 'error';
|
|
103
|
+
if (credential?.kind !== 'facts') return 'missing';
|
|
104
|
+
if (credential.configured) return credential.source === 'env' ? 'env' : 'saved';
|
|
105
|
+
return credential.writable ? 'missing' : 'readonly';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Declare a provider's route before it is used. Only OpenRouter needs one; a
|
|
110
|
+
* profile the user already has (their own models or endpoint) is left alone.
|
|
111
|
+
* @param settings - the host settings service.
|
|
112
|
+
* @returns whether the settings changed.
|
|
113
|
+
*/
|
|
114
|
+
export async function ensureProviderRoute(settings, provider) {
|
|
115
|
+
if (provider !== 'openrouter') return false;
|
|
116
|
+
if (typeof settings?.describe !== 'function' || typeof settings.mutate !== 'function') throw new Error('settings are unavailable; OpenRouter cannot be configured in this profile');
|
|
117
|
+
const descriptor = settings.describe({ redactSecrets: true }).find(entry => entry.ns === PI_AI_NS);
|
|
118
|
+
if (!descriptor) throw new Error('the OpenRouter adapter (llm-pi-ai) is not mounted in this profile');
|
|
119
|
+
if (descriptor.value?.providers?.openrouter !== undefined) return false;
|
|
120
|
+
if (settings.writable !== true) throw new Error('settings are read-only; OpenRouter cannot be configured here');
|
|
121
|
+
await settings.mutate(PI_AI_NS, [{ op: 'set', path: ['providers', 'openrouter'], value: openRouterProfile() }], descriptor.revision);
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Wait for a freshly declared route to reach the model directory. */
|
|
126
|
+
export async function waitForModels(loadModels, provider, { attempts = 30, delayMs = 100 } = {}) {
|
|
127
|
+
let directory;
|
|
128
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
129
|
+
directory = await loadModels();
|
|
130
|
+
if (directory.rows.some(row => row.provider === provider)) return directory;
|
|
131
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
132
|
+
}
|
|
133
|
+
return directory;
|
|
134
|
+
}
|
package/vendor/tui/index.mjs
CHANGED
|
@@ -39,6 +39,8 @@ function dscodeLoadFlag(name, fallback = false) {
|
|
|
39
39
|
function dscodeSaveFlag(name, value) {
|
|
40
40
|
try { fs.mkdirSync(join(homedir(), ".dsh", "dsh-code"), { recursive: true }); fs.writeFileSync(dscodeFlagFile(name), JSON.stringify({ [name]: value }, null, 2) + "\n"); } catch {}
|
|
41
41
|
}
|
|
42
|
+
// dscode-provider-v1
|
|
43
|
+
import { PROVIDERS as DSCODE_PROVIDERS, providerSpec as dscodeProviderSpec, providerArgument as dscodeProviderArgument, providerOfLabel as dscodeProviderOfLabel, splitModelLabel as dscodeSplitModelLabel, pickModel as dscodePickModel, credentialState as dscodeCredentialState, ensureProviderRoute as dscodeEnsureProviderRoute, waitForModels as dscodeWaitForModels } from "./dscode-providers/catalog.mjs";
|
|
42
44
|
import { readClipboardImage as dscodeReadClipboardImage } from "./dscode-clipboard-image/index.mjs";
|
|
43
45
|
// dscode-clipboard-image-v1
|
|
44
46
|
// dscode-large-paste-v1
|
|
@@ -26123,7 +26125,7 @@ function replayProjectEvent(acc, event) {
|
|
|
26123
26125
|
acc.streamingReasoning = "";
|
|
26124
26126
|
acc.streaming = "";
|
|
26125
26127
|
if (reason.kind === "error") {
|
|
26126
|
-
const recovery = reason.error.code === "MISSING_CREDENTIAL" ? " ·
|
|
26128
|
+
const recovery = reason.error.code === "MISSING_CREDENTIAL" ? " · run /login to add an API key" : "";
|
|
26127
26129
|
appended.push({
|
|
26128
26130
|
kind: "error",
|
|
26129
26131
|
text: `${reason.error.code}: ${reason.error.message}${recovery}`
|
|
@@ -31929,7 +31931,8 @@ const SYNCHRONIZED_UPDATE_END = "\x1B[?2026l";
|
|
|
31929
31931
|
/** One source of truth for TUI-owned slash commands in completion and `/help`. */
|
|
31930
31932
|
const LOCAL_COMMANDS = [
|
|
31931
31933
|
{ label: "/email", description: "browse email and steer into this session" },
|
|
31932
|
-
{ label: "/login", description: "save a
|
|
31934
|
+
{ label: "/login", description: "save a provider API key locally (/login [deepseek|openrouter])" },
|
|
31935
|
+
{ label: "/provider", description: "switch between DeepSeek and OpenRouter" },
|
|
31933
31936
|
// dscode: startup command discovery
|
|
31934
31937
|
{"label":"/status","description":"session, model, permissions and usage"},
|
|
31935
31938
|
{"label":"/doctor","description":"read-only runtime diagnostics"},
|
|
@@ -32371,7 +32374,7 @@ function Header({ cwd = "", model = "", effort = "", animated = false }) {
|
|
|
32371
32374
|
if (!full) return (0, import_react.createElement)(Box, { flexDirection: "column", paddingX: 2, marginBottom: 1 },
|
|
32372
32375
|
(0, import_react.createElement)(Text, { wrap: "truncate-end" },
|
|
32373
32376
|
(0, import_react.createElement)(Text, { color: inkColor(getPalette().brandBright), bold: true }, "❄ DSCODE"),
|
|
32374
|
-
(0, import_react.createElement)(Text, { color: inkColor(getPalette().dim) }, " v0.7.
|
|
32377
|
+
(0, import_react.createElement)(Text, { color: inkColor(getPalette().dim) }, " v0.7.4")),
|
|
32375
32378
|
(0, import_react.createElement)(Text, { wrap: "truncate-end" }, truncateColumns(modelName + " · " + effortName, width)),
|
|
32376
32379
|
(0, import_react.createElement)(Text, { color: inkColor(getPalette().dim), wrap: "truncate-end" }, welcomePath(cwd, width)));
|
|
32377
32380
|
return (0, import_react.createElement)(Box, { flexDirection: "column", width, borderStyle: "round", borderColor: inkColor(getPalette().brand), paddingX: 1 },
|
|
@@ -32382,7 +32385,7 @@ function Header({ cwd = "", model = "", effort = "", animated = false }) {
|
|
|
32382
32385
|
(0, import_react.createElement)(Box, { flexDirection: "column", width: detailsWidth, marginTop: 2 },
|
|
32383
32386
|
(0, import_react.createElement)(Text, { color: inkColor(getPalette().text), bold: true }, "DSCODE"),
|
|
32384
32387
|
(0, import_react.createElement)(Text, { color: inkColor(getPalette().brandDeep) }, "────────────"),
|
|
32385
|
-
(0, import_react.createElement)(Text, { color: inkColor(getPalette().dim) }, "v0.7.
|
|
32388
|
+
(0, import_react.createElement)(Text, { color: inkColor(getPalette().dim) }, "v0.7.4"),
|
|
32386
32389
|
(0, import_react.createElement)(Text, { wrap: "truncate-end" }, truncateColumns(dscodePadEnd(dscodeT("welcome.model"), 9) + modelName, detailsWidth)),
|
|
32387
32390
|
(0, import_react.createElement)(Text, { wrap: "truncate-end" }, truncateColumns(dscodePadEnd(dscodeT("welcome.effort"), 9) + effortName, detailsWidth)),
|
|
32388
32391
|
(0, import_react.createElement)(Text, null, " "),
|
|
@@ -33452,53 +33455,89 @@ function ProviderPanel({ directory, error, authorizations, authorizationError, o
|
|
|
33452
33455
|
}, truncateColumns("↑↓ move · enter configure · l login · o logout · d remove key · x remove provider · r retry · esc back", viewport.contentColumns)));
|
|
33453
33456
|
}
|
|
33454
33457
|
// dscode-login-v1
|
|
33455
|
-
function DscodeLoginPanel({ load, save, done, back }) {
|
|
33456
|
-
|
|
33457
|
-
|
|
33458
|
-
|
|
33459
|
-
|
|
33460
|
-
|
|
33461
|
-
|
|
33462
|
-
|
|
33463
|
-
|
|
33464
|
-
|
|
33465
|
-
|
|
33466
|
-
|
|
33467
|
-
|
|
33468
|
-
|
|
33469
|
-
|
|
33470
|
-
|
|
33471
|
-
|
|
33472
|
-
|
|
33473
|
-
|
|
33474
|
-
|
|
33475
|
-
|
|
33476
|
-
|
|
33477
|
-
|
|
33478
|
-
|
|
33479
|
-
|
|
33480
|
-
|
|
33481
|
-
|
|
33482
|
-
|
|
33483
|
-
|
|
33484
|
-
|
|
33485
|
-
|
|
33486
|
-
|
|
33487
|
-
|
|
33488
|
-
|
|
33489
|
-
|
|
33490
|
-
|
|
33491
|
-
|
|
33492
|
-
|
|
33493
|
-
|
|
33494
|
-
|
|
33495
|
-
|
|
33496
|
-
|
|
33497
|
-
|
|
33498
|
-
|
|
33499
|
-
|
|
33500
|
-
|
|
33501
|
-
|
|
33458
|
+
function DscodeLoginPanel({ provider, load, save, done, back }) {
|
|
33459
|
+
const spec = dscodeProviderSpec(provider) ?? DSCODE_PROVIDERS[0];
|
|
33460
|
+
const [draft, setDraft] = (0, import_react.useState)("");
|
|
33461
|
+
const [target, setTarget] = (0, import_react.useState)(void 0);
|
|
33462
|
+
const [error, setError] = (0, import_react.useState)("");
|
|
33463
|
+
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
33464
|
+
const saving = (0, import_react.useRef)(false);
|
|
33465
|
+
(0, import_react.useEffect)(() => {
|
|
33466
|
+
let active = true;
|
|
33467
|
+
Promise.resolve().then(() => load()).then(directory => {
|
|
33468
|
+
if (!active) return;
|
|
33469
|
+
const row = directory.rows.find(row => row.provider === spec.id);
|
|
33470
|
+
if (!row || !save) { setError(spec.name + " credential storage is unavailable."); return; }
|
|
33471
|
+
if (row.credential?.kind === "error") { setError("Could not read credential status. Check local file permissions."); return; }
|
|
33472
|
+
if (row.credential?.kind === "facts" && !row.credential.writable) { setError(spec.credentialRef + " is set by your environment. Remove it and restart to use /login."); return; }
|
|
33473
|
+
setTarget(row);
|
|
33474
|
+
}, () => { if (active) setError("Could not load " + spec.name + " credential settings."); });
|
|
33475
|
+
return () => { active = false; };
|
|
33476
|
+
}, [spec.id]);
|
|
33477
|
+
useStableInput((input, key) => {
|
|
33478
|
+
if (saving.current) return;
|
|
33479
|
+
if (key.escape || key.ctrl && input === "c") { setDraft(""); back(); return; }
|
|
33480
|
+
if (!target) return;
|
|
33481
|
+
if (key.return) {
|
|
33482
|
+
const raw = draft.trim();
|
|
33483
|
+
if (!raw || /[\s\x00-\x1f\x7f-]/.test(raw) || ENV_ASSIGNMENT.test(raw) || hasWrappingQuotes(raw)) {
|
|
33484
|
+
setError("Paste only the API key, without quotes, spaces or an environment-variable name."); return;
|
|
33485
|
+
}
|
|
33486
|
+
saving.current = true; setBusy(true); setError(""); setDraft("");
|
|
33487
|
+
Promise.resolve().then(() => save(target, raw)).then(done, () => {
|
|
33488
|
+
saving.current = false; setBusy(false);
|
|
33489
|
+
setError("Could not save the API key. Check file permissions and available disk space, then paste again.");
|
|
33490
|
+
});
|
|
33491
|
+
return;
|
|
33492
|
+
}
|
|
33493
|
+
if (key.ctrl && input === "u") { setDraft(""); setError(""); return; }
|
|
33494
|
+
if (key.backspace || key.delete) { setDraft(current => [...current].slice(0, -1).join("")); return; }
|
|
33495
|
+
if (key.ctrl || key.meta || !input) return;
|
|
33496
|
+
const pasted = stripPasteMarkers(input);
|
|
33497
|
+
setDraft(current => (current + pasted).slice(0, 4096));
|
|
33498
|
+
setError("");
|
|
33499
|
+
});
|
|
33500
|
+
return (0, import_react.createElement)(Box, { flexDirection: "column", paddingX: 2 },
|
|
33501
|
+
(0, import_react.createElement)(Text, { bold: true }, spec.name + " login"),
|
|
33502
|
+
(0, import_react.createElement)(Text, { dimColor: true }, "Saved on this Mac in ~/.dscode/credentials.yaml"),
|
|
33503
|
+
(0, import_react.createElement)(Text, null, busy ? "Saving…" : target ? "API key › " + (draft ? "••••••••" : "paste your key") : error ? "" : "Loading…"),
|
|
33504
|
+
error ? (0, import_react.createElement)(Text, { color: "red" }, error) : void 0,
|
|
33505
|
+
(0, import_react.createElement)(Text, { dimColor: true }, "Enter save · Esc cancel · Ctrl+U clear"));
|
|
33506
|
+
}
|
|
33507
|
+
function DscodeProviderPanel({ current, load, choose, back }) {
|
|
33508
|
+
const [directory, setDirectory] = (0, import_react.useState)(void 0);
|
|
33509
|
+
const [failed, setFailed] = (0, import_react.useState)(false);
|
|
33510
|
+
const [cursor, setCursor] = (0, import_react.useState)(() => Math.max(0, DSCODE_PROVIDERS.findIndex(provider => provider.id === current)));
|
|
33511
|
+
(0, import_react.useEffect)(() => {
|
|
33512
|
+
let active = true;
|
|
33513
|
+
Promise.resolve().then(() => load()).then(loaded => { if (active) setDirectory(loaded); }, () => { if (active) setFailed(true); });
|
|
33514
|
+
return () => { active = false; };
|
|
33515
|
+
}, []);
|
|
33516
|
+
useStableInput((input, key) => {
|
|
33517
|
+
const count = DSCODE_PROVIDERS.length;
|
|
33518
|
+
if (key.escape || key.ctrl && input === "c" || input === "q") { back(); return; }
|
|
33519
|
+
if (key.upArrow || input === "k") { setCursor(index => (index + count - 1) % count); return; }
|
|
33520
|
+
if (key.downArrow || input === "j") { setCursor(index => (index + 1) % count); return; }
|
|
33521
|
+
if (key.return) { back(); choose(DSCODE_PROVIDERS[cursor].id); }
|
|
33522
|
+
});
|
|
33523
|
+
const status = provider => {
|
|
33524
|
+
if (failed) return "status unavailable";
|
|
33525
|
+
if (directory === void 0) return "…";
|
|
33526
|
+
const row = directory.rows.find(row => row.provider === provider.id);
|
|
33527
|
+
const state = dscodeCredentialState(row);
|
|
33528
|
+
if (state === "saved") return "key saved";
|
|
33529
|
+
if (state === "env") return "key from " + provider.credentialRef;
|
|
33530
|
+
if (state === "readonly") return provider.credentialRef + " is empty";
|
|
33531
|
+
if (state === "error") return "credential status unavailable";
|
|
33532
|
+
if (state === "unavailable") return "unavailable in this profile";
|
|
33533
|
+
return row?.configured === true || provider.id === "deepseek-official" ? "needs an API key" : "not set up · Enter sets it up";
|
|
33534
|
+
};
|
|
33535
|
+
return (0, import_react.createElement)(Box, { flexDirection: "column", paddingX: 2 },
|
|
33536
|
+
(0, import_react.createElement)(Text, { bold: true }, "Provider"),
|
|
33537
|
+
(0, import_react.createElement)(Text, { dimColor: true, wrap: "truncate-end" }, "The next step uses the chosen provider; /model picks among its models."),
|
|
33538
|
+
...DSCODE_PROVIDERS.map((provider, index) => (0, import_react.createElement)(Text, { key: provider.id, bold: index === cursor, wrap: "truncate-end" },
|
|
33539
|
+
(index === cursor ? "› " : " ") + (provider.id === current ? "● " : "○ ") + provider.name.padEnd(11) + provider.id + " · " + status(provider))),
|
|
33540
|
+
(0, import_react.createElement)(Text, { dimColor: true }, "↑↓ choose · Enter switch · Esc cancel"));
|
|
33502
33541
|
}
|
|
33503
33542
|
|
|
33504
33543
|
function ProviderSetupPanel({ target, save, saveCredential, discover, effortDonors, done, back, onExit }) {
|
|
@@ -34628,7 +34667,7 @@ function CompletionMenu({ active, mention, index, rows, error }) {
|
|
|
34628
34667
|
* While a modal (approval / question / model panel) owns the keys, the
|
|
34629
34668
|
* box passes every key through untouched.
|
|
34630
34669
|
*/
|
|
34631
|
-
function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, skills, dispatch, steer, interrupt, quit, openEmail, emailFill, emailConsumed, openLogin, openModel, openEffort, openHelp, openMode, openPermission, openResume, openPlugin, openJobs, openStatusline, openTheme, openLanguage, openHistory, openAgents, openSubagent, openTodos, openDelete, openDiff, reviewChanges, deleteConfirm, confirmDelete, cancelDelete, createSession, forkSession, cancelSessionSwitch, notify, applyEditorKeys, hasNotice, dismissNotice, toggleReasoning, openVerbose, clearView, refresh, loadMentions, inspectImages, prepareImages, inspectFiles, prepareFiles, readClipboardImage, cyclePermission, exportTranscript, renameTitle, copyLastResponse, recallSpace, recordLocal, recordHistory, queued, cancelQueued, historyFill, historyConsumed, animations, applyAnimations, waveTier, waveStyle, maxRows, onEditorRows, onMenuRows, sessionKey }) {
|
|
34670
|
+
function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, skills, dispatch, steer, interrupt, quit, openEmail, emailFill, emailConsumed, openLogin, openProvider, openModel, openEffort, openHelp, openMode, openPermission, openResume, openPlugin, openJobs, openStatusline, openTheme, openLanguage, openHistory, openAgents, openSubagent, openTodos, openDelete, openDiff, reviewChanges, deleteConfirm, confirmDelete, cancelDelete, createSession, forkSession, cancelSessionSwitch, notify, applyEditorKeys, hasNotice, dismissNotice, toggleReasoning, openVerbose, clearView, refresh, loadMentions, inspectImages, prepareImages, inspectFiles, prepareFiles, readClipboardImage, cyclePermission, exportTranscript, renameTitle, copyLastResponse, recallSpace, recordLocal, recordHistory, queued, cancelQueued, historyFill, historyConsumed, animations, applyAnimations, waveTier, waveStyle, maxRows, onEditorRows, onMenuRows, sessionKey }) {
|
|
34632
34671
|
const columns = useStdout().stdout?.columns ?? 80;
|
|
34633
34672
|
const inputTerminalRows = useStdout().stdout?.rows ?? 30;
|
|
34634
34673
|
const dscodeImeStdout = useStdout().stdout;
|
|
@@ -35191,9 +35230,19 @@ function Input({ effortSurface, ultraPulse, active, frozen, busy, descriptors, s
|
|
|
35191
35230
|
valueRef.current = ""; cursorRef.current = 0;
|
|
35192
35231
|
setValue(""); setCursor(0); setCompletionIndex(0); setDismissedMenuValue(void 0);
|
|
35193
35232
|
recall.current = beginRecall(recallSpace, "");
|
|
35194
|
-
|
|
35233
|
+
const dscodeLoginTarget = dscodeProviderArgument(trimmed.slice(6));
|
|
35234
|
+
if (dscodeLoginTarget === null) { notify("Use /login, /login deepseek or /login openrouter, then paste the key in the private input.", "warning"); return; }
|
|
35195
35235
|
if (busy) { notify("Stop the running turn before /login.", "warning"); return; }
|
|
35196
|
-
openLogin(); return;
|
|
35236
|
+
openLogin(dscodeLoginTarget); return;
|
|
35237
|
+
}
|
|
35238
|
+
if (trimmed === "/provider" || /^\/provider\s/.test(trimmed)) {
|
|
35239
|
+
valueRef.current = ""; cursorRef.current = 0;
|
|
35240
|
+
setValue(""); setCursor(0); setCompletionIndex(0); setDismissedMenuValue(void 0);
|
|
35241
|
+
recall.current = beginRecall(recallSpace, "");
|
|
35242
|
+
const dscodeProviderTarget = dscodeProviderArgument(trimmed.slice(9));
|
|
35243
|
+
if (dscodeProviderTarget === null) { notify("Usage: /provider [deepseek|openrouter]", "warning"); return; }
|
|
35244
|
+
if (busy) { notify("Stop the running turn before /provider.", "warning"); return; }
|
|
35245
|
+
openProvider(dscodeProviderTarget); return;
|
|
35197
35246
|
}
|
|
35198
35247
|
if (draftImagesRef.current.length > 0 || draftFilesRef.current.length > 0) {
|
|
35199
35248
|
if (isSlashLine(text)) notify("commands cannot carry attachments; the line will be sent to the model as a prompt", "warning");
|
|
@@ -36190,6 +36239,27 @@ function App(props) {
|
|
|
36190
36239
|
notify(`model switch failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
36191
36240
|
}
|
|
36192
36241
|
};
|
|
36242
|
+
// dscode-provider-switch
|
|
36243
|
+
const dscodeSwitchProvider = (provider) => {
|
|
36244
|
+
const spec = dscodeProviderSpec(provider);
|
|
36245
|
+
if (props.loadModelProviders === void 0) { notify("provider switching is unavailable in this profile", "warning"); return; }
|
|
36246
|
+
Promise.resolve().then(async () => {
|
|
36247
|
+
if (props.dscodeEnsureProviderRoute !== void 0 && await props.dscodeEnsureProviderRoute(provider)) reloadModelSurfaces();
|
|
36248
|
+
const providers = await props.loadModelProviders();
|
|
36249
|
+
const state = dscodeCredentialState(providers.rows.find((row) => row.provider === provider));
|
|
36250
|
+
if (state === "unavailable") { notify(spec.name + " is unavailable in this profile", "error"); return; }
|
|
36251
|
+
if (state === "missing") {
|
|
36252
|
+
setProviderOpen(false); setEffortFor(void 0);
|
|
36253
|
+
setProviderAction({ kind: "dscode-key", provider, then: () => dscodeSwitchProvider(provider) }); setModelOpen(true);
|
|
36254
|
+
return;
|
|
36255
|
+
}
|
|
36256
|
+
if (state === "error" || state === "readonly") notify("could not confirm the " + spec.name + " API key; requests may fail until /login " + provider + " succeeds", "warning");
|
|
36257
|
+
const models = await dscodeWaitForModels(props.loadModels, provider);
|
|
36258
|
+
const pick = dscodePickModel(models.rows, provider, modelLabel, effortLabel);
|
|
36259
|
+
if (pick === void 0) { notify(spec.name + " serves no models yet — check /model", "error"); return; }
|
|
36260
|
+
applyModel(pick.row, pick.effort);
|
|
36261
|
+
}).catch((error) => notify("provider switch failed: " + (error instanceof Error ? error.message : String(error)), "error"));
|
|
36262
|
+
};
|
|
36193
36263
|
const reloadModelSurfaces = () => {
|
|
36194
36264
|
setModelLoadEpoch((epoch) => epoch + 1);
|
|
36195
36265
|
};
|
|
@@ -36232,10 +36302,21 @@ function App(props) {
|
|
|
36232
36302
|
}, [providerDirectory, directory]);
|
|
36233
36303
|
let modelSurface;
|
|
36234
36304
|
if (modelOpen && !approvalPending && !questionPending) {
|
|
36235
|
-
if (providerAction?.kind === "dscode-
|
|
36305
|
+
if (providerAction?.kind === "dscode-provider") modelSurface = (0, import_react.createElement)(DscodeProviderPanel, {
|
|
36306
|
+
current: dscodeProviderOfLabel(modelLabel),
|
|
36236
36307
|
load: props.loadModelProviders,
|
|
36308
|
+
choose: dscodeSwitchProvider,
|
|
36309
|
+
back: closeModelSurface
|
|
36310
|
+
});
|
|
36311
|
+
else if (providerAction?.kind === "dscode-key") modelSurface = (0, import_react.createElement)(DscodeLoginPanel, {
|
|
36312
|
+
provider: providerAction.provider,
|
|
36313
|
+
load: async () => { await props.dscodeEnsureProviderRoute?.(providerAction.provider); return props.loadModelProviders(); },
|
|
36237
36314
|
save: props.saveModelProviderCredential,
|
|
36238
|
-
done: () => {
|
|
36315
|
+
done: () => {
|
|
36316
|
+
const then = providerAction.then;
|
|
36317
|
+
closeModelSurface(); reloadModelSurfaces();
|
|
36318
|
+
if (then !== void 0) then(); else notify(dscodeProviderSpec(providerAction.provider).name + " API key saved locally; ready to use.");
|
|
36319
|
+
},
|
|
36239
36320
|
back: closeModelSurface
|
|
36240
36321
|
});
|
|
36241
36322
|
else if (providerAction?.kind === "login" && props.beginProviderAuthorization !== void 0 && props.cancelProviderAuthorization !== void 0 && props.openAuthorizationUrl !== void 0 && props.copyTextValue !== void 0) modelSurface = (0, import_react.createElement)(ProviderAuthorizationPanel, {
|
|
@@ -36587,9 +36668,14 @@ function App(props) {
|
|
|
36587
36668
|
quit: props.quit,
|
|
36588
36669
|
openEmail: () => setEmailOpen(true),
|
|
36589
36670
|
emailFill, emailConsumed,
|
|
36590
|
-
openLogin: () => {
|
|
36671
|
+
openLogin: (provider) => {
|
|
36672
|
+
setProviderOpen(false); setEffortFor(void 0);
|
|
36673
|
+
setProviderAction({ kind: "dscode-key", provider: provider ?? dscodeProviderOfLabel(modelLabel) }); setModelOpen(true);
|
|
36674
|
+
},
|
|
36675
|
+
openProvider: (provider) => {
|
|
36676
|
+
if (provider !== void 0) { dscodeSwitchProvider(provider); return; }
|
|
36591
36677
|
setProviderOpen(false); setEffortFor(void 0);
|
|
36592
|
-
setProviderAction({ kind: "dscode-
|
|
36678
|
+
setProviderAction({ kind: "dscode-provider" }); setModelOpen(true);
|
|
36593
36679
|
},
|
|
36594
36680
|
openModel: () => {
|
|
36595
36681
|
setDirectory(void 0);
|
|
@@ -36605,7 +36691,7 @@ function App(props) {
|
|
|
36605
36691
|
},
|
|
36606
36692
|
openEffort: () => {
|
|
36607
36693
|
props.loadModels().then((loaded) => {
|
|
36608
|
-
const
|
|
36694
|
+
const { provider, model } = dscodeSplitModelLabel(modelLabel);
|
|
36609
36695
|
const row = loaded.rows.find((candidate) => candidate.provider === provider && candidate.model === model) ?? loaded.rows.find((candidate) => candidate.model === model && candidate.reasoning !== void 0) ?? loaded.rows.find((candidate) => candidate.model === model);
|
|
36610
36696
|
if (row === void 0) {
|
|
36611
36697
|
notify("current model is not in the catalog", "warning");
|
|
@@ -39792,6 +39878,7 @@ async function run(ctx, startup, io) {
|
|
|
39792
39878
|
interrupt,
|
|
39793
39879
|
quit,
|
|
39794
39880
|
loadModels: () => loadModelDirectory(ctx),
|
|
39881
|
+
dscodeEnsureProviderRoute: (provider) => dscodeEnsureProviderRoute(ctx.get("settings"), provider),
|
|
39795
39882
|
loadModelProviders: () => loadProviderSettings(ctx),
|
|
39796
39883
|
subscribeModelProviders: (listener) => subscribeProviderSettings(ctx, listener),
|
|
39797
39884
|
saveModelProviderCredential: (target, key) => saveProviderCredential(ctx, target, key),
|