nolo-cli 0.1.10 → 0.1.11

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 (240) hide show
  1. package/README.md +0 -32
  2. package/agentRuntimeCommands.ts +3 -3
  3. package/commandRegistry.ts +2 -2
  4. package/machineCommands.ts +31 -6
  5. package/package.json +6 -22
  6. package/ai/agent/_executeModel.ts +0 -118
  7. package/ai/agent/agentSlice.ts +0 -525
  8. package/ai/agent/appWorkingMemory.ts +0 -126
  9. package/ai/agent/avatarUtils.ts +0 -24
  10. package/ai/agent/buildEditingContext.ts +0 -373
  11. package/ai/agent/buildSystemPrompt.ts +0 -532
  12. package/ai/agent/cleanAgentMessages.ts +0 -140
  13. package/ai/agent/cliChatClient.ts +0 -119
  14. package/ai/agent/cliExecutor.ts +0 -733
  15. package/ai/agent/cliPrompt.ts +0 -10
  16. package/ai/agent/contextCompiler.ts +0 -107
  17. package/ai/agent/contextLayerContract.ts +0 -44
  18. package/ai/agent/createAgentSchema.ts +0 -234
  19. package/ai/agent/executeToolCall.ts +0 -58
  20. package/ai/agent/fetchAgentContexts.ts +0 -42
  21. package/ai/agent/generatePrompt.ts +0 -3
  22. package/ai/agent/getFullChatContextKeys.ts +0 -168
  23. package/ai/agent/hooks/fetchPublicAgents.ts +0 -133
  24. package/ai/agent/hooks/useAgentConfig.ts +0 -61
  25. package/ai/agent/hooks/useAgentDialog.ts +0 -35
  26. package/ai/agent/hooks/useAgentFormValidation.ts +0 -202
  27. package/ai/agent/hooks/usePublicAgents.ts +0 -473
  28. package/ai/agent/machineRunPermissions.ts +0 -95
  29. package/ai/agent/persistMessageWithFixedId.ts +0 -37
  30. package/ai/agent/planSlice.ts +0 -259
  31. package/ai/agent/referenceUtils.ts +0 -229
  32. package/ai/agent/runAgentBackground.ts +0 -238
  33. package/ai/agent/runAgentClientLoop.ts +0 -138
  34. package/ai/agent/runtimeGuidance.ts +0 -97
  35. package/ai/agent/runtimeServerBase.ts +0 -37
  36. package/ai/agent/server/fetchPublicAgents.ts +0 -128
  37. package/ai/agent/startParallelAgentStreams.ts +0 -424
  38. package/ai/agent/startupProtocol.ts +0 -53
  39. package/ai/agent/streamAgentChatTurn.ts +0 -1278
  40. package/ai/agent/streamAgentChatTurnUtils.ts +0 -738
  41. package/ai/agent/types.ts +0 -71
  42. package/ai/agent/utils/imageOutput.ts +0 -33
  43. package/ai/agent/utils/sortUtils.ts +0 -250
  44. package/ai/agent/web/referencePickerUtils.ts +0 -146
  45. package/ai/ai.locale.ts +0 -1079
  46. package/ai/chat/accumulateToolCallChunks.ts +0 -95
  47. package/ai/chat/fetchUtils.native.ts +0 -276
  48. package/ai/chat/fetchUtils.ts +0 -153
  49. package/ai/chat/parseApiError.ts +0 -64
  50. package/ai/chat/parseMultilineSSE.ts +0 -95
  51. package/ai/chat/sendOpenAICompletionsRequest.native.ts +0 -682
  52. package/ai/chat/sendOpenAICompletionsRequest.ts +0 -703
  53. package/ai/chat/sendOpenAIResponseRequest.ts +0 -491
  54. package/ai/chat/shouldUseServerProxy.ts +0 -18
  55. package/ai/chat/sseClient.native.ts +0 -91
  56. package/ai/chat/sseClient.ts +0 -67
  57. package/ai/chat/streamReader.native.ts +0 -31
  58. package/ai/chat/streamReader.ts +0 -62
  59. package/ai/chat/updateTotalUsage.ts +0 -72
  60. package/ai/context/buildReferenceContext.ts +0 -437
  61. package/ai/context/calculateContextUsage.ts +0 -133
  62. package/ai/context/retention.ts +0 -165
  63. package/ai/context/tokenUtils.ts +0 -78
  64. package/ai/index.ts +0 -1
  65. package/ai/llm/calculateGeminiImageTokens.ts +0 -57
  66. package/ai/llm/deepinfra.ts +0 -28
  67. package/ai/llm/fireworks.ts +0 -50
  68. package/ai/llm/generateRequestBody.ts +0 -165
  69. package/ai/llm/getModelContextWindow.ts +0 -84
  70. package/ai/llm/getNoloKey.ts +0 -31
  71. package/ai/llm/getPricing.ts +0 -199
  72. package/ai/llm/hooks/useModelPricing.ts +0 -75
  73. package/ai/llm/imagePricing.ts +0 -40
  74. package/ai/llm/isResponseAPIModel.ts +0 -13
  75. package/ai/llm/mimo.ts +0 -71
  76. package/ai/llm/mistral.ts +0 -22
  77. package/ai/llm/modelAvatar.ts +0 -427
  78. package/ai/llm/models.ts +0 -45
  79. package/ai/llm/openrouterModels.ts +0 -269
  80. package/ai/llm/providers.ts +0 -306
  81. package/ai/llm/reasoningModels.ts +0 -28
  82. package/ai/llm/types.ts +0 -59
  83. package/ai/llm/usageRequestOptions.ts +0 -59
  84. package/ai/memory/capture.ts +0 -148
  85. package/ai/memory/consolidate.ts +0 -104
  86. package/ai/memory/delete.ts +0 -147
  87. package/ai/memory/overlay.ts +0 -84
  88. package/ai/memory/query.ts +0 -38
  89. package/ai/memory/queryShared.ts +0 -160
  90. package/ai/memory/rank.ts +0 -105
  91. package/ai/memory/recentRelationshipRecap.ts +0 -249
  92. package/ai/memory/remember.ts +0 -167
  93. package/ai/memory/runtime.ts +0 -76
  94. package/ai/memory/store.ts +0 -20
  95. package/ai/memory/storeShared.ts +0 -76
  96. package/ai/memory/types.ts +0 -46
  97. package/ai/memory/understanding.ts +0 -349
  98. package/ai/memory/understandingGreeting.ts +0 -264
  99. package/ai/messages/type.ts +0 -20
  100. package/ai/policy/personalizationDialog.ts +0 -333
  101. package/ai/policy/runtimePolicy.ts +0 -440
  102. package/ai/policy/selfUpdateFields.ts +0 -48
  103. package/ai/policy/types.ts +0 -64
  104. package/ai/skills/referenceRuntime.ts +0 -274
  105. package/ai/skills/skillDiagnostics.ts +0 -251
  106. package/ai/skills/skillDocBuilder.ts +0 -139
  107. package/ai/skills/skillDocProtocol.ts +0 -434
  108. package/ai/skills/skillReferenceSummary.ts +0 -63
  109. package/ai/skills/skillSummaryMarker.ts +0 -26
  110. package/ai/token/calculatePrice.ts +0 -544
  111. package/ai/token/db.ts +0 -98
  112. package/ai/token/externalToolCost.ts +0 -330
  113. package/ai/token/hooks/useRecords.ts +0 -65
  114. package/ai/token/missingUsageEstimate.ts +0 -42
  115. package/ai/token/modelUsageQuery.ts +0 -252
  116. package/ai/token/normalizeUsage.ts +0 -84
  117. package/ai/token/openaiImageGenerationUsage.ts +0 -56
  118. package/ai/token/prepareTokenUsageData.ts +0 -88
  119. package/ai/token/query.ts +0 -88
  120. package/ai/token/queryUserTokens.ts +0 -59
  121. package/ai/token/resolveBillingTarget.ts +0 -52
  122. package/ai/token/saveTokenRecord.ts +0 -53
  123. package/ai/token/serverDialogProjection.ts +0 -78
  124. package/ai/token/serverTokenWriter.ts +0 -143
  125. package/ai/token/stats.ts +0 -21
  126. package/ai/token/tokenThunks.ts +0 -24
  127. package/ai/token/types.ts +0 -93
  128. package/ai/tools/agent/agentTools.ts +0 -176
  129. package/ai/tools/agent/agentUpdateShared.ts +0 -311
  130. package/ai/tools/agent/callAgentTool.ts +0 -139
  131. package/ai/tools/agent/createAgentTool.ts +0 -512
  132. package/ai/tools/agent/createDialogTool.ts +0 -69
  133. package/ai/tools/agent/createSkillAgentTool.ts +0 -62
  134. package/ai/tools/agent/parallelBudget.ts +0 -221
  135. package/ai/tools/agent/presets/appBuilderPreset.ts +0 -145
  136. package/ai/tools/agent/runLlmTool.ts +0 -96
  137. package/ai/tools/agent/runStreamingAgentTool.ts +0 -73
  138. package/ai/tools/agent/skillAgentArgs.ts +0 -106
  139. package/ai/tools/agent/skillAgentPreset.ts +0 -89
  140. package/ai/tools/agent/streamParallelAgentsTool.ts +0 -122
  141. package/ai/tools/agent/updateAgentTool.ts +0 -96
  142. package/ai/tools/agent/updateSelfTool.ts +0 -113
  143. package/ai/tools/amazonProductScraperTool.ts +0 -86
  144. package/ai/tools/apifyActorClient.ts +0 -45
  145. package/ai/tools/appEditGuard.ts +0 -372
  146. package/ai/tools/appReadSnapshot.ts +0 -153
  147. package/ai/tools/appTools.ts +0 -1549
  148. package/ai/tools/applyEditTool.ts +0 -256
  149. package/ai/tools/applyLineEditsTool.ts +0 -312
  150. package/ai/tools/browserTools/click.ts +0 -33
  151. package/ai/tools/browserTools/closeSession.ts +0 -29
  152. package/ai/tools/browserTools/common.ts +0 -27
  153. package/ai/tools/browserTools/openSession.ts +0 -48
  154. package/ai/tools/browserTools/readContent.ts +0 -38
  155. package/ai/tools/browserTools/selectOption.ts +0 -46
  156. package/ai/tools/browserTools/typeText.ts +0 -42
  157. package/ai/tools/category/createCategoryTool.ts +0 -66
  158. package/ai/tools/category/queryContentsByCategoryTool.ts +0 -69
  159. package/ai/tools/category/updateContentCategoryTool.ts +0 -75
  160. package/ai/tools/cfBrowserTools.ts +0 -319
  161. package/ai/tools/cfSpeechToTextTool.ts +0 -49
  162. package/ai/tools/checkEnvTool.ts +0 -65
  163. package/ai/tools/cloudflareCrawlTool.ts +0 -289
  164. package/ai/tools/codeSearchTool.ts +0 -111
  165. package/ai/tools/codeTools.ts +0 -101
  166. package/ai/tools/createDocTool.ts +0 -132
  167. package/ai/tools/createPlanTool.ts +0 -999
  168. package/ai/tools/createSkillDocTool.ts +0 -155
  169. package/ai/tools/createWorkflowTool.ts +0 -154
  170. package/ai/tools/deepseekOcrTool.ts +0 -34
  171. package/ai/tools/delayTool.ts +0 -31
  172. package/ai/tools/deleteSpacesTool.ts +0 -325
  173. package/ai/tools/deleteSpacesToolModel.ts +0 -159
  174. package/ai/tools/devReloadUtils.ts +0 -29
  175. package/ai/tools/dialogMessageSearch.ts +0 -137
  176. package/ai/tools/doctorSkillTool.ts +0 -72
  177. package/ai/tools/ecommerceScraperTool.ts +0 -86
  178. package/ai/tools/emailTools.ts +0 -549
  179. package/ai/tools/evalSkillTool.ts +0 -92
  180. package/ai/tools/exaSearchTool.ts +0 -64
  181. package/ai/tools/execBashTool.ts +0 -379
  182. package/ai/tools/executeSqlTool.ts +0 -192
  183. package/ai/tools/fetchWebpageSupport.ts +0 -309
  184. package/ai/tools/fetchWebpageTool.ts +0 -84
  185. package/ai/tools/geminiImagePreviewTool.ts +0 -361
  186. package/ai/tools/generateDocxTool.ts +0 -215
  187. package/ai/tools/googleSearchScraperTool.ts +0 -106
  188. package/ai/tools/importDataTool.ts +0 -133
  189. package/ai/tools/importSkillTool.ts +0 -162
  190. package/ai/tools/index.ts +0 -1858
  191. package/ai/tools/listFilesTool.ts +0 -82
  192. package/ai/tools/listUserSpacesTool.ts +0 -113
  193. package/ai/tools/modelUsageTools.ts +0 -142
  194. package/ai/tools/olmOcrTool.ts +0 -34
  195. package/ai/tools/openaiImageTool.ts +0 -218
  196. package/ai/tools/paddleOcrTool.ts +0 -34
  197. package/ai/tools/prepareTools.ts +0 -23
  198. package/ai/tools/readDocTool.ts +0 -84
  199. package/ai/tools/readFileTool.ts +0 -211
  200. package/ai/tools/readTool.ts +0 -163
  201. package/ai/tools/readXPostTool.ts +0 -233
  202. package/ai/tools/rememberMemoryTool.ts +0 -84
  203. package/ai/tools/remotionVideoTool.ts +0 -151
  204. package/ai/tools/searchDialogMessagesTool.ts +0 -222
  205. package/ai/tools/searchRepoTool.ts +0 -115
  206. package/ai/tools/searchWorkspaceTool.ts +0 -259
  207. package/ai/tools/skillFollowup.ts +0 -86
  208. package/ai/tools/surfWeatherTool.ts +0 -169
  209. package/ai/tools/table/addTableRowTool.ts +0 -217
  210. package/ai/tools/table/createTableTool.ts +0 -315
  211. package/ai/tools/table/rowTools.ts +0 -366
  212. package/ai/tools/table/schemaTools.ts +0 -244
  213. package/ai/tools/table/shareTableTool.ts +0 -148
  214. package/ai/tools/table/toolShared.ts +0 -129
  215. package/ai/tools/toolApiClient.ts +0 -198
  216. package/ai/tools/toolNameAliases.ts +0 -57
  217. package/ai/tools/toolResultError.ts +0 -42
  218. package/ai/tools/toolRunSlice.ts +0 -303
  219. package/ai/tools/toolSchemaCompatibility.ts +0 -53
  220. package/ai/tools/toolVisibility.ts +0 -4
  221. package/ai/tools/types.ts +0 -20
  222. package/ai/tools/uiAskChoiceTool.ts +0 -104
  223. package/ai/tools/updateContentTitleTool.ts +0 -84
  224. package/ai/tools/updateDocTool.ts +0 -105
  225. package/ai/tools/updateUserPreferenceProfileTool.ts +0 -145
  226. package/ai/tools/whisperTool.ts +0 -77
  227. package/ai/tools/writeFileTool.ts +0 -210
  228. package/ai/tools/youtubeScraperTool.ts +0 -116
  229. package/ai/tools/ziweiChartTool.ts +0 -678
  230. package/ai/types.ts +0 -55
  231. package/ai/workflow/workflowExecutor.ts +0 -323
  232. package/ai/workflow/workflowSlice.ts +0 -73
  233. package/ai/workflow/workflowTypes.ts +0 -106
  234. package/client/compactDialog.ts +0 -222
  235. package/connector-experimental/capabilities.ts +0 -73
  236. package/connector-experimental/codexBinary.ts +0 -41
  237. package/connector-experimental/heartbeatLoop.ts +0 -22
  238. package/connector-experimental/index.ts +0 -5
  239. package/connector-experimental/machineInfo.ts +0 -46
  240. package/connector-experimental/protocol.ts +0 -54
@@ -1,733 +0,0 @@
1
- /**
2
- * CLI Executor - 通过命令行工具执行 AI 任务
3
- *
4
- * 设计为可扩展:当前支持 Copilot CLI、Gemini CLI、Codex CLI 与 Claude CLI。
5
- *
6
- * 注意:
7
- * - CLI provider 与普通 model 路由共享 prompt / model / 最近文本上下文这些能力面
8
- * - 但 CLI 不暴露本仓库可编排的 tool-calls 协议,因此这里只返回文本结果
9
- * - 对缺少稳定增量输出协议的 CLI,流式接口允许退化为“完成后一次性回传”
10
- *
11
- * 使用方式:
12
- * import { executeCli } from "ai/agent/cliExecutor";
13
- * const result = await executeCli("copilot", prompt, { model: "claude-haiku-4.5" });
14
- */
15
-
16
- import { exec, spawn } from "child_process";
17
- import { randomUUID } from "node:crypto";
18
- import { mkdtempSync, readFileSync, rmSync } from "node:fs";
19
- import { join } from "node:path";
20
- import { tmpdir } from "node:os";
21
- import { buildCliPrompt } from "./cliPrompt";
22
-
23
- // ── 类型定义 ──────────────────────────────────────────────────────────────────
24
-
25
- /** 已支持的 CLI 工具。新增时在此联合类型追加,并在 EXECUTORS 里注册实现 */
26
- export type CliProvider = "copilot" | "gemini" | "codex" | "claude";
27
-
28
- export interface CliExecuteOptions {
29
- model?: string;
30
- timeout?: number; // ms,默认 120_000
31
- cwd?: string;
32
- yolo?: boolean; // 允许所有工具,默认 true(后台任务常用)
33
- }
34
-
35
- export interface CliExecuteResult {
36
- text: string; // 解析后的纯文本回复
37
- raw: string; // 原始 stdout
38
- elapsed: number; // 耗时 ms
39
- }
40
-
41
- type CliSessionMessage = {
42
- role: "user" | "assistant";
43
- content: string;
44
- };
45
-
46
- export interface CliSessionHandle {
47
- sessionId: string;
48
- provider: CliProvider;
49
- }
50
-
51
- export interface CliSessionState extends CliSessionHandle {
52
- systemPrompt?: string;
53
- options: CliExecuteOptions;
54
- messages: CliSessionMessage[];
55
- createdAt: number;
56
- updatedAt: number;
57
- }
58
-
59
- export interface CliSessionTurnResult extends CliExecuteResult {
60
- sessionId: string;
61
- }
62
-
63
- const cliSessions = new Map<string, CliSessionState>();
64
-
65
- // ── 各 provider 实现 ─────────────────────────────────────────────────────────
66
-
67
- /**
68
- * Copilot CLI 执行器
69
- * 调用 gh copilot -- -p "..." --silent
70
- */
71
- function executeCopilot(
72
- prompt: string,
73
- options: CliExecuteOptions
74
- ): Promise<CliExecuteResult> {
75
- const {
76
- model,
77
- timeout = 120_000,
78
- cwd = process.cwd(),
79
- yolo = true,
80
- } = options;
81
-
82
- return new Promise((resolve, reject) => {
83
- const args = [
84
- "NO_COLOR=1",
85
- "gh copilot --",
86
- `-p ${JSON.stringify(prompt)}`,
87
- "--silent",
88
- "--disable-builtin-mcps",
89
- "--stream off",
90
- "--no-color",
91
- ];
92
- if (model) args.push(`--model ${model}`);
93
- if (yolo) args.push("--yolo");
94
-
95
- const cmd = args.join(" ");
96
- const start = Date.now();
97
-
98
- exec(
99
- cmd,
100
- { timeout, cwd, env: { ...process.env } },
101
- (error, stdout, stderr) => {
102
- if (error) {
103
- if (error.killed && (error as any).signal === "SIGTERM") {
104
- return reject(new Error(`Copilot CLI timed out after ${timeout}ms`));
105
- }
106
- return reject(
107
- new Error(`Copilot CLI failed: ${error.message}\nstderr: ${stderr}`)
108
- );
109
- }
110
-
111
- const text = stdout
112
- .split("\n")
113
- .filter((l) => !l.includes("A new release of gh is available"))
114
- .filter((l) => !l.includes("To upgrade, run:"))
115
- .filter((l) => !l.includes("https://github.com/cli/cli/releases"))
116
- .join("\n")
117
- .trim();
118
-
119
- resolve({ text, raw: stdout, elapsed: Date.now() - start });
120
- }
121
- );
122
- });
123
- }
124
-
125
- function normalizeGeminiContent(content: unknown): string {
126
- if (typeof content === "string") return content;
127
- if (Array.isArray(content)) {
128
- return content
129
- .map((part) => {
130
- if (typeof part === "string") return part;
131
- if (part && typeof part === "object" && typeof (part as any).text === "string") {
132
- return (part as any).text;
133
- }
134
- return "";
135
- })
136
- .join("");
137
- }
138
- if (content && typeof content === "object" && typeof (content as any).text === "string") {
139
- return (content as any).text;
140
- }
141
- return "";
142
- }
143
-
144
- function extractGeminiEventText(event: any): string {
145
- if (event?.type === "message" && event.role === "assistant") {
146
- return normalizeGeminiContent(event.content);
147
- }
148
- if (event?.type === "result" && typeof event.result === "string") {
149
- return event.result;
150
- }
151
- return "";
152
- }
153
-
154
- function parseGeminiStreamJson(stdout: string): string {
155
- const parts: string[] = [];
156
-
157
- for (const line of stdout.split("\n")) {
158
- const trimmed = line.trim();
159
- if (!trimmed) continue;
160
-
161
- try {
162
- const event = JSON.parse(trimmed);
163
- const text = extractGeminiEventText(event);
164
- if (text) parts.push(text);
165
- } catch {
166
- // ignore malformed lines
167
- }
168
- }
169
-
170
- return parts.join("");
171
- }
172
-
173
- function executeGemini(
174
- prompt: string,
175
- options: CliExecuteOptions
176
- ): Promise<CliExecuteResult> {
177
- const {
178
- model = "gemini-3-flash-preview",
179
- timeout = 120_000,
180
- cwd = process.cwd(),
181
- yolo = true,
182
- } = options;
183
-
184
- return new Promise((resolve, reject) => {
185
- const args = [
186
- "NO_COLOR=1",
187
- "NODE_OPTIONS='--no-deprecation'",
188
- "gemini",
189
- `-p ${JSON.stringify(prompt)}`,
190
- "--output-format stream-json",
191
- `-m ${JSON.stringify(model)}`,
192
- ];
193
- if (yolo) args.push("--yolo");
194
- args.push("-e none");
195
-
196
- const cmd = args.join(" ");
197
- const start = Date.now();
198
-
199
- exec(
200
- cmd,
201
- { timeout, cwd, env: { ...process.env } },
202
- (error, stdout, stderr) => {
203
- if (error) {
204
- if (error.killed && (error as any).signal === "SIGTERM") {
205
- return reject(new Error(`Gemini CLI timed out after ${timeout}ms`));
206
- }
207
- return reject(
208
- new Error(`Gemini CLI failed: ${error.message}\nstderr: ${stderr}`)
209
- );
210
- }
211
-
212
- resolve({
213
- text: parseGeminiStreamJson(stdout),
214
- raw: stdout,
215
- elapsed: Date.now() - start,
216
- });
217
- }
218
- );
219
- });
220
- }
221
-
222
- function executeCodex(
223
- prompt: string,
224
- options: CliExecuteOptions
225
- ): Promise<CliExecuteResult> {
226
- const {
227
- model,
228
- timeout = 120_000,
229
- cwd = process.cwd(),
230
- yolo = true,
231
- } = options;
232
-
233
- return new Promise((resolve, reject) => {
234
- const tempDir = mkdtempSync(join(tmpdir(), "codex-exec-"));
235
- const outputFile = join(tempDir, "last-message.txt");
236
- const args = [
237
- "exec",
238
- "--skip-git-repo-check",
239
- "--ephemeral",
240
- "--color",
241
- "never",
242
- "--output-last-message",
243
- outputFile,
244
- "--cd",
245
- cwd,
246
- ];
247
- if (model) {
248
- args.push("--model");
249
- args.push(model);
250
- }
251
- if (yolo) {
252
- args.push("--sandbox");
253
- args.push("danger-full-access");
254
- }
255
- const start = Date.now();
256
- const proc = spawn("codex", [...args, prompt], {
257
- cwd,
258
- env: { ...process.env, NO_COLOR: "1" },
259
- stdio: ["ignore", "pipe", "pipe"],
260
- });
261
- let stdout = "";
262
- let stderr = "";
263
- let timer: ReturnType<typeof setTimeout> | undefined;
264
-
265
- if (timeout > 0) {
266
- timer = setTimeout(() => {
267
- proc.kill("SIGTERM");
268
- reject(new Error(`Codex CLI timed out after ${timeout}ms`));
269
- }, timeout);
270
- }
271
-
272
- proc.stdout.on("data", (data: Buffer) => {
273
- stdout += data.toString();
274
- });
275
-
276
- proc.stderr.on("data", (data: Buffer) => {
277
- stderr += data.toString();
278
- });
279
-
280
- proc.on("close", (code) => {
281
- if (timer) clearTimeout(timer);
282
- if (code !== 0 && code !== null) {
283
- rmSync(tempDir, { recursive: true, force: true });
284
- reject(new Error(`Codex CLI exited with code ${code}\nstderr: ${stderr}`));
285
- return;
286
- }
287
-
288
- let text = stdout.trim();
289
- try {
290
- const lastMessage = readFileSync(outputFile, "utf8").trim();
291
- if (lastMessage) text = lastMessage;
292
- } catch {
293
- // Fall back to raw stdout when the output file is missing.
294
- } finally {
295
- rmSync(tempDir, { recursive: true, force: true });
296
- }
297
-
298
- resolve({
299
- text,
300
- raw: stdout,
301
- elapsed: Date.now() - start,
302
- });
303
- });
304
-
305
- proc.on("error", (err) => {
306
- if (timer) clearTimeout(timer);
307
- rmSync(tempDir, { recursive: true, force: true });
308
- reject(err);
309
- });
310
- });
311
- }
312
-
313
- function executeClaude(
314
- prompt: string,
315
- options: CliExecuteOptions
316
- ): Promise<CliExecuteResult> {
317
- const {
318
- model,
319
- timeout = 120_000,
320
- cwd = process.cwd(),
321
- yolo = true,
322
- } = options;
323
-
324
- return new Promise((resolve, reject) => {
325
- const args = ["-p", prompt];
326
- if (model) {
327
- args.push("--model");
328
- args.push(model);
329
- }
330
- if (yolo) {
331
- args.push("--permission-mode");
332
- args.push("bypassPermissions");
333
- }
334
-
335
- const start = Date.now();
336
- const proc = spawn("claude", args, {
337
- cwd,
338
- env: { ...process.env, NO_COLOR: "1" },
339
- stdio: ["ignore", "pipe", "pipe"],
340
- });
341
- let stdout = "";
342
- let stderr = "";
343
- let timer: ReturnType<typeof setTimeout> | undefined;
344
-
345
- if (timeout > 0) {
346
- timer = setTimeout(() => {
347
- proc.kill("SIGTERM");
348
- reject(new Error(`Claude CLI timed out after ${timeout}ms`));
349
- }, timeout);
350
- }
351
-
352
- proc.stdout.on("data", (data: Buffer) => {
353
- stdout += data.toString();
354
- });
355
-
356
- proc.stderr.on("data", (data: Buffer) => {
357
- stderr += data.toString();
358
- });
359
-
360
- proc.on("close", (code) => {
361
- if (timer) clearTimeout(timer);
362
- if (code !== 0 && code !== null) {
363
- reject(new Error(`Claude CLI exited with code ${code}\nstderr: ${stderr}`));
364
- return;
365
- }
366
-
367
- resolve({
368
- text: stdout.trim(),
369
- raw: stdout,
370
- elapsed: Date.now() - start,
371
- });
372
- });
373
-
374
- proc.on("error", (err) => {
375
- if (timer) clearTimeout(timer);
376
- reject(err);
377
- });
378
- });
379
- }
380
-
381
- // ── 注册表(新增 CLI 工具时在这里加) ────────────────────────────────────────
382
-
383
- const EXECUTORS: Record<
384
- CliProvider,
385
- (prompt: string, options: CliExecuteOptions) => Promise<CliExecuteResult>
386
- > = {
387
- copilot: executeCopilot,
388
- gemini: executeGemini,
389
- codex: executeCodex,
390
- claude: executeClaude,
391
- };
392
-
393
- function formatCliSessionTask(messages: CliSessionMessage[]): string {
394
- return messages
395
- .map((message, index) => {
396
- const speaker = message.role === "user" ? "用户" : "助手";
397
- return `[${index + 1}] ${speaker}\n${message.content.trim()}`;
398
- })
399
- .join("\n\n");
400
- }
401
-
402
- function buildCliSessionPrompt(session: CliSessionState, userInput: string): string {
403
- const transcript = formatCliSessionTask([
404
- ...session.messages,
405
- { role: "user", content: userInput },
406
- ]);
407
-
408
- return buildCliPrompt(
409
- session.systemPrompt,
410
- [
411
- "以下是当前对话,请基于完整上下文继续回答最后一条用户消息。",
412
- transcript,
413
- ].join("\n\n"),
414
- );
415
- }
416
-
417
- function getCliSessionOrThrow(sessionId: string): CliSessionState {
418
- const session = cliSessions.get(sessionId);
419
- if (!session) {
420
- throw new Error(`Unknown CLI session: "${sessionId}"`);
421
- }
422
- return session;
423
- }
424
-
425
- // ── 公开 API ──────────────────────────────────────────────────────────────────
426
-
427
- /**
428
- * 执行 CLI 任务
429
- *
430
- * @param provider CLI 工具类型(如 "copilot" | "gemini" | "codex" | "claude")
431
- * @param prompt 完整 prompt(system prompt 已由调用方拼好)
432
- * @param options 执行选项
433
- */
434
- export async function executeCli(
435
- provider: CliProvider,
436
- prompt: string,
437
- options: CliExecuteOptions = {}
438
- ): Promise<CliExecuteResult> {
439
- const executor = EXECUTORS[provider];
440
- if (!executor) {
441
- throw new Error(`Unknown CLI provider: "${provider}". Supported: ${Object.keys(EXECUTORS).join(", ")}`);
442
- }
443
- return executor(prompt, options);
444
- }
445
-
446
- export { buildCliPrompt };
447
-
448
- export function startCliSession(
449
- provider: CliProvider,
450
- options: CliExecuteOptions & { systemPrompt?: string } = {},
451
- ): CliSessionHandle {
452
- if (!EXECUTORS[provider]) {
453
- throw new Error(`Unknown CLI provider: "${provider}". Supported: ${Object.keys(EXECUTORS).join(", ")}`);
454
- }
455
-
456
- const sessionId = randomUUID();
457
- cliSessions.set(sessionId, {
458
- sessionId,
459
- provider,
460
- systemPrompt: options.systemPrompt,
461
- options: {
462
- model: options.model,
463
- timeout: options.timeout,
464
- cwd: options.cwd,
465
- yolo: options.yolo,
466
- },
467
- messages: [],
468
- createdAt: Date.now(),
469
- updatedAt: Date.now(),
470
- });
471
-
472
- return { sessionId, provider };
473
- }
474
-
475
- export function getCliSession(sessionId: string): CliSessionState | null {
476
- return cliSessions.get(sessionId) ?? null;
477
- }
478
-
479
- export function closeCliSession(sessionId: string): boolean {
480
- return cliSessions.delete(sessionId);
481
- }
482
-
483
- export async function executeCliSessionTurn(
484
- sessionId: string,
485
- userInput: string,
486
- options: Partial<CliExecuteOptions> = {},
487
- ): Promise<CliSessionTurnResult> {
488
- const trimmedInput = userInput.trim();
489
- if (!trimmedInput) {
490
- throw new Error("CLI session turn requires non-empty user input.");
491
- }
492
-
493
- const session = getCliSessionOrThrow(sessionId);
494
- const prompt = buildCliSessionPrompt(session, trimmedInput);
495
- const result = await executeCli(session.provider, prompt, {
496
- ...session.options,
497
- ...options,
498
- });
499
-
500
- session.messages.push(
501
- { role: "user", content: trimmedInput },
502
- { role: "assistant", content: result.text },
503
- );
504
- session.updatedAt = Date.now();
505
-
506
- return {
507
- ...result,
508
- sessionId: session.sessionId,
509
- };
510
- }
511
-
512
- export async function executeCliSessionTurnStreaming(
513
- sessionId: string,
514
- userInput: string,
515
- options: Partial<CliExecuteOptions> & { onChunk: (chunk: string) => void },
516
- ): Promise<CliSessionTurnResult> {
517
- const trimmedInput = userInput.trim();
518
- if (!trimmedInput) {
519
- throw new Error("CLI session turn requires non-empty user input.");
520
- }
521
-
522
- const session = getCliSessionOrThrow(sessionId);
523
- const prompt = buildCliSessionPrompt(session, trimmedInput);
524
- const result = await executeCliStreaming(session.provider, prompt, {
525
- ...session.options,
526
- ...options,
527
- });
528
-
529
- session.messages.push(
530
- { role: "user", content: trimmedInput },
531
- { role: "assistant", content: result.text },
532
- );
533
- session.updatedAt = Date.now();
534
-
535
- return {
536
- ...result,
537
- sessionId: session.sessionId,
538
- };
539
- }
540
-
541
- /** 忽略 gh 版本更新提示行 */
542
- function filterNoiseLine(line: string): boolean {
543
- return (
544
- !line.includes("A new release of gh is available") &&
545
- !line.includes("To upgrade, run:") &&
546
- !line.includes("https://github.com/cli/cli/releases")
547
- );
548
- }
549
-
550
- /**
551
- * 流式执行 Copilot CLI,通过 onChunk 回调逐块返回输出
552
- * 使用 spawn 替代 exec,不缓冲 stdout
553
- */
554
- export function executeCliStreaming(
555
- provider: CliProvider,
556
- prompt: string,
557
- options: CliExecuteOptions & { onChunk: (chunk: string) => void }
558
- ): Promise<CliExecuteResult> {
559
- if (provider === "gemini") {
560
- return executeGeminiStreaming(prompt, options);
561
- }
562
- if (provider === "codex") {
563
- return executeCli(provider, prompt, options).then((result) => {
564
- if (result.text) options.onChunk(result.text);
565
- return result;
566
- });
567
- }
568
- if (provider === "claude") {
569
- return executeCli(provider, prompt, options).then((result) => {
570
- if (result.text) options.onChunk(result.text);
571
- return result;
572
- });
573
- }
574
- if (provider !== "copilot") {
575
- return executeCli(provider, prompt, options);
576
- }
577
-
578
- const { model, timeout = 120_000, cwd = process.cwd(), yolo = true, onChunk } = options;
579
-
580
- return new Promise((resolve, reject) => {
581
- const args = [
582
- "copilot", "--",
583
- "-p", prompt,
584
- "--silent",
585
- "--disable-builtin-mcps",
586
- ];
587
- if (model) { args.push("--model"); args.push(model); }
588
- if (yolo) args.push("--yolo");
589
-
590
- const start = Date.now();
591
- const proc = spawn("gh", args, {
592
- cwd,
593
- env: { ...process.env, NO_COLOR: "1" },
594
- });
595
-
596
- let raw = "";
597
- let timer: ReturnType<typeof setTimeout> | undefined;
598
-
599
- if (timeout > 0) {
600
- timer = setTimeout(() => {
601
- proc.kill("SIGTERM");
602
- reject(new Error(`Copilot CLI timed out after ${timeout}ms`));
603
- }, timeout);
604
- }
605
-
606
- proc.stdout.on("data", (data: Buffer) => {
607
- const chunk = data.toString();
608
- raw += chunk;
609
- // 过滤噪音行后再推送
610
- const cleaned = chunk
611
- .split("\n")
612
- .filter(filterNoiseLine)
613
- .join("\n");
614
- if (cleaned) onChunk(cleaned);
615
- });
616
-
617
- proc.on("close", (code) => {
618
- if (timer) clearTimeout(timer);
619
- if (code !== 0 && code !== null) {
620
- reject(new Error(`Copilot CLI exited with code ${code}`));
621
- return;
622
- }
623
- const text = raw
624
- .split("\n")
625
- .filter(filterNoiseLine)
626
- .join("\n")
627
- .trim();
628
- resolve({ text, raw, elapsed: Date.now() - start });
629
- });
630
-
631
- proc.on("error", (err) => {
632
- if (timer) clearTimeout(timer);
633
- reject(err);
634
- });
635
- });
636
- }
637
-
638
- function executeGeminiStreaming(
639
- prompt: string,
640
- options: CliExecuteOptions & { onChunk: (chunk: string) => void }
641
- ): Promise<CliExecuteResult> {
642
- const {
643
- model = "gemini-3-flash-preview",
644
- timeout = 120_000,
645
- cwd = process.cwd(),
646
- yolo = true,
647
- onChunk,
648
- } = options;
649
-
650
- return new Promise((resolve, reject) => {
651
- const args = ["-p", prompt, "--output-format", "stream-json", "-m", model];
652
- if (yolo) args.push("--yolo");
653
- args.push("-e", "none");
654
-
655
- const start = Date.now();
656
- const proc = spawn("gemini", args, {
657
- cwd,
658
- env: { ...process.env, NO_COLOR: "1", NODE_OPTIONS: "--no-deprecation" },
659
- });
660
-
661
- let raw = "";
662
- let stderr = "";
663
- let lineBuffer = "";
664
- let timer: ReturnType<typeof setTimeout> | undefined;
665
-
666
- const flushLineBuffer = () => {
667
- const trimmed = lineBuffer.trim();
668
- if (!trimmed) return;
669
- try {
670
- const event = JSON.parse(trimmed);
671
- const text = extractGeminiEventText(event);
672
- if (text) onChunk(text);
673
- } catch {
674
- // ignore partial or malformed lines
675
- }
676
- lineBuffer = "";
677
- };
678
-
679
- if (timeout > 0) {
680
- timer = setTimeout(() => {
681
- proc.kill("SIGTERM");
682
- reject(new Error(`Gemini CLI timed out after ${timeout}ms`));
683
- }, timeout);
684
- }
685
-
686
- proc.stdout.on("data", (data: Buffer) => {
687
- const chunk = data.toString();
688
- raw += chunk;
689
- lineBuffer += chunk;
690
-
691
- let newlineIndex = lineBuffer.indexOf("\n");
692
- while (newlineIndex !== -1) {
693
- const line = lineBuffer.slice(0, newlineIndex).trim();
694
- lineBuffer = lineBuffer.slice(newlineIndex + 1);
695
- if (line) {
696
- try {
697
- const event = JSON.parse(line);
698
- const text = extractGeminiEventText(event);
699
- if (text) onChunk(text);
700
- } catch {
701
- // ignore malformed lines
702
- }
703
- }
704
- newlineIndex = lineBuffer.indexOf("\n");
705
- }
706
- });
707
-
708
- proc.stderr.on("data", (data: Buffer) => {
709
- stderr += data.toString();
710
- });
711
-
712
- proc.on("close", (code) => {
713
- if (timer) clearTimeout(timer);
714
- flushLineBuffer();
715
-
716
- if (code !== 0 && code !== null) {
717
- reject(new Error(`Gemini CLI exited with code ${code}\nstderr: ${stderr}`));
718
- return;
719
- }
720
-
721
- resolve({
722
- text: parseGeminiStreamJson(raw),
723
- raw,
724
- elapsed: Date.now() - start,
725
- });
726
- });
727
-
728
- proc.on("error", (err) => {
729
- if (timer) clearTimeout(timer);
730
- reject(err);
731
- });
732
- });
733
- }