dsh-more 0.0.1 → 0.0.3
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/CONTRIBUTING.md +6 -6
- package/README.md +21 -9
- package/README.zh-CN.md +21 -9
- package/dist/client.js +1232 -174
- package/dist/index.js +451 -25
- package/dist/types/generated/host-settings-schema.d.ts +6 -0
- package/dist/types/generated/patch-catalog.d.ts +24 -0
- package/dist/types/patches/conversation-markdown-export/client/filename.d.ts +1 -0
- package/dist/types/patches/conversation-markdown-export/client/index.d.ts +2 -0
- package/dist/types/patches/conversation-markdown-export/host/index.d.ts +2 -0
- package/dist/types/patches/conversation-markdown-export/host/transcript.d.ts +9 -0
- package/dist/types/patches/conversation-markdown-export/shared.d.ts +5 -0
- package/dist/types/patches/conversation-quick-navigation/client/index.d.ts +22 -0
- package/dist/types/patches/conversation-quick-navigation/client/navigation.d.ts +26 -0
- package/dist/types/patches/conversation-quick-navigation/host/index.d.ts +2 -0
- package/dist/types/patches/conversation-quick-navigation/shared.d.ts +1 -0
- package/dist/types/patches/model-reasoning-efforts/client/index.d.ts +39 -0
- package/dist/types/patches/model-reasoning-efforts/host/index.d.ts +2 -0
- package/dist/types/patches/model-reasoning-efforts/host/validate.d.ts +13 -0
- package/dist/types/patches/model-reasoning-efforts/shared.d.ts +51 -0
- package/dist/types/patches/session-delete/host/session-deletion.d.ts +0 -1
- package/dist/types/platform/dsh/client/api.d.ts +2 -0
- package/dist/types/platform/dsh/client/message-targets.d.ts +13 -1
- package/dist/types/platform/dsh/client/styles.d.ts +1 -1
- package/dist/types/platform/dsh/host/continuation.d.ts +4 -0
- package/dist/types/platform/dsh/host/error.d.ts +1 -1
- package/dist/types/platform/dsh/host/wire.d.ts +7 -0
- package/dsh.plugin.json +12 -2
- package/package.json +7 -4
package/dist/index.js
CHANGED
|
@@ -16,6 +16,8 @@ var DshMoreError = class extends Error {
|
|
|
16
16
|
this.status = status;
|
|
17
17
|
this.name = "DshMoreError";
|
|
18
18
|
}
|
|
19
|
+
code;
|
|
20
|
+
status;
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
// src/platform/dsh/host/agent-session.ts
|
|
@@ -76,13 +78,28 @@ function verifyConfirmationToken(secret, token, expected, now = Date.now()) {
|
|
|
76
78
|
|
|
77
79
|
// src/platform/dsh/host/wire.ts
|
|
78
80
|
var MAX_BODY_BYTES = 64 * 1024;
|
|
81
|
+
var RawPatchResponse = class {
|
|
82
|
+
constructor(body, contentType) {
|
|
83
|
+
this.body = body;
|
|
84
|
+
this.contentType = contentType;
|
|
85
|
+
}
|
|
86
|
+
body;
|
|
87
|
+
contentType;
|
|
88
|
+
};
|
|
89
|
+
function requireJsonContentType(req) {
|
|
90
|
+
const value = req.headers["content-type"];
|
|
91
|
+
const contentType = (Array.isArray(value) ? value[0] : value)?.split(";", 1)[0]?.trim().toLowerCase();
|
|
92
|
+
if (contentType !== "application/json") {
|
|
93
|
+
throw new DshMoreError("unsupported-media-type", "\u8BF7\u6C42\u5FC5\u987B\u4F7F\u7528 application/json\u3002", 415);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
79
96
|
async function readJson(req) {
|
|
80
97
|
const chunks = [];
|
|
81
98
|
let size = 0;
|
|
82
99
|
for await (const chunk of req) {
|
|
83
100
|
const bytes = Buffer.from(chunk);
|
|
84
101
|
size += bytes.length;
|
|
85
|
-
if (size > MAX_BODY_BYTES) throw new DshMoreError("
|
|
102
|
+
if (size > MAX_BODY_BYTES) throw new DshMoreError("payload-too-large", "\u8BF7\u6C42\u5185\u5BB9\u8FC7\u5927\u3002", 413);
|
|
86
103
|
chunks.push(bytes);
|
|
87
104
|
}
|
|
88
105
|
const text = Buffer.concat(chunks).toString("utf8");
|
|
@@ -112,6 +129,15 @@ function write(res, status, body) {
|
|
|
112
129
|
res.end(JSON.stringify(body));
|
|
113
130
|
}
|
|
114
131
|
function writeOk(res, value) {
|
|
132
|
+
if (value instanceof RawPatchResponse) {
|
|
133
|
+
res.writeHead(200, {
|
|
134
|
+
"content-type": value.contentType,
|
|
135
|
+
"cache-control": "no-store",
|
|
136
|
+
"x-content-type-options": "nosniff"
|
|
137
|
+
});
|
|
138
|
+
res.end(value.body);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
115
141
|
write(res, 200, { ok: true, value });
|
|
116
142
|
}
|
|
117
143
|
function writeError(res, error) {
|
|
@@ -119,8 +145,7 @@ function writeError(res, error) {
|
|
|
119
145
|
write(res, error.status, { ok: false, error: { code: error.code, message: error.message } });
|
|
120
146
|
return;
|
|
121
147
|
}
|
|
122
|
-
|
|
123
|
-
write(res, 500, { ok: false, error: { code: "internal", message } });
|
|
148
|
+
write(res, 500, { ok: false, error: { code: "internal", message: "\u5185\u90E8\u9519\u8BEF\uFF0C\u8BF7\u91CD\u8BD5\u6216\u67E5\u770B DSH \u65E5\u5FD7\u3002" } });
|
|
124
149
|
}
|
|
125
150
|
|
|
126
151
|
// src/patches/message-edit/shared.ts
|
|
@@ -180,6 +205,25 @@ function replaySeedRuntimeContext(agentCtx, seed) {
|
|
|
180
205
|
}));
|
|
181
206
|
}
|
|
182
207
|
|
|
208
|
+
// src/platform/dsh/host/continuation.ts
|
|
209
|
+
async function rollbackFailedContinuation(child, workspace, attached, cause) {
|
|
210
|
+
const errors = [cause];
|
|
211
|
+
if (attached && workspace !== void 0) {
|
|
212
|
+
try {
|
|
213
|
+
await workspace.detachSession(child.agent.id);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
errors.push(error);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
await child.dispose();
|
|
220
|
+
} catch (error) {
|
|
221
|
+
errors.push(error);
|
|
222
|
+
}
|
|
223
|
+
if (errors.length === 1) throw cause;
|
|
224
|
+
throw new AggregateError(errors, "continuation failed and cleanup was incomplete");
|
|
225
|
+
}
|
|
226
|
+
|
|
183
227
|
// src/patches/message-edit/host/edit-continuation.ts
|
|
184
228
|
function textDigest(text) {
|
|
185
229
|
return createHash("sha256").update(text).digest("hex");
|
|
@@ -244,20 +288,23 @@ async function createEditedContinuation(ctx, sourceAgent, cut, editedText, child
|
|
|
244
288
|
releaseRuntimeContextReplay = replaySeedRuntimeContext(agentCtx, seed);
|
|
245
289
|
}
|
|
246
290
|
});
|
|
291
|
+
const workspace = ctx.workspaceRegistry.list().find((candidate) => candidate.sessionIds.includes(source.id));
|
|
292
|
+
let attached = false;
|
|
247
293
|
try {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
294
|
+
if (workspace !== void 0) {
|
|
295
|
+
await workspace.attachSession(childId);
|
|
296
|
+
attached = true;
|
|
297
|
+
}
|
|
251
298
|
child.agent.followup(createUserMessage({
|
|
252
299
|
content: editedContent(original.data.content, editedText.trim()),
|
|
253
300
|
source: { kind: "user" }
|
|
254
301
|
}));
|
|
255
302
|
void child.agent.whenIdle().then(releaseReplay, releaseReplay);
|
|
303
|
+
await ctx.workspaceRegistry.archiveSession(source.id);
|
|
256
304
|
return { sessionId: childId };
|
|
257
305
|
} catch (error) {
|
|
258
306
|
releaseReplay();
|
|
259
|
-
|
|
260
|
-
throw error;
|
|
307
|
+
return rollbackFailedContinuation(child, workspace, attached, error);
|
|
261
308
|
}
|
|
262
309
|
}
|
|
263
310
|
|
|
@@ -547,15 +594,18 @@ async function createDeletedContinuation(ctx, sourceAgent, selection, childId =
|
|
|
547
594
|
});
|
|
548
595
|
}
|
|
549
596
|
});
|
|
597
|
+
const workspace = ctx.workspaceRegistry.list().find((candidate) => candidate.sessionIds.includes(source.id));
|
|
598
|
+
let attached = false;
|
|
550
599
|
try {
|
|
551
|
-
|
|
552
|
-
|
|
600
|
+
if (workspace !== void 0) {
|
|
601
|
+
await workspace.attachSession(childId);
|
|
602
|
+
attached = true;
|
|
603
|
+
}
|
|
553
604
|
await ctx.workspaceRegistry.archiveSession(source.id);
|
|
554
605
|
return { sessionId: childId };
|
|
555
606
|
} catch (error) {
|
|
556
607
|
releaseReplay();
|
|
557
|
-
|
|
558
|
-
throw error;
|
|
608
|
+
return rollbackFailedContinuation(child, workspace, attached, error);
|
|
559
609
|
}
|
|
560
610
|
}
|
|
561
611
|
|
|
@@ -618,7 +668,7 @@ var hostPatch2 = {
|
|
|
618
668
|
var SESSION_DELETE_PATCH_ID = "session-delete";
|
|
619
669
|
|
|
620
670
|
// src/patches/session-delete/host/live-session-handles.ts
|
|
621
|
-
var TRACKER = Symbol.for("dsh-more.session-delete.live-session-handles");
|
|
671
|
+
var TRACKER = /* @__PURE__ */ Symbol.for("dsh-more.session-delete.live-session-handles");
|
|
622
672
|
function trackedHandle(tracker, handle) {
|
|
623
673
|
let disposed = false;
|
|
624
674
|
const wrapped = {
|
|
@@ -667,7 +717,7 @@ function getLiveSessionHandle(ctx, sessionId) {
|
|
|
667
717
|
|
|
668
718
|
// src/patches/session-delete/host/session-deletion.ts
|
|
669
719
|
import { rm, stat } from "node:fs/promises";
|
|
670
|
-
import { dirname, isAbsolute
|
|
720
|
+
import { basename, dirname, isAbsolute } from "node:path";
|
|
671
721
|
import { SessionId as SessionId6 } from "@deepseek-ai/dsh-session";
|
|
672
722
|
function detachInternal(registry, sessionId, label) {
|
|
673
723
|
const internal = registry;
|
|
@@ -704,14 +754,16 @@ async function unloadLiveSession(ctx, sessionId) {
|
|
|
704
754
|
throw new DshMoreError("internal", "\u4F1A\u8BDD\u505C\u6B62\u540E\u4ECD\u5904\u4E8E\u52A0\u8F7D\u72B6\u6001\uFF0C\u672A\u6267\u884C\u78C1\u76D8\u5220\u9664\u3002", 500);
|
|
705
755
|
}
|
|
706
756
|
}
|
|
707
|
-
function
|
|
757
|
+
function sessionDirectoryFromLocation(locationPath) {
|
|
708
758
|
if (!isAbsolute(locationPath)) {
|
|
709
759
|
throw new DshMoreError("internal", "\u5F53\u524D\u6301\u4E45\u5316\u540E\u7AEF\u6CA1\u6709\u8FD4\u56DE\u7EDD\u5BF9\u4F1A\u8BDD\u8DEF\u5F84\u3002", 409);
|
|
710
760
|
}
|
|
761
|
+
const artifact = basename(locationPath);
|
|
762
|
+
if (artifact !== "session.jsonl" && artifact !== "session.jsonl.zstd") {
|
|
763
|
+
throw new DshMoreError("internal", "\u6301\u4E45\u5316\u540E\u7AEF\u8FD4\u56DE\u4E86\u672A\u77E5\u7684\u4F1A\u8BDD\u6587\u4EF6\u5E03\u5C40\u3002", 500);
|
|
764
|
+
}
|
|
711
765
|
const sessionDir = dirname(locationPath);
|
|
712
|
-
|
|
713
|
-
const withinRoot = relative(sessionsRoot, sessionDir);
|
|
714
|
-
if (withinRoot === "" || withinRoot.startsWith("..") || isAbsolute(withinRoot)) {
|
|
766
|
+
if (sessionDir === dirname(sessionDir)) {
|
|
715
767
|
throw new DshMoreError("internal", "\u6301\u4E45\u5316\u540E\u7AEF\u8FD4\u56DE\u4E86\u4E0D\u5B89\u5168\u7684\u4F1A\u8BDD\u8DEF\u5F84\u3002", 500);
|
|
716
768
|
}
|
|
717
769
|
return sessionDir;
|
|
@@ -728,7 +780,7 @@ async function deleteSessionPermanently(ctx, rawSessionId) {
|
|
|
728
780
|
if (location.kind !== "jsonl") {
|
|
729
781
|
throw new DshMoreError("internal", `\u6301\u4E45\u5316\u540E\u7AEF ${location.kind} \u4E0D\u652F\u6301\u5B89\u5168\u7684\u9010\u76EE\u5F55\u5220\u9664\u3002`, 409);
|
|
730
782
|
}
|
|
731
|
-
const sessionDir =
|
|
783
|
+
const sessionDir = sessionDirectoryFromLocation(location.path);
|
|
732
784
|
await unloadLiveSession(ctx, sessionId);
|
|
733
785
|
const info = await stat(sessionDir).catch(() => void 0);
|
|
734
786
|
if (info !== void 0) {
|
|
@@ -737,7 +789,7 @@ async function deleteSessionPermanently(ctx, rawSessionId) {
|
|
|
737
789
|
}
|
|
738
790
|
const workspaces = ctx.workspaceRegistry.list().filter((candidate) => candidate.sessionIds.includes(sessionId));
|
|
739
791
|
await Promise.all(workspaces.map(async (workspace) => workspace.detachSession(sessionId)));
|
|
740
|
-
return { sessionId
|
|
792
|
+
return { sessionId };
|
|
741
793
|
}
|
|
742
794
|
|
|
743
795
|
// src/patches/session-delete/host/index.ts
|
|
@@ -755,14 +807,378 @@ var hostPatch3 = {
|
|
|
755
807
|
})
|
|
756
808
|
};
|
|
757
809
|
|
|
810
|
+
// src/patches/conversation-markdown-export/shared.ts
|
|
811
|
+
var CONVERSATION_MARKDOWN_EXPORT_PATCH_ID = "conversation-markdown-export";
|
|
812
|
+
|
|
813
|
+
// src/patches/conversation-markdown-export/host/transcript.ts
|
|
814
|
+
function singleLine(value) {
|
|
815
|
+
return value.replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim();
|
|
816
|
+
}
|
|
817
|
+
function isoTime(value) {
|
|
818
|
+
const date = new Date(value);
|
|
819
|
+
return Number.isFinite(date.getTime()) ? date.toISOString() : "\u672A\u77E5\u65F6\u95F4";
|
|
820
|
+
}
|
|
821
|
+
function codeFence(value, language = "") {
|
|
822
|
+
let longest = 0;
|
|
823
|
+
let current = 0;
|
|
824
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
825
|
+
if (value.charCodeAt(index) === 96) {
|
|
826
|
+
current += 1;
|
|
827
|
+
if (current > longest) longest = current;
|
|
828
|
+
} else {
|
|
829
|
+
current = 0;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
const fence = "`".repeat(Math.max(3, longest + 1));
|
|
833
|
+
return `${fence}${language}
|
|
834
|
+
${value}
|
|
835
|
+
${fence}`;
|
|
836
|
+
}
|
|
837
|
+
function jsonBlock(value) {
|
|
838
|
+
let serialized;
|
|
839
|
+
try {
|
|
840
|
+
serialized = JSON.stringify(value, null, 2) ?? String(value);
|
|
841
|
+
} catch {
|
|
842
|
+
serialized = String(value);
|
|
843
|
+
}
|
|
844
|
+
return codeFence(serialized, "json");
|
|
845
|
+
}
|
|
846
|
+
function imageDescription(block) {
|
|
847
|
+
const attachment = block.attachment;
|
|
848
|
+
const name2 = singleLine(attachment.name ?? "\u56FE\u7247") || "\u56FE\u7247";
|
|
849
|
+
return `[${name2}\uFF1B\u9644\u4EF6 ID\uFF1A${String(attachment.attachmentId)}\uFF1B${attachment.mediaType}\uFF1B${String(attachment.width)}\xD7${String(attachment.height)}\uFF1B${String(attachment.bytes)} \u5B57\u8282]`;
|
|
850
|
+
}
|
|
851
|
+
function contentBlockMarkdown(block) {
|
|
852
|
+
switch (block.type) {
|
|
853
|
+
case "text":
|
|
854
|
+
return block.text;
|
|
855
|
+
case "reasoning":
|
|
856
|
+
return `<details>
|
|
857
|
+
<summary>\u601D\u8003\u8FC7\u7A0B</summary>
|
|
858
|
+
|
|
859
|
+
${block.text}
|
|
860
|
+
|
|
861
|
+
</details>`;
|
|
862
|
+
case "image":
|
|
863
|
+
return imageDescription(block);
|
|
864
|
+
case "tool-call": {
|
|
865
|
+
const name2 = singleLine(block.name) || "\u672A\u547D\u540D\u5DE5\u5177";
|
|
866
|
+
return `### \u5DE5\u5177\u8C03\u7528\uFF1A${name2}
|
|
867
|
+
|
|
868
|
+
${codeFence(block.arguments, "json")}`;
|
|
869
|
+
}
|
|
870
|
+
case "tool-result": {
|
|
871
|
+
const status = block.isError === true ? "\uFF08\u5931\u8D25\uFF09" : "";
|
|
872
|
+
return `### \u5DE5\u5177\u7ED3\u679C${status}
|
|
873
|
+
|
|
874
|
+
${contentMarkdown(block.content)}`;
|
|
875
|
+
}
|
|
876
|
+
default:
|
|
877
|
+
return `### \u672A\u8BC6\u522B\u5185\u5BB9
|
|
878
|
+
|
|
879
|
+
${jsonBlock(block)}`;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
function contentMarkdown(content) {
|
|
883
|
+
if (content.length === 0) return "\uFF08\u7A7A\u6D88\u606F\uFF09";
|
|
884
|
+
const parts = [];
|
|
885
|
+
for (const block of content) {
|
|
886
|
+
const part = contentBlockMarkdown(block);
|
|
887
|
+
if (part !== "") parts.push(part);
|
|
888
|
+
}
|
|
889
|
+
return parts.join("\n\n") || "\uFF08\u7A7A\u6D88\u606F\uFF09";
|
|
890
|
+
}
|
|
891
|
+
function messageSection(label, time, body) {
|
|
892
|
+
return `## ${singleLine(label)}
|
|
893
|
+
|
|
894
|
+
> \u65F6\u95F4\uFF1A${isoTime(time)}
|
|
895
|
+
|
|
896
|
+
${body}`;
|
|
897
|
+
}
|
|
898
|
+
function sourceLabel(event) {
|
|
899
|
+
const source = event.data.source;
|
|
900
|
+
if (source.kind === "user") return "\u7528\u6237";
|
|
901
|
+
if (source.kind === "plugin") return `\u4E0A\u4E0B\u6587 \xB7 ${singleLine(source.plugin) || "\u63D2\u4EF6"}`;
|
|
902
|
+
return "\u4E0A\u4E0B\u6587";
|
|
903
|
+
}
|
|
904
|
+
function conversationMarkdown(events, options) {
|
|
905
|
+
const sections = [];
|
|
906
|
+
const renderedCalls = /* @__PURE__ */ new Set();
|
|
907
|
+
const callNames = /* @__PURE__ */ new Map();
|
|
908
|
+
for (const event of events) {
|
|
909
|
+
if (event.type === "user/message") {
|
|
910
|
+
sections.push(messageSection(sourceLabel(event), event.time, contentMarkdown(event.data.content)));
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
if (event.type === "assistant/message") {
|
|
914
|
+
for (const block of event.data.message.content) {
|
|
915
|
+
if (block.type === "tool-call") renderedCalls.add(String(block.id));
|
|
916
|
+
}
|
|
917
|
+
sections.push(messageSection("\u52A9\u624B", event.time, contentMarkdown(event.data.message.content)));
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
if (event.type === "tool/call") {
|
|
921
|
+
const callId = String(event.data.callId);
|
|
922
|
+
callNames.set(callId, event.data.name);
|
|
923
|
+
if (!renderedCalls.has(callId)) {
|
|
924
|
+
sections.push(messageSection(
|
|
925
|
+
`\u5DE5\u5177\u8C03\u7528 \xB7 ${singleLine(event.data.name) || "\u672A\u547D\u540D\u5DE5\u5177"}`,
|
|
926
|
+
event.time,
|
|
927
|
+
codeFence(event.data.arguments, "json")
|
|
928
|
+
));
|
|
929
|
+
renderedCalls.add(callId);
|
|
930
|
+
}
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
if (event.type === "tool/result") {
|
|
934
|
+
const callId = String(event.data.message.source.callId);
|
|
935
|
+
const block = event.data.message.content[0];
|
|
936
|
+
const failed = block?.type === "tool-result" && block.isError === true;
|
|
937
|
+
const resultContent = block?.type === "tool-result" ? block.content : event.data.message.content;
|
|
938
|
+
const name2 = singleLine(callNames.get(callId) ?? callId) || "\u672A\u547D\u540D\u5DE5\u5177";
|
|
939
|
+
sections.push(messageSection(
|
|
940
|
+
`\u5DE5\u5177\u7ED3\u679C \xB7 ${name2}${failed ? "\uFF08\u5931\u8D25\uFF09" : ""}`,
|
|
941
|
+
event.time,
|
|
942
|
+
contentMarkdown(resultContent)
|
|
943
|
+
));
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
const title = singleLine(options.title) || "\u672A\u547D\u540D\u4F1A\u8BDD";
|
|
947
|
+
const exportedAt = isoTime(options.exportedAt ?? Date.now());
|
|
948
|
+
const header2 = [
|
|
949
|
+
`# ${title}`,
|
|
950
|
+
`- \u4F1A\u8BDD ID\uFF1A${singleLine(options.sessionId)}`,
|
|
951
|
+
`- \u5BFC\u51FA\u65F6\u95F4\uFF1A${exportedAt}`,
|
|
952
|
+
`- \u8BB0\u5F55\u6761\u76EE\uFF1A${String(sections.length)}`
|
|
953
|
+
].join("\n\n");
|
|
954
|
+
return {
|
|
955
|
+
markdown: sections.length === 0 ? `${header2}
|
|
956
|
+
|
|
957
|
+
\uFF08\u6682\u65E0\u804A\u5929\u8BB0\u5F55\uFF09
|
|
958
|
+
` : `${header2}
|
|
959
|
+
|
|
960
|
+
---
|
|
961
|
+
|
|
962
|
+
${sections.join("\n\n---\n\n")}
|
|
963
|
+
`,
|
|
964
|
+
entryCount: sections.length
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// src/patches/conversation-markdown-export/host/index.ts
|
|
969
|
+
var MAX_TITLE_LENGTH = 500;
|
|
970
|
+
function render(ctx, payload) {
|
|
971
|
+
const sessionId = requireString(payload, "sessionId");
|
|
972
|
+
const title = requireString(payload, "title");
|
|
973
|
+
if ([...title].length > MAX_TITLE_LENGTH) throw new DshMoreError("bad-request", "\u4F1A\u8BDD\u6807\u9898\u8FC7\u957F\u3002");
|
|
974
|
+
const session = requireLiveSession(ctx, sessionId);
|
|
975
|
+
requireIdleAgent(ctx, sessionId, session);
|
|
976
|
+
const transcript = conversationMarkdown(session.events, { title, sessionId });
|
|
977
|
+
return new RawPatchResponse(transcript.markdown, "text/markdown; charset=utf-8");
|
|
978
|
+
}
|
|
979
|
+
var hostPatch4 = {
|
|
980
|
+
id: CONVERSATION_MARKDOWN_EXPORT_PATCH_ID,
|
|
981
|
+
routes: ({ ctx }) => ({
|
|
982
|
+
render: (payload) => render(ctx, payload)
|
|
983
|
+
})
|
|
984
|
+
};
|
|
985
|
+
|
|
986
|
+
// src/patches/model-reasoning-efforts/host/index.ts
|
|
987
|
+
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
988
|
+
|
|
989
|
+
// src/patches/model-reasoning-efforts/shared.ts
|
|
990
|
+
var MODEL_REASONING_EFFORTS_PATCH_ID = "model-reasoning-efforts";
|
|
991
|
+
var LLM_PI_AI_SETTINGS_NAMESPACE = "llm-pi-ai";
|
|
992
|
+
var THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
993
|
+
var DEFAULT_REASONING_EFFORTS = {
|
|
994
|
+
low: "low",
|
|
995
|
+
medium: "medium",
|
|
996
|
+
high: "high"
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
// src/patches/model-reasoning-efforts/host/validate.ts
|
|
1000
|
+
var LEVEL_SET = new Set(THINKING_LEVELS);
|
|
1001
|
+
function validateReasoningEfforts(raw) {
|
|
1002
|
+
if (raw === null || raw === void 0) return null;
|
|
1003
|
+
if (raw === false) return false;
|
|
1004
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
1005
|
+
throw new DshMoreError("bad-request", "reasoningEfforts \u5FC5\u987B\u662F false\u3001\u601D\u8003\u7EA7\u522B\u5B57\u5178\u6216 null\u3002");
|
|
1006
|
+
}
|
|
1007
|
+
const dict = raw;
|
|
1008
|
+
const keys = Object.keys(dict);
|
|
1009
|
+
if (keys.length === 0) throw new DshMoreError("bad-request", "\u81F3\u5C11\u9700\u8981\u58F0\u660E\u4E00\u4E2A\u601D\u8003\u7EA7\u522B\u3002");
|
|
1010
|
+
const result = {};
|
|
1011
|
+
let offersThinking = false;
|
|
1012
|
+
for (const key of keys) {
|
|
1013
|
+
if (!LEVEL_SET.has(key)) throw new DshMoreError("bad-request", `\u672A\u77E5\u7684\u601D\u8003\u7EA7\u522B\uFF1A${key}`);
|
|
1014
|
+
const value = dict[key];
|
|
1015
|
+
if (value === null) {
|
|
1016
|
+
if (key !== "off") throw new DshMoreError("bad-request", `\u601D\u8003\u7EA7\u522B ${key} \u5FC5\u987B\u586B\u5199\u7EBF\u4E0A\u503C\uFF0C\u53EA\u6709 off \u53EF\u4EE5\u4E3A\u7A7A\u3002`);
|
|
1017
|
+
result[key] = null;
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1021
|
+
throw new DshMoreError("bad-request", `\u601D\u8003\u7EA7\u522B ${key} \u7684\u7EBF\u4E0A\u503C\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u3002`);
|
|
1022
|
+
}
|
|
1023
|
+
result[key] = value;
|
|
1024
|
+
if (key !== "off") offersThinking = true;
|
|
1025
|
+
}
|
|
1026
|
+
if (!offersThinking) throw new DshMoreError("bad-request", "\u9700\u8981\u81F3\u5C11\u58F0\u660E\u4E00\u4E2A\u975E off \u7684\u601D\u8003\u7EA7\u522B\u3002");
|
|
1027
|
+
return result;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// src/patches/model-reasoning-efforts/host/index.ts
|
|
1031
|
+
var LLM_PI_AI_NS = settingsNamespace(LLM_PI_AI_SETTINGS_NAMESPACE);
|
|
1032
|
+
function piAiSection(ctx) {
|
|
1033
|
+
const value = ctx.settings.get(LLM_PI_AI_NS);
|
|
1034
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
1035
|
+
const providers = value.providers;
|
|
1036
|
+
if (typeof providers !== "object" || providers === null || Array.isArray(providers)) return void 0;
|
|
1037
|
+
return { providers };
|
|
1038
|
+
}
|
|
1039
|
+
function normalizeEfforts(value) {
|
|
1040
|
+
if (value === false) return false;
|
|
1041
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
1042
|
+
return value;
|
|
1043
|
+
}
|
|
1044
|
+
function read(ctx) {
|
|
1045
|
+
const section = piAiSection(ctx);
|
|
1046
|
+
const rows = [];
|
|
1047
|
+
if (section !== void 0) {
|
|
1048
|
+
for (const [provider, profile] of Object.entries(section.providers)) {
|
|
1049
|
+
if (typeof profile !== "object" || profile === null || Array.isArray(profile)) continue;
|
|
1050
|
+
const models = profile.models;
|
|
1051
|
+
if (!Array.isArray(models)) continue;
|
|
1052
|
+
const displayName = profile.displayName;
|
|
1053
|
+
const providerName = typeof displayName === "string" && displayName.trim() !== "" ? displayName : provider;
|
|
1054
|
+
for (const model of models) {
|
|
1055
|
+
if (typeof model !== "object" || model === null || Array.isArray(model)) continue;
|
|
1056
|
+
const modelId = model.id;
|
|
1057
|
+
if (typeof modelId !== "string" || modelId === "") continue;
|
|
1058
|
+
rows.push({
|
|
1059
|
+
provider,
|
|
1060
|
+
providerName,
|
|
1061
|
+
modelId,
|
|
1062
|
+
reasoningEfforts: normalizeEfforts(model.reasoningEfforts)
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return {
|
|
1068
|
+
rows,
|
|
1069
|
+
writable: ctx.settings.writable,
|
|
1070
|
+
documentPath: ctx.settings.documentPath
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
async function save(ctx, payload) {
|
|
1074
|
+
const provider = requireString(payload, "provider");
|
|
1075
|
+
const modelId = requireString(payload, "modelId");
|
|
1076
|
+
const reasoningEfforts = validateReasoningEfforts(payload.reasoningEfforts);
|
|
1077
|
+
const section = piAiSection(ctx);
|
|
1078
|
+
if (section === void 0) throw new DshMoreError("bad-request", "llm-pi-ai \u914D\u7F6E\u5C1A\u672A\u6CE8\u518C\uFF0C\u65E0\u6CD5\u4FDD\u5B58\u601D\u8003\u5F3A\u5EA6\u3002");
|
|
1079
|
+
const profile = section.providers[provider];
|
|
1080
|
+
if (typeof profile !== "object" || profile === null || Array.isArray(profile)) {
|
|
1081
|
+
throw new DshMoreError("bad-request", `\u672A\u627E\u5230\u63D0\u4F9B\u65B9 ${provider} \u7684\u914D\u7F6E\u3002`);
|
|
1082
|
+
}
|
|
1083
|
+
const models = profile.models;
|
|
1084
|
+
if (!Array.isArray(models)) throw new DshMoreError("bad-request", `\u63D0\u4F9B\u65B9 ${provider} \u672A\u914D\u7F6E\u4EFB\u4F55\u6A21\u578B\u3002`);
|
|
1085
|
+
const index = models.findIndex((model) => {
|
|
1086
|
+
if (typeof model !== "object" || model === null) return false;
|
|
1087
|
+
return model.id === modelId;
|
|
1088
|
+
});
|
|
1089
|
+
if (index === -1) throw new DshMoreError("bad-request", `\u63D0\u4F9B\u65B9 ${provider} \u4E0B\u672A\u627E\u5230\u6A21\u578B ${modelId}\u3002`);
|
|
1090
|
+
const descriptor = ctx.settings.describe().find((entry) => entry.ns === LLM_PI_AI_NS);
|
|
1091
|
+
if (descriptor === void 0) throw new DshMoreError("bad-request", "llm-pi-ai \u914D\u7F6E\u5C1A\u672A\u6CE8\u518C\uFF0C\u65E0\u6CD5\u4FDD\u5B58\u601D\u8003\u5F3A\u5EA6\u3002");
|
|
1092
|
+
const user = descriptor.user;
|
|
1093
|
+
const userProviders = typeof user === "object" && user !== null && !Array.isArray(user) ? user.providers : void 0;
|
|
1094
|
+
const providers = typeof userProviders === "object" && userProviders !== null && !Array.isArray(userProviders) ? { ...userProviders } : {};
|
|
1095
|
+
const userProfile = providers[provider];
|
|
1096
|
+
const userModels = typeof userProfile === "object" && userProfile !== null && !Array.isArray(userProfile) ? userProfile.models : void 0;
|
|
1097
|
+
const storedModels = Array.isArray(userModels) ? [...userModels] : [];
|
|
1098
|
+
const storedIndex = storedModels.findIndex((model) => {
|
|
1099
|
+
if (typeof model !== "object" || model === null) return false;
|
|
1100
|
+
return model.id === modelId;
|
|
1101
|
+
});
|
|
1102
|
+
if (storedIndex === -1 && reasoningEfforts === null) {
|
|
1103
|
+
return { provider, modelId, reasoningEfforts };
|
|
1104
|
+
}
|
|
1105
|
+
const stored = storedIndex === -1 ? { id: modelId } : storedModels[storedIndex];
|
|
1106
|
+
let nextModel;
|
|
1107
|
+
if (reasoningEfforts === null) {
|
|
1108
|
+
const { reasoningEfforts: _dropped, ...rest } = stored;
|
|
1109
|
+
nextModel = rest;
|
|
1110
|
+
} else {
|
|
1111
|
+
nextModel = { ...stored, reasoningEfforts };
|
|
1112
|
+
}
|
|
1113
|
+
const nextModels = storedIndex === -1 ? [...storedModels, nextModel] : storedModels.map((model, modelIndex) => modelIndex === storedIndex ? nextModel : model);
|
|
1114
|
+
const nextProfile = {
|
|
1115
|
+
...typeof userProfile === "object" && userProfile !== null && !Array.isArray(userProfile) ? userProfile : {},
|
|
1116
|
+
models: nextModels
|
|
1117
|
+
};
|
|
1118
|
+
const ops = [{ op: "set", path: ["providers"], value: { ...providers, [provider]: nextProfile } }];
|
|
1119
|
+
await ctx.settings.mutate(LLM_PI_AI_NS, ops, descriptor.revision);
|
|
1120
|
+
return { provider, modelId, reasoningEfforts };
|
|
1121
|
+
}
|
|
1122
|
+
async function fillDefaults(ctx) {
|
|
1123
|
+
const descriptor = ctx.settings.describe().find((entry) => entry.ns === LLM_PI_AI_NS);
|
|
1124
|
+
if (descriptor === void 0) return { filled: 0 };
|
|
1125
|
+
const user = descriptor.user;
|
|
1126
|
+
const userProviders = typeof user === "object" && user !== null && !Array.isArray(user) ? user.providers : void 0;
|
|
1127
|
+
const providers = typeof userProviders === "object" && userProviders !== null && !Array.isArray(userProviders) ? { ...userProviders } : {};
|
|
1128
|
+
let filled = 0;
|
|
1129
|
+
const nextProviders = { ...providers };
|
|
1130
|
+
for (const [route, profile] of Object.entries(providers)) {
|
|
1131
|
+
if (typeof profile !== "object" || profile === null || Array.isArray(profile)) continue;
|
|
1132
|
+
const models = profile.models;
|
|
1133
|
+
if (!Array.isArray(models)) continue;
|
|
1134
|
+
let changed = false;
|
|
1135
|
+
const nextModels = models.map((model) => {
|
|
1136
|
+
if (typeof model !== "object" || model === null) return model;
|
|
1137
|
+
const entry = model;
|
|
1138
|
+
if ("reasoningEfforts" in entry) return model;
|
|
1139
|
+
changed = true;
|
|
1140
|
+
filled += 1;
|
|
1141
|
+
return { ...entry, reasoningEfforts: { ...DEFAULT_REASONING_EFFORTS } };
|
|
1142
|
+
});
|
|
1143
|
+
if (!changed) continue;
|
|
1144
|
+
nextProviders[route] = { ...profile, models: nextModels };
|
|
1145
|
+
}
|
|
1146
|
+
if (filled === 0) return { filled: 0 };
|
|
1147
|
+
const ops = [{ op: "set", path: ["providers"], value: nextProviders }];
|
|
1148
|
+
await ctx.settings.mutate(LLM_PI_AI_NS, ops, descriptor.revision);
|
|
1149
|
+
return { filled };
|
|
1150
|
+
}
|
|
1151
|
+
var hostPatch5 = {
|
|
1152
|
+
id: MODEL_REASONING_EFFORTS_PATCH_ID,
|
|
1153
|
+
routes: ({ ctx }) => ({
|
|
1154
|
+
read: () => read(ctx),
|
|
1155
|
+
save: (payload) => save(ctx, payload),
|
|
1156
|
+
fillDefaults: () => fillDefaults(ctx)
|
|
1157
|
+
})
|
|
1158
|
+
};
|
|
1159
|
+
|
|
1160
|
+
// src/patches/conversation-quick-navigation/shared.ts
|
|
1161
|
+
var CONVERSATION_QUICK_NAVIGATION_PATCH_ID = "conversation-quick-navigation";
|
|
1162
|
+
|
|
1163
|
+
// src/patches/conversation-quick-navigation/host/index.ts
|
|
1164
|
+
var hostPatch6 = {
|
|
1165
|
+
id: CONVERSATION_QUICK_NAVIGATION_PATCH_ID,
|
|
1166
|
+
routes: () => ({
|
|
1167
|
+
status: () => ({ available: true })
|
|
1168
|
+
})
|
|
1169
|
+
};
|
|
1170
|
+
|
|
758
1171
|
// src/generated/host-registry.ts
|
|
759
|
-
var HOST_PATCHES = [hostPatch, hostPatch2, hostPatch3];
|
|
1172
|
+
var HOST_PATCHES = [hostPatch, hostPatch2, hostPatch3, hostPatch4, hostPatch5, hostPatch6];
|
|
760
1173
|
|
|
761
1174
|
// src/generated/patch-catalog.ts
|
|
762
1175
|
var DEFAULT_PATCH_SETTINGS = {
|
|
763
1176
|
"message-edit": true,
|
|
764
1177
|
"message-delete": true,
|
|
765
|
-
"session-delete": true
|
|
1178
|
+
"session-delete": true,
|
|
1179
|
+
"conversation-markdown-export": true,
|
|
1180
|
+
"model-reasoning-efforts": true,
|
|
1181
|
+
"conversation-quick-navigation": true
|
|
766
1182
|
};
|
|
767
1183
|
|
|
768
1184
|
// src/kernel/host/activation.ts
|
|
@@ -772,6 +1188,7 @@ var HostPatchActivation = class {
|
|
|
772
1188
|
this.ctx = ctx;
|
|
773
1189
|
this.byId = new Map(patches.map((patch) => [patch.id, patch]));
|
|
774
1190
|
}
|
|
1191
|
+
ctx;
|
|
775
1192
|
byId;
|
|
776
1193
|
disposers = /* @__PURE__ */ new Map();
|
|
777
1194
|
settings = { ...DEFAULT_PATCH_SETTINGS };
|
|
@@ -798,18 +1215,21 @@ var HostPatchActivation = class {
|
|
|
798
1215
|
};
|
|
799
1216
|
|
|
800
1217
|
// src/kernel/host/settings.ts
|
|
801
|
-
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
1218
|
+
import { installSettingsSection, settingsNamespace as settingsNamespace2 } from "@deepseek-ai/dsh-settings";
|
|
802
1219
|
|
|
803
1220
|
// src/generated/host-settings-schema.ts
|
|
804
1221
|
import z2 from "@deepseek-ai/schemastery";
|
|
805
1222
|
var PatchSettingsSchema = z2.object({
|
|
806
1223
|
"message-edit": z2.boolean().default(true),
|
|
807
1224
|
"message-delete": z2.boolean().default(true),
|
|
808
|
-
"session-delete": z2.boolean().default(true)
|
|
1225
|
+
"session-delete": z2.boolean().default(true),
|
|
1226
|
+
"conversation-markdown-export": z2.boolean().default(true),
|
|
1227
|
+
"model-reasoning-efforts": z2.boolean().default(true),
|
|
1228
|
+
"conversation-quick-navigation": z2.boolean().default(true)
|
|
809
1229
|
});
|
|
810
1230
|
|
|
811
1231
|
// src/kernel/host/settings.ts
|
|
812
|
-
var PATCH_SETTINGS_NAMESPACE2 =
|
|
1232
|
+
var PATCH_SETTINGS_NAMESPACE2 = settingsNamespace2(PATCH_SETTINGS_NAMESPACE);
|
|
813
1233
|
function installPatchSettings(ctx, activation) {
|
|
814
1234
|
let source = () => DEFAULT_PATCH_SETTINGS;
|
|
815
1235
|
activation.apply(source());
|
|
@@ -878,6 +1298,7 @@ function collectRoutes(patches, runtime) {
|
|
|
878
1298
|
function registerPatchApi(ctx, patches, confirmationSecret, isEnabled) {
|
|
879
1299
|
const host = ctx;
|
|
880
1300
|
const routes = collectRoutes(patches, { ctx, confirmationSecret });
|
|
1301
|
+
const logger = ctx.logger(PLUGIN_NAME);
|
|
881
1302
|
ctx.effect(() => ctx.webServer.register({
|
|
882
1303
|
kind: "prefix",
|
|
883
1304
|
path: PLUGIN_API_PREFIX,
|
|
@@ -903,8 +1324,13 @@ function registerPatchApi(ctx, patches, confirmationSecret, isEnabled) {
|
|
|
903
1324
|
return;
|
|
904
1325
|
}
|
|
905
1326
|
try {
|
|
1327
|
+
requireJsonContentType(req);
|
|
906
1328
|
writeOk(res, await registered.handler(await readJson(req)));
|
|
907
1329
|
} catch (error) {
|
|
1330
|
+
if (!(error instanceof DshMoreError) || error.status >= 500) {
|
|
1331
|
+
const detail = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
1332
|
+
logger.error("patch API %s failed: %s", route, detail);
|
|
1333
|
+
}
|
|
908
1334
|
writeError(res, error);
|
|
909
1335
|
}
|
|
910
1336
|
}
|
|
@@ -3,8 +3,14 @@ export declare const PatchSettingsSchema: z<Schemastery.ObjectS<{
|
|
|
3
3
|
"message-edit": z<boolean, boolean>;
|
|
4
4
|
"message-delete": z<boolean, boolean>;
|
|
5
5
|
"session-delete": z<boolean, boolean>;
|
|
6
|
+
"conversation-markdown-export": z<boolean, boolean>;
|
|
7
|
+
"model-reasoning-efforts": z<boolean, boolean>;
|
|
8
|
+
"conversation-quick-navigation": z<boolean, boolean>;
|
|
6
9
|
}>, Schemastery.ObjectT<{
|
|
7
10
|
"message-edit": z<boolean, boolean>;
|
|
8
11
|
"message-delete": z<boolean, boolean>;
|
|
9
12
|
"session-delete": z<boolean, boolean>;
|
|
13
|
+
"conversation-markdown-export": z<boolean, boolean>;
|
|
14
|
+
"model-reasoning-efforts": z<boolean, boolean>;
|
|
15
|
+
"conversation-quick-navigation": z<boolean, boolean>;
|
|
10
16
|
}>>;
|
|
@@ -19,12 +19,36 @@ export declare const PATCH_CATALOG: readonly [{
|
|
|
19
19
|
readonly defaultEnabled: true;
|
|
20
20
|
readonly order: 300;
|
|
21
21
|
readonly clientKind: "standalone";
|
|
22
|
+
}, {
|
|
23
|
+
readonly id: "conversation-markdown-export";
|
|
24
|
+
readonly name: "导出完整聊天记录";
|
|
25
|
+
readonly description: "将当前会话的完整聊天记录下载为 Markdown 文件";
|
|
26
|
+
readonly defaultEnabled: true;
|
|
27
|
+
readonly order: 400;
|
|
28
|
+
readonly clientKind: "standalone";
|
|
29
|
+
}, {
|
|
30
|
+
readonly id: "model-reasoning-efforts";
|
|
31
|
+
readonly name: "模型思考强度";
|
|
32
|
+
readonly description: "在模型配置界面为每个模型配置支持的思考强度并写入 settings.yaml 的 reasoningEfforts";
|
|
33
|
+
readonly defaultEnabled: true;
|
|
34
|
+
readonly order: 500;
|
|
35
|
+
readonly clientKind: "standalone";
|
|
36
|
+
}, {
|
|
37
|
+
readonly id: "conversation-quick-navigation";
|
|
38
|
+
readonly name: "对话快速导航";
|
|
39
|
+
readonly description: "自动加载完整记录,并用常驻目录快速定位当前会话中的每一轮提问";
|
|
40
|
+
readonly defaultEnabled: true;
|
|
41
|
+
readonly order: 600;
|
|
42
|
+
readonly clientKind: "standalone";
|
|
22
43
|
}];
|
|
23
44
|
export type PatchId = typeof PATCH_CATALOG[number]['id'];
|
|
24
45
|
export interface PatchSettings {
|
|
25
46
|
"message-edit": boolean;
|
|
26
47
|
"message-delete": boolean;
|
|
27
48
|
"session-delete": boolean;
|
|
49
|
+
"conversation-markdown-export": boolean;
|
|
50
|
+
"model-reasoning-efforts": boolean;
|
|
51
|
+
"conversation-quick-navigation": boolean;
|
|
28
52
|
}
|
|
29
53
|
export declare const DEFAULT_PATCH_SETTINGS: PatchSettings;
|
|
30
54
|
export declare function decodePatchSettings(value: unknown): PatchSettings | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function markdownFilename(title: string, sessionId: string): string;
|