nolo-cli 0.1.18 → 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.
Files changed (111) hide show
  1. package/README.md +9 -1
  2. package/agent-runtime/agentConfigOptions.ts +12 -0
  3. package/agent-runtime/agentRecordConfig.ts +99 -0
  4. package/agent-runtime/agentRecordKeys.ts +14 -0
  5. package/agent-runtime/dialogMessageRecord.ts +16 -0
  6. package/agent-runtime/dialogWritePlan.ts +130 -0
  7. package/agent-runtime/hostAdapter.ts +14 -0
  8. package/agent-runtime/hybridRecordStore.ts +147 -0
  9. package/agent-runtime/index.ts +69 -0
  10. package/agent-runtime/localLoop.ts +78 -6
  11. package/agent-runtime/localToolPolicy.ts +130 -0
  12. package/agent-runtime/localWorkspaceTools.ts +1532 -0
  13. package/agent-runtime/openAiCompatibleProvider.ts +70 -0
  14. package/agent-runtime/openAiCompatibleProviderConfig.ts +38 -0
  15. package/agent-runtime/platformChatProvider.ts +241 -0
  16. package/agent-runtime/taskWorkspace.ts +193 -0
  17. package/agent-runtime/types.ts +2 -0
  18. package/agent-runtime/workspaceSession.ts +76 -0
  19. package/agentAliases.ts +37 -0
  20. package/agentPullCommand.ts +1 -1
  21. package/agentRunCommand.ts +289 -54
  22. package/agentRuntimeCommands.ts +354 -164
  23. package/agentRuntimeLocal.ts +38 -0
  24. package/ai/agent/agentSlice.ts +10 -0
  25. package/ai/agent/buildEditingContext.ts +5 -0
  26. package/ai/agent/buildSystemPrompt.ts +41 -18
  27. package/ai/agent/canvasEditingContext.ts +49 -0
  28. package/ai/agent/cliExecutor.ts +15 -4
  29. package/ai/agent/createAgentSchema.ts +2 -0
  30. package/ai/agent/executeToolCall.ts +3 -2
  31. package/ai/agent/hooks/usePublicAgents.ts +6 -0
  32. package/ai/agent/pageBuilderHandoffRules.ts +75 -0
  33. package/ai/agent/runAgentClientLoop.ts +4 -1
  34. package/ai/agent/runtimeGuidance.ts +19 -0
  35. package/ai/agent/server/fetchPublicAgents.ts +51 -1
  36. package/ai/agent/streamAgentChatTurn.ts +20 -2
  37. package/ai/agent/streamAgentChatTurnUtils.ts +60 -16
  38. package/ai/chat/accumulateToolCallChunks.ts +40 -9
  39. package/ai/chat/parseApiError.ts +3 -0
  40. package/ai/chat/sendOpenAICompletionsRequest.native.ts +23 -10
  41. package/ai/chat/sendOpenAICompletionsRequest.ts +13 -1
  42. package/ai/chat/updateTotalUsage.ts +26 -9
  43. package/ai/llm/deepinfra.ts +51 -0
  44. package/ai/llm/getPricing.ts +6 -0
  45. package/ai/llm/kimi.ts +2 -0
  46. package/ai/llm/openrouterModels.ts +0 -135
  47. package/ai/llm/providers.ts +1 -0
  48. package/ai/llm/types.ts +8 -0
  49. package/ai/taskRun/taskRunProtocol.ts +823 -0
  50. package/ai/token/calculatePrice.ts +30 -0
  51. package/ai/token/externalToolCost.ts +49 -29
  52. package/ai/token/prepareTokenUsageData.ts +6 -1
  53. package/ai/token/serverTokenWriter.ts +4 -2
  54. package/ai/tools/agent/agentTools.ts +21 -0
  55. package/ai/tools/agent/presets/appBuilderPreset.ts +7 -0
  56. package/ai/tools/agent/streamParallelAgentsTool.ts +2 -1
  57. package/ai/tools/agent/taskRunTool.ts +112 -0
  58. package/ai/tools/applyEditTool.ts +6 -3
  59. package/ai/tools/applyLineEditsTool.ts +6 -3
  60. package/ai/tools/checkEnvTool.ts +14 -9
  61. package/ai/tools/codeSearchTool.ts +17 -5
  62. package/ai/tools/execBashTool.ts +33 -29
  63. package/ai/tools/fetchWebpageSupport.ts +24 -0
  64. package/ai/tools/fetchWebpageTool.ts +18 -5
  65. package/ai/tools/index.ts +158 -0
  66. package/ai/tools/jdProductScraperTool.ts +821 -0
  67. package/ai/tools/listFilesTool.ts +6 -3
  68. package/ai/tools/localFilesTool.ts +200 -0
  69. package/ai/tools/readFileTool.ts +6 -3
  70. package/ai/tools/searchRepoTool.ts +6 -3
  71. package/ai/tools/table/rowTools.ts +6 -1
  72. package/ai/tools/taobaoTmallProductScraperTool.ts +49 -0
  73. package/ai/tools/toolApiClient.ts +20 -6
  74. package/ai/tools/wereadGatewayTool.ts +152 -0
  75. package/ai/tools/writeFileTool.ts +6 -3
  76. package/client/agentConfigResolver.test.ts +70 -0
  77. package/client/agentConfigResolver.ts +1 -0
  78. package/client/agentRun.test.ts +361 -7
  79. package/client/agentRun.ts +449 -63
  80. package/client/hybridRecordStore.test.ts +115 -0
  81. package/client/hybridRecordStore.ts +41 -0
  82. package/client/localAgentRecords.test.ts +27 -0
  83. package/client/localAgentRecords.ts +7 -0
  84. package/client/localDialogRecords.test.ts +124 -0
  85. package/client/localDialogRecords.ts +30 -0
  86. package/client/localProviderResolver.test.ts +78 -0
  87. package/client/localProviderResolver.ts +1 -0
  88. package/client/localRuntimeAdapter.test.ts +813 -20
  89. package/client/localRuntimeAdapter.ts +279 -232
  90. package/client/localRuntimeDryRun.test.ts +116 -0
  91. package/client/localToolPolicy.ts +8 -81
  92. package/client/taskRunPrompt.ts +26 -0
  93. package/client/taskWorktree.ts +8 -0
  94. package/client/workspaceSession.test.ts +57 -0
  95. package/client/workspaceSession.ts +11 -0
  96. package/commandRegistry.ts +23 -6
  97. package/connectorRunArtifact.ts +121 -0
  98. package/database/actions/write.ts +16 -2
  99. package/database/hooks/useUserData.ts +9 -3
  100. package/database/server/dataHandlers.ts +18 -20
  101. package/database/server/emailRepository.ts +3 -3
  102. package/database/server/patch.ts +18 -10
  103. package/database/server/query.ts +43 -4
  104. package/database/server/read.ts +24 -38
  105. package/database/server/recordIdentity.ts +100 -0
  106. package/database/server/write.ts +21 -25
  107. package/index.ts +70 -33
  108. package/machineCommands.ts +318 -144
  109. package/package.json +4 -1
  110. package/tableCommands.ts +181 -0
  111. package/taskRunCommand.ts +237 -0
@@ -111,6 +111,22 @@ const scaleModelInputOutputPrice = (
111
111
  output: price.output * multiplier,
112
112
  });
113
113
 
114
+ const scaleModelServiceTierPrice = (
115
+ price: ModelPrice,
116
+ inputOutputMultiplier: number,
117
+ cacheMultiplier?: number
118
+ ): ModelPrice => ({
119
+ ...price,
120
+ input: price.input * inputOutputMultiplier,
121
+ output: price.output * inputOutputMultiplier,
122
+ ...(typeof cacheMultiplier === "number" && typeof price.cachingWrite === "number"
123
+ ? { cachingWrite: price.cachingWrite * cacheMultiplier }
124
+ : {}),
125
+ ...(typeof cacheMultiplier === "number" && typeof price.cachingRead === "number"
126
+ ? { cachingRead: price.cachingRead * cacheMultiplier }
127
+ : {}),
128
+ });
129
+
114
130
  const resolveGoogleServiceTierPrice = (
115
131
  model: Model,
116
132
  price: ModelPrice,
@@ -128,6 +144,20 @@ const resolveGoogleServiceTierPrice = (
128
144
  typeof imageOutputPrice === "number"
129
145
  ? { ...price, output: imageOutputPrice }
130
146
  : price;
147
+ const serviceTierMultiplier =
148
+ normalizedTier === "batch" ||
149
+ normalizedTier === "flex" ||
150
+ normalizedTier === "priority"
151
+ ? model.serviceTierPriceMultipliers?.[normalizedTier]
152
+ : undefined;
153
+
154
+ if (serviceTierMultiplier) {
155
+ return scaleModelServiceTierPrice(
156
+ priceWithImageOutput,
157
+ serviceTierMultiplier.inputOutput,
158
+ serviceTierMultiplier.cache
159
+ );
160
+ }
131
161
 
132
162
  if (normalizedTier === "flex" || normalizedTier === "batch") {
133
163
  return scaleModelInputOutputPrice(priceWithImageOutput, 0.5);
@@ -11,13 +11,13 @@
11
11
  * chargeExternalTool({ userId, toolId, ... })。
12
12
  */
13
13
 
14
- import { ulid } from "ulid";
15
- import { format } from "date-fns";
16
- import serverDb from "database/server/db";
17
- import { createKey, createTokenKey, createTokenStatsKey } from "database/keys";
18
- import { deductUserBalance } from "auth/server/deduct";
19
- import { DataType } from "create/types";
20
- import pino from "pino";
14
+ import { ulid } from "ulid";
15
+ import { format } from "date-fns";
16
+ import serverDb from "database/server/db";
17
+ import { createKey, createTokenKey, createTokenStatsKey } from "database/keys";
18
+ import { chargeTokenUsageWithLedger } from "auth/server/tokenUsageBilling";
19
+ import { DataType } from "create/types";
20
+ import pino from "pino";
21
21
  import { applyServerDialogProjectionDelta } from "./serverDialogProjection";
22
22
 
23
23
  const logger = pino({ name: "external-tool-cost" });
@@ -219,23 +219,42 @@ export async function chargeExternalTool(
219
219
 
220
220
  cost = Number(cost.toFixed(6));
221
221
  if (cost <= 0) return { cost: 0, skipped: true, reason: "zero_cost" };
222
-
223
- // 扣余额
224
- const deductReason = reason ?? toolId;
225
- await deductUserBalance(userId, cost, deductReason);
226
-
227
- // 写 usage 记录(与 LLM token 记录同格式,显示在 Usage 页面)
228
- await writeUsageRecord({
229
- userId,
230
- toolId,
231
- dialogId,
222
+
223
+ const deductReason = reason ?? toolId;
224
+ // usage 记录(与 LLM token 记录同格式,显示在 Usage 页面)
225
+ const usageRecord = await writeUsageRecord({
226
+ userId,
227
+ toolId,
228
+ dialogId,
232
229
  inputTokens,
233
230
  outputTokens,
234
- cost,
235
- provider: pricing.provider,
236
- });
237
-
238
- if (dialogId) {
231
+ cost,
232
+ provider: pricing.provider,
233
+ });
234
+
235
+ const chargeResult = await chargeTokenUsageWithLedger({
236
+ store: serverDb,
237
+ userId,
238
+ tokenKey: usageRecord.recordKey,
239
+ tokenRecord: usageRecord.tokenRecord,
240
+ reason: deductReason,
241
+ txId: `token-${usageRecord.recordKey}`,
242
+ });
243
+
244
+ if (!chargeResult.success) {
245
+ logger.warn(
246
+ {
247
+ userId,
248
+ toolId,
249
+ tokenKey: usageRecord.recordKey,
250
+ error: chargeResult.error,
251
+ },
252
+ "External tool split-ledger charge failed",
253
+ );
254
+ return { cost, skipped: true, reason: chargeResult.error ?? "charge_failed" };
255
+ }
256
+
257
+ if (dialogId) {
239
258
  try {
240
259
  await applyServerDialogProjectionDelta({
241
260
  userId,
@@ -260,7 +279,7 @@ export async function chargeExternalTool(
260
279
  // 内部:写 usage 记录到 LevelDB
261
280
  // ─────────────────────────────────────────
262
281
 
263
- async function writeUsageRecord(opts: {
282
+ async function writeUsageRecord(opts: {
264
283
  userId: string;
265
284
  toolId: string;
266
285
  dialogId?: string;
@@ -268,7 +287,7 @@ async function writeUsageRecord(opts: {
268
287
  outputTokens: number;
269
288
  cost: number;
270
289
  provider: string;
271
- }): Promise<void> {
290
+ }): Promise<{ recordKey: string; tokenRecord: any }> {
272
291
  const { userId, toolId, dialogId, inputTokens, outputTokens, cost, provider } = opts;
273
292
  const timestamp = Date.now();
274
293
  const dateKey = format(timestamp, "yyyy-MM-dd");
@@ -314,8 +333,9 @@ async function writeUsageRecord(opts: {
314
333
  models: { ...base.models, [toolId]: inc(base.models?.[toolId]) },
315
334
  providers: { ...base.providers, [provider]: inc(base.providers?.[provider]) },
316
335
  };
317
- await serverDb.batch([
318
- { type: "put", key: recordKey, value: record },
319
- { type: "put", key: statsKey, value: newStats },
320
- ]);
321
- }
336
+ await serverDb.batch([
337
+ { type: "put", key: recordKey, value: record },
338
+ { type: "put", key: statsKey, value: newStats },
339
+ ]);
340
+ return { recordKey, tokenRecord: record };
341
+ }
@@ -4,13 +4,17 @@ import { normalizeUsage } from "./normalizeUsage";
4
4
  import { resolveBillingTarget } from "./resolveBillingTarget";
5
5
  import type { RawUsage, TokenUsageData } from "./types";
6
6
 
7
+ type SharingLevel = "default" | "split" | "full";
8
+
7
9
  interface BillingAgentConfig {
8
10
  model: string;
9
11
  provider?: string;
10
12
  apiSource?: string;
11
13
  inputPrice?: number;
12
14
  outputPrice?: number;
15
+ sharingLevel?: SharingLevel;
13
16
  id?: string;
17
+ userId?: string;
14
18
  }
15
19
 
16
20
  interface PrepareTokenUsageDataParams {
@@ -60,9 +64,10 @@ export const prepareTokenUsageData = ({
60
64
  ? {
61
65
  input: agentConfig.inputPrice ?? 0,
62
66
  output: agentConfig.outputPrice ?? 0,
63
- creatorId: extractUserId(agentConfig.id),
67
+ creatorId: agentConfig.userId ?? extractUserId(agentConfig.id),
64
68
  }
65
69
  : undefined,
70
+ sharingLevel: agentConfig.sharingLevel,
66
71
  });
67
72
 
68
73
  return {
@@ -25,13 +25,15 @@ interface WriteTokenOpts {
25
25
  apiSource?: string;
26
26
  inputPrice?: number;
27
27
  outputPrice?: number;
28
+ sharingLevel?: "default" | "split" | "full";
28
29
  id?: string;
30
+ userId?: string;
29
31
  };
30
32
  runId: string; // 对应 dialogId
31
33
  rawUsage: RawUsage;
32
34
  }
33
35
 
34
- export async function writeServerTokenRecord(opts: WriteTokenOpts): Promise<{ cost: number }> {
36
+ export async function writeServerTokenRecord(opts: WriteTokenOpts): Promise<{ cost: number; recordKey: string; tokenRecord: any }> {
35
37
  const { userId, username = "", agentKey, agentConfig, runId, rawUsage } = opts;
36
38
 
37
39
  const prepared = prepareTokenUsageData({
@@ -139,5 +141,5 @@ export async function writeServerTokenRecord(opts: WriteTokenOpts): Promise<{ co
139
141
  }
140
142
 
141
143
  logger.info({ userId, agentKey, cost, model: billedModel, provider: recordProvider }, "Token record written");
142
- return { cost };
144
+ return { cost, recordKey, tokenRecord };
143
145
  }
@@ -36,6 +36,10 @@ import {
36
36
  runLlmFunctionSchema,
37
37
  runLlmToolFunc,
38
38
  } from "./runLlmTool";
39
+ import {
40
+ taskRunFunctionSchema,
41
+ taskRunFunc,
42
+ } from "./taskRunTool";
39
43
 
40
44
  import type { ToolDefinition } from "../index";
41
45
 
@@ -173,4 +177,21 @@ export const agentToolDefinitions: ToolDefinition[] = [
173
177
  behavior: "action",
174
178
  uiGroup: "agent",
175
179
  },
180
+ {
181
+ id: "taskRun",
182
+ schema: taskRunFunctionSchema,
183
+ executor: taskRunFunc,
184
+ description: {
185
+ name: "taskRun",
186
+ description:
187
+ "读写任务卡里的轻量 task-run 上下文,让 PM、实现 agent、reviewer 共享工作项、交接物、阻塞和 review 状态。",
188
+ category: "计划与编排",
189
+ },
190
+ behavior: "action",
191
+ uiGroup: "agent",
192
+ capability: "general",
193
+ riskLevel: "medium",
194
+ costLevel: "low",
195
+ defaultConsent: "auto",
196
+ },
176
197
  ];
@@ -1,6 +1,13 @@
1
1
  /**
2
2
  * App Builder 预设:面向非技术用户的网站型小应用构建助手配置
3
3
  * 用途:在 AgentForm 中预填充,或通过 createAgent 工具一键创建
4
+ *
5
+ * Maintenance note:
6
+ * - The online App Builder agent record is the runtime source of truth.
7
+ * - Keep this preset as a creation/bootstrap template only.
8
+ * - To make App Builder fully online-truth-only, replace consumers of this
9
+ * preset with a read/pull of agent-pub-01APPBUILDER00000001YAII3I and leave
10
+ * only a tiny emergency placeholder here.
4
11
  */
5
12
  import { FIREWORKS_KIMI_LATEST_MODEL } from "ai/llm/kimi";
6
13
 
@@ -45,7 +45,8 @@ export const streamParallelAgentsFunctionSchema = {
45
45
  },
46
46
  timeoutMs: {
47
47
  type: "number",
48
- description: "可选:单个分支的超时时间(毫秒)。",
48
+ description:
49
+ "可选:单个分支的超时时间(毫秒)。本地 CLI/machine agent 会把该值作为本次 connector 请求预算。",
49
50
  },
50
51
  budgetCredits: {
51
52
  type: "number",
@@ -0,0 +1,112 @@
1
+ export const taskRunFunctionSchema = {
2
+ name: "taskRun",
3
+ description:
4
+ "读写任务卡里的轻量 task-run 上下文。用于 PM、实现 agent、reviewer 共享 workItems、artifact、blocker、review 状态;不直接执行代码、不固定具体工作流。",
5
+ parameters: {
6
+ type: "object",
7
+ properties: {
8
+ action: {
9
+ type: "string",
10
+ enum: [
11
+ "readContext",
12
+ "readTaskContext",
13
+ "queryReadyWorkItems",
14
+ "updateWorkItems",
15
+ "upsertWorkItem",
16
+ "claimOrDispatch",
17
+ "claimWorkItem",
18
+ "openDialog",
19
+ "recordArtifact",
20
+ "markBlocked",
21
+ "setBlocker",
22
+ "clearBlocker",
23
+ "requestReview",
24
+ "recordReview",
25
+ "submitOutcome",
26
+ "completeWorkItem",
27
+ "closeWorkItem",
28
+ ],
29
+ description:
30
+ "要执行的 task-run 原语动作。新 agent 优先使用 readTaskContext/queryReadyWorkItems/upsertWorkItem/claimWorkItem/openDialog/setBlocker/clearBlocker/submitOutcome/closeWorkItem;旧动作保留兼容。",
31
+ },
32
+ tenantId: { type: "string", description: "任务表所属用户/租户 ID。" },
33
+ tableId: { type: "string", description: "任务表 ID,例如 NOLOTASKBOARD。" },
34
+ rowId: { type: "string", description: "任务卡 rowId。" },
35
+ dbKey: { type: "string", description: "任务卡完整 dbKey;有 dbKey 时可不传 rowId。" },
36
+ workItems: {
37
+ type: "array",
38
+ description: "updateWorkItems 使用。每项是一个轻量工作项,可包含 dependsOn、writeScope、ownerAgent、reviewPolicy。",
39
+ items: { type: "object" },
40
+ },
41
+ workItemId: {
42
+ type: "string",
43
+ description: "claimWorkItem / openDialog / submitOutcome / closeWorkItem 等动作定位的 work item ID。",
44
+ },
45
+ targetWorkItemId: {
46
+ type: "string",
47
+ description: "recordReview 使用。明确被 review 的目标 work item ID;未提供时兼容使用 workItemId。",
48
+ },
49
+ agentKey: { type: "string", description: "claimWorkItem / openDialog 使用。接管或参与该 work item 的 agent key。" },
50
+ role: { type: "string", description: "claimWorkItem 使用。agent 在本任务里的角色标签。" },
51
+ dialogId: { type: "string", description: "相关 agent 对话 ID。" },
52
+ participantAgentKeys: {
53
+ type: "array",
54
+ items: { type: "string" },
55
+ description: "openDialog 使用。参与该对话的 agent key 列表。",
56
+ },
57
+ purpose: {
58
+ type: "string",
59
+ description: "openDialog 使用。对话目的,例如 assignment/review/status/question/handoff。",
60
+ },
61
+ message: {
62
+ type: "string",
63
+ description: "openDialog 使用。给目标 agent 的简短消息。",
64
+ },
65
+ artifact: {
66
+ type: "object",
67
+ description: "recordArtifact 使用。包含 kind/ref/summary,可选 producerRunId/workItemId。",
68
+ },
69
+ artifactIds: {
70
+ type: "array",
71
+ items: { type: "string" },
72
+ description: "requestReview / recordReview / submitOutcome 使用。被 review 或提交的 artifact id 列表。",
73
+ },
74
+ blocker: {
75
+ type: "object",
76
+ description: "setBlocker/markBlocked 使用。包含 layer/code/message,可选 evidence。",
77
+ },
78
+ blockerId: {
79
+ type: "string",
80
+ description: "clearBlocker 使用。要清除的 blocker id。",
81
+ },
82
+ reviewerAgentKey: {
83
+ type: "string",
84
+ description:
85
+ "requestReview / recordReview 使用。Reviewer agent key。requestReview 配合 workItemId 时会创建 review work item 和 review dialog link。",
86
+ },
87
+ reviewStatus: {
88
+ type: "string",
89
+ enum: ["passed", "needs_changes", "blocked"],
90
+ description: "recordReview 使用。",
91
+ },
92
+ findings: {
93
+ type: "array",
94
+ items: { type: "object" },
95
+ description: "recordReview 使用。review findings,包含 priority/summary/file/line/workItemId。",
96
+ },
97
+ summary: {
98
+ type: "string",
99
+ description: "submitOutcome / closeWorkItem 使用。对本次提交或关闭的简短总结。",
100
+ },
101
+ reason: {
102
+ type: "string",
103
+ description: "可选。说明本次状态变更原因;submitOutcome 未提供 summary 时可作为 summary。",
104
+ },
105
+ },
106
+ required: ["action"],
107
+ },
108
+ };
109
+
110
+ export async function taskRunFunc(): Promise<never> {
111
+ throw new Error("taskRun is a server-side agent tool. Use it through /api/agent/run.");
112
+ }
@@ -1,5 +1,5 @@
1
1
  import { bumpDevReloadSuppressIfSelfEditing } from "./devReloadUtils";
2
- import { getToolBaseUrl } from "./toolApiClient";
2
+ import { buildToolRequestHeaders, getToolBaseUrl } from "./toolApiClient";
3
3
 
4
4
  type EditMatchOptions = {
5
5
  occurrence?: number;
@@ -200,7 +200,7 @@ function assertValidArgs(args: ApplyEditArgs): void {
200
200
  export async function applyEditFunc(
201
201
  args: ApplyEditArgs,
202
202
  thunkApi: any,
203
- context?: { parentMessageId?: string; signal?: AbortSignal }
203
+ context?: { parentMessageId?: string; signal?: AbortSignal; agentKey?: string }
204
204
  ): Promise<{ rawData: any; displayData?: string }> {
205
205
  assertValidArgs(args);
206
206
 
@@ -217,7 +217,10 @@ export async function applyEditFunc(
217
217
  const apiUrl = `${baseUrl.replace(/\/+$/, "")}/api/apply-edit`;
218
218
  const response = await fetch(apiUrl, {
219
219
  method: "POST",
220
- headers: { "Content-Type": "application/json" },
220
+ headers: buildToolRequestHeaders(thunkApi, {
221
+ withAuth: true,
222
+ agentKey: context?.agentKey,
223
+ }),
221
224
  signal: context?.signal,
222
225
  body: JSON.stringify({ filePath, edits }),
223
226
  });
@@ -1,7 +1,7 @@
1
1
  // 文件路径: packages/ai/tools/applyLineEditsTool.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
 
@@ -253,7 +253,7 @@ function assertValidArgs(args: ApplyLineEditsArgs): void {
253
253
  export async function applyLineEditsFunc(
254
254
  args: ApplyLineEditsArgs,
255
255
  thunkApi: any,
256
- context?: { parentMessageId?: string; signal?: AbortSignal }
256
+ context?: { parentMessageId?: string; signal?: AbortSignal; agentKey?: string }
257
257
  ): Promise<{ rawData: any; displayData?: string }> {
258
258
  assertValidArgs(args);
259
259
 
@@ -272,7 +272,10 @@ export async function applyLineEditsFunc(
272
272
 
273
273
  const response = await fetch(apiUrl, {
274
274
  method: "POST",
275
- headers: { "Content-Type": "application/json" },
275
+ headers: buildToolRequestHeaders(thunkApi, {
276
+ withAuth: true,
277
+ agentKey: context?.agentKey,
278
+ }),
276
279
  signal: context?.signal,
277
280
  body: JSON.stringify({ filePath, edits }),
278
281
  });
@@ -25,17 +25,22 @@ export const checkEnvFunctionSchema = {
25
25
  },
26
26
  };
27
27
 
28
- export async function checkEnvFunc(
29
- args: CheckEnvArgs,
30
- thunkApi: any
31
- ): Promise<{ rawData: any; displayData: string }> {
28
+ export async function checkEnvFunc(
29
+ args: CheckEnvArgs,
30
+ thunkApi: any,
31
+ context?: { agentKey?: string }
32
+ ): Promise<{ rawData: any; displayData: string }> {
32
33
  const check = args?.check === "context" ? "context" : "build";
33
34
 
34
- const result = await callToolApi<any>(
35
- thunkApi,
36
- "/api/check-env",
37
- { check }
38
- );
35
+ const result = await callToolApi<any>(
36
+ thunkApi,
37
+ "/api/check-env",
38
+ { check },
39
+ {
40
+ withAuth: true,
41
+ agentKey: context?.agentKey,
42
+ }
43
+ );
39
44
 
40
45
  const ok = !!result?.ok;
41
46
  const exitCode = typeof result?.exitCode === "number" ? result.exitCode : -1;
@@ -1,4 +1,4 @@
1
- import { getToolBaseUrl } from "./toolApiClient";
1
+ import { buildToolRequestHeaders, getToolBaseUrl } from "./toolApiClient";
2
2
 
3
3
  export type CodeSearchArgs = {
4
4
  query?: string;
@@ -70,7 +70,7 @@ export const codeSearchFunctionSchema = {
70
70
  export async function codeSearchFunc(
71
71
  args: CodeSearchArgs,
72
72
  thunkApi: any,
73
- context?: { signal?: AbortSignal },
73
+ context?: { signal?: AbortSignal; agentKey?: string },
74
74
  ): Promise<{ rawData: any; displayData?: string }> {
75
75
  const baseUrl = getToolBaseUrl(thunkApi);
76
76
  const apiUrl = `${baseUrl}/api/code-search`;
@@ -78,7 +78,10 @@ export async function codeSearchFunc(
78
78
 
79
79
  const response = await fetch(apiUrl, {
80
80
  method: "POST",
81
- headers: { "Content-Type": "application/json" },
81
+ headers: buildToolRequestHeaders(thunkApi, {
82
+ withAuth: true,
83
+ agentKey: context?.agentKey,
84
+ }),
82
85
  signal: context?.signal,
83
86
  body: JSON.stringify({
84
87
  query: args.query,
@@ -90,9 +93,18 @@ export async function codeSearchFunc(
90
93
  }),
91
94
  });
92
95
 
93
- const data = await response.json() as any;
96
+ const responseText = await response.text();
97
+ let data: any = null;
98
+ try {
99
+ data = responseText ? JSON.parse(responseText) : {};
100
+ } catch {
101
+ data = null;
102
+ }
94
103
  if (!response.ok || data?.error) {
95
- throw new Error(data?.error || `codeSearch 请求失败: ${response.status}`);
104
+ throw new Error(
105
+ data?.error ||
106
+ `codeSearch 请求失败: ${response.status}${responseText ? `. 响应: ${responseText}` : ""}`
107
+ );
96
108
  }
97
109
 
98
110
  if (mode === "files") {
@@ -142,18 +142,22 @@ function isDangerousCommand(rawCommand: string): boolean {
142
142
  return false;
143
143
  }
144
144
 
145
- async function requestExec(
146
- args: ExecBashRequestArgs,
147
- thunkApi?: any,
148
- endpoint = "/api/exec-bash",
149
- ): Promise<any> {
145
+ async function requestExec(
146
+ args: ExecBashRequestArgs,
147
+ thunkApi?: any,
148
+ endpoint = "/api/exec-bash",
149
+ context?: { agentKey?: string },
150
+ ): Promise<any> {
150
151
  const payload = Object.fromEntries(
151
152
  Object.entries(args).filter(([, value]) => value !== undefined)
152
153
  );
153
154
 
154
- if (thunkApi?.getState) {
155
- return callToolApi(thunkApi, endpoint, payload);
156
- }
155
+ if (thunkApi?.getState) {
156
+ return callToolApi(thunkApi, endpoint, payload, {
157
+ withAuth: true,
158
+ agentKey: context?.agentKey,
159
+ });
160
+ }
157
161
 
158
162
  const baseUrl =
159
163
  (typeof process !== "undefined" &&
@@ -309,7 +313,7 @@ export const startExecShellSession = (
309
313
  "/api/exec-shell",
310
314
  );
311
315
 
312
- export async function execBashFunc(args: any): Promise<{
316
+ export async function execBashFunc(args: any, thunkApi?: any, context?: { agentKey?: string }): Promise<{
313
317
  rawData: any;
314
318
  displayData: string;
315
319
  }> {
@@ -327,15 +331,15 @@ export async function execBashFunc(args: any): Promise<{
327
331
  }
328
332
 
329
333
  try {
330
- const json = await requestExec({
331
- ...(trimmedCommand ? { command: trimmedCommand } : {}),
332
- ...(cwd ? { cwd } : {}),
333
- ...(interactive ? { interactive: true } : {}),
334
- ...(pty ? { pty: true } : {}),
335
- ...(isSessionFollowup ? { sessionId } : {}),
336
- ...(typeof input === "string" ? { input } : {}),
337
- ...(close ? { close: true } : {}),
338
- });
334
+ const json = await requestExec({
335
+ ...(trimmedCommand ? { command: trimmedCommand } : {}),
336
+ ...(cwd ? { cwd } : {}),
337
+ ...(interactive ? { interactive: true } : {}),
338
+ ...(pty ? { pty: true } : {}),
339
+ ...(isSessionFollowup ? { sessionId } : {}),
340
+ ...(typeof input === "string" ? { input } : {}),
341
+ ...(close ? { close: true } : {}),
342
+ }, thunkApi, "/api/exec-bash", context);
339
343
  return formatExecResult("execBash", { cwd }, json, trimmedCommand, sessionId);
340
344
  } catch (error: any) {
341
345
  const msg = `execBash 调用失败:${error?.message || String(error)}`;
@@ -343,7 +347,7 @@ export async function execBashFunc(args: any): Promise<{
343
347
  }
344
348
  }
345
349
 
346
- export async function execShellFunc(args: any): Promise<{
350
+ export async function execShellFunc(args: any, thunkApi?: any, context?: { agentKey?: string }): Promise<{
347
351
  rawData: any;
348
352
  displayData: string;
349
353
  }> {
@@ -361,16 +365,16 @@ export async function execShellFunc(args: any): Promise<{
361
365
  }
362
366
 
363
367
  try {
364
- const json = await requestExec({
365
- ...(trimmedCommand ? { command: trimmedCommand } : {}),
366
- ...(cwd ? { cwd } : {}),
367
- ...(shell ? { shell } : {}),
368
- ...(interactive ? { interactive: true } : {}),
369
- ...(pty ? { pty: true } : {}),
370
- ...(isSessionFollowup ? { sessionId } : {}),
371
- ...(typeof input === "string" ? { input } : {}),
372
- ...(close ? { close: true } : {}),
373
- }, undefined, "/api/exec-shell");
368
+ const json = await requestExec({
369
+ ...(trimmedCommand ? { command: trimmedCommand } : {}),
370
+ ...(cwd ? { cwd } : {}),
371
+ ...(shell ? { shell } : {}),
372
+ ...(interactive ? { interactive: true } : {}),
373
+ ...(pty ? { pty: true } : {}),
374
+ ...(isSessionFollowup ? { sessionId } : {}),
375
+ ...(typeof input === "string" ? { input } : {}),
376
+ ...(close ? { close: true } : {}),
377
+ }, thunkApi, "/api/exec-shell", context);
374
378
  return formatExecResult("execShell", { cwd, shell }, json, trimmedCommand, sessionId);
375
379
  } catch (error: any) {
376
380
  const msg = `execShell 调用失败:${error?.message || String(error)}`;
@@ -16,6 +16,30 @@ export interface ExtractionIssue {
16
16
  message: string;
17
17
  }
18
18
 
19
+ export function extractAdvertisedMarkdownUrl(markdown: string, sourceUrl: string) {
20
+ if (!markdown.trim()) return null;
21
+
22
+ let source: URL;
23
+ try {
24
+ source = new URL(sourceUrl);
25
+ } catch {
26
+ return null;
27
+ }
28
+
29
+ const hintMatch = markdown.match(/Get this page as Markdown:\s*(https?:\/\/[^\s)]+)/i);
30
+ const rawHint = hintMatch?.[1]?.trim();
31
+ if (!rawHint) return null;
32
+
33
+ try {
34
+ const hinted = new URL(rawHint);
35
+ if (hinted.origin !== source.origin) return null;
36
+ if (!/\.md$/i.test(hinted.pathname)) return null;
37
+ return hinted.toString();
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
19
43
  function normalizePathname(pathname: string) {
20
44
  return pathname
21
45
  .replace(/\/index\.md$/i, "/")