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.
Files changed (29) hide show
  1. package/CONTRIBUTING.md +6 -6
  2. package/README.md +21 -9
  3. package/README.zh-CN.md +21 -9
  4. package/dist/client.js +1232 -174
  5. package/dist/index.js +451 -25
  6. package/dist/types/generated/host-settings-schema.d.ts +6 -0
  7. package/dist/types/generated/patch-catalog.d.ts +24 -0
  8. package/dist/types/patches/conversation-markdown-export/client/filename.d.ts +1 -0
  9. package/dist/types/patches/conversation-markdown-export/client/index.d.ts +2 -0
  10. package/dist/types/patches/conversation-markdown-export/host/index.d.ts +2 -0
  11. package/dist/types/patches/conversation-markdown-export/host/transcript.d.ts +9 -0
  12. package/dist/types/patches/conversation-markdown-export/shared.d.ts +5 -0
  13. package/dist/types/patches/conversation-quick-navigation/client/index.d.ts +22 -0
  14. package/dist/types/patches/conversation-quick-navigation/client/navigation.d.ts +26 -0
  15. package/dist/types/patches/conversation-quick-navigation/host/index.d.ts +2 -0
  16. package/dist/types/patches/conversation-quick-navigation/shared.d.ts +1 -0
  17. package/dist/types/patches/model-reasoning-efforts/client/index.d.ts +39 -0
  18. package/dist/types/patches/model-reasoning-efforts/host/index.d.ts +2 -0
  19. package/dist/types/patches/model-reasoning-efforts/host/validate.d.ts +13 -0
  20. package/dist/types/patches/model-reasoning-efforts/shared.d.ts +51 -0
  21. package/dist/types/patches/session-delete/host/session-deletion.d.ts +0 -1
  22. package/dist/types/platform/dsh/client/api.d.ts +2 -0
  23. package/dist/types/platform/dsh/client/message-targets.d.ts +13 -1
  24. package/dist/types/platform/dsh/client/styles.d.ts +1 -1
  25. package/dist/types/platform/dsh/host/continuation.d.ts +4 -0
  26. package/dist/types/platform/dsh/host/error.d.ts +1 -1
  27. package/dist/types/platform/dsh/host/wire.d.ts +7 -0
  28. package/dsh.plugin.json +12 -2
  29. 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("bad-request", "\u8BF7\u6C42\u5185\u5BB9\u8FC7\u5927\u3002");
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
- const message = error instanceof Error ? error.message : String(error);
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
- const workspace = ctx.workspaceRegistry.list().find((candidate) => candidate.sessionIds.includes(source.id));
249
- if (workspace !== void 0) await workspace.attachSession(childId);
250
- await ctx.workspaceRegistry.archiveSession(source.id);
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
- await child.dispose().catch(() => void 0);
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
- const workspace = ctx.workspaceRegistry.list().find((candidate) => candidate.sessionIds.includes(source.id));
552
- if (workspace !== void 0) await workspace.attachSession(childId);
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
- await child.dispose().catch(() => void 0);
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, relative } from "node:path";
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 safeSessionDirectory(locationPath) {
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
- const sessionsRoot = dirname(dirname(sessionDir));
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 = safeSessionDirectory(location.path);
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, deletedPath: sessionDir };
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 = settingsNamespace(PATCH_SETTINGS_NAMESPACE);
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;