@tencent-ai/cloud-agent-sdk 0.2.5 → 0.2.6-next.8dd93c4.20260127

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/dist/index.cjs CHANGED
@@ -1,10 +1,633 @@
1
- require('./MockAgentProvider-D-basTXz.cjs');
1
+ let zod = require("zod");
2
2
  let _agentclientprotocol_sdk = require("@agentclientprotocol/sdk");
3
3
  require("@connectrpc/connect");
4
4
  require("@connectrpc/connect-web");
5
5
  require("@bufbuild/protobuf");
6
6
  let e2b = require("e2b");
7
7
 
8
+ //#region ../agent-provider/src/common/_legacy/tool-schemas.ts
9
+ /**
10
+ * ACP Tool Input/Output Schema 定义
11
+ *
12
+ * 用于约束 ACP 协议中 ToolCallUpdate 的 rawInput 和 rawOutput 字段
13
+ * 基于 getCraftToolProvider 使用的工具定义
14
+ */
15
+ /**
16
+ * 工具输入 Schema 定义
17
+ * 用于验证和约束 rawInput
18
+ */
19
+ const ToolInputSchemas = {
20
+ list_dir: zod.z.object({
21
+ target_directory: zod.z.string().describe("要列出内容的目录路径"),
22
+ ignore_globs: zod.z.string().optional().describe("可选的 glob 模式数组,用于忽略特定文件")
23
+ }),
24
+ search_file: zod.z.object({
25
+ target_directory: zod.z.string().describe("搜索的目录绝对路径"),
26
+ pattern: zod.z.string().describe("文件模式(如 \"*.js\"),支持通配符"),
27
+ recursive: zod.z.boolean().describe("是否递归搜索子目录"),
28
+ caseSensitive: zod.z.boolean().describe("是否区分大小写")
29
+ }),
30
+ read_file: zod.z.object({
31
+ filePath: zod.z.string().describe("要读取的文件的绝对路径"),
32
+ offset: zod.z.number().optional().describe("开始读取的行号"),
33
+ limit: zod.z.number().optional().describe("要读取的行数")
34
+ }),
35
+ read_lints: zod.z.object({ paths: zod.z.string().optional().describe("要读取 lint 错误的文件或目录路径") }),
36
+ rag_search: zod.z.object({
37
+ queryString: zod.z.string().describe("用户的实际问题或搜索查询"),
38
+ knowledgeBaseNames: zod.z.string().describe("知识库名称,多个用逗号分隔")
39
+ }),
40
+ read_rules: zod.z.object({ ruleNames: zod.z.string().describe("要读取的规则关键词,用逗号分隔,格式:{ruleName}_{ruleId}") }),
41
+ mcp_get_tool_description: zod.z.object({ toolRequests: zod.z.string().describe("JSON 字符串,二维数组格式:[[\"server1\", \"tool1\"], [\"server2\", \"tool2\"]]") }),
42
+ mcp_call_tool: zod.z.object({
43
+ serverName: zod.z.string().describe("MCP 服务器名称"),
44
+ toolName: zod.z.string().describe("要调用的工具名称"),
45
+ arguments: zod.z.string().describe("目标 MCP 工具的参数,JSON 格式字符串"),
46
+ maxOutputLength: zod.z.number().optional().describe("控制工具输出的最大长度,默认 200000")
47
+ }),
48
+ fetch_mcp_resource: zod.z.object({
49
+ server: zod.z.string().describe("MCP 服务器标识符"),
50
+ uri: zod.z.string().describe("要读取的资源 URI"),
51
+ arguments: zod.z.record(zod.z.unknown()).optional().describe("资源模板的参数"),
52
+ downloadPath: zod.z.string().optional().describe("可选的绝对路径,用于保存资源到磁盘")
53
+ }),
54
+ create_rule: zod.z.object({
55
+ ruleScope: zod.z.string().describe("规则范围,project rule 或 user rule"),
56
+ ruleName: zod.z.string().describe("规则文件名,不带扩展名"),
57
+ ruleType: zod.z.string().describe("规则类型,always、manual 或 requested"),
58
+ ruleContent: zod.z.string().describe("规则内容,使用 Markdown 格式"),
59
+ ruleDescription: zod.z.string().optional().describe("规则描述,使用 Markdown 格式")
60
+ }),
61
+ update_memory: zod.z.object({
62
+ action: zod.z.enum([
63
+ "create",
64
+ "update",
65
+ "delete"
66
+ ]).optional().describe("执行的操作"),
67
+ existing_knowledge_id: zod.z.string().optional().describe("更新或删除时必需,现有记忆的 ID"),
68
+ knowledge_to_store: zod.z.string().optional().describe("要存储的特定记忆"),
69
+ title: zod.z.string().optional().describe("记忆的标题")
70
+ }),
71
+ search_content: zod.z.object({
72
+ pattern: zod.z.string().describe("要搜索的关键字或正则表达式模式"),
73
+ directory: zod.z.string().describe("要搜索的目录的绝对路径"),
74
+ fileTypes: zod.z.string().optional().describe("可选的逗号分隔文件扩展名"),
75
+ contextBefore: zod.z.number().optional().describe("每个匹配前显示的行数"),
76
+ contextAfter: zod.z.number().optional().describe("每个匹配后显示的行数"),
77
+ contextAround: zod.z.number().optional().describe("每个匹配前后显示的行数"),
78
+ outputMode: zod.z.string().optional().describe("输出模式:content、files_with_matches 或 count"),
79
+ caseSensitive: zod.z.boolean().optional().describe("是否区分大小写")
80
+ }),
81
+ write_to_file: zod.z.object({
82
+ filePath: zod.z.string().describe("目标文件的绝对路径"),
83
+ content: zod.z.string().describe("要写入的内容")
84
+ }),
85
+ replace_in_file: zod.z.object({
86
+ filePath: zod.z.string().describe("要修改的文件的绝对路径"),
87
+ old_str: zod.z.string().describe("要替换的文本"),
88
+ new_str: zod.z.string().describe("替换后的文本")
89
+ }),
90
+ delete_file: zod.z.object({
91
+ target_file: zod.z.string().describe("要删除的文件的绝对路径"),
92
+ explanation: zod.z.string().optional().describe("为什么使用此工具的一句话解释")
93
+ }),
94
+ execute_command: zod.z.object({
95
+ command: zod.z.string().describe("要执行的 CLI 命令"),
96
+ requires_approval: zod.z.boolean().describe("命令是否需要用户批准")
97
+ }),
98
+ preview_url: zod.z.object({ url: zod.z.string().describe("要打开的完整、有效的 HTTP/HTTPS URL") }),
99
+ ask_followup_question: zod.z.object({ questions: zod.z.array(zod.z.object({
100
+ question: zod.z.string(),
101
+ header: zod.z.string().max(12),
102
+ options: zod.z.array(zod.z.object({
103
+ label: zod.z.string().max(50),
104
+ description: zod.z.string()
105
+ })).min(2).max(4),
106
+ multiSelect: zod.z.boolean().optional()
107
+ })).min(1).max(4) }),
108
+ invoke_integration: zod.z.object({}).passthrough(),
109
+ call_integration: zod.z.object({}).passthrough(),
110
+ search_integration_tool: zod.z.object({}).passthrough(),
111
+ supabase_get_logs: zod.z.object({}).passthrough(),
112
+ supabase_execute_sql: zod.z.object({}).passthrough(),
113
+ supabase_apply_migration: zod.z.object({}).passthrough(),
114
+ supabase_list_migration: zod.z.object({}).passthrough(),
115
+ supabase_list_tables: zod.z.object({}).passthrough(),
116
+ cloud_studio_fetch_log: zod.z.object({}).passthrough(),
117
+ cloud_studio_execute_command: zod.z.object({}).passthrough(),
118
+ cloud_studio_deploy_sandbox: zod.z.object({}).passthrough(),
119
+ component_get_prompt: zod.z.object({}).passthrough(),
120
+ web_fetch: zod.z.object({
121
+ url: zod.z.string().describe("要获取内容的 URL"),
122
+ fetchInfo: zod.z.string().describe("用户想要获取的信息描述")
123
+ }),
124
+ use_skill: zod.z.object({ command: zod.z.string().describe("技能名称(不含参数),如 \"pdf\" 或 \"xlsx\"") }),
125
+ web_search: zod.z.object({
126
+ explanation: zod.z.string().describe("为什么使用此工具的一句话解释"),
127
+ searchTerm: zod.z.string().describe("要在网络上搜索的搜索词")
128
+ }),
129
+ task: zod.z.object({
130
+ subagent_name: zod.z.string().describe("要调用的子代理名称"),
131
+ description: zod.z.string().describe("任务的简短描述(3-5 个词)"),
132
+ prompt: zod.z.string().describe("子代理要执行的任务"),
133
+ subagent_path: zod.z.string().optional().describe("子代理定义文件的路径")
134
+ }),
135
+ codebase_search: zod.z.object({
136
+ query: zod.z.string().describe("关于你想理解的内容的完整问题"),
137
+ path: zod.z.string().describe("限制搜索范围的目录路径前缀"),
138
+ limit: zod.z.number().max(100).optional().describe("返回的最大结果数,默认 10")
139
+ }),
140
+ lsp: zod.z.object({}).passthrough(),
141
+ spec_create: zod.z.object({
142
+ name: zod.z.string().describe("Plan 名称,用作稳定标识符/文件名"),
143
+ overview: zod.z.string().describe("用一两句话精确概括本次 plan 的主要内容"),
144
+ relative_history: zod.z.string().describe("准备阶段的上下文,包含用户需求、代码位置、额外上下文等")
145
+ }),
146
+ spec_update: zod.z.object({ status: zod.z.enum([
147
+ "prepare",
148
+ "ready",
149
+ "building",
150
+ "finished"
151
+ ]).optional().describe("Plan 状态") })
152
+ };
153
+ /**
154
+ * 工具输出 Schema 定义
155
+ * 用于验证和约束 rawOutput
156
+ */
157
+ const ToolOutputSchemas = {
158
+ list_dir: zod.z.object({
159
+ type: zod.z.literal("list_files_result"),
160
+ files: zod.z.array(zod.z.object({
161
+ filePath: zod.z.string(),
162
+ size: zod.z.string(),
163
+ modifyTime: zod.z.string()
164
+ })),
165
+ root: zod.z.string(),
166
+ listing: zod.z.string().optional()
167
+ }),
168
+ search_file: zod.z.object({
169
+ type: zod.z.literal("search_file_result"),
170
+ path: zod.z.string(),
171
+ pattern: zod.z.string(),
172
+ recursive: zod.z.boolean().optional(),
173
+ caseSensitive: zod.z.boolean().optional(),
174
+ results: zod.z.array(zod.z.object({
175
+ filePath: zod.z.string(),
176
+ size: zod.z.string(),
177
+ modifyTime: zod.z.string()
178
+ }))
179
+ }),
180
+ read_file: zod.z.object({
181
+ type: zod.z.literal("read_file_result"),
182
+ path: zod.z.string(),
183
+ content: zod.z.string(),
184
+ totalLineCount: zod.z.number(),
185
+ hasMore: zod.z.boolean(),
186
+ diagnostic: zod.z.string().optional(),
187
+ hint: zod.z.string().optional(),
188
+ image: zod.z.object({
189
+ data: zod.z.string(),
190
+ mimeType: zod.z.string()
191
+ }).optional()
192
+ }),
193
+ read_lints: zod.z.object({
194
+ type: zod.z.literal("read_lints_result"),
195
+ diagnostics: zod.z.array(zod.z.string()),
196
+ totalCount: zod.z.number().optional(),
197
+ hint: zod.z.string().optional(),
198
+ isTruncated: zod.z.boolean().optional()
199
+ }),
200
+ rag_search: zod.z.object({
201
+ type: zod.z.literal("knowledge_base_result"),
202
+ selectedKnowledgeBases: zod.z.string(),
203
+ queryInput: zod.z.string()
204
+ }),
205
+ read_rules: zod.z.object({
206
+ type: zod.z.literal("rule_match_result"),
207
+ ruleDescription: zod.z.string(),
208
+ filePaths: zod.z.array(zod.z.string())
209
+ }),
210
+ mcp_get_tool_description: zod.z.object({}).passthrough(),
211
+ mcp_call_tool: zod.z.object({
212
+ type: zod.z.literal("mcp_call_tool_result"),
213
+ serverName: zod.z.string(),
214
+ toolName: zod.z.string(),
215
+ data: zod.z.array(zod.z.union([
216
+ zod.z.object({
217
+ type: zod.z.literal("text"),
218
+ text: zod.z.string()
219
+ }),
220
+ zod.z.object({
221
+ type: zod.z.literal("image"),
222
+ data: zod.z.string(),
223
+ mimeType: zod.z.string()
224
+ }),
225
+ zod.z.object({
226
+ type: zod.z.literal("resource"),
227
+ resource: zod.z.object({
228
+ uri: zod.z.string(),
229
+ mimeType: zod.z.string().optional(),
230
+ text: zod.z.string().optional(),
231
+ blob: zod.z.string().optional()
232
+ })
233
+ })
234
+ ])),
235
+ isError: zod.z.boolean().optional(),
236
+ error: zod.z.unknown().optional(),
237
+ hint: zod.z.string().optional()
238
+ }),
239
+ fetch_mcp_resource: zod.z.object({
240
+ type: zod.z.literal("fetch_mcp_resource_result"),
241
+ server: zod.z.string(),
242
+ uri: zod.z.string(),
243
+ content: zod.z.string(),
244
+ downloadPath: zod.z.string().optional()
245
+ }),
246
+ create_rule: zod.z.object({
247
+ type: zod.z.literal("rule_create_result"),
248
+ ruleName: zod.z.string(),
249
+ createState: zod.z.enum([
250
+ "success",
251
+ "invoke",
252
+ "cancelled"
253
+ ]),
254
+ hint: zod.z.string().optional(),
255
+ filePath: zod.z.string().optional()
256
+ }),
257
+ update_memory: zod.z.object({
258
+ type: zod.z.literal("update_memory_result"),
259
+ success: zod.z.boolean(),
260
+ message: zod.z.string(),
261
+ action: zod.z.enum([
262
+ "create",
263
+ "update",
264
+ "delete"
265
+ ]),
266
+ knowledge_id: zod.z.string().optional()
267
+ }),
268
+ search_content: zod.z.object({
269
+ type: zod.z.literal("search_content_result"),
270
+ directory: zod.z.string(),
271
+ pattern: zod.z.string(),
272
+ fileTypes: zod.z.string(),
273
+ matches: zod.z.array(zod.z.object({
274
+ filePath: zod.z.string(),
275
+ content: zod.z.string(),
276
+ startLine: zod.z.number(),
277
+ endLine: zod.z.number(),
278
+ size: zod.z.string(),
279
+ modifyTime: zod.z.string()
280
+ })),
281
+ totalCount: zod.z.number(),
282
+ hasMore: zod.z.boolean(),
283
+ offset: zod.z.number(),
284
+ limit: zod.z.number(),
285
+ contextBefore: zod.z.number(),
286
+ contextAfter: zod.z.number(),
287
+ contextAround: zod.z.number().optional(),
288
+ outputMode: zod.z.string(),
289
+ caseSensitive: zod.z.boolean(),
290
+ hint: zod.z.string().optional()
291
+ }),
292
+ write_to_file: zod.z.object({
293
+ type: zod.z.literal("write_to_file_result"),
294
+ path: zod.z.string(),
295
+ addLineCount: zod.z.number(),
296
+ removedLines: zod.z.number(),
297
+ addedChars: zod.z.number().optional(),
298
+ removedChars: zod.z.number().optional(),
299
+ bytesWritten: zod.z.number(),
300
+ isNewFile: zod.z.boolean(),
301
+ oldContent: zod.z.string().optional(),
302
+ diagnostic: zod.z.string().optional()
303
+ }),
304
+ replace_in_file: zod.z.object({
305
+ type: zod.z.literal("replace_in_file_result"),
306
+ path: zod.z.string(),
307
+ addLineCount: zod.z.number().optional(),
308
+ removedLines: zod.z.number().optional(),
309
+ addedChars: zod.z.number().optional(),
310
+ removedChars: zod.z.number().optional(),
311
+ matchCount: zod.z.number().optional(),
312
+ hint: zod.z.string().optional(),
313
+ diagnosticChange: zod.z.object({
314
+ added: zod.z.string(),
315
+ removed: zod.z.string(),
316
+ unchanged: zod.z.string()
317
+ }).optional()
318
+ }),
319
+ delete_file: zod.z.object({
320
+ type: zod.z.literal("delete_file_result"),
321
+ path: zod.z.string(),
322
+ recursive: zod.z.boolean(),
323
+ hint: zod.z.string().optional()
324
+ }),
325
+ execute_command: zod.z.object({
326
+ type: zod.z.literal("execute_command_result"),
327
+ stdout: zod.z.string(),
328
+ stderr: zod.z.string(),
329
+ exitCode: zod.z.number(),
330
+ hint: zod.z.string().optional(),
331
+ serviceInfo: zod.z.object({
332
+ isWatchCommand: zod.z.boolean(),
333
+ isServiceOutput: zod.z.boolean(),
334
+ serviceReady: zod.z.boolean(),
335
+ message: zod.z.string()
336
+ }).optional(),
337
+ use_standalone_terminal: zod.z.boolean().optional()
338
+ }),
339
+ preview_url: zod.z.object({
340
+ type: zod.z.literal("preview_tool_result"),
341
+ url: zod.z.string(),
342
+ message: zod.z.string()
343
+ }),
344
+ ask_followup_question: zod.z.object({
345
+ type: zod.z.literal("multi_question_result"),
346
+ questions: zod.z.array(zod.z.object({
347
+ id: zod.z.string(),
348
+ question: zod.z.string(),
349
+ options: zod.z.array(zod.z.string()),
350
+ multiSelect: zod.z.boolean().optional(),
351
+ title: zod.z.string().optional()
352
+ })),
353
+ answers: zod.z.record(zod.z.union([zod.z.string(), zod.z.array(zod.z.string())])),
354
+ message: zod.z.string()
355
+ }),
356
+ invoke_integration: zod.z.object({
357
+ type: zod.z.literal("invoke_integration_tool_result"),
358
+ recommend: zod.z.object({
359
+ id: zod.z.string(),
360
+ type: zod.z.string(),
361
+ status: zod.z.enum(["connected", "disconnected"])
362
+ }),
363
+ message: zod.z.string()
364
+ }),
365
+ call_integration: zod.z.object({
366
+ type: zod.z.literal("call_integration_tool_result"),
367
+ integrationId: zod.z.string(),
368
+ toolName: zod.z.string(),
369
+ data: zod.z.object({
370
+ type: zod.z.literal("text"),
371
+ text: zod.z.string()
372
+ }),
373
+ isError: zod.z.boolean().optional(),
374
+ error: zod.z.unknown().optional()
375
+ }),
376
+ search_integration_tool: zod.z.object({
377
+ type: zod.z.literal("search_integration_tool_result"),
378
+ data: zod.z.array(zod.z.object({
379
+ integrationId: zod.z.string(),
380
+ integrationName: zod.z.string(),
381
+ toolName: zod.z.string(),
382
+ description: zod.z.string(),
383
+ inputSchema: zod.z.record(zod.z.unknown())
384
+ })),
385
+ hint: zod.z.string().optional()
386
+ }),
387
+ supabase_get_logs: zod.z.object({
388
+ type: zod.z.enum([
389
+ "supabase_get_logs_result",
390
+ "supabase_execute_sql_result",
391
+ "supabase_apply_migration_result",
392
+ "supabase_list_migration_result",
393
+ "supabase_list_tables_result"
394
+ ]),
395
+ message: zod.z.string()
396
+ }),
397
+ supabase_execute_sql: zod.z.object({
398
+ type: zod.z.enum([
399
+ "supabase_get_logs_result",
400
+ "supabase_execute_sql_result",
401
+ "supabase_apply_migration_result",
402
+ "supabase_list_migration_result",
403
+ "supabase_list_tables_result"
404
+ ]),
405
+ message: zod.z.string()
406
+ }),
407
+ supabase_apply_migration: zod.z.object({
408
+ type: zod.z.enum([
409
+ "supabase_get_logs_result",
410
+ "supabase_execute_sql_result",
411
+ "supabase_apply_migration_result",
412
+ "supabase_list_migration_result",
413
+ "supabase_list_tables_result"
414
+ ]),
415
+ message: zod.z.string()
416
+ }),
417
+ supabase_list_migration: zod.z.object({
418
+ type: zod.z.enum([
419
+ "supabase_get_logs_result",
420
+ "supabase_execute_sql_result",
421
+ "supabase_apply_migration_result",
422
+ "supabase_list_migration_result",
423
+ "supabase_list_tables_result"
424
+ ]),
425
+ message: zod.z.string()
426
+ }),
427
+ supabase_list_tables: zod.z.object({
428
+ type: zod.z.enum([
429
+ "supabase_get_logs_result",
430
+ "supabase_execute_sql_result",
431
+ "supabase_apply_migration_result",
432
+ "supabase_list_migration_result",
433
+ "supabase_list_tables_result"
434
+ ]),
435
+ message: zod.z.string()
436
+ }),
437
+ cloud_studio_fetch_log: zod.z.object({
438
+ type: zod.z.literal("cloud_studio_fetch_log_result"),
439
+ success: zod.z.boolean(),
440
+ logs: zod.z.record(zod.z.string())
441
+ }),
442
+ cloud_studio_execute_command: zod.z.object({
443
+ type: zod.z.literal("cloud_studio_execute_command_result"),
444
+ success: zod.z.boolean(),
445
+ message: zod.z.string()
446
+ }),
447
+ cloud_studio_deploy_sandbox: zod.z.object({
448
+ type: zod.z.literal("cloud_studio_integration_result"),
449
+ previewUrl: zod.z.string().optional(),
450
+ steps: zod.z.array(zod.z.object({
451
+ status: zod.z.enum([
452
+ "idle",
453
+ "success",
454
+ "running",
455
+ "error"
456
+ ]),
457
+ name: zod.z.enum([
458
+ "createSandbox",
459
+ "uploadProject",
460
+ "installDependencies",
461
+ "startService",
462
+ "preview"
463
+ ]),
464
+ error: zod.z.object({
465
+ code: zod.z.number().optional(),
466
+ message: zod.z.string().optional()
467
+ }).optional()
468
+ }))
469
+ }),
470
+ component_get_prompt: zod.z.object({
471
+ type: zod.z.literal("component_get_prompt_result"),
472
+ componentType: zod.z.string(),
473
+ webFramework: zod.z.string(),
474
+ data: zod.z.object({
475
+ type: zod.z.literal("text"),
476
+ text: zod.z.string()
477
+ })
478
+ }),
479
+ web_fetch: zod.z.object({
480
+ type: zod.z.literal("web_fetch_tool_result"),
481
+ message: zod.z.string(),
482
+ data: zod.z.string(),
483
+ loading: zod.z.string().optional(),
484
+ title: zod.z.string().optional(),
485
+ favicon: zod.z.string().optional()
486
+ }),
487
+ use_skill: zod.z.object({
488
+ type: zod.z.literal("use_skill_tool_result"),
489
+ commandMessage: zod.z.string(),
490
+ message: zod.z.string()
491
+ }),
492
+ web_search: zod.z.object({
493
+ type: zod.z.literal("web_search_tool_result"),
494
+ data: zod.z.array(zod.z.object({
495
+ passage: zod.z.string(),
496
+ uri: zod.z.string(),
497
+ site: zod.z.string(),
498
+ title: zod.z.string(),
499
+ snippets: zod.z.array(zod.z.string()).optional(),
500
+ content: zod.z.string().optional()
501
+ })),
502
+ searchInput: zod.z.string().optional()
503
+ }),
504
+ task: zod.z.object({
505
+ type: zod.z.literal("task_tool_result"),
506
+ toolInfo: zod.z.array(zod.z.object({
507
+ name: zod.z.string(),
508
+ info: zod.z.string(),
509
+ needApprove: zod.z.boolean().optional(),
510
+ toolCallId: zod.z.string().optional(),
511
+ executeStatus: zod.z.enum([
512
+ "ing",
513
+ "completed",
514
+ "cancel",
515
+ "fail"
516
+ ]).optional()
517
+ })).optional(),
518
+ startCallTool: zod.z.boolean().optional(),
519
+ finalResult: zod.z.string().optional(),
520
+ toolCallBrief: zod.z.string().optional()
521
+ }),
522
+ codebase_search: zod.z.object({
523
+ type: zod.z.literal("codebase_search_result"),
524
+ query: zod.z.string(),
525
+ path: zod.z.string(),
526
+ content: zod.z.string()
527
+ }),
528
+ lsp: zod.z.object({
529
+ type: zod.z.literal("lsp_tool_result"),
530
+ operation: zod.z.string(),
531
+ result: zod.z.string(),
532
+ resultCount: zod.z.number().optional(),
533
+ fileCount: zod.z.number().optional(),
534
+ character: zod.z.number().optional()
535
+ }),
536
+ spec_create: zod.z.object({
537
+ type: zod.z.literal("plan_create_tool_result"),
538
+ message: zod.z.string(),
539
+ data: zod.z.string()
540
+ }),
541
+ spec_update: zod.z.object({
542
+ type: zod.z.literal("plan_update_tool_result"),
543
+ status: zod.z.enum([
544
+ "prepare",
545
+ "ready",
546
+ "building",
547
+ "finished"
548
+ ]),
549
+ data: zod.z.string(),
550
+ reminder: zod.z.string()
551
+ })
552
+ };
553
+
554
+ //#endregion
555
+ //#region ../agent-provider/src/common/_legacy/MockAgentProvider.ts
556
+ /**
557
+ * Mock 会话数据存储
558
+ * 每个会话都有对应的模拟历史消息
559
+ */
560
+ const mockSessionHistories = /* @__PURE__ */ new Map();
561
+ mockSessionHistories.set("1", [
562
+ {
563
+ type: "user",
564
+ content: "帮我开发一个五子棋游戏,需要包含以下功能:\n1. 双人对战模式\n2. 悔棋功能\n3. 计时器",
565
+ timestamp: Date.now() - 18e5 - 6e4
566
+ },
567
+ {
568
+ type: "assistant",
569
+ content: "好的,我来帮你开发一个五子棋游戏。我会创建以下文件结构:\n\n- `index.html` - 主页面\n- `game.js` - 游戏逻辑\n- `style.css` - 样式文件\n\n让我开始创建这些文件...",
570
+ timestamp: Date.now() - 18e5 - 5e4
571
+ },
572
+ {
573
+ type: "assistant",
574
+ content: "我已经完成了所有文件的创建和修改。游戏支持双人对战、悔棋和计时功能。你可以直接在浏览器中打开 index.html 开始游戏。",
575
+ timestamp: Date.now() - 18e5
576
+ }
577
+ ]);
578
+ mockSessionHistories.set("2", [
579
+ {
580
+ type: "user",
581
+ content: "登录页面的样式有问题,按钮颜色不对齐,帮我修复一下",
582
+ timestamp: Date.now() - 72e5 - 12e4
583
+ },
584
+ {
585
+ type: "assistant",
586
+ content: "我来检查登录页面的样式。让我先看看相关的 CSS 文件...",
587
+ timestamp: Date.now() - 72e5 - 1e5
588
+ },
589
+ {
590
+ type: "assistant",
591
+ content: "样式问题已修复,请查看效果。主要修改了按钮的 flex 布局和颜色变量。",
592
+ timestamp: Date.now() - 72e5
593
+ }
594
+ ]);
595
+ mockSessionHistories.set("3", [
596
+ {
597
+ type: "user",
598
+ content: "API 接口响应太慢了,需要添加缓存和错误重试机制",
599
+ timestamp: Date.now() - 144e5 - 18e4
600
+ },
601
+ {
602
+ type: "assistant",
603
+ content: "好的,我来优化 API 接口。我会实现:\n1. Redis 缓存层\n2. 指数退避重试策略\n3. 请求去重",
604
+ timestamp: Date.now() - 144e5 - 15e4
605
+ },
606
+ {
607
+ type: "assistant",
608
+ content: "已添加缓存和错误重试机制。性能提升了约 60%。",
609
+ timestamp: Date.now() - 144e5
610
+ }
611
+ ]);
612
+ mockSessionHistories.set("4", [
613
+ {
614
+ type: "user",
615
+ content: "帮我为核心模块添加单元测试,覆盖率要达到 80% 以上",
616
+ timestamp: Date.now() - 864e5 - 3e5
617
+ },
618
+ {
619
+ type: "assistant",
620
+ content: "好的,我会使用 Jest 来编写单元测试。让我先看看核心模块的代码结构...",
621
+ timestamp: Date.now() - 864e5 - 25e4
622
+ },
623
+ {
624
+ type: "assistant",
625
+ content: "测试覆盖率已达到 85%,超过了目标。主要测试了:\n- 用户认证逻辑\n- 数据验证\n- 错误处理",
626
+ timestamp: Date.now() - 864e5
627
+ }
628
+ ]);
629
+
630
+ //#endregion
8
631
  //#region ../agent-client-protocol/src/common/types.ts
9
632
  /**
10
633
  * Protocol Extension Types for Agent Client Protocol
@@ -1526,12 +2149,13 @@ var StreamableHttpClient = class {
1526
2149
  * - Emit 'disconnected' event when connection becomes unhealthy so provider can clean up cache
1527
2150
  */
1528
2151
  var CloudAgentConnection = class {
1529
- constructor(agentId, config) {
2152
+ constructor(agentId, config, cwd = "/workspace") {
1530
2153
  this.listeners = /* @__PURE__ */ new Map();
1531
2154
  this.onceListeners = /* @__PURE__ */ new Map();
1532
2155
  this._isStreaming = false;
1533
2156
  this.transport = "cloud";
1534
2157
  this.agentId = agentId;
2158
+ this.cwd = cwd;
1535
2159
  this.client = new StreamableHttpClient({
1536
2160
  endpoint: config.endpoint,
1537
2161
  authToken: config.authToken,
@@ -1687,13 +2311,13 @@ var CloudAgentConnection = class {
1687
2311
  }
1688
2312
  async createSession(params) {
1689
2313
  return {
1690
- ...await this.client.loadSession(this.agentId, params.cwd),
2314
+ ...await this.client.loadSession(this.agentId, this.cwd),
1691
2315
  sessionId: this.agentId
1692
2316
  };
1693
2317
  }
1694
2318
  async loadSession(params) {
1695
2319
  if (!params.sessionId) throw new Error("sessionId is required for loadSession");
1696
- return this.client.loadSession(params.sessionId, params.cwd);
2320
+ return this.client.loadSession(params.sessionId, this.cwd);
1697
2321
  }
1698
2322
  async setSessionMode(sessionId, modeId) {
1699
2323
  return this.client.setSessionMode({
@@ -1852,7 +2476,6 @@ var E2BFilesystem = class E2BFilesystem {
1852
2476
  */
1853
2477
  static async connect(info) {
1854
2478
  return new E2BFilesystem(await e2b.Sandbox.connect(info.sandboxId, {
1855
- apiKey: info.apiKey,
1856
2479
  domain: info.domain,
1857
2480
  apiUrl: info.apiUrl,
1858
2481
  requestTimeoutMs: info.requestTimeoutMs,
@@ -1896,6 +2519,49 @@ var E2BFilesystem = class E2BFilesystem {
1896
2519
  }
1897
2520
  };
1898
2521
 
2522
+ //#endregion
2523
+ //#region ../agent-provider/src/common/utils/concurrency.ts
2524
+ /**
2525
+ * 并发执行任务,返回包含成功/失败状态的结果
2526
+ *
2527
+ * @param tasks - 任务函数数组
2528
+ * @param concurrency - 最大并发数,默认 5
2529
+ * @returns 所有任务的结果数组,包含状态信息
2530
+ *
2531
+ * @example
2532
+ * ```typescript
2533
+ * const results = await runWithConcurrencySettled(tasks, 3);
2534
+ * const successes = results.filter(r => r.status === 'fulfilled');
2535
+ * const failures = results.filter(r => r.status === 'rejected');
2536
+ * ```
2537
+ */
2538
+ async function runWithConcurrencySettled(tasks, concurrency = 5) {
2539
+ if (tasks.length === 0) return [];
2540
+ const limit = Math.max(1, concurrency);
2541
+ const results = new Array(tasks.length);
2542
+ let currentIndex = 0;
2543
+ async function runNext() {
2544
+ while (currentIndex < tasks.length) {
2545
+ const index = currentIndex++;
2546
+ const task = tasks[index];
2547
+ try {
2548
+ results[index] = {
2549
+ status: "fulfilled",
2550
+ value: await task()
2551
+ };
2552
+ } catch (reason) {
2553
+ results[index] = {
2554
+ status: "rejected",
2555
+ reason
2556
+ };
2557
+ }
2558
+ }
2559
+ }
2560
+ const workers = Array(Math.min(limit, tasks.length)).fill(null).map(() => runNext());
2561
+ await Promise.all(workers);
2562
+ return results;
2563
+ }
2564
+
1899
2565
  //#endregion
1900
2566
  //#region ../agent-provider/src/common/providers/cloud-agent-provider/cos-upload-service.ts
1901
2567
  /**
@@ -1906,6 +2572,7 @@ var E2BFilesystem = class E2BFilesystem {
1906
2572
  * const service = new CosUploadService({
1907
2573
  * request: (method, path, body) => cloudProvider.request(method, path, body),
1908
2574
  * logger: console,
2575
+ * uploadConcurrency: 3,
1909
2576
  * });
1910
2577
  *
1911
2578
  * const result = await service.uploadFile(file);
@@ -1919,6 +2586,7 @@ var CosUploadService = class {
1919
2586
  this.request = options.request;
1920
2587
  this.logger = options.logger;
1921
2588
  this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
2589
+ this.uploadConcurrency = options.uploadConcurrency ?? 3;
1922
2590
  }
1923
2591
  /**
1924
2592
  * 生成唯一的 objectKey
@@ -1929,13 +2597,13 @@ var CosUploadService = class {
1929
2597
  return `uploads/${Date.now()}-${Math.random().toString(36).substring(2, 10)}-${encodeURIComponent(filename)}`;
1930
2598
  }
1931
2599
  /**
1932
- * 获取预签名 URL
2600
+ * 批量获取预签名 URL
1933
2601
  *
1934
- * POST /conversation/artifacts/presigned_url
2602
+ * POST /conversations/presigned_url
1935
2603
  */
1936
- async getPresignedUrl(params) {
1937
- const response = await this.request("POST", "/conversation/artifacts/presigned_url", params);
1938
- if (!response.ok) throw new Error(`Failed to get presigned URL: ${response.statusText}`);
2604
+ async getPresignedUrls(objectKeys) {
2605
+ const response = await this.request("POST", "/conversations/presigned_url", { object_keys: objectKeys });
2606
+ if (!response.ok) throw new Error(`Failed to get presigned URLs: ${response.statusText}`);
1939
2607
  const apiResponse = await response.json();
1940
2608
  if (!apiResponse.data) throw new Error("No data in presigned URL response");
1941
2609
  return apiResponse.data;
@@ -1944,22 +2612,17 @@ var CosUploadService = class {
1944
2612
  * 上传单个文件到 COS
1945
2613
  *
1946
2614
  * @param file - 要上传的文件
1947
- * @param expireSeconds - 预签名 URL 过期时间(秒),默认 3600
1948
2615
  * @returns 上传结果,包含访问 URL 或错误信息
1949
2616
  */
1950
- async uploadFile(file, expireSeconds = 3600) {
2617
+ async uploadFile(file) {
1951
2618
  const filename = file.name;
1952
2619
  this.logger?.info(`[CosUploadService] Uploading file: ${filename}`);
1953
2620
  try {
1954
2621
  const objectKey = this.generateObjectKey(filename);
1955
2622
  this.logger?.debug(`[CosUploadService] Generated objectKey: ${objectKey}`);
1956
- const putPresigned = await this.getPresignedUrl({
1957
- object_key: objectKey,
1958
- method: "PUT",
1959
- expire_seconds: expireSeconds
1960
- });
1961
- this.logger?.debug(`[CosUploadService] Got PUT presigned URL`);
1962
- const uploadResponse = await this.fetchImpl(putPresigned.url, {
2623
+ const presignedItem = (await this.getPresignedUrls([objectKey])).items[0];
2624
+ if (!presignedItem) throw new Error("No presigned URL item returned");
2625
+ const uploadResponse = await this.fetchImpl(presignedItem.upload_url, {
1963
2626
  method: "PUT",
1964
2627
  body: file,
1965
2628
  headers: { "Content-Type": file.type || "application/octet-stream" }
@@ -1969,15 +2632,11 @@ var CosUploadService = class {
1969
2632
  throw new Error(`COS upload failed: ${uploadResponse.status} ${errorText}`);
1970
2633
  }
1971
2634
  this.logger?.debug(`[CosUploadService] File uploaded to COS`);
1972
- const getPresigned = await this.getPresignedUrl({
1973
- object_key: objectKey,
1974
- method: "GET",
1975
- expire_seconds: expireSeconds
1976
- });
1977
2635
  this.logger?.info(`[CosUploadService] Upload success: ${filename}`);
1978
2636
  return {
1979
2637
  success: true,
1980
- url: getPresigned.url
2638
+ url: presignedItem.download_url,
2639
+ objectKey
1981
2640
  };
1982
2641
  } catch (error) {
1983
2642
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
@@ -1991,1260 +2650,1164 @@ var CosUploadService = class {
1991
2650
  /**
1992
2651
  * 批量上传文件到 COS
1993
2652
  *
2653
+ * 使用并发控制,限制同时上传的文件数量
2654
+ *
1994
2655
  * @param files - 要上传的文件数组
1995
- * @param expireSeconds - 预签名 URL 过期时间(秒),默认 3600
1996
2656
  * @returns 所有文件的上传结果
1997
2657
  */
1998
- async uploadFiles(files, expireSeconds = 3600) {
1999
- this.logger?.info(`[CosUploadService] Uploading ${files.length} file(s)`);
2000
- const results = [];
2001
- const urls = [];
2002
- for (const file of files) {
2003
- const result = await this.uploadFile(file, expireSeconds);
2004
- results.push(result);
2005
- if (result.success && result.url) urls.push(result.url);
2006
- }
2007
- const failedResults = results.filter((r) => !r.success);
2008
- if (failedResults.length > 0) {
2009
- const failedErrors = failedResults.map((r) => r.error).join("; ");
2658
+ async uploadFiles(files) {
2659
+ if (files.length === 0) return {
2660
+ success: true,
2661
+ urls: [],
2662
+ results: []
2663
+ };
2664
+ this.logger?.info(`[CosUploadService] Uploading ${files.length} file(s) with concurrency ${this.uploadConcurrency}`);
2665
+ try {
2666
+ const fileInfos = files.map((file) => ({
2667
+ file,
2668
+ objectKey: this.generateObjectKey(file.name)
2669
+ }));
2670
+ const objectKeys = fileInfos.map((info) => info.objectKey);
2671
+ const presignedResponse = await this.getPresignedUrls(objectKeys);
2672
+ this.logger?.debug(`[CosUploadService] Got ${presignedResponse.items.length} presigned URLs`);
2673
+ const presignedMap = /* @__PURE__ */ new Map();
2674
+ for (const item of presignedResponse.items) presignedMap.set(item.object_key, item);
2675
+ const results = (await runWithConcurrencySettled(fileInfos.map(({ file, objectKey }) => async () => {
2676
+ const presignedItem = presignedMap.get(objectKey);
2677
+ if (!presignedItem) return {
2678
+ success: false,
2679
+ error: `No presigned URL for ${file.name}`,
2680
+ objectKey
2681
+ };
2682
+ try {
2683
+ const uploadResponse = await this.fetchImpl(presignedItem.upload_url, {
2684
+ method: "PUT",
2685
+ body: file,
2686
+ headers: { "Content-Type": file.type || "application/octet-stream" }
2687
+ });
2688
+ if (!uploadResponse.ok) {
2689
+ const errorText = await uploadResponse.text().catch(() => uploadResponse.statusText);
2690
+ return {
2691
+ success: false,
2692
+ error: `COS upload failed: ${uploadResponse.status} ${errorText}`,
2693
+ objectKey
2694
+ };
2695
+ }
2696
+ this.logger?.debug(`[CosUploadService] Uploaded: ${file.name}`);
2697
+ return {
2698
+ success: true,
2699
+ url: presignedItem.download_url,
2700
+ objectKey
2701
+ };
2702
+ } catch (error) {
2703
+ return {
2704
+ success: false,
2705
+ error: error instanceof Error ? error.message : "Unknown error",
2706
+ objectKey
2707
+ };
2708
+ }
2709
+ }), this.uploadConcurrency)).map((result, index) => {
2710
+ if (result.status === "fulfilled") return result.value;
2711
+ return {
2712
+ success: false,
2713
+ error: result.reason instanceof Error ? result.reason.message : "Unknown error",
2714
+ objectKey: fileInfos[index].objectKey
2715
+ };
2716
+ });
2717
+ const urls = results.filter((r) => r.success && r.url).map((r) => r.url);
2718
+ const failedResults = results.filter((r) => !r.success);
2719
+ if (failedResults.length > 0) {
2720
+ const failedErrors = failedResults.map((r) => r.error).join("; ");
2721
+ return {
2722
+ success: false,
2723
+ error: `${failedResults.length} file(s) failed: ${failedErrors}`,
2724
+ expireSeconds: presignedResponse.expire,
2725
+ results
2726
+ };
2727
+ }
2728
+ this.logger?.info(`[CosUploadService] All ${files.length} file(s) uploaded successfully`);
2010
2729
  return {
2011
- success: false,
2012
- error: `${failedResults.length} file(s) failed: ${failedErrors}`,
2730
+ success: true,
2731
+ urls,
2732
+ expireSeconds: presignedResponse.expire,
2013
2733
  results
2014
2734
  };
2735
+ } catch (error) {
2736
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2737
+ this.logger?.error(`[CosUploadService] Batch upload failed`, error);
2738
+ return {
2739
+ success: false,
2740
+ error: errorMessage,
2741
+ results: files.map(() => ({
2742
+ success: false,
2743
+ error: errorMessage
2744
+ }))
2745
+ };
2015
2746
  }
2016
- this.logger?.info(`[CosUploadService] All ${files.length} file(s) uploaded successfully`);
2017
- return {
2018
- success: true,
2019
- urls,
2020
- results
2021
- };
2022
2747
  }
2023
2748
  };
2024
2749
 
2025
2750
  //#endregion
2026
- //#region ../agent-provider/src/backend/types.ts
2751
+ //#region ../agent-provider/src/account/account-service.ts
2027
2752
  /**
2028
- * 套餐代码
2753
+ * AccountService 类
2754
+ *
2755
+ * 单例模式,管理全局账号状态
2029
2756
  */
2757
+ var AccountService = class {
2758
+ constructor() {
2759
+ this.account = null;
2760
+ this.listeners = /* @__PURE__ */ new Set();
2761
+ this.initialized = false;
2762
+ this.initPromise = null;
2763
+ this.initResolve = null;
2764
+ this.initPromise = new Promise((resolve) => {
2765
+ this.initResolve = resolve;
2766
+ });
2767
+ }
2768
+ /**
2769
+ * 获取当前账号
2770
+ * @returns 当前账号,未登录或未加载时返回 null
2771
+ */
2772
+ getAccount() {
2773
+ return this.account;
2774
+ }
2775
+ /**
2776
+ * 设置账号
2777
+ * @param account 账号信息,登出时传 null
2778
+ */
2779
+ setAccount(account) {
2780
+ const prev = this.account;
2781
+ this.account = account;
2782
+ if (!this.initialized) {
2783
+ this.initialized = true;
2784
+ this.initResolve?.(account);
2785
+ }
2786
+ if (prev?.uid !== account?.uid) this.notifyListeners();
2787
+ }
2788
+ /**
2789
+ * 清除账号(登出)
2790
+ */
2791
+ clearAccount() {
2792
+ this.setAccount(null);
2793
+ }
2794
+ /**
2795
+ * 订阅账号变化
2796
+ * @param callback 变化时的回调函数
2797
+ * @returns 取消订阅函数
2798
+ */
2799
+ subscribe(callback) {
2800
+ this.listeners.add(callback);
2801
+ return () => {
2802
+ this.listeners.delete(callback);
2803
+ };
2804
+ }
2805
+ /**
2806
+ * 等待首次账号加载完成
2807
+ * @returns Promise<Account | null>
2808
+ */
2809
+ waitForInit() {
2810
+ if (this.initialized) return Promise.resolve(this.account);
2811
+ return this.initPromise;
2812
+ }
2813
+ /**
2814
+ * 是否已初始化
2815
+ */
2816
+ isInitialized() {
2817
+ return this.initialized;
2818
+ }
2819
+ /**
2820
+ * 是否已登录
2821
+ */
2822
+ isLoggedIn() {
2823
+ return this.account !== null;
2824
+ }
2825
+ /**
2826
+ * 通知所有订阅者
2827
+ */
2828
+ notifyListeners() {
2829
+ this.listeners.forEach((callback) => {
2830
+ try {
2831
+ callback(this.account);
2832
+ } catch (error) {
2833
+ console.error("[AccountService] Listener error:", error);
2834
+ }
2835
+ });
2836
+ }
2837
+ /**
2838
+ * 重置服务状态(仅用于测试)
2839
+ */
2840
+ _reset() {
2841
+ this.account = null;
2842
+ this.listeners.clear();
2843
+ this.initialized = false;
2844
+ this.initPromise = new Promise((resolve) => {
2845
+ this.initResolve = resolve;
2846
+ });
2847
+ }
2848
+ };
2030
2849
  /**
2031
- * TCACA_code_001_PqouKr6QWV CodeBuddy海外版免费包
2032
- * TCACA_code_002_AkiJS3ZHF5 CodeBuddy海外版Pro版本包-包月/CodeBuddy Pro Plan - Monthly:
2033
- * TCACA_code_006_DbXS0lrypC CodeBuddy海外版一次性免费赠送2周的Pro版本包/CodeBuddy One-time Free 2-Week Pro Plan Trial
2034
- * TCACA_code_007_nzdH5h4Nl0 CodeBuddy海外版运营裂变包/CodeBuddy Growth Plan
2035
- * TCACA_code_003_FAnt7lcmRT CodeBuddy海外版Pro版本包-包年/CodeBuddy Pro Plan - Yearly
2036
- * TCACA_code_008_cfWoLwvjU4 赠送月包
2850
+ * 导出单例实例
2037
2851
  */
2038
- let CommodityCode = /* @__PURE__ */ function(CommodityCode) {
2039
- CommodityCode["free"] = "TCACA_code_001_PqouKr6QWV";
2040
- CommodityCode["proMon"] = "TCACA_code_002_AkiJS3ZHF5";
2041
- CommodityCode["gift"] = "TCACA_code_006_DbXS0lrypC";
2042
- CommodityCode["activity"] = "TCACA_code_007_nzdH5h4Nl0";
2043
- CommodityCode["proYear"] = "TCACA_code_003_FAnt7lcmRT";
2044
- CommodityCode["freeMon"] = "TCACA_code_008_cfWoLwvjU4";
2045
- CommodityCode["extra"] = "TCACA_code_009_0XmEQc2xOf";
2046
- return CommodityCode;
2047
- }({});
2852
+ const accountService = new AccountService();
2048
2853
 
2049
2854
  //#endregion
2050
- //#region ../agent-provider/src/backend/backend-provider.ts
2855
+ //#region ../agent-provider/src/common/providers/cloud-agent-provider/cloud-provider.ts
2051
2856
  /**
2052
- * Backend Provider 实现
2857
+ * Normalize a path by resolving `.` and `..` segments
2858
+ * This is a simplified version that works in browser environment
2859
+ */
2860
+ function normalizePath(path) {
2861
+ const segments = path.split("/");
2862
+ const result = [];
2863
+ for (const segment of segments) if (segment === "..") {
2864
+ if (result.length > 0 && result[result.length - 1] !== "") result.pop();
2865
+ } else if (segment !== "." && segment !== "") result.push(segment);
2866
+ return (path.startsWith("/") ? "/" : "") + result.join("/");
2867
+ }
2868
+ /**
2869
+ * Resolve agent:/// URI to filesystem path
2053
2870
  *
2054
- * 封装与后端 API HTTP 通信
2871
+ * 只处理 agent:// 协议的 URI,raw path 直接透传不做任何处理
2872
+ *
2873
+ * Supported formats:
2874
+ * - `agent:///workspace/{path}` → `/workspace/{path}`
2875
+ * - `agent:///plans/{path}` → `/root/.codebuddy/plans/{path}`
2876
+ * - `agent:///{path}` → `/{path}`
2877
+ * - Raw paths (e.g. `/foo/bar`) → 直接透传,不处理
2878
+ *
2879
+ * Security: Path traversal attacks are prevented by normalizing paths
2880
+ * and verifying they stay within expected boundaries.
2055
2881
  */
2056
- /** 获取当前域名的登录页面 URL */
2057
- const getLoginUrl = () => `${window.location.origin}/login`;
2058
- /** 获取当前域名的账号选择页面 URL */
2059
- const getSelectAccountUrl = () => `${window.location.origin}/login/select`;
2060
- /** localStorage 中存储选中账号 ID 的 key */
2061
- const SELECTED_ACCOUNT_KEY = "CODEBUDDY_IDE_SELECTED_ACCOUNT_ID";
2882
+ function resolveAgentUri(input) {
2883
+ if (!input.startsWith("agent://")) return input;
2884
+ const path = input.slice(8);
2885
+ if (!path.startsWith("/")) return input;
2886
+ const normalizedPath = normalizePath(path);
2887
+ if (normalizedPath.startsWith("/plans/") || normalizedPath === "/plans") {
2888
+ const finalPath = normalizePath("/root/.codebuddy" + normalizedPath);
2889
+ if (!finalPath.startsWith("/root/.codebuddy/plans")) throw new Error(`Invalid path: path traversal detected in ${input}`);
2890
+ return finalPath;
2891
+ }
2892
+ return normalizedPath;
2893
+ }
2062
2894
  /**
2063
- * Backend Provider 实现类
2895
+ * Create a FilesResource wrapper that resolves agent:/// URIs
2064
2896
  */
2065
- var BackendProvider = class {
2066
- constructor(config) {
2067
- this.baseUrl = config.baseUrl.replace(/\/$/, "");
2068
- this.authToken = config.authToken;
2897
+ function createAgentFilesystem(fs) {
2898
+ return {
2899
+ read: (path, opts) => fs.read(resolveAgentUri(path), opts),
2900
+ write: (pathOrFiles, dataOrOpts, opts) => {
2901
+ if (Array.isArray(pathOrFiles)) {
2902
+ const resolved = pathOrFiles.map((f) => ({
2903
+ ...f,
2904
+ path: resolveAgentUri(f.path)
2905
+ }));
2906
+ return fs.write(resolved, dataOrOpts);
2907
+ }
2908
+ return fs.write(resolveAgentUri(pathOrFiles), dataOrOpts, opts);
2909
+ },
2910
+ list: (path, opts) => fs.list(resolveAgentUri(path), opts),
2911
+ exists: (path, opts) => fs.exists(resolveAgentUri(path), opts),
2912
+ makeDir: (path, opts) => fs.makeDir(resolveAgentUri(path), opts),
2913
+ remove: (path, opts) => fs.remove(resolveAgentUri(path), opts),
2914
+ rename: (oldPath, newPath, opts) => fs.rename(resolveAgentUri(oldPath), resolveAgentUri(newPath), opts),
2915
+ getInfo: (path, opts) => fs.getInfo(resolveAgentUri(path), opts),
2916
+ watchDir: (path, onEvent, opts) => fs.watchDir(resolveAgentUri(path), onEvent, opts)
2917
+ };
2918
+ }
2919
+ /**
2920
+ * CloudAgentProvider - Manages cloud-hosted agents via REST API
2921
+ *
2922
+ * API Endpoints:
2923
+ * - POST {endpoint}/console/cloudagent/agentmgmt/agents - Create new agent
2924
+ * - GET {endpoint}/console/cloudagent/agentmgmt/agents/{id} - Get agent data
2925
+ * - GET {endpoint}/console/cloudagent/agentmgmt/agents - List all agents
2926
+ * - POST {endpoint}/console/cloudagent/agentmgmt/agents/{id}/delete - Delete agent
2927
+ * - GET {endpoint}/console/cloudagent/agentmgmt/agents/{id}/session - Get agent session (includes sandboxId)
2928
+ * - GET {endpoint}/console/cloudagent/agentmgmt/models - Get available models
2929
+ *
2930
+ * The provider stores agent endpoint configurations in the cloud backend.
2931
+ * When connect() is called, it creates a CloudAgentConnection to the agent's
2932
+ * endpoint and returns an Agent instance.
2933
+ *
2934
+ * @example
2935
+ * ```typescript
2936
+ * const provider = new CloudAgentProvider({
2937
+ * endpoint: 'https://staging-copilot.tencent.com',
2938
+ * authToken: 'token'
2939
+ * });
2940
+ *
2941
+ * // List all agents (uses default pagination and sorting)
2942
+ * const allAgents = await provider.list();
2943
+ *
2944
+ * // List agents with custom pagination
2945
+ * const page2 = await provider.list({
2946
+ * page: 2,
2947
+ * size: 50
2948
+ * });
2949
+ *
2950
+ * // List agents with filtering
2951
+ * const runningAgents = await provider.list({
2952
+ * filters: [
2953
+ * { field: 'status', value: 'running' }
2954
+ * ]
2955
+ * });
2956
+ *
2957
+ * // List agents with custom sorting
2958
+ * const sortedAgents = await provider.list({
2959
+ * sort: {
2960
+ * orderBy: 'createdAt',
2961
+ * order: 'desc'
2962
+ * }
2963
+ * });
2964
+ *
2965
+ * // List agents created in last 14 days with multiple filters
2966
+ * const recentAgents = await provider.list({
2967
+ * dayRange: 14,
2968
+ * filters: [
2969
+ * { field: 'status', value: 'running,stopped' }
2970
+ * ],
2971
+ * page: 1,
2972
+ * size: 20
2973
+ * });
2974
+ *
2975
+ * // Get agent state
2976
+ * const state = await provider.get('agent-id');
2977
+ *
2978
+ * // Connect to agent
2979
+ * const agent = await provider.connect('agent-id');
2980
+ *
2981
+ * // Use agent
2982
+ * const session = await agent.sessions.create({ cwd: '/workspace' });
2983
+ *
2984
+ * // Get available models
2985
+ * const models = await provider.getModels('my-repo');
2986
+ * ```
2987
+ */
2988
+ var CloudAgentProvider = class CloudAgentProvider {
2989
+ constructor(options) {
2990
+ this.filesystemCache = /* @__PURE__ */ new Map();
2991
+ this.connectionCache = /* @__PURE__ */ new Map();
2992
+ this.eventListeners = /* @__PURE__ */ new Map();
2993
+ this.options = options;
2994
+ this.logger = options.logger;
2995
+ this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
2996
+ this.cosUploadService = new CosUploadService({
2997
+ request: (method, path, body) => this.request(method, path, body),
2998
+ logger: this.logger,
2999
+ fetch: this.fetchImpl
3000
+ });
2069
3001
  }
2070
3002
  /**
2071
- * 获取 Agent 列表
2072
- * API 端点: GET /v2/cloudagent/agentmgmt/agents
3003
+ * Dispose the provider and clean up resources
2073
3004
  */
2074
- async getAgents(request = {}) {
2075
- const { MockAgentProvider } = await Promise.resolve().then(() => require("./MockAgentProvider-4e4oOusg.cjs"));
2076
- const sessions = new MockAgentProvider().getAllSessions();
2077
- const mockTitles = {
2078
- "1": "开发五子棋游戏",
2079
- "2": "修复登录页面样式",
2080
- "3": "API 接口优化"
2081
- };
2082
- const agents = sessions.map((session, index) => ({
2083
- id: session.sessionId,
2084
- name: mockTitles[session.sessionId] || `Agent ${session.sessionId}`,
2085
- status: "RUNNING",
2086
- visibility: "PRIVATE",
2087
- createdAt: new Date(session.createdAt).toISOString(),
2088
- summary: `Session created at ${new Date(session.createdAt).toLocaleString()}`,
2089
- source: {
2090
- provider: "github",
2091
- ref: "refs/heads/main",
2092
- repository: session.cwd
2093
- },
2094
- target: {
2095
- autoCreatePr: false,
2096
- branchName: "feature/mock",
2097
- prUrl: void 0,
2098
- url: void 0
2099
- }
2100
- }));
2101
- return {
2102
- agents,
2103
- pagination: {
2104
- hasNext: false,
2105
- hasPrev: false,
2106
- page: 1,
2107
- size: agents.length,
2108
- total: agents.length,
2109
- totalPages: 1
2110
- }
2111
- };
3005
+ dispose() {
3006
+ this.filesystemCache.clear();
3007
+ this.connectionCache.clear();
2112
3008
  }
2113
3009
  /**
2114
- * 获取可用模型列表
2115
- * API 端点: GET /v2/cloudagent/models (假设)
2116
- *
2117
- * 当前实现: 返回 Mock 数据
2118
- */
2119
- async getModels(request) {
2120
- const mockModels = [{
2121
- id: "glm-4.7",
2122
- name: "GLM-4.7",
2123
- vendor: "f",
2124
- maxOutputTokens: 48e3,
2125
- maxInputTokens: 2e5,
2126
- supportsToolCall: true,
2127
- supportsImages: false,
2128
- disabledMultimodal: true,
2129
- maxAllowedSize: 2e5,
2130
- supportsReasoning: true,
2131
- onlyReasoning: true,
2132
- temperature: 1,
2133
- reasoning: {
2134
- effort: "medium",
2135
- summary: "auto"
2136
- },
2137
- descriptionEn: "GLM-4.7 model, Well-rounded model for everyday use",
2138
- descriptionZh: "GLM-4.7 大模型,能力均衡,适合日常使用"
2139
- }, {
2140
- id: "glm-4.7-flash",
2141
- name: "GLM-4.7 Flash",
2142
- vendor: "f",
2143
- maxOutputTokens: 4e4,
2144
- maxInputTokens: 128e3,
2145
- supportsToolCall: true,
2146
- supportsImages: false,
2147
- disabledMultimodal: false,
2148
- maxAllowedSize: 128e3,
2149
- supportsReasoning: false,
2150
- onlyReasoning: false,
2151
- temperature: .7,
2152
- reasoning: {
2153
- effort: "low",
2154
- summary: "never"
2155
- },
2156
- descriptionEn: "GLM-4.7 Flash, Fast and efficient model",
2157
- descriptionZh: "GLM-4.7 Flash,快速高效的模型"
2158
- }];
2159
- console.log("[BackendProvider] getModels called for repository:", request.repository);
2160
- return { models: mockModels };
3010
+ * Get the filesystem provider (returns self)
3011
+ */
3012
+ get filesystem() {
3013
+ return this;
2161
3014
  }
2162
3015
  /**
2163
- * 获取当前账号信息
2164
- * API 端点: GET /console/accounts (返回账号列表)
2165
- *
2166
- * 逻辑:
2167
- * 1. 从 localStorage 读取 CODEBUDDY_IDE_SELECTED_ACCOUNT_ID
2168
- * 2. 根据 CODEBUDDY_IDE_SELECTED_ACCOUNT_ID 找到对应账号
2169
- * - personal 类型: uid 匹配
2170
- * - 其他类型: 用 enterpriseId 匹配
2171
- * 3. 如果没有选中的账号,跳转到账号选择页面
2172
- * 4. 获取套餐信息并合并到账号中
3016
+ * Get filesystem resource for an agent
3017
+ *
3018
+ * Creates or returns cached filesystem instance for the agent's sandbox.
3019
+ * The filesystem supports both `agent:///` URIs and raw paths.
3020
+ *
3021
+ * @param agentId - Agent ID to get filesystem for
3022
+ * @returns FilesResource instance for the agent's sandbox (with URI support)
3023
+ *
3024
+ * @example
3025
+ * ```typescript
3026
+ * const fs = await provider.getFilesystem(agentId);
3027
+ *
3028
+ * // Use agent:/// URIs
3029
+ * const content = await fs.read('agent:///files/src/app.ts');
3030
+ * await fs.write('agent:///artifacts/output.txt', 'Hello');
3031
+ *
3032
+ * // Raw paths still work (backward compatible)
3033
+ * const content2 = await fs.read('/src/app.ts');
3034
+ * ```
2173
3035
  */
2174
- async getAccount() {
2175
- const url = `${this.baseUrl}/console/accounts`;
2176
- const headers = {
2177
- "Content-Type": "application/json",
2178
- "Accept": "application/json"
3036
+ async getFilesystem(agentId) {
3037
+ const cached = this.filesystemCache.get(agentId);
3038
+ if (cached) return cached;
3039
+ const info = await this.getSandboxInfo(agentId);
3040
+ const filesystem = createAgentFilesystem(await E2BFilesystem.connect(info));
3041
+ this.filesystemCache.set(agentId, filesystem);
3042
+ this.logger?.debug(`Created filesystem for agent: ${agentId}`);
3043
+ return filesystem;
3044
+ }
3045
+ /**
3046
+ * Get sandbox information from backend
3047
+ *
3048
+ * Uses GET {endpoint}/console/cloudagent/agentmgmt/agents/{agentId}/session
3049
+ * to retrieve sandbox information. Extracts sandboxId from the session response
3050
+ * and constructs the apiUrl for E2B proxy.
3051
+ *
3052
+ * @param agentId - Agent ID
3053
+ * @returns E2B Sandbox connection information with sandboxId and apiUrl
3054
+ */
3055
+ async getSandboxInfo(agentId) {
3056
+ const response = await this.request("GET", `/console/cloudagent/agentmgmt/agents/${agentId}/session`);
3057
+ if (!response.ok) throw new Error(`Failed to get sandbox info: ${response.statusText}`);
3058
+ const apiResponse = await response.json();
3059
+ if (!apiResponse.data) throw new Error("No data in API response");
3060
+ const apiUrl = new URL(`/console/cloudagent/e2bproxy/agents/${agentId}`, this.options.endpoint).toString();
3061
+ const currentEnterpriseId = localStorage.getItem("currentEnterpriseId");
3062
+ return {
3063
+ sandboxId: apiResponse.data.sandboxId,
3064
+ apiUrl,
3065
+ accessToken: apiResponse.data.token,
3066
+ headers: { ...currentEnterpriseId && { "X-Enterprise-Id": currentEnterpriseId } }
2179
3067
  };
2180
- if (this.authToken) headers["Authorization"] = `Bearer ${this.authToken}`;
3068
+ }
3069
+ /**
3070
+ * Get agent state by ID
3071
+ */
3072
+ async get(agentId) {
2181
3073
  try {
2182
- const response = await fetch(url, {
2183
- method: "GET",
2184
- headers,
2185
- credentials: "include"
2186
- });
2187
- if (!response.ok) {
2188
- if (response.status === 401 || response.status === 403) return null;
2189
- const error = await response.json().catch(() => ({ message: response.statusText }));
2190
- throw new Error(error.message || `HTTP ${response.status}`);
2191
- }
2192
- if (!(response.headers.get("Content-Type") || "").includes("application/json")) return null;
2193
- const { data = {} } = await response.json();
2194
- const accounts = data?.accounts || [];
2195
- if (!accounts || accounts.length === 0) return null;
2196
- const selectedAccountId = localStorage.getItem(SELECTED_ACCOUNT_KEY);
2197
- let selectedAccount;
2198
- if (selectedAccountId) {
2199
- selectedAccount = accounts.find((account) => {
2200
- if (account.type === "personal") return account.uid === selectedAccountId;
2201
- return account.enterpriseId === selectedAccountId;
2202
- });
2203
- if (selectedAccount) try {
2204
- const plan = await this.getCurrentPlan();
2205
- const editionType = this.getEditionDisplayType(selectedAccount.type, plan.isPro);
2206
- console.log("account", {
2207
- ...selectedAccount,
2208
- ...plan,
2209
- editionType
2210
- });
2211
- return {
2212
- ...selectedAccount,
2213
- ...plan,
2214
- editionType
2215
- };
2216
- } catch (error) {
2217
- return { ...selectedAccount };
2218
- }
2219
- }
2220
- if (accounts.length === 1) {
2221
- selectedAccount = accounts[0];
2222
- const accountId = selectedAccount.type === "personal" ? selectedAccount.uid : selectedAccount.enterpriseId;
2223
- if (accountId) localStorage.setItem(SELECTED_ACCOUNT_KEY, accountId);
2224
- const plan = await this.getCurrentPlan();
2225
- const editionType = this.getEditionDisplayType(selectedAccount.type, plan.isPro);
2226
- console.log("account (auto-selected)", {
2227
- ...selectedAccount,
2228
- ...plan,
2229
- editionType
2230
- });
2231
- return {
2232
- ...selectedAccount,
2233
- ...plan,
2234
- editionType
2235
- };
2236
- }
2237
- const redirectUrl = encodeURIComponent(window.location.href);
2238
- window.location.href = `${getSelectAccountUrl()}?platform=website&state=0&redirect_uri=${redirectUrl}`;
2239
- return null;
3074
+ const response = await this.request("GET", `/console/cloudagent/agentmgmt/agents/${agentId}`);
3075
+ if (response.status === 404) return;
3076
+ if (!response.ok) throw new Error(`Failed to get agent: ${response.statusText}`);
3077
+ const apiResponse = await response.json();
3078
+ if (!apiResponse.data) throw new Error("No data in API response");
3079
+ return this.toAgentState(apiResponse.data);
2240
3080
  } catch (error) {
2241
- console.error("[BackendProvider] getAccount failed:", error);
2242
- return null;
3081
+ this.logger?.error(`Failed to get agent ${agentId}:`, error);
3082
+ throw error;
2243
3083
  }
2244
3084
  }
2245
3085
  /**
2246
- * 获取当前套餐信息
2247
- * 从计量计费接口获取用户的套餐信息
2248
- * API: POST /billing/meter/get-user-resource
3086
+ * List all agent states with pagination information
3087
+ *
3088
+ * @param options - Optional query parameters for filtering, sorting, and pagination
3089
+ * @returns Object containing agents array and pagination info
2249
3090
  */
2250
- async getCurrentPlan() {
2251
- const defaultPlan = {
2252
- isPro: false,
2253
- expireAt: 0,
2254
- renewFlag: 0,
2255
- PackageCode: void 0,
2256
- name: ""
2257
- };
3091
+ async list(options) {
2258
3092
  try {
2259
- const url = `${this.baseUrl}/billing/meter/get-user-resource`;
2260
- const headers = {
2261
- "Content-Type": "application/json",
2262
- "Accept": "application/json"
2263
- };
2264
- if (this.authToken) headers["Authorization"] = `Bearer ${this.authToken}`;
2265
- const now = /* @__PURE__ */ new Date();
2266
- const futureDate = new Date(now.getTime() + 101 * 365 * 24 * 60 * 60 * 1e3);
2267
- const formatDate = (d) => {
2268
- const pad = (n) => n.toString().padStart(2, "0");
2269
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
2270
- };
2271
- const body = {
2272
- PageNumber: 1,
2273
- PageSize: 100,
2274
- ProductCode: "p_tcaca",
2275
- Status: [0, 3],
2276
- PackageEndTimeRangeBegin: formatDate(now),
2277
- PackageEndTimeRangeEnd: formatDate(futureDate)
3093
+ console.log("[CloudAgentProvider] list called with options:", JSON.stringify(options, null, 2));
3094
+ const params = {
3095
+ page: 1,
3096
+ size: 30,
3097
+ sort: {
3098
+ order: "desc",
3099
+ orderBy: "status"
3100
+ },
3101
+ ...options && {
3102
+ ...options.dayRange !== void 0 && { dayRange: options.dayRange },
3103
+ ...options.page !== void 0 && { page: options.page },
3104
+ ...options.size !== void 0 && { size: options.size },
3105
+ ...options.sort !== void 0 && { sort: options.sort },
3106
+ ...options.filters !== void 0 && { filters: options.filters },
3107
+ ...options.title !== void 0 && { title: options.title }
3108
+ }
2278
3109
  };
2279
- const response = await fetch(url, {
2280
- method: "POST",
2281
- headers,
2282
- credentials: "include",
2283
- body: JSON.stringify(body)
3110
+ console.log("[CloudAgentProvider] API request params:", JSON.stringify(params, null, 2));
3111
+ const response = await this.request("GET", "/console/cloudagent/agentmgmt/agents", params);
3112
+ if (!response.ok) throw new Error(`Failed to list agents: ${response.statusText}`);
3113
+ const apiResponse = await response.json();
3114
+ if (!apiResponse.data) throw new Error("No data in API response");
3115
+ const agents = apiResponse.data.agents.map((a) => this.toAgentState(a));
3116
+ const pagination = apiResponse.data.pagination;
3117
+ console.log("[CloudAgentProvider] API response:", {
3118
+ agentsCount: agents.length,
3119
+ pagination
2284
3120
  });
2285
- if (!response.ok) {
2286
- console.warn("[BackendProvider] getCurrentPlan failed:", response.status);
2287
- return defaultPlan;
2288
- }
2289
- const result = await response.json();
2290
- const resources = result?.data?.Response?.Data?.Accounts || result?.data?.Accounts || [];
2291
- if (!resources || resources.length === 0) return defaultPlan;
2292
- const proPlan = resources.find((r) => r.PackageCode === CommodityCode.proYear || r.PackageCode === CommodityCode.proMon);
2293
- const trialPlan = resources.find((r) => r.PackageCode === CommodityCode.gift);
2294
- const activePlan = proPlan || trialPlan;
2295
- if (activePlan) return {
2296
- isPro: !!proPlan,
2297
- expireAt: activePlan.ExpiredTime || activePlan.CycleEndTime || 0,
2298
- renewFlag: Number(activePlan.AutoRenewFlag) === 1 ? 1 : 0,
2299
- PackageCode: activePlan.PackageCode,
2300
- name: activePlan.PackageName || ""
3121
+ return {
3122
+ agents,
3123
+ pagination
2301
3124
  };
2302
- return defaultPlan;
2303
3125
  } catch (error) {
2304
- console.error("[BackendProvider] getCurrentPlan error:", error);
2305
- return defaultPlan;
3126
+ this.logger?.error("Failed to list agents:", error);
3127
+ throw error;
2306
3128
  }
2307
3129
  }
2308
3130
  /**
2309
- * 根据账号类型和 Pro 状态计算版本展示类型
2310
- * - personal + isPro = 'pro'
2311
- * - personal + !isPro = 'free'
2312
- * - ultimate = 'ultimate' (旗舰版/团队版)
2313
- * - exclusive = 'exclusive' (专享版/企业版)
2314
- */
2315
- getEditionDisplayType(type, isPro) {
2316
- if (type === "personal") return isPro ? "pro" : "free";
2317
- if (type === "ultimate") return "ultimate";
2318
- if (type === "exclusive") return "exclusive";
2319
- return "free";
2320
- }
2321
- /**
2322
- * 触发登录流程
2323
- * Web 环境: 跳转到登录页面
2324
- */
2325
- async login() {
2326
- const redirectUrl = encodeURIComponent(window.location.href);
2327
- window.location.href = `${getLoginUrl()}?platform=website&state=0&redirect_uri=${redirectUrl}`;
2328
- }
2329
- /**
2330
- * 登出账号
2331
- * Web 环境: 跳转到登出页面或清除 cookie
3131
+ * Create a new agent
3132
+ * POST {endpoint}/console/cloudagent/agentmgmt/agents
2332
3133
  */
2333
- async logout() {
2334
- const url = `${this.baseUrl}/console/logout`;
3134
+ async create() {
2335
3135
  try {
2336
- await fetch(url, {
2337
- method: "POST",
2338
- credentials: "include"
3136
+ const response = await this.request("POST", "/console/cloudagent/agentmgmt/agents", {
3137
+ prompt: "",
3138
+ model: "deepseek-r1"
2339
3139
  });
3140
+ if (!response.ok) throw new Error(`Failed to create agent: ${response.statusText}`);
3141
+ const apiResponse = await response.json();
3142
+ if (!apiResponse.data) throw new Error("No data in API response");
3143
+ this.logger?.info(`Created agent: ${apiResponse.data.id}`);
3144
+ return apiResponse.data.id;
2340
3145
  } catch (error) {
2341
- console.error("[BackendProvider] logout failed:", error);
3146
+ this.logger?.error("Failed to create agent:", error);
3147
+ throw error;
2342
3148
  }
2343
- localStorage.removeItem(SELECTED_ACCOUNT_KEY);
2344
- window.location.reload();
2345
- }
2346
- };
2347
- /**
2348
- * 创建 BackendProvider 实例
2349
- */
2350
- function createBackendProvider(config) {
2351
- return new BackendProvider(config);
2352
- }
2353
-
2354
- //#endregion
2355
- //#region ../agent-provider/src/backend/ipc-backend-provider.ts
2356
- /**
2357
- * Backend 请求类型常量
2358
- */
2359
- const BACKEND_REQUEST_TYPES = {
2360
- GET_AGENTS: "backend:get-agents-request",
2361
- GET_MODELS: "backend:get-models",
2362
- LOGIN: "backend:login",
2363
- LOGOUT: "backend:logout",
2364
- GET_ACCOUNT: "backend:get-account"
2365
- };
2366
- /**
2367
- * 生成唯一请求 ID
2368
- */
2369
- function generateRequestId() {
2370
- return `req-${Date.now()}-${Math.random().toString(36).slice(2)}`;
2371
- }
2372
- /**
2373
- * IPC Backend Provider 实现类
2374
- *
2375
- * 通过 IWidgetChannel 与后端通信获取 Agent 列表
2376
- */
2377
- var IPCBackendProvider = class {
2378
- constructor(config) {
2379
- this.channel = config.channel;
2380
- this.debug = config.debug ?? false;
2381
- this.timeoutMs = config.timeoutMs ?? 3e4;
2382
- this.log("Initialized with IWidgetChannel");
2383
3149
  }
2384
3150
  /**
2385
- * 发送统一格式的后端请求
2386
- * @param requestType 请求类型
2387
- * @param params 请求参数
2388
- * @returns 响应数据
3151
+ * Connect to an agent and return the connection
3152
+ *
3153
+ * This method:
3154
+ * 1. Fetches the agent configuration from the backend
3155
+ * 2. Checks if an existing connection can be reused (based on endpoint link)
3156
+ * 3. Creates a CloudAgentConnection to the agent's endpoint if not cached
3157
+ * 4. Saves session connection info to the connection
3158
+ * 5. Connects and initializes the connection
3159
+ * 6. Returns the connected CloudAgentConnection
3160
+ *
3161
+ * Connection caching:
3162
+ * - Connections are cached by endpoint link to enable reuse
3163
+ * - CloudAgentConnection is responsible for handling connection health checks
3164
+ * and token expiration internally
2389
3165
  */
2390
- async sendBackendRequest(requestType, params) {
2391
- const message = {
2392
- type: "backend",
2393
- requestId: generateRequestId(),
2394
- params: {
2395
- type: requestType,
2396
- params
3166
+ async connect(agentId) {
3167
+ const sessionResponse = await this.request("GET", `/console/cloudagent/agentmgmt/agents/${agentId}/session`);
3168
+ if (sessionResponse.status === 404) throw new Error(`Agent not found: ${agentId}`);
3169
+ if (!sessionResponse.ok) throw new Error(`Failed to get agent for connection: ${sessionResponse.statusText}`);
3170
+ const sessionApiResponse = await sessionResponse.json();
3171
+ if (!sessionApiResponse.data) throw new Error("No data in API response");
3172
+ const sessionData = sessionApiResponse.data;
3173
+ const endpoint = sessionData.link.replace(/^http:\/\//, "https://");
3174
+ const cwd = sessionApiResponse.data.cwd || "/workspace";
3175
+ const agentResponse = await this.request("GET", `/console/cloudagent/agentmgmt/agents/${agentId}`);
3176
+ let agentName;
3177
+ let agentCreatedAt;
3178
+ let agentStatus;
3179
+ if (agentResponse.ok) {
3180
+ const agentApiResponse = await agentResponse.json();
3181
+ if (agentApiResponse.data) {
3182
+ agentName = agentApiResponse.data.name;
3183
+ agentCreatedAt = agentApiResponse.data.createdAt ? new Date(agentApiResponse.data.createdAt) : void 0;
3184
+ agentStatus = agentApiResponse.data.sessionStatus || agentApiResponse.data.status;
3185
+ }
3186
+ }
3187
+ const existingConnection = this.connectionCache.get(endpoint);
3188
+ if (existingConnection?.isInitialized) {
3189
+ this.logger?.info(`Reusing existing connection for agent: ${agentId}`);
3190
+ return existingConnection;
3191
+ }
3192
+ const clientCapabilities = {
3193
+ ...this.options.clientCapabilities,
3194
+ _meta: {
3195
+ ...this.options.clientCapabilities?._meta,
3196
+ "codebuddy.ai": {
3197
+ ...this.options.clientCapabilities?._meta?.["codebuddy.ai"],
3198
+ cwd
3199
+ }
2397
3200
  }
2398
3201
  };
2399
- this.log("Sending backend request:", message);
2400
- const response = await this.channel.callMethod("__backend__", message, this.timeoutMs);
2401
- this.log("Received response:", response);
2402
- if (response?.error) throw new Error(response.error);
2403
- return response?.data !== void 0 ? response.data : response;
2404
- }
2405
- /**
2406
- * 获取 Agent 列表
2407
- * 通过 IWidgetChannel 发送请求到后端
2408
- */
2409
- async getAgents(request = {}) {
2410
- this.log("Getting agents with request:", request);
3202
+ const connection = new CloudAgentConnection(agentId, {
3203
+ endpoint,
3204
+ authToken: sessionData.token,
3205
+ logger: this.logger,
3206
+ clientCapabilities
3207
+ }, cwd);
3208
+ connection.setSessionConnectionInfo({
3209
+ sessionId: sessionData.sessionId,
3210
+ agentId: sessionData.id,
3211
+ link: sessionData.link,
3212
+ token: sessionData.token,
3213
+ sandboxId: sessionData.sandboxId,
3214
+ expireAt: sessionData.expireAt,
3215
+ cwd
3216
+ });
2411
3217
  try {
2412
- return await this.sendBackendRequest(BACKEND_REQUEST_TYPES.GET_AGENTS, request);
3218
+ await connection.connect();
2413
3219
  } catch (error) {
2414
- this.log("Get agents failed:", error);
3220
+ this.logger?.error(`Failed to connect to agent ${agentId}:`, error);
2415
3221
  throw error;
2416
3222
  }
3223
+ this.connectionCache.set(endpoint, connection);
3224
+ connection.once("disconnected", () => {
3225
+ this.connectionCache.delete(endpoint);
3226
+ this.logger?.debug(`Connection removed from cache: ${endpoint}`);
3227
+ });
3228
+ this.logger?.info(`Connected to agent: ${agentId}`);
3229
+ this.emitEvent("sessionCreated", {
3230
+ id: agentId,
3231
+ agentId,
3232
+ name: agentName,
3233
+ status: agentStatus,
3234
+ cwd,
3235
+ createdAt: agentCreatedAt
3236
+ });
3237
+ return connection;
2417
3238
  }
2418
3239
  /**
2419
- * 获取可用模型列表
2420
- * 通过 IWidgetChannel 发送请求到后端
3240
+ * Delete an agent by ID
3241
+ * POST {endpoint}/console/cloudagent/agentmgmt/agents/{agentId}/delete
2421
3242
  */
2422
- async getModels(request) {
2423
- this.log("Getting models with request:", request);
3243
+ async delete(agentId) {
2424
3244
  try {
2425
- return await this.sendBackendRequest(BACKEND_REQUEST_TYPES.GET_MODELS, request);
3245
+ const requestBody = { id: agentId };
3246
+ const response = await this.request("POST", `/console/cloudagent/agentmgmt/agents/${agentId}/delete`, requestBody);
3247
+ if (response.status === 404) return false;
3248
+ if (!response.ok) throw new Error(`Failed to delete agent: ${response.statusText}`);
3249
+ return true;
2426
3250
  } catch (error) {
2427
- this.log("Get models failed:", error);
3251
+ this.logger?.error(`Failed to delete agent ${agentId}:`, error);
2428
3252
  throw error;
2429
3253
  }
2430
3254
  }
2431
3255
  /**
2432
- * 获取当前账号信息
2433
- * IDE 环境: 通过 IPC 获取账号信息
3256
+ * Archive an agent by ID
3257
+ * POST {endpoint}/console/cloudagent/agentmgmt/agents/{agentId}/archive
3258
+ *
3259
+ * @param agentId - Agent ID to archive
3260
+ * @returns ArchiveAgentResponse containing the archived agent ID
3261
+ *
3262
+ * @example
3263
+ * ```typescript
3264
+ * const result = await provider.archive('agent-123');
3265
+ * console.log('Archived agent:', result.id);
3266
+ * ```
2434
3267
  */
2435
- async getAccount() {
2436
- this.log("Getting account via IPC");
3268
+ async archive(agentId) {
2437
3269
  try {
2438
- return await this.sendBackendRequest(BACKEND_REQUEST_TYPES.GET_ACCOUNT);
3270
+ const response = await this.request("POST", `/console/cloudagent/agentmgmt/agents/${agentId}/archive`);
3271
+ if (!response.ok) throw new Error(`Failed to archive agent: ${response.statusText}`);
3272
+ const apiResponse = await response.json();
3273
+ if (!apiResponse.data) {
3274
+ this.logger?.info(`Archived agent: ${agentId}`);
3275
+ return { id: agentId };
3276
+ }
3277
+ this.logger?.info(`Archived agent: ${apiResponse.data.id}`);
3278
+ return apiResponse.data;
2439
3279
  } catch (error) {
2440
- this.log("Get account failed:", error);
2441
- return null;
3280
+ this.logger?.error(`Failed to archive agent ${agentId}:`, error);
3281
+ throw error;
2442
3282
  }
2443
3283
  }
2444
3284
  /**
2445
- * 触发登录流程
2446
- * IDE 环境: 通过 IPC 通知 IDE 打开登录流程
3285
+ * Rename an agent by ID
3286
+ * PATCH {endpoint}/v2/cloudagent/agentmgmt/agents/{agentId}
3287
+ *
3288
+ * @param agentId - Agent ID to rename
3289
+ * @param title - New title for the agent
3290
+ * @returns RenameAgentResponse containing the renamed agent ID
3291
+ *
3292
+ * @example
3293
+ * ```typescript
3294
+ * const result = await provider.rename('agent-123', 'New Title');
3295
+ * console.log('Renamed agent:', result.id);
3296
+ * ```
2447
3297
  */
2448
- async login() {
2449
- this.log("Triggering login via IPC");
3298
+ async rename(agentId, title) {
2450
3299
  try {
2451
- await this.sendBackendRequest(BACKEND_REQUEST_TYPES.LOGIN);
3300
+ const body = { title };
3301
+ const response = await this.request("POST", `/console/cloudagent/agentmgmt/agents/${agentId}`, body);
3302
+ if (!response.ok) throw new Error(`Failed to rename agent: ${response.statusText}`);
3303
+ const apiResponse = await response.json();
3304
+ if (!apiResponse.data) {
3305
+ this.logger?.info(`Renamed agent: ${agentId} to "${title}"`);
3306
+ return { id: agentId };
3307
+ }
3308
+ this.logger?.info(`Renamed agent: ${apiResponse.data.id} to "${title}"`);
3309
+ return apiResponse.data;
2452
3310
  } catch (error) {
2453
- this.log("Login request failed:", error);
3311
+ this.logger?.error(`Failed to rename agent ${agentId}:`, error);
2454
3312
  throw error;
2455
3313
  }
2456
3314
  }
2457
3315
  /**
2458
- * 登出账号
2459
- * IDE 环境: 通过 IPC 通知 IDE 登出
3316
+ * Get available models from product configuration
3317
+ *
3318
+ * GET {endpoint}/v3/config?repos[]={repo}
3319
+ *
3320
+ * This method fetches the product configuration from /v3/config API
3321
+ * and extracts the models array from the response.
3322
+ *
3323
+ * @param repo - Optional repository URL for context-specific config
3324
+ * @returns Array of ModelInfo with full model details
2460
3325
  */
2461
- async logout() {
2462
- this.log("Triggering logout via IPC");
3326
+ async getModels(repo) {
2463
3327
  try {
2464
- await this.sendBackendRequest(BACKEND_REQUEST_TYPES.LOGOUT);
3328
+ let url = `${this.options.endpoint}/v3/config`;
3329
+ if (repo) url += `?repos[]=${encodeURIComponent(repo)}`;
3330
+ const headers = {
3331
+ "Accept": "application/json, text/plain, */*",
3332
+ "X-Requested-With": "XMLHttpRequest",
3333
+ ...this.options.headers
3334
+ };
3335
+ if (this.options.authToken) headers["Authorization"] = `Bearer ${this.options.authToken}`;
3336
+ const account = accountService.getAccount();
3337
+ if (account?.uid) headers["X-User-Id"] = account.uid;
3338
+ if (account?.enterpriseId) {
3339
+ headers["X-Enterprise-Id"] = account.enterpriseId;
3340
+ headers["X-Tenant-Id"] = account.enterpriseId;
3341
+ }
3342
+ headers["X-Product"] = "SaaS";
3343
+ headers["X-User-Agent"] = "CLI/2.38.0 CodeBuddy/2.38.0";
3344
+ headers["X-Request-ID"] = this.generateRequestId();
3345
+ this.logger?.debug(`[CloudAgentProvider] GET ${url}`);
3346
+ const response = await this.fetchImpl(url, {
3347
+ method: "GET",
3348
+ headers,
3349
+ credentials: "include"
3350
+ });
3351
+ if (!response.ok) throw new Error(`Failed to get config: ${response.statusText}`);
3352
+ const apiResponse = await response.json();
3353
+ if (!apiResponse.data) {
3354
+ this.logger?.warn("[CloudAgentProvider] No data in config response, returning empty models");
3355
+ return [];
3356
+ }
3357
+ const models = apiResponse.data.models ?? [];
3358
+ this.logger?.info(`[CloudAgentProvider] Retrieved ${models.length} models from /v3/config`);
3359
+ return models.map((model) => ({
3360
+ id: model.id,
3361
+ name: model.name ?? model.id,
3362
+ description: model.description,
3363
+ credits: model.credits,
3364
+ configurable: model.configurable,
3365
+ configured: model.configured,
3366
+ isDefault: model.isDefault,
3367
+ supportsImages: model.supportsImages,
3368
+ supportsReasoning: model.supportsReasoning,
3369
+ onlyReasoning: model.onlyReasoning,
3370
+ disabledMultimodal: model.disabledMultimodal,
3371
+ disabled: model.disabled,
3372
+ disabledReason: model.disabledReason,
3373
+ disabledAction: model.disabledAction
3374
+ }));
2465
3375
  } catch (error) {
2466
- this.log("Logout request failed:", error);
3376
+ this.logger?.error(`[CloudAgentProvider] Failed to get models:`, error);
2467
3377
  throw error;
2468
3378
  }
2469
3379
  }
2470
3380
  /**
2471
- * 监听 channel 事件
2472
- * 用于监听账户变化等事件
3381
+ * Generate a unique request ID
2473
3382
  */
2474
- on(event, callback) {
2475
- this.log("Registering event listener:", event);
2476
- this.channel.on(event, callback);
2477
- return () => {
2478
- this.channel.off(event, callback);
2479
- };
3383
+ generateRequestId() {
3384
+ return "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".replace(/x/g, () => Math.floor(Math.random() * 16).toString(16));
3385
+ }
3386
+ static {
3387
+ this.IMAGE_MIME_TYPES = [
3388
+ "image/png",
3389
+ "image/jpeg",
3390
+ "image/jpg",
3391
+ "image/gif",
3392
+ "image/webp",
3393
+ "image/svg+xml",
3394
+ "image/bmp"
3395
+ ];
2480
3396
  }
2481
3397
  /**
2482
- * 调试日志
3398
+ * Pick files using browser's native file input
3399
+ *
3400
+ * @param params - File picker parameters
3401
+ * @returns Response with selected file paths (filenames in browser)
2483
3402
  */
2484
- log(...args) {
2485
- if (this.debug) console.log("[IPCBackendProvider]", ...args);
3403
+ async pickFile(params) {
3404
+ return new Promise((resolve) => {
3405
+ const input = document.createElement("input");
3406
+ input.type = "file";
3407
+ input.style.display = "none";
3408
+ if (params?.filters && params.filters.length > 0) {
3409
+ const acceptTypes = [];
3410
+ for (const filter of params.filters) for (const ext of filter.extensions) {
3411
+ const mimeType = this.extensionToMimeType(ext);
3412
+ acceptTypes.push(mimeType || `.${ext}`);
3413
+ }
3414
+ input.accept = acceptTypes.join(",");
3415
+ } else input.accept = CloudAgentProvider.IMAGE_MIME_TYPES.join(",");
3416
+ input.multiple = params?.canSelectMany ?? false;
3417
+ input.onchange = () => {
3418
+ const files = input.files;
3419
+ if (!files || files.length === 0) resolve({
3420
+ files: [],
3421
+ canceled: true
3422
+ });
3423
+ else {
3424
+ const fileArray = Array.from(files);
3425
+ this.logger?.info(`Picked ${fileArray.length} file(s)`);
3426
+ resolve({
3427
+ files: fileArray,
3428
+ canceled: false
3429
+ });
3430
+ }
3431
+ document.body.removeChild(input);
3432
+ };
3433
+ input.oncancel = () => {
3434
+ resolve({
3435
+ files: [],
3436
+ canceled: true
3437
+ });
3438
+ document.body.removeChild(input);
3439
+ };
3440
+ document.body.appendChild(input);
3441
+ input.click();
3442
+ });
2486
3443
  }
2487
- };
2488
- /**
2489
- * 创建 IPCBackendProvider 实例
2490
- */
2491
- function createIPCBackendProvider(config) {
2492
- return new IPCBackendProvider(config);
2493
- }
2494
-
2495
- //#endregion
2496
- //#region ../agent-provider/src/common/providers/cloud-agent-provider/cloud-provider.ts
2497
- /**
2498
- * CloudAgentProvider - Manages cloud-hosted agents via REST API
2499
- *
2500
- * API Endpoints:
2501
- * - POST {endpoint}/console/cloudagent/agentmgmt/agents - Create new agent
2502
- * - GET {endpoint}/console/cloudagent/agentmgmt/agents/{id} - Get agent data
2503
- * - GET {endpoint}/console/cloudagent/agentmgmt/agents - List all agents
2504
- * - POST {endpoint}/console/cloudagent/agentmgmt/agents/{id}/delete - Delete agent
2505
- * - GET {endpoint}/console/cloudagent/agentmgmt/agents/{id}/session - Get agent session (includes sandboxId)
2506
- * - GET {endpoint}/console/cloudagent/agentmgmt/models - Get available models
2507
- *
2508
- * The provider stores agent endpoint configurations in the cloud backend.
2509
- * When connect() is called, it creates a CloudAgentConnection to the agent's
2510
- * endpoint and returns an Agent instance.
2511
- *
2512
- * @example
2513
- * ```typescript
2514
- * const provider = new CloudAgentProvider({
2515
- * endpoint: 'https://staging-copilot.tencent.com',
2516
- * authToken: 'token'
2517
- * });
2518
- *
2519
- * // List all agents (uses default pagination and sorting)
2520
- * const allAgents = await provider.list();
2521
- *
2522
- * // List agents with custom pagination
2523
- * const page2 = await provider.list({
2524
- * page: 2,
2525
- * size: 50
2526
- * });
2527
- *
2528
- * // List agents with filtering
2529
- * const runningAgents = await provider.list({
2530
- * filters: [
2531
- * { field: 'status', value: 'running' }
2532
- * ]
2533
- * });
3444
+ /**
3445
+ * Convert file extension to MIME type
3446
+ */
3447
+ extensionToMimeType(ext) {
3448
+ const extLower = ext.toLowerCase().replace(/^\./, "");
3449
+ return {
3450
+ "png": "image/png",
3451
+ "jpg": "image/jpeg",
3452
+ "jpeg": "image/jpeg",
3453
+ "gif": "image/gif",
3454
+ "webp": "image/webp",
3455
+ "svg": "image/svg+xml",
3456
+ "bmp": "image/bmp",
3457
+ "ico": "image/x-icon"
3458
+ }[extLower] || null;
3459
+ }
3460
+ /**
3461
+ * Upload files to cloud storage via COS presigned URL
3462
+ *
3463
+ * @param params - files array (File objects in browser)
3464
+ * @returns Response with corresponding cloud URLs
3465
+ */
3466
+ async uploadFile(params) {
3467
+ this.logger?.info(`[CloudAgentProvider] uploadFile called for ${params.files.length} file(s)`);
3468
+ const files = params.files.filter((f) => typeof f !== "string");
3469
+ if (files.length === 0) return {
3470
+ success: false,
3471
+ error: "No valid File objects provided"
3472
+ };
3473
+ const result = await this.cosUploadService.uploadFiles(files);
3474
+ return {
3475
+ success: result.success,
3476
+ urls: result.urls,
3477
+ expireSeconds: result.expireSeconds,
3478
+ error: result.error
3479
+ };
3480
+ }
3481
+ /**
3482
+ * Register event listener
3483
+ * @param event - Event name
3484
+ * @param handler - Event handler function
3485
+ */
3486
+ on(event, handler) {
3487
+ if (!this.eventListeners.has(event)) this.eventListeners.set(event, /* @__PURE__ */ new Set());
3488
+ this.eventListeners.get(event).add(handler);
3489
+ return () => {
3490
+ this.off(event, handler);
3491
+ };
3492
+ }
3493
+ /**
3494
+ * Unregister event listener
3495
+ * @param event - Event name
3496
+ * @param handler - Event handler function
3497
+ */
3498
+ off(event, handler) {
3499
+ const listeners = this.eventListeners.get(event);
3500
+ if (listeners) listeners.delete(handler);
3501
+ }
3502
+ /**
3503
+ * Emit event to all registered listeners
3504
+ * @param event - Event name
3505
+ * @param args - Event arguments
3506
+ */
3507
+ emitEvent(event, ...args) {
3508
+ const listeners = this.eventListeners.get(event);
3509
+ if (listeners && listeners.size > 0) {
3510
+ this.logger?.debug(`Emitting event: ${event}`, args);
3511
+ for (const handler of listeners) try {
3512
+ handler(...args);
3513
+ } catch (error) {
3514
+ this.logger?.error(`Error in event handler for ${event}:`, error);
3515
+ }
3516
+ }
3517
+ }
3518
+ toAgentState(data) {
3519
+ const status = data.sessionStatus || data.status;
3520
+ return {
3521
+ id: data.id,
3522
+ name: data.name,
3523
+ description: data.summary,
3524
+ type: "cloud",
3525
+ status,
3526
+ createdAt: data.createdAt ? new Date(data.createdAt) : void 0,
3527
+ capabilities: this.options.clientCapabilities
3528
+ };
3529
+ }
3530
+ async request(method, path, body) {
3531
+ let url = `${this.options.endpoint}${path}`;
3532
+ const headers = {
3533
+ "Content-Type": "application/json",
3534
+ ...this.options.headers
3535
+ };
3536
+ if (this.options.authToken) headers["Authorization"] = `Bearer ${this.options.authToken}`;
3537
+ const account = accountService.getAccount();
3538
+ if (account?.uid) headers["X-User-Id"] = account.uid;
3539
+ if (account?.enterpriseId) {
3540
+ headers["X-Enterprise-Id"] = account.enterpriseId;
3541
+ headers["X-Tenant-Id"] = account.enterpriseId;
3542
+ }
3543
+ headers["X-Product"] = "SaaS";
3544
+ headers["X-User-Agent"] = "CLI/2.38.0 CodeBuddy/2.38.0";
3545
+ const init = {
3546
+ method,
3547
+ headers
3548
+ };
3549
+ if (method === "GET" && body !== void 0) {
3550
+ const params = new URLSearchParams();
3551
+ for (const [key, value] of Object.entries(body)) if (value !== void 0 && value !== null) {
3552
+ const stringValue = typeof value === "object" ? JSON.stringify(value) : String(value);
3553
+ params.append(key, stringValue);
3554
+ }
3555
+ const queryString = params.toString();
3556
+ if (queryString) url += `?${queryString}`;
3557
+ } else if (body !== void 0) init.body = JSON.stringify(body);
3558
+ this.logger?.debug(`${method} ${url}`);
3559
+ return this.fetchImpl(url, init);
3560
+ }
3561
+ };
3562
+
3563
+ //#endregion
3564
+ //#region ../agent-provider/src/common/providers/local-agent-provider/local-connection.ts
3565
+ /**
3566
+ * Local Agent Connection
3567
+ * Wraps AcpJsonRpcClient to implement AgentConnection interface
2534
3568
  *
2535
- * // List agents with custom sorting
2536
- * const sortedAgents = await provider.list({
2537
- * sort: {
2538
- * orderBy: 'createdAt',
2539
- * order: 'desc'
2540
- * }
2541
- * });
3569
+ * Uses IWidgetChannel for IPC communication with ExtensionHost
3570
+ * Migrated from ipc-agent-provider for unified local agent access
3571
+ */
3572
+
3573
+ //#endregion
3574
+ //#region ../agent-provider/src/common/client/session.ts
3575
+ /**
3576
+ * ActiveSessionImpl - Implements the ActiveSession interface
2542
3577
  *
2543
- * // List agents created in last 14 days with multiple filters
2544
- * const recentAgents = await provider.list({
2545
- * dayRange: 14,
2546
- * filters: [
2547
- * { field: 'status', value: 'running,stopped' }
2548
- * ],
2549
- * page: 1,
2550
- * size: 20
2551
- * });
3578
+ * This class wraps an AgentConnection and provides the session-centric API.
3579
+ * It is created by SessionManager when creating or loading sessions.
2552
3580
  *
2553
- * // Get agent state
2554
- * const state = await provider.get('agent-id');
3581
+ * @example
3582
+ * ```typescript
3583
+ * // Created by client.sessions.new() or client.sessions.load()
3584
+ * const session = await client.sessions.new({ cwd: '/workspace' });
2555
3585
  *
2556
- * // Connect to agent
2557
- * const agent = await provider.connect('agent-id');
3586
+ * // Access agent state
3587
+ * console.log(session.agentState.status);
2558
3588
  *
2559
- * // Use agent
2560
- * const session = await agent.sessions.create({ cwd: '/workspace' });
3589
+ * // Send prompt
3590
+ * const response = await session.prompts.send({ content: 'Hello!' });
2561
3591
  *
2562
- * // Get available models
2563
- * const models = await provider.getModels('my-repo');
3592
+ * // Cleanup
3593
+ * session.disconnect();
2564
3594
  * ```
2565
3595
  */
2566
- var CloudAgentProvider = class CloudAgentProvider {
2567
- constructor(options) {
2568
- this.filesystemCache = /* @__PURE__ */ new Map();
2569
- this.connectionCache = /* @__PURE__ */ new Map();
2570
- this.options = options;
3596
+ var ActiveSessionImpl = class {
3597
+ /**
3598
+ * Create an ActiveSessionImpl instance
3599
+ *
3600
+ * @param sessionId - Session ID
3601
+ * @param agentId - Agent ID
3602
+ * @param connection - Already connected AgentConnection
3603
+ * @param options - Additional options
3604
+ */
3605
+ constructor(sessionId, agentId, connection, options = {}) {
3606
+ this._availableCommands = [];
3607
+ this.listeners = /* @__PURE__ */ new Map();
3608
+ this.onceListeners = /* @__PURE__ */ new Map();
3609
+ this._id = sessionId;
3610
+ this._agentId = agentId;
3611
+ this.connection = connection;
2571
3612
  this.logger = options.logger;
2572
- this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
2573
- this.cosUploadService = new CosUploadService({
2574
- request: (method, path, body) => this.request(method, path, body),
2575
- logger: this.logger,
2576
- fetch: this.fetchImpl
2577
- });
3613
+ this._getFilesystem = options.getFilesystem;
3614
+ this._connectionInfo = options.connectionInfo;
3615
+ this.setupConnectionEvents(connection);
3616
+ this.agent = this.createAgentOperations();
3617
+ this.prompts = this.createPromptsResource();
3618
+ this.artifacts = this.createArtifactsResource();
3619
+ this.files = this.createFilesResource();
2578
3620
  }
2579
3621
  /**
2580
- * Get the filesystem provider (returns self)
3622
+ * Session ID
2581
3623
  */
2582
- get filesystem() {
2583
- return this;
3624
+ get id() {
3625
+ return this._id;
2584
3626
  }
2585
3627
  /**
2586
- * Get filesystem resource for an agent
2587
- *
2588
- * Creates or returns cached E2BFilesystem instance for the agent's sandbox.
2589
- *
2590
- * @param agentId - Agent ID to get filesystem for
2591
- * @returns FilesResource instance for the agent's sandbox
3628
+ * Agent ID
2592
3629
  */
2593
- async getFilesystem(agentId) {
2594
- const cached = this.filesystemCache.get(agentId);
2595
- if (cached) return cached;
2596
- const info = await this.getSandboxInfo(agentId);
2597
- const filesystem = await E2BFilesystem.connect(info);
2598
- this.filesystemCache.set(agentId, filesystem);
2599
- this.logger?.debug(`Created filesystem for agent: ${agentId}`);
2600
- return filesystem;
3630
+ get agentId() {
3631
+ return this._agentId;
2601
3632
  }
2602
3633
  /**
2603
- * Get sandbox information from backend
2604
- *
2605
- * Uses GET {endpoint}/console/cloudagent/agentmgmt/agents/{agentId}/session
2606
- * to retrieve sandbox information. Extracts sandboxId from the session response
2607
- * and constructs the apiUrl for E2B proxy.
2608
- *
2609
- * @param agentId - Agent ID
2610
- * @returns E2B Sandbox connection information with sandboxId and apiUrl
3634
+ * Agent state (live connection state)
3635
+ * Returns LocalAgentState or CloudAgentState based on transport type
2611
3636
  */
2612
- async getSandboxInfo(agentId) {
2613
- const response = await this.request("GET", `/console/cloudagent/agentmgmt/agents/${agentId}/session`);
2614
- if (!response.ok) throw new Error(`Failed to get sandbox info: ${response.statusText}`);
2615
- const apiResponse = await response.json();
2616
- if (!apiResponse.data) throw new Error("No data in API response");
2617
- const apiUrl = new URL(`/console/cloudagent/e2bproxy/agents/${agentId}`, this.options.endpoint).toString();
2618
- const currentEnterpriseId = localStorage.getItem("currentEnterpriseId");
3637
+ get agentState() {
2619
3638
  return {
2620
- sandboxId: apiResponse.data.sandboxId,
2621
- apiUrl,
2622
- apiKey: "backend-not-used",
2623
- accessToken: apiResponse.data.token,
2624
- headers: { ...currentEnterpriseId && { "X-Enterprise-Id": currentEnterpriseId } }
3639
+ id: this._agentId,
3640
+ status: this.connection.state,
3641
+ capabilities: this.connection.capabilities,
3642
+ type: this.connection.transport,
3643
+ cwd: this.connection.cwd || ""
2625
3644
  };
2626
3645
  }
2627
3646
  /**
2628
- * Get agent state by ID
3647
+ * Get agent capabilities (available after connection)
2629
3648
  */
2630
- async get(agentId) {
2631
- try {
2632
- const response = await this.request("GET", `/console/cloudagent/agentmgmt/agents/${agentId}`);
2633
- if (response.status === 404) return;
2634
- if (!response.ok) throw new Error(`Failed to get agent: ${response.statusText}`);
2635
- const apiResponse = await response.json();
2636
- if (!apiResponse.data) throw new Error("No data in API response");
2637
- return this.toAgentState(apiResponse.data);
2638
- } catch (error) {
2639
- this.logger?.error(`Failed to get agent ${agentId}:`, error);
2640
- throw error;
2641
- }
3649
+ get capabilities() {
3650
+ return this.connection.capabilities;
2642
3651
  }
2643
3652
  /**
2644
- * List all agent states with pagination information
2645
- *
2646
- * @param options - Optional query parameters for filtering, sorting, and pagination
2647
- * @returns Object containing agents array and pagination info
3653
+ * Available session modes
2648
3654
  */
2649
- async list(options) {
2650
- try {
2651
- console.log("[CloudAgentProvider] list called with options:", JSON.stringify(options, null, 2));
2652
- const params = {
2653
- page: 1,
2654
- size: 30,
2655
- sort: {
2656
- order: "desc",
2657
- orderBy: "status"
2658
- },
2659
- ...options && {
2660
- ...options.dayRange !== void 0 && { dayRange: options.dayRange },
2661
- ...options.page !== void 0 && { page: options.page },
2662
- ...options.size !== void 0 && { size: options.size },
2663
- ...options.sort !== void 0 && { sort: options.sort },
2664
- ...options.filters !== void 0 && { filters: options.filters },
2665
- ...options.title !== void 0 && { title: options.title }
2666
- }
2667
- };
2668
- console.log("[CloudAgentProvider] API request params:", JSON.stringify(params, null, 2));
2669
- const response = await this.request("GET", "/console/cloudagent/agentmgmt/agents", params);
2670
- if (!response.ok) throw new Error(`Failed to list agents: ${response.statusText}`);
2671
- const apiResponse = await response.json();
2672
- if (!apiResponse.data) throw new Error("No data in API response");
2673
- const agents = apiResponse.data.agents.map((a) => this.toAgentState(a));
2674
- const pagination = apiResponse.data.pagination;
2675
- console.log("[CloudAgentProvider] API response:", {
2676
- agentsCount: agents.length,
2677
- pagination
2678
- });
2679
- return {
2680
- agents,
2681
- pagination
2682
- };
2683
- } catch (error) {
2684
- this.logger?.error("Failed to list agents:", error);
2685
- throw error;
2686
- }
3655
+ get availableModes() {
3656
+ return this._availableModes;
2687
3657
  }
2688
3658
  /**
2689
- * Create a new agent
2690
- * POST {endpoint}/console/cloudagent/agentmgmt/agents
3659
+ * Current session mode
2691
3660
  */
2692
- async create() {
2693
- try {
2694
- const response = await this.request("POST", "/console/cloudagent/agentmgmt/agents", {
2695
- prompt: "",
2696
- model: "deepseek-r1"
2697
- });
2698
- if (!response.ok) throw new Error(`Failed to create agent: ${response.statusText}`);
2699
- const apiResponse = await response.json();
2700
- if (!apiResponse.data) throw new Error("No data in API response");
2701
- this.logger?.info(`Created agent: ${apiResponse.data.id}`);
2702
- return apiResponse.data.id;
2703
- } catch (error) {
2704
- this.logger?.error("Failed to create agent:", error);
2705
- throw error;
2706
- }
3661
+ get currentMode() {
3662
+ return this._currentMode;
2707
3663
  }
2708
3664
  /**
2709
- * Connect to an agent and return the connection
2710
- *
2711
- * This method:
2712
- * 1. Fetches the agent configuration from the backend
2713
- * 2. Checks if an existing connection can be reused (based on endpoint link)
2714
- * 3. Creates a CloudAgentConnection to the agent's endpoint if not cached
2715
- * 4. Saves session connection info to the connection
2716
- * 5. Connects and initializes the connection
2717
- * 6. Returns the connected CloudAgentConnection
3665
+ * Available slash commands
2718
3666
  *
2719
- * Connection caching:
2720
- * - Connections are cached by endpoint link to enable reuse
2721
- * - CloudAgentConnection is responsible for handling connection health checks
2722
- * and token expiration internally
3667
+ * When Agent sends available_commands_update, this list is automatically updated.
3668
+ * Commands can be accessed directly without waiting for events.
2723
3669
  */
2724
- async connect(agentId) {
2725
- const response = await this.request("GET", `/console/cloudagent/agentmgmt/agents/${agentId}/session`);
2726
- if (response.status === 404) throw new Error(`Agent not found: ${agentId}`);
2727
- if (!response.ok) throw new Error(`Failed to get agent for connection: ${response.statusText}`);
2728
- const apiResponse = await response.json();
2729
- if (!apiResponse.data) throw new Error("No data in API response");
2730
- const sessionData = apiResponse.data;
2731
- const endpoint = sessionData.link.replace(/^http:\/\//, "https://");
2732
- const existingConnection = this.connectionCache.get(endpoint);
2733
- if (existingConnection?.isInitialized) {
2734
- this.logger?.info(`Reusing existing connection for agent: ${agentId}`);
2735
- return existingConnection;
2736
- }
2737
- const connection = new CloudAgentConnection(agentId, {
2738
- endpoint,
2739
- authToken: sessionData.token,
2740
- logger: this.logger,
2741
- clientCapabilities: this.options.clientCapabilities
2742
- });
2743
- connection.setSessionConnectionInfo({
2744
- sessionId: sessionData.sessionId,
2745
- agentId: sessionData.id,
2746
- link: sessionData.link,
2747
- token: sessionData.token,
2748
- sandboxId: sessionData.sandboxId,
2749
- expireAt: sessionData.expireAt
2750
- });
2751
- try {
2752
- await connection.connect();
2753
- } catch (error) {
2754
- this.logger?.error(`Failed to connect to agent ${agentId}:`, error);
2755
- throw error;
2756
- }
2757
- this.connectionCache.set(endpoint, connection);
2758
- connection.once("disconnected", () => {
2759
- this.connectionCache.delete(endpoint);
2760
- this.logger?.debug(`Connection removed from cache: ${endpoint}`);
2761
- });
2762
- this.logger?.info(`Connected to agent: ${agentId}`);
2763
- return connection;
3670
+ get availableCommands() {
3671
+ return this._availableCommands;
2764
3672
  }
2765
3673
  /**
2766
- * Delete an agent by ID
2767
- * POST {endpoint}/console/cloudagent/agentmgmt/agents/{agentId}/delete
3674
+ * Check if the session is active
2768
3675
  */
2769
- async delete(agentId) {
2770
- try {
2771
- const requestBody = { id: agentId };
2772
- const response = await this.request("POST", `/console/cloudagent/agentmgmt/agents/${agentId}/delete`, requestBody);
2773
- if (response.status === 404) return false;
2774
- if (!response.ok) throw new Error(`Failed to delete agent: ${response.statusText}`);
2775
- return true;
2776
- } catch (error) {
2777
- this.logger?.error(`Failed to delete agent ${agentId}:`, error);
2778
- throw error;
2779
- }
3676
+ get isActive() {
3677
+ return this.connection.isInitialized;
2780
3678
  }
2781
3679
  /**
2782
- * Archive an agent by ID
2783
- * POST {endpoint}/console/cloudagent/agentmgmt/agents/{agentId}/archive
2784
- *
2785
- * @param agentId - Agent ID to archive
2786
- * @returns ArchiveAgentResponse containing the archived agent ID
2787
- *
2788
- * @example
2789
- * ```typescript
2790
- * const result = await provider.archive('agent-123');
2791
- * console.log('Archived agent:', result.id);
2792
- * ```
3680
+ * Session connection information (only available for cloud sessions)
3681
+ * 会话连接信息,包括sandboxId、link、token等
2793
3682
  */
2794
- async archive(agentId) {
2795
- try {
2796
- const response = await this.request("POST", `/console/cloudagent/agentmgmt/agents/${agentId}/archive`);
2797
- if (!response.ok) throw new Error(`Failed to archive agent: ${response.statusText}`);
2798
- const apiResponse = await response.json();
2799
- if (!apiResponse.data) {
2800
- this.logger?.info(`Archived agent: ${agentId}`);
2801
- return { id: agentId };
2802
- }
2803
- this.logger?.info(`Archived agent: ${apiResponse.data.id}`);
2804
- return apiResponse.data;
2805
- } catch (error) {
2806
- this.logger?.error(`Failed to archive agent ${agentId}:`, error);
2807
- throw error;
2808
- }
3683
+ get connectionInfo() {
3684
+ return this._connectionInfo;
2809
3685
  }
2810
3686
  /**
2811
- * Rename an agent by ID
2812
- * PATCH {endpoint}/v2/cloudagent/agentmgmt/agents/{agentId}
2813
- *
2814
- * @param agentId - Agent ID to rename
2815
- * @param title - New title for the agent
2816
- * @returns RenameAgentResponse containing the renamed agent ID
2817
- *
2818
- * @example
2819
- * ```typescript
2820
- * const result = await provider.rename('agent-123', 'New Title');
2821
- * console.log('Renamed agent:', result.id);
2822
- * ```
3687
+ * Set session modes (called after create/load)
2823
3688
  */
2824
- async rename(agentId, title) {
2825
- try {
2826
- const body = { title };
2827
- const response = await this.request("POST", `/console/cloudagent/agentmgmt/agents/${agentId}`, body);
2828
- if (!response.ok) throw new Error(`Failed to rename agent: ${response.statusText}`);
2829
- const apiResponse = await response.json();
2830
- if (!apiResponse.data) {
2831
- this.logger?.info(`Renamed agent: ${agentId} to "${title}"`);
2832
- return { id: agentId };
3689
+ setModes(availableModes, currentMode) {
3690
+ this._availableModes = availableModes;
3691
+ this._currentMode = currentMode;
3692
+ }
3693
+ createAgentOperations() {
3694
+ const self = this;
3695
+ return {
3696
+ get id() {
3697
+ return self._agentId;
3698
+ },
3699
+ get state() {
3700
+ return self.agentState;
3701
+ },
3702
+ get isConnected() {
3703
+ return self.connection.isInitialized;
3704
+ },
3705
+ get capabilities() {
3706
+ return self.connection.capabilities;
2833
3707
  }
2834
- this.logger?.info(`Renamed agent: ${apiResponse.data.id} to "${title}"`);
2835
- return apiResponse.data;
2836
- } catch (error) {
2837
- this.logger?.error(`Failed to rename agent ${agentId}:`, error);
2838
- throw error;
2839
- }
3708
+ };
3709
+ }
3710
+ createPromptsResource() {
3711
+ return {
3712
+ send: async (params) => {
3713
+ const response = await this.getConnectionOrThrow().prompt(this._id, params);
3714
+ return this.mapPromptResponse(response);
3715
+ },
3716
+ stream: (params) => {
3717
+ return this.getConnectionOrThrow().promptStream(this._id, params);
3718
+ },
3719
+ cancel: async () => {
3720
+ await this.getConnectionOrThrow().cancel(this._id);
3721
+ }
3722
+ };
3723
+ }
3724
+ createArtifactsResource() {
3725
+ const notSupported = () => {
3726
+ throw new Error("Artifact management is no longer supported through this API");
3727
+ };
3728
+ return {
3729
+ list: async (_params) => {
3730
+ notSupported();
3731
+ return [];
3732
+ },
3733
+ retrieve: async (_artifactId) => {
3734
+ notSupported();
3735
+ },
3736
+ content: async (_artifactId) => {
3737
+ notSupported();
3738
+ return "";
3739
+ }
3740
+ };
2840
3741
  }
2841
3742
  /**
2842
- * Get available models for a repository
2843
- *
2844
- * GET {endpoint}/console/cloudagent/agentmgmt/models?repo={repo}
3743
+ * Create files resource with lazy-loaded filesystem
2845
3744
  *
2846
- * @param repo - Repository identifier
2847
- * @returns Array of model names (backend only provides names, not full ModelInfo)
3745
+ * The filesystem is lazily loaded on first use to avoid unnecessary
3746
+ * connections to the sandbox. The actual filesystem instance is obtained
3747
+ * via the getter function provided by SessionManager.
2848
3748
  */
2849
- async getModels(repo) {
2850
- try {
2851
- const response = await this.request("GET", `/console/cloudagent/agentmgmt/models?repo=${encodeURIComponent(repo)}`);
2852
- if (!response.ok) throw new Error(`Failed to get models: ${response.statusText}`);
2853
- const apiResponse = await response.json();
2854
- if (!apiResponse.data) throw new Error("No data in API response");
2855
- const models = apiResponse.data.models;
2856
- this.logger?.info(`Retrieved ${models.length} models for repo: ${repo}`);
2857
- return models.map((m) => ({
2858
- id: m,
2859
- name: "",
2860
- description: ""
2861
- }));
2862
- } catch (error) {
2863
- this.logger?.error(`Failed to get models for repo ${repo}:`, error);
2864
- throw error;
2865
- }
2866
- }
2867
- static {
2868
- this.IMAGE_MIME_TYPES = [
2869
- "image/png",
2870
- "image/jpeg",
2871
- "image/jpg",
2872
- "image/gif",
2873
- "image/webp",
2874
- "image/svg+xml",
2875
- "image/bmp"
2876
- ];
2877
- }
2878
- /**
2879
- * Pick files using browser's native file input
2880
- *
2881
- * @param params - File picker parameters
2882
- * @returns Response with selected file paths (filenames in browser)
2883
- */
2884
- async pickFile(params) {
2885
- return new Promise((resolve) => {
2886
- const input = document.createElement("input");
2887
- input.type = "file";
2888
- input.style.display = "none";
2889
- if (params?.filters && params.filters.length > 0) {
2890
- const acceptTypes = [];
2891
- for (const filter of params.filters) for (const ext of filter.extensions) {
2892
- const mimeType = this.extensionToMimeType(ext);
2893
- acceptTypes.push(mimeType || `.${ext}`);
2894
- }
2895
- input.accept = acceptTypes.join(",");
2896
- } else input.accept = CloudAgentProvider.IMAGE_MIME_TYPES.join(",");
2897
- input.multiple = params?.canSelectMany ?? false;
2898
- input.onchange = () => {
2899
- const files = input.files;
2900
- if (!files || files.length === 0) resolve({
2901
- files: [],
2902
- canceled: true
2903
- });
2904
- else {
2905
- const fileArray = Array.from(files);
2906
- this.logger?.info(`Picked ${fileArray.length} file(s)`);
2907
- resolve({
2908
- files: fileArray,
2909
- canceled: false
2910
- });
2911
- }
2912
- document.body.removeChild(input);
2913
- };
2914
- input.oncancel = () => {
2915
- resolve({
2916
- files: [],
2917
- canceled: true
2918
- });
2919
- document.body.removeChild(input);
2920
- };
2921
- document.body.appendChild(input);
2922
- input.click();
2923
- });
2924
- }
2925
- /**
2926
- * Convert file extension to MIME type
2927
- */
2928
- extensionToMimeType(ext) {
2929
- const extLower = ext.toLowerCase().replace(/^\./, "");
2930
- return {
2931
- "png": "image/png",
2932
- "jpg": "image/jpeg",
2933
- "jpeg": "image/jpeg",
2934
- "gif": "image/gif",
2935
- "webp": "image/webp",
2936
- "svg": "image/svg+xml",
2937
- "bmp": "image/bmp",
2938
- "ico": "image/x-icon"
2939
- }[extLower] || null;
2940
- }
2941
- /**
2942
- * Upload files to cloud storage via COS presigned URL
2943
- *
2944
- * @param params - files array (File objects in browser)
2945
- * @returns Response with corresponding cloud URLs
2946
- */
2947
- async uploadFile(params) {
2948
- this.logger?.info(`[CloudAgentProvider] uploadFile called for ${params.files.length} file(s)`);
2949
- const files = params.files.filter((f) => typeof f !== "string");
2950
- if (files.length === 0) return {
2951
- success: false,
2952
- error: "No valid File objects provided"
2953
- };
2954
- const result = await this.cosUploadService.uploadFiles(files);
2955
- return {
2956
- success: result.success,
2957
- urls: result.urls,
2958
- error: result.error
3749
+ createFilesResource() {
3750
+ const self = this;
3751
+ let filesPromise = null;
3752
+ /**
3753
+ * Get or create the filesystem instance
3754
+ */
3755
+ const getFs = async () => {
3756
+ if (!self._getFilesystem) throw new Error("Filesystem not available: provider does not support filesystem operations");
3757
+ if (!filesPromise) filesPromise = self._getFilesystem();
3758
+ return filesPromise;
2959
3759
  };
2960
- }
2961
- toAgentState(data) {
2962
- const status = data.sessionStatus || data.status;
2963
3760
  return {
2964
- id: data.id,
2965
- name: data.name,
2966
- description: data.summary,
2967
- type: "cloud",
2968
- status,
2969
- createdAt: data.createdAt ? new Date(data.createdAt) : void 0,
2970
- capabilities: this.options.clientCapabilities
2971
- };
2972
- }
2973
- async request(method, path, body) {
2974
- let url = `${this.options.endpoint}${path}`;
2975
- const headers = {
2976
- "Content-Type": "application/json",
2977
- ...this.options.headers
2978
- };
2979
- if (this.options.authToken) headers["Authorization"] = `Bearer ${this.options.authToken}`;
2980
- const enterpriseId = typeof localStorage !== "undefined" ? localStorage.getItem(SELECTED_ACCOUNT_KEY) : null;
2981
- if (enterpriseId) headers["X-Enterprise-Id"] = enterpriseId;
2982
- const init = {
2983
- method,
2984
- headers
3761
+ read: (async (path, opts) => (await getFs()).read(path, opts)),
3762
+ write: (async (pathOrFiles, dataOrOpts, opts) => {
3763
+ const fs = await getFs();
3764
+ if (Array.isArray(pathOrFiles)) return fs.write(pathOrFiles, dataOrOpts);
3765
+ return fs.write(pathOrFiles, dataOrOpts, opts);
3766
+ }),
3767
+ list: async (path, opts) => (await getFs()).list(path, opts),
3768
+ exists: async (path, opts) => (await getFs()).exists(path, opts),
3769
+ makeDir: async (path, opts) => (await getFs()).makeDir(path, opts),
3770
+ remove: async (path, opts) => (await getFs()).remove(path, opts),
3771
+ rename: async (oldPath, newPath, opts) => (await getFs()).rename(oldPath, newPath, opts),
3772
+ getInfo: async (path, opts) => (await getFs()).getInfo(path, opts),
3773
+ watchDir: async (path, onEvent, opts) => (await getFs()).watchDir(path, onEvent, opts)
2985
3774
  };
2986
- if (method === "GET" && body !== void 0) {
2987
- const params = new URLSearchParams();
2988
- for (const [key, value] of Object.entries(body)) if (value !== void 0 && value !== null) {
2989
- const stringValue = typeof value === "object" ? JSON.stringify(value) : String(value);
2990
- params.append(key, stringValue);
2991
- }
2992
- const queryString = params.toString();
2993
- if (queryString) url += `?${queryString}`;
2994
- } else if (body !== void 0) init.body = JSON.stringify(body);
2995
- this.logger?.debug(`${method} ${url}`);
2996
- return this.fetchImpl(url, init);
2997
- }
2998
- };
2999
-
3000
- //#endregion
3001
- //#region ../agent-provider/src/common/providers/local-agent-provider/local-connection.ts
3002
- /**
3003
- * Local Agent Connection
3004
- * Wraps AcpJsonRpcClient to implement AgentConnection interface
3005
- *
3006
- * Uses IWidgetChannel for IPC communication with ExtensionHost
3007
- * Migrated from ipc-agent-provider for unified local agent access
3008
- */
3009
-
3010
- //#endregion
3011
- //#region ../agent-provider/src/common/client/session.ts
3012
- /**
3013
- * ActiveSessionImpl - Implements the ActiveSession interface
3014
- *
3015
- * This class wraps an AgentConnection and provides the session-centric API.
3016
- * It is created by SessionManager when creating or loading sessions.
3017
- *
3018
- * @example
3019
- * ```typescript
3020
- * // Created by client.sessions.new() or client.sessions.load()
3021
- * const session = await client.sessions.new({ cwd: '/workspace' });
3022
- *
3023
- * // Access agent state
3024
- * console.log(session.agentState.status);
3025
- *
3026
- * // Send prompt
3027
- * const response = await session.prompts.send({ content: 'Hello!' });
3028
- *
3029
- * // Cleanup
3030
- * session.disconnect();
3031
- * ```
3032
- */
3033
- var ActiveSessionImpl = class {
3034
- /**
3035
- * Create an ActiveSessionImpl instance
3036
- *
3037
- * @param sessionId - Session ID
3038
- * @param agentId - Agent ID
3039
- * @param connection - Already connected AgentConnection
3040
- * @param options - Additional options
3041
- */
3042
- constructor(sessionId, agentId, connection, options = {}) {
3043
- this._availableCommands = [];
3044
- this.listeners = /* @__PURE__ */ new Map();
3045
- this.onceListeners = /* @__PURE__ */ new Map();
3046
- this._id = sessionId;
3047
- this._agentId = agentId;
3048
- this.connection = connection;
3049
- this.logger = options.logger;
3050
- this._getFilesystem = options.getFilesystem;
3051
- this._connectionInfo = options.connectionInfo;
3052
- this.setupConnectionEvents(connection);
3053
- this.agent = this.createAgentOperations();
3054
- this.prompts = this.createPromptsResource();
3055
- this.artifacts = this.createArtifactsResource();
3056
- this.files = this.createFilesResource();
3057
3775
  }
3058
3776
  /**
3059
- * Session ID
3060
- */
3061
- get id() {
3062
- return this._id;
3063
- }
3064
- /**
3065
- * Agent ID
3777
+ * Resolve a permission request
3066
3778
  */
3067
- get agentId() {
3068
- return this._agentId;
3779
+ resolvePermission(requestId, optionId) {
3780
+ return this.connection.resolvePermission(requestId, optionId);
3069
3781
  }
3070
3782
  /**
3071
- * Agent state (live connection state)
3072
- * Returns LocalAgentState or CloudAgentState based on transport type
3783
+ * Reject a permission request
3073
3784
  */
3074
- get agentState() {
3075
- return {
3076
- id: this._agentId,
3077
- status: this.connection.state,
3078
- capabilities: this.connection.capabilities,
3079
- type: "local",
3080
- cwd: ""
3081
- };
3785
+ rejectPermission(requestId, reason) {
3786
+ return this.connection.rejectPermission(requestId, reason);
3082
3787
  }
3083
3788
  /**
3084
- * Get agent capabilities (available after connection)
3789
+ * Answer a question request with user's selections
3085
3790
  */
3086
- get capabilities() {
3087
- return this.connection.capabilities;
3791
+ answerQuestion(toolCallId, answers) {
3792
+ return this.connection.answerQuestion(toolCallId, answers);
3088
3793
  }
3089
3794
  /**
3090
- * Available session modes
3795
+ * Cancel a question request
3091
3796
  */
3092
- get availableModes() {
3093
- return this._availableModes;
3797
+ cancelQuestion(toolCallId, reason) {
3798
+ return this.connection.cancelQuestion(toolCallId, reason);
3094
3799
  }
3095
3800
  /**
3096
- * Current session mode
3801
+ * Callback for tool operations (skip or cancel)
3802
+ * @param toolCallId Tool call ID
3803
+ * @param toolName Tool name
3804
+ * @param action Action to perform ('skip' or 'cancel')
3097
3805
  */
3098
- get currentMode() {
3099
- return this._currentMode;
3806
+ async toolCallback(toolCallId, toolName, action) {
3807
+ return await this.getConnectionOrThrow().toolCallback(this._id, toolCallId, toolName, action);
3100
3808
  }
3101
3809
  /**
3102
- * Available slash commands
3103
- *
3104
- * When Agent sends available_commands_update, this list is automatically updated.
3105
- * Commands can be accessed directly without waiting for events.
3106
- */
3107
- get availableCommands() {
3108
- return this._availableCommands;
3109
- }
3110
- /**
3111
- * Check if the session is active
3112
- */
3113
- get isActive() {
3114
- return this.connection.isInitialized;
3115
- }
3116
- /**
3117
- * Session connection information (only available for cloud sessions)
3118
- * 会话连接信息,包括sandboxId、link、token等
3119
- */
3120
- get connectionInfo() {
3121
- return this._connectionInfo;
3122
- }
3123
- /**
3124
- * Set session modes (called after create/load)
3125
- */
3126
- setModes(availableModes, currentMode) {
3127
- this._availableModes = availableModes;
3128
- this._currentMode = currentMode;
3129
- }
3130
- createAgentOperations() {
3131
- const self = this;
3132
- return {
3133
- get id() {
3134
- return self._agentId;
3135
- },
3136
- get state() {
3137
- return self.agentState;
3138
- },
3139
- get isConnected() {
3140
- return self.connection.isInitialized;
3141
- },
3142
- get capabilities() {
3143
- return self.connection.capabilities;
3144
- }
3145
- };
3146
- }
3147
- createPromptsResource() {
3148
- return {
3149
- send: async (params) => {
3150
- const response = await this.getConnectionOrThrow().prompt(this._id, params);
3151
- return this.mapPromptResponse(response);
3152
- },
3153
- stream: (params) => {
3154
- return this.getConnectionOrThrow().promptStream(this._id, params);
3155
- },
3156
- cancel: async () => {
3157
- await this.getConnectionOrThrow().cancel(this._id);
3158
- }
3159
- };
3160
- }
3161
- createArtifactsResource() {
3162
- const notSupported = () => {
3163
- throw new Error("Artifact management is no longer supported through this API");
3164
- };
3165
- return {
3166
- list: async (_params) => {
3167
- notSupported();
3168
- return [];
3169
- },
3170
- retrieve: async (_artifactId) => {
3171
- notSupported();
3172
- },
3173
- content: async (_artifactId) => {
3174
- notSupported();
3175
- return "";
3176
- }
3177
- };
3178
- }
3179
- /**
3180
- * Create files resource with lazy-loaded filesystem
3181
- *
3182
- * The filesystem is lazily loaded on first use to avoid unnecessary
3183
- * connections to the sandbox. The actual filesystem instance is obtained
3184
- * via the getter function provided by SessionManager.
3185
- */
3186
- createFilesResource() {
3187
- const self = this;
3188
- let filesPromise = null;
3189
- /**
3190
- * Get or create the filesystem instance
3191
- */
3192
- const getFs = async () => {
3193
- if (!self._getFilesystem) throw new Error("Filesystem not available: provider does not support filesystem operations");
3194
- if (!filesPromise) filesPromise = self._getFilesystem();
3195
- return filesPromise;
3196
- };
3197
- return {
3198
- read: (async (path, opts) => (await getFs()).read(path, opts)),
3199
- write: (async (pathOrFiles, dataOrOpts, opts) => {
3200
- const fs = await getFs();
3201
- if (Array.isArray(pathOrFiles)) return fs.write(pathOrFiles, dataOrOpts);
3202
- return fs.write(pathOrFiles, dataOrOpts, opts);
3203
- }),
3204
- list: async (path, opts) => (await getFs()).list(path, opts),
3205
- exists: async (path, opts) => (await getFs()).exists(path, opts),
3206
- makeDir: async (path, opts) => (await getFs()).makeDir(path, opts),
3207
- remove: async (path, opts) => (await getFs()).remove(path, opts),
3208
- rename: async (oldPath, newPath, opts) => (await getFs()).rename(oldPath, newPath, opts),
3209
- getInfo: async (path, opts) => (await getFs()).getInfo(path, opts),
3210
- watchDir: async (path, onEvent, opts) => (await getFs()).watchDir(path, onEvent, opts)
3211
- };
3212
- }
3213
- /**
3214
- * Resolve a permission request
3215
- */
3216
- resolvePermission(requestId, optionId) {
3217
- return this.connection.resolvePermission(requestId, optionId);
3218
- }
3219
- /**
3220
- * Reject a permission request
3221
- */
3222
- rejectPermission(requestId, reason) {
3223
- return this.connection.rejectPermission(requestId, reason);
3224
- }
3225
- /**
3226
- * Answer a question request with user's selections
3227
- */
3228
- answerQuestion(toolCallId, answers) {
3229
- return this.connection.answerQuestion(toolCallId, answers);
3230
- }
3231
- /**
3232
- * Cancel a question request
3233
- */
3234
- cancelQuestion(toolCallId, reason) {
3235
- return this.connection.cancelQuestion(toolCallId, reason);
3236
- }
3237
- /**
3238
- * Callback for tool operations (skip or cancel)
3239
- * @param toolCallId Tool call ID
3240
- * @param toolName Tool name
3241
- * @param action Action to perform ('skip' or 'cancel')
3242
- */
3243
- async toolCallback(toolCallId, toolName, action) {
3244
- return await this.getConnectionOrThrow().toolCallback(this._id, toolCallId, toolName, action);
3245
- }
3246
- /**
3247
- * Set the current session mode
3810
+ * Set the current session mode
3248
3811
  *
3249
3812
  * @param modeId - The mode ID to switch to (must be in availableModes)
3250
3813
  * @throws Error if modeId is not in availableModes or connection fails
@@ -3864,6 +4427,396 @@ function isCloudAgentState(state) {
3864
4427
  return state.type === "cloud";
3865
4428
  }
3866
4429
 
4430
+ //#endregion
4431
+ //#region ../agent-provider/src/backend/types.ts
4432
+ /**
4433
+ * 套餐代码
4434
+ */
4435
+ /**
4436
+ * TCACA_code_001_PqouKr6QWV CodeBuddy海外版免费包
4437
+ * TCACA_code_002_AkiJS3ZHF5 CodeBuddy海外版Pro版本包-包月/CodeBuddy Pro Plan - Monthly:
4438
+ * TCACA_code_006_DbXS0lrypC CodeBuddy海外版一次性免费赠送2周的Pro版本包/CodeBuddy One-time Free 2-Week Pro Plan Trial
4439
+ * TCACA_code_007_nzdH5h4Nl0 CodeBuddy海外版运营裂变包/CodeBuddy Growth Plan
4440
+ * TCACA_code_003_FAnt7lcmRT CodeBuddy海外版Pro版本包-包年/CodeBuddy Pro Plan - Yearly
4441
+ * TCACA_code_008_cfWoLwvjU4 赠送月包
4442
+ */
4443
+ let CommodityCode = /* @__PURE__ */ function(CommodityCode) {
4444
+ CommodityCode["free"] = "TCACA_code_001_PqouKr6QWV";
4445
+ CommodityCode["proMon"] = "TCACA_code_002_AkiJS3ZHF5";
4446
+ CommodityCode["proMonPlus"] = "TCACA_code_005_maRGyrHhw1";
4447
+ CommodityCode["gift"] = "TCACA_code_006_DbXS0lrypC";
4448
+ CommodityCode["activity"] = "TCACA_code_007_nzdH5h4Nl0";
4449
+ CommodityCode["proYear"] = "TCACA_code_003_FAnt7lcmRT";
4450
+ CommodityCode["freeMon"] = "TCACA_code_008_cfWoLwvjU4";
4451
+ CommodityCode["extra"] = "TCACA_code_009_0XmEQc2xOf";
4452
+ return CommodityCode;
4453
+ }({});
4454
+ /** 账户状态 */
4455
+ let AccountStatus = /* @__PURE__ */ function(AccountStatus) {
4456
+ /** 有效 */
4457
+ AccountStatus[AccountStatus["valid"] = 0] = "valid";
4458
+ /** 已退款 */
4459
+ AccountStatus[AccountStatus["refund"] = 1] = "refund";
4460
+ /** 已过期 */
4461
+ AccountStatus[AccountStatus["expired"] = 2] = "expired";
4462
+ /** 已用完 */
4463
+ AccountStatus[AccountStatus["usedUp"] = 3] = "usedUp";
4464
+ return AccountStatus;
4465
+ }({});
4466
+
4467
+ //#endregion
4468
+ //#region ../agent-provider/src/backend/backend-provider.ts
4469
+ /**
4470
+ * Backend Provider 实现
4471
+ *
4472
+ * 封装与后端 API 的 HTTP 通信
4473
+ */
4474
+ /** 获取当前域名的登录页面 URL */
4475
+ const getLoginUrl = () => `${window.location.origin}/login`;
4476
+ /** 获取当前域名的账号选择页面 URL */
4477
+ const getSelectAccountUrl = () => `${window.location.origin}/login/select`;
4478
+ /** localStorage 中存储选中账号 ID 的 key */
4479
+ const SELECTED_ACCOUNT_KEY = "CODEBUDDY_IDE_SELECTED_ACCOUNT_ID";
4480
+ /**
4481
+ * Backend Provider 实现类
4482
+ *
4483
+ * 职责:
4484
+ * - 触发登录/登出流程
4485
+ * - 获取 account 后自动同步到 accountService
4486
+ *
4487
+ * 注意:getAgents 和 getModels 方法已废弃并移除,
4488
+ * 请使用 IAgentAdapter 中的对应方法
4489
+ */
4490
+ var BackendProvider = class {
4491
+ constructor(config) {
4492
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
4493
+ this.authToken = config.authToken;
4494
+ }
4495
+ /**
4496
+ * 获取当前账号信息
4497
+ * API 端点: GET /console/accounts (返回账号列表)
4498
+ *
4499
+ * 逻辑:
4500
+ * 1. 从 localStorage 读取 CODEBUDDY_IDE_SELECTED_ACCOUNT_ID
4501
+ * 2. 根据 CODEBUDDY_IDE_SELECTED_ACCOUNT_ID 找到对应账号
4502
+ * - personal 类型: 用 uid 匹配
4503
+ * - 其他类型: 用 enterpriseId 匹配
4504
+ * 3. 如果没有选中的账号,跳转到账号选择页面
4505
+ * 4. 获取套餐信息并合并到账号中
4506
+ * 5. 同步到 accountService
4507
+ */
4508
+ async getAccount() {
4509
+ const url = `${this.baseUrl}/console/accounts`;
4510
+ const headers = {
4511
+ "Content-Type": "application/json",
4512
+ "Accept": "application/json"
4513
+ };
4514
+ if (this.authToken) headers["Authorization"] = `Bearer ${this.authToken}`;
4515
+ try {
4516
+ const response = await fetch(url, {
4517
+ method: "GET",
4518
+ headers,
4519
+ credentials: "include"
4520
+ });
4521
+ if (!response.ok) {
4522
+ if (response.status === 401 || response.status === 403) {
4523
+ accountService.setAccount(null);
4524
+ return null;
4525
+ }
4526
+ const error = await response.json().catch(() => ({ message: response.statusText }));
4527
+ throw new Error(error.message || `HTTP ${response.status}`);
4528
+ }
4529
+ if (!(response.headers.get("Content-Type") || "").includes("application/json")) {
4530
+ accountService.setAccount(null);
4531
+ return null;
4532
+ }
4533
+ const { data = {} } = await response.json();
4534
+ const accounts = data?.accounts || [];
4535
+ if (!accounts || accounts.length === 0) {
4536
+ accountService.setAccount(null);
4537
+ return null;
4538
+ }
4539
+ const selectedAccountId = localStorage.getItem(SELECTED_ACCOUNT_KEY);
4540
+ let selectedAccount;
4541
+ if (selectedAccountId) {
4542
+ selectedAccount = accounts.find((account) => {
4543
+ if (account.type === "personal") return account.uid === selectedAccountId;
4544
+ return account.enterpriseId === selectedAccountId;
4545
+ });
4546
+ if (selectedAccount) try {
4547
+ const plan = await this.getCurrentPlan();
4548
+ const editionType = this.getEditionDisplayType(selectedAccount.type, plan.isPro);
4549
+ console.log("account", {
4550
+ ...selectedAccount,
4551
+ ...plan,
4552
+ editionType
4553
+ });
4554
+ const account = {
4555
+ ...selectedAccount,
4556
+ ...plan,
4557
+ editionType
4558
+ };
4559
+ accountService.setAccount(account);
4560
+ return account;
4561
+ } catch (error) {
4562
+ accountService.setAccount(selectedAccount);
4563
+ return { ...selectedAccount };
4564
+ }
4565
+ }
4566
+ if (accounts.length === 1) {
4567
+ selectedAccount = accounts[0];
4568
+ const accountId = selectedAccount.type === "personal" ? selectedAccount.uid : selectedAccount.enterpriseId;
4569
+ if (accountId) localStorage.setItem(SELECTED_ACCOUNT_KEY, accountId);
4570
+ const plan = await this.getCurrentPlan();
4571
+ const editionType = this.getEditionDisplayType(selectedAccount.type, plan.isPro);
4572
+ console.log("account (auto-selected)", {
4573
+ ...selectedAccount,
4574
+ ...plan,
4575
+ editionType
4576
+ });
4577
+ const account = {
4578
+ ...selectedAccount,
4579
+ ...plan,
4580
+ editionType
4581
+ };
4582
+ accountService.setAccount(account);
4583
+ return account;
4584
+ }
4585
+ const redirectUrl = encodeURIComponent(window.location.href);
4586
+ window.location.href = `${getSelectAccountUrl()}?platform=website&state=0&redirect_uri=${redirectUrl}`;
4587
+ accountService.setAccount(null);
4588
+ return null;
4589
+ } catch (error) {
4590
+ console.error("[BackendProvider] getAccount failed:", error);
4591
+ accountService.setAccount(null);
4592
+ return null;
4593
+ }
4594
+ }
4595
+ /**
4596
+ * 获取当前套餐信息
4597
+ * 从计量计费接口获取用户的套餐信息
4598
+ * API: POST /billing/meter/get-user-resource
4599
+ */
4600
+ async getCurrentPlan() {
4601
+ const defaultPlan = {
4602
+ isPro: false,
4603
+ expireAt: 0,
4604
+ renewFlag: 0,
4605
+ PackageCode: void 0,
4606
+ name: ""
4607
+ };
4608
+ try {
4609
+ const url = `${this.baseUrl}/billing/meter/get-user-resource`;
4610
+ const headers = {
4611
+ "Content-Type": "application/json",
4612
+ "Accept": "application/json"
4613
+ };
4614
+ if (this.authToken) headers["Authorization"] = `Bearer ${this.authToken}`;
4615
+ const now = /* @__PURE__ */ new Date();
4616
+ const futureDate = new Date(now.getTime() + 101 * 365 * 24 * 60 * 60 * 1e3);
4617
+ const formatDate = (d) => {
4618
+ const pad = (n) => n.toString().padStart(2, "0");
4619
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
4620
+ };
4621
+ const body = {
4622
+ PageNumber: 1,
4623
+ PageSize: 100,
4624
+ ProductCode: "p_tcaca",
4625
+ Status: [AccountStatus.valid, AccountStatus.usedUp],
4626
+ PackageEndTimeRangeBegin: formatDate(now),
4627
+ PackageEndTimeRangeEnd: formatDate(futureDate)
4628
+ };
4629
+ const response = await fetch(url, {
4630
+ method: "POST",
4631
+ headers,
4632
+ credentials: "include",
4633
+ body: JSON.stringify(body)
4634
+ });
4635
+ if (!response.ok) {
4636
+ console.warn("[BackendProvider] getCurrentPlan failed:", response.status);
4637
+ return defaultPlan;
4638
+ }
4639
+ const resources = (await response.json())?.data?.Response?.Data?.Accounts || [];
4640
+ if (!resources || resources.length === 0) return defaultPlan;
4641
+ const proPlan = resources.find((r) => r.PackageCode === CommodityCode.proYear || r.PackageCode === CommodityCode.proMon || r.PackageCode === CommodityCode.proMonPlus);
4642
+ const trialPlan = resources.find((r) => r.PackageCode === CommodityCode.gift || r.PackageCode === CommodityCode.freeMon);
4643
+ const activePlan = proPlan || trialPlan;
4644
+ if (activePlan) {
4645
+ const parseTime = (time) => {
4646
+ if (!time) return 0;
4647
+ return new Date(time).getTime();
4648
+ };
4649
+ return {
4650
+ isPro: !!proPlan,
4651
+ isTria: [AccountStatus.valid, AccountStatus.usedUp].includes(trialPlan?.Status),
4652
+ expireAt: parseTime(activePlan.DeductionEndTime || activePlan.ExpiredTime || activePlan.CycleEndTime),
4653
+ refreshAt: parseTime(activePlan.CycleEndTime),
4654
+ renewFlag: Number(activePlan.AutoRenewFlag) === 1 ? 1 : 0,
4655
+ PackageCode: activePlan.PackageCode,
4656
+ name: activePlan.PackageName || "",
4657
+ usageTotal: activePlan.CycleCapacitySizePrecise,
4658
+ usageUsed: activePlan.CapacityUsedPrecise,
4659
+ usageLeft: activePlan.CycleCapacityRemainPrecise
4660
+ };
4661
+ }
4662
+ return defaultPlan;
4663
+ } catch (error) {
4664
+ console.error("[BackendProvider] getCurrentPlan error:", error);
4665
+ return defaultPlan;
4666
+ }
4667
+ }
4668
+ /**
4669
+ * 根据账号类型和 Pro 状态计算版本展示类型
4670
+ * - personal + isPro = 'pro'
4671
+ * - personal + !isPro = 'free'
4672
+ * - ultimate = 'ultimate' (旗舰版/团队版)
4673
+ * - exclusive = 'exclusive' (专享版/企业版)
4674
+ */
4675
+ getEditionDisplayType(type, isPro) {
4676
+ if (type === "personal") return isPro ? "pro" : "free";
4677
+ if (type === "ultimate") return "ultimate";
4678
+ if (type === "exclusive") return "exclusive";
4679
+ return "free";
4680
+ }
4681
+ /**
4682
+ * 触发登录流程
4683
+ * Web 环境: 跳转到登录页面
4684
+ */
4685
+ async login() {
4686
+ const redirectUrl = encodeURIComponent(window.location.href);
4687
+ window.location.href = `${getLoginUrl()}?platform=website&state=0&redirect_uri=${redirectUrl}`;
4688
+ }
4689
+ /**
4690
+ * 登出账号
4691
+ * Web 环境: 调用登出接口并清除本地状态
4692
+ */
4693
+ async logout() {
4694
+ const url = `${this.baseUrl}/console/logout`;
4695
+ try {
4696
+ await fetch(url, {
4697
+ method: "POST",
4698
+ credentials: "include"
4699
+ });
4700
+ } catch (error) {
4701
+ console.error("[BackendProvider] logout failed:", error);
4702
+ }
4703
+ localStorage.removeItem(SELECTED_ACCOUNT_KEY);
4704
+ accountService.clearAccount();
4705
+ }
4706
+ };
4707
+ /**
4708
+ * 创建 BackendProvider 实例
4709
+ */
4710
+ function createBackendProvider(config) {
4711
+ return new BackendProvider(config);
4712
+ }
4713
+
4714
+ //#endregion
4715
+ //#region ../agent-provider/src/backend/ipc-backend-provider.ts
4716
+ /**
4717
+ * Backend 请求类型常量
4718
+ */
4719
+ const BACKEND_REQUEST_TYPES = {
4720
+ LOGIN: "backend:login",
4721
+ LOGOUT: "backend:logout",
4722
+ GET_ACCOUNT: "backend:get-account"
4723
+ };
4724
+ /**
4725
+ * 生成唯一请求 ID
4726
+ */
4727
+ function generateRequestId() {
4728
+ return `req-${Date.now()}-${Math.random().toString(36).slice(2)}`;
4729
+ }
4730
+ /**
4731
+ * IPC Backend Provider 实现类
4732
+ *
4733
+ * 通过 IWidgetChannel 与后端通信
4734
+ */
4735
+ var IPCBackendProvider = class {
4736
+ constructor(config) {
4737
+ this.channel = config.channel;
4738
+ this.debug = config.debug ?? false;
4739
+ this.timeoutMs = config.timeoutMs ?? 3e4;
4740
+ this.log("Initialized with IWidgetChannel");
4741
+ }
4742
+ /**
4743
+ * 发送统一格式的后端请求
4744
+ * @param requestType 请求类型
4745
+ * @param params 请求参数
4746
+ * @returns 响应数据
4747
+ */
4748
+ async sendBackendRequest(requestType, params) {
4749
+ const message = {
4750
+ type: "backend",
4751
+ requestId: generateRequestId(),
4752
+ params: {
4753
+ type: requestType,
4754
+ params
4755
+ }
4756
+ };
4757
+ this.log("Sending backend request:", message);
4758
+ const response = await this.channel.callMethod("__backend__", message, this.timeoutMs);
4759
+ this.log("Received response:", response);
4760
+ if (response?.error) throw new Error(response.error);
4761
+ return response?.data !== void 0 ? response.data : response;
4762
+ }
4763
+ /**
4764
+ * 获取当前账号信息
4765
+ * IDE 环境: 通过 IPC 获取账号信息,并同步到 accountService
4766
+ */
4767
+ async getAccount() {
4768
+ this.log("Getting account via IPC");
4769
+ try {
4770
+ const account = await this.sendBackendRequest(BACKEND_REQUEST_TYPES.GET_ACCOUNT);
4771
+ accountService.setAccount(account);
4772
+ return account;
4773
+ } catch (error) {
4774
+ this.log("Get account failed:", error);
4775
+ accountService.setAccount(null);
4776
+ return null;
4777
+ }
4778
+ }
4779
+ /**
4780
+ * 触发登录流程
4781
+ * IDE 环境: 通过 IPC 通知 IDE 打开登录流程
4782
+ */
4783
+ async login() {
4784
+ this.log("Triggering login via IPC");
4785
+ try {
4786
+ await this.sendBackendRequest(BACKEND_REQUEST_TYPES.LOGIN);
4787
+ } catch (error) {
4788
+ this.log("Login request failed:", error);
4789
+ throw error;
4790
+ }
4791
+ }
4792
+ /**
4793
+ * 登出账号
4794
+ * IDE 环境: 通过 IPC 通知 IDE 登出
4795
+ */
4796
+ async logout() {
4797
+ this.log("Triggering logout via IPC");
4798
+ try {
4799
+ await this.sendBackendRequest(BACKEND_REQUEST_TYPES.LOGOUT);
4800
+ accountService.clearAccount();
4801
+ } catch (error) {
4802
+ this.log("Logout request failed:", error);
4803
+ throw error;
4804
+ }
4805
+ }
4806
+ /**
4807
+ * 调试日志
4808
+ */
4809
+ log(...args) {
4810
+ if (this.debug) console.log("[IPCBackendProvider]", ...args);
4811
+ }
4812
+ };
4813
+ /**
4814
+ * 创建 IPCBackendProvider 实例
4815
+ */
4816
+ function createIPCBackendProvider(config) {
4817
+ return new IPCBackendProvider(config);
4818
+ }
4819
+
3867
4820
  //#endregion
3868
4821
  exports.ActiveSessionImpl = ActiveSessionImpl;
3869
4822
  exports.AgentClient = AgentClient;