nolo-cli 0.1.13 → 0.1.14

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 (320) hide show
  1. package/README.md +9 -2
  2. package/agent-runtime/hostAdapter.ts +53 -0
  3. package/agent-runtime/index.ts +28 -0
  4. package/agent-runtime/localLoop.ts +62 -0
  5. package/agent-runtime/runtimeDecision.ts +70 -0
  6. package/agent-runtime/types.ts +87 -0
  7. package/agentRuntimeCommands.ts +139 -22
  8. package/agentRuntimeLocal.ts +7 -0
  9. package/ai/agent/_executeModel.ts +118 -0
  10. package/ai/agent/agentSlice.ts +544 -1
  11. package/ai/agent/appWorkingMemory.ts +126 -0
  12. package/ai/agent/avatarUtils.ts +24 -0
  13. package/ai/agent/buildEditingContext.ts +373 -0
  14. package/ai/agent/buildSystemPrompt.ts +532 -0
  15. package/ai/agent/cleanAgentMessages.ts +140 -0
  16. package/ai/agent/cliChatClient.ts +119 -0
  17. package/ai/agent/contextCompiler.ts +107 -0
  18. package/ai/agent/contextLayerContract.ts +44 -0
  19. package/ai/agent/createAgentSchema.ts +234 -0
  20. package/ai/agent/executeToolCall.ts +58 -0
  21. package/ai/agent/fetchAgentContexts.ts +42 -0
  22. package/ai/agent/generatePrompt.ts +3 -0
  23. package/ai/agent/getFullChatContextKeys.ts +168 -0
  24. package/ai/agent/hooks/fetchPublicAgents.ts +133 -0
  25. package/ai/agent/hooks/useAgentConfig.ts +61 -0
  26. package/ai/agent/hooks/useAgentDialog.ts +35 -0
  27. package/ai/agent/hooks/useAgentFormValidation.ts +202 -0
  28. package/ai/agent/hooks/usePublicAgents.ts +473 -0
  29. package/ai/agent/persistMessageWithFixedId.ts +37 -0
  30. package/ai/agent/planSlice.ts +259 -0
  31. package/ai/agent/referenceUtils.ts +229 -0
  32. package/ai/agent/runAgentBackground.ts +238 -0
  33. package/ai/agent/runAgentClientLoop.ts +138 -0
  34. package/ai/agent/runtimeGuidance.ts +97 -0
  35. package/ai/agent/runtimeServerBase.ts +37 -0
  36. package/ai/agent/server/fetchPublicAgents.ts +128 -0
  37. package/ai/agent/startParallelAgentStreams.ts +424 -0
  38. package/ai/agent/startupProtocol.ts +53 -0
  39. package/ai/agent/streamAgentChatTurn.ts +1299 -0
  40. package/ai/agent/streamAgentChatTurnUtils.ts +738 -0
  41. package/ai/agent/types.ts +71 -0
  42. package/ai/agent/utils/imageOutput.ts +39 -0
  43. package/ai/agent/utils/publicImageAgentMode.ts +26 -0
  44. package/ai/agent/utils/sortUtils.ts +250 -0
  45. package/ai/agent/web/referencePickerUtils.ts +146 -0
  46. package/ai/ai.locale.ts +1083 -0
  47. package/ai/chat/accumulateToolCallChunks.ts +95 -0
  48. package/ai/chat/fetchUtils.native.ts +276 -0
  49. package/ai/chat/fetchUtils.ts +153 -0
  50. package/ai/chat/inlineImageUrlsForCustomProvider.ts +117 -0
  51. package/ai/chat/parseApiError.ts +64 -0
  52. package/ai/chat/parseMultilineSSE.ts +95 -0
  53. package/ai/chat/sendOpenAICompletionsRequest.native.ts +682 -0
  54. package/ai/chat/sendOpenAICompletionsRequest.ts +712 -0
  55. package/ai/chat/sendOpenAIResponseRequest.ts +512 -0
  56. package/ai/chat/shouldUseServerProxy.ts +18 -0
  57. package/ai/chat/sseClient.native.ts +91 -0
  58. package/ai/chat/sseClient.ts +67 -0
  59. package/ai/chat/streamReader.native.ts +31 -0
  60. package/ai/chat/streamReader.ts +62 -0
  61. package/ai/chat/updateTotalUsage.ts +72 -0
  62. package/ai/context/buildReferenceContext.ts +437 -0
  63. package/ai/context/calculateContextUsage.ts +133 -0
  64. package/ai/context/retention.ts +165 -0
  65. package/ai/context/tokenUtils.ts +78 -0
  66. package/ai/index.ts +1 -1
  67. package/ai/llm/agentCapabilities.ts +74 -0
  68. package/ai/llm/calculateGeminiImageTokens.ts +57 -0
  69. package/ai/llm/deepinfra.ts +28 -0
  70. package/ai/llm/fireworks.ts +68 -0
  71. package/ai/llm/generateRequestBody.ts +165 -0
  72. package/ai/llm/getModelContextWindow.ts +84 -0
  73. package/ai/llm/getNoloKey.ts +37 -0
  74. package/ai/llm/getPricing.ts +232 -0
  75. package/ai/llm/hooks/useModelPricing.ts +75 -0
  76. package/ai/llm/imagePricing.ts +66 -0
  77. package/ai/llm/isResponseAPIModel.ts +13 -0
  78. package/ai/llm/kimi.ts +18 -0
  79. package/ai/llm/mimo.ts +71 -0
  80. package/ai/llm/mistral.ts +22 -0
  81. package/ai/llm/modelAvatar.ts +427 -0
  82. package/ai/llm/models.ts +45 -0
  83. package/ai/llm/openrouterModels.ts +141 -0
  84. package/ai/llm/providers.ts +307 -0
  85. package/ai/llm/reasoningModels.ts +28 -0
  86. package/ai/llm/types.ts +59 -0
  87. package/ai/llm/usageRequestOptions.ts +59 -0
  88. package/ai/memory/capture.ts +148 -0
  89. package/ai/memory/consolidate.ts +104 -0
  90. package/ai/memory/delete.ts +147 -0
  91. package/ai/memory/overlay.ts +84 -0
  92. package/ai/memory/query.ts +38 -0
  93. package/ai/memory/queryShared.ts +160 -0
  94. package/ai/memory/rank.ts +105 -0
  95. package/ai/memory/recentRelationshipRecap.ts +247 -0
  96. package/ai/memory/remember.ts +167 -0
  97. package/ai/memory/runtime.ts +76 -0
  98. package/ai/memory/store.ts +20 -0
  99. package/ai/memory/storeShared.ts +76 -0
  100. package/ai/memory/types.ts +46 -0
  101. package/ai/memory/understanding.ts +349 -0
  102. package/ai/memory/understandingGreeting.ts +264 -0
  103. package/ai/messages/type.ts +20 -0
  104. package/ai/policy/personalizationDialog.ts +333 -0
  105. package/ai/policy/runtimePolicy.ts +440 -0
  106. package/ai/policy/selfUpdateFields.ts +48 -0
  107. package/ai/policy/types.ts +64 -0
  108. package/ai/skills/referenceRuntime.ts +274 -0
  109. package/ai/skills/skillDiagnostics.ts +251 -0
  110. package/ai/skills/skillDocBuilder.ts +139 -0
  111. package/ai/skills/skillDocProtocol.ts +434 -0
  112. package/ai/skills/skillReferenceSummary.ts +63 -0
  113. package/ai/skills/skillSummaryMarker.ts +26 -0
  114. package/ai/token/calculatePrice.ts +546 -0
  115. package/ai/token/db.ts +98 -0
  116. package/ai/token/externalToolCost.ts +321 -0
  117. package/ai/token/hooks/useRecords.ts +65 -0
  118. package/ai/token/missingUsageEstimate.ts +42 -0
  119. package/ai/token/modelUsageQuery.ts +252 -0
  120. package/ai/token/normalizeUsage.ts +84 -0
  121. package/ai/token/openaiImageGenerationUsage.ts +56 -0
  122. package/ai/token/prepareTokenUsageData.ts +88 -0
  123. package/ai/token/query.ts +88 -0
  124. package/ai/token/queryUserTokens.ts +59 -0
  125. package/ai/token/resolveBillingTarget.ts +52 -0
  126. package/ai/token/saveTokenRecord.ts +53 -0
  127. package/ai/token/serverDialogProjection.ts +78 -0
  128. package/ai/token/serverTokenWriter.ts +143 -0
  129. package/ai/token/stats.ts +21 -0
  130. package/ai/token/tokenThunks.ts +24 -0
  131. package/ai/token/types.ts +93 -0
  132. package/ai/tools/agent/agentTools.ts +176 -0
  133. package/ai/tools/agent/agentUpdateShared.ts +311 -0
  134. package/ai/tools/agent/callAgentTool.ts +139 -0
  135. package/ai/tools/agent/createAgentTool.ts +512 -0
  136. package/ai/tools/agent/createDialogTool.ts +69 -0
  137. package/ai/tools/agent/createSkillAgentTool.ts +62 -0
  138. package/ai/tools/agent/parallelBudget.ts +221 -0
  139. package/ai/tools/agent/presets/appBuilderPreset.ts +147 -0
  140. package/ai/tools/agent/runLlmTool.ts +96 -0
  141. package/ai/tools/agent/runStreamingAgentTool.ts +73 -0
  142. package/ai/tools/agent/skillAgentArgs.ts +106 -0
  143. package/ai/tools/agent/skillAgentPreset.ts +89 -0
  144. package/ai/tools/agent/streamParallelAgentsTool.ts +122 -0
  145. package/ai/tools/agent/updateAgentTool.ts +96 -0
  146. package/ai/tools/agent/updateSelfTool.ts +113 -0
  147. package/ai/tools/amazonProductScraperTool.ts +86 -0
  148. package/ai/tools/apifyActorClient.ts +45 -0
  149. package/ai/tools/appEditGuard.ts +372 -0
  150. package/ai/tools/appReadSnapshot.ts +153 -0
  151. package/ai/tools/appTools.ts +1549 -0
  152. package/ai/tools/applyEditTool.ts +256 -0
  153. package/ai/tools/applyLineEditsTool.ts +312 -0
  154. package/ai/tools/browserTools/click.ts +33 -0
  155. package/ai/tools/browserTools/closeSession.ts +29 -0
  156. package/ai/tools/browserTools/common.ts +27 -0
  157. package/ai/tools/browserTools/openSession.ts +48 -0
  158. package/ai/tools/browserTools/readContent.ts +38 -0
  159. package/ai/tools/browserTools/selectOption.ts +46 -0
  160. package/ai/tools/browserTools/typeText.ts +42 -0
  161. package/ai/tools/category/createCategoryTool.ts +66 -0
  162. package/ai/tools/category/queryContentsByCategoryTool.ts +69 -0
  163. package/ai/tools/category/updateContentCategoryTool.ts +75 -0
  164. package/ai/tools/cfBrowserTools.ts +319 -0
  165. package/ai/tools/cfSpeechToTextTool.ts +49 -0
  166. package/ai/tools/checkEnvTool.ts +65 -0
  167. package/ai/tools/cloudflareCrawlTool.ts +289 -0
  168. package/ai/tools/codeSearchTool.ts +111 -0
  169. package/ai/tools/codeTools.ts +101 -0
  170. package/ai/tools/createDocTool.ts +132 -0
  171. package/ai/tools/createPlanTool.ts +999 -0
  172. package/ai/tools/createSkillDocTool.ts +155 -0
  173. package/ai/tools/createWorkflowTool.ts +154 -0
  174. package/ai/tools/deepseekOcrTool.ts +34 -0
  175. package/ai/tools/delayTool.ts +31 -0
  176. package/ai/tools/deleteSpacesTool.ts +325 -0
  177. package/ai/tools/deleteSpacesToolModel.ts +159 -0
  178. package/ai/tools/devReloadUtils.ts +29 -0
  179. package/ai/tools/dialogMessageSearch.ts +137 -0
  180. package/ai/tools/doctorSkillTool.ts +72 -0
  181. package/ai/tools/ecommerceScraperTool.ts +86 -0
  182. package/ai/tools/emailTools.ts +549 -0
  183. package/ai/tools/evalSkillTool.ts +92 -0
  184. package/ai/tools/exaSearchTool.ts +64 -0
  185. package/ai/tools/execBashTool.ts +379 -0
  186. package/ai/tools/executeSqlTool.ts +192 -0
  187. package/ai/tools/fetchWebpageSupport.ts +309 -0
  188. package/ai/tools/fetchWebpageTool.ts +84 -0
  189. package/ai/tools/geminiImagePreviewTool.ts +361 -0
  190. package/ai/tools/generateDocxTool.ts +215 -0
  191. package/ai/tools/googleSearchScraperTool.ts +106 -0
  192. package/ai/tools/importDataTool.ts +133 -0
  193. package/ai/tools/importSkillTool.ts +162 -0
  194. package/ai/tools/index.ts +1927 -0
  195. package/ai/tools/listFilesTool.ts +82 -0
  196. package/ai/tools/listUserSpacesTool.ts +113 -0
  197. package/ai/tools/modelUsageTools.ts +199 -0
  198. package/ai/tools/olmOcrTool.ts +34 -0
  199. package/ai/tools/openaiImageTool.ts +267 -0
  200. package/ai/tools/prepareTools.ts +23 -0
  201. package/ai/tools/readDocTool.ts +84 -0
  202. package/ai/tools/readFileTool.ts +211 -0
  203. package/ai/tools/readTool.ts +163 -0
  204. package/ai/tools/readXPostTool.ts +233 -0
  205. package/ai/tools/rememberMemoryTool.ts +84 -0
  206. package/ai/tools/remotionVideoTool.ts +151 -0
  207. package/ai/tools/searchDialogMessagesTool.ts +222 -0
  208. package/ai/tools/searchRepoTool.ts +115 -0
  209. package/ai/tools/searchWorkspaceTool.ts +259 -0
  210. package/ai/tools/skillFollowup.ts +86 -0
  211. package/ai/tools/surfWeatherTool.ts +169 -0
  212. package/ai/tools/table/addTableRowTool.ts +217 -0
  213. package/ai/tools/table/createTableTool.ts +315 -0
  214. package/ai/tools/table/rowTools.ts +366 -0
  215. package/ai/tools/table/schemaTools.ts +244 -0
  216. package/ai/tools/table/shareTableTool.ts +148 -0
  217. package/ai/tools/table/toolShared.ts +129 -0
  218. package/ai/tools/toolApiClient.ts +198 -0
  219. package/ai/tools/toolNameAliases.ts +57 -0
  220. package/ai/tools/toolResultError.ts +42 -0
  221. package/ai/tools/toolRunSlice.ts +303 -0
  222. package/ai/tools/toolSchemaCompatibility.ts +53 -0
  223. package/ai/tools/toolVisibility.ts +4 -0
  224. package/ai/tools/types.ts +20 -0
  225. package/ai/tools/uiAskChoiceTool.ts +104 -0
  226. package/ai/tools/updateContentTitleTool.ts +84 -0
  227. package/ai/tools/updateDocTool.ts +105 -0
  228. package/ai/tools/updateUserPreferenceProfileTool.ts +145 -0
  229. package/ai/tools/whisperTool.ts +77 -0
  230. package/ai/tools/writeFileTool.ts +210 -0
  231. package/ai/tools/youtubeScraperTool.ts +116 -0
  232. package/ai/tools/ziweiChartTool.ts +678 -0
  233. package/ai/types.ts +55 -0
  234. package/ai/workflow/workflowExecutor.ts +323 -0
  235. package/ai/workflow/workflowSlice.ts +73 -0
  236. package/ai/workflow/workflowTypes.ts +106 -0
  237. package/client/agentRun.test.ts +240 -0
  238. package/client/agentRun.ts +182 -19
  239. package/client/compactDialog.test.ts +238 -0
  240. package/client/localRuntimeAdapter.test.ts +135 -0
  241. package/client/localRuntimeAdapter.ts +244 -0
  242. package/client/profileConfig.test.ts +40 -0
  243. package/client/streamingOutput.test.ts +22 -0
  244. package/client/streamingOutput.ts +38 -0
  245. package/commandRegistry.ts +9 -2
  246. package/connector-experimental/index.ts +5 -0
  247. package/database/actions/cacheMergedUserData.ts +64 -0
  248. package/database/actions/common.ts +242 -0
  249. package/database/actions/deleteFile.ts +40 -0
  250. package/database/actions/fetchUserData.ts +16 -0
  251. package/database/actions/fileContent.ts +125 -0
  252. package/database/actions/patch.ts +155 -0
  253. package/database/actions/read.ts +337 -0
  254. package/database/actions/readAndWait.ts +224 -0
  255. package/database/actions/readRequestManager.ts +120 -0
  256. package/database/actions/remove.ts +94 -0
  257. package/database/actions/replication.ts +366 -0
  258. package/database/actions/upload.ts +174 -0
  259. package/database/actions/upsert.ts +56 -0
  260. package/database/actions/write.ts +126 -0
  261. package/database/client/db.native.ts +73 -0
  262. package/database/client/db.ts +51 -0
  263. package/database/client/fetchUserData.ts +61 -0
  264. package/database/client/handleError.ts +19 -0
  265. package/database/client/queryRequest.ts +21 -0
  266. package/database/config.ts +21 -0
  267. package/database/dbActionThunks.ts +1 -0
  268. package/database/dbSlice.ts +149 -0
  269. package/database/email.ts +42 -0
  270. package/database/fileRing.ts +51 -0
  271. package/database/fileSharding.ts +70 -0
  272. package/database/fileStorage.native.ts +92 -0
  273. package/database/fileStorage.ts +232 -0
  274. package/database/fileUrl.ts +34 -0
  275. package/database/hooks/useUserData.ts +489 -0
  276. package/database/index.ts +1 -0
  277. package/database/keys.ts +765 -0
  278. package/database/queryPrefixes.ts +14 -0
  279. package/database/requests.ts +443 -0
  280. package/database/runtimeServerContext.ts +35 -0
  281. package/database/server/MemoryDB.ts +76 -0
  282. package/database/server/actorAccess.ts +76 -0
  283. package/database/server/agentDelegation.ts +124 -0
  284. package/database/server/coreDataOwnership.ts +13 -0
  285. package/database/server/coreDataProxy.ts +76 -0
  286. package/database/server/cybotReadonly.ts +18 -0
  287. package/database/server/dataHandlers.ts +111 -0
  288. package/database/server/db.ts +118 -0
  289. package/database/server/dbPath.ts +20 -0
  290. package/database/server/delete.ts +499 -0
  291. package/database/server/emailRepository.ts +1480 -0
  292. package/database/server/ensureDbOpen.ts +12 -0
  293. package/database/server/fileRead.ts +337 -0
  294. package/database/server/fileService.ts +436 -0
  295. package/database/server/handleTransaction.ts +86 -0
  296. package/database/server/patch.ts +282 -0
  297. package/database/server/query.ts +138 -0
  298. package/database/server/read.ts +325 -0
  299. package/database/server/resourceAccess.ts +211 -0
  300. package/database/server/routes.ts +110 -0
  301. package/database/server/spaceMemberAuthority.ts +67 -0
  302. package/database/server/upload.ts +159 -0
  303. package/database/server/write.ts +494 -0
  304. package/database/server/writeAuthority.ts +133 -0
  305. package/database/sqliteDb.ts +46 -0
  306. package/database/table/deleteTable.ts +120 -0
  307. package/database/tenantPlacement.ts +57 -0
  308. package/database/tombstones.ts +52 -0
  309. package/database/userDataLoadDecision.ts +17 -0
  310. package/database/userDataMerge.ts +95 -0
  311. package/database/userPreferenceRegister.ts +108 -0
  312. package/database/utils/dbPath.ts +47 -0
  313. package/database/utils/ulid.native.ts +6 -0
  314. package/database/utils/ulid.ts +1 -0
  315. package/index.ts +25 -15
  316. package/localRuntimeDb.ts +28 -0
  317. package/package.json +16 -4
  318. package/runtimeModeArgs.ts +33 -0
  319. package/tui/readlineWorkspace.ts +1 -0
  320. package/tui/session.ts +22 -0
@@ -0,0 +1,436 @@
1
+ // 文件路径: database/server/fileService.ts
2
+
3
+ import path from "path";
4
+ import { mkdir } from "node:fs/promises";
5
+ import { ulid } from "ulid";
6
+ import serverDb from "./db";
7
+ import { blobKey, fileKey, fileIdIndexKey, fileStatKey } from "../keys";
8
+ import { isTombstoneRecord } from "../tombstones";
9
+ import { DataType } from "create/types";
10
+
11
+ const UPLOAD_DIR = path.join(process.cwd(), "data", "uploads");
12
+
13
+ type JsonRecord = Record<string, any>;
14
+
15
+ export interface BlobRecord {
16
+ sha256: string;
17
+ path: string;
18
+ size: number;
19
+ mimeType: string;
20
+ refCount: number;
21
+ createdAt: string;
22
+ }
23
+
24
+ /**
25
+ * 文件元数据(服务器端)
26
+ *
27
+ * 注意:
28
+ * - File 自己在 LevelDB 中的主键是 file-{tenantId}-{fileId},和 ownerDbKey 无关。
29
+ * - ownerDbKey 表示“这个文件属于哪条业务记录”,例如某个 profile/page 的 dbKey。
30
+ */
31
+ export interface FileMetadata {
32
+ id: string;
33
+ tenantId: string;
34
+ sha256: string;
35
+ size: number;
36
+ mimeType: string;
37
+ filePath: string;
38
+
39
+ originalName: string;
40
+
41
+ // 归属信息
42
+ ownerType?: string; // "user" | "space" | "system" | ...
43
+ ownerId?: string;
44
+ uploadedBy?: string;
45
+
46
+ /**
47
+ * 指向业务记录的主键(例如 profile/page 等的 dbKey),
48
+ * 不是 File 自己在 LevelDB 中的主键。
49
+ */
50
+ ownerDbKey?: string;
51
+
52
+ tags?: string[];
53
+
54
+ // AI 信息
55
+ source?: string; // "user-upload" | "ai-generated" | "system" | ...
56
+ model?: string;
57
+ prompt?: string;
58
+
59
+ // 生命周期
60
+ createdAt: string;
61
+ deletedAt?: string;
62
+
63
+ // 去重 / 变体等预留
64
+ duplicateOf?: string;
65
+ variants?: {
66
+ thumb?: string;
67
+ medium?: string;
68
+ origin?: string;
69
+ [key: string]: string | undefined;
70
+ };
71
+
72
+ // 允许扩展
73
+ [key: string]: any;
74
+ }
75
+
76
+ export interface SaveFileOptions {
77
+ tenantId: string;
78
+
79
+ originalName: string;
80
+ mimeType?: string;
81
+
82
+ ownerType?: string;
83
+ ownerId?: string;
84
+ uploadedBy?: string;
85
+
86
+ /**
87
+ * 业务记录的 dbKey,例如 createUserKey.profile(userId)、
88
+ * PAGE-... / DIALOG-... 等。
89
+ */
90
+ ownerDbKey?: string;
91
+
92
+ source?: string;
93
+ model?: string;
94
+ prompt?: string;
95
+ tags?: string[];
96
+
97
+ /**
98
+ * 客户端传来的原始 metadata(例如前端 uploadFileAction 生成的)
99
+ * 服务端会以自己计算的 sha256/size/filePath/createdAt 覆盖同名字段。
100
+ * 若其中有 dbKey,则会映射为 ownerDbKey 并删除 dbKey 字段。
101
+ */
102
+ clientMetadata?: JsonRecord | null;
103
+
104
+ /**
105
+ * 若客户端已经生成了全局唯一 ID(例如前端 ulid()),可以传入,
106
+ * 服务端则不再重新生成。
107
+ */
108
+ clientProvidedId?: string;
109
+ }
110
+
111
+ /**
112
+ * 租户 ID 兜底:避免出现空字符串
113
+ */
114
+ const normalizeTenantId = (tenantId?: string): string =>
115
+ tenantId && tenantId.trim() ? tenantId.trim() : "default";
116
+
117
+ /**
118
+ * 确保上传目录存在
119
+ */
120
+ const ensureUploadDirExists = async (): Promise<void> => {
121
+ await mkdir(UPLOAD_DIR, { recursive: true });
122
+ };
123
+
124
+ /**
125
+ * 使用 Bun.CryptoHasher 计算 sha256
126
+ */
127
+ const calculateSha256 = (buffer: Buffer): string => {
128
+ const hasher = new Bun.CryptoHasher("sha256");
129
+ hasher.update(buffer);
130
+ return hasher.digest("hex");
131
+ };
132
+
133
+ /**
134
+ * 封装 LevelDB get,缺失时返回 null
135
+ */
136
+ const dbGetOrNull = async <T = any>(key: string): Promise<T | null> => {
137
+ try {
138
+ const value = await serverDb.get(key);
139
+ return value as T;
140
+ } catch (err: any) {
141
+ if (err && (err.code === "LEVEL_NOT_FOUND" || err.notFound === true)) {
142
+ return null;
143
+ }
144
+ throw err;
145
+ }
146
+ };
147
+
148
+ /**
149
+ * 今天的 dateKey: "YYYYMMDD"
150
+ */
151
+ const getTodayDateKey = (): string => {
152
+ const iso = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
153
+ return iso.replace(/-/g, "");
154
+ };
155
+
156
+ /**
157
+ * 更新按租户的日统计(最小版本)
158
+ */
159
+ const updateTenantDailyStatsOnCreate = async (
160
+ tenantId: string,
161
+ size: number,
162
+ isAi: boolean
163
+ ): Promise<void> => {
164
+ const dateKey = getTodayDateKey();
165
+ const statKey = fileStatKey.tenantPerDay(tenantId, dateKey);
166
+
167
+ const existing =
168
+ (await dbGetOrNull<JsonRecord>(statKey)) ??
169
+ ({
170
+ tenantId,
171
+ dateKey,
172
+ filesCreated: 0,
173
+ bytesAdded: 0,
174
+ aiFilesCreated: 0,
175
+ } satisfies JsonRecord);
176
+
177
+ existing.filesCreated = (existing.filesCreated || 0) + 1;
178
+ existing.bytesAdded = (existing.bytesAdded || 0) + size;
179
+ if (isAi) {
180
+ existing.aiFilesCreated = (existing.aiFilesCreated || 0) + 1;
181
+ }
182
+
183
+ await serverDb.put(statKey, existing);
184
+ };
185
+
186
+ /**
187
+ * 更新按模型的日统计(仅对有 model 的记录)
188
+ */
189
+ const updateModelDailyStatsOnCreate = async (
190
+ modelName: string,
191
+ size: number
192
+ ): Promise<void> => {
193
+ const dateKey = getTodayDateKey();
194
+ const statKey = fileStatKey.modelPerDay(modelName, dateKey);
195
+
196
+ const existing =
197
+ (await dbGetOrNull<JsonRecord>(statKey)) ??
198
+ ({
199
+ modelName,
200
+ dateKey,
201
+ filesCreated: 0,
202
+ bytesAdded: 0,
203
+ } satisfies JsonRecord);
204
+
205
+ existing.filesCreated = (existing.filesCreated || 0) + 1;
206
+ existing.bytesAdded = (existing.bytesAdded || 0) + size;
207
+
208
+ await serverDb.put(statKey, existing);
209
+ };
210
+
211
+ /**
212
+ * 处理 Blob 记录(查重 + refCount)
213
+ * 返回 BlobRecord(包含 path / size / mimeType)
214
+ */
215
+ const ensureBlobRecord = async (
216
+ buffer: Buffer,
217
+ mimeType: string
218
+ ): Promise<BlobRecord> => {
219
+ const sha256 = calculateSha256(buffer);
220
+ const key = blobKey(sha256);
221
+
222
+ const existing = await dbGetOrNull<BlobRecord>(key);
223
+ if (existing) {
224
+ const updated: BlobRecord = {
225
+ ...existing,
226
+ refCount: (existing.refCount || 0) + 1,
227
+ };
228
+ await serverDb.put(key, updated);
229
+ return updated;
230
+ }
231
+
232
+ // 新建 Blob
233
+ await ensureUploadDirExists();
234
+ const blobFileName = sha256;
235
+ const blobPath = path.join(UPLOAD_DIR, blobFileName);
236
+
237
+ await Bun.write(blobPath, buffer);
238
+
239
+ const record: BlobRecord = {
240
+ sha256,
241
+ path: blobPath,
242
+ size: buffer.byteLength,
243
+ mimeType,
244
+ refCount: 1,
245
+ createdAt: new Date().toISOString(),
246
+ };
247
+
248
+ await serverDb.put(key, record);
249
+ return record;
250
+ };
251
+
252
+ /**
253
+ * 将 Blob refCount -1,若为 0 则删除物理文件和记录
254
+ * (当前 demo 暂时未对外暴露 delete File 的 API,可留 TODO 使用)
255
+ */
256
+ export const decrementBlobRefCount = async (sha256: string): Promise<void> => {
257
+ const key = blobKey(sha256);
258
+ const existing = await dbGetOrNull<BlobRecord>(key);
259
+ if (!existing) return;
260
+
261
+ const nextCount = (existing.refCount || 0) - 1;
262
+ if (nextCount <= 0) {
263
+ try {
264
+ await Bun.write(existing.path, new Uint8Array());
265
+ } catch {
266
+ // 忽略物理删除失败
267
+ }
268
+ await serverDb.del(key);
269
+ return;
270
+ }
271
+
272
+ const updated: BlobRecord = { ...existing, refCount: nextCount };
273
+ await serverDb.put(key, updated);
274
+ };
275
+
276
+ /**
277
+ * 保存二进制内容为 File(统一入口)
278
+ */
279
+ export const saveBufferAsFile = async (
280
+ buffer: Buffer,
281
+ options: SaveFileOptions
282
+ ): Promise<{ fileId: string; metadata: FileMetadata }> => {
283
+ const tenantId = normalizeTenantId(options.tenantId);
284
+ const mimeType = options.mimeType || "application/octet-stream";
285
+
286
+ // 1) Blob 处理(查重 + refCount)
287
+ const blob = await ensureBlobRecord(buffer, mimeType);
288
+
289
+ // 2) FileId
290
+ const fileId = options.clientProvidedId || ulid();
291
+
292
+ // 3) 处理 clientMetadata
293
+ const clientMetaSanitized: JsonRecord = {
294
+ ...(options.clientMetadata || {}),
295
+ };
296
+
297
+ // 如果客户端提供了 dbKey,我们记录下来,但不一定要在 merged 里删除,
298
+ // 因为它是物理主键的蓝图。
299
+ const targetDbKey = options.ownerDbKey || clientMetaSanitized.dbKey;
300
+
301
+ // 4) 组装 FileMetadata(以服务端字段覆盖 clientMetadata)
302
+ const base: FileMetadata = {
303
+ id: fileId,
304
+ type: DataType.FILE,
305
+ tenantId,
306
+ sha256: blob.sha256,
307
+ size: blob.size,
308
+ mimeType: blob.mimeType,
309
+ filePath: blob.path,
310
+
311
+ originalName: options.originalName,
312
+
313
+ ownerType: options.ownerType,
314
+ ownerId: options.ownerId,
315
+ uploadedBy: options.uploadedBy,
316
+
317
+ ownerDbKey: options.ownerDbKey ?? clientMetaSanitized.ownerDbKey,
318
+ tags: options.tags,
319
+
320
+ source: options.source,
321
+ model: options.model,
322
+ prompt: options.prompt,
323
+
324
+ createdAt: new Date().toISOString(),
325
+ };
326
+
327
+ const merged: FileMetadata = {
328
+ ...clientMetaSanitized,
329
+ ...base,
330
+ };
331
+
332
+ // 5) 写入 File 主记录
333
+ // 如果 targetDbKey 符合 file- 规则,优先直接用(保持跨机一致性)
334
+ const mainKey =
335
+ targetDbKey && targetDbKey.startsWith("file-")
336
+ ? targetDbKey
337
+ : fileKey.single(tenantId, fileId);
338
+
339
+ await serverDb.put(mainKey, { ...merged, dbKey: mainKey });
340
+
341
+ // 6) 写入 fileId 索引
342
+ const indexKey = fileIdIndexKey(fileId);
343
+ await serverDb.put(indexKey, {
344
+ tenantId,
345
+ fileId,
346
+ mainKey,
347
+ createdAt: base.createdAt,
348
+ });
349
+
350
+ // 7) 更新简单统计(tenant / model)
351
+ const isAi = merged.source === "ai-generated";
352
+ await updateTenantDailyStatsOnCreate(tenantId, merged.size, isAi);
353
+ if (merged.model) {
354
+ await updateModelDailyStatsOnCreate(merged.model, merged.size);
355
+ }
356
+
357
+ return { fileId, metadata: merged };
358
+ };
359
+
360
+ /**
361
+ * 通过 fileId 查到 { tenantId, fileId, mainKey }
362
+ */
363
+ const resolveTenantAndFileId = async (
364
+ fileId: string
365
+ ): Promise<{ tenantId: string; fileId: string; mainKey?: string } | null> => {
366
+ const indexKey = fileIdIndexKey(fileId);
367
+ const index = await dbGetOrNull<{ tenantId: string; fileId: string; mainKey?: string }>(
368
+ indexKey
369
+ );
370
+ if (!index) return null;
371
+ return { tenantId: index.tenantId, fileId: index.fileId, mainKey: index.mainKey };
372
+ };
373
+
374
+ /**
375
+ * 通过 fileId 获取 FileMetadata
376
+ */
377
+ export const getFileMetadataById = async (
378
+ fileId: string
379
+ ): Promise<FileMetadata | null> => {
380
+ // 1. 优先尝试直接读取(支持传入完整的 dbKey,如 file-userId-ulid)
381
+ if (fileId.startsWith("file-")) {
382
+ const metadata = await dbGetOrNull<FileMetadata>(fileId);
383
+ if (metadata && !isTombstoneRecord(metadata)) return metadata;
384
+ if (metadata && isTombstoneRecord(metadata)) return null;
385
+ }
386
+
387
+ // 2. 尝试通过 fileId 索引查找(支持传入纯 ulid)
388
+ const resolved = await resolveTenantAndFileId(fileId);
389
+ if (!resolved) return null;
390
+
391
+ // 优先使用索引中存储的 mainKey(避免重建不一致),回退到按规则拼接
392
+ const mainKey = resolved.mainKey || fileKey.single(resolved.tenantId, resolved.fileId);
393
+ const metadata = await dbGetOrNull<FileMetadata>(mainKey);
394
+ if (!metadata || isTombstoneRecord(metadata)) {
395
+ return null;
396
+ }
397
+
398
+ return metadata;
399
+ };
400
+
401
+ export const hasFileTombstoneById = async (fileId: string): Promise<boolean> => {
402
+ if (fileId.startsWith("file-")) {
403
+ const metadata = await dbGetOrNull<FileMetadata>(fileId);
404
+ return Boolean(metadata && isTombstoneRecord(metadata));
405
+ }
406
+
407
+ const resolved = await resolveTenantAndFileId(fileId);
408
+ if (!resolved) return false;
409
+
410
+ const mainKey = resolved.mainKey || fileKey.single(resolved.tenantId, resolved.fileId);
411
+ const metadata = await dbGetOrNull<FileMetadata>(mainKey);
412
+ return Boolean(metadata && isTombstoneRecord(metadata));
413
+ };
414
+
415
+ /**
416
+ * 通过 fileId 获取文件内容(二进制)+ Metadata
417
+ */
418
+ export const getFileContentById = async (
419
+ fileId: string
420
+ ): Promise<{ buffer: Buffer; metadata: FileMetadata }> => {
421
+ const metadata = await getFileMetadataById(fileId);
422
+ if (!metadata) {
423
+ throw new Error(`File not found: ${fileId}`);
424
+ }
425
+
426
+ const file = Bun.file(metadata.filePath);
427
+ const exists = await file.exists();
428
+ if (!exists) {
429
+ throw new Error(`File content missing on disk: ${fileId}`);
430
+ }
431
+
432
+ const arrayBuffer = await file.arrayBuffer();
433
+ const buffer = Buffer.from(arrayBuffer);
434
+
435
+ return { buffer, metadata };
436
+ };
@@ -0,0 +1,86 @@
1
+ import { rechargeUserBalance } from "auth/server/recharge";
2
+ import { isSystemAdmin } from "core/init";
3
+
4
+ // 添加类型定义
5
+ interface TransactionData {
6
+ transactionType: "recharge";
7
+ amount: number;
8
+ toUserId: string;
9
+ reason?: string;
10
+ }
11
+
12
+ interface TransactionResponse {
13
+ success: boolean;
14
+ error?: string;
15
+ [key: string]: any;
16
+ }
17
+
18
+ export const handleTransaction = async (
19
+ data: TransactionData,
20
+ res: Response,
21
+ customKey: string,
22
+ actionUserId: string
23
+ ): Promise<Response> => {
24
+ const { transactionType, amount, toUserId } = data;
25
+
26
+ console.log("[Transaction] Start:", {
27
+ type: transactionType,
28
+ toUserId,
29
+ amount,
30
+ customKey,
31
+ });
32
+
33
+ if (transactionType === "recharge") {
34
+ const isAdmin = isSystemAdmin(actionUserId);
35
+
36
+ if (!isAdmin) {
37
+ console.warn("[Transaction] Permission denied:", {
38
+ actionUserId,
39
+ toUserId,
40
+ });
41
+ return res.status(403).json({
42
+ message: "Need admin permission",
43
+ });
44
+ }
45
+
46
+ console.log("[Transaction] Processing recharge:", {
47
+ toUserId,
48
+ amount,
49
+ reason: data.reason,
50
+ });
51
+
52
+ const result = await rechargeUserBalance(
53
+ toUserId, // 修改参数名
54
+ amount,
55
+ data.reason,
56
+ customKey
57
+ );
58
+
59
+ if (!result.success) {
60
+ console.error("[Transaction] Recharge failed:", {
61
+ error: result.error,
62
+ toUserId,
63
+ amount,
64
+ });
65
+ return res.status(400).json({
66
+ message: result.error,
67
+ error: result.error,
68
+ });
69
+ }
70
+
71
+ console.log("[Transaction] Completed successfully:", {
72
+ toUserId,
73
+ amount,
74
+ result,
75
+ });
76
+
77
+ return res.status(200).json({
78
+ message: "Transaction completed",
79
+ ...result,
80
+ });
81
+ }
82
+
83
+ return res.status(400).json({
84
+ message: "Unsupported transaction type",
85
+ });
86
+ };