nolo-cli 0.1.8 → 0.1.9

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 (239) hide show
  1. package/README.md +32 -0
  2. package/agentRuntimeCommands.ts +3 -3
  3. package/ai/agent/_executeModel.ts +118 -0
  4. package/ai/agent/agentSlice.ts +525 -0
  5. package/ai/agent/appWorkingMemory.ts +126 -0
  6. package/ai/agent/avatarUtils.ts +24 -0
  7. package/ai/agent/buildEditingContext.ts +373 -0
  8. package/ai/agent/buildSystemPrompt.ts +532 -0
  9. package/ai/agent/cleanAgentMessages.ts +140 -0
  10. package/ai/agent/cliChatClient.ts +119 -0
  11. package/ai/agent/cliExecutor.ts +733 -0
  12. package/ai/agent/cliPrompt.ts +10 -0
  13. package/ai/agent/contextCompiler.ts +107 -0
  14. package/ai/agent/contextLayerContract.ts +44 -0
  15. package/ai/agent/createAgentSchema.ts +234 -0
  16. package/ai/agent/executeToolCall.ts +58 -0
  17. package/ai/agent/fetchAgentContexts.ts +42 -0
  18. package/ai/agent/generatePrompt.ts +3 -0
  19. package/ai/agent/getFullChatContextKeys.ts +168 -0
  20. package/ai/agent/hooks/fetchPublicAgents.ts +133 -0
  21. package/ai/agent/hooks/useAgentConfig.ts +61 -0
  22. package/ai/agent/hooks/useAgentDialog.ts +35 -0
  23. package/ai/agent/hooks/useAgentFormValidation.ts +202 -0
  24. package/ai/agent/hooks/usePublicAgents.ts +473 -0
  25. package/ai/agent/machineRunPermissions.ts +95 -0
  26. package/ai/agent/persistMessageWithFixedId.ts +37 -0
  27. package/ai/agent/planSlice.ts +259 -0
  28. package/ai/agent/referenceUtils.ts +229 -0
  29. package/ai/agent/runAgentBackground.ts +238 -0
  30. package/ai/agent/runAgentClientLoop.ts +138 -0
  31. package/ai/agent/runtimeGuidance.ts +97 -0
  32. package/ai/agent/runtimeServerBase.ts +37 -0
  33. package/ai/agent/server/fetchPublicAgents.ts +128 -0
  34. package/ai/agent/startParallelAgentStreams.ts +424 -0
  35. package/ai/agent/startupProtocol.ts +53 -0
  36. package/ai/agent/streamAgentChatTurn.ts +1278 -0
  37. package/ai/agent/streamAgentChatTurnUtils.ts +738 -0
  38. package/ai/agent/types.ts +71 -0
  39. package/ai/agent/utils/imageOutput.ts +33 -0
  40. package/ai/agent/utils/sortUtils.ts +250 -0
  41. package/ai/agent/web/referencePickerUtils.ts +146 -0
  42. package/ai/ai.locale.ts +1075 -0
  43. package/ai/chat/accumulateToolCallChunks.ts +95 -0
  44. package/ai/chat/fetchUtils.native.ts +276 -0
  45. package/ai/chat/fetchUtils.ts +153 -0
  46. package/ai/chat/parseApiError.ts +64 -0
  47. package/ai/chat/parseMultilineSSE.ts +95 -0
  48. package/ai/chat/sendOpenAICompletionsRequest.native.ts +682 -0
  49. package/ai/chat/sendOpenAICompletionsRequest.ts +703 -0
  50. package/ai/chat/sendOpenAIResponseRequest.ts +491 -0
  51. package/ai/chat/shouldUseServerProxy.ts +18 -0
  52. package/ai/chat/sseClient.native.ts +91 -0
  53. package/ai/chat/sseClient.ts +67 -0
  54. package/ai/chat/streamReader.native.ts +31 -0
  55. package/ai/chat/streamReader.ts +62 -0
  56. package/ai/chat/updateTotalUsage.ts +72 -0
  57. package/ai/context/buildReferenceContext.ts +437 -0
  58. package/ai/context/calculateContextUsage.ts +133 -0
  59. package/ai/context/retention.ts +165 -0
  60. package/ai/context/tokenUtils.ts +78 -0
  61. package/ai/index.ts +1 -0
  62. package/ai/llm/calculateGeminiImageTokens.ts +57 -0
  63. package/ai/llm/deepinfra.ts +28 -0
  64. package/ai/llm/fireworks.ts +50 -0
  65. package/ai/llm/generateRequestBody.ts +165 -0
  66. package/ai/llm/getModelContextWindow.ts +84 -0
  67. package/ai/llm/getNoloKey.ts +31 -0
  68. package/ai/llm/getPricing.ts +199 -0
  69. package/ai/llm/hooks/useModelPricing.ts +75 -0
  70. package/ai/llm/imagePricing.ts +40 -0
  71. package/ai/llm/isResponseAPIModel.ts +13 -0
  72. package/ai/llm/mimo.ts +71 -0
  73. package/ai/llm/mistral.ts +22 -0
  74. package/ai/llm/modelAvatar.ts +427 -0
  75. package/ai/llm/models.ts +45 -0
  76. package/ai/llm/openrouterModels.ts +269 -0
  77. package/ai/llm/providers.ts +306 -0
  78. package/ai/llm/reasoningModels.ts +28 -0
  79. package/ai/llm/types.ts +59 -0
  80. package/ai/llm/usageRequestOptions.ts +59 -0
  81. package/ai/memory/capture.ts +148 -0
  82. package/ai/memory/consolidate.ts +104 -0
  83. package/ai/memory/delete.ts +147 -0
  84. package/ai/memory/overlay.ts +84 -0
  85. package/ai/memory/query.ts +38 -0
  86. package/ai/memory/queryShared.ts +160 -0
  87. package/ai/memory/rank.ts +105 -0
  88. package/ai/memory/recentRelationshipRecap.ts +249 -0
  89. package/ai/memory/remember.ts +167 -0
  90. package/ai/memory/runtime.ts +76 -0
  91. package/ai/memory/store.ts +20 -0
  92. package/ai/memory/storeShared.ts +76 -0
  93. package/ai/memory/types.ts +46 -0
  94. package/ai/memory/understanding.ts +349 -0
  95. package/ai/memory/understandingGreeting.ts +264 -0
  96. package/ai/messages/type.ts +20 -0
  97. package/ai/policy/personalizationDialog.ts +333 -0
  98. package/ai/policy/runtimePolicy.ts +440 -0
  99. package/ai/policy/selfUpdateFields.ts +48 -0
  100. package/ai/policy/types.ts +64 -0
  101. package/ai/skills/referenceRuntime.ts +274 -0
  102. package/ai/skills/skillDiagnostics.ts +251 -0
  103. package/ai/skills/skillDocBuilder.ts +139 -0
  104. package/ai/skills/skillDocProtocol.ts +434 -0
  105. package/ai/skills/skillReferenceSummary.ts +63 -0
  106. package/ai/skills/skillSummaryMarker.ts +26 -0
  107. package/ai/token/calculatePrice.ts +544 -0
  108. package/ai/token/db.ts +98 -0
  109. package/ai/token/externalToolCost.ts +330 -0
  110. package/ai/token/hooks/useRecords.ts +65 -0
  111. package/ai/token/missingUsageEstimate.ts +42 -0
  112. package/ai/token/modelUsageQuery.ts +252 -0
  113. package/ai/token/normalizeUsage.ts +84 -0
  114. package/ai/token/openaiImageGenerationUsage.ts +56 -0
  115. package/ai/token/prepareTokenUsageData.ts +88 -0
  116. package/ai/token/query.ts +88 -0
  117. package/ai/token/queryUserTokens.ts +59 -0
  118. package/ai/token/resolveBillingTarget.ts +52 -0
  119. package/ai/token/saveTokenRecord.ts +53 -0
  120. package/ai/token/serverDialogProjection.ts +78 -0
  121. package/ai/token/serverTokenWriter.ts +143 -0
  122. package/ai/token/stats.ts +21 -0
  123. package/ai/token/tokenThunks.ts +24 -0
  124. package/ai/token/types.ts +93 -0
  125. package/ai/tools/agent/agentTools.ts +176 -0
  126. package/ai/tools/agent/agentUpdateShared.ts +311 -0
  127. package/ai/tools/agent/callAgentTool.ts +139 -0
  128. package/ai/tools/agent/createAgentTool.ts +512 -0
  129. package/ai/tools/agent/createDialogTool.ts +69 -0
  130. package/ai/tools/agent/createSkillAgentTool.ts +62 -0
  131. package/ai/tools/agent/parallelBudget.ts +221 -0
  132. package/ai/tools/agent/presets/appBuilderPreset.ts +145 -0
  133. package/ai/tools/agent/runLlmTool.ts +96 -0
  134. package/ai/tools/agent/runStreamingAgentTool.ts +73 -0
  135. package/ai/tools/agent/skillAgentArgs.ts +106 -0
  136. package/ai/tools/agent/skillAgentPreset.ts +89 -0
  137. package/ai/tools/agent/streamParallelAgentsTool.ts +122 -0
  138. package/ai/tools/agent/updateAgentTool.ts +96 -0
  139. package/ai/tools/agent/updateSelfTool.ts +113 -0
  140. package/ai/tools/amazonProductScraperTool.ts +86 -0
  141. package/ai/tools/apifyActorClient.ts +45 -0
  142. package/ai/tools/appEditGuard.ts +372 -0
  143. package/ai/tools/appReadSnapshot.ts +153 -0
  144. package/ai/tools/appTools.ts +1549 -0
  145. package/ai/tools/applyEditTool.ts +256 -0
  146. package/ai/tools/applyLineEditsTool.ts +312 -0
  147. package/ai/tools/browserTools/click.ts +33 -0
  148. package/ai/tools/browserTools/closeSession.ts +29 -0
  149. package/ai/tools/browserTools/common.ts +27 -0
  150. package/ai/tools/browserTools/openSession.ts +48 -0
  151. package/ai/tools/browserTools/readContent.ts +38 -0
  152. package/ai/tools/browserTools/selectOption.ts +46 -0
  153. package/ai/tools/browserTools/typeText.ts +42 -0
  154. package/ai/tools/category/createCategoryTool.ts +66 -0
  155. package/ai/tools/category/queryContentsByCategoryTool.ts +69 -0
  156. package/ai/tools/category/updateContentCategoryTool.ts +75 -0
  157. package/ai/tools/cfBrowserTools.ts +319 -0
  158. package/ai/tools/cfSpeechToTextTool.ts +49 -0
  159. package/ai/tools/checkEnvTool.ts +65 -0
  160. package/ai/tools/cloudflareCrawlTool.ts +289 -0
  161. package/ai/tools/codeSearchTool.ts +111 -0
  162. package/ai/tools/codeTools.ts +101 -0
  163. package/ai/tools/createDocTool.ts +132 -0
  164. package/ai/tools/createPlanTool.ts +999 -0
  165. package/ai/tools/createSkillDocTool.ts +155 -0
  166. package/ai/tools/createWorkflowTool.ts +154 -0
  167. package/ai/tools/deepseekOcrTool.ts +34 -0
  168. package/ai/tools/delayTool.ts +31 -0
  169. package/ai/tools/deleteSpacesTool.ts +325 -0
  170. package/ai/tools/deleteSpacesToolModel.ts +159 -0
  171. package/ai/tools/devReloadUtils.ts +29 -0
  172. package/ai/tools/dialogMessageSearch.ts +137 -0
  173. package/ai/tools/doctorSkillTool.ts +72 -0
  174. package/ai/tools/ecommerceScraperTool.ts +86 -0
  175. package/ai/tools/emailTools.ts +549 -0
  176. package/ai/tools/evalSkillTool.ts +92 -0
  177. package/ai/tools/exaSearchTool.ts +64 -0
  178. package/ai/tools/execBashTool.ts +379 -0
  179. package/ai/tools/executeSqlTool.ts +192 -0
  180. package/ai/tools/fetchWebpageSupport.ts +309 -0
  181. package/ai/tools/fetchWebpageTool.ts +84 -0
  182. package/ai/tools/geminiImagePreviewTool.ts +361 -0
  183. package/ai/tools/generateDocxTool.ts +215 -0
  184. package/ai/tools/googleSearchScraperTool.ts +106 -0
  185. package/ai/tools/importDataTool.ts +133 -0
  186. package/ai/tools/importSkillTool.ts +162 -0
  187. package/ai/tools/index.ts +1858 -0
  188. package/ai/tools/listFilesTool.ts +82 -0
  189. package/ai/tools/listUserSpacesTool.ts +113 -0
  190. package/ai/tools/modelUsageTools.ts +142 -0
  191. package/ai/tools/olmOcrTool.ts +34 -0
  192. package/ai/tools/openaiImageTool.ts +218 -0
  193. package/ai/tools/paddleOcrTool.ts +34 -0
  194. package/ai/tools/prepareTools.ts +23 -0
  195. package/ai/tools/readDocTool.ts +84 -0
  196. package/ai/tools/readFileTool.ts +211 -0
  197. package/ai/tools/readTool.ts +163 -0
  198. package/ai/tools/readXPostTool.ts +233 -0
  199. package/ai/tools/rememberMemoryTool.ts +84 -0
  200. package/ai/tools/remotionVideoTool.ts +151 -0
  201. package/ai/tools/searchDialogMessagesTool.ts +222 -0
  202. package/ai/tools/searchRepoTool.ts +115 -0
  203. package/ai/tools/searchWorkspaceTool.ts +259 -0
  204. package/ai/tools/skillFollowup.ts +86 -0
  205. package/ai/tools/surfWeatherTool.ts +169 -0
  206. package/ai/tools/table/addTableRowTool.ts +217 -0
  207. package/ai/tools/table/createTableTool.ts +315 -0
  208. package/ai/tools/table/rowTools.ts +366 -0
  209. package/ai/tools/table/schemaTools.ts +244 -0
  210. package/ai/tools/table/shareTableTool.ts +148 -0
  211. package/ai/tools/table/toolShared.ts +129 -0
  212. package/ai/tools/toolApiClient.ts +198 -0
  213. package/ai/tools/toolNameAliases.ts +57 -0
  214. package/ai/tools/toolResultError.ts +42 -0
  215. package/ai/tools/toolRunSlice.ts +303 -0
  216. package/ai/tools/toolSchemaCompatibility.ts +53 -0
  217. package/ai/tools/toolVisibility.ts +4 -0
  218. package/ai/tools/types.ts +20 -0
  219. package/ai/tools/uiAskChoiceTool.ts +104 -0
  220. package/ai/tools/updateContentTitleTool.ts +84 -0
  221. package/ai/tools/updateDocTool.ts +105 -0
  222. package/ai/tools/updateUserPreferenceProfileTool.ts +145 -0
  223. package/ai/tools/whisperTool.ts +77 -0
  224. package/ai/tools/writeFileTool.ts +210 -0
  225. package/ai/tools/youtubeScraperTool.ts +116 -0
  226. package/ai/tools/ziweiChartTool.ts +678 -0
  227. package/ai/types.ts +55 -0
  228. package/ai/workflow/workflowExecutor.ts +323 -0
  229. package/ai/workflow/workflowSlice.ts +73 -0
  230. package/ai/workflow/workflowTypes.ts +106 -0
  231. package/client/compactDialog.ts +222 -0
  232. package/connector-experimental/capabilities.ts +73 -0
  233. package/connector-experimental/codexBinary.ts +41 -0
  234. package/connector-experimental/heartbeatLoop.ts +22 -0
  235. package/connector-experimental/index.ts +5 -0
  236. package/connector-experimental/machineInfo.ts +46 -0
  237. package/connector-experimental/protocol.ts +54 -0
  238. package/machineCommands.ts +4 -4
  239. package/package.json +8 -6
package/README.md CHANGED
@@ -171,3 +171,35 @@ Future product-direction examples for the broader TUI command model:
171
171
 
172
172
  See [`docs/nolo-cli-tui.md`](../../docs/nolo-cli-tui.md) for the product and
173
173
  technical direction.
174
+
175
+ ## Building for Publish
176
+
177
+ The CLI is developed in a monorepo with workspace dependencies (`ai` and
178
+ `connector-experimental`). To generate a publish-safe package that can be
179
+ installed via npm outside the monorepo:
180
+
181
+ ```bash
182
+ bun run build:publish
183
+ ```
184
+
185
+ This creates a `dist/` directory with:
186
+ - All source files from the `files` array in package.json
187
+ - Inlined workspace dependencies (copied as nested directories)
188
+ - A modified package.json with workspace dependencies stripped
189
+
190
+ The `dist/` directory can be published to npm:
191
+
192
+ ```bash
193
+ cd dist
194
+ npm publish
195
+ ```
196
+
197
+ Key differences between repo-local and published versions:
198
+ - **Repo-local**: Runs from source (`packages/cli/index.ts`) with workspace
199
+ dependencies resolved by the monorepo
200
+ - **Published**: Runs from dist (`dist/index.ts`) with workspace dependencies
201
+ inlined as nested directories
202
+
203
+ Both versions use the same Bun runtime and TypeScript source files. The build
204
+ process does not transpile; it only restructures the package for standalone use.
205
+
@@ -1,10 +1,10 @@
1
- import type { MachineHeartbeat } from "connector-experimental/protocol";
2
- import { detectMachineInfo } from "connector-experimental/machineInfo";
1
+ import type { MachineHeartbeat } from "./connector-experimental/protocol";
2
+ import { detectMachineInfo } from "./connector-experimental/machineInfo";
3
3
  import {
4
4
  assertMachineRunAllowed,
5
5
  buildMachinePermissionPromptBlock,
6
6
  resolveMachineRunPermissionPolicy,
7
- } from "../ai/agent/machineRunPermissions";
7
+ } from "./ai/agent/machineRunPermissions";
8
8
  import { resolveConnectorWebSocketTarget } from "./connectorWebSocketTarget";
9
9
  import { DEFAULT_NOLO_SERVER_URL } from "./defaultServer";
10
10
 
@@ -0,0 +1,118 @@
1
+ import { RootState } from "app/store";
2
+ import { Agent, Message } from "app/types";
3
+ import { selectCurrentDialogConfig } from "chat/dialog/dialogSlice";
4
+ import { read } from "database/dbSlice";
5
+ import { fetchAgentContexts } from "ai/agent/fetchAgentContexts";
6
+ import { filterAndCleanMessages } from "integrations/openai/filterAndCleanMessages";
7
+ import { selectAllMsgs } from "chat/messages/messageSlice";
8
+ import { generateRequestBody } from "ai/llm/generateRequestBody";
9
+ import { getApiEndpoint } from "ai/llm/providers";
10
+ import { selectCurrentServer } from "app/settings/settingSlice";
11
+ import { selectCurrentToken } from "auth/authSlice";
12
+
13
+ import { sendOpenAICompletionsRequest } from "../chat/sendOpenAICompletionsRequest";
14
+ import { performFetchRequest } from "../chat/fetchUtils";
15
+ import { updateTokensAction } from "chat/dialog/actions/updateTokensAction";
16
+ import { extractCustomId } from "core/prefix";
17
+
18
+ export const _executeModel = async (
19
+ options: {
20
+ isStreaming: boolean;
21
+ withAgentContext: boolean;
22
+ withChatHistory: boolean;
23
+ agentConfigOverrides?: Record<string, any>;
24
+ },
25
+ args: {
26
+ llmConfig?: Partial<Agent> & Pick<Agent, "provider" | "model">;
27
+ agentKey?: string;
28
+ agentConfig?: Partial<Agent> & Pick<Agent, "provider" | "model">;
29
+ content: any;
30
+ parentMessageId?: string;
31
+ billingDialogKey?: string;
32
+ },
33
+ thunkApi: any
34
+ ) => {
35
+ const { isStreaming, withAgentContext, withChatHistory, agentConfigOverrides } = options;
36
+ const { getState, dispatch, rejectWithValue } = thunkApi;
37
+ const { content } = args;
38
+ const state = getState() as RootState;
39
+
40
+ let agentConfig: Partial<Agent> & Pick<Agent, "provider" | "model">;
41
+ if (args.llmConfig) {
42
+ agentConfig = args.llmConfig;
43
+ } else if (args.agentConfig) {
44
+ agentConfig = args.agentConfig;
45
+ } else {
46
+ const agentKey = args.agentKey || selectCurrentDialogConfig(state)?.cybots?.[0];
47
+ if (!agentKey) {
48
+ const msg = "Model execution failed: No llmConfig, agentConfig, or agentKey provided.";
49
+ console.error(msg);
50
+ return rejectWithValue(msg);
51
+ }
52
+ try {
53
+ agentConfig = await dispatch(read({ dbKey: agentKey })).unwrap();
54
+ } catch (error: any) {
55
+ console.error(`_executeModel failed to load agent [${agentKey}]`, error);
56
+ return rejectWithValue(error.message);
57
+ }
58
+ }
59
+
60
+ try {
61
+ const resolvedConfig = agentConfigOverrides
62
+ ? { ...agentConfig, ...agentConfigOverrides }
63
+ : agentConfig;
64
+ const agentContexts = withAgentContext
65
+ ? await fetchAgentContexts(resolvedConfig.references, dispatch)
66
+ : {};
67
+
68
+ let messages: Message[];
69
+ if (withChatHistory) {
70
+ messages = filterAndCleanMessages(selectAllMsgs(state));
71
+ messages.push({ role: "user", content: args.content });
72
+ } else {
73
+ messages = [{ role: "user", content: args.content }];
74
+ }
75
+
76
+ const bodyData = generateRequestBody({
77
+ agentConfig: resolvedConfig,
78
+ messages,
79
+ userInput: content,
80
+ contexts: agentContexts,
81
+ });
82
+ bodyData.stream = isStreaming;
83
+
84
+ if (isStreaming) {
85
+ await sendOpenAICompletionsRequest({
86
+ bodyData,
87
+ agentConfig: resolvedConfig,
88
+ thunkApi,
89
+ dialogKey: selectCurrentDialogConfig(state)?.dbKey,
90
+ parentMessageId: args.parentMessageId,
91
+ });
92
+ } else {
93
+ const response = await performFetchRequest({
94
+ agentConfig: resolvedConfig,
95
+ api: getApiEndpoint(resolvedConfig),
96
+ bodyData,
97
+ currentServer: selectCurrentServer(state),
98
+ token: selectCurrentToken(state),
99
+ });
100
+ const result = await response.json();
101
+ if (args.billingDialogKey && result?.usage) {
102
+ await updateTokensAction(
103
+ {
104
+ dialogId: extractCustomId(args.billingDialogKey),
105
+ dialogKey: args.billingDialogKey,
106
+ usage: result.usage,
107
+ agentConfig: resolvedConfig,
108
+ },
109
+ thunkApi
110
+ );
111
+ }
112
+ return result.choices[0].message.content;
113
+ }
114
+ } catch (error: any) {
115
+ console.error(`_executeModel failed`, error);
116
+ return rejectWithValue(error.message);
117
+ }
118
+ };
@@ -0,0 +1,525 @@
1
+ // 路径: ai/agent/agentSlice.ts
2
+
3
+ import { asyncThunkCreator, buildCreateSlice, type PayloadAction } from "@reduxjs/toolkit";
4
+ import type { Agent, ReferenceItem } from "app/types";
5
+
6
+ import { write, patch, remove } from "database/dbSlice";
7
+ import { createCybotKey, createAgentKey } from "database/keys";
8
+ import { DataType } from "create/types";
9
+ import { ulid } from "ulid";
10
+ import type { FormData as AgentFormData } from "ai/agent/createAgentSchema";
11
+
12
+ const createSliceWithThunks = buildCreateSlice({
13
+ creators: { asyncThunk: asyncThunkCreator },
14
+ });
15
+
16
+ /** Slice State 定义 */
17
+ interface AgentState {
18
+ pubCybots: {
19
+ loading: boolean;
20
+ error: string | null;
21
+ data: Agent[];
22
+ };
23
+ }
24
+
25
+ /** runLlm 参数(通用 LLM 调用) */
26
+ interface RunLlmArgs {
27
+ /** 直接传入内置 llmConfig,完全不依赖 Agent 数据 */
28
+ llmConfig?: Partial<Agent> & Pick<Agent, "provider" | "model">;
29
+ agentKey?: string;
30
+ /** 兼容旧调用:直接传入 agentConfig,跳过 DB 读取 */
31
+ agentConfig?: Partial<Agent> & Pick<Agent, "provider" | "model">;
32
+ content: unknown;
33
+ isStreaming?: boolean;
34
+ parentMessageId?: string;
35
+ /** 覆盖 Agent 配置中的 system prompt */
36
+ systemPromptOverride?: string;
37
+ /** 覆盖 Agent 配置中的工具列表(tool id 数组) */
38
+ toolsOverride?: string[];
39
+ billingDialogKey?: string;
40
+ }
41
+
42
+ /** runAgent 参数(通用 Agent 调用,多轮工具循环) */
43
+ interface RunAgentArgs {
44
+ agentKey: string;
45
+ content: unknown;
46
+ parentMessageId?: string;
47
+ billingDialogKey?: string;
48
+ }
49
+
50
+ /** createAgent 参数(新建 Agent) */
51
+ interface CreateAgentArgs {
52
+ userId: string;
53
+ formData: AgentFormData; // 已通过表单或 Tool 构造的完整数据
54
+ spaceId?: string; // 可选的空间 ID,如果提供,则将 Agent 添加到该空间
55
+ }
56
+
57
+ /** updateAgent 参数(更新 Agent,支持部分字段 patch) */
58
+ interface UpdateAgentArgs {
59
+ userId: string;
60
+ agentId: string; // 纯 id(不带 db path 前缀)
61
+ formData: Partial<AgentFormData>; // 允许只传需要修改的字段
62
+ previousAgent?: Partial<Agent>; // UI 编辑时可以传,用来保持公共副本同步
63
+ }
64
+
65
+ const initialState: AgentState = {
66
+ pubCybots: {
67
+ loading: false,
68
+ error: null,
69
+ data: [],
70
+ },
71
+ };
72
+
73
+ const normalizeAgentReferences = (references: any[]): ReferenceItem[] => {
74
+ if (!Array.isArray(references)) return [];
75
+ return references.map((ref) => ({
76
+ dbKey: ref.dbKey || "",
77
+ title: ref.title || "",
78
+ type: ref.type === "page" ? "knowledge" : ref.type || "knowledge",
79
+ }));
80
+ };
81
+
82
+ /**
83
+ * 创建场景:表单数据 -> 持久化数据(全量)
84
+ */
85
+ const processAgentCreateForm = (formData: AgentFormData) => {
86
+ const isPublic = !!formData.isPublic;
87
+ const machineId = typeof (formData as any).machineId === "string"
88
+ ? (formData as any).machineId.trim()
89
+ : "";
90
+
91
+ const result: any = {
92
+ ...formData,
93
+ // tags: "a, b" -> ["a", "b"]
94
+ tags: formData.tags
95
+ ? formData.tags
96
+ .split(",")
97
+ .map((s) => s.trim())
98
+ .filter(Boolean)
99
+ : [],
100
+ // 归一化 references
101
+ references: normalizeAgentReferences(formData.references || []),
102
+ // 非公开时,强制清空白名单,避免脏数据
103
+ whitelist: isPublic ? formData.whitelist || [] : [],
104
+ };
105
+
106
+ delete result.machineId;
107
+ if (formData.apiSource === "cli" && machineId) {
108
+ result.runtimeBinding = {
109
+ ...(result.runtimeBinding && typeof result.runtimeBinding === "object"
110
+ ? result.runtimeBinding
111
+ : {}),
112
+ machineId,
113
+ };
114
+ }
115
+
116
+ return result;
117
+ };
118
+
119
+ /**
120
+ * 更新场景:部分表单字段 -> patch changes
121
+ * - 只对传进来的字段做转换 / 归一化
122
+ * - 未出现在 formData 里的字段一律不修改
123
+ */
124
+ const processAgentUpdateChanges = (data: Partial<AgentFormData>) => {
125
+ const changes: any = {};
126
+
127
+ // 基本字符串字段
128
+ if ("name" in data) {
129
+ changes.name = String(data.name ?? "").trim();
130
+ }
131
+ if ("model" in data) {
132
+ changes.model = (data.model ?? "").trim();
133
+ }
134
+ if ("provider" in data) {
135
+ changes.provider = (data.provider ?? "").trim();
136
+ }
137
+ if ("prompt" in data) {
138
+ changes.prompt = (data.prompt ?? "").trim();
139
+ }
140
+ if ("introduction" in data) {
141
+ changes.introduction = (data.introduction ?? "").trim();
142
+ }
143
+ if ("customProviderUrl" in data) {
144
+ changes.customProviderUrl = (data.customProviderUrl ?? "").trim();
145
+ }
146
+ if ("apiKey" in data) {
147
+ changes.apiKey = (data.apiKey ?? "").trim();
148
+ }
149
+
150
+ // 简单标志位
151
+ if ("hasVision" in data && data.hasVision !== undefined) {
152
+ changes.hasVision = !!data.hasVision;
153
+ }
154
+ if ("apiSource" in data && data.apiSource) {
155
+ changes.apiSource = data.apiSource;
156
+ }
157
+ if ("cliProvider" in data) {
158
+ changes.cliProvider = (data as any).cliProvider || "";
159
+ }
160
+ if ("machineId" in data) {
161
+ const machineId = String((data as any).machineId ?? "").trim();
162
+ if (machineId) {
163
+ changes.runtimeBinding = {
164
+ ...((changes.runtimeBinding && typeof changes.runtimeBinding === "object")
165
+ ? changes.runtimeBinding
166
+ : {}),
167
+ machineId,
168
+ };
169
+ } else {
170
+ changes.runtimeBinding = null;
171
+ }
172
+ }
173
+ if ("useServerProxy" in data && data.useServerProxy !== undefined) {
174
+ changes.useServerProxy = !!data.useServerProxy;
175
+ }
176
+
177
+ // greeting / tools 直接透传
178
+ if ("greeting" in data) {
179
+ changes.greeting = data.greeting as any;
180
+ }
181
+ if ("tools" in data) {
182
+ changes.tools = Array.isArray(data.tools) ? data.tools.slice() : [];
183
+ }
184
+
185
+ // 数值字段
186
+ const numericKeys: (keyof AgentFormData)[] = [
187
+ "inputPrice",
188
+ "outputPrice",
189
+ "temperature",
190
+ "top_p",
191
+ "frequency_penalty",
192
+ "presence_penalty",
193
+ "max_tokens",
194
+ ];
195
+ numericKeys.forEach((key) => {
196
+ if (key in data) {
197
+ const raw = data[key];
198
+ if (raw === undefined || raw === null) {
199
+ changes[key] = raw as any;
200
+ } else {
201
+ const num = Number(raw as any);
202
+ changes[key] = Number.isNaN(num) ? raw : num;
203
+ }
204
+ }
205
+ });
206
+
207
+ // ⚠ 唯一实质改动:reasoning_effort 允许传 null,用于“清空该字段”
208
+ // 原来是:if ("reasoning_effort" in data && data.reasoning_effort) { ... }
209
+ // 这样会把 null 吞掉,无法让后端 patch + deepMerge 删除字段。
210
+ if ("reasoning_effort" in data) {
211
+ changes.reasoning_effort = (data as any).reasoning_effort;
212
+ }
213
+
214
+ // tags: string 或 string[]
215
+ if ("tags" in data) {
216
+ const raw = (data as any).tags;
217
+ let arr: string[] = [];
218
+ if (Array.isArray(raw)) {
219
+ arr = raw.map((s) => String(s || "").trim()).filter(Boolean);
220
+ } else if (typeof raw === "string") {
221
+ arr = raw
222
+ .split(",")
223
+ .map((s) => s.trim())
224
+ .filter(Boolean);
225
+ }
226
+ changes.tags = arr;
227
+ }
228
+
229
+ // references
230
+ if ("references" in data) {
231
+ changes.references = normalizeAgentReferences(
232
+ ((data.references as any) || []) as any[]
233
+ );
234
+ }
235
+
236
+ // whitelist + isPublic
237
+ if ("whitelist" in data) {
238
+ changes.whitelist = (data.whitelist as string[]) || [];
239
+ }
240
+
241
+ if ("isPublic" in data) {
242
+ changes.isPublic = !!data.isPublic;
243
+ if (!changes.isPublic) {
244
+ // 一旦改为私有,强制清空白名单
245
+ changes.whitelist = [];
246
+ }
247
+ }
248
+
249
+ return changes;
250
+ };
251
+
252
+ export const slice = createSliceWithThunks({
253
+ // 注意:保持 name = "cybot",以兼容原有 Redux state 结构
254
+ name: "cybot",
255
+ initialState,
256
+ reducers: (create) => ({
257
+ /**
258
+ * SSR 首屏:服务端预取公开 Agent 列表后注入,走 __PRELOADED_STATE__ 链路
259
+ */
260
+ setSSRPublicAgents: create.reducer(
261
+ (state, action: PayloadAction<Agent[]>) => {
262
+ state.pubCybots.data = Array.isArray(action.payload) ? action.payload : [];
263
+ state.pubCybots.loading = false;
264
+ state.pubCybots.error = null;
265
+ }
266
+ ),
267
+
268
+ /**
269
+ * 通用 LLM 调用(不带 Agent 上下文 / 历史)
270
+ */
271
+ runLlm: create.asyncThunk(async (args: RunLlmArgs, thunkApi) => {
272
+ const overrides: Record<string, any> = {};
273
+ if (args.systemPromptOverride !== undefined) overrides.prompt = args.systemPromptOverride;
274
+ if (args.toolsOverride !== undefined) overrides.tools = args.toolsOverride;
275
+ const { _executeModel } = await import("ai/agent/_executeModel");
276
+ return _executeModel(
277
+ {
278
+ isStreaming: args.isStreaming ?? false,
279
+ withAgentContext: false,
280
+ withChatHistory: false,
281
+ agentConfigOverrides: Object.keys(overrides).length ? overrides : undefined,
282
+ },
283
+ args,
284
+ thunkApi
285
+ );
286
+ }),
287
+
288
+ /**
289
+ * 通用 Agent 调用(带 Agent 上下文,多轮工具循环)
290
+ *
291
+ * 使用客户端 runAgentClientLoop:
292
+ * - 每轮调用 LLM(非流式)
293
+ * - 遇到 tool_calls 时通过 findToolExecutor 本地执行工具
294
+ * - 循环直到无工具调用或触发其他运行时停止条件
295
+ */
296
+ runAgent: create.asyncThunk(async (args: RunAgentArgs, thunkApi) => {
297
+ const { runAgentClientLoop } = await import("ai/agent/runAgentClientLoop");
298
+ const { content: loopContent, toolCallCount } = await runAgentClientLoop(
299
+ {
300
+ agentKey: args.agentKey,
301
+ content: args.content,
302
+ parentMessageId: args.parentMessageId,
303
+ billingDialogKey: args.billingDialogKey,
304
+ },
305
+ thunkApi
306
+ );
307
+ return loopContent;
308
+ }),
309
+
310
+ /**
311
+ * 聊天轮次流式 Agent 调用
312
+ */
313
+ streamAgentChatTurn: create.asyncThunk(async (args: any, thunkApi) => {
314
+ const { streamAgentChatTurnHandler } = await import("ai/agent/streamAgentChatTurn");
315
+ return streamAgentChatTurnHandler(args, thunkApi);
316
+ }),
317
+
318
+ /**
319
+ * 创建 Agent:
320
+ * - 写入用户私有路径
321
+ * - 如 isPublic=true,则同时写入公共路径
322
+ * - 返回完整 Agent 对象(包含 id / meta 字段)
323
+ */
324
+ createAgent: create.asyncThunk(
325
+ async ({ userId, formData, spaceId }: CreateAgentArgs, thunkApi) => {
326
+ const processed = processAgentCreateForm(formData);
327
+
328
+ const now = Date.now();
329
+ const id = ulid();
330
+
331
+ const privateKey = createAgentKey.private(userId, id);
332
+ const publicKey = createAgentKey.public(id);
333
+
334
+ const agent: Agent = {
335
+ ...(processed as any),
336
+ id,
337
+ type: DataType.AGENT,
338
+ userId,
339
+ createdAt: now,
340
+ updatedAt: now,
341
+ dialogCount: 0,
342
+ messageCount: 0,
343
+ tokenCount: 0,
344
+ spaceId: spaceId, // 记录 spaceId
345
+ };
346
+
347
+ // 写入私有副本
348
+ await thunkApi
349
+ .dispatch(
350
+ write({
351
+ data: agent,
352
+ customKey: privateKey,
353
+ })
354
+ )
355
+ .unwrap();
356
+
357
+ // 如需公开,再写入公共副本
358
+ if (agent.isPublic) {
359
+ await thunkApi
360
+ .dispatch(
361
+ write({
362
+ data: agent,
363
+ customKey: publicKey,
364
+ })
365
+ )
366
+ .unwrap();
367
+ }
368
+
369
+ return agent;
370
+ }
371
+ ),
372
+
373
+ /**
374
+ * 更新 Agent(支持局部字段 patch):
375
+ * - patch 私有副本
376
+ * - 如提供 previousAgent,则同步更新 / 删除公共副本
377
+ *
378
+ * 注意:
379
+ * - Tool 场景下一般不提供 previousAgent,此时只保证私有副本被更新;
380
+ * 公共副本(应用市场)不做强一致保证。
381
+ */
382
+ updateAgent: create.asyncThunk(
383
+ async (
384
+ { userId, agentId, formData, previousAgent }: UpdateAgentArgs,
385
+ thunkApi
386
+ ) => {
387
+ const normalizedAgentId = (() => {
388
+ const raw = agentId.trim();
389
+ if (raw.startsWith("agent-") || raw.startsWith("cybot-")) {
390
+ const parts = raw.split("-");
391
+ if (parts.length >= 3) return parts[parts.length - 1];
392
+ }
393
+ return raw;
394
+ })();
395
+
396
+ // 【兼容层】双前缀处理:
397
+ // 1. 如果 agentId 是以 cybot- 开头,或者 previousAgent.type 是 cybot,说明这是存量旧数据。
398
+ // 2. 存量数据必须使用 createCybotKey 才能正确定位到数据库中的位置。
399
+ // 3. 新数据则默认使用 createAgentKey (agent- 前缀)。
400
+ let privateKey = createAgentKey.private(userId, normalizedAgentId);
401
+ let publicKey = createAgentKey.public(normalizedAgentId);
402
+
403
+ if (agentId.startsWith("cybot-") || previousAgent?.type === "cybot") {
404
+ privateKey = createCybotKey.private(userId, normalizedAgentId);
405
+ publicKey = createCybotKey.public(normalizedAgentId);
406
+ }
407
+
408
+ const changes = processAgentUpdateChanges(formData || {});
409
+
410
+ // 1) 检查本地是否存在
411
+ let localExists = false;
412
+ try {
413
+ const { db } = thunkApi.extra as any;
414
+ const localData = await db.get(privateKey);
415
+ localExists = !!localData;
416
+ } catch (e) {
417
+ // ignore
418
+ }
419
+
420
+ if (localExists) {
421
+ // 有本地数据,直接 patch
422
+ await thunkApi
423
+ .dispatch(
424
+ patch({
425
+ dbKey: privateKey,
426
+ changes,
427
+ })
428
+ )
429
+ .unwrap();
430
+ } else if (previousAgent) {
431
+ // 无本地数据,但有 UI 传来的 previousAgent,用 write 回填
432
+ const merged = {
433
+ ...previousAgent,
434
+ ...changes,
435
+ id: normalizedAgentId,
436
+ type: previousAgent.type || DataType.AGENT,
437
+ userId,
438
+ };
439
+ await thunkApi
440
+ .dispatch(
441
+ write({
442
+ data: merged,
443
+ customKey: privateKey,
444
+ })
445
+ )
446
+ .unwrap();
447
+ } else {
448
+ // 既无本地也无 previousAgent,尝试标准 path (可能会失败 if remote also fails or path throws)
449
+ // 但既然到了 update,大概率之前 read 过。
450
+ // 兜底调用 patch,让 patch 内部去报错
451
+ await thunkApi
452
+ .dispatch(
453
+ patch({
454
+ dbKey: privateKey,
455
+ changes,
456
+ })
457
+ )
458
+ .unwrap();
459
+ }
460
+
461
+ // 2) 如提供 previousAgent,则尝试保持公共副本同步
462
+ if (previousAgent) {
463
+ const wasPublic = !!previousAgent.isPublic;
464
+ const hasIsPublicChange = Object.prototype.hasOwnProperty.call(
465
+ changes,
466
+ "isPublic"
467
+ );
468
+ const nowPublic = hasIsPublicChange
469
+ ? !!(changes as any).isPublic
470
+ : wasPublic;
471
+
472
+ if (nowPublic) {
473
+ const mergedPublic: Agent = {
474
+ ...(previousAgent as any),
475
+ ...(changes as any),
476
+ id: normalizedAgentId,
477
+ type: previousAgent.type || DataType.AGENT,
478
+ userId,
479
+ };
480
+
481
+ await thunkApi
482
+ .dispatch(
483
+ write({
484
+ data: mergedPublic,
485
+ customKey: publicKey,
486
+ })
487
+ )
488
+ .unwrap();
489
+ } else if (wasPublic && !nowPublic) {
490
+ await thunkApi
491
+ .dispatch(remove(publicKey))
492
+ .unwrap();
493
+ }
494
+ }
495
+
496
+ // 3) 返回“私有视角”的最新 Agent(主要给前端本地状态使用)
497
+ const base = previousAgent ?? ({} as Partial<Agent>);
498
+ const mergedPrivate: Agent = {
499
+ ...(base as any),
500
+ ...(changes as any),
501
+ id: normalizedAgentId,
502
+ type: base.type || DataType.AGENT,
503
+ userId,
504
+ };
505
+
506
+ return mergedPrivate;
507
+ }
508
+ ),
509
+ }),
510
+ });
511
+
512
+ export const {
513
+ runLlm,
514
+ runAgent,
515
+ streamAgentChatTurn,
516
+ createAgent,
517
+ updateAgent,
518
+ setSSRPublicAgents,
519
+ } = slice.actions;
520
+
521
+ export default slice.reducer;
522
+
523
+ /** 读取 SSR 预载的公开 Agent 列表(首页 AI 广场) */
524
+ export const selectSSRPublicAgents = (state: any): Agent[] =>
525
+ state.cybot?.pubCybots?.data ?? [];