nolo-cli 0.1.8 → 0.1.10
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.
- package/README.md +32 -0
- package/agentRuntimeCommands.ts +3 -3
- package/ai/agent/_executeModel.ts +118 -0
- package/ai/agent/agentSlice.ts +525 -0
- package/ai/agent/appWorkingMemory.ts +126 -0
- package/ai/agent/avatarUtils.ts +24 -0
- package/ai/agent/buildEditingContext.ts +373 -0
- package/ai/agent/buildSystemPrompt.ts +532 -0
- package/ai/agent/cleanAgentMessages.ts +140 -0
- package/ai/agent/cliChatClient.ts +119 -0
- package/ai/agent/cliExecutor.ts +733 -0
- package/ai/agent/cliPrompt.ts +10 -0
- package/ai/agent/contextCompiler.ts +107 -0
- package/ai/agent/contextLayerContract.ts +44 -0
- package/ai/agent/createAgentSchema.ts +234 -0
- package/ai/agent/executeToolCall.ts +58 -0
- package/ai/agent/fetchAgentContexts.ts +42 -0
- package/ai/agent/generatePrompt.ts +3 -0
- package/ai/agent/getFullChatContextKeys.ts +168 -0
- package/ai/agent/hooks/fetchPublicAgents.ts +133 -0
- package/ai/agent/hooks/useAgentConfig.ts +61 -0
- package/ai/agent/hooks/useAgentDialog.ts +35 -0
- package/ai/agent/hooks/useAgentFormValidation.ts +202 -0
- package/ai/agent/hooks/usePublicAgents.ts +473 -0
- package/ai/agent/machineRunPermissions.ts +95 -0
- package/ai/agent/persistMessageWithFixedId.ts +37 -0
- package/ai/agent/planSlice.ts +259 -0
- package/ai/agent/referenceUtils.ts +229 -0
- package/ai/agent/runAgentBackground.ts +238 -0
- package/ai/agent/runAgentClientLoop.ts +138 -0
- package/ai/agent/runtimeGuidance.ts +97 -0
- package/ai/agent/runtimeServerBase.ts +37 -0
- package/ai/agent/server/fetchPublicAgents.ts +128 -0
- package/ai/agent/startParallelAgentStreams.ts +424 -0
- package/ai/agent/startupProtocol.ts +53 -0
- package/ai/agent/streamAgentChatTurn.ts +1278 -0
- package/ai/agent/streamAgentChatTurnUtils.ts +738 -0
- package/ai/agent/types.ts +71 -0
- package/ai/agent/utils/imageOutput.ts +33 -0
- package/ai/agent/utils/sortUtils.ts +250 -0
- package/ai/agent/web/referencePickerUtils.ts +146 -0
- package/ai/ai.locale.ts +1079 -0
- package/ai/chat/accumulateToolCallChunks.ts +95 -0
- package/ai/chat/fetchUtils.native.ts +276 -0
- package/ai/chat/fetchUtils.ts +153 -0
- package/ai/chat/parseApiError.ts +64 -0
- package/ai/chat/parseMultilineSSE.ts +95 -0
- package/ai/chat/sendOpenAICompletionsRequest.native.ts +682 -0
- package/ai/chat/sendOpenAICompletionsRequest.ts +703 -0
- package/ai/chat/sendOpenAIResponseRequest.ts +491 -0
- package/ai/chat/shouldUseServerProxy.ts +18 -0
- package/ai/chat/sseClient.native.ts +91 -0
- package/ai/chat/sseClient.ts +67 -0
- package/ai/chat/streamReader.native.ts +31 -0
- package/ai/chat/streamReader.ts +62 -0
- package/ai/chat/updateTotalUsage.ts +72 -0
- package/ai/context/buildReferenceContext.ts +437 -0
- package/ai/context/calculateContextUsage.ts +133 -0
- package/ai/context/retention.ts +165 -0
- package/ai/context/tokenUtils.ts +78 -0
- package/ai/index.ts +1 -0
- package/ai/llm/calculateGeminiImageTokens.ts +57 -0
- package/ai/llm/deepinfra.ts +28 -0
- package/ai/llm/fireworks.ts +50 -0
- package/ai/llm/generateRequestBody.ts +165 -0
- package/ai/llm/getModelContextWindow.ts +84 -0
- package/ai/llm/getNoloKey.ts +31 -0
- package/ai/llm/getPricing.ts +199 -0
- package/ai/llm/hooks/useModelPricing.ts +75 -0
- package/ai/llm/imagePricing.ts +40 -0
- package/ai/llm/isResponseAPIModel.ts +13 -0
- package/ai/llm/mimo.ts +71 -0
- package/ai/llm/mistral.ts +22 -0
- package/ai/llm/modelAvatar.ts +427 -0
- package/ai/llm/models.ts +45 -0
- package/ai/llm/openrouterModels.ts +269 -0
- package/ai/llm/providers.ts +306 -0
- package/ai/llm/reasoningModels.ts +28 -0
- package/ai/llm/types.ts +59 -0
- package/ai/llm/usageRequestOptions.ts +59 -0
- package/ai/memory/capture.ts +148 -0
- package/ai/memory/consolidate.ts +104 -0
- package/ai/memory/delete.ts +147 -0
- package/ai/memory/overlay.ts +84 -0
- package/ai/memory/query.ts +38 -0
- package/ai/memory/queryShared.ts +160 -0
- package/ai/memory/rank.ts +105 -0
- package/ai/memory/recentRelationshipRecap.ts +249 -0
- package/ai/memory/remember.ts +167 -0
- package/ai/memory/runtime.ts +76 -0
- package/ai/memory/store.ts +20 -0
- package/ai/memory/storeShared.ts +76 -0
- package/ai/memory/types.ts +46 -0
- package/ai/memory/understanding.ts +349 -0
- package/ai/memory/understandingGreeting.ts +264 -0
- package/ai/messages/type.ts +20 -0
- package/ai/policy/personalizationDialog.ts +333 -0
- package/ai/policy/runtimePolicy.ts +440 -0
- package/ai/policy/selfUpdateFields.ts +48 -0
- package/ai/policy/types.ts +64 -0
- package/ai/skills/referenceRuntime.ts +274 -0
- package/ai/skills/skillDiagnostics.ts +251 -0
- package/ai/skills/skillDocBuilder.ts +139 -0
- package/ai/skills/skillDocProtocol.ts +434 -0
- package/ai/skills/skillReferenceSummary.ts +63 -0
- package/ai/skills/skillSummaryMarker.ts +26 -0
- package/ai/token/calculatePrice.ts +544 -0
- package/ai/token/db.ts +98 -0
- package/ai/token/externalToolCost.ts +330 -0
- package/ai/token/hooks/useRecords.ts +65 -0
- package/ai/token/missingUsageEstimate.ts +42 -0
- package/ai/token/modelUsageQuery.ts +252 -0
- package/ai/token/normalizeUsage.ts +84 -0
- package/ai/token/openaiImageGenerationUsage.ts +56 -0
- package/ai/token/prepareTokenUsageData.ts +88 -0
- package/ai/token/query.ts +88 -0
- package/ai/token/queryUserTokens.ts +59 -0
- package/ai/token/resolveBillingTarget.ts +52 -0
- package/ai/token/saveTokenRecord.ts +53 -0
- package/ai/token/serverDialogProjection.ts +78 -0
- package/ai/token/serverTokenWriter.ts +143 -0
- package/ai/token/stats.ts +21 -0
- package/ai/token/tokenThunks.ts +24 -0
- package/ai/token/types.ts +93 -0
- package/ai/tools/agent/agentTools.ts +176 -0
- package/ai/tools/agent/agentUpdateShared.ts +311 -0
- package/ai/tools/agent/callAgentTool.ts +139 -0
- package/ai/tools/agent/createAgentTool.ts +512 -0
- package/ai/tools/agent/createDialogTool.ts +69 -0
- package/ai/tools/agent/createSkillAgentTool.ts +62 -0
- package/ai/tools/agent/parallelBudget.ts +221 -0
- package/ai/tools/agent/presets/appBuilderPreset.ts +145 -0
- package/ai/tools/agent/runLlmTool.ts +96 -0
- package/ai/tools/agent/runStreamingAgentTool.ts +73 -0
- package/ai/tools/agent/skillAgentArgs.ts +106 -0
- package/ai/tools/agent/skillAgentPreset.ts +89 -0
- package/ai/tools/agent/streamParallelAgentsTool.ts +122 -0
- package/ai/tools/agent/updateAgentTool.ts +96 -0
- package/ai/tools/agent/updateSelfTool.ts +113 -0
- package/ai/tools/amazonProductScraperTool.ts +86 -0
- package/ai/tools/apifyActorClient.ts +45 -0
- package/ai/tools/appEditGuard.ts +372 -0
- package/ai/tools/appReadSnapshot.ts +153 -0
- package/ai/tools/appTools.ts +1549 -0
- package/ai/tools/applyEditTool.ts +256 -0
- package/ai/tools/applyLineEditsTool.ts +312 -0
- package/ai/tools/browserTools/click.ts +33 -0
- package/ai/tools/browserTools/closeSession.ts +29 -0
- package/ai/tools/browserTools/common.ts +27 -0
- package/ai/tools/browserTools/openSession.ts +48 -0
- package/ai/tools/browserTools/readContent.ts +38 -0
- package/ai/tools/browserTools/selectOption.ts +46 -0
- package/ai/tools/browserTools/typeText.ts +42 -0
- package/ai/tools/category/createCategoryTool.ts +66 -0
- package/ai/tools/category/queryContentsByCategoryTool.ts +69 -0
- package/ai/tools/category/updateContentCategoryTool.ts +75 -0
- package/ai/tools/cfBrowserTools.ts +319 -0
- package/ai/tools/cfSpeechToTextTool.ts +49 -0
- package/ai/tools/checkEnvTool.ts +65 -0
- package/ai/tools/cloudflareCrawlTool.ts +289 -0
- package/ai/tools/codeSearchTool.ts +111 -0
- package/ai/tools/codeTools.ts +101 -0
- package/ai/tools/createDocTool.ts +132 -0
- package/ai/tools/createPlanTool.ts +999 -0
- package/ai/tools/createSkillDocTool.ts +155 -0
- package/ai/tools/createWorkflowTool.ts +154 -0
- package/ai/tools/deepseekOcrTool.ts +34 -0
- package/ai/tools/delayTool.ts +31 -0
- package/ai/tools/deleteSpacesTool.ts +325 -0
- package/ai/tools/deleteSpacesToolModel.ts +159 -0
- package/ai/tools/devReloadUtils.ts +29 -0
- package/ai/tools/dialogMessageSearch.ts +137 -0
- package/ai/tools/doctorSkillTool.ts +72 -0
- package/ai/tools/ecommerceScraperTool.ts +86 -0
- package/ai/tools/emailTools.ts +549 -0
- package/ai/tools/evalSkillTool.ts +92 -0
- package/ai/tools/exaSearchTool.ts +64 -0
- package/ai/tools/execBashTool.ts +379 -0
- package/ai/tools/executeSqlTool.ts +192 -0
- package/ai/tools/fetchWebpageSupport.ts +309 -0
- package/ai/tools/fetchWebpageTool.ts +84 -0
- package/ai/tools/geminiImagePreviewTool.ts +361 -0
- package/ai/tools/generateDocxTool.ts +215 -0
- package/ai/tools/googleSearchScraperTool.ts +106 -0
- package/ai/tools/importDataTool.ts +133 -0
- package/ai/tools/importSkillTool.ts +162 -0
- package/ai/tools/index.ts +1858 -0
- package/ai/tools/listFilesTool.ts +82 -0
- package/ai/tools/listUserSpacesTool.ts +113 -0
- package/ai/tools/modelUsageTools.ts +142 -0
- package/ai/tools/olmOcrTool.ts +34 -0
- package/ai/tools/openaiImageTool.ts +218 -0
- package/ai/tools/paddleOcrTool.ts +34 -0
- package/ai/tools/prepareTools.ts +23 -0
- package/ai/tools/readDocTool.ts +84 -0
- package/ai/tools/readFileTool.ts +211 -0
- package/ai/tools/readTool.ts +163 -0
- package/ai/tools/readXPostTool.ts +233 -0
- package/ai/tools/rememberMemoryTool.ts +84 -0
- package/ai/tools/remotionVideoTool.ts +151 -0
- package/ai/tools/searchDialogMessagesTool.ts +222 -0
- package/ai/tools/searchRepoTool.ts +115 -0
- package/ai/tools/searchWorkspaceTool.ts +259 -0
- package/ai/tools/skillFollowup.ts +86 -0
- package/ai/tools/surfWeatherTool.ts +169 -0
- package/ai/tools/table/addTableRowTool.ts +217 -0
- package/ai/tools/table/createTableTool.ts +315 -0
- package/ai/tools/table/rowTools.ts +366 -0
- package/ai/tools/table/schemaTools.ts +244 -0
- package/ai/tools/table/shareTableTool.ts +148 -0
- package/ai/tools/table/toolShared.ts +129 -0
- package/ai/tools/toolApiClient.ts +198 -0
- package/ai/tools/toolNameAliases.ts +57 -0
- package/ai/tools/toolResultError.ts +42 -0
- package/ai/tools/toolRunSlice.ts +303 -0
- package/ai/tools/toolSchemaCompatibility.ts +53 -0
- package/ai/tools/toolVisibility.ts +4 -0
- package/ai/tools/types.ts +20 -0
- package/ai/tools/uiAskChoiceTool.ts +104 -0
- package/ai/tools/updateContentTitleTool.ts +84 -0
- package/ai/tools/updateDocTool.ts +105 -0
- package/ai/tools/updateUserPreferenceProfileTool.ts +145 -0
- package/ai/tools/whisperTool.ts +77 -0
- package/ai/tools/writeFileTool.ts +210 -0
- package/ai/tools/youtubeScraperTool.ts +116 -0
- package/ai/tools/ziweiChartTool.ts +678 -0
- package/ai/types.ts +55 -0
- package/ai/workflow/workflowExecutor.ts +323 -0
- package/ai/workflow/workflowSlice.ts +73 -0
- package/ai/workflow/workflowTypes.ts +106 -0
- package/client/compactDialog.ts +222 -0
- package/connector-experimental/capabilities.ts +73 -0
- package/connector-experimental/codexBinary.ts +41 -0
- package/connector-experimental/heartbeatLoop.ts +22 -0
- package/connector-experimental/index.ts +5 -0
- package/connector-experimental/machineInfo.ts +46 -0
- package/connector-experimental/protocol.ts +54 -0
- package/machineCommands.ts +4 -4
- package/package.json +22 -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
|
+
|
package/agentRuntimeCommands.ts
CHANGED
|
@@ -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 "
|
|
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 ?? [];
|