nolo-cli 0.1.13 → 0.1.15

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 (321) 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/agentRunCommand.ts +104 -0
  8. package/agentRuntimeCommands.ts +139 -22
  9. package/agentRuntimeLocal.ts +7 -0
  10. package/ai/agent/_executeModel.ts +118 -0
  11. package/ai/agent/agentSlice.ts +544 -1
  12. package/ai/agent/appWorkingMemory.ts +126 -0
  13. package/ai/agent/avatarUtils.ts +24 -0
  14. package/ai/agent/buildEditingContext.ts +373 -0
  15. package/ai/agent/buildSystemPrompt.ts +532 -0
  16. package/ai/agent/cleanAgentMessages.ts +140 -0
  17. package/ai/agent/cliChatClient.ts +119 -0
  18. package/ai/agent/contextCompiler.ts +107 -0
  19. package/ai/agent/contextLayerContract.ts +44 -0
  20. package/ai/agent/createAgentSchema.ts +234 -0
  21. package/ai/agent/executeToolCall.ts +58 -0
  22. package/ai/agent/fetchAgentContexts.ts +42 -0
  23. package/ai/agent/generatePrompt.ts +3 -0
  24. package/ai/agent/getFullChatContextKeys.ts +168 -0
  25. package/ai/agent/hooks/fetchPublicAgents.ts +133 -0
  26. package/ai/agent/hooks/useAgentConfig.ts +61 -0
  27. package/ai/agent/hooks/useAgentDialog.ts +35 -0
  28. package/ai/agent/hooks/useAgentFormValidation.ts +202 -0
  29. package/ai/agent/hooks/usePublicAgents.ts +473 -0
  30. package/ai/agent/persistMessageWithFixedId.ts +37 -0
  31. package/ai/agent/planSlice.ts +259 -0
  32. package/ai/agent/referenceUtils.ts +229 -0
  33. package/ai/agent/runAgentBackground.ts +238 -0
  34. package/ai/agent/runAgentClientLoop.ts +138 -0
  35. package/ai/agent/runtimeGuidance.ts +97 -0
  36. package/ai/agent/runtimeServerBase.ts +37 -0
  37. package/ai/agent/server/fetchPublicAgents.ts +128 -0
  38. package/ai/agent/startParallelAgentStreams.ts +424 -0
  39. package/ai/agent/startupProtocol.ts +53 -0
  40. package/ai/agent/streamAgentChatTurn.ts +1299 -0
  41. package/ai/agent/streamAgentChatTurnUtils.ts +738 -0
  42. package/ai/agent/types.ts +71 -0
  43. package/ai/agent/utils/imageOutput.ts +39 -0
  44. package/ai/agent/utils/publicImageAgentMode.ts +26 -0
  45. package/ai/agent/utils/sortUtils.ts +250 -0
  46. package/ai/agent/web/referencePickerUtils.ts +146 -0
  47. package/ai/ai.locale.ts +1083 -0
  48. package/ai/chat/accumulateToolCallChunks.ts +95 -0
  49. package/ai/chat/fetchUtils.native.ts +276 -0
  50. package/ai/chat/fetchUtils.ts +153 -0
  51. package/ai/chat/inlineImageUrlsForCustomProvider.ts +117 -0
  52. package/ai/chat/parseApiError.ts +64 -0
  53. package/ai/chat/parseMultilineSSE.ts +95 -0
  54. package/ai/chat/sendOpenAICompletionsRequest.native.ts +682 -0
  55. package/ai/chat/sendOpenAICompletionsRequest.ts +712 -0
  56. package/ai/chat/sendOpenAIResponseRequest.ts +512 -0
  57. package/ai/chat/shouldUseServerProxy.ts +18 -0
  58. package/ai/chat/sseClient.native.ts +91 -0
  59. package/ai/chat/sseClient.ts +67 -0
  60. package/ai/chat/streamReader.native.ts +31 -0
  61. package/ai/chat/streamReader.ts +62 -0
  62. package/ai/chat/updateTotalUsage.ts +72 -0
  63. package/ai/context/buildReferenceContext.ts +437 -0
  64. package/ai/context/calculateContextUsage.ts +133 -0
  65. package/ai/context/retention.ts +165 -0
  66. package/ai/context/tokenUtils.ts +78 -0
  67. package/ai/index.ts +1 -1
  68. package/ai/llm/agentCapabilities.ts +74 -0
  69. package/ai/llm/calculateGeminiImageTokens.ts +57 -0
  70. package/ai/llm/deepinfra.ts +28 -0
  71. package/ai/llm/fireworks.ts +68 -0
  72. package/ai/llm/generateRequestBody.ts +165 -0
  73. package/ai/llm/getModelContextWindow.ts +84 -0
  74. package/ai/llm/getNoloKey.ts +37 -0
  75. package/ai/llm/getPricing.ts +232 -0
  76. package/ai/llm/hooks/useModelPricing.ts +75 -0
  77. package/ai/llm/imagePricing.ts +66 -0
  78. package/ai/llm/isResponseAPIModel.ts +13 -0
  79. package/ai/llm/kimi.ts +18 -0
  80. package/ai/llm/mimo.ts +71 -0
  81. package/ai/llm/mistral.ts +22 -0
  82. package/ai/llm/modelAvatar.ts +427 -0
  83. package/ai/llm/models.ts +45 -0
  84. package/ai/llm/openrouterModels.ts +141 -0
  85. package/ai/llm/providers.ts +307 -0
  86. package/ai/llm/reasoningModels.ts +28 -0
  87. package/ai/llm/types.ts +59 -0
  88. package/ai/llm/usageRequestOptions.ts +59 -0
  89. package/ai/memory/capture.ts +148 -0
  90. package/ai/memory/consolidate.ts +104 -0
  91. package/ai/memory/delete.ts +147 -0
  92. package/ai/memory/overlay.ts +84 -0
  93. package/ai/memory/query.ts +38 -0
  94. package/ai/memory/queryShared.ts +160 -0
  95. package/ai/memory/rank.ts +105 -0
  96. package/ai/memory/recentRelationshipRecap.ts +247 -0
  97. package/ai/memory/remember.ts +167 -0
  98. package/ai/memory/runtime.ts +76 -0
  99. package/ai/memory/store.ts +20 -0
  100. package/ai/memory/storeShared.ts +76 -0
  101. package/ai/memory/types.ts +46 -0
  102. package/ai/memory/understanding.ts +349 -0
  103. package/ai/memory/understandingGreeting.ts +264 -0
  104. package/ai/messages/type.ts +20 -0
  105. package/ai/policy/personalizationDialog.ts +333 -0
  106. package/ai/policy/runtimePolicy.ts +440 -0
  107. package/ai/policy/selfUpdateFields.ts +48 -0
  108. package/ai/policy/types.ts +64 -0
  109. package/ai/skills/referenceRuntime.ts +274 -0
  110. package/ai/skills/skillDiagnostics.ts +251 -0
  111. package/ai/skills/skillDocBuilder.ts +139 -0
  112. package/ai/skills/skillDocProtocol.ts +434 -0
  113. package/ai/skills/skillReferenceSummary.ts +63 -0
  114. package/ai/skills/skillSummaryMarker.ts +26 -0
  115. package/ai/token/calculatePrice.ts +546 -0
  116. package/ai/token/db.ts +98 -0
  117. package/ai/token/externalToolCost.ts +321 -0
  118. package/ai/token/hooks/useRecords.ts +65 -0
  119. package/ai/token/missingUsageEstimate.ts +42 -0
  120. package/ai/token/modelUsageQuery.ts +252 -0
  121. package/ai/token/normalizeUsage.ts +84 -0
  122. package/ai/token/openaiImageGenerationUsage.ts +56 -0
  123. package/ai/token/prepareTokenUsageData.ts +88 -0
  124. package/ai/token/query.ts +88 -0
  125. package/ai/token/queryUserTokens.ts +59 -0
  126. package/ai/token/resolveBillingTarget.ts +52 -0
  127. package/ai/token/saveTokenRecord.ts +53 -0
  128. package/ai/token/serverDialogProjection.ts +78 -0
  129. package/ai/token/serverTokenWriter.ts +143 -0
  130. package/ai/token/stats.ts +21 -0
  131. package/ai/token/tokenThunks.ts +24 -0
  132. package/ai/token/types.ts +93 -0
  133. package/ai/tools/agent/agentTools.ts +176 -0
  134. package/ai/tools/agent/agentUpdateShared.ts +311 -0
  135. package/ai/tools/agent/callAgentTool.ts +139 -0
  136. package/ai/tools/agent/createAgentTool.ts +512 -0
  137. package/ai/tools/agent/createDialogTool.ts +69 -0
  138. package/ai/tools/agent/createSkillAgentTool.ts +62 -0
  139. package/ai/tools/agent/parallelBudget.ts +221 -0
  140. package/ai/tools/agent/presets/appBuilderPreset.ts +147 -0
  141. package/ai/tools/agent/runLlmTool.ts +96 -0
  142. package/ai/tools/agent/runStreamingAgentTool.ts +73 -0
  143. package/ai/tools/agent/skillAgentArgs.ts +106 -0
  144. package/ai/tools/agent/skillAgentPreset.ts +89 -0
  145. package/ai/tools/agent/streamParallelAgentsTool.ts +122 -0
  146. package/ai/tools/agent/updateAgentTool.ts +96 -0
  147. package/ai/tools/agent/updateSelfTool.ts +113 -0
  148. package/ai/tools/amazonProductScraperTool.ts +86 -0
  149. package/ai/tools/apifyActorClient.ts +45 -0
  150. package/ai/tools/appEditGuard.ts +372 -0
  151. package/ai/tools/appReadSnapshot.ts +153 -0
  152. package/ai/tools/appTools.ts +1549 -0
  153. package/ai/tools/applyEditTool.ts +256 -0
  154. package/ai/tools/applyLineEditsTool.ts +312 -0
  155. package/ai/tools/browserTools/click.ts +33 -0
  156. package/ai/tools/browserTools/closeSession.ts +29 -0
  157. package/ai/tools/browserTools/common.ts +27 -0
  158. package/ai/tools/browserTools/openSession.ts +48 -0
  159. package/ai/tools/browserTools/readContent.ts +38 -0
  160. package/ai/tools/browserTools/selectOption.ts +46 -0
  161. package/ai/tools/browserTools/typeText.ts +42 -0
  162. package/ai/tools/category/createCategoryTool.ts +66 -0
  163. package/ai/tools/category/queryContentsByCategoryTool.ts +69 -0
  164. package/ai/tools/category/updateContentCategoryTool.ts +75 -0
  165. package/ai/tools/cfBrowserTools.ts +319 -0
  166. package/ai/tools/cfSpeechToTextTool.ts +49 -0
  167. package/ai/tools/checkEnvTool.ts +65 -0
  168. package/ai/tools/cloudflareCrawlTool.ts +289 -0
  169. package/ai/tools/codeSearchTool.ts +111 -0
  170. package/ai/tools/codeTools.ts +101 -0
  171. package/ai/tools/createDocTool.ts +132 -0
  172. package/ai/tools/createPlanTool.ts +999 -0
  173. package/ai/tools/createSkillDocTool.ts +155 -0
  174. package/ai/tools/createWorkflowTool.ts +154 -0
  175. package/ai/tools/deepseekOcrTool.ts +34 -0
  176. package/ai/tools/delayTool.ts +31 -0
  177. package/ai/tools/deleteSpacesTool.ts +325 -0
  178. package/ai/tools/deleteSpacesToolModel.ts +159 -0
  179. package/ai/tools/devReloadUtils.ts +29 -0
  180. package/ai/tools/dialogMessageSearch.ts +137 -0
  181. package/ai/tools/doctorSkillTool.ts +72 -0
  182. package/ai/tools/ecommerceScraperTool.ts +86 -0
  183. package/ai/tools/emailTools.ts +549 -0
  184. package/ai/tools/evalSkillTool.ts +92 -0
  185. package/ai/tools/exaSearchTool.ts +64 -0
  186. package/ai/tools/execBashTool.ts +379 -0
  187. package/ai/tools/executeSqlTool.ts +192 -0
  188. package/ai/tools/fetchWebpageSupport.ts +309 -0
  189. package/ai/tools/fetchWebpageTool.ts +84 -0
  190. package/ai/tools/geminiImagePreviewTool.ts +361 -0
  191. package/ai/tools/generateDocxTool.ts +215 -0
  192. package/ai/tools/googleSearchScraperTool.ts +106 -0
  193. package/ai/tools/importDataTool.ts +133 -0
  194. package/ai/tools/importSkillTool.ts +162 -0
  195. package/ai/tools/index.ts +1927 -0
  196. package/ai/tools/listFilesTool.ts +82 -0
  197. package/ai/tools/listUserSpacesTool.ts +113 -0
  198. package/ai/tools/modelUsageTools.ts +199 -0
  199. package/ai/tools/olmOcrTool.ts +34 -0
  200. package/ai/tools/openaiImageTool.ts +267 -0
  201. package/ai/tools/prepareTools.ts +23 -0
  202. package/ai/tools/readDocTool.ts +84 -0
  203. package/ai/tools/readFileTool.ts +211 -0
  204. package/ai/tools/readTool.ts +163 -0
  205. package/ai/tools/readXPostTool.ts +233 -0
  206. package/ai/tools/rememberMemoryTool.ts +84 -0
  207. package/ai/tools/remotionVideoTool.ts +151 -0
  208. package/ai/tools/searchDialogMessagesTool.ts +222 -0
  209. package/ai/tools/searchRepoTool.ts +115 -0
  210. package/ai/tools/searchWorkspaceTool.ts +259 -0
  211. package/ai/tools/skillFollowup.ts +86 -0
  212. package/ai/tools/surfWeatherTool.ts +169 -0
  213. package/ai/tools/table/addTableRowTool.ts +217 -0
  214. package/ai/tools/table/createTableTool.ts +315 -0
  215. package/ai/tools/table/rowTools.ts +366 -0
  216. package/ai/tools/table/schemaTools.ts +244 -0
  217. package/ai/tools/table/shareTableTool.ts +148 -0
  218. package/ai/tools/table/toolShared.ts +129 -0
  219. package/ai/tools/toolApiClient.ts +198 -0
  220. package/ai/tools/toolNameAliases.ts +57 -0
  221. package/ai/tools/toolResultError.ts +42 -0
  222. package/ai/tools/toolRunSlice.ts +303 -0
  223. package/ai/tools/toolSchemaCompatibility.ts +53 -0
  224. package/ai/tools/toolVisibility.ts +4 -0
  225. package/ai/tools/types.ts +20 -0
  226. package/ai/tools/uiAskChoiceTool.ts +104 -0
  227. package/ai/tools/updateContentTitleTool.ts +84 -0
  228. package/ai/tools/updateDocTool.ts +105 -0
  229. package/ai/tools/updateUserPreferenceProfileTool.ts +145 -0
  230. package/ai/tools/whisperTool.ts +77 -0
  231. package/ai/tools/writeFileTool.ts +210 -0
  232. package/ai/tools/youtubeScraperTool.ts +116 -0
  233. package/ai/tools/ziweiChartTool.ts +678 -0
  234. package/ai/types.ts +55 -0
  235. package/ai/workflow/workflowExecutor.ts +323 -0
  236. package/ai/workflow/workflowSlice.ts +73 -0
  237. package/ai/workflow/workflowTypes.ts +106 -0
  238. package/client/agentRun.test.ts +240 -0
  239. package/client/agentRun.ts +182 -19
  240. package/client/compactDialog.test.ts +238 -0
  241. package/client/localRuntimeAdapter.test.ts +135 -0
  242. package/client/localRuntimeAdapter.ts +244 -0
  243. package/client/profileConfig.test.ts +40 -0
  244. package/client/streamingOutput.test.ts +22 -0
  245. package/client/streamingOutput.ts +38 -0
  246. package/commandRegistry.ts +11 -2
  247. package/connector-experimental/index.ts +5 -0
  248. package/database/actions/cacheMergedUserData.ts +64 -0
  249. package/database/actions/common.ts +242 -0
  250. package/database/actions/deleteFile.ts +40 -0
  251. package/database/actions/fetchUserData.ts +16 -0
  252. package/database/actions/fileContent.ts +125 -0
  253. package/database/actions/patch.ts +155 -0
  254. package/database/actions/read.ts +337 -0
  255. package/database/actions/readAndWait.ts +224 -0
  256. package/database/actions/readRequestManager.ts +120 -0
  257. package/database/actions/remove.ts +94 -0
  258. package/database/actions/replication.ts +366 -0
  259. package/database/actions/upload.ts +174 -0
  260. package/database/actions/upsert.ts +56 -0
  261. package/database/actions/write.ts +126 -0
  262. package/database/client/db.native.ts +73 -0
  263. package/database/client/db.ts +51 -0
  264. package/database/client/fetchUserData.ts +61 -0
  265. package/database/client/handleError.ts +19 -0
  266. package/database/client/queryRequest.ts +21 -0
  267. package/database/config.ts +21 -0
  268. package/database/dbActionThunks.ts +1 -0
  269. package/database/dbSlice.ts +149 -0
  270. package/database/email.ts +42 -0
  271. package/database/fileRing.ts +51 -0
  272. package/database/fileSharding.ts +70 -0
  273. package/database/fileStorage.native.ts +92 -0
  274. package/database/fileStorage.ts +232 -0
  275. package/database/fileUrl.ts +34 -0
  276. package/database/hooks/useUserData.ts +489 -0
  277. package/database/index.ts +1 -0
  278. package/database/keys.ts +765 -0
  279. package/database/queryPrefixes.ts +14 -0
  280. package/database/requests.ts +443 -0
  281. package/database/runtimeServerContext.ts +35 -0
  282. package/database/server/MemoryDB.ts +76 -0
  283. package/database/server/actorAccess.ts +76 -0
  284. package/database/server/agentDelegation.ts +124 -0
  285. package/database/server/coreDataOwnership.ts +13 -0
  286. package/database/server/coreDataProxy.ts +76 -0
  287. package/database/server/cybotReadonly.ts +18 -0
  288. package/database/server/dataHandlers.ts +111 -0
  289. package/database/server/db.ts +118 -0
  290. package/database/server/dbPath.ts +20 -0
  291. package/database/server/delete.ts +499 -0
  292. package/database/server/emailRepository.ts +1480 -0
  293. package/database/server/ensureDbOpen.ts +12 -0
  294. package/database/server/fileRead.ts +337 -0
  295. package/database/server/fileService.ts +436 -0
  296. package/database/server/handleTransaction.ts +86 -0
  297. package/database/server/patch.ts +282 -0
  298. package/database/server/query.ts +138 -0
  299. package/database/server/read.ts +325 -0
  300. package/database/server/resourceAccess.ts +211 -0
  301. package/database/server/routes.ts +110 -0
  302. package/database/server/spaceMemberAuthority.ts +67 -0
  303. package/database/server/upload.ts +159 -0
  304. package/database/server/write.ts +494 -0
  305. package/database/server/writeAuthority.ts +133 -0
  306. package/database/sqliteDb.ts +46 -0
  307. package/database/table/deleteTable.ts +120 -0
  308. package/database/tenantPlacement.ts +57 -0
  309. package/database/tombstones.ts +52 -0
  310. package/database/userDataLoadDecision.ts +17 -0
  311. package/database/userDataMerge.ts +95 -0
  312. package/database/userPreferenceRegister.ts +108 -0
  313. package/database/utils/dbPath.ts +47 -0
  314. package/database/utils/ulid.native.ts +6 -0
  315. package/database/utils/ulid.ts +1 -0
  316. package/index.ts +37 -19
  317. package/localRuntimeDb.ts +28 -0
  318. package/package.json +17 -4
  319. package/runtimeModeArgs.ts +33 -0
  320. package/tui/readlineWorkspace.ts +1 -0
  321. package/tui/session.ts +22 -0
@@ -0,0 +1,159 @@
1
+ // 文件路径: database/server/upload.ts
2
+ import { Request } from "bun";
3
+ import { saveBufferAsFile, FileMetadata } from "./fileService";
4
+
5
+ /**
6
+ * 检查请求方法是否为 POST
7
+ */
8
+ const validateRequestMethod = (method: string): Response | null => {
9
+ if (method !== "POST") {
10
+ return new Response(JSON.stringify({ error: "Method not allowed" }), {
11
+ status: 405,
12
+ headers: { "Content-Type": "application/json" },
13
+ });
14
+ }
15
+ return null;
16
+ };
17
+
18
+ /**
19
+ * 从 URL / metadata / userId 中解析 tenantId
20
+ */
21
+ const resolveTenantId = (params: {
22
+ explicitTenantId?: string | null;
23
+ userId?: string | null;
24
+ clientMetadata?: Record<string, any> | null;
25
+ }): string | undefined => {
26
+ const { explicitTenantId, userId, clientMetadata } = params;
27
+
28
+ if (explicitTenantId && explicitTenantId.trim()) return explicitTenantId;
29
+ if (clientMetadata?.tenantId && String(clientMetadata.tenantId).trim()) {
30
+ return String(clientMetadata.tenantId).trim();
31
+ }
32
+ if (userId && userId.trim()) return userId;
33
+
34
+ return undefined;
35
+ };
36
+
37
+ /**
38
+ * 处理文件上传请求
39
+ *
40
+ * 支持的 FormData 字段:
41
+ * - file : File (必需)
42
+ * - metadata : string(JSON) (可选,来自前端 uploadFileAction)
43
+ * - customKey : string (可选,作为 ownerDbKey 的候选值)
44
+ * - userId : string (可选,上传者)
45
+ * - tenantId : string (可选,显式指定租户)
46
+ * - ownerType : string (可选)
47
+ * - ownerId : string (可选)
48
+ */
49
+ export const handleUpload = async (req: Request): Promise<Response> => {
50
+ try {
51
+ const methodValidation = validateRequestMethod(req.method);
52
+ if (methodValidation) return methodValidation;
53
+
54
+ const formData = await req.formData();
55
+ const file = formData.get("file") as File | null;
56
+ if (!file) {
57
+ return new Response(JSON.stringify({ error: "No file uploaded" }), {
58
+ status: 400,
59
+ headers: { "Content-Type": "application/json" },
60
+ });
61
+ }
62
+
63
+ const metadataRaw = formData.get("metadata");
64
+ const customKeyRaw = formData.get("customKey");
65
+ const userIdRaw = formData.get("userId");
66
+ const tenantIdRaw = formData.get("tenantId");
67
+ const ownerTypeRaw = formData.get("ownerType");
68
+ const ownerIdRaw = formData.get("ownerId");
69
+
70
+ let clientMetadata: Record<string, any> | null = null;
71
+ if (typeof metadataRaw === "string") {
72
+ try {
73
+ clientMetadata = JSON.parse(metadataRaw);
74
+ } catch (e) {
75
+ console.warn("[handleUpload] Failed to parse metadata JSON:", e);
76
+ }
77
+ }
78
+
79
+ const customKey =
80
+ typeof customKeyRaw === "string"
81
+ ? customKeyRaw
82
+ : undefined;
83
+
84
+ const userId =
85
+ typeof userIdRaw === "string"
86
+ ? userIdRaw
87
+ : clientMetadata?.userId ?? undefined;
88
+
89
+ const explicitTenantId =
90
+ typeof tenantIdRaw === "string" ? tenantIdRaw : undefined;
91
+
92
+ const ownerType =
93
+ typeof ownerTypeRaw === "string"
94
+ ? ownerTypeRaw
95
+ : clientMetadata?.ownerType;
96
+
97
+ const ownerId =
98
+ typeof ownerIdRaw === "string"
99
+ ? ownerIdRaw
100
+ : clientMetadata?.ownerId ?? userId;
101
+
102
+ const tenantId = resolveTenantId({
103
+ explicitTenantId,
104
+ userId,
105
+ clientMetadata,
106
+ });
107
+
108
+ const clientProvidedId =
109
+ clientMetadata && typeof clientMetadata.id === "string"
110
+ ? clientMetadata.id
111
+ : undefined;
112
+
113
+ const buffer = Buffer.from(await file.arrayBuffer());
114
+
115
+ const { fileId, metadata } = await saveBufferAsFile(buffer, {
116
+ tenantId: tenantId || "default",
117
+ originalName: file.name || "upload",
118
+ mimeType: file.type || "application/octet-stream",
119
+
120
+ ownerType: ownerType || (userId ? "user" : "system"),
121
+ ownerId: ownerId,
122
+ uploadedBy: userId,
123
+ // 这里的 ownerDbKey 是“业务记录的 dbKey”,比如 profile/key/page key
124
+ ownerDbKey: customKey ?? clientMetadata?.ownerDbKey,
125
+ source: clientMetadata?.source || "user-upload",
126
+ model: clientMetadata?.model,
127
+ prompt: clientMetadata?.prompt,
128
+ tags: clientMetadata?.tags,
129
+
130
+ clientMetadata,
131
+ clientProvidedId,
132
+ });
133
+
134
+ const responseBody = {
135
+ message: "File uploaded successfully",
136
+ fileId,
137
+ metadata,
138
+ };
139
+
140
+ return new Response(JSON.stringify(responseBody), {
141
+ status: 200,
142
+ headers: {
143
+ "Content-Type": "application/json",
144
+ },
145
+ });
146
+ } catch (error: any) {
147
+ console.error("[handleUpload] File upload error:", error);
148
+ return new Response(
149
+ JSON.stringify({
150
+ error: "Failed to upload file",
151
+ details: error?.message || "Unknown error",
152
+ }),
153
+ {
154
+ status: 500,
155
+ headers: { "Content-Type": "application/json" },
156
+ }
157
+ );
158
+ }
159
+ };
@@ -0,0 +1,494 @@
1
+ // 文件路径: packages/database/server/write.ts
2
+
3
+ import { logger } from "auth/server/shared";
4
+ import { deductUserBalance } from "auth/server/deduct";
5
+ import { DataType } from "create/types";
6
+ import serverDb, { ensureServerDbOpen } from "./db";
7
+ import { handleToken, handleCybot } from "./dataHandlers";
8
+ import { handleTransaction } from "./handleTransaction";
9
+ import { handleToken as handleAuthToken } from "auth/server/token";
10
+ import {
11
+ CYBOT_READONLY_MESSAGE,
12
+ isCybotReadOnlyMutation,
13
+ } from "./cybotReadonly";
14
+ import { invalidatePublicAgentsCache } from "ai/agent/server/fetchPublicAgents";
15
+ import { emitSpaceMemberAddedNotification } from "server/handlers/notificationEmitter";
16
+ import { canWriteRecord } from "./writeAuthority";
17
+ import { canWriteSpaceMemberRecord, parseSpaceMemberKey } from "./spaceMemberAuthority";
18
+ import {
19
+ bootstrapReplicatedTable,
20
+ findActiveLiveShareByTable,
21
+ patchShareMeta,
22
+ reconcileReplicaTable,
23
+ replicateSharedTableMutation,
24
+ } from "../../share/server/tableReplication";
25
+
26
+ const getRequestOrigin = (req: any): string => {
27
+ try {
28
+ return new URL(req?.url ?? "").origin.replace(/\/+$/, "");
29
+ } catch {
30
+ return "";
31
+ }
32
+ };
33
+
34
+ const getForwardHeaders = (req: any): Record<string, string> => {
35
+ const headers: Record<string, string> = {
36
+ "Content-Type": "application/json",
37
+ };
38
+ const authHeader =
39
+ typeof req?.headers?.authorization === "string"
40
+ ? req.headers.authorization
41
+ : typeof req?.headers?.Authorization === "string"
42
+ ? req.headers.Authorization
43
+ : "";
44
+ if (authHeader) {
45
+ headers.Authorization = authHeader;
46
+ }
47
+ return headers;
48
+ };
49
+
50
+ const normalizeShareMetaTableOwner = (tableDbKey: string) => {
51
+ const parts = tableDbKey.split("-");
52
+ if (parts.length >= 3 && parts[0] === "meta") {
53
+ return parts[1];
54
+ }
55
+ return "";
56
+ };
57
+
58
+ const normalizeOriginServer = (value: unknown) =>
59
+ typeof value === "string" && value.trim()
60
+ ? value.trim().replace(/\/+$/, "")
61
+ : "";
62
+
63
+ const bootstrapLiveShareFromWrite = async (
64
+ req: any,
65
+ customKey: string,
66
+ data: Record<string, any>,
67
+ actionUserId: string
68
+ ) => {
69
+ if (
70
+ typeof data?.meta?.mode !== "string" ||
71
+ data.meta.mode !== "live" ||
72
+ typeof data?.meta?.tableDbKey !== "string" ||
73
+ typeof data?.type !== "string" ||
74
+ data.type !== DataType.TABLE
75
+ ) {
76
+ return;
77
+ }
78
+
79
+ const tableDbKey = data.meta.tableDbKey;
80
+ const tableOwnerId =
81
+ typeof data.meta.tableOwnerId === "string"
82
+ ? data.meta.tableOwnerId
83
+ : normalizeShareMetaTableOwner(tableDbKey);
84
+ if (!tableOwnerId || !tableDbKey) {
85
+ return;
86
+ }
87
+
88
+ const replicaServers = Array.isArray(data.meta.replicaServers)
89
+ ? data.meta.replicaServers
90
+ .map((server) =>
91
+ typeof server === "string" ? server.replace(/\/+$/, "") : ""
92
+ )
93
+ .filter(Boolean)
94
+ : [];
95
+ if (replicaServers.length === 0) {
96
+ return;
97
+ }
98
+
99
+ const originServer = normalizeOriginServer(data.meta.originServer);
100
+ const requestOrigin = normalizeOriginServer(getRequestOrigin(req));
101
+ if (!originServer || originServer !== requestOrigin) {
102
+ return;
103
+ }
104
+
105
+ await bootstrapReplicatedTable({
106
+ shareDbKey: customKey,
107
+ shareRecord: data,
108
+ tableDbKey,
109
+ tableOwnerId,
110
+ originServer: requestOrigin,
111
+ replicaServers,
112
+ thunkApi: null,
113
+ putRecordToServer: async (server, dbKey, record) => {
114
+ try {
115
+ const response = await fetch(`${server}/api/v1/db/write/`, {
116
+ method: "POST",
117
+ headers: getForwardHeaders(req),
118
+ body: JSON.stringify({
119
+ data: record,
120
+ customKey: dbKey,
121
+ userId: record?.userId ?? record?.tenantId ?? actionUserId,
122
+ }),
123
+ });
124
+ return {
125
+ ok: response.ok,
126
+ status: response.status,
127
+ detail: response.ok ? "" : await response.text(),
128
+ };
129
+ } catch (error: any) {
130
+ return {
131
+ ok: false,
132
+ detail: error?.message ?? "Replica bootstrap request failed",
133
+ };
134
+ }
135
+ },
136
+ patchShareMeta,
137
+ });
138
+ };
139
+
140
+ export const handleWrite = async (req: any, res: any) => {
141
+ // 1. 鉴权:从 token 中还原用户
142
+ req.user = await handleAuthToken(req, res);
143
+
144
+ const { user } = req;
145
+ const actionUserId = user?.userId;
146
+
147
+ const { userId, data, customKey, indexKeys } = req.body ?? {};
148
+
149
+ // 基本参数校验
150
+ if (!data || !customKey) {
151
+ logger.warn({
152
+ event: "write_invalid_payload",
153
+ actionUserId,
154
+ body: req.body,
155
+ });
156
+ return res.status(400).json({
157
+ message: "Invalid payload: missing data or customKey",
158
+ });
159
+ }
160
+
161
+ if (isCybotReadOnlyMutation({ dbKey: customKey, dataType: data.type })) {
162
+ logger.warn({
163
+ event: "cybot_write_blocked",
164
+ actionUserId,
165
+ customKey,
166
+ dataType: data.type,
167
+ });
168
+ return res.status(403).json({
169
+ message: CYBOT_READONLY_MESSAGE,
170
+ error: "cybot_readonly",
171
+ });
172
+ }
173
+
174
+ const saveUserId = userId || data.userId;
175
+ const writeAuthority = canWriteRecord({
176
+ dbKey: customKey,
177
+ actionUserId,
178
+ record: {
179
+ ...data,
180
+ userId: saveUserId,
181
+ },
182
+ });
183
+ const isSpaceMemberWrite = parseSpaceMemberKey(customKey) !== null;
184
+
185
+ // 禁止写入 local 用户数据
186
+ if (saveUserId === "local") {
187
+ logger.warn({
188
+ event: "local_write_rejected",
189
+ actionUserId,
190
+ });
191
+ return res.status(400).json({
192
+ message: "local data is not allowed.",
193
+ });
194
+ }
195
+
196
+ // ⭐ 确保 LevelDB 已打开(后面要 get/put)
197
+ await ensureServerDbOpen();
198
+
199
+ // 校验当前操作用户是否存在
200
+ const userExist = await serverDb.get(`user:${actionUserId}`);
201
+ if (!userExist) {
202
+ logger.warn({
203
+ event: "write_permission_denied",
204
+ actionUserId,
205
+ targetUserId: saveUserId,
206
+ dataType: data.type,
207
+ error: "用户不存在",
208
+ });
209
+ return res.status(403).json({
210
+ message: "操作不被允许:用户不存在",
211
+ error: "用户不存在",
212
+ });
213
+ }
214
+
215
+ const canManageSpaceMember =
216
+ isSpaceMemberWrite &&
217
+ (await canWriteSpaceMemberRecord({
218
+ dbKey: customKey,
219
+ actionUserId,
220
+ }));
221
+
222
+ if ((!writeAuthority.isAllowed && !canManageSpaceMember) || !userExist) {
223
+ logger.warn({
224
+ event: "write_permission_denied",
225
+ actionUserId,
226
+ targetUserId: saveUserId,
227
+ keyOwnerId: writeAuthority.keyOwnerId,
228
+ recordOwnerId: writeAuthority.recordOwnerId,
229
+ canManageSpaceMember,
230
+ dataType: data.type,
231
+ error: `用户 ${actionUserId} 无权写入 key ${customKey}`,
232
+ });
233
+ return res.status(403).json({
234
+ message: `操作不被允许:用户 ${actionUserId} 无权写入 key ${customKey}`,
235
+ error: `无权写入,当前用户:${actionUserId},keyOwner:${writeAuthority.keyOwnerId}, recordOwner:${writeAuthority.recordOwnerId}`,
236
+ });
237
+ }
238
+
239
+ try {
240
+ const dataType: DataType = data.type;
241
+ let result;
242
+
243
+ switch (dataType) {
244
+ case DataType.TRANSACTION: {
245
+ result = await handleTransaction(data, res, customKey, actionUserId);
246
+ break;
247
+ }
248
+
249
+ case DataType.TOKEN: {
250
+ result = await handleToken(data, res, userId, customKey, actionUserId, deductUserBalance);
251
+ break;
252
+ }
253
+
254
+ case DataType.CYBOT: {
255
+ result = await handleCybot(data, res, customKey);
256
+ break;
257
+ }
258
+
259
+ default: {
260
+ // ✅ 统一简单类型的写入逻辑(直接写 KV)
261
+ if (
262
+ dataType === DataType.AGENT ||
263
+ dataType === DataType.MSG ||
264
+ dataType === DataType.DOC ||
265
+ dataType === DataType.DIALOG ||
266
+ dataType === DataType.NOTIFICATION ||
267
+ dataType === DataType.SPACE ||
268
+ dataType === DataType.SETTING ||
269
+ dataType === DataType.TABLE ||
270
+ dataType === DataType.TABLE_ROW ||
271
+ dataType === DataType.EMAIL ||
272
+ dataType === DataType.IMAGE ||
273
+ dataType === DataType.APP
274
+ ) {
275
+ if (dataType === DataType.SPACE) {
276
+ console.log("Creating space with key:", customKey);
277
+ }
278
+
279
+ if (isSpaceMemberWrite && !data.userId) {
280
+ const parsedMemberKey = parseSpaceMemberKey(customKey);
281
+ if (parsedMemberKey) {
282
+ data.userId = parsedMemberKey.memberUserId;
283
+ }
284
+ }
285
+
286
+ // Generic author enrichment (fallback if frontend didn't provide)
287
+ if (user) {
288
+ if (!data.authorName) data.authorName = user.name || user.username || "Anonymous";
289
+ if (!data.authorAvatar) data.authorAvatar = user.avatar;
290
+ }
291
+
292
+ // Write data + optional index keys (frontend-driven)
293
+ if (Array.isArray(indexKeys) && indexKeys.length > 0) {
294
+ // Validate indexKeys — server stays dumb but safe
295
+ const shareToken = customKey.startsWith("share-")
296
+ ? customKey.slice("share-".length)
297
+ : "";
298
+ if (!shareToken) {
299
+ return res.status(400).json({ error: "indexKeys only allowed for share keys" });
300
+ }
301
+ for (const ik of indexKeys) {
302
+ if (typeof ik !== "string" || !ik.startsWith("shareidx-")) {
303
+ return res.status(400).json({ error: "indexKey must start with shareidx-" });
304
+ }
305
+ if (!ik.endsWith(`-${shareToken}`)) {
306
+ return res.status(400).json({ error: "indexKey token mismatch" });
307
+ }
308
+ // Ownership guard: owner-{userId} and creator-{userId} must match authenticated user
309
+ const ownerPrefix = `shareidx-owner-${actionUserId}-`;
310
+ if (ik.startsWith("shareidx-owner-") && !ik.startsWith(ownerPrefix)) {
311
+ return res.status(403).json({ error: "indexKey owner mismatch" });
312
+ }
313
+ const creatorPrefix = `shareidx-community-creator-${actionUserId}-`;
314
+ if (ik.startsWith("shareidx-community-creator-") && !ik.startsWith(creatorPrefix)) {
315
+ return res.status(403).json({ error: "indexKey creator mismatch" });
316
+ }
317
+ }
318
+
319
+ const batch = serverDb.batch();
320
+ batch.put(customKey, data);
321
+ for (const ik of indexKeys) {
322
+ batch.put(ik, customKey);
323
+ }
324
+ await batch.write();
325
+ } else {
326
+ await serverDb.put(customKey, data);
327
+ }
328
+
329
+ if (
330
+ customKey.startsWith("share-") &&
331
+ data?.type === DataType.TABLE
332
+ ) {
333
+ await bootstrapLiveShareFromWrite(req, customKey, data as Record<string, any>, actionUserId);
334
+ }
335
+
336
+ if (isSpaceMemberWrite) {
337
+ await emitSpaceMemberAddedNotification({
338
+ actionUserId,
339
+ customKey,
340
+ data,
341
+ });
342
+ }
343
+
344
+ if (
345
+ (dataType === DataType.TABLE || dataType === DataType.TABLE_ROW) &&
346
+ typeof data?.tenantId === "string" &&
347
+ typeof data?.tableId === "string"
348
+ ) {
349
+ await replicateSharedTableMutation({
350
+ mutation: {
351
+ kind: "write",
352
+ dbKey: customKey,
353
+ data,
354
+ dataType,
355
+ tenantId: data.tenantId,
356
+ tableId: data.tableId,
357
+ },
358
+ originServer: getRequestOrigin(req),
359
+ replicaServers: [],
360
+ findActiveLiveShare: findActiveLiveShareByTable,
361
+ putMutationToReplica: async (server, mutation) => {
362
+ try {
363
+ const response = await fetch(`${server}/api/v1/db/write/`, {
364
+ method: "POST",
365
+ headers: getForwardHeaders(req),
366
+ body: JSON.stringify({
367
+ data: mutation.data,
368
+ customKey: mutation.dbKey,
369
+ userId:
370
+ mutation.data?.userId ??
371
+ mutation.data?.tenantId ??
372
+ actionUserId,
373
+ }),
374
+ });
375
+ return {
376
+ ok: response.ok,
377
+ status: response.status,
378
+ detail: response.ok ? "" : await response.text(),
379
+ };
380
+ } catch (error: any) {
381
+ return {
382
+ ok: false,
383
+ detail: error?.message ?? "Replica write request failed",
384
+ };
385
+ }
386
+ },
387
+ reconcileReplicaTable: async ({
388
+ shareDbKey,
389
+ tableDbKey,
390
+ tableOwnerId,
391
+ replicaServers,
392
+ }) => {
393
+ const resolvedTableDbKey =
394
+ tableDbKey || `meta-${data.tenantId}-${data.tableId}`;
395
+ const resolvedTableOwnerId = tableOwnerId || data.tenantId;
396
+ await reconcileReplicaTable({
397
+ shareDbKey,
398
+ tableDbKey: resolvedTableDbKey,
399
+ tableOwnerId: resolvedTableOwnerId,
400
+ originServer: getRequestOrigin(req),
401
+ replicaServers,
402
+ thunkApi: null,
403
+ putRecordToServer: async (server, dbKey, record) => {
404
+ try {
405
+ const response = await fetch(`${server}/api/v1/db/write/`, {
406
+ method: "POST",
407
+ headers: getForwardHeaders(req),
408
+ body: JSON.stringify({
409
+ data: record,
410
+ customKey: dbKey,
411
+ userId:
412
+ record?.userId ?? record?.tenantId ?? actionUserId,
413
+ }),
414
+ });
415
+ return {
416
+ ok: response.ok,
417
+ status: response.status,
418
+ detail: response.ok ? "" : await response.text(),
419
+ };
420
+ } catch (error: any) {
421
+ return {
422
+ ok: false,
423
+ detail: error?.message ?? "Replica reconcile request failed",
424
+ };
425
+ }
426
+ },
427
+ resetReplicaTable: async (server, tableDbKey) => {
428
+ try {
429
+ const response = await fetch(
430
+ `${server}/api/v1/db/delete/${encodeURIComponent(tableDbKey)}?type=table`,
431
+ {
432
+ method: "DELETE",
433
+ headers: getForwardHeaders(req),
434
+ }
435
+ );
436
+ return {
437
+ ok: response.ok,
438
+ status: response.status,
439
+ detail: response.ok ? "" : await response.text(),
440
+ };
441
+ } catch (error: any) {
442
+ return {
443
+ ok: false,
444
+ detail: error?.message ?? "Replica table reset failed",
445
+ };
446
+ }
447
+ },
448
+ patchShareMeta,
449
+ });
450
+ },
451
+ patchShareMeta,
452
+ });
453
+ }
454
+
455
+ if (customKey.startsWith("agent-pub-") || customKey.startsWith("cybot-pub-")) {
456
+ invalidatePublicAgentsCache();
457
+ }
458
+
459
+ result = res.status(200).json({
460
+ message: "Data written to file successfully.",
461
+ id: customKey,
462
+ dbKey: customKey,
463
+ ...data,
464
+ });
465
+ }
466
+ break;
467
+ }
468
+ }
469
+
470
+ if (result) return result;
471
+
472
+ // data.type 不在已知列表中
473
+ logger.warn({
474
+ event: "write_invalid_data_type",
475
+ actionUserId,
476
+ targetUserId: saveUserId,
477
+ dataType,
478
+ });
479
+
480
+ return res.status(400).json({
481
+ message: "Invalid data type",
482
+ });
483
+ } catch (error) {
484
+ logger.error({
485
+ event: "write_failed",
486
+ error,
487
+ data: { id: customKey, dbKey: customKey, type: data.type },
488
+ });
489
+ return res.status(500).json({
490
+ message: "Failed to write data",
491
+ error,
492
+ });
493
+ }
494
+ };