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/LICENSE +21 -0
- package/README.md +324 -0
- package/dist/index.d.mts +1504 -0
- package/dist/index.d.ts +1504 -0
- package/dist/index.js +3022 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2913 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +78 -0
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
|