mycto_agent 0.1.0

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.mjs ADDED
@@ -0,0 +1,2913 @@
1
+ import { convertToModelMessages, stepCountIs, streamText, zodSchema } from 'ai';
2
+ import { createAnthropic } from '@ai-sdk/anthropic';
3
+ import { createOpenAI } from '@ai-sdk/openai';
4
+ import { createGoogleGenerativeAI } from '@ai-sdk/google';
5
+ import { getEncoding } from 'js-tiktoken';
6
+ import { z } from 'zod';
7
+ import { spawn } from 'child_process';
8
+ import { stat, readFile, mkdir, writeFile } from 'fs/promises';
9
+ import { existsSync } from 'fs';
10
+ import { isAbsolute, resolve, dirname } from 'path';
11
+ import { glob } from 'glob';
12
+
13
+ // src/core/session-store.ts
14
+ var currentStore = null;
15
+ function setSessionStore(store) {
16
+ currentStore = store;
17
+ }
18
+ function getSessionStore() {
19
+ if (!currentStore) {
20
+ throw new Error("SessionStore not initialized. Call setSessionStore() or initOpenAgent() first.");
21
+ }
22
+ return currentStore;
23
+ }
24
+ function hasSessionStore() {
25
+ return currentStore !== null;
26
+ }
27
+
28
+ // src/utils/id.ts
29
+ var counter = 0;
30
+ var lastTimestamp = 0;
31
+ function generateId(prefix) {
32
+ const timestamp = Date.now();
33
+ if (timestamp === lastTimestamp) {
34
+ counter++;
35
+ } else {
36
+ counter = 0;
37
+ lastTimestamp = timestamp;
38
+ }
39
+ const random = Math.random().toString(36).substring(2, 8);
40
+ const id = `${timestamp.toString(36)}_${counter.toString(36)}_${random}`;
41
+ return prefix ? `${prefix}_${id}` : id;
42
+ }
43
+ function sessionId() {
44
+ return generateId("ses");
45
+ }
46
+ function messageId() {
47
+ return generateId("msg");
48
+ }
49
+ function partId() {
50
+ return generateId("part");
51
+ }
52
+ function toolCallId() {
53
+ return generateId("call");
54
+ }
55
+
56
+ // src/core/memory-store.ts
57
+ function createStorage() {
58
+ return {
59
+ sessions: /* @__PURE__ */ new Map(),
60
+ messages: /* @__PURE__ */ new Map(),
61
+ parts: /* @__PURE__ */ new Map(),
62
+ sessionMessages: /* @__PURE__ */ new Map(),
63
+ messageParts: /* @__PURE__ */ new Map()
64
+ };
65
+ }
66
+ var MemorySessionStore = class {
67
+ storage;
68
+ constructor() {
69
+ this.storage = createStorage();
70
+ }
71
+ // ---- Session CRUD ----
72
+ async createSession(input) {
73
+ const id = sessionId();
74
+ const now = /* @__PURE__ */ new Date();
75
+ const session = {
76
+ id,
77
+ projectId: input.projectId,
78
+ taskId: input.taskId,
79
+ parentId: input.parentId,
80
+ agent: input.agent,
81
+ mode: input.mode ?? "build",
82
+ status: "IDLE",
83
+ title: input.title,
84
+ totalCost: 0,
85
+ createdAt: now,
86
+ updatedAt: now
87
+ };
88
+ this.storage.sessions.set(id, session);
89
+ this.storage.sessionMessages.set(id, []);
90
+ return session;
91
+ }
92
+ async getSession(id) {
93
+ return this.storage.sessions.get(id) ?? null;
94
+ }
95
+ async updateSession(id, data) {
96
+ const session = this.storage.sessions.get(id);
97
+ if (!session) {
98
+ throw new Error(`Session not found: ${id}`);
99
+ }
100
+ const updated = {
101
+ ...session,
102
+ ...data,
103
+ updatedAt: /* @__PURE__ */ new Date()
104
+ };
105
+ this.storage.sessions.set(id, updated);
106
+ return updated;
107
+ }
108
+ async deleteSession(id) {
109
+ const messageIds = this.storage.sessionMessages.get(id) ?? [];
110
+ for (const messageId2 of messageIds) {
111
+ const partIds = this.storage.messageParts.get(messageId2) ?? [];
112
+ for (const partId3 of partIds) {
113
+ this.storage.parts.delete(partId3);
114
+ }
115
+ this.storage.messageParts.delete(messageId2);
116
+ this.storage.messages.delete(messageId2);
117
+ }
118
+ this.storage.sessionMessages.delete(id);
119
+ this.storage.sessions.delete(id);
120
+ }
121
+ async listSessions(options) {
122
+ let sessions = Array.from(this.storage.sessions.values());
123
+ if (options.projectId) {
124
+ sessions = sessions.filter((s) => s.projectId === options.projectId);
125
+ }
126
+ if (options.taskId) {
127
+ sessions = sessions.filter((s) => s.taskId === options.taskId);
128
+ }
129
+ if (options.status) {
130
+ sessions = sessions.filter((s) => s.status === options.status);
131
+ }
132
+ sessions.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
133
+ const offset = options.offset ?? 0;
134
+ const limit = options.limit ?? 50;
135
+ return sessions.slice(offset, offset + limit);
136
+ }
137
+ // ---- Message CRUD ----
138
+ async createMessage(data) {
139
+ const id = messageId();
140
+ const now = /* @__PURE__ */ new Date();
141
+ const message = {
142
+ id,
143
+ sessionId: data.sessionId,
144
+ parentId: data.parentId,
145
+ role: data.role,
146
+ agent: data.agent,
147
+ providerId: data.providerId,
148
+ modelId: data.modelId,
149
+ cost: 0,
150
+ createdAt: now
151
+ };
152
+ this.storage.messages.set(id, message);
153
+ this.storage.messageParts.set(id, []);
154
+ const messageIds = this.storage.sessionMessages.get(data.sessionId) ?? [];
155
+ messageIds.push(id);
156
+ this.storage.sessionMessages.set(data.sessionId, messageIds);
157
+ return message;
158
+ }
159
+ async getMessage(id) {
160
+ const message = this.storage.messages.get(id);
161
+ if (!message) return null;
162
+ const partIds = this.storage.messageParts.get(id) ?? [];
163
+ const parts = partIds.map((pid) => this.storage.parts.get(pid)).filter((p) => p !== void 0).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
164
+ return { ...message, parts };
165
+ }
166
+ async updateMessage(id, data) {
167
+ const message = this.storage.messages.get(id);
168
+ if (!message) {
169
+ throw new Error(`Message not found: ${id}`);
170
+ }
171
+ const updated = {
172
+ ...message,
173
+ ...data
174
+ };
175
+ this.storage.messages.set(id, updated);
176
+ return updated;
177
+ }
178
+ async getSessionMessages(sessionId2) {
179
+ const messageIds = this.storage.sessionMessages.get(sessionId2) ?? [];
180
+ const messages = [];
181
+ for (const messageId2 of messageIds) {
182
+ const message = await this.getMessage(messageId2);
183
+ if (message) {
184
+ messages.push(message);
185
+ }
186
+ }
187
+ messages.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
188
+ return messages;
189
+ }
190
+ // ---- Part CRUD ----
191
+ async createPart(messageId2, data) {
192
+ const id = partId();
193
+ const now = /* @__PURE__ */ new Date();
194
+ let part;
195
+ switch (data.type) {
196
+ case "TEXT":
197
+ part = {
198
+ id,
199
+ messageId: messageId2,
200
+ type: "TEXT",
201
+ text: data.text,
202
+ createdAt: now
203
+ };
204
+ break;
205
+ case "TOOL":
206
+ part = {
207
+ id,
208
+ messageId: messageId2,
209
+ type: "TOOL",
210
+ toolName: data.toolName,
211
+ toolCallId: data.toolCallId,
212
+ toolInput: data.toolInput,
213
+ toolOutput: data.toolOutput,
214
+ toolStatus: data.toolStatus,
215
+ toolMeta: data.toolMeta,
216
+ createdAt: now
217
+ };
218
+ break;
219
+ case "REASONING":
220
+ part = {
221
+ id,
222
+ messageId: messageId2,
223
+ type: "REASONING",
224
+ reasoning: data.reasoning,
225
+ createdAt: now
226
+ };
227
+ break;
228
+ case "FILE":
229
+ part = {
230
+ id,
231
+ messageId: messageId2,
232
+ type: "FILE",
233
+ fileUrl: data.fileUrl,
234
+ fileName: data.fileName,
235
+ fileMime: data.fileMime,
236
+ createdAt: now
237
+ };
238
+ break;
239
+ }
240
+ this.storage.parts.set(id, part);
241
+ const partIds = this.storage.messageParts.get(messageId2) ?? [];
242
+ partIds.push(id);
243
+ this.storage.messageParts.set(messageId2, partIds);
244
+ return part;
245
+ }
246
+ async updatePart(id, data) {
247
+ const part = this.storage.parts.get(id);
248
+ if (!part) {
249
+ throw new Error(`Part not found: ${id}`);
250
+ }
251
+ let updated;
252
+ if (part.type === "TEXT" && data.text !== void 0) {
253
+ updated = { ...part, text: data.text };
254
+ } else if (part.type === "TOOL") {
255
+ updated = {
256
+ ...part,
257
+ toolOutput: data.toolOutput ?? part.toolOutput,
258
+ toolStatus: data.toolStatus ?? part.toolStatus,
259
+ toolMeta: data.toolMeta ?? part.toolMeta
260
+ };
261
+ } else if (part.type === "REASONING" && data.reasoning !== void 0) {
262
+ updated = { ...part, reasoning: data.reasoning };
263
+ } else {
264
+ updated = part;
265
+ }
266
+ this.storage.parts.set(id, updated);
267
+ return updated;
268
+ }
269
+ async appendPartText(id, delta) {
270
+ const part = this.storage.parts.get(id);
271
+ if (!part || part.type !== "TEXT") {
272
+ throw new Error(`Text part not found: ${id}`);
273
+ }
274
+ const updated = {
275
+ ...part,
276
+ text: (part.text ?? "") + delta
277
+ };
278
+ this.storage.parts.set(id, updated);
279
+ }
280
+ async appendPartReasoning(id, delta) {
281
+ const part = this.storage.parts.get(id);
282
+ if (!part || part.type !== "REASONING") {
283
+ throw new Error(`Reasoning part not found: ${id}`);
284
+ }
285
+ const updated = {
286
+ ...part,
287
+ reasoning: (part.reasoning ?? "") + delta
288
+ };
289
+ this.storage.parts.set(id, updated);
290
+ }
291
+ // ---- Batch Operations ----
292
+ async batchSaveMessage(input) {
293
+ const message = await this.createMessage({
294
+ sessionId: input.sessionId,
295
+ parentId: input.parentId,
296
+ role: input.role,
297
+ agent: input.agent,
298
+ providerId: input.providerId,
299
+ modelId: input.modelId
300
+ });
301
+ const updated = await this.updateMessage(message.id, {
302
+ finish: input.finish,
303
+ cost: input.cost,
304
+ tokens: input.tokens,
305
+ completedAt: input.completedAt
306
+ });
307
+ const parts = [];
308
+ for (const partInput of input.parts) {
309
+ const part = await this.createPart(message.id, partInput);
310
+ parts.push(part);
311
+ }
312
+ return { ...updated, parts };
313
+ }
314
+ async batchUpdateSession(input) {
315
+ await this.updateSession(input.sessionId, {
316
+ totalCost: input.totalCost,
317
+ totalTokens: input.totalTokens
318
+ });
319
+ }
320
+ // ---- Utility ----
321
+ /**
322
+ * 清空所有存储(用于测试)
323
+ */
324
+ clear() {
325
+ this.storage = createStorage();
326
+ }
327
+ /**
328
+ * 获取存储统计
329
+ */
330
+ stats() {
331
+ return {
332
+ sessions: this.storage.sessions.size,
333
+ messages: this.storage.messages.size,
334
+ parts: this.storage.parts.size
335
+ };
336
+ }
337
+ };
338
+ function createMemoryStore() {
339
+ return new MemorySessionStore();
340
+ }
341
+
342
+ // src/utils/log.ts
343
+ var LOG_LEVELS = {
344
+ debug: 0,
345
+ info: 1,
346
+ warn: 2,
347
+ error: 3
348
+ };
349
+ var currentLevel = process.env.LOG_LEVEL || "info";
350
+ function shouldLog(level) {
351
+ return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
352
+ }
353
+ function formatMessage(level, service, message, data) {
354
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
355
+ const dataStr = data ? ` ${JSON.stringify(data)}` : "";
356
+ return `[${timestamp}] [${level.toUpperCase()}] [${service}] ${message}${dataStr}`;
357
+ }
358
+ function createLogger(service) {
359
+ return {
360
+ debug(message, data) {
361
+ if (shouldLog("debug")) {
362
+ console.debug(formatMessage("debug", service, message, data));
363
+ }
364
+ },
365
+ info(message, data) {
366
+ if (shouldLog("info")) {
367
+ console.info(formatMessage("info", service, message, data));
368
+ }
369
+ },
370
+ warn(message, data) {
371
+ if (shouldLog("warn")) {
372
+ console.warn(formatMessage("warn", service, message, data));
373
+ }
374
+ },
375
+ error(message, data) {
376
+ if (shouldLog("error")) {
377
+ console.error(formatMessage("error", service, message, data));
378
+ }
379
+ }
380
+ };
381
+ }
382
+
383
+ // src/core/session.ts
384
+ var log = createLogger("session");
385
+ async function createSession(input) {
386
+ log.info("Creating session", { agent: input.agent, mode: input.mode });
387
+ return getSessionStore().createSession(input);
388
+ }
389
+ async function getSession(id) {
390
+ return getSessionStore().getSession(id);
391
+ }
392
+ async function updateSession(id, data) {
393
+ return getSessionStore().updateSession(id, data);
394
+ }
395
+ async function deleteSession(id) {
396
+ return getSessionStore().deleteSession(id);
397
+ }
398
+ async function listSessions(options) {
399
+ return getSessionStore().listSessions(options);
400
+ }
401
+ async function createMessage(data) {
402
+ return getSessionStore().createMessage(data);
403
+ }
404
+ async function getMessage(id) {
405
+ return getSessionStore().getMessage(id);
406
+ }
407
+ async function updateMessage(id, data) {
408
+ return getSessionStore().updateMessage(id, data);
409
+ }
410
+ async function getSessionMessages(sessionId2) {
411
+ return getSessionStore().getSessionMessages(sessionId2);
412
+ }
413
+ async function createPart(messageId2, data) {
414
+ return getSessionStore().createPart(messageId2, data);
415
+ }
416
+ async function updatePart(id, data) {
417
+ return getSessionStore().updatePart(id, data);
418
+ }
419
+ async function batchSaveMessage(input) {
420
+ log.info("Batch saving message", {
421
+ sessionId: input.sessionId,
422
+ role: input.role,
423
+ partsCount: input.parts.length
424
+ });
425
+ return getSessionStore().batchSaveMessage(input);
426
+ }
427
+ async function batchUpdateSession(input) {
428
+ return getSessionStore().batchUpdateSession(input);
429
+ }
430
+
431
+ // src/utils/error.ts
432
+ var OpenAgentError = class extends Error {
433
+ constructor(message, code, data) {
434
+ super(message);
435
+ this.code = code;
436
+ this.data = data;
437
+ this.name = "OpenAgentError";
438
+ }
439
+ toJSON() {
440
+ return {
441
+ name: this.name,
442
+ message: this.message,
443
+ code: this.code,
444
+ data: this.data
445
+ };
446
+ }
447
+ };
448
+ var SessionNotFoundError = class extends OpenAgentError {
449
+ constructor(sessionId2) {
450
+ super(`Session not found: ${sessionId2}`, "SESSION_NOT_FOUND", { sessionId: sessionId2 });
451
+ this.name = "SessionNotFoundError";
452
+ }
453
+ };
454
+ var SessionBusyError = class extends OpenAgentError {
455
+ constructor(sessionId2) {
456
+ super(`Session is busy: ${sessionId2}`, "SESSION_BUSY", { sessionId: sessionId2 });
457
+ this.name = "SessionBusyError";
458
+ }
459
+ };
460
+ var AgentNotFoundError = class extends OpenAgentError {
461
+ constructor(agentName) {
462
+ super(`Agent not found: ${agentName}`, "AGENT_NOT_FOUND", { agentName });
463
+ this.name = "AgentNotFoundError";
464
+ }
465
+ };
466
+ var ModelNotFoundError = class extends OpenAgentError {
467
+ constructor(providerId, modelId) {
468
+ super(
469
+ `Model not found: ${providerId}/${modelId}`,
470
+ "MODEL_NOT_FOUND",
471
+ { providerId, modelId }
472
+ );
473
+ this.name = "ModelNotFoundError";
474
+ }
475
+ };
476
+ var ToolExecutionError = class extends OpenAgentError {
477
+ constructor(toolName, message, cause) {
478
+ super(`Tool execution failed: ${toolName} - ${message}`, "TOOL_EXECUTION_ERROR", {
479
+ toolName,
480
+ cause: cause?.message
481
+ });
482
+ this.name = "ToolExecutionError";
483
+ if (cause) {
484
+ this.cause = cause;
485
+ }
486
+ }
487
+ };
488
+ var PermissionDeniedError = class extends OpenAgentError {
489
+ constructor(permission, pattern) {
490
+ super(
491
+ `Permission denied: ${permission} for ${pattern}`,
492
+ "PERMISSION_DENIED",
493
+ { permission, pattern }
494
+ );
495
+ this.name = "PermissionDeniedError";
496
+ }
497
+ };
498
+ var McpConnectionError = class extends OpenAgentError {
499
+ constructor(serverName, message) {
500
+ super(
501
+ `MCP connection failed: ${serverName} - ${message}`,
502
+ "MCP_CONNECTION_ERROR",
503
+ { serverName }
504
+ );
505
+ this.name = "McpConnectionError";
506
+ }
507
+ };
508
+ var ProviderApiError = class extends OpenAgentError {
509
+ constructor(message, statusCode, provider) {
510
+ super(message, "PROVIDER_API_ERROR", { statusCode, provider });
511
+ this.statusCode = statusCode;
512
+ this.provider = provider;
513
+ this.name = "ProviderApiError";
514
+ }
515
+ };
516
+ var ContextOverflowError = class extends OpenAgentError {
517
+ constructor(maxTokens, currentTokens) {
518
+ super(
519
+ `Context overflow: ${currentTokens} tokens exceeds limit of ${maxTokens}`,
520
+ "CONTEXT_OVERFLOW",
521
+ { maxTokens, currentTokens }
522
+ );
523
+ this.name = "ContextOverflowError";
524
+ }
525
+ };
526
+ function toMessageError(error) {
527
+ if (error instanceof OpenAgentError) {
528
+ return {
529
+ name: error.name,
530
+ message: error.message,
531
+ code: error.code
532
+ };
533
+ }
534
+ if (error instanceof Error) {
535
+ return {
536
+ name: error.name,
537
+ message: error.message
538
+ };
539
+ }
540
+ return {
541
+ name: "Error",
542
+ message: String(error)
543
+ };
544
+ }
545
+ function extractErrorInfo(error) {
546
+ if (error instanceof OpenAgentError) {
547
+ return {
548
+ type: error.name,
549
+ message: error.message,
550
+ details: { code: error.code, ...error.data }
551
+ };
552
+ }
553
+ if (error instanceof Error) {
554
+ return {
555
+ type: error.name,
556
+ message: error.message,
557
+ details: error.cause ? { cause: String(error.cause) } : void 0
558
+ };
559
+ }
560
+ return {
561
+ type: "UnknownError",
562
+ message: String(error)
563
+ };
564
+ }
565
+
566
+ // src/core/agent.ts
567
+ var DEFAULT_AGENTS = {
568
+ "general": {
569
+ name: "general",
570
+ displayName: "General Assistant",
571
+ description: "A general-purpose AI assistant",
572
+ systemPrompt: `You are a helpful AI assistant.
573
+
574
+ You have access to tools that allow you to:
575
+ - Read, write, and edit files
576
+ - Search files with glob patterns
577
+ - Search file contents with grep
578
+ - Execute bash commands
579
+ - Fetch web content
580
+
581
+ When working on tasks:
582
+ 1. First understand the request fully
583
+ 2. Break down complex tasks into steps
584
+ 3. Use tools to gather information before making changes
585
+ 4. Verify your changes work as expected
586
+
587
+ Be precise, efficient, and thorough in your work.`
588
+ },
589
+ "coder": {
590
+ name: "coder",
591
+ displayName: "Code Assistant",
592
+ description: "Specialized in writing and modifying code",
593
+ systemPrompt: `You are an expert software developer AI assistant.
594
+
595
+ ## Your Capabilities
596
+ - Read and analyze code files
597
+ - Write new code and create files
598
+ - Edit existing code with precise modifications
599
+ - Search codebases efficiently
600
+ - Execute commands to test and build code
601
+
602
+ ## Code Quality Guidelines
603
+ - Write clean, maintainable code
604
+ - Follow existing code style and conventions
605
+ - Include appropriate comments and documentation
606
+ - Handle errors gracefully
607
+ - Use TypeScript types properly
608
+
609
+ ## Working Process
610
+ 1. Understand the requirements
611
+ 2. Explore the codebase to understand the context
612
+ 3. Plan your changes
613
+ 4. Implement changes incrementally
614
+ 5. Verify changes work correctly
615
+
616
+ Be precise with edits - always read a file before editing it to ensure accuracy.`
617
+ }
618
+ };
619
+ var runtimeAgents = /* @__PURE__ */ new Map();
620
+ function getDefaultAgent(name) {
621
+ return DEFAULT_AGENTS[name];
622
+ }
623
+ function getDefaultAgentNames() {
624
+ return Object.keys(DEFAULT_AGENTS);
625
+ }
626
+ function getAllAgentNames() {
627
+ const names = /* @__PURE__ */ new Set([
628
+ ...Object.keys(DEFAULT_AGENTS),
629
+ ...runtimeAgents.keys()
630
+ ]);
631
+ return Array.from(names);
632
+ }
633
+ function getAgent(name) {
634
+ const runtimeAgent = runtimeAgents.get(name);
635
+ if (runtimeAgent) {
636
+ return runtimeAgent;
637
+ }
638
+ const defaultAgent = DEFAULT_AGENTS[name];
639
+ if (!defaultAgent) {
640
+ throw new AgentNotFoundError(name);
641
+ }
642
+ return defaultAgent;
643
+ }
644
+ function registerAgent(agent) {
645
+ runtimeAgents.set(agent.name, agent);
646
+ }
647
+ function registerAgents(agents) {
648
+ for (const agent of agents) {
649
+ registerAgent(agent);
650
+ }
651
+ }
652
+ function unregisterAgent(name) {
653
+ return runtimeAgents.delete(name);
654
+ }
655
+ function clearAgents() {
656
+ runtimeAgents.clear();
657
+ }
658
+ var log2 = createLogger("provider");
659
+ var providers = {};
660
+ var providerInstances = /* @__PURE__ */ new Map();
661
+ function configureProvider(config) {
662
+ log2.info("Configuring provider", { id: config.id, type: config.type });
663
+ providers[config.id] = config;
664
+ providerInstances.delete(config.id);
665
+ }
666
+ function configureProviders(configs) {
667
+ for (const config of configs) {
668
+ configureProvider(config);
669
+ }
670
+ }
671
+ function configureFromOpenCodeConfig(config) {
672
+ for (const [providerId, providerConfig] of Object.entries(config.provider)) {
673
+ let type = "openai";
674
+ if (providerId === "anthropic" || providerId.includes("anthropic")) {
675
+ type = "anthropic";
676
+ } else if (providerId === "google" || providerId.includes("google") || providerId.includes("gemini")) {
677
+ type = "google";
678
+ }
679
+ const models = Object.entries(providerConfig.models).map(([modelId, model]) => ({
680
+ id: model.id || modelId,
681
+ name: model.name || modelId,
682
+ contextWindow: model.limit?.context || 128e3,
683
+ maxOutput: model.limit?.output || 8192,
684
+ supportsFunctions: model.tool_call ?? true,
685
+ supportsVision: model.attachment ?? false,
686
+ supportsStreaming: true
687
+ }));
688
+ configureProvider({
689
+ id: providerId,
690
+ name: providerId.charAt(0).toUpperCase() + providerId.slice(1),
691
+ type,
692
+ options: {
693
+ baseURL: providerConfig.options.baseURL,
694
+ apiKey: providerConfig.options.apiKey,
695
+ headers: providerConfig.options.headers
696
+ },
697
+ models
698
+ });
699
+ }
700
+ }
701
+ function resetProviders() {
702
+ providers = {};
703
+ providerInstances.clear();
704
+ }
705
+ function getOrCreateProviderInstance(providerId) {
706
+ const cached = providerInstances.get(providerId);
707
+ if (cached) return cached;
708
+ const config = providers[providerId];
709
+ if (!config) {
710
+ throw new ModelNotFoundError(providerId, "unknown");
711
+ }
712
+ log2.debug("Creating provider instance", {
713
+ providerId,
714
+ type: config.type,
715
+ hasBaseURL: !!config.options.baseURL,
716
+ hasApiKey: !!config.options.apiKey
717
+ });
718
+ let instance;
719
+ switch (config.type) {
720
+ case "anthropic": {
721
+ const options = {};
722
+ if (config.options.baseURL) {
723
+ options.baseURL = config.options.baseURL;
724
+ }
725
+ if (config.options.apiKey) {
726
+ options.apiKey = config.options.apiKey;
727
+ }
728
+ if (config.options.headers) {
729
+ options.headers = config.options.headers;
730
+ }
731
+ instance = createAnthropic(options);
732
+ break;
733
+ }
734
+ case "openai": {
735
+ const options = {};
736
+ if (config.options.baseURL) {
737
+ options.baseURL = config.options.baseURL;
738
+ }
739
+ if (config.options.apiKey) {
740
+ options.apiKey = config.options.apiKey;
741
+ }
742
+ if (config.options.headers) {
743
+ options.headers = config.options.headers;
744
+ }
745
+ instance = createOpenAI(options);
746
+ break;
747
+ }
748
+ case "google": {
749
+ const options = {};
750
+ if (config.options.baseURL) {
751
+ options.baseURL = config.options.baseURL;
752
+ }
753
+ if (config.options.apiKey) {
754
+ options.apiKey = config.options.apiKey;
755
+ }
756
+ if (config.options.headers) {
757
+ options.headers = config.options.headers;
758
+ }
759
+ instance = createGoogleGenerativeAI(options);
760
+ break;
761
+ }
762
+ default:
763
+ throw new Error(`Unknown provider type: ${config.type}`);
764
+ }
765
+ providerInstances.set(providerId, instance);
766
+ return instance;
767
+ }
768
+ var PROVIDERS = new Proxy({}, {
769
+ get(_, key) {
770
+ const config = providers[key];
771
+ if (!config) return void 0;
772
+ return {
773
+ id: config.id,
774
+ name: config.name,
775
+ models: config.models
776
+ };
777
+ },
778
+ ownKeys() {
779
+ return Object.keys(providers);
780
+ },
781
+ getOwnPropertyDescriptor() {
782
+ return { enumerable: true, configurable: true };
783
+ }
784
+ });
785
+ function getProviders() {
786
+ return Object.values(providers).map((config) => ({
787
+ id: config.id,
788
+ name: config.name,
789
+ models: config.models
790
+ }));
791
+ }
792
+ function getProvider(providerId) {
793
+ const config = providers[providerId];
794
+ if (!config) return void 0;
795
+ return {
796
+ id: config.id,
797
+ name: config.name,
798
+ models: config.models
799
+ };
800
+ }
801
+ function getModelInfo(providerId, modelId) {
802
+ const config = providers[providerId];
803
+ if (!config) return void 0;
804
+ return config.models.find((m) => m.id === modelId);
805
+ }
806
+ function getLanguageModel(providerId, modelId) {
807
+ const config = providers[providerId];
808
+ if (!config) {
809
+ throw new ModelNotFoundError(providerId, modelId);
810
+ }
811
+ const modelInfo = config.models.find((m) => m.id === modelId);
812
+ if (!modelInfo) {
813
+ log2.warn("Model not in registry, creating anyway", { providerId, modelId });
814
+ }
815
+ log2.debug("Creating language model", { providerId, modelId, type: config.type });
816
+ const instance = getOrCreateProviderInstance(providerId);
817
+ if (config.type === "openai" && providerId === "zhipuai") {
818
+ return instance.chat(modelId);
819
+ }
820
+ return instance(modelId);
821
+ }
822
+ function getDefaultModel() {
823
+ const defaultProvider = process.env.DEFAULT_AI_PROVIDER || "anthropic";
824
+ const defaultModel = process.env.DEFAULT_AI_MODEL || "claude-3-5-haiku";
825
+ return {
826
+ providerId: defaultProvider,
827
+ modelId: defaultModel
828
+ };
829
+ }
830
+ function calculateCost(providerId, modelId, tokens) {
831
+ const modelInfo = getModelInfo(providerId, modelId);
832
+ if (!modelInfo?.pricing) return 0;
833
+ const { pricing } = modelInfo;
834
+ let cost = 0;
835
+ cost += tokens.input / 1e6 * pricing.input;
836
+ cost += tokens.output / 1e6 * pricing.output;
837
+ if (tokens.cache && pricing.cache) {
838
+ cost += tokens.cache.read / 1e6 * pricing.cache.read;
839
+ cost += tokens.cache.write / 1e6 * pricing.cache.write;
840
+ }
841
+ return cost;
842
+ }
843
+ var encoder = null;
844
+ function getEncoder() {
845
+ if (!encoder) {
846
+ encoder = getEncoding("cl100k_base");
847
+ }
848
+ return encoder;
849
+ }
850
+ function estimateTokens(text) {
851
+ if (!text) return 0;
852
+ try {
853
+ return getEncoder().encode(text).length;
854
+ } catch {
855
+ return Math.ceil(text.length / 4);
856
+ }
857
+ }
858
+ function estimateJsonTokens(obj) {
859
+ try {
860
+ return estimateTokens(JSON.stringify(obj));
861
+ } catch {
862
+ return 0;
863
+ }
864
+ }
865
+ function estimateMessageTokens(message) {
866
+ let tokens = 0;
867
+ tokens += 4;
868
+ if (typeof message.content === "string") {
869
+ tokens += estimateTokens(message.content);
870
+ } else if (Array.isArray(message.content)) {
871
+ for (const part of message.content) {
872
+ if (part.type === "text") {
873
+ tokens += estimateTokens(part.text);
874
+ } else if (part.type === "image") {
875
+ tokens += 1e3;
876
+ } else if (part.type === "tool-call") {
877
+ tokens += estimateTokens(part.toolName);
878
+ tokens += estimateJsonTokens(part.input);
879
+ } else if (part.type === "tool-result") {
880
+ tokens += estimateJsonTokens(part.output);
881
+ }
882
+ }
883
+ }
884
+ return tokens;
885
+ }
886
+ function estimateMessagesTokens(messages) {
887
+ let total = 0;
888
+ for (const message of messages) {
889
+ total += estimateMessageTokens(message);
890
+ }
891
+ total += 3;
892
+ return total;
893
+ }
894
+
895
+ // src/utils/truncation.ts
896
+ var log3 = createLogger("truncation");
897
+ var DEFAULT_TRUNCATION_CONFIG = {
898
+ maxLines: 2e3,
899
+ maxBytes: 50 * 1024,
900
+ lineMaxLength: 2e3
901
+ };
902
+ function truncateLine(line, maxLength) {
903
+ if (line.length <= maxLength) return line;
904
+ return line.substring(0, maxLength) + "... (line truncated)";
905
+ }
906
+ function truncateSimple(text, maxLength = 1e5) {
907
+ if (text.length <= maxLength) {
908
+ return { content: text, truncated: false };
909
+ }
910
+ return {
911
+ content: text.substring(0, maxLength) + "\n\n... (output truncated)",
912
+ truncated: true
913
+ };
914
+ }
915
+ function truncateToolOutput(output, config) {
916
+ const cfg = { ...DEFAULT_TRUNCATION_CONFIG, ...config };
917
+ const lines = output.split("\n");
918
+ const totalBytes = Buffer.byteLength(output, "utf-8");
919
+ if (lines.length <= cfg.maxLines && totalBytes <= cfg.maxBytes) {
920
+ const truncatedLines2 = lines.map((line) => truncateLine(line, cfg.lineMaxLength));
921
+ return {
922
+ content: truncatedLines2.join("\n"),
923
+ truncated: false
924
+ };
925
+ }
926
+ const truncatedLines = [];
927
+ let currentBytes = 0;
928
+ for (let i = 0; i < lines.length && i < cfg.maxLines; i++) {
929
+ const line = truncateLine(lines[i], cfg.lineMaxLength);
930
+ const lineBytes = Buffer.byteLength(line, "utf-8") + 1;
931
+ if (currentBytes + lineBytes > cfg.maxBytes) {
932
+ break;
933
+ }
934
+ truncatedLines.push(line);
935
+ currentBytes += lineBytes;
936
+ }
937
+ const removedLines = lines.length - truncatedLines.length;
938
+ const removedBytes = totalBytes - currentBytes;
939
+ const hint = `
940
+
941
+ ... (${removedLines} lines / ${removedBytes} bytes truncated)`;
942
+ return {
943
+ content: truncatedLines.join("\n") + hint,
944
+ truncated: true
945
+ };
946
+ }
947
+ var DEFAULT_SMART_TRUNCATION_CONFIG = {
948
+ maxTokens: 1e4,
949
+ headRatio: 0.3,
950
+ tailRatio: 0.7
951
+ };
952
+ function truncateForAI(text, config) {
953
+ const cfg = { ...DEFAULT_SMART_TRUNCATION_CONFIG, ...config };
954
+ const originalTokens = estimateTokens(text);
955
+ if (originalTokens <= cfg.maxTokens) {
956
+ return {
957
+ content: text,
958
+ truncated: false,
959
+ originalTokens,
960
+ keptTokens: originalTokens
961
+ };
962
+ }
963
+ const lines = text.split("\n");
964
+ const totalLines = lines.length;
965
+ const headTokens = Math.floor(cfg.maxTokens * cfg.headRatio);
966
+ const tailTokens = Math.floor(cfg.maxTokens * cfg.tailRatio);
967
+ const headLines = [];
968
+ let headTokenCount = 0;
969
+ for (let i = 0; i < lines.length; i++) {
970
+ const lineTokens = estimateTokens(lines[i]);
971
+ if (headTokenCount + lineTokens > headTokens) {
972
+ break;
973
+ }
974
+ headLines.push(lines[i]);
975
+ headTokenCount += lineTokens;
976
+ }
977
+ const tailLines = [];
978
+ let tailTokenCount = 0;
979
+ for (let i = lines.length - 1; i >= headLines.length; i--) {
980
+ const lineTokens = estimateTokens(lines[i]);
981
+ if (tailTokenCount + lineTokens > tailTokens) {
982
+ break;
983
+ }
984
+ tailLines.unshift(lines[i]);
985
+ tailTokenCount += lineTokens;
986
+ }
987
+ const omittedLines = totalLines - headLines.length - tailLines.length;
988
+ const omittedTokens = originalTokens - headTokenCount - tailTokenCount;
989
+ const separator = cfg.partId ? `
990
+
991
+ ... [${omittedLines} lines / ~${omittedTokens} tokens omitted]
992
+ [Use read_tool_output with partId="${cfg.partId}" to see full content]
993
+
994
+ ` : `
995
+
996
+ ... [${omittedLines} lines / ~${omittedTokens} tokens omitted]
997
+
998
+ `;
999
+ const truncatedContent = headLines.join("\n") + separator + tailLines.join("\n");
1000
+ const keptTokens = headTokenCount + tailTokenCount + estimateTokens(separator);
1001
+ log3.debug("Smart truncation applied", {
1002
+ originalTokens,
1003
+ keptTokens,
1004
+ headLines: headLines.length,
1005
+ tailLines: tailLines.length,
1006
+ omittedLines
1007
+ });
1008
+ return {
1009
+ content: truncatedContent,
1010
+ truncated: true,
1011
+ originalTokens,
1012
+ keptTokens
1013
+ };
1014
+ }
1015
+
1016
+ // src/core/llm.ts
1017
+ var log4 = createLogger("llm");
1018
+ var TOOL_OUTPUT_MAX_TOKENS = 1e4;
1019
+ var TOOL_OUTPUT_TRUNCATION_CONFIG = {
1020
+ maxTokens: TOOL_OUTPUT_MAX_TOKENS,
1021
+ headRatio: 0.3,
1022
+ tailRatio: 0.7
1023
+ };
1024
+ function toModelOutput(output) {
1025
+ if (typeof output === "string") {
1026
+ return { type: "text", value: output };
1027
+ }
1028
+ if (typeof output === "object" && output !== null) {
1029
+ const obj = output;
1030
+ if (obj.text !== void 0) {
1031
+ return { type: "text", value: obj.text };
1032
+ }
1033
+ return { type: "json", value: output };
1034
+ }
1035
+ return { type: "json", value: output };
1036
+ }
1037
+ async function toModelMessages(messages) {
1038
+ const result = [];
1039
+ const toolNames = /* @__PURE__ */ new Set();
1040
+ for (const msg of messages) {
1041
+ if (msg.parts.length === 0) continue;
1042
+ if (msg.role === "USER") {
1043
+ const userMessage = {
1044
+ id: msg.id,
1045
+ role: "user",
1046
+ parts: []
1047
+ };
1048
+ for (const part of msg.parts) {
1049
+ if (part.type === "TEXT" && part.text) {
1050
+ userMessage.parts.push({
1051
+ type: "text",
1052
+ text: part.text
1053
+ });
1054
+ }
1055
+ if (part.type === "FILE" && part.fileUrl) {
1056
+ if (part.fileMime?.startsWith("image/")) {
1057
+ userMessage.parts.push({
1058
+ type: "file",
1059
+ url: part.fileUrl,
1060
+ mediaType: part.fileMime,
1061
+ filename: part.fileName
1062
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1063
+ });
1064
+ }
1065
+ }
1066
+ }
1067
+ if (userMessage.parts.length > 0) {
1068
+ result.push(userMessage);
1069
+ }
1070
+ } else if (msg.role === "ASSISTANT") {
1071
+ const assistantMessage = {
1072
+ id: msg.id,
1073
+ role: "assistant",
1074
+ parts: []
1075
+ };
1076
+ for (const part of msg.parts) {
1077
+ if (part.type === "TEXT" && part.text) {
1078
+ assistantMessage.parts.push({
1079
+ type: "text",
1080
+ text: part.text
1081
+ });
1082
+ }
1083
+ if (part.type === "REASONING" && part.reasoning) {
1084
+ assistantMessage.parts.push({
1085
+ type: "reasoning",
1086
+ text: part.reasoning
1087
+ });
1088
+ }
1089
+ if (part.type === "TOOL" && part.toolName && part.toolCallId) {
1090
+ toolNames.add(part.toolName);
1091
+ if (part.toolStatus === "COMPLETED") {
1092
+ let output;
1093
+ if (part.prunedAt) {
1094
+ output = `[Tool output pruned - use read_tool_output tool with partId="${part.id}" to retrieve if needed]`;
1095
+ } else {
1096
+ const rawOutput = part.toolOutput || "";
1097
+ const truncateResult = truncateForAI(rawOutput, {
1098
+ ...TOOL_OUTPUT_TRUNCATION_CONFIG,
1099
+ partId: part.id
1100
+ });
1101
+ output = truncateResult.content;
1102
+ }
1103
+ assistantMessage.parts.push({
1104
+ type: `tool-${part.toolName}`,
1105
+ state: "output-available",
1106
+ toolCallId: part.toolCallId,
1107
+ input: part.toolInput || {},
1108
+ output
1109
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1110
+ });
1111
+ } else if (part.toolStatus === "ERROR") {
1112
+ assistantMessage.parts.push({
1113
+ type: `tool-${part.toolName}`,
1114
+ state: "output-error",
1115
+ toolCallId: part.toolCallId,
1116
+ input: part.toolInput || {},
1117
+ errorText: part.toolOutput || "Tool execution failed"
1118
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1119
+ });
1120
+ } else {
1121
+ assistantMessage.parts.push({
1122
+ type: `tool-${part.toolName}`,
1123
+ state: "output-error",
1124
+ toolCallId: part.toolCallId,
1125
+ input: part.toolInput || {},
1126
+ errorText: "[Tool execution was interrupted]"
1127
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1128
+ });
1129
+ }
1130
+ }
1131
+ }
1132
+ if (assistantMessage.parts.length > 0) {
1133
+ result.push(assistantMessage);
1134
+ }
1135
+ }
1136
+ }
1137
+ const tools = Object.fromEntries(
1138
+ Array.from(toolNames).map((toolName) => [toolName, { toModelOutput }])
1139
+ );
1140
+ let modelMessages = await convertToModelMessages(result, {
1141
+ // @ts-expect-error convertToModelMessages expects a ToolSet but only actually needs tools[name]?.toModelOutput
1142
+ tools
1143
+ });
1144
+ modelMessages = modelMessages.map((msg) => {
1145
+ if (typeof msg.content === "string") {
1146
+ if (msg.content === "") return void 0;
1147
+ return msg;
1148
+ }
1149
+ if (!Array.isArray(msg.content)) return msg;
1150
+ const filtered = msg.content.filter((part) => {
1151
+ if (part.type === "text" || part.type === "reasoning") {
1152
+ return part.text !== "" && part.text !== void 0;
1153
+ }
1154
+ return true;
1155
+ });
1156
+ if (filtered.length === 0) return void 0;
1157
+ return { ...msg, content: filtered };
1158
+ }).filter((msg) => msg !== void 0 && msg.content !== "");
1159
+ return modelMessages;
1160
+ }
1161
+ var CONTEXT_OVERFLOW_PATTERNS = [
1162
+ /prompt is too long/i,
1163
+ /input is too long/i,
1164
+ /exceeds.*context/i,
1165
+ /token.*limit.*exceeded/i,
1166
+ /context.*overflow/i,
1167
+ /maximum.*context.*length/i,
1168
+ /too many tokens/i,
1169
+ /request too large/i
1170
+ ];
1171
+ function isContextOverflowError(error) {
1172
+ const message = error instanceof Error ? error.message : String(error);
1173
+ return CONTEXT_OVERFLOW_PATTERNS.some((pattern) => pattern.test(message));
1174
+ }
1175
+ async function stream(input) {
1176
+ const { providerId, modelId, messages, system, tools, temperature, abort, maxSteps = 10 } = input;
1177
+ log4.info("Starting LLM stream", {
1178
+ providerId,
1179
+ modelId,
1180
+ messageCount: messages.length,
1181
+ hasSystem: !!system,
1182
+ toolCount: tools ? Object.keys(tools).length : 0,
1183
+ maxSteps
1184
+ });
1185
+ const model = getLanguageModel(providerId, modelId);
1186
+ const streamOptions = {
1187
+ model,
1188
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1189
+ messages,
1190
+ system,
1191
+ tools,
1192
+ temperature,
1193
+ abortSignal: abort,
1194
+ stopWhen: stepCountIs(maxSteps)
1195
+ };
1196
+ if (input.maxOutputTokens) {
1197
+ streamOptions.maxTokens = input.maxOutputTokens;
1198
+ }
1199
+ const response = streamText(streamOptions);
1200
+ let resolvedUsage = null;
1201
+ async function* convertStream() {
1202
+ try {
1203
+ for await (const event of (await response).fullStream) {
1204
+ const e = event;
1205
+ switch (e.type) {
1206
+ case "text-delta":
1207
+ yield { type: "text-delta", text: e.text ?? e.textDelta ?? "" };
1208
+ break;
1209
+ case "reasoning-delta":
1210
+ yield { type: "reasoning-delta", text: e.text ?? e.textDelta ?? "" };
1211
+ break;
1212
+ case "tool-call":
1213
+ yield {
1214
+ type: "tool-call",
1215
+ toolCallId: e.toolCallId,
1216
+ toolName: e.toolName,
1217
+ args: e.args ?? e.input ?? {}
1218
+ };
1219
+ break;
1220
+ case "tool-result":
1221
+ yield {
1222
+ type: "tool-result",
1223
+ toolCallId: e.toolCallId,
1224
+ result: e.result ?? e.output
1225
+ };
1226
+ break;
1227
+ case "finish":
1228
+ const usage = await (await response).usage;
1229
+ const tokens = {
1230
+ input: usage?.inputTokens ?? usage?.promptTokens ?? 0,
1231
+ output: usage?.outputTokens ?? usage?.completionTokens ?? 0
1232
+ };
1233
+ const cost = calculateCost(providerId, modelId, tokens);
1234
+ resolvedUsage = { tokens, cost };
1235
+ yield {
1236
+ type: "finish",
1237
+ finishReason: e.finishReason,
1238
+ usage: resolvedUsage
1239
+ };
1240
+ break;
1241
+ case "error":
1242
+ yield { type: "error", error: e.error };
1243
+ break;
1244
+ }
1245
+ }
1246
+ } catch (error) {
1247
+ log4.error("LLM stream error", { error: String(error) });
1248
+ yield { type: "error", error };
1249
+ }
1250
+ }
1251
+ return {
1252
+ fullStream: convertStream(),
1253
+ textPromise: (async () => {
1254
+ const result = await response;
1255
+ return await result.text;
1256
+ })(),
1257
+ usagePromise: (async () => {
1258
+ if (resolvedUsage) return resolvedUsage;
1259
+ const result = await response;
1260
+ const usage = await result.usage;
1261
+ const tokens = {
1262
+ input: usage?.inputTokens ?? usage?.promptTokens ?? 0,
1263
+ output: usage?.outputTokens ?? usage?.completionTokens ?? 0
1264
+ };
1265
+ return {
1266
+ tokens,
1267
+ cost: calculateCost(providerId, modelId, tokens)
1268
+ };
1269
+ })()
1270
+ };
1271
+ }
1272
+ var TOOL_USAGE_GUIDELINES = `
1273
+ ## Tool Usage Guidelines
1274
+
1275
+ ### Parallel Tool Execution
1276
+ - You can call multiple tools in a single response when the calls are independent
1277
+ - This significantly reduces latency when you need to:
1278
+ - Read multiple files
1279
+ - Run multiple searches
1280
+ - Fetch multiple web pages
1281
+ `;
1282
+ function buildSystemPrompt(agent, additionalContext) {
1283
+ const parts = [];
1284
+ parts.push(agent.systemPrompt);
1285
+ parts.push(TOOL_USAGE_GUIDELINES);
1286
+ if (additionalContext) {
1287
+ parts.push("");
1288
+ parts.push(additionalContext);
1289
+ }
1290
+ return parts.join("\n");
1291
+ }
1292
+ function defineTool(config) {
1293
+ return {
1294
+ id: config.id,
1295
+ description: config.description,
1296
+ parameters: config.parameters,
1297
+ execute: async (args, ctx) => {
1298
+ try {
1299
+ config.parameters.parse(args);
1300
+ } catch (error) {
1301
+ if (error instanceof z.ZodError) {
1302
+ const zodError = error;
1303
+ const messages = zodError.issues?.map((e) => e.message).join(", ") ?? String(error);
1304
+ throw new Error(`Tool "${config.id}" received invalid arguments: ${messages}`);
1305
+ }
1306
+ throw error;
1307
+ }
1308
+ return config.execute(args, ctx);
1309
+ }
1310
+ };
1311
+ }
1312
+ function toAITool(toolDef, ctx) {
1313
+ const inputSchema = zodSchema(toolDef.parameters);
1314
+ const tool = {
1315
+ description: toolDef.description,
1316
+ inputSchema,
1317
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1318
+ execute: async (args, options) => {
1319
+ const fullCtx = {
1320
+ ...ctx,
1321
+ callId: options?.toolCallId
1322
+ };
1323
+ const result = await toolDef.execute(args, fullCtx);
1324
+ return result.output;
1325
+ }
1326
+ };
1327
+ return tool;
1328
+ }
1329
+ function toAITools(tools, ctx) {
1330
+ const result = {};
1331
+ for (const tool of tools) {
1332
+ result[tool.id] = toAITool(tool, ctx);
1333
+ }
1334
+ return result;
1335
+ }
1336
+
1337
+ // src/types.ts
1338
+ var TOOL_PERMISSIONS = {
1339
+ // 只读工具 - plan 模式下允许
1340
+ readonly: [
1341
+ "read",
1342
+ // 读取文件
1343
+ "glob",
1344
+ // 文件模式匹配
1345
+ "grep",
1346
+ // 内容搜索
1347
+ "list",
1348
+ // 列出目录
1349
+ "bash",
1350
+ // Shell 命令 (plan 模式下仅限只读命令,由 system prompt 约束)
1351
+ "webfetch",
1352
+ // 获取网页内容
1353
+ "websearch",
1354
+ // 网页搜索
1355
+ "codesearch",
1356
+ // 代码搜索
1357
+ "read_tool_output",
1358
+ // 读取工具输出
1359
+ "batch",
1360
+ // 批量执行 (只能批量执行 plan 模式允许的工具)
1361
+ "todoread"
1362
+ // 读取待办事项
1363
+ ],
1364
+ // 写操作工具 - 仅 build 模式下允许
1365
+ write: [
1366
+ "write",
1367
+ // 写入文件
1368
+ "edit",
1369
+ // 编辑文件
1370
+ "todowrite",
1371
+ // 写入待办事项
1372
+ "task",
1373
+ // 创建子任务 (可能触发写操作)
1374
+ "question",
1375
+ // 询问用户 (仅 build 模式需要交互)
1376
+ "skill"
1377
+ // 加载技能 (可能包含写操作)
1378
+ ]
1379
+ };
1380
+ function getAllowedToolsForMode(mode) {
1381
+ if (mode === "plan") {
1382
+ return [...TOOL_PERMISSIONS.readonly];
1383
+ }
1384
+ return [...TOOL_PERMISSIONS.readonly, ...TOOL_PERMISSIONS.write];
1385
+ }
1386
+ function isToolAllowedInMode(toolId, mode) {
1387
+ if (mode === "build") {
1388
+ return true;
1389
+ }
1390
+ return TOOL_PERMISSIONS.readonly.includes(toolId);
1391
+ }
1392
+ function getModeChangePrompt(from, to) {
1393
+ if (from === "plan" && to === "build") {
1394
+ return `<system-reminder>
1395
+ Your operational mode has changed from plan to build.
1396
+ You are no longer in read-only mode.
1397
+ You are permitted to make file changes, run shell commands, and utilize your arsenal of tools as needed.
1398
+ </system-reminder>`;
1399
+ }
1400
+ if (from === "build" && to === "plan") {
1401
+ return `<system-reminder>
1402
+ Your operational mode has changed from build to plan.
1403
+ You are now in read-only mode.
1404
+ You MUST NOT make any file edits, write new files, or run shell commands that have side effects.
1405
+ You are only permitted to use read-only tools for analysis, exploration, and planning.
1406
+ Focus on understanding the codebase and creating a detailed plan for the task.
1407
+ </system-reminder>`;
1408
+ }
1409
+ return "";
1410
+ }
1411
+
1412
+ // src/tool/registry.ts
1413
+ var log5 = createLogger("tool-registry");
1414
+ var registeredTools = /* @__PURE__ */ new Map();
1415
+ function registerTool(tool) {
1416
+ log5.info("Registering tool", { id: tool.id });
1417
+ registeredTools.set(tool.id, tool);
1418
+ }
1419
+ function registerTools(tools) {
1420
+ for (const tool of tools) {
1421
+ registerTool(tool);
1422
+ }
1423
+ }
1424
+ function getTool(id) {
1425
+ return registeredTools.get(id);
1426
+ }
1427
+ function getAllTools() {
1428
+ return Array.from(registeredTools.values());
1429
+ }
1430
+ function unregisterTool(id) {
1431
+ return registeredTools.delete(id);
1432
+ }
1433
+ function clearTools() {
1434
+ registeredTools.clear();
1435
+ }
1436
+ function getToolsForAgent(agent, mode) {
1437
+ const allTools = getAllTools();
1438
+ return allTools.filter((tool) => {
1439
+ if (mode && !isToolAllowedInMode(tool.id, mode)) {
1440
+ return false;
1441
+ }
1442
+ if (agent.allowedTools && agent.allowedTools.length > 0) {
1443
+ return agent.allowedTools.includes(tool.id);
1444
+ }
1445
+ if (agent.deniedTools && agent.deniedTools.length > 0) {
1446
+ return !agent.deniedTools.includes(tool.id);
1447
+ }
1448
+ return true;
1449
+ });
1450
+ }
1451
+ function getAIToolsForAgent(agent, ctx, mode) {
1452
+ const tools = getToolsForAgent(agent, mode);
1453
+ const result = {};
1454
+ for (const tool of tools) {
1455
+ result[tool.id] = toAITool(tool, ctx);
1456
+ }
1457
+ return result;
1458
+ }
1459
+ function getToolsForMode(mode) {
1460
+ const allTools = getAllTools();
1461
+ return allTools.filter((tool) => isToolAllowedInMode(tool.id, mode));
1462
+ }
1463
+ function getToolDescriptions(tools) {
1464
+ return tools.map((tool) => `- ${tool.id}: ${tool.description}`).join("\n");
1465
+ }
1466
+ var DEFAULT_TIMEOUT = 12e4;
1467
+ async function localBashExecutor(args, ctx) {
1468
+ const { command, workdir, timeout = DEFAULT_TIMEOUT } = args;
1469
+ const cwd = workdir ?? ctx.workingDirectory ?? process.cwd();
1470
+ return new Promise((resolve6, reject) => {
1471
+ const child = spawn("bash", ["-c", command], {
1472
+ cwd,
1473
+ env: process.env,
1474
+ stdio: ["pipe", "pipe", "pipe"]
1475
+ });
1476
+ let stdout = "";
1477
+ let stderr = "";
1478
+ let killed = false;
1479
+ const timeoutId = setTimeout(() => {
1480
+ killed = true;
1481
+ child.kill("SIGTERM");
1482
+ setTimeout(() => {
1483
+ if (!child.killed) {
1484
+ child.kill("SIGKILL");
1485
+ }
1486
+ }, 5e3);
1487
+ }, timeout);
1488
+ const abortHandler = () => {
1489
+ killed = true;
1490
+ child.kill("SIGTERM");
1491
+ clearTimeout(timeoutId);
1492
+ };
1493
+ ctx.abort.addEventListener("abort", abortHandler);
1494
+ child.stdout?.on("data", (data) => {
1495
+ stdout += data.toString();
1496
+ });
1497
+ child.stderr?.on("data", (data) => {
1498
+ stderr += data.toString();
1499
+ });
1500
+ child.on("close", (code) => {
1501
+ clearTimeout(timeoutId);
1502
+ ctx.abort.removeEventListener("abort", abortHandler);
1503
+ if (killed && ctx.abort.aborted) {
1504
+ reject(new Error("Command aborted"));
1505
+ return;
1506
+ }
1507
+ if (killed) {
1508
+ resolve6({
1509
+ title: `Command timed out after ${timeout}ms`,
1510
+ output: `TIMEOUT: Command exceeded ${timeout}ms limit.
1511
+
1512
+ Partial stdout:
1513
+ ${stdout}
1514
+
1515
+ Partial stderr:
1516
+ ${stderr}`
1517
+ });
1518
+ return;
1519
+ }
1520
+ let output = "";
1521
+ if (stdout) {
1522
+ output += stdout;
1523
+ }
1524
+ if (stderr) {
1525
+ output += (output ? "\n\n" : "") + `stderr:
1526
+ ${stderr}`;
1527
+ }
1528
+ if (!output) {
1529
+ output = "(no output)";
1530
+ }
1531
+ if (code !== 0) {
1532
+ output += `
1533
+
1534
+ (exit code: ${code})`;
1535
+ }
1536
+ resolve6({
1537
+ title: args.description ?? `bash: ${command.slice(0, 50)}${command.length > 50 ? "..." : ""}`,
1538
+ output,
1539
+ metadata: {
1540
+ exitCode: code,
1541
+ cwd
1542
+ }
1543
+ });
1544
+ });
1545
+ child.on("error", (err) => {
1546
+ clearTimeout(timeoutId);
1547
+ ctx.abort.removeEventListener("abort", abortHandler);
1548
+ reject(err);
1549
+ });
1550
+ });
1551
+ }
1552
+ var DEFAULT_LIMIT = 2e3;
1553
+ var MAX_LINE_LENGTH = 2e3;
1554
+ async function localReadExecutor(args, ctx) {
1555
+ const { filePath, offset = 0, limit = DEFAULT_LIMIT } = args;
1556
+ const absolutePath = isAbsolute(filePath) ? filePath : resolve(ctx.workingDirectory ?? process.cwd(), filePath);
1557
+ if (!existsSync(absolutePath)) {
1558
+ return {
1559
+ title: `File not found: ${filePath}`,
1560
+ output: `Error: File does not exist at path: ${absolutePath}`
1561
+ };
1562
+ }
1563
+ const stats = await stat(absolutePath);
1564
+ if (stats.isDirectory()) {
1565
+ return {
1566
+ title: `Path is a directory: ${filePath}`,
1567
+ output: `Error: Path is a directory, not a file: ${absolutePath}`
1568
+ };
1569
+ }
1570
+ const content = await readFile(absolutePath, "utf-8");
1571
+ const lines = content.split("\n");
1572
+ const totalLines = lines.length;
1573
+ const startLine = offset;
1574
+ const endLine = Math.min(startLine + limit, totalLines);
1575
+ const selectedLines = lines.slice(startLine, endLine);
1576
+ const maxLineNum = endLine;
1577
+ const lineNumWidth = String(maxLineNum).length;
1578
+ const formattedLines = selectedLines.map((line, idx) => {
1579
+ const lineNum = startLine + idx + 1;
1580
+ const paddedNum = String(lineNum).padStart(lineNumWidth, " ");
1581
+ const truncatedLine = line.length > MAX_LINE_LENGTH ? line.slice(0, MAX_LINE_LENGTH) + "... (truncated)" : line;
1582
+ return `${paddedNum} ${truncatedLine}`;
1583
+ });
1584
+ let output = formattedLines.join("\n");
1585
+ if (startLine > 0 || endLine < totalLines) {
1586
+ output = `<file>
1587
+ ${output}
1588
+
1589
+ (Showing lines ${startLine + 1}-${endLine} of ${totalLines} total)
1590
+ </file>`;
1591
+ } else {
1592
+ output = `<file>
1593
+ ${output}
1594
+
1595
+ (End of file - total ${totalLines} lines)
1596
+ </file>`;
1597
+ }
1598
+ return {
1599
+ title: `Read ${filePath}`,
1600
+ output,
1601
+ metadata: {
1602
+ path: absolutePath,
1603
+ totalLines,
1604
+ startLine: startLine + 1,
1605
+ endLine
1606
+ }
1607
+ };
1608
+ }
1609
+ async function localWriteExecutor(args, ctx) {
1610
+ const { filePath, content } = args;
1611
+ const absolutePath = isAbsolute(filePath) ? filePath : resolve(ctx.workingDirectory ?? process.cwd(), filePath);
1612
+ const existed = existsSync(absolutePath);
1613
+ const dir = dirname(absolutePath);
1614
+ if (!existsSync(dir)) {
1615
+ await mkdir(dir, { recursive: true });
1616
+ }
1617
+ await writeFile(absolutePath, content, "utf-8");
1618
+ const lines = content.split("\n").length;
1619
+ return {
1620
+ title: existed ? `Updated ${filePath}` : `Created ${filePath}`,
1621
+ output: `Successfully ${existed ? "updated" : "created"} file: ${absolutePath}
1622
+
1623
+ File contains ${lines} lines.`,
1624
+ metadata: {
1625
+ path: absolutePath,
1626
+ lines,
1627
+ created: !existed
1628
+ }
1629
+ };
1630
+ }
1631
+
1632
+ // src/tool/local/edit-replacers.ts
1633
+ var SINGLE_CANDIDATE_SIMILARITY_THRESHOLD = 0;
1634
+ var MULTIPLE_CANDIDATES_SIMILARITY_THRESHOLD = 0.3;
1635
+ function levenshtein(a, b) {
1636
+ if (a === "" || b === "") {
1637
+ return Math.max(a.length, b.length);
1638
+ }
1639
+ const matrix = Array.from(
1640
+ { length: a.length + 1 },
1641
+ (_, i) => Array.from({ length: b.length + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0)
1642
+ );
1643
+ for (let i = 1; i <= a.length; i++) {
1644
+ for (let j = 1; j <= b.length; j++) {
1645
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
1646
+ matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
1647
+ }
1648
+ }
1649
+ return matrix[a.length][b.length];
1650
+ }
1651
+ var SimpleReplacer = function* (_content, find) {
1652
+ yield find;
1653
+ };
1654
+ var LineTrimmedReplacer = function* (content, find) {
1655
+ const originalLines = content.split("\n");
1656
+ const searchLines = find.split("\n");
1657
+ if (searchLines[searchLines.length - 1] === "") {
1658
+ searchLines.pop();
1659
+ }
1660
+ for (let i = 0; i <= originalLines.length - searchLines.length; i++) {
1661
+ let matches = true;
1662
+ for (let j = 0; j < searchLines.length; j++) {
1663
+ const originalTrimmed = originalLines[i + j].trim();
1664
+ const searchTrimmed = searchLines[j].trim();
1665
+ if (originalTrimmed !== searchTrimmed) {
1666
+ matches = false;
1667
+ break;
1668
+ }
1669
+ }
1670
+ if (matches) {
1671
+ let matchStartIndex = 0;
1672
+ for (let k = 0; k < i; k++) {
1673
+ matchStartIndex += originalLines[k].length + 1;
1674
+ }
1675
+ let matchEndIndex = matchStartIndex;
1676
+ for (let k = 0; k < searchLines.length; k++) {
1677
+ matchEndIndex += originalLines[i + k].length;
1678
+ if (k < searchLines.length - 1) {
1679
+ matchEndIndex += 1;
1680
+ }
1681
+ }
1682
+ yield content.substring(matchStartIndex, matchEndIndex);
1683
+ }
1684
+ }
1685
+ };
1686
+ var BlockAnchorReplacer = function* (content, find) {
1687
+ const originalLines = content.split("\n");
1688
+ const searchLines = find.split("\n");
1689
+ if (searchLines.length < 3) {
1690
+ return;
1691
+ }
1692
+ if (searchLines[searchLines.length - 1] === "") {
1693
+ searchLines.pop();
1694
+ }
1695
+ const firstLineSearch = searchLines[0].trim();
1696
+ const lastLineSearch = searchLines[searchLines.length - 1].trim();
1697
+ const searchBlockSize = searchLines.length;
1698
+ const candidates = [];
1699
+ for (let i = 0; i < originalLines.length; i++) {
1700
+ if (originalLines[i].trim() !== firstLineSearch) {
1701
+ continue;
1702
+ }
1703
+ for (let j = i + 2; j < originalLines.length; j++) {
1704
+ if (originalLines[j].trim() === lastLineSearch) {
1705
+ candidates.push({ startLine: i, endLine: j });
1706
+ break;
1707
+ }
1708
+ }
1709
+ }
1710
+ if (candidates.length === 0) {
1711
+ return;
1712
+ }
1713
+ if (candidates.length === 1) {
1714
+ const { startLine, endLine } = candidates[0];
1715
+ const actualBlockSize = endLine - startLine + 1;
1716
+ let similarity = 0;
1717
+ const linesToCheck = Math.min(searchBlockSize - 2, actualBlockSize - 2);
1718
+ if (linesToCheck > 0) {
1719
+ for (let j = 1; j < searchBlockSize - 1 && j < actualBlockSize - 1; j++) {
1720
+ const originalLine = originalLines[startLine + j].trim();
1721
+ const searchLine = searchLines[j].trim();
1722
+ const maxLen = Math.max(originalLine.length, searchLine.length);
1723
+ if (maxLen === 0) {
1724
+ continue;
1725
+ }
1726
+ const distance = levenshtein(originalLine, searchLine);
1727
+ similarity += (1 - distance / maxLen) / linesToCheck;
1728
+ if (similarity >= SINGLE_CANDIDATE_SIMILARITY_THRESHOLD) {
1729
+ break;
1730
+ }
1731
+ }
1732
+ } else {
1733
+ similarity = 1;
1734
+ }
1735
+ if (similarity >= SINGLE_CANDIDATE_SIMILARITY_THRESHOLD) {
1736
+ let matchStartIndex = 0;
1737
+ for (let k = 0; k < startLine; k++) {
1738
+ matchStartIndex += originalLines[k].length + 1;
1739
+ }
1740
+ let matchEndIndex = matchStartIndex;
1741
+ for (let k = startLine; k <= endLine; k++) {
1742
+ matchEndIndex += originalLines[k].length;
1743
+ if (k < endLine) {
1744
+ matchEndIndex += 1;
1745
+ }
1746
+ }
1747
+ yield content.substring(matchStartIndex, matchEndIndex);
1748
+ }
1749
+ return;
1750
+ }
1751
+ let bestMatch = null;
1752
+ let maxSimilarity = -1;
1753
+ for (const candidate of candidates) {
1754
+ const { startLine, endLine } = candidate;
1755
+ const actualBlockSize = endLine - startLine + 1;
1756
+ let similarity = 0;
1757
+ const linesToCheck = Math.min(searchBlockSize - 2, actualBlockSize - 2);
1758
+ if (linesToCheck > 0) {
1759
+ for (let j = 1; j < searchBlockSize - 1 && j < actualBlockSize - 1; j++) {
1760
+ const originalLine = originalLines[startLine + j].trim();
1761
+ const searchLine = searchLines[j].trim();
1762
+ const maxLen = Math.max(originalLine.length, searchLine.length);
1763
+ if (maxLen === 0) {
1764
+ continue;
1765
+ }
1766
+ const distance = levenshtein(originalLine, searchLine);
1767
+ similarity += 1 - distance / maxLen;
1768
+ }
1769
+ similarity /= linesToCheck;
1770
+ } else {
1771
+ similarity = 1;
1772
+ }
1773
+ if (similarity > maxSimilarity) {
1774
+ maxSimilarity = similarity;
1775
+ bestMatch = candidate;
1776
+ }
1777
+ }
1778
+ if (maxSimilarity >= MULTIPLE_CANDIDATES_SIMILARITY_THRESHOLD && bestMatch) {
1779
+ const { startLine, endLine } = bestMatch;
1780
+ let matchStartIndex = 0;
1781
+ for (let k = 0; k < startLine; k++) {
1782
+ matchStartIndex += originalLines[k].length + 1;
1783
+ }
1784
+ let matchEndIndex = matchStartIndex;
1785
+ for (let k = startLine; k <= endLine; k++) {
1786
+ matchEndIndex += originalLines[k].length;
1787
+ if (k < endLine) {
1788
+ matchEndIndex += 1;
1789
+ }
1790
+ }
1791
+ yield content.substring(matchStartIndex, matchEndIndex);
1792
+ }
1793
+ };
1794
+ var WhitespaceNormalizedReplacer = function* (content, find) {
1795
+ const normalizeWhitespace = (text) => text.replace(/\s+/g, " ").trim();
1796
+ const normalizedFind = normalizeWhitespace(find);
1797
+ const lines = content.split("\n");
1798
+ for (let i = 0; i < lines.length; i++) {
1799
+ const line = lines[i];
1800
+ if (normalizeWhitespace(line) === normalizedFind) {
1801
+ yield line;
1802
+ } else {
1803
+ const normalizedLine = normalizeWhitespace(line);
1804
+ if (normalizedLine.includes(normalizedFind)) {
1805
+ const words = find.trim().split(/\s+/);
1806
+ if (words.length > 0) {
1807
+ const pattern = words.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s+");
1808
+ try {
1809
+ const regex = new RegExp(pattern);
1810
+ const match = line.match(regex);
1811
+ if (match) {
1812
+ yield match[0];
1813
+ }
1814
+ } catch {
1815
+ }
1816
+ }
1817
+ }
1818
+ }
1819
+ }
1820
+ const findLines = find.split("\n");
1821
+ if (findLines.length > 1) {
1822
+ for (let i = 0; i <= lines.length - findLines.length; i++) {
1823
+ const block = lines.slice(i, i + findLines.length);
1824
+ if (normalizeWhitespace(block.join("\n")) === normalizedFind) {
1825
+ yield block.join("\n");
1826
+ }
1827
+ }
1828
+ }
1829
+ };
1830
+ var IndentationFlexibleReplacer = function* (content, find) {
1831
+ const removeIndentation = (text) => {
1832
+ const lines = text.split("\n");
1833
+ const nonEmptyLines = lines.filter((line) => line.trim().length > 0);
1834
+ if (nonEmptyLines.length === 0) return text;
1835
+ const minIndent = Math.min(
1836
+ ...nonEmptyLines.map((line) => {
1837
+ const match = line.match(/^(\s*)/);
1838
+ return match ? match[1].length : 0;
1839
+ })
1840
+ );
1841
+ return lines.map((line) => line.trim().length === 0 ? line : line.slice(minIndent)).join("\n");
1842
+ };
1843
+ const normalizedFind = removeIndentation(find);
1844
+ const contentLines = content.split("\n");
1845
+ const findLines = find.split("\n");
1846
+ for (let i = 0; i <= contentLines.length - findLines.length; i++) {
1847
+ const block = contentLines.slice(i, i + findLines.length).join("\n");
1848
+ if (removeIndentation(block) === normalizedFind) {
1849
+ yield block;
1850
+ }
1851
+ }
1852
+ };
1853
+ var EscapeNormalizedReplacer = function* (content, find) {
1854
+ const unescapeString = (str) => {
1855
+ return str.replace(/\\(n|t|r|'|"|`|\\|\n|\$)/g, (match, capturedChar) => {
1856
+ switch (capturedChar) {
1857
+ case "n":
1858
+ return "\n";
1859
+ case "t":
1860
+ return " ";
1861
+ case "r":
1862
+ return "\r";
1863
+ case "'":
1864
+ return "'";
1865
+ case '"':
1866
+ return '"';
1867
+ case "`":
1868
+ return "`";
1869
+ case "\\":
1870
+ return "\\";
1871
+ case "\n":
1872
+ return "\n";
1873
+ case "$":
1874
+ return "$";
1875
+ default:
1876
+ return match;
1877
+ }
1878
+ });
1879
+ };
1880
+ const unescapedFind = unescapeString(find);
1881
+ if (content.includes(unescapedFind)) {
1882
+ yield unescapedFind;
1883
+ }
1884
+ const lines = content.split("\n");
1885
+ const findLines = unescapedFind.split("\n");
1886
+ for (let i = 0; i <= lines.length - findLines.length; i++) {
1887
+ const block = lines.slice(i, i + findLines.length).join("\n");
1888
+ const unescapedBlock = unescapeString(block);
1889
+ if (unescapedBlock === unescapedFind) {
1890
+ yield block;
1891
+ }
1892
+ }
1893
+ };
1894
+ var TrimmedBoundaryReplacer = function* (content, find) {
1895
+ const trimmedFind = find.trim();
1896
+ if (trimmedFind === find) {
1897
+ return;
1898
+ }
1899
+ if (content.includes(trimmedFind)) {
1900
+ yield trimmedFind;
1901
+ }
1902
+ const lines = content.split("\n");
1903
+ const findLines = find.split("\n");
1904
+ for (let i = 0; i <= lines.length - findLines.length; i++) {
1905
+ const block = lines.slice(i, i + findLines.length).join("\n");
1906
+ if (block.trim() === trimmedFind) {
1907
+ yield block;
1908
+ }
1909
+ }
1910
+ };
1911
+ var ContextAwareReplacer = function* (content, find) {
1912
+ const findLines = find.split("\n");
1913
+ if (findLines.length < 3) {
1914
+ return;
1915
+ }
1916
+ if (findLines[findLines.length - 1] === "") {
1917
+ findLines.pop();
1918
+ }
1919
+ const contentLines = content.split("\n");
1920
+ const firstLine = findLines[0].trim();
1921
+ const lastLine = findLines[findLines.length - 1].trim();
1922
+ for (let i = 0; i < contentLines.length; i++) {
1923
+ if (contentLines[i].trim() !== firstLine) continue;
1924
+ for (let j = i + 2; j < contentLines.length; j++) {
1925
+ if (contentLines[j].trim() === lastLine) {
1926
+ const blockLines = contentLines.slice(i, j + 1);
1927
+ const block = blockLines.join("\n");
1928
+ if (blockLines.length === findLines.length) {
1929
+ let matchingLines = 0;
1930
+ let totalNonEmptyLines = 0;
1931
+ for (let k = 1; k < blockLines.length - 1; k++) {
1932
+ const blockLine = blockLines[k].trim();
1933
+ const findLine = findLines[k].trim();
1934
+ if (blockLine.length > 0 || findLine.length > 0) {
1935
+ totalNonEmptyLines++;
1936
+ if (blockLine === findLine) {
1937
+ matchingLines++;
1938
+ }
1939
+ }
1940
+ }
1941
+ if (totalNonEmptyLines === 0 || matchingLines / totalNonEmptyLines >= 0.5) {
1942
+ yield block;
1943
+ break;
1944
+ }
1945
+ }
1946
+ break;
1947
+ }
1948
+ }
1949
+ }
1950
+ };
1951
+ var MultiOccurrenceReplacer = function* (content, find) {
1952
+ let startIndex = 0;
1953
+ while (true) {
1954
+ const index = content.indexOf(find, startIndex);
1955
+ if (index === -1) break;
1956
+ yield find;
1957
+ startIndex = index + find.length;
1958
+ }
1959
+ };
1960
+ var ALL_REPLACERS = [
1961
+ SimpleReplacer,
1962
+ LineTrimmedReplacer,
1963
+ BlockAnchorReplacer,
1964
+ WhitespaceNormalizedReplacer,
1965
+ IndentationFlexibleReplacer,
1966
+ EscapeNormalizedReplacer,
1967
+ TrimmedBoundaryReplacer,
1968
+ ContextAwareReplacer,
1969
+ MultiOccurrenceReplacer
1970
+ ];
1971
+ function smartReplace(content, oldString, newString, replaceAll = false) {
1972
+ if (oldString === newString) {
1973
+ throw new Error("oldString and newString must be different");
1974
+ }
1975
+ let notFound = true;
1976
+ for (const replacer of ALL_REPLACERS) {
1977
+ for (const search of replacer(content, oldString)) {
1978
+ const index = content.indexOf(search);
1979
+ if (index === -1) continue;
1980
+ notFound = false;
1981
+ if (replaceAll) {
1982
+ return {
1983
+ newContent: content.replaceAll(search, newString),
1984
+ matchedString: search
1985
+ };
1986
+ }
1987
+ const lastIndex = content.lastIndexOf(search);
1988
+ if (index !== lastIndex) continue;
1989
+ return {
1990
+ newContent: content.substring(0, index) + newString + content.substring(index + search.length),
1991
+ matchedString: search
1992
+ };
1993
+ }
1994
+ }
1995
+ if (notFound) {
1996
+ throw new Error("oldString not found in file content");
1997
+ }
1998
+ throw new Error(
1999
+ "Found multiple matches for oldString. Provide more surrounding lines in oldString to identify the correct match."
2000
+ );
2001
+ }
2002
+
2003
+ // src/tool/local/edit.ts
2004
+ async function localEditExecutor(args, ctx) {
2005
+ const { filePath, oldString, newString, replaceAll = false } = args;
2006
+ const absolutePath = isAbsolute(filePath) ? filePath : resolve(ctx.workingDirectory ?? process.cwd(), filePath);
2007
+ if (!existsSync(absolutePath)) {
2008
+ return {
2009
+ title: `File not found: ${filePath}`,
2010
+ output: `Error: File does not exist at path: ${absolutePath}`
2011
+ };
2012
+ }
2013
+ const content = await readFile(absolutePath, "utf-8");
2014
+ try {
2015
+ const { newContent, matchedString } = smartReplace(content, oldString, newString, replaceAll);
2016
+ await writeFile(absolutePath, newContent, "utf-8");
2017
+ const oldLines = content.split("\n").length;
2018
+ const newLines = newContent.split("\n").length;
2019
+ const lineDiff = newLines - oldLines;
2020
+ let output = `Successfully edited ${absolutePath}`;
2021
+ if (matchedString !== oldString) {
2022
+ output += `
2023
+
2024
+ Note: Used fuzzy matching to find the text.`;
2025
+ }
2026
+ output += `
2027
+
2028
+ Lines: ${oldLines} -> ${newLines} (${lineDiff >= 0 ? "+" : ""}${lineDiff})`;
2029
+ return {
2030
+ title: `Edited ${filePath}`,
2031
+ output,
2032
+ metadata: {
2033
+ path: absolutePath,
2034
+ oldLines,
2035
+ newLines,
2036
+ lineDiff,
2037
+ fuzzyMatch: matchedString !== oldString
2038
+ }
2039
+ };
2040
+ } catch (error) {
2041
+ const message = error instanceof Error ? error.message : String(error);
2042
+ return {
2043
+ title: `Edit failed: ${filePath}`,
2044
+ output: `Error: ${message}`
2045
+ };
2046
+ }
2047
+ }
2048
+ var MAX_RESULTS = 500;
2049
+ async function localGlobExecutor(args, ctx) {
2050
+ const { pattern, path } = args;
2051
+ const basePath = path ? isAbsolute(path) ? path : resolve(ctx.workingDirectory ?? process.cwd(), path) : ctx.workingDirectory ?? process.cwd();
2052
+ try {
2053
+ const files = await glob(pattern, {
2054
+ cwd: basePath,
2055
+ nodir: true,
2056
+ absolute: true,
2057
+ ignore: ["**/node_modules/**", "**/.git/**"]
2058
+ });
2059
+ if (files.length === 0) {
2060
+ return {
2061
+ title: `No files found matching: ${pattern}`,
2062
+ output: `No files found matching pattern "${pattern}" in ${basePath}`
2063
+ };
2064
+ }
2065
+ const filesWithStats = await Promise.all(
2066
+ files.slice(0, MAX_RESULTS).map(async (file) => {
2067
+ try {
2068
+ const stats = await stat(file);
2069
+ return { file, mtime: stats.mtime };
2070
+ } catch {
2071
+ return { file, mtime: /* @__PURE__ */ new Date(0) };
2072
+ }
2073
+ })
2074
+ );
2075
+ filesWithStats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
2076
+ const sortedFiles = filesWithStats.map((f) => f.file);
2077
+ let output = sortedFiles.join("\n");
2078
+ if (files.length > MAX_RESULTS) {
2079
+ output += `
2080
+
2081
+ (Showing ${MAX_RESULTS} of ${files.length} files)`;
2082
+ }
2083
+ return {
2084
+ title: `Found ${files.length} files matching: ${pattern}`,
2085
+ output,
2086
+ metadata: {
2087
+ count: files.length,
2088
+ pattern,
2089
+ basePath,
2090
+ truncated: files.length > MAX_RESULTS
2091
+ }
2092
+ };
2093
+ } catch (error) {
2094
+ const message = error instanceof Error ? error.message : String(error);
2095
+ return {
2096
+ title: `Glob error: ${pattern}`,
2097
+ output: `Error searching for files: ${message}`
2098
+ };
2099
+ }
2100
+ }
2101
+ var MAX_FILES = 100;
2102
+ var MAX_MATCHES_PER_FILE = 10;
2103
+ var MAX_TOTAL_MATCHES = 200;
2104
+ async function localGrepExecutor(args, ctx) {
2105
+ const { pattern, path, include } = args;
2106
+ const basePath = path ? isAbsolute(path) ? path : resolve(ctx.workingDirectory ?? process.cwd(), path) : ctx.workingDirectory ?? process.cwd();
2107
+ try {
2108
+ const regex = new RegExp(pattern, "g");
2109
+ const filePattern = include ?? "**/*";
2110
+ const files = await glob(filePattern, {
2111
+ cwd: basePath,
2112
+ nodir: true,
2113
+ absolute: true,
2114
+ ignore: ["**/node_modules/**", "**/.git/**", "**/*.lock", "**/*.min.js", "**/*.min.css"]
2115
+ });
2116
+ const matches = [];
2117
+ let filesSearched = 0;
2118
+ let totalMatches = 0;
2119
+ for (const file of files.slice(0, MAX_FILES)) {
2120
+ if (totalMatches >= MAX_TOTAL_MATCHES) break;
2121
+ try {
2122
+ const stats = await stat(file);
2123
+ if (stats.size > 1024 * 1024) continue;
2124
+ const content = await readFile(file, "utf-8");
2125
+ const lines = content.split("\n");
2126
+ let fileMatches = 0;
2127
+ for (let i = 0; i < lines.length; i++) {
2128
+ if (fileMatches >= MAX_MATCHES_PER_FILE) break;
2129
+ if (totalMatches >= MAX_TOTAL_MATCHES) break;
2130
+ const line = lines[i];
2131
+ if (regex.test(line)) {
2132
+ matches.push({
2133
+ file,
2134
+ line: i + 1,
2135
+ content: line.slice(0, 200) + (line.length > 200 ? "..." : ""),
2136
+ mtime: stats.mtime
2137
+ });
2138
+ fileMatches++;
2139
+ totalMatches++;
2140
+ }
2141
+ regex.lastIndex = 0;
2142
+ }
2143
+ filesSearched++;
2144
+ } catch {
2145
+ }
2146
+ }
2147
+ if (matches.length === 0) {
2148
+ return {
2149
+ title: `No matches found for: ${pattern}`,
2150
+ output: `No matches found for pattern "${pattern}" in ${basePath}
2151
+
2152
+ Searched ${filesSearched} files.`
2153
+ };
2154
+ }
2155
+ matches.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
2156
+ const output = matches.map((m) => `${m.file}:${m.line}: ${m.content}`).join("\n");
2157
+ return {
2158
+ title: `Found ${matches.length} matches for: ${pattern}`,
2159
+ output: output + (totalMatches >= MAX_TOTAL_MATCHES ? `
2160
+
2161
+ (Showing first ${MAX_TOTAL_MATCHES} matches)` : ""),
2162
+ metadata: {
2163
+ matches: matches.length,
2164
+ filesSearched,
2165
+ pattern,
2166
+ basePath,
2167
+ truncated: totalMatches >= MAX_TOTAL_MATCHES
2168
+ }
2169
+ };
2170
+ } catch (error) {
2171
+ const message = error instanceof Error ? error.message : String(error);
2172
+ return {
2173
+ title: `Grep error: ${pattern}`,
2174
+ output: `Error searching files: ${message}`
2175
+ };
2176
+ }
2177
+ }
2178
+
2179
+ // src/tool/builtin/read.ts
2180
+ var readTool = defineTool({
2181
+ id: "read",
2182
+ description: `Read a file from the filesystem.
2183
+
2184
+ Usage:
2185
+ - The filePath parameter must be an absolute path, not a relative path
2186
+ - By default, it reads up to 2000 lines starting from the beginning
2187
+ - You can optionally specify a line offset and limit for long files
2188
+ - Results are returned with line numbers (cat -n format)`,
2189
+ parameters: z.object({
2190
+ filePath: z.string().describe("The absolute path to the file to read"),
2191
+ offset: z.number().optional().describe("The line number to start reading from (0-based)"),
2192
+ limit: z.number().optional().describe("The number of lines to read (defaults to 2000)")
2193
+ }),
2194
+ execute: async (args, ctx) => {
2195
+ return localReadExecutor(args, ctx);
2196
+ }
2197
+ });
2198
+ var writeTool = defineTool({
2199
+ id: "write",
2200
+ description: `Write content to a file. Creates the file if it doesn't exist, or overwrites if it does.
2201
+
2202
+ Usage:
2203
+ - ALWAYS prefer editing existing files with the 'edit' tool
2204
+ - Only use this for creating new files or complete rewrites
2205
+ - The directory will be created if it doesn't exist`,
2206
+ parameters: z.object({
2207
+ filePath: z.string().describe("The absolute path to the file to write"),
2208
+ content: z.string().describe("The content to write to the file")
2209
+ }),
2210
+ execute: async (args, ctx) => {
2211
+ return localWriteExecutor(args, ctx);
2212
+ }
2213
+ });
2214
+ var editTool = defineTool({
2215
+ id: "edit",
2216
+ description: `Edit a file by replacing specific text.
2217
+
2218
+ Usage:
2219
+ - You must read the file first before editing
2220
+ - Provide the exact text to find (oldString) and the replacement text (newString)
2221
+ - The edit uses smart matching to handle minor whitespace/indentation differences
2222
+ - Use replaceAll to replace all occurrences of the text`,
2223
+ parameters: z.object({
2224
+ filePath: z.string().describe("The absolute path to the file to modify"),
2225
+ oldString: z.string().describe("The text to replace"),
2226
+ newString: z.string().describe("The text to replace it with"),
2227
+ replaceAll: z.boolean().optional().describe("Replace all occurrences (default false)")
2228
+ }),
2229
+ execute: async (args, ctx) => {
2230
+ return localEditExecutor(args, ctx);
2231
+ }
2232
+ });
2233
+ var globTool = defineTool({
2234
+ id: "glob",
2235
+ description: `Find files matching a glob pattern.
2236
+
2237
+ Usage:
2238
+ - Supports glob patterns like "**/*.js" or "src/**/*.ts"
2239
+ - Returns matching file paths sorted by modification time
2240
+ - Use this to find files by name patterns`,
2241
+ parameters: z.object({
2242
+ pattern: z.string().describe("The glob pattern to match files against"),
2243
+ path: z.string().optional().describe("The directory to search in (defaults to working directory)")
2244
+ }),
2245
+ execute: async (args, ctx) => {
2246
+ return localGlobExecutor(args, ctx);
2247
+ }
2248
+ });
2249
+ var grepTool = defineTool({
2250
+ id: "grep",
2251
+ description: `Search file contents using regular expressions.
2252
+
2253
+ Usage:
2254
+ - Supports full regex syntax (e.g., "log.*Error", "function\\s+\\w+")
2255
+ - Filter files by pattern with the include parameter
2256
+ - Returns file paths and line numbers with matches`,
2257
+ parameters: z.object({
2258
+ pattern: z.string().describe("The regex pattern to search for in file contents"),
2259
+ path: z.string().optional().describe("The directory to search in (defaults to working directory)"),
2260
+ include: z.string().optional().describe('File pattern to include (e.g., "*.js", "*.{ts,tsx}")')
2261
+ }),
2262
+ execute: async (args, ctx) => {
2263
+ return localGrepExecutor(args, ctx);
2264
+ }
2265
+ });
2266
+ var bashTool = defineTool({
2267
+ id: "bash",
2268
+ description: `Execute a bash command and return the output. Use this for system operations.
2269
+
2270
+ IMPORTANT:
2271
+ - Commands will timeout after 2 minutes (120000ms) by default.
2272
+ - For long-running commands (servers, watch processes), add '&' at the end to run in background.
2273
+
2274
+ Examples:
2275
+ - Normal command: npm install
2276
+ - Background command: npm run dev &`,
2277
+ parameters: z.object({
2278
+ command: z.string().describe("The bash command to execute"),
2279
+ workdir: z.string().optional().describe("The working directory to run the command in"),
2280
+ timeout: z.number().optional().describe("Timeout in milliseconds. Default is 120000 (2 minutes)."),
2281
+ description: z.string().optional().describe("A short description of what this command does")
2282
+ }),
2283
+ execute: async (args, ctx) => {
2284
+ return localBashExecutor(args, ctx);
2285
+ }
2286
+ });
2287
+ var webfetchTool = defineTool({
2288
+ id: "webfetch",
2289
+ description: "Fetch content from a URL. Returns the content in the specified format.",
2290
+ parameters: z.object({
2291
+ url: z.string().describe("The URL to fetch"),
2292
+ method: z.enum(["GET", "POST", "PUT", "DELETE"]).optional().describe("HTTP method (default: GET)"),
2293
+ headers: z.record(z.string(), z.string()).optional().describe("Request headers"),
2294
+ body: z.string().optional().describe("Request body (for POST/PUT)"),
2295
+ format: z.enum(["text", "json", "markdown"]).optional().describe("Response format (default: text)"),
2296
+ timeout: z.number().optional().describe("Timeout in milliseconds (default: 30000)")
2297
+ }),
2298
+ execute: async (args, ctx) => {
2299
+ const { url, method, headers, body, format, timeout } = args;
2300
+ const actualMethod = method ?? "GET";
2301
+ const actualFormat = format ?? "text";
2302
+ const actualTimeout = timeout ?? 3e4;
2303
+ await ctx.metadata({ title: `Fetching ${url}` });
2304
+ const controller = new AbortController();
2305
+ const timeoutId = setTimeout(() => controller.abort(), actualTimeout);
2306
+ try {
2307
+ const response = await fetch(url, {
2308
+ method: actualMethod,
2309
+ headers: {
2310
+ "User-Agent": "OpenAgent/1.0",
2311
+ ...headers
2312
+ },
2313
+ body: actualMethod !== "GET" ? body : void 0,
2314
+ signal: controller.signal
2315
+ });
2316
+ clearTimeout(timeoutId);
2317
+ if (!response.ok) {
2318
+ return {
2319
+ title: `HTTP ${response.status}`,
2320
+ output: `HTTP Error: ${response.status} ${response.statusText}`,
2321
+ metadata: {
2322
+ status: response.status,
2323
+ statusText: response.statusText
2324
+ }
2325
+ };
2326
+ }
2327
+ let content;
2328
+ const contentType = response.headers.get("content-type") || "";
2329
+ if (actualFormat === "json" || contentType.includes("application/json")) {
2330
+ const json = await response.json();
2331
+ content = JSON.stringify(json, null, 2);
2332
+ } else {
2333
+ content = await response.text();
2334
+ }
2335
+ const maxLength = 5e4;
2336
+ if (content.length > maxLength) {
2337
+ content = content.substring(0, maxLength) + "\n\n... (truncated)";
2338
+ }
2339
+ return {
2340
+ title: `Fetched ${url}`,
2341
+ output: content,
2342
+ metadata: {
2343
+ status: response.status,
2344
+ contentType,
2345
+ contentLength: content.length
2346
+ }
2347
+ };
2348
+ } catch (error) {
2349
+ clearTimeout(timeoutId);
2350
+ if (error instanceof Error && error.name === "AbortError") {
2351
+ return {
2352
+ title: "Request timeout",
2353
+ output: `Request to ${url} timed out after ${actualTimeout}ms`,
2354
+ metadata: { error: "timeout" }
2355
+ };
2356
+ }
2357
+ const message = error instanceof Error ? error.message : String(error);
2358
+ return {
2359
+ title: "Fetch failed",
2360
+ output: `Failed to fetch ${url}: ${message}`,
2361
+ metadata: { error: message }
2362
+ };
2363
+ }
2364
+ }
2365
+ });
2366
+ var questionTool = defineTool({
2367
+ id: "question",
2368
+ description: "Ask the user a question and wait for their response. Use this when you need clarification or user input.",
2369
+ parameters: z.object({
2370
+ question: z.string().describe("The question to ask the user"),
2371
+ options: z.array(z.object({
2372
+ label: z.string().describe("Short label for the option"),
2373
+ description: z.string().optional().describe("Description of the option")
2374
+ })).optional().describe("Predefined options for the user to choose from"),
2375
+ multiple: z.boolean().optional().describe("Whether the user can select multiple options")
2376
+ }),
2377
+ execute: async (args, ctx) => {
2378
+ const { question, options, multiple } = args;
2379
+ await ctx.metadata({
2380
+ title: "Waiting for user input",
2381
+ metadata: { question, options, multiple }
2382
+ });
2383
+ return {
2384
+ title: "Question asked",
2385
+ output: `[WAITING_FOR_USER_INPUT]
2386
+ Question: ${question}
2387
+ ${options ? `Options: ${options.map((o) => o.label).join(", ")}` : "Free-form input expected"}`,
2388
+ metadata: {
2389
+ question,
2390
+ options,
2391
+ multiple,
2392
+ waitingForInput: true
2393
+ }
2394
+ };
2395
+ }
2396
+ });
2397
+
2398
+ // src/tool/builtin/index.ts
2399
+ var builtinTools = [
2400
+ // 文件操作工具
2401
+ readTool,
2402
+ writeTool,
2403
+ editTool,
2404
+ // 搜索工具
2405
+ globTool,
2406
+ grepTool,
2407
+ // 命令执行
2408
+ bashTool,
2409
+ // 交互和辅助工具
2410
+ webfetchTool,
2411
+ questionTool
2412
+ ];
2413
+ function getBuiltinTools() {
2414
+ return builtinTools;
2415
+ }
2416
+
2417
+ // src/agent.ts
2418
+ var log6 = createLogger("OpenAgent");
2419
+ var DEFAULT_MODELS = {
2420
+ anthropic: {
2421
+ id: "claude-sonnet-4-20250514",
2422
+ contextWindow: 2e5,
2423
+ maxOutput: 8192
2424
+ },
2425
+ openai: {
2426
+ id: "gpt-4o",
2427
+ contextWindow: 128e3,
2428
+ maxOutput: 4096
2429
+ },
2430
+ google: {
2431
+ id: "gemini-1.5-pro",
2432
+ contextWindow: 1e6,
2433
+ maxOutput: 8192
2434
+ }
2435
+ };
2436
+ var OpenAgent = class {
2437
+ constructor(options = {}) {
2438
+ this.options = options;
2439
+ const providerType = options.provider ?? "anthropic";
2440
+ const defaultModel = DEFAULT_MODELS[providerType];
2441
+ this.providerId = providerType;
2442
+ this.modelId = options.model ?? defaultModel.id;
2443
+ this.mode = options.mode ?? "build";
2444
+ this.maxSteps = options.maxSteps ?? 10;
2445
+ this.temperature = options.temperature;
2446
+ this.maxOutputTokens = options.maxOutputTokens;
2447
+ this.workingDirectory = options.workingDirectory ?? process.cwd();
2448
+ this.customTools = options.tools ?? [];
2449
+ this.agent = options.agent ?? {
2450
+ name: "default",
2451
+ systemPrompt: `You are a helpful AI assistant with access to tools.
2452
+
2453
+ When working on tasks:
2454
+ 1. Understand the request fully before acting
2455
+ 2. Use tools to gather information and make changes
2456
+ 3. Be precise and verify your work
2457
+
2458
+ You can read, write, and edit files, search code, and execute commands.`
2459
+ };
2460
+ }
2461
+ providerId;
2462
+ modelId;
2463
+ agent;
2464
+ mode;
2465
+ maxSteps;
2466
+ temperature;
2467
+ maxOutputTokens;
2468
+ workingDirectory;
2469
+ sessionId;
2470
+ initialized = false;
2471
+ customTools = [];
2472
+ /**
2473
+ * 初始化(延迟初始化,首次调用时执行)
2474
+ */
2475
+ async ensureInitialized() {
2476
+ if (this.initialized) return;
2477
+ log6.info("Initializing OpenAgent...");
2478
+ if (this.options.sessionStore) {
2479
+ setSessionStore(this.options.sessionStore);
2480
+ } else {
2481
+ setSessionStore(createMemoryStore());
2482
+ }
2483
+ if (this.options.providerConfig) {
2484
+ configureProvider(this.options.providerConfig);
2485
+ } else if (this.options.apiKey) {
2486
+ const providerType = this.options.provider ?? "anthropic";
2487
+ const defaultModel = DEFAULT_MODELS[providerType];
2488
+ configureProvider({
2489
+ id: providerType,
2490
+ name: providerType.charAt(0).toUpperCase() + providerType.slice(1),
2491
+ type: providerType,
2492
+ options: {
2493
+ apiKey: this.options.apiKey,
2494
+ baseURL: this.options.baseURL,
2495
+ headers: this.options.headers
2496
+ },
2497
+ models: [{
2498
+ id: this.modelId,
2499
+ name: this.modelId,
2500
+ contextWindow: defaultModel.contextWindow,
2501
+ maxOutput: defaultModel.maxOutput,
2502
+ supportsFunctions: true,
2503
+ supportsVision: true,
2504
+ supportsStreaming: true
2505
+ }]
2506
+ });
2507
+ }
2508
+ if (this.options.useBuiltinTools !== false) {
2509
+ registerTools(builtinTools);
2510
+ log6.info("Registered builtin tools", { count: builtinTools.length });
2511
+ }
2512
+ if (this.customTools.length > 0) {
2513
+ registerTools(this.customTools);
2514
+ log6.info("Registered custom tools", { count: this.customTools.length });
2515
+ }
2516
+ registerAgent(this.agent);
2517
+ this.initialized = true;
2518
+ log6.info("OpenAgent initialized", {
2519
+ provider: this.providerId,
2520
+ model: this.modelId,
2521
+ mode: this.mode
2522
+ });
2523
+ }
2524
+ /**
2525
+ * 获取或创建 Session
2526
+ */
2527
+ async getOrCreateSession() {
2528
+ await this.ensureInitialized();
2529
+ if (this.sessionId) {
2530
+ const session2 = await getSession(this.sessionId);
2531
+ if (session2) return this.sessionId;
2532
+ }
2533
+ const session = await createSession({
2534
+ agent: this.agent.name,
2535
+ mode: this.mode
2536
+ });
2537
+ this.sessionId = session.id;
2538
+ return session.id;
2539
+ }
2540
+ /**
2541
+ * 构建 ToolContext
2542
+ */
2543
+ createToolContext(sessionId2, messageId2) {
2544
+ const abortController = new AbortController();
2545
+ return {
2546
+ sessionId: sessionId2,
2547
+ messageId: messageId2,
2548
+ agent: this.agent.name,
2549
+ abort: abortController.signal,
2550
+ workingDirectory: this.workingDirectory,
2551
+ model: {
2552
+ providerId: this.providerId,
2553
+ modelId: this.modelId
2554
+ },
2555
+ metadata: async () => {
2556
+ },
2557
+ ask: async () => {
2558
+ }
2559
+ };
2560
+ }
2561
+ /**
2562
+ * 执行工具调用
2563
+ */
2564
+ async executeTool(toolName, args, ctx) {
2565
+ const tool = getTool(toolName);
2566
+ if (!tool) {
2567
+ return {
2568
+ title: `Unknown tool: ${toolName}`,
2569
+ output: `Error: Tool "${toolName}" is not registered`
2570
+ };
2571
+ }
2572
+ try {
2573
+ return await tool.execute(args, ctx);
2574
+ } catch (error) {
2575
+ const message = error instanceof Error ? error.message : String(error);
2576
+ return {
2577
+ title: `Tool error: ${toolName}`,
2578
+ output: `Error executing tool: ${message}`
2579
+ };
2580
+ }
2581
+ }
2582
+ /**
2583
+ * 非流式对话(带 Agent Loop)
2584
+ */
2585
+ async chat(message, options) {
2586
+ await this.ensureInitialized();
2587
+ const sessionId2 = options?.sessionId ?? await this.getOrCreateSession();
2588
+ await batchSaveMessage({
2589
+ sessionId: sessionId2,
2590
+ role: "USER",
2591
+ parts: [{ type: "TEXT", text: message }]
2592
+ });
2593
+ const msgId = `msg_${Date.now()}`;
2594
+ const ctx = this.createToolContext(sessionId2, msgId);
2595
+ const tools = getAIToolsForAgent(this.agent, ctx, this.mode);
2596
+ let fullText = "";
2597
+ const toolCalls = [];
2598
+ let totalUsage = { input: 0, output: 0 };
2599
+ let totalCost = 0;
2600
+ let finishReason = "unknown";
2601
+ let step = 0;
2602
+ while (step < this.maxSteps) {
2603
+ step++;
2604
+ log6.debug(`Agent loop step ${step}/${this.maxSteps}`);
2605
+ const messages = await getSessionMessages(sessionId2);
2606
+ const modelMessages = await toModelMessages(messages);
2607
+ const result = await stream({
2608
+ providerId: this.providerId,
2609
+ modelId: this.modelId,
2610
+ messages: modelMessages,
2611
+ system: buildSystemPrompt(this.agent),
2612
+ tools,
2613
+ temperature: this.temperature,
2614
+ maxOutputTokens: this.maxOutputTokens,
2615
+ maxSteps: 1
2616
+ // 每次只执行一步,由我们控制循环
2617
+ });
2618
+ let stepText = "";
2619
+ const stepToolCalls = [];
2620
+ for await (const event of result.fullStream) {
2621
+ switch (event.type) {
2622
+ case "text-delta":
2623
+ stepText += event.text;
2624
+ break;
2625
+ case "tool-call":
2626
+ stepToolCalls.push({
2627
+ id: event.toolCallId,
2628
+ name: event.toolName,
2629
+ args: event.args,
2630
+ status: "PENDING"
2631
+ });
2632
+ break;
2633
+ case "finish":
2634
+ finishReason = event.finishReason;
2635
+ totalUsage.input += event.usage.tokens.input;
2636
+ totalUsage.output += event.usage.tokens.output;
2637
+ totalCost += event.usage.cost;
2638
+ break;
2639
+ }
2640
+ }
2641
+ if (stepText) {
2642
+ fullText += (fullText && stepText ? "\n" : "") + stepText;
2643
+ }
2644
+ if (stepToolCalls.length === 0) {
2645
+ if (stepText) {
2646
+ await batchSaveMessage({
2647
+ sessionId: sessionId2,
2648
+ role: "ASSISTANT",
2649
+ parts: [{ type: "TEXT", text: stepText }]
2650
+ });
2651
+ }
2652
+ break;
2653
+ }
2654
+ const assistantParts = [];
2655
+ if (stepText) {
2656
+ assistantParts.push({ type: "TEXT", text: stepText });
2657
+ }
2658
+ for (const call of stepToolCalls) {
2659
+ log6.debug(`Executing tool: ${call.name}`, { args: call.args });
2660
+ const result2 = await this.executeTool(call.name, call.args, ctx);
2661
+ call.output = result2.output;
2662
+ call.status = "COMPLETED";
2663
+ toolCalls.push({
2664
+ name: call.name,
2665
+ input: call.args,
2666
+ output: result2.output,
2667
+ status: "COMPLETED"
2668
+ });
2669
+ assistantParts.push({
2670
+ type: "TOOL",
2671
+ toolName: call.name,
2672
+ toolCallId: call.id,
2673
+ toolInput: call.args,
2674
+ toolOutput: result2.output,
2675
+ toolStatus: "COMPLETED"
2676
+ });
2677
+ }
2678
+ await batchSaveMessage({
2679
+ sessionId: sessionId2,
2680
+ role: "ASSISTANT",
2681
+ parts: assistantParts
2682
+ });
2683
+ }
2684
+ return {
2685
+ text: fullText,
2686
+ toolCalls,
2687
+ usage: totalUsage,
2688
+ cost: totalCost,
2689
+ finishReason,
2690
+ sessionId: sessionId2
2691
+ };
2692
+ }
2693
+ /**
2694
+ * 流式对话
2695
+ */
2696
+ async *stream(message, options) {
2697
+ await this.ensureInitialized();
2698
+ const sessionId2 = options?.sessionId ?? await this.getOrCreateSession();
2699
+ await batchSaveMessage({
2700
+ sessionId: sessionId2,
2701
+ role: "USER",
2702
+ parts: [{ type: "TEXT", text: message }]
2703
+ });
2704
+ const msgId = `msg_${Date.now()}`;
2705
+ const ctx = this.createToolContext(sessionId2, msgId);
2706
+ const tools = getAIToolsForAgent(this.agent, ctx, this.mode);
2707
+ let fullText = "";
2708
+ const toolCalls = [];
2709
+ let totalUsage = { input: 0, output: 0 };
2710
+ let totalCost = 0;
2711
+ let finishReason = "unknown";
2712
+ let step = 0;
2713
+ try {
2714
+ while (step < this.maxSteps) {
2715
+ step++;
2716
+ const messages = await getSessionMessages(sessionId2);
2717
+ const modelMessages = await toModelMessages(messages);
2718
+ const result = await stream({
2719
+ providerId: this.providerId,
2720
+ modelId: this.modelId,
2721
+ messages: modelMessages,
2722
+ system: buildSystemPrompt(this.agent),
2723
+ tools,
2724
+ temperature: this.temperature,
2725
+ maxOutputTokens: this.maxOutputTokens,
2726
+ maxSteps: 1
2727
+ });
2728
+ let stepText = "";
2729
+ const stepToolCalls = [];
2730
+ for await (const event of result.fullStream) {
2731
+ switch (event.type) {
2732
+ case "text-delta":
2733
+ stepText += event.text;
2734
+ yield { type: "text", text: event.text };
2735
+ break;
2736
+ case "reasoning-delta":
2737
+ yield { type: "reasoning", text: event.text };
2738
+ break;
2739
+ case "tool-call":
2740
+ stepToolCalls.push({
2741
+ id: event.toolCallId,
2742
+ name: event.toolName,
2743
+ args: event.args,
2744
+ status: "PENDING"
2745
+ });
2746
+ yield {
2747
+ type: "tool-start",
2748
+ name: event.toolName,
2749
+ input: event.args
2750
+ };
2751
+ break;
2752
+ case "finish":
2753
+ finishReason = event.finishReason;
2754
+ totalUsage.input += event.usage.tokens.input;
2755
+ totalUsage.output += event.usage.tokens.output;
2756
+ totalCost += event.usage.cost;
2757
+ break;
2758
+ case "error":
2759
+ yield { type: "error", error: event.error };
2760
+ return;
2761
+ }
2762
+ }
2763
+ if (stepText) {
2764
+ fullText += (fullText && stepText ? "\n" : "") + stepText;
2765
+ }
2766
+ if (stepToolCalls.length === 0) {
2767
+ if (stepText) {
2768
+ await batchSaveMessage({
2769
+ sessionId: sessionId2,
2770
+ role: "ASSISTANT",
2771
+ parts: [{ type: "TEXT", text: stepText }]
2772
+ });
2773
+ }
2774
+ break;
2775
+ }
2776
+ const assistantParts = [];
2777
+ if (stepText) {
2778
+ assistantParts.push({ type: "TEXT", text: stepText });
2779
+ }
2780
+ for (const call of stepToolCalls) {
2781
+ const toolResult = await this.executeTool(call.name, call.args, ctx);
2782
+ call.output = toolResult.output;
2783
+ call.status = "COMPLETED";
2784
+ toolCalls.push({
2785
+ name: call.name,
2786
+ input: call.args,
2787
+ output: toolResult.output,
2788
+ status: "COMPLETED"
2789
+ });
2790
+ yield {
2791
+ type: "tool-end",
2792
+ name: call.name,
2793
+ output: toolResult.output
2794
+ };
2795
+ assistantParts.push({
2796
+ type: "TOOL",
2797
+ toolName: call.name,
2798
+ toolCallId: call.id,
2799
+ toolInput: call.args,
2800
+ toolOutput: toolResult.output,
2801
+ toolStatus: "COMPLETED"
2802
+ });
2803
+ }
2804
+ await batchSaveMessage({
2805
+ sessionId: sessionId2,
2806
+ role: "ASSISTANT",
2807
+ parts: assistantParts
2808
+ });
2809
+ }
2810
+ yield {
2811
+ type: "done",
2812
+ result: {
2813
+ text: fullText,
2814
+ toolCalls,
2815
+ usage: totalUsage,
2816
+ cost: totalCost,
2817
+ finishReason,
2818
+ sessionId: sessionId2
2819
+ }
2820
+ };
2821
+ } catch (error) {
2822
+ yield { type: "error", error };
2823
+ }
2824
+ }
2825
+ /**
2826
+ * 继续已有会话
2827
+ */
2828
+ continueSession(sessionId2) {
2829
+ this.sessionId = sessionId2;
2830
+ return this;
2831
+ }
2832
+ /**
2833
+ * 开始新会话
2834
+ */
2835
+ newSession() {
2836
+ this.sessionId = void 0;
2837
+ return this;
2838
+ }
2839
+ /**
2840
+ * 添加自定义工具
2841
+ */
2842
+ addTool(tool) {
2843
+ this.customTools.push(tool);
2844
+ if (this.initialized) {
2845
+ registerTools([tool]);
2846
+ }
2847
+ return this;
2848
+ }
2849
+ /**
2850
+ * 设置模式
2851
+ */
2852
+ setMode(mode) {
2853
+ this.mode = mode;
2854
+ return this;
2855
+ }
2856
+ };
2857
+ function createAgent(options) {
2858
+ return new OpenAgent(options);
2859
+ }
2860
+ function anthropic(apiKey, model) {
2861
+ return new OpenAgent({
2862
+ provider: "anthropic",
2863
+ apiKey,
2864
+ model: model ?? "claude-sonnet-4-20250514"
2865
+ });
2866
+ }
2867
+ function openai(apiKey, model) {
2868
+ return new OpenAgent({
2869
+ provider: "openai",
2870
+ apiKey,
2871
+ model: model ?? "gpt-4o"
2872
+ });
2873
+ }
2874
+ function google(apiKey, model) {
2875
+ return new OpenAgent({
2876
+ provider: "google",
2877
+ apiKey,
2878
+ model: model ?? "gemini-1.5-pro"
2879
+ });
2880
+ }
2881
+
2882
+ // src/index.ts
2883
+ var log7 = createLogger("openagent");
2884
+ var initialized = false;
2885
+ async function initOpenAgent(config) {
2886
+ if (initialized) {
2887
+ log7.info("OpenAgent already initialized");
2888
+ return;
2889
+ }
2890
+ log7.info("Initializing OpenAgent...");
2891
+ setSessionStore(createMemoryStore());
2892
+ log7.info("Using memory session store");
2893
+ if (config?.registerBuiltinTools !== false) {
2894
+ registerTools(builtinTools);
2895
+ log7.info("Registered builtin tools", { count: builtinTools.length });
2896
+ }
2897
+ if (config?.providers && config.providers.length > 0) {
2898
+ configureProviders(config.providers);
2899
+ log7.info("Configured providers", { count: config.providers.length });
2900
+ }
2901
+ initialized = true;
2902
+ log7.info("OpenAgent initialized");
2903
+ }
2904
+ function isInitialized() {
2905
+ return initialized;
2906
+ }
2907
+ function resetOpenAgent() {
2908
+ initialized = false;
2909
+ }
2910
+
2911
+ export { AgentNotFoundError, ContextOverflowError, McpConnectionError, MemorySessionStore, ModelNotFoundError, OpenAgent, OpenAgentError, PROVIDERS, PermissionDeniedError, ProviderApiError, SessionBusyError, SessionNotFoundError, TOOL_PERMISSIONS, ToolExecutionError, anthropic, bashTool, batchSaveMessage, batchUpdateSession, buildSystemPrompt, builtinTools, calculateCost, clearAgents, clearTools, configureFromOpenCodeConfig, configureProvider, configureProviders, createAgent, createLogger, createMemoryStore, createMessage, createPart, createSession, defineTool, deleteSession, editTool, estimateJsonTokens, estimateMessageTokens, estimateMessagesTokens, estimateTokens, extractErrorInfo, generateId, getAIToolsForAgent, getAgent, getAllAgentNames, getAllTools, getAllowedToolsForMode, getBuiltinTools, getDefaultAgent, getDefaultAgentNames, getDefaultModel, getLanguageModel, getMessage, getModeChangePrompt, getModelInfo, getProvider, getProviders, getSession, getSessionMessages, getSessionStore, getTool, getToolDescriptions, getToolsForAgent, getToolsForMode, globTool, google, grepTool, hasSessionStore, initOpenAgent, isContextOverflowError, isInitialized, isToolAllowedInMode, listSessions, stream as llmStream, localBashExecutor, localEditExecutor, localGlobExecutor, localGrepExecutor, localReadExecutor, localWriteExecutor, messageId, openai, partId, questionTool, readTool, registerAgent, registerAgents, registerTool, registerTools, resetOpenAgent, resetProviders, sessionId, setSessionStore, smartReplace, toAITool, toAITools, toMessageError, toModelMessages, toolCallId, truncateForAI, truncateSimple, truncateToolOutput, unregisterAgent, unregisterTool, updateMessage, updatePart, updateSession, webfetchTool, writeTool };
2912
+ //# sourceMappingURL=index.mjs.map
2913
+ //# sourceMappingURL=index.mjs.map