@xbzbing/dsh-git-panel 1.7.0 → 1.8.0
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/README.en.md +31 -22
- package/README.md +31 -22
- package/lib/client.js +58 -26
- package/lib/client.mermaid.js +4338 -0
- package/lib/host/core.d.ts +10 -4
- package/lib/host/core.js +8 -3
- package/lib/host/index.d.ts +7 -1
- package/lib/host/index.js +27 -2
- package/lib/host/llm-face.d.ts +21 -0
- package/lib/host/queries.js +68 -9
- package/lib/host/suggest.js +19 -3
- package/lib/host/types.d.ts +41 -1
- package/package.json +19 -5
package/lib/host/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { GitRunner } from './git.js';
|
|
2
|
-
import type { AgentDefaultModelFace, LlmFace } from './llm-face.js';
|
|
2
|
+
import type { AgentDefaultModelFace, LlmFace, SessionModelSelection } from './llm-face.js';
|
|
3
3
|
import type { DiffViewMode, GraphStyle, GitChange, GitErrorCode, GitRemote, GitSnapshotResult, SuggestLanguage } from './types.js';
|
|
4
4
|
export interface GitPanelConfig {
|
|
5
5
|
readonly timeoutMs: number;
|
|
@@ -26,6 +26,9 @@ export interface GitPanelConfig {
|
|
|
26
26
|
/** Optional provider/model route override; absent → the agent default. */
|
|
27
27
|
readonly suggestProvider?: string;
|
|
28
28
|
readonly suggestModel?: string;
|
|
29
|
+
/** Adapter-owned effort for the fixed route; absent → omit from the request
|
|
30
|
+
* (the model / adapter default applies). Ignored when the route is not fixed. */
|
|
31
|
+
readonly suggestReasoningEffort?: string;
|
|
29
32
|
}
|
|
30
33
|
export declare const DEFAULT_CONFIG: GitPanelConfig;
|
|
31
34
|
export declare function normalizeConfig(raw: unknown): GitPanelConfig;
|
|
@@ -94,14 +97,17 @@ export interface SnapshotDeps {
|
|
|
94
97
|
*/
|
|
95
98
|
readonly rootNegCache?: Map<string, number>;
|
|
96
99
|
/**
|
|
97
|
-
* Optional per-request resolvers for the host
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
+
* Optional per-request resolvers for the host model services (see
|
|
101
|
+
* llm-face.ts). Resolved on every suggest call — never frozen at
|
|
102
|
+
* construction — so a provider fiber that activates late or restarts is
|
|
100
103
|
* picked up. Absent in deployments without a model backend; the suggest
|
|
101
104
|
* endpoint then reports `llm-unavailable` instead of failing activation.
|
|
102
105
|
*/
|
|
103
106
|
readonly getLlm?: () => LlmFace | undefined;
|
|
104
107
|
readonly getAgentDefaultModel?: () => AgentDefaultModelFace | undefined;
|
|
108
|
+
/** The session's durable model selection (projection next); undefined when
|
|
109
|
+
* the session has never selected a model. */
|
|
110
|
+
readonly getSessionModel?: (sessionId: string) => SessionModelSelection | undefined;
|
|
105
111
|
}
|
|
106
112
|
/**
|
|
107
113
|
* Pick the primary remote from `git remote -v` output and parse its fetch URL.
|
package/lib/host/core.js
CHANGED
|
@@ -25,9 +25,14 @@ function normalizeConfig(raw) {
|
|
|
25
25
|
const raw2 = unwrapVolatile(v);
|
|
26
26
|
return typeof raw2 === "number" && Number.isFinite(raw2) ? Math.min(limit.max, Math.max(limit.min, Math.floor(raw2))) : limit.default;
|
|
27
27
|
};
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
28
|
+
const provider = unwrapVolatile(c.suggestProvider);
|
|
29
|
+
const model = unwrapVolatile(c.suggestModel);
|
|
30
|
+
const effort = unwrapVolatile(c.suggestReasoningEffort);
|
|
31
|
+
const route = typeof provider === "string" && provider !== "" && typeof model === "string" && model !== "" ? {
|
|
32
|
+
suggestProvider: provider,
|
|
33
|
+
suggestModel: model,
|
|
34
|
+
...typeof effort === "string" && effort !== "" ? { suggestReasoningEffort: effort } : {}
|
|
35
|
+
} : {};
|
|
31
36
|
return {
|
|
32
37
|
timeoutMs: num(c.timeoutMs, DEFAULT_CONFIG.timeoutMs),
|
|
33
38
|
maxBytes: num(c.maxBytes, DEFAULT_CONFIG.maxBytes),
|
package/lib/host/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
|
10
10
|
import type { Context } from '@deepseek-ai/cordis';
|
|
11
11
|
import Schema from '@deepseek-ai/schemastery';
|
|
12
12
|
import type { GitActionRequest, GitActionResult, GitQueryRequest, GitQueryResponse, GitSnapshotRequest, GitSnapshotResult, GitSuggestRequest, GitSuggestResult, GitVersionInfo, GitVersionRequest } from './types.js';
|
|
13
|
-
export type { GitSnapshot, GitSnapshotResult, GitSnapshotRequest, GitFailure, GitCommit, GraphCommit, GitRef, GitChange, GitChangeStatus, GitAction, GitActionRequest, GitActionResult, GitErrorCode, GitQuery, GitQueryRequest, GitQueryResponse, GitQueryResult, GitBranch, GitFileStat, WorktreeStats, StashEntry, GitVersionRequest, GitVersionInfo, DiffViewMode, GraphStyle, DirEntry, ResetMode, GitSuggestRequest, GitSuggestResult, GitRemote, RemoteHostKind, SuggestLanguage, ResolvedLanguage, } from './types.js';
|
|
13
|
+
export type { GitSnapshot, GitSnapshotResult, GitSnapshotRequest, GitFailure, GitCommit, GraphCommit, GitRef, GitChange, GitChangeStatus, GitAction, GitActionRequest, GitActionResult, GitErrorCode, GitQuery, GitQueryRequest, GitQueryResponse, GitQueryResult, GitBranch, GitFileStat, WorktreeStats, StashEntry, GitVersionRequest, GitVersionInfo, DiffViewMode, GraphStyle, DirEntry, ResetMode, GitSuggestRequest, GitSuggestResult, GitRemote, RemoteHostKind, SuggestLanguage, ResolvedLanguage, ModelCatalogGroup, ModelCatalogModel, ModelCatalogEffort, ModelCatalogFailure, } from './types.js';
|
|
14
14
|
export { normalizeConfig, DEFAULT_CONFIG, snapshotForSession, resolveWorkspace, parsePrimaryRemote, readSuggestLanguage } from './core.js';
|
|
15
15
|
export { createGitRunner } from './git.js';
|
|
16
16
|
export { parseStatus, parseGraphLog, parseBranches, parseNameStatus, parseStashList, parseTags, sumNumstat } from './parser.js';
|
|
@@ -36,6 +36,9 @@ export declare class GitPanelService extends TypertRemoteService {
|
|
|
36
36
|
suggestMaxBytes: Schema<number, number, "volatile-defined">;
|
|
37
37
|
suggestMaxOutputTokens: Schema<number, number, "volatile-defined">;
|
|
38
38
|
suggestTimeoutMs: Schema<number, number, "volatile-defined">;
|
|
39
|
+
suggestProvider: Schema<string, string, "volatile">;
|
|
40
|
+
suggestModel: Schema<string, string, "volatile">;
|
|
41
|
+
suggestReasoningEffort: Schema<string, string, "volatile">;
|
|
39
42
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
40
43
|
showInputPill: Schema<boolean, boolean, "volatile-defined">;
|
|
41
44
|
defaultDiffView: Schema<"split" | "unified", "split" | "unified", "volatile-defined">;
|
|
@@ -45,6 +48,9 @@ export declare class GitPanelService extends TypertRemoteService {
|
|
|
45
48
|
suggestMaxBytes: Schema<number, number, "volatile-defined">;
|
|
46
49
|
suggestMaxOutputTokens: Schema<number, number, "volatile-defined">;
|
|
47
50
|
suggestTimeoutMs: Schema<number, number, "volatile-defined">;
|
|
51
|
+
suggestProvider: Schema<string, string, "volatile">;
|
|
52
|
+
suggestModel: Schema<string, string, "volatile">;
|
|
53
|
+
suggestReasoningEffort: Schema<string, string, "volatile">;
|
|
48
54
|
}>>, "plain">;
|
|
49
55
|
private readonly deps;
|
|
50
56
|
private config;
|
package/lib/host/index.js
CHANGED
|
@@ -86,6 +86,7 @@ class GitPanelService extends (_a = TypertRemoteService, _snapshot_dec = [Remote
|
|
|
86
86
|
} } : createGitRunner(subprocess, config.timeoutMs, config.maxBytes);
|
|
87
87
|
const sessions = get("sessions");
|
|
88
88
|
const persistence = get("sessionPersistence");
|
|
89
|
+
const projections = get("sessionProjections");
|
|
89
90
|
return {
|
|
90
91
|
run,
|
|
91
92
|
fs,
|
|
@@ -108,7 +109,25 @@ class GitPanelService extends (_a = TypertRemoteService, _snapshot_dec = [Remote
|
|
|
108
109
|
// a model backend, where the suggest endpoint reports `llm-unavailable`
|
|
109
110
|
// rather than failing activation (kept out of `static inject` on purpose).
|
|
110
111
|
getLlm: () => get("llm"),
|
|
111
|
-
getAgentDefaultModel: () => get("agentDefaultModel")
|
|
112
|
+
getAgentDefaultModel: () => get("agentDefaultModel"),
|
|
113
|
+
// Session model selection: projection `next` is pending ?? lastUsed —
|
|
114
|
+
// the route the next prompt in this session would use.
|
|
115
|
+
getSessionModel: (sessionId) => {
|
|
116
|
+
const session = sessions?.get(sessionId);
|
|
117
|
+
if (session === void 0 || projections === void 0) return void 0;
|
|
118
|
+
try {
|
|
119
|
+
const next = projections.snapshot(session, ["modelSelection"]).values?.modelSelection?.next;
|
|
120
|
+
if (next == null || typeof next.provider !== "string" || typeof next.model !== "string") return void 0;
|
|
121
|
+
if (next.provider === "" || next.model === "") return void 0;
|
|
122
|
+
return {
|
|
123
|
+
provider: next.provider,
|
|
124
|
+
model: next.model,
|
|
125
|
+
...typeof next.reasoningEffort === "string" && next.reasoningEffort !== "" ? { reasoningEffort: next.reasoningEffort } : {}
|
|
126
|
+
};
|
|
127
|
+
} catch {
|
|
128
|
+
return void 0;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
112
131
|
};
|
|
113
132
|
}
|
|
114
133
|
async snapshot(request, signal) {
|
|
@@ -174,7 +193,13 @@ __publicField(GitPanelService, "Config", Schema.object({
|
|
|
174
193
|
// a literal here could silently drift from what normalizeConfig enforces.
|
|
175
194
|
suggestMaxBytes: Schema.number().default(DEFAULT_CONFIG.suggestMaxBytes).volatile().description("AI \u751F\u6210\u8F93\u5165\u9884\u7B97\uFF08diff \u5B57\u8282\u4E0A\u9650\uFF0C16\u2013512 KiB\uFF09"),
|
|
176
195
|
suggestMaxOutputTokens: Schema.number().default(DEFAULT_CONFIG.suggestMaxOutputTokens).volatile().description("AI \u751F\u6210\u8F93\u51FA\u4E0A\u9650\uFF08tokens\uFF0C256\u201316384\uFF09"),
|
|
177
|
-
suggestTimeoutMs: Schema.number().default(DEFAULT_CONFIG.suggestTimeoutMs).volatile().description("AI \u751F\u6210\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF0C5\u2013120 \u79D2\uFF09")
|
|
196
|
+
suggestTimeoutMs: Schema.number().default(DEFAULT_CONFIG.suggestTimeoutMs).volatile().description("AI \u751F\u6210\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF0C5\u2013120 \u79D2\uFF09"),
|
|
197
|
+
// Fixed model route: both halves of the pair must be non-empty to take
|
|
198
|
+
// effect (normalizeConfig); effort is omitted from the request when blank.
|
|
199
|
+
// No .default(): without one the field stays optional in the object schema.
|
|
200
|
+
suggestProvider: Schema.string().volatile().description("AI \u751F\u6210\u56FA\u5B9A\u6A21\u578B\u7684 provider\uFF08\u7559\u7A7A = \u8DDF\u968F\u4F1A\u8BDD\u9ED8\u8BA4\uFF09"),
|
|
201
|
+
suggestModel: Schema.string().volatile().description("AI \u751F\u6210\u56FA\u5B9A\u6A21\u578B\u7684 model id\uFF08\u987B\u4E0E provider \u6210\u5BF9\uFF09"),
|
|
202
|
+
suggestReasoningEffort: Schema.string().volatile().description("AI \u751F\u6210\u56FA\u5B9A\u6A21\u578B\u7684\u63A8\u7406\u5F3A\u5EA6\uFF08\u7559\u7A7A = \u4E0D\u8BBE\u7F6E\uFF0C\u7528\u6A21\u578B\u9ED8\u8BA4\uFF09")
|
|
178
203
|
}));
|
|
179
204
|
var index_default = GitPanelService;
|
|
180
205
|
export {
|
package/lib/host/llm-face.d.ts
CHANGED
|
@@ -16,7 +16,22 @@ export interface LlmFace {
|
|
|
16
16
|
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
17
17
|
listProviders?(): readonly {
|
|
18
18
|
readonly id: string;
|
|
19
|
+
readonly name?: string;
|
|
19
20
|
}[];
|
|
21
|
+
/** Adapter-advertised models for one provider; optional on the wire. */
|
|
22
|
+
listModels?(provider: string): Promise<readonly {
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly name?: string;
|
|
25
|
+
}[]>;
|
|
26
|
+
/** Exact-route metadata (reasoning efforts etc.); optional on the wire. */
|
|
27
|
+
resolveModelInfo?(provider: string, model: string, signal?: AbortSignal): Promise<{
|
|
28
|
+
readonly reasoning?: {
|
|
29
|
+
readonly efforts: readonly {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly name: string;
|
|
32
|
+
}[];
|
|
33
|
+
};
|
|
34
|
+
}>;
|
|
20
35
|
}
|
|
21
36
|
/** Minimal face of the host `agentDefaultModel` service. */
|
|
22
37
|
export interface AgentDefaultModelFace {
|
|
@@ -25,3 +40,9 @@ export interface AgentDefaultModelFace {
|
|
|
25
40
|
readonly model: string;
|
|
26
41
|
} | undefined;
|
|
27
42
|
}
|
|
43
|
+
/** One session's durable model selection (projection `next`: pending, else lastUsed). */
|
|
44
|
+
export interface SessionModelSelection {
|
|
45
|
+
readonly provider: string;
|
|
46
|
+
readonly model: string;
|
|
47
|
+
readonly reasoningEffort?: string;
|
|
48
|
+
}
|
package/lib/host/queries.js
CHANGED
|
@@ -9,6 +9,13 @@ const FIRST_PARENT_DIFF = "--diff-merges=first-parent";
|
|
|
9
9
|
const VISIBLE_REFS = ["--branches", "--remotes", "--tags", "HEAD"];
|
|
10
10
|
async function runQuery(deps, config, request) {
|
|
11
11
|
const q = request.query;
|
|
12
|
+
if (q.kind === "model-catalog") {
|
|
13
|
+
try {
|
|
14
|
+
return await queryModelCatalog(deps);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
return { ok: false, error: { code: "git-error", message: error instanceof Error ? error.message : String(error) } };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
12
19
|
if (q.kind === "dir-list" || q.kind === "file-content") {
|
|
13
20
|
const browse = await resolveBrowseRoot(deps, request.sessionId);
|
|
14
21
|
if (!browse.ok) return { ok: false, error: browse.error };
|
|
@@ -307,7 +314,8 @@ async function queryDirList(deps, root, q, isGitRepo) {
|
|
|
307
314
|
if (rel !== "" && !isSafePath(rel) || hasGitSegment(rel)) return { ok: false, error: { code: "invalid-path", message: rel } };
|
|
308
315
|
const dir = rel === "" ? root : join(root, rel);
|
|
309
316
|
const inside = await isInsideRoot(deps, root, dir, true);
|
|
310
|
-
if (
|
|
317
|
+
if (inside === "missing") return { ok: false, error: { code: "file-not-found", message: rel } };
|
|
318
|
+
if (inside === "outside") return { ok: false, error: { code: "invalid-path", message: rel } };
|
|
311
319
|
let raw;
|
|
312
320
|
try {
|
|
313
321
|
raw = await deps.fs.readdir(dir);
|
|
@@ -358,12 +366,13 @@ async function queryFileContent(deps, config, root, q) {
|
|
|
358
366
|
if (!isSafePath(q.path) || hasGitSegment(q.path)) return { ok: false, error: { code: "invalid-path", message: q.path } };
|
|
359
367
|
const file = join(root, q.path);
|
|
360
368
|
const inside = await isInsideRoot(deps, root, file, true);
|
|
361
|
-
if (
|
|
369
|
+
if (inside === "missing") return { ok: false, error: { code: "file-not-found", message: q.path } };
|
|
370
|
+
if (inside === "outside") return { ok: false, error: { code: "invalid-path", message: q.path } };
|
|
362
371
|
let info;
|
|
363
372
|
try {
|
|
364
373
|
info = await deps.fs.stat(file);
|
|
365
374
|
} catch (error) {
|
|
366
|
-
return { ok: false, error: { code: "git-error", message: error instanceof Error ? error.message : "stat failed" } };
|
|
375
|
+
return { ok: false, error: { code: isMissingPath(error) ? "file-not-found" : "git-error", message: error instanceof Error ? error.message : "stat failed" } };
|
|
367
376
|
}
|
|
368
377
|
const mime = imageMimeFor(q.path);
|
|
369
378
|
const cap = mime !== null ? config.maxBytes : Math.max(config.maxBytes, FILE_COPY_MAX_BYTES);
|
|
@@ -374,7 +383,7 @@ async function queryFileContent(deps, config, root, q) {
|
|
|
374
383
|
try {
|
|
375
384
|
buf = await deps.fs.readFile(file);
|
|
376
385
|
} catch (error) {
|
|
377
|
-
return { ok: false, error: { code: "git-error", message: error instanceof Error ? error.message : "read failed" } };
|
|
386
|
+
return { ok: false, error: { code: isMissingPath(error) ? "file-not-found" : "git-error", message: error instanceof Error ? error.message : "read failed" } };
|
|
378
387
|
}
|
|
379
388
|
if (buf.length > cap) return tooLarge;
|
|
380
389
|
if (mime !== null) {
|
|
@@ -383,19 +392,22 @@ async function queryFileContent(deps, config, root, q) {
|
|
|
383
392
|
if (isBinaryBuffer(buf)) return { ok: true, value: { kind: "file-content", path: q.path, variant: "binary", ...statFields } };
|
|
384
393
|
const content = buf.toString("utf8");
|
|
385
394
|
const lines = content === "" ? 0 : content.split("\n").length - (content.endsWith("\n") ? 1 : 0);
|
|
386
|
-
return { ok: true, value: { kind: "file-content", path: q.path, variant: "text", content, lines, ...statFields } };
|
|
395
|
+
return { ok: true, value: { kind: "file-content", path: q.path, variant: "text", content, lines, absolutePath: file, ...statFields } };
|
|
387
396
|
}
|
|
388
397
|
function hasGitSegment(path) {
|
|
389
398
|
return path.split(/[\\/]/).includes(".git");
|
|
390
399
|
}
|
|
400
|
+
function isMissingPath(error) {
|
|
401
|
+
return error?.code === "ENOENT";
|
|
402
|
+
}
|
|
391
403
|
async function isInsideRoot(deps, root, path, hideGit = false) {
|
|
392
404
|
try {
|
|
393
405
|
const real = await deps.fs.realpath(path);
|
|
394
406
|
const rootReal = await deps.fs.realpath(root);
|
|
395
|
-
if (real !== rootReal && !real.startsWith(rootReal + sep)) return
|
|
396
|
-
return !hideGit || !hasGitSegment(real.slice(rootReal.length + 1));
|
|
397
|
-
} catch {
|
|
398
|
-
return
|
|
407
|
+
if (real !== rootReal && !real.startsWith(rootReal + sep)) return "outside";
|
|
408
|
+
return !hideGit || !hasGitSegment(real.slice(rootReal.length + 1)) ? "inside" : "outside";
|
|
409
|
+
} catch (error) {
|
|
410
|
+
return isMissingPath(error) ? "missing" : "outside";
|
|
399
411
|
}
|
|
400
412
|
}
|
|
401
413
|
function isBinaryBuffer(buf) {
|
|
@@ -501,6 +513,53 @@ async function queryWorktreeStats(deps, config, sessionId) {
|
|
|
501
513
|
if (!snapshot.ok) return { ok: false, error: { code: "git-error", message: "snapshot failed" } };
|
|
502
514
|
return { ok: true, value: { kind: "worktree-stats", stats: snapshot.value.stats } };
|
|
503
515
|
}
|
|
516
|
+
async function queryModelCatalog(deps) {
|
|
517
|
+
const llm = deps.getLlm?.();
|
|
518
|
+
const empty = { kind: "model-catalog", groups: [], failures: [] };
|
|
519
|
+
if (llm === void 0 || typeof llm.listModels !== "function") {
|
|
520
|
+
return { ok: true, value: empty };
|
|
521
|
+
}
|
|
522
|
+
const listModels = llm.listModels.bind(llm);
|
|
523
|
+
const resolveModelInfo = typeof llm.resolveModelInfo === "function" ? llm.resolveModelInfo.bind(llm) : void 0;
|
|
524
|
+
const providers = llm.listProviders?.() ?? [];
|
|
525
|
+
const results = await Promise.all(providers.map(async (provider) => {
|
|
526
|
+
const name = provider.name ?? provider.id;
|
|
527
|
+
let listed;
|
|
528
|
+
try {
|
|
529
|
+
listed = await listModels(provider.id);
|
|
530
|
+
} catch (error) {
|
|
531
|
+
return { id: provider.id, name, message: error instanceof Error ? error.message : String(error) };
|
|
532
|
+
}
|
|
533
|
+
const models = await Promise.all(listed.map(async (model) => {
|
|
534
|
+
const entry = { id: model.id, name: model.name ?? model.id };
|
|
535
|
+
if (resolveModelInfo === void 0) return entry;
|
|
536
|
+
try {
|
|
537
|
+
const info = await resolveModelInfo(provider.id, model.id, deps.signal);
|
|
538
|
+
const reasoning = info.reasoning;
|
|
539
|
+
if (reasoning !== void 0 && reasoning.efforts.length > 0) {
|
|
540
|
+
return {
|
|
541
|
+
...entry,
|
|
542
|
+
reasoning: {
|
|
543
|
+
efforts: reasoning.efforts.map((effort) => ({ id: effort.id, name: effort.name }))
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
} catch {
|
|
548
|
+
}
|
|
549
|
+
return entry;
|
|
550
|
+
}));
|
|
551
|
+
if (models.length === 0) return null;
|
|
552
|
+
return { id: provider.id, name, models };
|
|
553
|
+
}));
|
|
554
|
+
const groups = [];
|
|
555
|
+
const failures = [];
|
|
556
|
+
for (const one of results) {
|
|
557
|
+
if (one === null) continue;
|
|
558
|
+
if ("models" in one) groups.push(one);
|
|
559
|
+
else failures.push(one);
|
|
560
|
+
}
|
|
561
|
+
return { ok: true, value: { kind: "model-catalog", groups, failures } };
|
|
562
|
+
}
|
|
504
563
|
export {
|
|
505
564
|
runQuery
|
|
506
565
|
};
|
package/lib/host/suggest.js
CHANGED
|
@@ -60,9 +60,20 @@ function systemPrompt(language) {
|
|
|
60
60
|
}
|
|
61
61
|
const UNTRACKED_FILE_CAP = 16;
|
|
62
62
|
const PROMPT_FILE_CAP = 200;
|
|
63
|
-
function resolveRoute(config, agentDefaultModel) {
|
|
63
|
+
function resolveRoute(config, sessionModel, agentDefaultModel) {
|
|
64
64
|
if (config.suggestProvider !== void 0 && config.suggestModel !== void 0) {
|
|
65
|
-
return {
|
|
65
|
+
return {
|
|
66
|
+
provider: config.suggestProvider,
|
|
67
|
+
model: config.suggestModel,
|
|
68
|
+
...config.suggestReasoningEffort !== void 0 ? { reasoningEffort: config.suggestReasoningEffort } : {}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (sessionModel !== void 0) {
|
|
72
|
+
return {
|
|
73
|
+
provider: sessionModel.provider,
|
|
74
|
+
model: sessionModel.model,
|
|
75
|
+
...sessionModel.reasoningEffort !== void 0 ? { reasoningEffort: sessionModel.reasoningEffort } : {}
|
|
76
|
+
};
|
|
66
77
|
}
|
|
67
78
|
return agentDefaultModel?.currentSelection();
|
|
68
79
|
}
|
|
@@ -238,7 +249,7 @@ async function runSuggest(deps, config, request) {
|
|
|
238
249
|
diff = untracked.diff;
|
|
239
250
|
truncated = truncated || untracked.truncated;
|
|
240
251
|
const llm = deps.getLlm?.();
|
|
241
|
-
const route = resolveRoute(config, deps.getAgentDefaultModel?.());
|
|
252
|
+
const route = resolveRoute(config, deps.getSessionModel?.(request.sessionId), deps.getAgentDefaultModel?.());
|
|
242
253
|
if (llm === void 0 || route === void 0) {
|
|
243
254
|
return { ok: false, error: { code: "llm-unavailable", message: "no model service or route is configured" } };
|
|
244
255
|
}
|
|
@@ -252,6 +263,10 @@ async function runSuggest(deps, config, request) {
|
|
|
252
263
|
const options = {
|
|
253
264
|
provider: route.provider,
|
|
254
265
|
model: route.model,
|
|
266
|
+
// Catalog effort ids come from the same adapter surface GenerateOptions
|
|
267
|
+
// accepts; the brand is compile-time only (the runtime factory lives in
|
|
268
|
+
// @deepseek-ai/dsh-llm, which is externalized), so a cast is the boundary.
|
|
269
|
+
...route.reasoningEffort !== void 0 ? { reasoningEffort: route.reasoningEffort } : {},
|
|
255
270
|
system: systemPrompt(language),
|
|
256
271
|
messages: [{
|
|
257
272
|
id: "git-panel-suggest",
|
|
@@ -292,6 +307,7 @@ async function runSuggest(deps, config, request) {
|
|
|
292
307
|
message,
|
|
293
308
|
provider: route.provider,
|
|
294
309
|
model: route.model,
|
|
310
|
+
...route.reasoningEffort !== void 0 ? { reasoningEffort: route.reasoningEffort } : {},
|
|
295
311
|
...truncated ? { truncated: true } : {}
|
|
296
312
|
}
|
|
297
313
|
};
|
package/lib/host/types.d.ts
CHANGED
|
@@ -228,7 +228,7 @@ export type GitAction = {
|
|
|
228
228
|
/** `git reset` modes, from least to most destructive. Single source so the
|
|
229
229
|
* client dialog and the command builder cannot drift. */
|
|
230
230
|
export type ResetMode = 'soft' | 'mixed' | 'hard';
|
|
231
|
-
export type GitErrorCode = 'cwd-unavailable' | 'not-a-git-repo' | 'git-unavailable' | 'invalid-path' | 'invalid-name' | 'invalid-index' | 'git-error' | 'timeout' | 'cancelled' | 'empty-message' | 'local-changes-block' | 'not-ff' | 'push-rejected' | 'auth-failed' | 'conflict' | 'revert-conflict' | 'revert-stuck' | 'revert-merge' | 'not-found' | 'branch-in-use' | 'stash-switch-failed' | 'index-busy' | 'nested-repo' | 'nested-repo-partial' | 'empty-diff' | 'llm-unavailable' | 'llm-error' | 'llm-output' | 'suggest-disabled';
|
|
231
|
+
export type GitErrorCode = 'cwd-unavailable' | 'not-a-git-repo' | 'git-unavailable' | 'invalid-path' | 'invalid-name' | 'invalid-index' | 'git-error' | 'timeout' | 'cancelled' | 'empty-message' | 'local-changes-block' | 'not-ff' | 'push-rejected' | 'auth-failed' | 'conflict' | 'revert-conflict' | 'revert-stuck' | 'revert-merge' | 'not-found' | 'file-not-found' | 'branch-in-use' | 'stash-switch-failed' | 'index-busy' | 'nested-repo' | 'nested-repo-partial' | 'empty-diff' | 'llm-unavailable' | 'llm-error' | 'llm-output' | 'suggest-disabled';
|
|
232
232
|
export type GitActionResult = {
|
|
233
233
|
readonly ok: true;
|
|
234
234
|
readonly snapshot: GitSnapshot;
|
|
@@ -324,6 +324,8 @@ export type GitQuery = {
|
|
|
324
324
|
readonly kind: 'pull-preview';
|
|
325
325
|
} | {
|
|
326
326
|
readonly kind: 'push-preview';
|
|
327
|
+
} | {
|
|
328
|
+
readonly kind: 'model-catalog';
|
|
327
329
|
};
|
|
328
330
|
/** One entry in a `dir-list` result. */
|
|
329
331
|
export interface DirEntry {
|
|
@@ -358,6 +360,33 @@ export interface StashEntry {
|
|
|
358
360
|
/** Human relative time (e.g. "2 hours ago"). */
|
|
359
361
|
readonly relTime: string;
|
|
360
362
|
}
|
|
363
|
+
/** Selectable reasoning effort for one exact model route (ids go straight
|
|
364
|
+
* into `GenerateOptions.reasoningEffort`). */
|
|
365
|
+
export interface ModelCatalogEffort {
|
|
366
|
+
readonly id: string;
|
|
367
|
+
readonly name: string;
|
|
368
|
+
}
|
|
369
|
+
/** One model inside a provider group of the host-level catalog. */
|
|
370
|
+
export interface ModelCatalogModel {
|
|
371
|
+
readonly id: string;
|
|
372
|
+
readonly name: string;
|
|
373
|
+
/** Adapter-advertised efforts; absent when the model exposes none. */
|
|
374
|
+
readonly reasoning?: {
|
|
375
|
+
readonly efforts: readonly ModelCatalogEffort[];
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
/** One provider and its successfully listed models. */
|
|
379
|
+
export interface ModelCatalogGroup {
|
|
380
|
+
readonly id: string;
|
|
381
|
+
readonly name: string;
|
|
382
|
+
readonly models: readonly ModelCatalogModel[];
|
|
383
|
+
}
|
|
384
|
+
/** One provider / model whose catalog lookup failed in isolation. */
|
|
385
|
+
export interface ModelCatalogFailure {
|
|
386
|
+
readonly id: string;
|
|
387
|
+
readonly name: string;
|
|
388
|
+
readonly message: string;
|
|
389
|
+
}
|
|
361
390
|
/** Working-tree statistics for the changes page header. */
|
|
362
391
|
export interface WorktreeStats {
|
|
363
392
|
/** Distinct changed-file paths (staged ∪ modified ∪ untracked). */
|
|
@@ -492,6 +521,10 @@ export type GitQueryResult = {
|
|
|
492
521
|
readonly files: number;
|
|
493
522
|
readonly insertions: number;
|
|
494
523
|
readonly deletions: number;
|
|
524
|
+
} | {
|
|
525
|
+
readonly kind: 'model-catalog';
|
|
526
|
+
readonly groups: readonly ModelCatalogGroup[];
|
|
527
|
+
readonly failures: readonly ModelCatalogFailure[];
|
|
495
528
|
} | {
|
|
496
529
|
readonly kind: 'dir-list';
|
|
497
530
|
readonly path: string;
|
|
@@ -505,6 +538,11 @@ export type GitQueryResult = {
|
|
|
505
538
|
readonly content?: string;
|
|
506
539
|
/** text variant: total line count. */
|
|
507
540
|
readonly lines?: number;
|
|
541
|
+
/** text variant: absolute path of this file — the rendered-Markdown
|
|
542
|
+
* image resolver needs the document's own location to resolve relative
|
|
543
|
+
* destinations onto the authenticated `api/file` route. Absent on older
|
|
544
|
+
* hosts; the client then keeps the alt-text fallback. */
|
|
545
|
+
readonly absolutePath?: string;
|
|
508
546
|
/** image variant: a `data:<mime>;base64,…` URL. */
|
|
509
547
|
readonly dataUrl?: string;
|
|
510
548
|
/** The file exceeded the byte cap, so no content is returned. */
|
|
@@ -591,6 +629,8 @@ export type GitSuggestResult = {
|
|
|
591
629
|
/** Actual provider/model route used (display only). */
|
|
592
630
|
readonly provider?: string;
|
|
593
631
|
readonly model?: string;
|
|
632
|
+
/** Effort actually sent; absent when the request omitted it. */
|
|
633
|
+
readonly reasoningEffort?: string;
|
|
594
634
|
/** The diff reached the model truncated: over the input cap, or cut
|
|
595
635
|
* by the per-command runner stdout cap before it. */
|
|
596
636
|
readonly truncated?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xbzbing/dsh-git-panel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "DeepSeek Harness (dsh) plugin: an IDE-style Git panel in the Web GUI — branch/history overview, uncommitted changes with commit & amend, diff stats, and a zsh-style branch marker in the input bar.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"lib/client.js",
|
|
34
|
+
"lib/client.mermaid.js",
|
|
34
35
|
"lib/host",
|
|
35
36
|
"locale/*.json",
|
|
36
37
|
"icon.svg",
|
|
@@ -41,11 +42,12 @@
|
|
|
41
42
|
],
|
|
42
43
|
"scripts": {
|
|
43
44
|
"build": "node build.mjs",
|
|
45
|
+
"lint": "eslint .",
|
|
44
46
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
45
|
-
"test:unit": "node build.mjs && node --test
|
|
47
|
+
"test:unit": "node build.mjs && node --test test/unit/*.test.mjs",
|
|
46
48
|
"test:e2e": "node build.mjs && node test/e2e/setup.mjs && node test/e2e/all.mjs",
|
|
47
49
|
"test": "npm run test:unit && npm run test:e2e",
|
|
48
|
-
"check": "npm run
|
|
50
|
+
"check": "npm run lint && npm run typecheck && npm run test"
|
|
49
51
|
},
|
|
50
52
|
"dsh": {
|
|
51
53
|
"bundle": {
|
|
@@ -63,7 +65,10 @@
|
|
|
63
65
|
"compatibility": {
|
|
64
66
|
"dsh": ">=0.1.7-rc.2",
|
|
65
67
|
"dshReleases": {
|
|
66
|
-
"0.
|
|
68
|
+
"0.1.7-rc.2": "compatible",
|
|
69
|
+
"0.2.0-rc.2": "compatible",
|
|
70
|
+
"0.2.1-rc.1": "compatible",
|
|
71
|
+
"0.2.1-rc.2": "compatible"
|
|
67
72
|
}
|
|
68
73
|
}
|
|
69
74
|
},
|
|
@@ -84,14 +89,23 @@
|
|
|
84
89
|
"@deepseek-ai/dsh-typert-protocol": ">=0.1.2-rc.1",
|
|
85
90
|
"@deepseek-ai/schemastery": ">=3.18.4"
|
|
86
91
|
},
|
|
92
|
+
"peerDependenciesMeta": {
|
|
93
|
+
"@deepseek-ai/dsh": {
|
|
94
|
+
"optional": true
|
|
95
|
+
}
|
|
96
|
+
},
|
|
87
97
|
"devDependencies": {
|
|
88
98
|
"@types/node": "22.10.5",
|
|
89
99
|
"@types/react": "18.3.18",
|
|
90
100
|
"@types/react-dom": "18.3.5",
|
|
91
101
|
"esbuild": "0.24.2",
|
|
102
|
+
"eslint": "9.39.5",
|
|
103
|
+
"eslint-plugin-react-hooks": "7.1.1",
|
|
104
|
+
"mermaid": "12.2.0",
|
|
92
105
|
"playwright-core": "1.63.0",
|
|
93
106
|
"react": "18.3.1",
|
|
94
107
|
"react-dom": "18.3.1",
|
|
95
|
-
"typescript": "5.7.3"
|
|
108
|
+
"typescript": "5.7.3",
|
|
109
|
+
"typescript-eslint": "8.71.2"
|
|
96
110
|
}
|
|
97
111
|
}
|