nolo-cli 0.1.19 → 0.1.20
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 +9 -1
- package/agent-runtime/agentConfigOptions.ts +12 -0
- package/agent-runtime/agentRecordConfig.ts +99 -0
- package/agent-runtime/agentRecordKeys.ts +14 -0
- package/agent-runtime/dialogMessageRecord.ts +16 -0
- package/agent-runtime/dialogWritePlan.ts +130 -0
- package/agent-runtime/hostAdapter.ts +13 -0
- package/agent-runtime/hybridRecordStore.ts +147 -0
- package/agent-runtime/index.ts +69 -0
- package/agent-runtime/localLoop.ts +69 -5
- package/agent-runtime/localToolPolicy.ts +130 -0
- package/agent-runtime/localWorkspaceTools.ts +1532 -0
- package/agent-runtime/openAiCompatibleProvider.ts +70 -0
- package/agent-runtime/openAiCompatibleProviderConfig.ts +38 -0
- package/agent-runtime/platformChatProvider.ts +241 -0
- package/agent-runtime/taskWorkspace.ts +193 -0
- package/agent-runtime/types.ts +1 -0
- package/agent-runtime/workspaceSession.ts +76 -0
- package/agentAliases.ts +37 -0
- package/agentPullCommand.ts +1 -1
- package/agentRunCommand.ts +278 -52
- package/agentRuntimeCommands.ts +354 -164
- package/agentRuntimeLocal.ts +38 -0
- package/ai/agent/agentSlice.ts +10 -0
- package/ai/agent/buildEditingContext.ts +5 -0
- package/ai/agent/buildSystemPrompt.ts +41 -18
- package/ai/agent/canvasEditingContext.ts +49 -0
- package/ai/agent/cliExecutor.ts +15 -4
- package/ai/agent/createAgentSchema.ts +2 -0
- package/ai/agent/executeToolCall.ts +3 -2
- package/ai/agent/hooks/usePublicAgents.ts +6 -0
- package/ai/agent/pageBuilderHandoffRules.ts +75 -0
- package/ai/agent/runAgentClientLoop.ts +4 -1
- package/ai/agent/runtimeGuidance.ts +19 -0
- package/ai/agent/server/fetchPublicAgents.ts +51 -1
- package/ai/agent/streamAgentChatTurn.ts +20 -2
- package/ai/agent/streamAgentChatTurnUtils.ts +60 -16
- package/ai/chat/accumulateToolCallChunks.ts +40 -9
- package/ai/chat/parseApiError.ts +3 -0
- package/ai/chat/sendOpenAICompletionsRequest.native.ts +23 -10
- package/ai/chat/sendOpenAICompletionsRequest.ts +13 -1
- package/ai/chat/updateTotalUsage.ts +26 -9
- package/ai/llm/deepinfra.ts +51 -0
- package/ai/llm/getPricing.ts +6 -0
- package/ai/llm/kimi.ts +2 -0
- package/ai/llm/openrouterModels.ts +0 -135
- package/ai/llm/providers.ts +1 -0
- package/ai/llm/types.ts +8 -0
- package/ai/taskRun/taskRunProtocol.ts +823 -0
- package/ai/token/calculatePrice.ts +30 -0
- package/ai/token/externalToolCost.ts +49 -29
- package/ai/token/prepareTokenUsageData.ts +6 -1
- package/ai/token/serverTokenWriter.ts +4 -2
- package/ai/tools/agent/agentTools.ts +21 -0
- package/ai/tools/agent/presets/appBuilderPreset.ts +7 -0
- package/ai/tools/agent/streamParallelAgentsTool.ts +2 -1
- package/ai/tools/agent/taskRunTool.ts +112 -0
- package/ai/tools/applyEditTool.ts +6 -3
- package/ai/tools/applyLineEditsTool.ts +6 -3
- package/ai/tools/checkEnvTool.ts +14 -9
- package/ai/tools/codeSearchTool.ts +17 -5
- package/ai/tools/execBashTool.ts +33 -29
- package/ai/tools/fetchWebpageSupport.ts +24 -0
- package/ai/tools/fetchWebpageTool.ts +18 -5
- package/ai/tools/index.ts +158 -0
- package/ai/tools/jdProductScraperTool.ts +821 -0
- package/ai/tools/listFilesTool.ts +6 -3
- package/ai/tools/localFilesTool.ts +200 -0
- package/ai/tools/readFileTool.ts +6 -3
- package/ai/tools/searchRepoTool.ts +6 -3
- package/ai/tools/table/rowTools.ts +6 -1
- package/ai/tools/taobaoTmallProductScraperTool.ts +49 -0
- package/ai/tools/toolApiClient.ts +20 -6
- package/ai/tools/wereadGatewayTool.ts +152 -0
- package/ai/tools/writeFileTool.ts +6 -3
- package/client/agentConfigResolver.test.ts +70 -0
- package/client/agentConfigResolver.ts +1 -0
- package/client/agentRun.test.ts +361 -7
- package/client/agentRun.ts +449 -63
- package/client/hybridRecordStore.test.ts +115 -0
- package/client/hybridRecordStore.ts +41 -0
- package/client/localAgentRecords.test.ts +27 -0
- package/client/localAgentRecords.ts +7 -0
- package/client/localDialogRecords.test.ts +124 -0
- package/client/localDialogRecords.ts +30 -0
- package/client/localProviderResolver.test.ts +78 -0
- package/client/localProviderResolver.ts +1 -0
- package/client/localRuntimeAdapter.test.ts +621 -9
- package/client/localRuntimeAdapter.ts +275 -250
- package/client/localRuntimeDryRun.test.ts +116 -0
- package/client/localToolPolicy.ts +8 -81
- package/client/taskRunPrompt.ts +26 -0
- package/client/taskWorktree.ts +8 -0
- package/client/workspaceSession.test.ts +57 -0
- package/client/workspaceSession.ts +11 -0
- package/commandRegistry.ts +23 -6
- package/connectorRunArtifact.ts +121 -0
- package/database/actions/write.ts +16 -2
- package/database/hooks/useUserData.ts +9 -3
- package/database/server/dataHandlers.ts +18 -20
- package/database/server/emailRepository.ts +3 -3
- package/database/server/patch.ts +18 -10
- package/database/server/query.ts +43 -4
- package/database/server/read.ts +24 -38
- package/database/server/recordIdentity.ts +100 -0
- package/database/server/write.ts +21 -25
- package/index.ts +70 -33
- package/machineCommands.ts +318 -144
- package/package.json +4 -1
- package/tableCommands.ts +181 -0
- package/taskRunCommand.ts +237 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// 文件路径: packages/ai/tools/listFilesTool.ts
|
|
2
2
|
|
|
3
|
-
import { getToolBaseUrl } from "./toolApiClient";
|
|
3
|
+
import { buildToolRequestHeaders, getToolBaseUrl } from "./toolApiClient";
|
|
4
4
|
|
|
5
5
|
// ---- Types ----
|
|
6
6
|
|
|
@@ -44,7 +44,7 @@ export const listFilesFunctionSchema = {
|
|
|
44
44
|
export async function listFilesFunc(
|
|
45
45
|
args: ListFilesArgs,
|
|
46
46
|
thunkApi: any,
|
|
47
|
-
context?: { signal?: AbortSignal }
|
|
47
|
+
context?: { signal?: AbortSignal; agentKey?: string }
|
|
48
48
|
): Promise<{ rawData: any; displayData?: string }> {
|
|
49
49
|
try {
|
|
50
50
|
const baseUrl = getToolBaseUrl(thunkApi);
|
|
@@ -52,7 +52,10 @@ export async function listFilesFunc(
|
|
|
52
52
|
|
|
53
53
|
const response = await fetch(apiUrl, {
|
|
54
54
|
method: "POST",
|
|
55
|
-
headers: {
|
|
55
|
+
headers: buildToolRequestHeaders(thunkApi, {
|
|
56
|
+
withAuth: true,
|
|
57
|
+
agentKey: context?.agentKey,
|
|
58
|
+
}),
|
|
56
59
|
signal: context?.signal,
|
|
57
60
|
body: JSON.stringify(args),
|
|
58
61
|
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { getToolBaseUrl } from "./toolApiClient";
|
|
2
|
+
|
|
3
|
+
export type LocalFileOperation = {
|
|
4
|
+
kind: "mkdir" | "move" | "rename" | "writeText" | "deleteToTrash";
|
|
5
|
+
sourceRelativePath?: string;
|
|
6
|
+
destinationRelativePath?: string;
|
|
7
|
+
content?: string;
|
|
8
|
+
reason: string;
|
|
9
|
+
conflictPolicy: "skip" | "overwrite" | "rename";
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type ToolResult = {
|
|
13
|
+
rawData: any;
|
|
14
|
+
displayData?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type LocalFilesDeps = {
|
|
18
|
+
getBaseUrl?: (thunkApi: any) => string;
|
|
19
|
+
fetchImpl?: typeof fetch;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const listLocalFilesFunctionSchema = {
|
|
23
|
+
name: "listLocalFiles",
|
|
24
|
+
description: "列出 Windows 桌面客户端中用户已授权本地文件夹内的文件和目录。只能访问已授权根目录内的相对路径。",
|
|
25
|
+
parameters: {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: {
|
|
28
|
+
rootId: { type: "string", description: "已授权的本地文件夹 ID。" },
|
|
29
|
+
relativePath: { type: "string", description: "相对已授权根目录的路径,例如 . 或 Documents。" },
|
|
30
|
+
},
|
|
31
|
+
required: ["rootId"],
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const readLocalFileFunctionSchema = {
|
|
36
|
+
name: "readLocalFile",
|
|
37
|
+
description: "读取 Windows 桌面客户端中用户已授权本地文件夹内的文本文件。路径必须是授权根目录内的相对路径。",
|
|
38
|
+
parameters: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
rootId: { type: "string", description: "已授权的本地文件夹 ID。" },
|
|
42
|
+
relativePath: { type: "string", description: "相对已授权根目录的文本文件路径。" },
|
|
43
|
+
},
|
|
44
|
+
required: ["rootId", "relativePath"],
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const proposeLocalFileChangesFunctionSchema = {
|
|
49
|
+
name: "proposeLocalFileChanges",
|
|
50
|
+
description: "为用户已授权的本地文件夹创建整理计划。该工具只创建待确认计划,不会直接修改本地文件。",
|
|
51
|
+
parameters: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
rootId: { type: "string", description: "已授权的本地文件夹 ID。" },
|
|
55
|
+
summary: { type: "string", description: "本次整理计划摘要。" },
|
|
56
|
+
operations: {
|
|
57
|
+
type: "array",
|
|
58
|
+
description: "待执行的文件操作列表。",
|
|
59
|
+
items: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {
|
|
62
|
+
kind: { type: "string", enum: ["mkdir", "move", "rename", "writeText", "deleteToTrash"] },
|
|
63
|
+
sourceRelativePath: { type: "string" },
|
|
64
|
+
destinationRelativePath: { type: "string" },
|
|
65
|
+
content: { type: "string" },
|
|
66
|
+
reason: { type: "string" },
|
|
67
|
+
conflictPolicy: { type: "string", enum: ["skip", "overwrite", "rename"] },
|
|
68
|
+
},
|
|
69
|
+
required: ["kind", "reason", "conflictPolicy"],
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
riskLevel: { type: "string", enum: ["low", "medium", "high"] },
|
|
73
|
+
},
|
|
74
|
+
required: ["rootId", "summary", "operations"],
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const executeApprovedLocalFileChangesFunctionSchema = {
|
|
79
|
+
name: "executeApprovedLocalFileChanges",
|
|
80
|
+
description: "执行用户已经确认的本地文件整理计划。必须传入 proposeLocalFileChanges 返回的 planId。",
|
|
81
|
+
parameters: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
planId: { type: "string", description: "已确认计划的 ID。" },
|
|
85
|
+
},
|
|
86
|
+
required: ["planId"],
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const undoLocalFileChangeBatchFunctionSchema = {
|
|
91
|
+
name: "undoLocalFileChangeBatch",
|
|
92
|
+
description: "撤销一个本地文件整理批次中可撤销的移动或重命名操作。",
|
|
93
|
+
parameters: {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {
|
|
96
|
+
batchId: { type: "string", description: "执行历史批次 ID。" },
|
|
97
|
+
},
|
|
98
|
+
required: ["batchId"],
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export function createLocalFilesToolHandlers(deps: LocalFilesDeps = {}) {
|
|
103
|
+
const post = async (thunkApi: any, path: string, body: object, signal?: AbortSignal) => {
|
|
104
|
+
const baseUrl = (deps.getBaseUrl ?? getToolBaseUrl)(thunkApi).replace(/\/+$/, "");
|
|
105
|
+
const response = await (deps.fetchImpl ?? fetch)(`${baseUrl}${path}`, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: { "Content-Type": "application/json" },
|
|
108
|
+
signal,
|
|
109
|
+
body: JSON.stringify(body),
|
|
110
|
+
});
|
|
111
|
+
const text = await response.text();
|
|
112
|
+
const data = text ? JSON.parse(text) : {};
|
|
113
|
+
if (!response.ok || data?.ok === false || data?.error) {
|
|
114
|
+
throw new Error(data?.error || `desktop files bridge failed: ${response.status}`);
|
|
115
|
+
}
|
|
116
|
+
return data;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
async list(args: { rootId: string; relativePath?: string }, thunkApi: any, context?: { signal?: AbortSignal }): Promise<ToolResult> {
|
|
121
|
+
requireString(args.rootId, "rootId");
|
|
122
|
+
const data = await post(thunkApi, "/api/desktop/files/list", {
|
|
123
|
+
rootId: args.rootId,
|
|
124
|
+
relativePath: args.relativePath || ".",
|
|
125
|
+
}, context?.signal);
|
|
126
|
+
return {
|
|
127
|
+
rawData: data,
|
|
128
|
+
displayData: `已列出本地文件: ${args.relativePath || "."}`,
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
async read(args: { rootId: string; relativePath: string }, thunkApi: any, context?: { signal?: AbortSignal }): Promise<ToolResult> {
|
|
133
|
+
requireString(args.rootId, "rootId");
|
|
134
|
+
requireString(args.relativePath, "relativePath");
|
|
135
|
+
const data = await post(thunkApi, "/api/desktop/files/read", args, context?.signal);
|
|
136
|
+
return {
|
|
137
|
+
rawData: data,
|
|
138
|
+
displayData: `已读取本地文件: ${args.relativePath}`,
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
async propose(args: { rootId: string; summary: string; operations: LocalFileOperation[]; riskLevel?: "low" | "medium" | "high" }, thunkApi: any, context?: { signal?: AbortSignal }): Promise<ToolResult> {
|
|
143
|
+
requireString(args.rootId, "rootId");
|
|
144
|
+
requireString(args.summary, "summary");
|
|
145
|
+
if (!Array.isArray(args.operations) || args.operations.length === 0) {
|
|
146
|
+
throw new Error("operations 不能为空。");
|
|
147
|
+
}
|
|
148
|
+
const data = await post(thunkApi, "/api/desktop/files/plan", args, context?.signal);
|
|
149
|
+
return {
|
|
150
|
+
rawData: data,
|
|
151
|
+
displayData: `已创建本地文件整理计划,等待确认: ${args.summary}`,
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
async proposePreview(args: { rootId: string; summary: string; operations: LocalFileOperation[] }): Promise<ToolResult> {
|
|
156
|
+
return {
|
|
157
|
+
rawData: {
|
|
158
|
+
previewOnly: true,
|
|
159
|
+
rootId: args.rootId,
|
|
160
|
+
summary: args.summary,
|
|
161
|
+
operations: args.operations,
|
|
162
|
+
},
|
|
163
|
+
displayData: `待确认本地文件整理计划: ${args.summary}(${args.operations?.length ?? 0} 项)`,
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
async execute(args: { planId: string }, thunkApi: any, context?: { signal?: AbortSignal }): Promise<ToolResult> {
|
|
168
|
+
requireString(args.planId, "planId");
|
|
169
|
+
const data = await post(thunkApi, "/api/desktop/files/execute", args, context?.signal);
|
|
170
|
+
return {
|
|
171
|
+
rawData: data,
|
|
172
|
+
displayData: `已执行本地文件整理计划: ${args.planId}`,
|
|
173
|
+
};
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
async undo(args: { batchId: string }, thunkApi: any, context?: { signal?: AbortSignal }): Promise<ToolResult> {
|
|
177
|
+
requireString(args.batchId, "batchId");
|
|
178
|
+
const data = await post(thunkApi, "/api/desktop/files/undo", args, context?.signal);
|
|
179
|
+
return {
|
|
180
|
+
rawData: data,
|
|
181
|
+
displayData: `已撤销本地文件整理批次: ${args.batchId}`,
|
|
182
|
+
};
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function requireString(value: unknown, name: string) {
|
|
188
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
189
|
+
throw new Error(`${name} 必须是非空字符串。`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const defaultHandlers = createLocalFilesToolHandlers();
|
|
194
|
+
|
|
195
|
+
export const listLocalFilesFunc = defaultHandlers.list;
|
|
196
|
+
export const readLocalFileFunc = defaultHandlers.read;
|
|
197
|
+
export const proposeLocalFileChangesFunc = defaultHandlers.propose;
|
|
198
|
+
export const proposeLocalFileChangesPreviewFunc = defaultHandlers.proposePreview;
|
|
199
|
+
export const executeApprovedLocalFileChangesFunc = defaultHandlers.execute;
|
|
200
|
+
export const undoLocalFileChangeBatchFunc = defaultHandlers.undo;
|
package/ai/tools/readFileTool.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// 文件路径: packages/ai/tools/readFileTool.ts
|
|
2
2
|
|
|
3
|
-
import { getToolBaseUrl } from "./toolApiClient";
|
|
3
|
+
import { buildToolRequestHeaders, getToolBaseUrl } from "./toolApiClient";
|
|
4
4
|
|
|
5
5
|
// ---- Types ----
|
|
6
6
|
|
|
@@ -106,7 +106,7 @@ export async function readFilePreviewFunc(
|
|
|
106
106
|
export async function readFileFunc(
|
|
107
107
|
args: ReadFileArgs,
|
|
108
108
|
thunkApi: any,
|
|
109
|
-
context?: { parentMessageId?: string; signal?: AbortSignal }
|
|
109
|
+
context?: { parentMessageId?: string; signal?: AbortSignal; agentKey?: string }
|
|
110
110
|
): Promise<{ rawData: any; displayData?: string }> {
|
|
111
111
|
const { filePath, startLine, endLine } = args;
|
|
112
112
|
|
|
@@ -125,7 +125,10 @@ export async function readFileFunc(
|
|
|
125
125
|
|
|
126
126
|
const response = await fetch(apiUrl, {
|
|
127
127
|
method: "POST",
|
|
128
|
-
headers: {
|
|
128
|
+
headers: buildToolRequestHeaders(thunkApi, {
|
|
129
|
+
withAuth: true,
|
|
130
|
+
agentKey: context?.agentKey,
|
|
131
|
+
}),
|
|
129
132
|
signal: context?.signal,
|
|
130
133
|
body: JSON.stringify({ filePath, startLine, endLine }),
|
|
131
134
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// 文件路径: packages/ai/tools/searchRepoTool.ts
|
|
2
2
|
|
|
3
|
-
import { getToolBaseUrl } from "./toolApiClient";
|
|
3
|
+
import { buildToolRequestHeaders, getToolBaseUrl } from "./toolApiClient";
|
|
4
4
|
|
|
5
5
|
// ---- Types ----
|
|
6
6
|
|
|
@@ -76,7 +76,7 @@ function buildSearchRepoDisplayData(
|
|
|
76
76
|
export async function searchRepoFunc(
|
|
77
77
|
args: SearchRepoArgs,
|
|
78
78
|
thunkApi: any,
|
|
79
|
-
context?: { signal?: AbortSignal }
|
|
79
|
+
context?: { signal?: AbortSignal; agentKey?: string }
|
|
80
80
|
): Promise<{ rawData: any; displayData?: string }> {
|
|
81
81
|
const { query, pathScope, maxResults, contextLines } = args;
|
|
82
82
|
|
|
@@ -86,7 +86,10 @@ export async function searchRepoFunc(
|
|
|
86
86
|
|
|
87
87
|
const response = await fetch(apiUrl, {
|
|
88
88
|
method: "POST",
|
|
89
|
-
headers: {
|
|
89
|
+
headers: buildToolRequestHeaders(thunkApi, {
|
|
90
|
+
withAuth: true,
|
|
91
|
+
agentKey: context?.agentKey,
|
|
92
|
+
}),
|
|
90
93
|
signal: context?.signal,
|
|
91
94
|
body: JSON.stringify({ query, pathScope, maxResults, contextLines }),
|
|
92
95
|
});
|
|
@@ -47,6 +47,10 @@ export const queryTableRowsFunctionSchema = {
|
|
|
47
47
|
},
|
|
48
48
|
limit: { type: "number", description: "最多返回多少行,默认 20,最大 200。" },
|
|
49
49
|
offset: { type: "number", description: "从第几行开始返回,默认 0。" },
|
|
50
|
+
includeBaseFields: {
|
|
51
|
+
type: "boolean",
|
|
52
|
+
description: "配合 columns 使用。设为 false 时不额外返回 dbKey/rowId/tenantId/tableId/createdAt/updatedAt,适合任务表概览。",
|
|
53
|
+
},
|
|
50
54
|
sortBy: { type: "string", description: "排序字段,默认 updatedAt。" },
|
|
51
55
|
sortOrder: {
|
|
52
56
|
type: "string",
|
|
@@ -69,11 +73,12 @@ export async function queryTableRowsFunc(args: any, thunkApi: any) {
|
|
|
69
73
|
const columns = Array.isArray(args?.columns)
|
|
70
74
|
? args.columns.filter((value: unknown): value is string => typeof value === "string" && value.trim().length > 0)
|
|
71
75
|
: undefined;
|
|
76
|
+
const includeBaseFields = args?.includeBaseFields !== false;
|
|
72
77
|
|
|
73
78
|
const filtered = applyRowFilters(rows, filters);
|
|
74
79
|
const sorted = sortRows(filtered, sortBy, sortOrder);
|
|
75
80
|
const page = sorted.slice(offset, offset + limit);
|
|
76
|
-
const items = columns ? page.map((row) => pickRowColumns(row, columns)) : page;
|
|
81
|
+
const items = columns ? page.map((row) => pickRowColumns(row, columns, { includeBaseFields })) : page;
|
|
77
82
|
|
|
78
83
|
return {
|
|
79
84
|
rawData: {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { callApifyActor } from "./apifyActorClient";
|
|
2
|
+
|
|
3
|
+
export const taobaoTmallProductScraperFunctionSchema = {
|
|
4
|
+
name: "taobaoTmallProductScraper",
|
|
5
|
+
description:
|
|
6
|
+
"使用 Apify Taobao/Tmall Product Scraper 获取淘宝/天猫商品真实详情,返回标题、价格、SKU、库存、图片、店铺、属性和规格参数。适合用户提供淘宝/天猫链接或商品 ID 后做真实参数对比。",
|
|
7
|
+
parameters: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
itemId: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description:
|
|
13
|
+
"淘宝/天猫数字商品 ID。可从链接中的 id 参数提取,例如 https://item.taobao.com/item.htm?id=744983869996。",
|
|
14
|
+
},
|
|
15
|
+
detailDepth: {
|
|
16
|
+
type: "string",
|
|
17
|
+
enum: ["lite", "standard", "full"],
|
|
18
|
+
description:
|
|
19
|
+
"详情深度。full 会尽量返回优惠后价格、属性、规格、SKU 和促销信息;默认 full。",
|
|
20
|
+
default: "full",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
required: ["itemId"],
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export async function taobaoTmallProductScraperFunc(
|
|
28
|
+
args: {
|
|
29
|
+
itemId: string;
|
|
30
|
+
detailDepth?: "lite" | "standard" | "full";
|
|
31
|
+
},
|
|
32
|
+
thunkApi: any
|
|
33
|
+
) {
|
|
34
|
+
const itemId = String(args.itemId ?? "").trim();
|
|
35
|
+
if (!/^\d{6,}$/.test(itemId)) {
|
|
36
|
+
throw new Error("淘宝/天猫商品详情抓取失败:itemId 必须是有效的数字商品 ID。");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return callApifyActor(thunkApi, {
|
|
40
|
+
actorId: "sian.agency~taobao-tmall-product-scraper",
|
|
41
|
+
input: {
|
|
42
|
+
operation: "productDetail",
|
|
43
|
+
itemId,
|
|
44
|
+
detailDepth: args.detailDepth ?? "full",
|
|
45
|
+
},
|
|
46
|
+
resultType: "datasetItems",
|
|
47
|
+
displayName: "Taobao/Tmall Product Scraper",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -72,6 +72,8 @@ export const getToolRequestContext = (thunkApi: any): ToolRequestContext => {
|
|
|
72
72
|
export interface CallToolApiOptions {
|
|
73
73
|
/** 是否在请求头中附带 Authorization token,默认 false */
|
|
74
74
|
withAuth?: boolean;
|
|
75
|
+
/** 生产受控 devtool 路由需要知道是哪一个 agent 在调用。 */
|
|
76
|
+
agentKey?: string | null;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
const maybeAttachDialogId = (thunkApi: any, body: object): object => {
|
|
@@ -103,6 +105,22 @@ export class ToolApiError extends Error {
|
|
|
103
105
|
}
|
|
104
106
|
}
|
|
105
107
|
|
|
108
|
+
export const buildToolRequestHeaders = (
|
|
109
|
+
thunkApi: any,
|
|
110
|
+
options: CallToolApiOptions = {}
|
|
111
|
+
): Record<string, string> => {
|
|
112
|
+
const { withAuth = false, agentKey } = options;
|
|
113
|
+
const { token } = getRequestConfig(thunkApi);
|
|
114
|
+
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
115
|
+
if (withAuth && token) {
|
|
116
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
117
|
+
}
|
|
118
|
+
if (agentKey && typeof agentKey === "string" && agentKey.trim()) {
|
|
119
|
+
headers["X-Nolo-Agent-Key"] = agentKey.trim();
|
|
120
|
+
}
|
|
121
|
+
return headers;
|
|
122
|
+
};
|
|
123
|
+
|
|
106
124
|
const buildResponsePreview = (text: string): string =>
|
|
107
125
|
text
|
|
108
126
|
.replace(/\s+/g, " ")
|
|
@@ -132,14 +150,10 @@ export async function callToolApi<T = any>(
|
|
|
132
150
|
body: object,
|
|
133
151
|
options: CallToolApiOptions = {}
|
|
134
152
|
): Promise<T> {
|
|
135
|
-
const {
|
|
136
|
-
const { currentServer, token } = getRequestConfig(thunkApi);
|
|
153
|
+
const { currentServer } = getRequestConfig(thunkApi);
|
|
137
154
|
|
|
138
155
|
const url = `${currentServer}${path}`;
|
|
139
|
-
const headers
|
|
140
|
-
if (withAuth && token) {
|
|
141
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
142
|
-
}
|
|
156
|
+
const headers = buildToolRequestHeaders(thunkApi, options);
|
|
143
157
|
|
|
144
158
|
const response = await fetch(url, {
|
|
145
159
|
method: "POST",
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { callToolApi } from "./toolApiClient";
|
|
2
|
+
|
|
3
|
+
export const WEREAD_DEFAULT_SKILL_VERSION = "1.0.3";
|
|
4
|
+
export const WEREAD_GATEWAY_URL = "https://i.weread.qq.com/api/agent/gateway";
|
|
5
|
+
|
|
6
|
+
export interface WereadGatewayArgs {
|
|
7
|
+
api_name?: string;
|
|
8
|
+
skill_version?: string;
|
|
9
|
+
params?: Record<string, unknown>;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface WereadGatewayRunOptions {
|
|
14
|
+
apiKey: string;
|
|
15
|
+
fetchImpl?: typeof fetch;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const wereadGatewayFunctionSchema = {
|
|
19
|
+
name: "wereadGateway",
|
|
20
|
+
description:
|
|
21
|
+
"调用微信读书 Agent API Gateway。用于搜索书籍、查看书架、阅读统计、笔记划线、书评和推荐。业务参数必须平铺在请求体顶层。",
|
|
22
|
+
parameters: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
api_name: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "微信读书接口名,例如 /store/search、/shelf/sync、/user/notebooks。",
|
|
28
|
+
},
|
|
29
|
+
skill_version: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "微信读书 skill 版本;未传时使用当前内置版本。",
|
|
32
|
+
},
|
|
33
|
+
params: {
|
|
34
|
+
type: "object",
|
|
35
|
+
description: "业务参数对象。工具会自动把字段平铺到请求体顶层,不会以 params 包裹转发。",
|
|
36
|
+
additionalProperties: true,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ["api_name"],
|
|
40
|
+
} as const,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const RESERVED_ARG_KEYS = new Set(["api_name", "skill_version", "params"]);
|
|
44
|
+
|
|
45
|
+
function normalizeApiName(value: unknown): string {
|
|
46
|
+
return typeof value === "string" ? value.trim() : "";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function normalizeSkillVersion(value: unknown): string {
|
|
50
|
+
return typeof value === "string" && value.trim()
|
|
51
|
+
? value.trim()
|
|
52
|
+
: WEREAD_DEFAULT_SKILL_VERSION;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeParams(value: unknown): Record<string, unknown> {
|
|
56
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
57
|
+
return value as Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function buildWereadGatewayRequestBody(args: WereadGatewayArgs): Record<string, unknown> {
|
|
61
|
+
const apiName = normalizeApiName(args.api_name);
|
|
62
|
+
if (!apiName) {
|
|
63
|
+
throw new Error("wereadGateway 需要 api_name。");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const flattened: Record<string, unknown> = {
|
|
67
|
+
...normalizeParams(args.params),
|
|
68
|
+
};
|
|
69
|
+
for (const [key, value] of Object.entries(args)) {
|
|
70
|
+
if (!RESERVED_ARG_KEYS.has(key)) {
|
|
71
|
+
flattened[key] = value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
api_name: apiName,
|
|
77
|
+
...flattened,
|
|
78
|
+
skill_version: normalizeSkillVersion(args.skill_version),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function extractUpgradeMessage(data: unknown): string | null {
|
|
83
|
+
const upgradeInfo = (data as any)?.upgrade_info;
|
|
84
|
+
return typeof upgradeInfo?.message === "string" && upgradeInfo.message.trim()
|
|
85
|
+
? upgradeInfo.message.trim()
|
|
86
|
+
: null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function extractErrcode(data: unknown): number {
|
|
90
|
+
const raw = (data as any)?.errcode;
|
|
91
|
+
return typeof raw === "number" && Number.isFinite(raw) ? raw : 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function extractErrorMessage(data: unknown): string {
|
|
95
|
+
const record = data as any;
|
|
96
|
+
return (
|
|
97
|
+
(typeof record?.errmsg === "string" && record.errmsg.trim()) ||
|
|
98
|
+
(typeof record?.message === "string" && record.message.trim()) ||
|
|
99
|
+
"微信读书接口返回错误"
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function runWereadGatewayRequest(
|
|
104
|
+
args: WereadGatewayArgs,
|
|
105
|
+
options: WereadGatewayRunOptions
|
|
106
|
+
): Promise<{ rawData: unknown; displayData: string }> {
|
|
107
|
+
const apiKey = options.apiKey.trim();
|
|
108
|
+
if (!apiKey) {
|
|
109
|
+
throw new Error("缺少 WEREAD_API_KEY。请先在设置 -> 密钥中保存 WEREAD_API_KEY。");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const body = buildWereadGatewayRequestBody(args);
|
|
113
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
114
|
+
const response = await fetchImpl(WEREAD_GATEWAY_URL, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: {
|
|
117
|
+
Authorization: `Bearer ${apiKey}`,
|
|
118
|
+
"Content-Type": "application/json",
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify(body),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const data = await response.json().catch(async () => ({
|
|
124
|
+
errmsg: await response.text().catch(() => ""),
|
|
125
|
+
}));
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
throw new Error(`微信读书接口请求失败:HTTP ${response.status}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const upgradeMessage = extractUpgradeMessage(data);
|
|
131
|
+
if (upgradeMessage) {
|
|
132
|
+
throw new Error(`微信读书 Skill 需要升级:${upgradeMessage}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const errcode = extractErrcode(data);
|
|
136
|
+
if (errcode !== 0) {
|
|
137
|
+
throw new Error(`微信读书接口返回错误 ${errcode}:${extractErrorMessage(data)}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const apiName = String(body.api_name);
|
|
141
|
+
return {
|
|
142
|
+
rawData: data,
|
|
143
|
+
displayData: `微信读书接口 ${apiName} 调用成功。\n\n${JSON.stringify(data, null, 2)}`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function wereadGatewayFunc(
|
|
148
|
+
args: WereadGatewayArgs,
|
|
149
|
+
thunkApi: any
|
|
150
|
+
): Promise<{ rawData: unknown; displayData: string }> {
|
|
151
|
+
return callToolApi(thunkApi, "/api/weread/gateway", args, { withAuth: true });
|
|
152
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// 文件路径: packages/ai/tools/writeFileTool.ts
|
|
2
2
|
|
|
3
3
|
import { bumpDevReloadSuppressIfSelfEditing } from "./devReloadUtils";
|
|
4
|
-
import { getToolBaseUrl } from "./toolApiClient";
|
|
4
|
+
import { buildToolRequestHeaders, getToolBaseUrl } from "./toolApiClient";
|
|
5
5
|
|
|
6
6
|
// ---- Types ----
|
|
7
7
|
|
|
@@ -107,7 +107,7 @@ export async function writeFilePreviewFunc(
|
|
|
107
107
|
export async function writeFileFunc(
|
|
108
108
|
args: WriteFileArgs,
|
|
109
109
|
thunkApi: any,
|
|
110
|
-
context?: { parentMessageId?: string; signal?: AbortSignal }
|
|
110
|
+
context?: { parentMessageId?: string; signal?: AbortSignal; agentKey?: string }
|
|
111
111
|
): Promise<{ rawData: any; displayData?: string }> {
|
|
112
112
|
const { filePath, content, overwrite } = args;
|
|
113
113
|
|
|
@@ -132,7 +132,10 @@ export async function writeFileFunc(
|
|
|
132
132
|
|
|
133
133
|
const response = await fetch(apiUrl, {
|
|
134
134
|
method: "POST",
|
|
135
|
-
headers: {
|
|
135
|
+
headers: buildToolRequestHeaders(thunkApi, {
|
|
136
|
+
withAuth: true,
|
|
137
|
+
agentKey: context?.agentKey,
|
|
138
|
+
}),
|
|
136
139
|
signal: context?.signal,
|
|
137
140
|
body: JSON.stringify({ filePath, content, overwrite }),
|
|
138
141
|
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { resolveAgentRuntimeConfigFromRecord } from "./agentConfigResolver";
|
|
4
|
+
|
|
5
|
+
describe("agent config resolver", () => {
|
|
6
|
+
test("keeps complete runtime-relevant agent config while normalizing tools", () => {
|
|
7
|
+
const rawRecord = {
|
|
8
|
+
dbKey: "agent-user-1-frontend",
|
|
9
|
+
key: "agent-user-1-frontend",
|
|
10
|
+
name: "Frontend implementer",
|
|
11
|
+
prompt: "Fix UI carefully.",
|
|
12
|
+
model: "gpt-5.4",
|
|
13
|
+
provider: "openai",
|
|
14
|
+
apiSource: "platform",
|
|
15
|
+
cliProvider: "codex",
|
|
16
|
+
customProviderUrl: "https://provider.example/v1",
|
|
17
|
+
toolNames: ["legacyTool", "readFile"],
|
|
18
|
+
tools: ["readFile", "applyEdit"],
|
|
19
|
+
runtimeBinding: { machineId: "machine-1", localWorkspaceMode: "task-worktree" },
|
|
20
|
+
delegation: { target: "local" },
|
|
21
|
+
temperature: 0.2,
|
|
22
|
+
top_p: 0.9,
|
|
23
|
+
max_tokens: 4096,
|
|
24
|
+
reasoning_effort: "medium",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
expect(resolveAgentRuntimeConfigFromRecord("agent-user-1-frontend", rawRecord)).toEqual({
|
|
28
|
+
key: "agent-user-1-frontend",
|
|
29
|
+
name: "Frontend implementer",
|
|
30
|
+
prompt: "Fix UI carefully.",
|
|
31
|
+
model: "gpt-5.4",
|
|
32
|
+
provider: "openai",
|
|
33
|
+
apiSource: "platform",
|
|
34
|
+
cliProvider: "codex",
|
|
35
|
+
customProviderUrl: "https://provider.example/v1",
|
|
36
|
+
toolNames: ["legacyTool", "readFile", "applyEdit"],
|
|
37
|
+
runtimeBinding: { machineId: "machine-1", localWorkspaceMode: "task-worktree" },
|
|
38
|
+
localWorkspaceMode: "task-worktree",
|
|
39
|
+
delegation: { target: "local" },
|
|
40
|
+
temperature: 0.2,
|
|
41
|
+
top_p: 0.9,
|
|
42
|
+
max_tokens: 4096,
|
|
43
|
+
reasoning_effort: "medium",
|
|
44
|
+
rawRecord,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("falls back provider to apiSource and tools to tools", () => {
|
|
49
|
+
expect(resolveAgentRuntimeConfigFromRecord("agent-user-1-cli", {
|
|
50
|
+
apiSource: "cli",
|
|
51
|
+
tools: ["execShell"],
|
|
52
|
+
})).toMatchObject({
|
|
53
|
+
key: "agent-user-1-cli",
|
|
54
|
+
provider: "cli",
|
|
55
|
+
apiSource: "cli",
|
|
56
|
+
toolNames: ["execShell"],
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("returns a minimal config for sparse records", () => {
|
|
61
|
+
expect(resolveAgentRuntimeConfigFromRecord("agent-user-1-empty", {
|
|
62
|
+
dbKey: "agent-user-1-empty",
|
|
63
|
+
})).toEqual({
|
|
64
|
+
key: "agent-user-1-empty",
|
|
65
|
+
rawRecord: {
|
|
66
|
+
dbKey: "agent-user-1-empty",
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { resolveAgentRuntimeConfigFromRecord } from "../agentRuntimeLocal";
|