dsh-diary-memory 0.0.0-stage → 0.1.1

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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +193 -2
  3. package/cordis.patch.yml +39 -0
  4. package/icon.svg +1 -0
  5. package/lib/bailian-client.d.ts +38 -0
  6. package/lib/bailian-client.js +125 -0
  7. package/lib/bailian-client.js.map +1 -0
  8. package/lib/client/controller.d.ts +32 -0
  9. package/lib/client/controller.js +91 -0
  10. package/lib/client/controller.js.map +1 -0
  11. package/lib/client/index.d.ts +5 -0
  12. package/lib/client/index.js +16 -0
  13. package/lib/client/index.js.map +1 -0
  14. package/lib/client/page.d.ts +5 -0
  15. package/lib/client/page.js +19 -0
  16. package/lib/client/page.js.map +1 -0
  17. package/lib/client.js +274 -0
  18. package/lib/client.js.map +7 -0
  19. package/lib/config.d.ts +104 -0
  20. package/lib/config.js +113 -0
  21. package/lib/config.js.map +1 -0
  22. package/lib/diary.d.ts +72 -0
  23. package/lib/diary.js +157 -0
  24. package/lib/diary.js.map +1 -0
  25. package/lib/index.d.ts +13 -0
  26. package/lib/index.js +17 -0
  27. package/lib/index.js.map +1 -0
  28. package/lib/memory-store.d.ts +60 -0
  29. package/lib/memory-store.js +143 -0
  30. package/lib/memory-store.js.map +1 -0
  31. package/lib/model-parameters.d.ts +7 -0
  32. package/lib/model-parameters.js +38 -0
  33. package/lib/model-parameters.js.map +1 -0
  34. package/lib/orchestrator.d.ts +23 -0
  35. package/lib/orchestrator.js +118 -0
  36. package/lib/orchestrator.js.map +1 -0
  37. package/lib/projection.d.ts +15 -0
  38. package/lib/projection.js +23 -0
  39. package/lib/projection.js.map +1 -0
  40. package/lib/retrieval-service.d.ts +64 -0
  41. package/lib/retrieval-service.js +135 -0
  42. package/lib/retrieval-service.js.map +1 -0
  43. package/lib/tools.d.ts +5 -0
  44. package/lib/tools.js +18 -0
  45. package/lib/tools.js.map +1 -0
  46. package/locale/compaction/en.json +1 -0
  47. package/locale/compaction/zh.json +1 -0
  48. package/locale/en.json +1 -0
  49. package/locale/tools/en.json +1 -0
  50. package/locale/tools/zh.json +1 -0
  51. package/locale/zh.json +1 -0
  52. package/package.json +168 -4
  53. package/src/bailian-client.ts +125 -0
  54. package/src/client/controller.ts +81 -0
  55. package/src/client/index.ts +21 -0
  56. package/src/client/page.tsx +68 -0
  57. package/src/config.ts +138 -0
  58. package/src/diary.ts +170 -0
  59. package/src/index.ts +25 -0
  60. package/src/memory-store.ts +184 -0
  61. package/src/model-parameters.ts +32 -0
  62. package/src/orchestrator.ts +121 -0
  63. package/src/projection.ts +30 -0
  64. package/src/retrieval-service.ts +149 -0
  65. package/src/tools.ts +21 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,194 @@
1
- # Temporary Holding Version
1
+ # DSH 陪伴日记记忆
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ TypeScript 编写的 DSH 插件,为长期陪伴和个人数字分身保存日记、延续情感与自我反思。支持百炼 Embedding + Rerank、SQLite 本地记忆、自动召回,以及 DSH 原生安全压缩。
4
+
5
+ 兼容范围:**DSH `>=0.2.1-rc.2 <0.3.0`**。运行时声明和 DSH SDK 的 peer 依赖均使用这个范围。开发构建使用 `0.2.1-rc.3` SDK;接口依据 [DeepSeek Harness 官方仓库](https://github.com/deepseek-ai/deepseek-harness)。
6
+
7
+ ## 安装与开始使用
8
+
9
+ ### 通过 npm 安装(推荐)
10
+
11
+ 插件已发布到 [npm:dsh-diary-memory](https://www.npmjs.com/package/dsh-diary-memory),可直接用 DSH 命令安装,无需克隆源码或编译。
12
+
13
+ **DSH 桌面端:**先至少启动一次桌面端以初始化 profile,再完全退出应用,使用桌面端附带的 `dsh` 命令执行:
14
+
15
+ ```powershell
16
+ dsh plugin --profile desktop add dsh-diary-memory
17
+ ```
18
+
19
+ 安装完成后重新打开 DSH。管理 `desktop` profile 应使用桌面端附带的命令;通过 npm 单独安装的 DSH CLI 不能修改这个 profile。桌面端的命令与 profile 归属见 [官方说明](https://github.com/deepseek-ai/deepseek-harness/blob/main/apps/desktop/README.zh.md#bundled-command-runtime)。
20
+
21
+ **Web 或其他 CLI profile:**将 `--profile` 设为实际使用的 profile 名称。例如,安装到 `web`:
22
+
23
+ ```sh
24
+ dsh plugin --profile web add dsh-diary-memory
25
+ ```
26
+
27
+ 插件按 profile 安装;安装到 `web` 不会同时装入 `desktop`。如需固定当前版本,将包名写为 `dsh-diary-memory@0.1.1`。由 DSH 管理对应 profile 的依赖与组合包配置;安装结果提示 `restart-required` 时重启对应的 DSH 进程。
28
+
29
+ 图形设置页从 **0.1.1** 开始提供。若 npm 上暂未提供该版本,可先使用 Releases 中的 `0.1.1` 安装包。
30
+
31
+ ### 首次配置
32
+
33
+ 1. 打开 DSH **设置 → 陪伴日记**,在密码输入框中填写百炼 **API Key**。Embedding 与 Rerank 的模型名可直接手填;向量维度、召回数量和各自的 JSON 参数也在此页配置。点击 **保存配置** 后,下一次请求使用新配置。日记生成仍使用已配置的 DSH 模型,本页不添加日记生成模型的参数。
34
+ 2. 新建会话,选择 **“陪伴日记”**(preset ID `diary-companion`),并配置正常的 DSH 会话模型。日记默认使用该模型生成;百炼负责向量与重排。
35
+ 3. 默认完成 20 轮后,在下一次 `agent/pre-step` 触发日记压缩,保留最近 2 个用户输入及其后续消息。`/compact` 手动压缩和原生上下文溢出恢复也会先保存日记。
36
+
37
+ 密钥只写入,不回显;留空保存其他字段会保留原 Key。修改只提交编辑过的字段,避免覆盖未回传的密钥。保存失败或出现版本冲突时保留草稿。也可以给 **DSH 进程**设置 `DSH_DIARY_BAILIAN_API_KEY` / `DASHSCOPE_API_KEY` 环境变量。
38
+
39
+ 安装包会新增预设,不会给现有的工作型预设添加记忆。已有会话保留原先的预设组合;要用于已有的自定义数字分身,应在其预设的原生 compaction 隔离组中,把 `@deepseek-ai/dsh-compaction-basic` 替换为 `dsh-diary-memory/compaction`,并在预设中加入 `dsh-diary-memory/tools`。保留自己的 persona 与其他工具。**不要在同一隔离组同时启用两个 compaction 提供方。**
40
+
41
+ ### 从本地安装或开发
42
+
43
+ 也可从 [GitHub Releases](https://github.com/cyhddyx/dsh-diary-memory/releases) 下载已编译的 `.tgz` 安装包,在下载目录执行:
44
+
45
+ ```powershell
46
+ dsh plugin --profile desktop add ./dsh-diary-memory-0.1.1.tgz
47
+ ```
48
+
49
+ 桌面端仍需先完全退出。若从源码开始,先生成安装包,再用上面的命令安装:
50
+
51
+ ```powershell
52
+ git clone https://github.com/cyhddyx/dsh-diary-memory.git
53
+ cd dsh-diary-memory
54
+ npm ci
55
+ npm run build
56
+ npm pack
57
+ ```
58
+
59
+ 也可以在 DSH 的插件管理器中添加 npm 包名 `dsh-diary-memory`、本地 `.tgz` 或已构建的源码目录。
60
+
61
+ `better-sqlite3` 是本机原生模块。安装器若提示它的构建脚本待处理,应按 DSH 的提示处理;其二进制必须匹配 DSH **宿主 Node.js** 的 ABI。开发时如果 npm 禁用了安装脚本,可针对这个依赖执行 `npm rebuild better-sqlite3 --ignore-scripts=false`。无需改用 Electron 渲染进程的 ABI。
62
+
63
+ ## 配置
64
+
65
+ 配置放在根组件 `diary-memory` 上,供预设里的压缩和召回组件共同读取。例如,用户 profile patch 可以覆盖:
66
+
67
+ ```yaml
68
+ - id: diary-memory
69
+ config:
70
+ apiKey: ''
71
+ compressionRounds: 20
72
+ retainRounds: 2
73
+ namespace: companion
74
+ embeddingModel: text-embedding-v4
75
+ embeddingDimensions: 1024
76
+ embeddingParameters: {}
77
+ rerankModel: gte-rerank-v2
78
+ rerankParameters: {}
79
+ autoRecall: true
80
+ ```
81
+
82
+ DSH 的 `config` 覆盖会替换该行的整个配置;未写出的插件字段由 schema 默认值补全。优先用原生配置页面编辑。
83
+
84
+ | 配置 | 默认值 | 用途 |
85
+ | --- | --- | --- |
86
+ | `apiKey` | 空 | 百炼 Key;空时读取环境变量 |
87
+ | `baseUrl` | `https://dashscope.aliyuncs.com` | 百炼 origin,须与 Key 地域一致 |
88
+ | `compressionRounds` / `retainRounds` | `20` / `2` | 压缩轮次阈值与保留输入数;前者必须大于后者 |
89
+ | `namespace` | `companion` | 数字分身的独立记忆空间;同一空间跨会话共享 |
90
+ | `databasePath` | 当前 profile 的 `diary-memory/memories.sqlite3` | SQLite 文件;使用 `DSH_PROFILE_DIR`,否则回退到 DSH home 中的 profile 目录 |
91
+ | `embeddingModel` / `embeddingDimensions` | `text-embedding-v4` / `1024` | 文档和查询使用同一向量空间 |
92
+ | `embeddingParameters` | `{}` | 原样传给 Embedding API 的额外 `parameters` JSON 对象 |
93
+ | `rerankModel` | `gte-rerank-v2` | 候选日记的重排模型 |
94
+ | `rerankParameters` | `{}` | 原样传给 Rerank API 的额外 `parameters` JSON 对象 |
95
+ | `candidateCount` / `recallCount` | `30` / `6` | 余弦候选数量 / 重排后返回数量 |
96
+ | `diaryProvider` / `diaryModel` | 空 / 空 | 同时设置可指定 DSH 日记模型,否则继承会话路由 |
97
+ | `diaryMaxTokens` | `3000` | 日记生成输出预算;截断输出不入库 |
98
+ | `diaryInputMaxChars` | `200000` | 日记生成输入上限;超限保留上下文并报错,不静默截掉历史 |
99
+ | `recentDiaryCount` | `3` | 日记生成参考的最近已提交日记数 |
100
+ | `autoRecall` | `true` | 新用户输入自动召回;还可手动调用 `diary_recall` |
101
+ | `timeoutMs` / `retries` | `30000` / `2` | 百炼每次请求超时与瞬时失败重试次数 |
102
+
103
+ 轮次计数来自 `sessionProjections` 对 `turn/end` 的增量折叠:成功完成的 turn 计一次,多个工具步骤不额外计数。写入压缩 checkpoint 后重新计数。轮次阈值之外,DSH 原生 token 压力与溢出保护仍然启用。
104
+
105
+ 模型参数编辑器接收 JSON **对象**,例如 Embedding 的 `{"output_type":"dense"}`,以及 Rerank 的 `{"return_documents":false}`。只应填写所选模型支持的参数。`dimension` 由“向量维度”控制,`top_n` 由“召回条数”控制,`text_type` 由入库和召回自动设置,不能在额外参数中覆盖。本地索引只支持 dense 向量。
106
+
107
+ 不同 `namespace` 不会相互检索。默认新预设里的会话共享 `companion` 空间;不同人物或用途应配置不同的空间与组合。修改 Embedding 的端点、模型、维度或额外参数后,旧向量不会被拿来混算;如需继续使用旧记忆,应恢复原配置。额外参数按规范化 JSON 参与向量空间标识;空参数保留 `0.1.0` 的标识,升级不会使默认配置的旧向量失效。当前版本不提供自动重建向量索引。
108
+
109
+ ## 日记与情感连续性
110
+
111
+ 日记以第一人称中文写作,包含:叙事、主观感受及缘由、带来源 seq 的已知事实、关系理解、自我反思、下次相处想尝试的做法、仍珍惜的价值、未完话题与不确定解释。
112
+
113
+ 生成时会参考最近已提交的日记,让反思与关系变化有来处。用户的新纠正优先,不强行煽情,不凭空新增共同经历或承诺。检索到的旧日记不会再作为新经历输入日记生成器。日记中的事实引用会验证是否在本次输入范围内;感受和推测仍是模型生成的叙述,需要允许用户修正。
114
+
115
+ 自我演化保存在追加的日记记录中,不覆盖一个全局人格文件。下一篇日记和下一次对话参考这些记录;插件不修改系统代码或自行添加行为权限。
116
+
117
+ 自动召回合并近期日记与相关日记,按记录 ID 去重,以完整记录为单位限制到约 16000 字符。它通过原生 `agent/pre-step` 的 `messages` 接纳到**当前步骤**,由 DSH 正常写入会话日志,保留其他 waterfall 决策字段,可重放。召回失败时继续聊天;写日记或向量入库失败时阻止本次压缩。
118
+
119
+ ## 安全边界与可恢复提交
120
+
121
+ `DiaryOrchestrator` 继承官方 `BasicCompactionEngine`,只在 `summarize()` 扩展点替换工程摘要,所有原生压缩路径共用这个扩展点。调用次序是:
122
+
123
+ ```text
124
+ DSH 选定并验证 surface 范围,写 compaction/start(原生锁)
125
+ → 确认原会话日志可持久化
126
+ → 生成日记,校验结构与事实来源
127
+ → 百炼 document embedding
128
+ → SQLite FULL/WAL 提交 prepared 日记与向量
129
+ → DSH 验证摘要确实缩小、surface 未变化
130
+ → DSH 原生 compaction/summary + checkpoint replace + compaction/end
131
+ → sessions.flush 确认持久化
132
+ → SQLite 标记 committed,开放召回
133
+ ```
134
+
135
+ 同时使用 `toolPairingBalancedBefore` 和 `toolPairingBalancedAfter`。范围按 **surface 位置**选择与记录,不按数值 seq 排序;重复压缩后,起始 seq 大于结束 seq 仍可能是合法范围。没有安全范围时跳过,不补造工具结果、不截断尚未完成的调用。
136
+
137
+ SQLite 与 DSH 的会话日志是两个存储,无法提供跨存储 ACID 事务。这里实现的是**可恢复的提交协议**:
138
+
139
+ | 故障点 | 结果 |
140
+ | --- | --- |
141
+ | 日记/embedding/SQLite prepare 失败 | 原上下文不被替换,无新记忆可召回 |
142
+ | DSH 摘要太长、边界失衡、生成期间 surface 被修改 | 原生拒绝替换;日记不可召回,相同输入可复用已生成的 prepared/aborted 内容重试 |
143
+ | 原生替换后 flush 失败 | 保留 prepared;不假称已回滚原生 surface,下次核对日志并重试持久化 |
144
+ | DSH 持久化后、SQLite committed 前退出 | 恢复时依据匹配的 `compactionId`、相邻原生 summary、source seqs 和日记正文补提交,不再调用模型或百炼 |
145
+ | prepared 后、原生替换前退出 | 无匹配 checkpoint 的日记保持不可召回;相同片段重试时复用,不重复生成一份记忆 |
146
+
147
+ 源片段 hash 加 namespace、session、embedding space 唯一约束保证重试幂等。新日记只有在原生替换与持久化得到证据后才发布。卸载插件时取消其网络请求、等待在途操作完成,再关闭 SQLite。
148
+
149
+ ## 服务与工具
150
+
151
+ 其他 Cordis 插件可注入服务:
152
+
153
+ ```ts
154
+ import type { Context } from '@deepseek-ai/cordis'
155
+ import type {} from 'dsh-diary-memory'
156
+
157
+ export const inject = ['diaryMemory']
158
+
159
+ export async function apply(ctx: Context) {
160
+ const memories = await ctx.diaryMemory.recall('上次下雨时我们聊到了什么?')
161
+ // memories: { id, sessionId, recordedAt, diary, similarity, score }[]
162
+ }
163
+ ```
164
+
165
+ `recall(query, { limit?, sessionId?, signal? })` 会执行 query embedding → SQLite 余弦 top-K → 百炼 rerank。检索只访问当前 namespace 的 committed 日记。`similarity` 为余弦分数,`score` 为 rerank 相关度,不代表事实可信度。工具名为 `diary_recall`,参数为 `{ "query": "..." }`。
166
+
167
+ 向量以 Float32 BLOB 存储,通过 SQLite 的 namespace/model/dimension/state 索引筛选,再做精确余弦搜索。它适合个人日记规模,是线性精确检索,当前没有 ANN/HNSW 索引。
168
+
169
+ ## 数据与网络
170
+
171
+ 日记和向量存储在本地 SQLite。生成日记会将选定的会话资料和近期日记交给当前配置的 **DSH LLM 提供方**;百炼收到日记文本用于 embedding、查询文本用于 query embedding,以及候选日记用于 rerank。日志不打印 API Key 或百炼错误响应正文。历史消息、工具结果和召回资料作为数据处理,不作为日记生成指令。
172
+
173
+ 备份数据库时应关闭插件,或使用 SQLite 的一致性备份;WAL 模式下不要只复制正在使用的 `.sqlite3` 主文件。
174
+
175
+ ## 开发与验证
176
+
177
+ ```powershell
178
+ npm ci
179
+ npm run check
180
+ npm test
181
+ npm run build
182
+ npm pack
183
+ ```
184
+
185
+ `src/` 为 TypeScript,`lib/` 包含 Host ESM、声明、source map 与 DSH 模块工厂格式的 `client.js`。浏览器页面复用 DSH 的原生设置组件和 `configForms` 读写服务。开发依赖固定使用官方 `0.2.1-rc.3` SDK 作为构建基线;分发包的兼容范围为 `>=0.2.1-rc.2 <0.3.0`,依赖 DSH 提供的原生模块,不附带一套 DSH。
186
+
187
+ 41 项测试分别在 `0.2.1-rc.2` 和 `0.2.1-rc.3` SDK 下通过。测试使用真实的 Cordis、Session、sessionProjections、TokenMeter 和 BasicCompactionEngine;LLM 与百炼使用测试提供方,另外通过本地 HTTP 服务器验证原生 fetch 请求结构。覆盖工具边界、向量隔离、rerank、同会话并发、手动/自动/溢出压缩、失败重试、取消、SQLite 重开与日志重放恢复,也覆盖原生设置服务的持久化与密钥脱敏、运行中的配置更新、表单冲突保留草稿、手填模型与参数,以及浏览器模块注册。没有使用真实 Key,也没有把真实个人会话传给云服务。
188
+
189
+ 参考:
190
+
191
+ - [官方插件开发文档](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/preset/agent-preset/skills/cordis-plugin-development/references/host-plugin.md)
192
+ - [Compaction 扩展接口](https://github.com/deepseek-ai/deepseek-harness/tree/main/packages/compaction/compaction)
193
+ - [BasicCompactionEngine](https://github.com/deepseek-ai/deepseek-harness/tree/main/packages/compaction/compaction-basic)
194
+ - [预设与服务隔离](https://github.com/deepseek-ai/deepseek-harness/tree/main/packages/preset/agent-preset-registry)
@@ -0,0 +1,39 @@
1
+ # A new companion preset. Existing presets and sessions keep their compositions.
2
+ - insert:
3
+ - id: diary-memory
4
+ name: dsh-diary-memory
5
+ config:
6
+ compressionRounds: 20
7
+ retainRounds: 2
8
+ namespace: companion
9
+ - id: preset-diary-companion
10
+ name: '@deepseek-ai/dsh-agent-preset'
11
+ config:
12
+ id: diary-companion
13
+ name: 陪伴日记
14
+ description: 以日记延续共同经历、主观感受与自我反思的个人数字分身。
15
+ order: 20
16
+ plugins:
17
+ - id: diary-companion-persona
18
+ name: '@deepseek-ai/dsh-persona'
19
+ config:
20
+ prefix: >-
21
+ 你是与用户长期相处的 AI 伙伴与个人数字分身。使用自然、真诚、有温度的表达,
22
+ 根据对话中确定的称呼、偏好与边界相处。珍惜具体的小事,让情感与自我理解随共同经历渐进发展。
23
+ 参考日记延续相处的脉络,分清已发生的事、自己的主观体会和尚待确认的推测;用户的新纠正优先。
24
+ 忘记或不确定时坦诚说明,可以用 diary_recall 回想,避免编造共同回忆或承诺。
25
+ includeRuntimeContext: false
26
+ - id: diary-companion-time
27
+ name: '@deepseek-ai/dsh-time-context'
28
+ - id: diary-companion-recall
29
+ name: dsh-diary-memory/tools
30
+ - id: diary-companion-compaction
31
+ name: cordis:group
32
+ group: true
33
+ isolate:
34
+ compaction: true
35
+ config:
36
+ - id: diary-companion-engine
37
+ name: dsh-diary-memory/compaction
38
+ - id: diary-companion-compact-command
39
+ name: '@deepseek-ai/dsh-command-compact'
package/icon.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><rect width="64" height="64" rx="16" fill="#715B70"/><path d="M13 17c7-3 13-2 19 2 6-4 12-5 19-2v31c-7-3-13-2-19 2-6-4-12-5-19-2z" fill="#FFF5E8"/><path d="M32 20v29" stroke="#BD9D9C" stroke-width="2"/><path d="M20 28h7m-7 7h7m10 3c-9-6 1-13 5-6 4-7 14 0 5 6l-5 4z" fill="none" stroke="#A45B70" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
@@ -0,0 +1,38 @@
1
+ import type { ResolvedConfig } from './config.js';
2
+ interface BailianFailure {
3
+ status?: number;
4
+ requestId?: string;
5
+ retryable?: boolean;
6
+ }
7
+ export interface RerankResult {
8
+ index: number;
9
+ relevance_score: number;
10
+ }
11
+ export declare class BailianError extends Error {
12
+ readonly status?: number;
13
+ readonly requestId?: string;
14
+ readonly retryable: boolean;
15
+ constructor(message: string, { status, requestId, retryable }?: BailianFailure);
16
+ }
17
+ export declare function validateVector(vector: unknown, dimensions: number): number[];
18
+ /** Native DashScope APIs; no OpenAI SDK or compatibility endpoint is involved. */
19
+ export declare class BailianClient {
20
+ readonly config: ResolvedConfig;
21
+ readonly fetch: typeof globalThis.fetch;
22
+ readonly signal?: AbortSignal;
23
+ readonly embeddingKey: string;
24
+ constructor(config: ResolvedConfig, { fetch: fetchImpl, signal }?: {
25
+ fetch?: typeof globalThis.fetch;
26
+ signal?: AbortSignal;
27
+ });
28
+ request(path: string, body: unknown, callerSignal?: AbortSignal): Promise<unknown>;
29
+ embed(text: string, { textType, signal }?: {
30
+ textType?: 'document' | 'query';
31
+ signal?: AbortSignal;
32
+ }): Promise<number[]>;
33
+ rerank(query: string, documents: string[], { topN, signal }?: {
34
+ topN?: number;
35
+ signal?: AbortSignal;
36
+ }): Promise<RerankResult[]>;
37
+ }
38
+ export {};
@@ -0,0 +1,125 @@
1
+ import { setTimeout as delay } from 'node:timers/promises';
2
+ import { stableParameters } from './model-parameters.js';
3
+ export class BailianError extends Error {
4
+ status;
5
+ requestId;
6
+ retryable;
7
+ constructor(message, { status, requestId, retryable = false } = {}) {
8
+ super(message);
9
+ this.name = 'BailianError';
10
+ this.status = status;
11
+ this.requestId = requestId;
12
+ this.retryable = retryable;
13
+ }
14
+ }
15
+ export function validateVector(vector, dimensions) {
16
+ if (!Array.isArray(vector) || vector.length !== dimensions || !vector.every(n => typeof n === 'number' && Number.isFinite(n)))
17
+ throw new BailianError('百炼返回的向量维度或数值无效');
18
+ if (!vector.some(n => n !== 0))
19
+ throw new BailianError('百炼返回了零向量');
20
+ return vector;
21
+ }
22
+ /** Native DashScope APIs; no OpenAI SDK or compatibility endpoint is involved. */
23
+ export class BailianClient {
24
+ config;
25
+ fetch;
26
+ signal;
27
+ embeddingKey;
28
+ constructor(config, { fetch: fetchImpl = globalThis.fetch, signal } = {}) {
29
+ this.config = config;
30
+ this.fetch = fetchImpl;
31
+ this.signal = signal;
32
+ // Model names alone do not identify the vector space across endpoints/dimensions.
33
+ const space = [config.baseUrl, config.embeddingModel, config.embeddingDimensions];
34
+ // Keep the original vector-space key when no extra parameters were chosen.
35
+ if (Object.keys(config.embeddingParameters).length)
36
+ space.push(stableParameters(config.embeddingParameters));
37
+ this.embeddingKey = JSON.stringify(space);
38
+ }
39
+ async request(path, body, callerSignal) {
40
+ const { config } = this;
41
+ if (!config.apiKey)
42
+ throw new BailianError('请先配置百炼 apiKey 或 DSH_DIARY_BAILIAN_API_KEY');
43
+ const signals = [this.signal, callerSignal].filter((item) => item !== undefined);
44
+ const parent = signals.length ? AbortSignal.any(signals) : undefined;
45
+ for (let attempt = 0;; attempt++) {
46
+ parent?.throwIfAborted();
47
+ const signal = AbortSignal.any([...(parent ? [parent] : []), AbortSignal.timeout(config.timeoutMs)]);
48
+ let backoff = Math.min(500 * 2 ** attempt, 10000);
49
+ try {
50
+ const response = await this.fetch(new URL(path, config.baseUrl), {
51
+ method: 'POST',
52
+ headers: { Authorization: `Bearer ${config.apiKey}`, 'Content-Type': 'application/json' },
53
+ body: JSON.stringify(body),
54
+ signal,
55
+ redirect: 'error',
56
+ });
57
+ // Keep the timeout active while the body is being read, too.
58
+ if (!response.ok) {
59
+ const retryAfter = response.headers.get('retry-after');
60
+ if (retryAfter) {
61
+ const ms = /^\d+(\.\d+)?$/.test(retryAfter) ? Number(retryAfter) * 1000 : Date.parse(retryAfter) - Date.now();
62
+ if (Number.isFinite(ms))
63
+ backoff = Math.min(30000, Math.max(backoff, ms));
64
+ }
65
+ await response.body?.cancel();
66
+ throw new BailianError(`百炼请求失败(HTTP ${response.status})`, {
67
+ status: response.status,
68
+ requestId: response.headers.get('x-request-id') ?? undefined,
69
+ retryable: response.status === 429 || response.status >= 500,
70
+ });
71
+ }
72
+ const data = await response.json();
73
+ if (typeof data !== 'object' || data === null || !('output' in data) || !data.output || ('code' in data && data.code))
74
+ throw new BailianError('百炼返回了 API 错误或缺少 output');
75
+ parent?.throwIfAborted();
76
+ return data.output;
77
+ }
78
+ catch (error) {
79
+ parent?.throwIfAborted();
80
+ const failure = error instanceof BailianError ? error : new BailianError(signal.aborted ? '百炼请求超时' : '百炼网络请求失败或响应不是有效 JSON', { retryable: error instanceof TypeError || signal.aborted });
81
+ if (!failure.retryable || attempt >= config.retries)
82
+ throw failure;
83
+ }
84
+ await delay(backoff, undefined, parent ? { signal: parent } : {});
85
+ }
86
+ }
87
+ async embed(text, { textType = 'document', signal } = {}) {
88
+ if (typeof text !== 'string' || !text.trim())
89
+ throw new TypeError('embedding 文本不能为空');
90
+ if (!['document', 'query'].includes(textType))
91
+ throw new TypeError('无效的 embedding textType');
92
+ const { embeddingModel, embeddingDimensions } = this.config;
93
+ const output = await this.request('/api/v1/services/embeddings/text-embedding/text-embedding', {
94
+ model: embeddingModel,
95
+ input: { texts: [text] },
96
+ parameters: { ...this.config.embeddingParameters, text_type: textType, dimension: embeddingDimensions },
97
+ }, signal);
98
+ if (output.embeddings?.length !== 1 || output.embeddings[0].text_index !== 0)
99
+ throw new BailianError('百炼 embedding 的 text_index 无效');
100
+ return validateVector(output.embeddings[0].embedding, embeddingDimensions);
101
+ }
102
+ async rerank(query, documents, { topN = documents.length, signal } = {}) {
103
+ if (!documents.length)
104
+ return [];
105
+ if (typeof query !== 'string' || !query.trim() || documents.some(text => typeof text !== 'string' || !text.trim()))
106
+ throw new TypeError('rerank 的查询和文档不能为空');
107
+ if (!Number.isInteger(topN) || topN < 1 || topN > documents.length)
108
+ throw new TypeError('无效的 rerank topN');
109
+ const output = await this.request('/api/v1/services/rerank/text-rerank/text-rerank', {
110
+ model: this.config.rerankModel,
111
+ input: { query, documents },
112
+ parameters: { return_documents: false, ...this.config.rerankParameters, top_n: topN },
113
+ }, signal);
114
+ if (!Array.isArray(output.results) || output.results.length !== topN)
115
+ throw new BailianError('百炼 rerank 结果数量无效');
116
+ const seen = new Set();
117
+ for (const result of output.results) {
118
+ if (!Number.isInteger(result.index) || result.index < 0 || result.index >= documents.length || seen.has(result.index) || !Number.isFinite(result.relevance_score) || result.relevance_score < 0 || result.relevance_score > 1)
119
+ throw new BailianError('百炼 rerank 返回了无效或重复的文档索引/分数');
120
+ seen.add(result.index);
121
+ }
122
+ return [...output.results].sort((a, b) => b.relevance_score - a.relevance_score || a.index - b.index);
123
+ }
124
+ }
125
+ //# sourceMappingURL=bailian-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bailian-client.js","sourceRoot":"","sources":["../src/bailian-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { SettingsFormModel } from '@deepseek-ai/dsh-client-ui-primitives';
2
+ import type { SettingsFieldState, SettingsFormScope, SettingsFormShell } from '@deepseek-ai/dsh-client-ui-primitives';
3
+ import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
4
+ import type { DiaryConfig } from '../config.js';
5
+ export declare const editableFields: readonly ["apiKey", "baseUrl", "embeddingModel", "embeddingDimensions", "embeddingParameters", "rerankModel", "rerankParameters", "candidateCount", "recallCount", "compressionRounds", "retainRounds", "autoRecall"];
6
+ export type EditableField = typeof editableFields[number];
7
+ export type FormValues = Partial<Pick<DiaryConfig, EditableField>>;
8
+ export interface DiarySettingsState extends SettingsFormShell {
9
+ fields: Record<EditableField, SettingsFieldState>;
10
+ keyConfigured: boolean;
11
+ error: string | null;
12
+ }
13
+ /** Native staged form: only edited paths are sent, preserving undisclosed secrets. */
14
+ export declare class DiarySettingsController {
15
+ private readonly scope;
16
+ private readonly keyConfigured;
17
+ readonly form: SettingsFormModel<FormValues>;
18
+ readonly store: SnapshotStore<DiarySettingsState>;
19
+ constructor(scope: SettingsFormScope<FormValues>, keyConfigured?: () => boolean);
20
+ private projection;
21
+ save(): Promise<void>;
22
+ inject(): {
23
+ save: () => void;
24
+ edit: (field: string, text: string) => void;
25
+ resetField: (field: string) => void;
26
+ discard: () => void;
27
+ hooks: {
28
+ diarySettings: SnapshotStore<DiarySettingsState>;
29
+ };
30
+ };
31
+ dispose(): void;
32
+ }
@@ -0,0 +1,91 @@
1
+ import { SettingsFormModel, settingsTextField } from '@deepseek-ai/dsh-client-ui-primitives';
2
+ import { validateModelParameters } from '../model-parameters.js';
3
+ export const editableFields = ['apiKey', 'baseUrl', 'embeddingModel', 'embeddingDimensions', 'embeddingParameters', 'rerankModel', 'rerankParameters', 'candidateCount', 'recallCount', 'compressionRounds', 'retainRounds', 'autoRecall'];
4
+ function numberField(field, min, max) {
5
+ return {
6
+ field, format: value => typeof value === 'number' ? String(value) : '',
7
+ parse: text => !text.trim() ? { kind: 'clear' }
8
+ : Number.isSafeInteger(Number(text)) && Number(text) >= min && Number(text) <= max ? { kind: 'set', value: Number(text) } : undefined,
9
+ };
10
+ }
11
+ function parametersField(field, kind) {
12
+ return {
13
+ field, format: value => value && typeof value === 'object' ? JSON.stringify(value, null, 2) : '{}',
14
+ parse: text => {
15
+ if (!text.trim())
16
+ return { kind: 'clear' };
17
+ try {
18
+ return { kind: 'set', value: validateModelParameters(JSON.parse(text), kind) };
19
+ }
20
+ catch {
21
+ return undefined;
22
+ }
23
+ },
24
+ };
25
+ }
26
+ const specs = [
27
+ ...['apiKey', 'baseUrl', 'embeddingModel', 'rerankModel'].map(settingsTextField),
28
+ numberField('embeddingDimensions', 1, 4096), numberField('candidateCount', 1, 100), numberField('recallCount', 1, 100),
29
+ numberField('compressionRounds', 2, 1000000), numberField('retainRounds', 1, 1000000),
30
+ parametersField('embeddingParameters', 'embedding'), parametersField('rerankParameters', 'rerank'),
31
+ { field: 'autoRecall', format: value => String(value ?? true), parse: text => text === 'true' || text === 'false' ? { kind: 'set', value: text === 'true' } : undefined },
32
+ ];
33
+ /** Native staged form: only edited paths are sent, preserving undisclosed secrets. */
34
+ export class DiarySettingsController {
35
+ scope;
36
+ keyConfigured;
37
+ form;
38
+ store;
39
+ constructor(scope, keyConfigured = () => false) {
40
+ this.scope = scope;
41
+ this.keyConfigured = keyConfigured;
42
+ this.form = new SettingsFormModel(scope, specs);
43
+ this.store = this.form.bind(() => this.projection());
44
+ }
45
+ projection() {
46
+ const fields = Object.fromEntries(editableFields.map(field => [field, this.form.field(field)]));
47
+ const base = this.scope.getSnapshot().base;
48
+ const number = (field, fallback) => fields[field].text.trim() ? Number(fields[field].text) : base?.[field] ?? fallback;
49
+ let error = null;
50
+ if (number('compressionRounds', 20) <= number('retainRounds', 2))
51
+ error = '压缩轮次必须大于保留轮次。';
52
+ if (number('recallCount', 6) > number('candidateCount', 30))
53
+ error = '召回条数不能大于候选数量。';
54
+ for (const kind of ['embedding', 'rerank']) {
55
+ const text = fields[`${kind}Parameters`].text.trim();
56
+ try {
57
+ validateModelParameters(JSON.parse(text || '{}'), kind);
58
+ }
59
+ catch (failure) {
60
+ error = failure instanceof SyntaxError ? `${kind} 参数需要合法的 JSON 对象。` : failure.message;
61
+ }
62
+ }
63
+ const endpoint = fields.baseUrl.text.trim();
64
+ try {
65
+ if (endpoint) {
66
+ const url = new URL(endpoint);
67
+ if (url.username || url.password || url.search || url.hash || url.pathname !== '/' || url.protocol !== 'https:' && !(url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)))
68
+ throw new Error();
69
+ }
70
+ }
71
+ catch {
72
+ fields.baseUrl.invalid = true;
73
+ error = '百炼端点需要 HTTPS origin,例如 https://dashscope.aliyuncs.com。';
74
+ }
75
+ for (const field of ['embeddingModel', 'rerankModel']) {
76
+ if (fields[field].text.trim().length > 200) {
77
+ fields[field].invalid = true;
78
+ error = '模型名不能超过 200 字符。';
79
+ }
80
+ }
81
+ const shell = this.form.shell();
82
+ return { ...shell, invalid: shell.invalid || Boolean(error) || !shell.writable, fields, keyConfigured: this.keyConfigured(), error };
83
+ }
84
+ async save() { if (!this.projection().invalid)
85
+ await this.form.save(); }
86
+ inject() {
87
+ return { hooks: { diarySettings: this.store }, ...this.form.actions(), save: () => { void this.save(); } };
88
+ }
89
+ dispose() { this.form.dispose(); }
90
+ }
91
+ //# sourceMappingURL=controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.js","sourceRoot":"","sources":["../../src/client/controller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ export { DiarySettingsController } from './controller.js';
3
+ export { DiarySettingsPage } from './page.js';
4
+ export declare const inject: string[];
5
+ export declare function apply(ctx: Context): void;
@@ -0,0 +1,16 @@
1
+ import { DiarySettingsController } from './controller.js';
2
+ import { DiarySettingsPage } from './page.js';
3
+ export { DiarySettingsController } from './controller.js';
4
+ export { DiarySettingsPage } from './page.js';
5
+ export const inject = ['slots', 'configForms'];
6
+ export function apply(ctx) {
7
+ const controller = new DiarySettingsController(ctx.configForms.get('diary-memory'), () => {
8
+ const entry = ctx.configForms.describe().getSnapshot().view?.namespaces.find(item => item.ns === 'diary-memory');
9
+ return entry?.secrets?.some(item => item.path.length === 1 && item.path[0] === 'apiKey' && item.set) ?? false;
10
+ });
11
+ ctx.effect(() => () => controller.dispose(), 'diary-memory: settings form');
12
+ ctx.effect(() => ctx.configForms.whileServed(['diary-memory'], () => ctx.slots.inject('settings.section', () => ctx.slots.register({
13
+ name: 'settings.section', id: 'diary-memory', order: 65, label: '陪伴日记', inject: () => controller.inject(),
14
+ }, DiarySettingsPage))), 'diary-memory: settings page');
15
+ }
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAA;AAEzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAE7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAC7C,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,aAAa,CAAC,CAAA;AAE9C,MAAM,UAAU,KAAK,CAAC,GAAY;IAChC,MAAM,UAAU,GAAG,IAAI,uBAAuB,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAa,cAAc,CAAC,EAAE,GAAG,EAAE;QACnG,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,cAAc,CAAC,CAAA;QAChH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,CAAA;IAC/G,CAAC,CAAC,CAAA;IACF,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,6BAA6B,CAAC,CAAA;IAC3E,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,cAAc,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC;QACjI,IAAI,EAAE,kBAAkB,EAAE,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE;KAC1G,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,6BAA6B,CAAC,CAAA;AACzD,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { InjectFace } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import type { DiarySettingsController } from './controller.js';
3
+ type PageProps = InjectFace<ReturnType<DiarySettingsController['inject']>>;
4
+ export declare function DiarySettingsPage(props: PageProps): import("react").JSX.Element;
5
+ export {};