@avasis-ai/synthcode 1.0.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.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +292 -0
  3. package/dist/chunk-53ZOIXM4.js +624 -0
  4. package/dist/chunk-53ZOIXM4.js.map +1 -0
  5. package/dist/chunk-BWXHO6UJ.js +115 -0
  6. package/dist/chunk-BWXHO6UJ.js.map +1 -0
  7. package/dist/chunk-CARUMOML.js +123 -0
  8. package/dist/chunk-CARUMOML.js.map +1 -0
  9. package/dist/chunk-DGUM43GV.js +11 -0
  10. package/dist/chunk-DGUM43GV.js.map +1 -0
  11. package/dist/chunk-F34HO4RA.js +487 -0
  12. package/dist/chunk-F34HO4RA.js.map +1 -0
  13. package/dist/chunk-FK7S2S7V.js +132 -0
  14. package/dist/chunk-FK7S2S7V.js.map +1 -0
  15. package/dist/chunk-MQ7XP6VT.js +174 -0
  16. package/dist/chunk-MQ7XP6VT.js.map +1 -0
  17. package/dist/chunk-TLPOO6C3.js +176 -0
  18. package/dist/chunk-TLPOO6C3.js.map +1 -0
  19. package/dist/chunk-W6OLZ2OI.js +56 -0
  20. package/dist/chunk-W6OLZ2OI.js.map +1 -0
  21. package/dist/cli/index.cjs +151 -0
  22. package/dist/cli/index.cjs.map +1 -0
  23. package/dist/cli/index.d.cts +1 -0
  24. package/dist/cli/index.d.ts +1 -0
  25. package/dist/cli/index.js +8 -0
  26. package/dist/cli/index.js.map +1 -0
  27. package/dist/cli/run.cjs +128 -0
  28. package/dist/cli/run.cjs.map +1 -0
  29. package/dist/cli/run.d.cts +1 -0
  30. package/dist/cli/run.d.ts +1 -0
  31. package/dist/cli/run.js +126 -0
  32. package/dist/cli/run.js.map +1 -0
  33. package/dist/index-D-K6sx8s.d.cts +8 -0
  34. package/dist/index-D-K6sx8s.d.ts +8 -0
  35. package/dist/index.cjs +2909 -0
  36. package/dist/index.cjs.map +1 -0
  37. package/dist/index.d.cts +274 -0
  38. package/dist/index.d.ts +274 -0
  39. package/dist/index.js +1048 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/llm/index.cjs +531 -0
  42. package/dist/llm/index.cjs.map +1 -0
  43. package/dist/llm/index.d.cts +70 -0
  44. package/dist/llm/index.d.ts +70 -0
  45. package/dist/llm/index.js +24 -0
  46. package/dist/llm/index.js.map +1 -0
  47. package/dist/mcp/index.cjs +323 -0
  48. package/dist/mcp/index.cjs.map +1 -0
  49. package/dist/mcp/index.d.cts +39 -0
  50. package/dist/mcp/index.d.ts +39 -0
  51. package/dist/mcp/index.js +11 -0
  52. package/dist/mcp/index.js.map +1 -0
  53. package/dist/memory/index.cjs +146 -0
  54. package/dist/memory/index.cjs.map +1 -0
  55. package/dist/memory/index.d.cts +51 -0
  56. package/dist/memory/index.d.ts +51 -0
  57. package/dist/memory/index.js +10 -0
  58. package/dist/memory/index.js.map +1 -0
  59. package/dist/tools/fuzzy-edit.cjs +200 -0
  60. package/dist/tools/fuzzy-edit.cjs.map +1 -0
  61. package/dist/tools/fuzzy-edit.d.cts +9 -0
  62. package/dist/tools/fuzzy-edit.d.ts +9 -0
  63. package/dist/tools/fuzzy-edit.js +12 -0
  64. package/dist/tools/fuzzy-edit.js.map +1 -0
  65. package/dist/tools/index.cjs +1032 -0
  66. package/dist/tools/index.cjs.map +1 -0
  67. package/dist/tools/index.d.cts +4 -0
  68. package/dist/tools/index.d.ts +4 -0
  69. package/dist/tools/index.js +39 -0
  70. package/dist/tools/index.js.map +1 -0
  71. package/dist/types-C11cw5ZD.d.cts +177 -0
  72. package/dist/types-C11cw5ZD.d.ts +177 -0
  73. package/dist/utils-TF4TBXQJ.js +10 -0
  74. package/dist/utils-TF4TBXQJ.js.map +1 -0
  75. package/dist/web-fetch-B42QzYD2.d.cts +85 -0
  76. package/dist/web-fetch-EDdhxmEf.d.ts +85 -0
  77. package/package.json +134 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,2909 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/utils.ts
34
+ var utils_exports = {};
35
+ __export(utils_exports, {
36
+ globToRegExp: () => globToRegExp,
37
+ walkDir: () => walkDir
38
+ });
39
+ function globToRegExp(pattern) {
40
+ const parts = pattern.split("/");
41
+ let regex = "";
42
+ for (let i = 0; i < parts.length; i++) {
43
+ const part = parts[i];
44
+ if (part === "**") {
45
+ regex += "(?:[^/]*(?:\\/|$))*";
46
+ } else {
47
+ const escaped = part.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
48
+ regex += escaped + "\\/";
49
+ }
50
+ }
51
+ regex = regex.replace(/\\\//g, "/").replace(/\/$/, "");
52
+ return new RegExp(`(^|/)${regex}(/|$)`);
53
+ }
54
+ async function walkDir(dir, options) {
55
+ const maxDepth = options?.maxDepth ?? Infinity;
56
+ const followSymlinks = options?.followSymlinks ?? false;
57
+ const results = [];
58
+ async function walk(current, depth) {
59
+ if (depth > maxDepth) return;
60
+ let entries;
61
+ try {
62
+ entries = await fs4.promises.readdir(current, { withFileTypes: true });
63
+ } catch {
64
+ return;
65
+ }
66
+ for (const entry of entries) {
67
+ const fullPath = path4.join(current, entry.name);
68
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
69
+ try {
70
+ let stat;
71
+ if (entry.isSymbolicLink() && !followSymlinks) continue;
72
+ stat = await fs4.promises.stat(fullPath);
73
+ if (stat.isFile()) {
74
+ results.push({ path: fullPath, stat });
75
+ } else if (stat.isDirectory()) {
76
+ await walk(fullPath, depth + 1);
77
+ }
78
+ } catch {
79
+ continue;
80
+ }
81
+ }
82
+ }
83
+ await walk(dir, 0);
84
+ return results;
85
+ }
86
+ var fs4, path4;
87
+ var init_utils = __esm({
88
+ "src/utils.ts"() {
89
+ "use strict";
90
+ fs4 = __toESM(require("fs"), 1);
91
+ path4 = __toESM(require("path"), 1);
92
+ }
93
+ });
94
+
95
+ // src/index.ts
96
+ var src_exports = {};
97
+ __export(src_exports, {
98
+ Agent: () => Agent,
99
+ AnthropicProvider: () => AnthropicProvider,
100
+ BaseProvider: () => BaseProvider,
101
+ BashTool: () => BashTool,
102
+ ContextManager: () => ContextManager,
103
+ CostTracker: () => CostTracker,
104
+ DEFAULT_COMPACT_THRESHOLD: () => DEFAULT_COMPACT_THRESHOLD,
105
+ DEFAULT_CONTEXT_WINDOW: () => DEFAULT_CONTEXT_WINDOW,
106
+ DEFAULT_MAX_OUTPUT_TOKENS: () => DEFAULT_MAX_OUTPUT_TOKENS,
107
+ DEFAULT_MAX_TURNS: () => DEFAULT_MAX_TURNS,
108
+ DEFAULT_PRICING: () => DEFAULT_PRICING,
109
+ FileEditTool: () => FileEditTool,
110
+ FileReadTool: () => FileReadTool,
111
+ FileWriteTool: () => FileWriteTool,
112
+ GlobTool: () => GlobTool,
113
+ GrepTool: () => GrepTool,
114
+ HookRunner: () => HookRunner,
115
+ InMemoryStore: () => InMemoryStore,
116
+ MAX_CONCURRENT_TOOLS: () => MAX_CONCURRENT_TOOLS,
117
+ MCPClient: () => MCPClient,
118
+ OllamaProvider: () => OllamaProvider,
119
+ OpenAIProvider: () => OpenAIProvider,
120
+ PermissionEngine: () => PermissionEngine,
121
+ RetryableError: () => RetryableError,
122
+ SQLiteStore: () => SQLiteStore,
123
+ ToolRegistry: () => ToolRegistry,
124
+ WebFetchTool: () => WebFetchTool,
125
+ agentLoop: () => agentLoop,
126
+ anthropic: () => anthropic,
127
+ createProvider: () => createProvider,
128
+ createStreamAggregator: () => createStreamAggregator,
129
+ defineTool: () => defineTool,
130
+ defineToolFromClass: () => defineToolFromClass,
131
+ estimateConversationTokens: () => estimateConversationTokens,
132
+ estimateMessageTokens: () => estimateMessageTokens,
133
+ estimateTokens: () => estimateTokens,
134
+ init: () => init,
135
+ loadMCPTools: () => loadMCPTools,
136
+ ollama: () => ollama,
137
+ openai: () => openai,
138
+ orchestrateTools: () => orchestrateTools
139
+ });
140
+ module.exports = __toCommonJS(src_exports);
141
+
142
+ // src/types.ts
143
+ var DEFAULT_CONTEXT_WINDOW = 2e5;
144
+ var DEFAULT_MAX_OUTPUT_TOKENS = 16384;
145
+ var DEFAULT_COMPACT_THRESHOLD = 0.85;
146
+ var DEFAULT_MAX_TURNS = 100;
147
+ var MAX_CONCURRENT_TOOLS = 10;
148
+
149
+ // src/tools/orchestrator.ts
150
+ var Semaphore = class {
151
+ constructor(max) {
152
+ this.max = max;
153
+ }
154
+ max;
155
+ current = 0;
156
+ queue = [];
157
+ acquire() {
158
+ if (this.current < this.max) {
159
+ this.current++;
160
+ return Promise.resolve();
161
+ }
162
+ return new Promise((resolve) => {
163
+ this.queue.push(resolve);
164
+ });
165
+ }
166
+ release() {
167
+ this.current--;
168
+ const next = this.queue.shift();
169
+ if (next) {
170
+ this.current++;
171
+ next();
172
+ }
173
+ }
174
+ };
175
+ function partitionToolCalls(toolCalls, registry) {
176
+ const batches = [];
177
+ let currentBatch = [];
178
+ let currentBatchIsConcurrent = false;
179
+ for (const call of toolCalls) {
180
+ const tool = registry.get(call.name);
181
+ const isConcurrent = tool ? tool.isReadOnly && tool.isConcurrencySafe : false;
182
+ if (isConcurrent && currentBatchIsConcurrent) {
183
+ currentBatch.push(call);
184
+ } else {
185
+ if (currentBatch.length > 0) {
186
+ batches.push(currentBatch);
187
+ }
188
+ currentBatch = [call];
189
+ currentBatchIsConcurrent = isConcurrent;
190
+ }
191
+ }
192
+ if (currentBatch.length > 0) {
193
+ batches.push(currentBatch);
194
+ }
195
+ return batches;
196
+ }
197
+ async function executeToolCall(call, registry, context, permissionCheck, abortSignal) {
198
+ if (abortSignal?.aborted) {
199
+ return {
200
+ id: call.id,
201
+ name: call.name,
202
+ output: "Execution aborted",
203
+ isError: true,
204
+ durationMs: 0
205
+ };
206
+ }
207
+ const tool = registry.get(call.name);
208
+ if (!tool) {
209
+ return {
210
+ id: call.id,
211
+ name: call.name,
212
+ output: `Unknown tool: ${call.name}`,
213
+ isError: true,
214
+ durationMs: 0
215
+ };
216
+ }
217
+ const start = performance.now();
218
+ try {
219
+ const parseResult = tool.inputSchema.safeParse(call.input);
220
+ if (!parseResult.success) {
221
+ return {
222
+ id: call.id,
223
+ name: call.name,
224
+ output: `Invalid input for ${call.name}: ${parseResult.error.issues.map((i) => i.message).join(", ")}`,
225
+ isError: true,
226
+ durationMs: Math.round(performance.now() - start)
227
+ };
228
+ }
229
+ if (permissionCheck) {
230
+ const allowed = await permissionCheck(call.name, call.input);
231
+ if (!allowed) {
232
+ return {
233
+ id: call.id,
234
+ name: call.name,
235
+ output: `Permission denied for tool: ${call.name}`,
236
+ isError: true,
237
+ durationMs: Math.round(performance.now() - start)
238
+ };
239
+ }
240
+ }
241
+ const output = await tool.execute(parseResult.data, context);
242
+ return {
243
+ id: call.id,
244
+ name: call.name,
245
+ output,
246
+ isError: false,
247
+ durationMs: Math.round(performance.now() - start)
248
+ };
249
+ } catch (err) {
250
+ const message = err instanceof Error ? err.message : String(err);
251
+ return {
252
+ id: call.id,
253
+ name: call.name,
254
+ output: `Error executing ${call.name}: ${message}`,
255
+ isError: true,
256
+ durationMs: Math.round(performance.now() - start)
257
+ };
258
+ }
259
+ }
260
+ async function orchestrateTools(tools, toolCalls, context, permissionCheck, abortSignal) {
261
+ const batches = partitionToolCalls(toolCalls, tools);
262
+ const results = [];
263
+ for (const batch of batches) {
264
+ if (abortSignal?.aborted) {
265
+ for (const call of batch) {
266
+ results.push({
267
+ id: call.id,
268
+ name: call.name,
269
+ output: "Execution aborted",
270
+ isError: true,
271
+ durationMs: 0
272
+ });
273
+ }
274
+ continue;
275
+ }
276
+ if (batch.length > 1) {
277
+ const semaphore = new Semaphore(10);
278
+ const promises2 = batch.map(
279
+ (call) => semaphore.acquire().then(async () => {
280
+ try {
281
+ return await executeToolCall(call, tools, context, permissionCheck, abortSignal);
282
+ } finally {
283
+ semaphore.release();
284
+ }
285
+ })
286
+ );
287
+ const settled = await Promise.allSettled(promises2);
288
+ for (let i = 0; i < settled.length; i++) {
289
+ const s = settled[i];
290
+ if (s.status === "fulfilled") {
291
+ results.push(s.value);
292
+ } else {
293
+ results.push({
294
+ id: batch[i].id,
295
+ name: batch[i].name,
296
+ output: `Unexpected error: ${s.reason instanceof Error ? s.reason.message : String(s.reason)}`,
297
+ isError: true,
298
+ durationMs: 0
299
+ });
300
+ }
301
+ }
302
+ } else {
303
+ const result = await executeToolCall(batch[0], tools, context, permissionCheck, abortSignal);
304
+ results.push(result);
305
+ }
306
+ }
307
+ return results;
308
+ }
309
+
310
+ // src/hooks.ts
311
+ var HookRunner = class {
312
+ hooks;
313
+ constructor(hooks) {
314
+ this.hooks = hooks ?? {};
315
+ }
316
+ async runOnTurnStart(turn, messages) {
317
+ if (!this.hooks.onTurnStart) return messages;
318
+ const result = await this.hooks.onTurnStart(turn, messages);
319
+ return Array.isArray(result) ? result : messages;
320
+ }
321
+ async runOnTurnEnd(turn, messages) {
322
+ await this.hooks.onTurnEnd?.(turn, messages);
323
+ }
324
+ async runOnToolUse(name, input) {
325
+ if (!this.hooks.onToolUse) return { allow: true, input };
326
+ const result = await this.hooks.onToolUse(name, input);
327
+ return {
328
+ allow: result?.allow ?? true,
329
+ input: result?.input ?? input
330
+ };
331
+ }
332
+ async runOnToolResult(result) {
333
+ if (!this.hooks.onToolResult) return result.output;
334
+ const modified = await this.hooks.onToolResult(result);
335
+ return modified ?? result.output;
336
+ }
337
+ async runOnError(error, turn) {
338
+ if (!this.hooks.onError) return { retry: false };
339
+ const result = await this.hooks.onError(error, turn);
340
+ return { retry: result?.retry ?? false, message: result?.message };
341
+ }
342
+ async runOnCompact(result) {
343
+ await this.hooks.onCompact?.(result);
344
+ }
345
+ };
346
+
347
+ // src/loop.ts
348
+ var DEFAULT_MAX_RETRIES = 5;
349
+ var INITIAL_RETRY_DELAY_MS = 1e3;
350
+ var MAX_RETRY_DELAY_MS = 3e4;
351
+ var DOOM_LOOP_THRESHOLD = 3;
352
+ var PRE_OVERFLOW_BUFFER = 2e4;
353
+ var OUTPUT_MAX_LINES = 2e3;
354
+ var OUTPUT_MAX_BYTES = 5e4;
355
+ function parseRetryAfterMs(error) {
356
+ const msg = error.message ?? "";
357
+ const retryAfterMsMatch = msg.match(/retry-after-ms:\s*(\d+)/i);
358
+ if (retryAfterMsMatch) return parseInt(retryAfterMsMatch[1], 10);
359
+ const retryAfterMatch = msg.match(/retry-after:\s*(\d+)/i);
360
+ if (retryAfterMatch) return parseInt(retryAfterMatch[1], 10) * 1e3;
361
+ return null;
362
+ }
363
+ function truncateOutput(output, toolName, hasSubAgent) {
364
+ const lines = output.split("\n");
365
+ if (lines.length <= OUTPUT_MAX_LINES && output.length <= OUTPUT_MAX_BYTES) return output;
366
+ let truncated = "";
367
+ let byteCount = 0;
368
+ for (const line of lines) {
369
+ if (byteCount + line.length > OUTPUT_MAX_BYTES) break;
370
+ truncated += line + "\n";
371
+ byteCount += line.length;
372
+ if (byteCount > OUTPUT_MAX_BYTES) break;
373
+ }
374
+ const lineCount = truncated.split("\n").filter((l) => l.length > 0).length;
375
+ const suffix = hasSubAgent ? `
376
+
377
+ [Output truncated: ${lineCount}/${lines.length} lines shown. Use the Task tool to have a sub-agent process the full output.]` : `
378
+
379
+ [Output truncated: ${lineCount}/${lines.length} lines shown. Use Grep to search or Read with offset/limit for the full content.]`;
380
+ return truncated.trimEnd() + suffix;
381
+ }
382
+ function detectDoomLoop(messages) {
383
+ const recentToolCalls = [];
384
+ for (let i = messages.length - 1; i >= 0; i--) {
385
+ const msg = messages[i];
386
+ if (msg.role !== "assistant") continue;
387
+ if (typeof msg.content === "string") continue;
388
+ for (const block of msg.content) {
389
+ if (block.type === "tool_use") recentToolCalls.push(block);
390
+ }
391
+ if (recentToolCalls.length >= DOOM_LOOP_THRESHOLD) break;
392
+ }
393
+ if (recentToolCalls.length < DOOM_LOOP_THRESHOLD) return null;
394
+ const last = recentToolCalls[0];
395
+ return recentToolCalls.every(
396
+ (tc) => tc.name === last.name && JSON.stringify(tc.input) === JSON.stringify(last.input)
397
+ ) ? last : null;
398
+ }
399
+ async function* agentLoop(config) {
400
+ const {
401
+ model,
402
+ tools,
403
+ messages,
404
+ systemPrompt,
405
+ maxTurns = DEFAULT_MAX_TURNS,
406
+ contextManager,
407
+ permissionEngine,
408
+ cwd = process.cwd(),
409
+ abortSignal,
410
+ hooks,
411
+ costTracker
412
+ } = config;
413
+ const hookRunner = new HookRunner(hooks);
414
+ const context = { cwd, env: { ...process.env } };
415
+ let turns = 0;
416
+ let consecutiveRetries = 0;
417
+ const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
418
+ let totalUsage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };
419
+ let inFlightText = "";
420
+ let inFlightReasoning = "";
421
+ let inFlightToolCalls = [];
422
+ while (turns < maxTurns) {
423
+ if (abortSignal?.aborted) {
424
+ yield finalizeLoop("Aborted");
425
+ return;
426
+ }
427
+ const messagesAfterHook = await hookRunner.runOnTurnStart(turns + 1, messages);
428
+ if (messagesAfterHook !== messages) {
429
+ messages.length = 0;
430
+ messages.push(...messagesAfterHook);
431
+ }
432
+ const contextCheck = contextManager.check(messages);
433
+ if (contextCheck.needsCompact) {
434
+ const result = await contextManager.compact(messages);
435
+ messages.length = 0;
436
+ messages.push(...result.messages);
437
+ if (result.tokensSaved > 0) {
438
+ const thinking = `Context ${result.method}: saved ~${result.tokensSaved} tokens (${contextCheck.totalTokens} \u2192 ${contextCheck.totalTokens - result.tokensSaved})`;
439
+ yield { type: "thinking", thinking };
440
+ await hookRunner.runOnCompact(result);
441
+ }
442
+ const pruned = contextManager.pruneToolOutputs(messages);
443
+ if (pruned.length !== messages.length) {
444
+ messages.length = 0;
445
+ messages.push(...pruned);
446
+ yield { type: "thinking", thinking: "Pruned large tool outputs to free additional context" };
447
+ }
448
+ }
449
+ const estimatedTotal = totalUsage.inputTokens + totalUsage.outputTokens + (totalUsage.cacheReadTokens ?? 0) + (totalUsage.cacheWriteTokens ?? 0);
450
+ const maxContext = contextManager.maxTokens;
451
+ const maxOutput = contextManager.maxOutputTokens;
452
+ if (maxContext > 0 && estimatedTotal + maxOutput + PRE_OVERFLOW_BUFFER >= maxContext) {
453
+ yield { type: "thinking", thinking: "Approaching context limit, compacting preemptively..." };
454
+ const result = await contextManager.compact(messages);
455
+ messages.length = 0;
456
+ messages.push(...result.messages);
457
+ if (result.tokensSaved > 0) {
458
+ yield { type: "thinking", thinking: `Pre-overflow compact: freed ~${result.tokensSaved} tokens` };
459
+ }
460
+ }
461
+ const available = contextManager.getAvailableTokens(messages);
462
+ const maxTokens = Math.min(available, contextManager.maxOutputTokens);
463
+ if (maxTokens < 1024) {
464
+ yield finalizeLoop("Insufficient context window for next turn");
465
+ return;
466
+ }
467
+ inFlightText = "";
468
+ inFlightReasoning = "";
469
+ inFlightToolCalls = [];
470
+ let response;
471
+ try {
472
+ const mappedMessages = messages.map((m) => {
473
+ if (m.role === "tool") {
474
+ const toolCall = inFlightToolCalls.find((tc) => tc.id === m.tool_use_id);
475
+ if (toolCall) {
476
+ return {
477
+ role: "tool",
478
+ tool_use_id: m.role === "tool" ? m.tool_use_id : void 0,
479
+ is_error: m.role === "tool" ? m.is_error : void 0,
480
+ content: truncateOutput(
481
+ m.content,
482
+ toolCall.name,
483
+ tools.has("delegate_agent")
484
+ )
485
+ };
486
+ }
487
+ }
488
+ return {
489
+ role: m.role,
490
+ content: m.role === "tool" ? m.content : m.role === "user" ? m.content : m.content,
491
+ tool_use_id: m.role === "tool" ? m.tool_use_id : void 0,
492
+ is_error: m.role === "tool" ? m.is_error : void 0
493
+ };
494
+ });
495
+ response = await model.chat({
496
+ messages: mappedMessages,
497
+ tools: tools.getAPI(),
498
+ systemPrompt,
499
+ maxOutputTokens: maxTokens,
500
+ abortSignal
501
+ });
502
+ } catch (err) {
503
+ yield* flushInFlight();
504
+ const error = err instanceof Error ? err : new Error(String(err));
505
+ const hookResult = await hookRunner.runOnError(error, turns + 1);
506
+ if (hookResult.retry && hookResult.message) {
507
+ messages.push({ role: "user", content: hookResult.message });
508
+ turns++;
509
+ continue;
510
+ }
511
+ if (error.message.includes("429") || error.message.includes("529") || error.message.includes("overloaded")) {
512
+ consecutiveRetries++;
513
+ if (consecutiveRetries > maxRetries) {
514
+ yield { type: "error", error: new Error(`Max retries (${maxRetries}) exceeded`) };
515
+ return;
516
+ }
517
+ const headerDelay = parseRetryAfterMs(error);
518
+ const backoffDelay = headerDelay ?? Math.min(INITIAL_RETRY_DELAY_MS * 2 ** (consecutiveRetries - 1) + Math.random() * 500, MAX_RETRY_DELAY_MS);
519
+ yield { type: "thinking", thinking: `Rate limited, retry ${consecutiveRetries}/${maxRetries} in ${Math.round(backoffDelay)}ms...` };
520
+ await new Promise((r) => setTimeout(r, backoffDelay));
521
+ continue;
522
+ }
523
+ yield { type: "error", error };
524
+ return;
525
+ }
526
+ totalUsage.inputTokens += response.usage.inputTokens;
527
+ totalUsage.outputTokens += response.usage.outputTokens;
528
+ totalUsage.cacheReadTokens = (totalUsage.cacheReadTokens ?? 0) + (response.usage.cacheReadTokens ?? 0);
529
+ totalUsage.cacheWriteTokens = (totalUsage.cacheWriteTokens ?? 0) + (response.usage.cacheWriteTokens ?? 0);
530
+ if (costTracker) {
531
+ costTracker.record(model.model, response.usage, turns + 1);
532
+ }
533
+ const toolCalls = [];
534
+ for (const block of response.content) {
535
+ if (block.type === "text") {
536
+ inFlightText += block.text;
537
+ yield { type: "text", text: block.text };
538
+ } else if (block.type === "thinking") {
539
+ inFlightReasoning += block.thinking;
540
+ yield { type: "thinking", thinking: block.thinking };
541
+ } else if (block.type === "tool_use") {
542
+ const resolvedName = tools.has(block.name) ? block.name : tools.findCaseInsensitive(block.name);
543
+ if (!resolvedName) {
544
+ yield { type: "tool_result", id: block.id, name: block.name, output: `Unknown tool: ${block.name}. Available: ${tools.listNames().join(", ")}`, isError: true };
545
+ messages.push({ role: "assistant", content: response.content });
546
+ messages.push({ role: "tool", tool_use_id: block.id, content: `Unknown tool: ${block.name}`, is_error: true });
547
+ continue;
548
+ }
549
+ const modifiedBlock = resolvedName !== block.name ? { ...block, name: resolvedName } : block;
550
+ const hookToolResult = await hookRunner.runOnToolUse(modifiedBlock.name, modifiedBlock.input);
551
+ if (!hookToolResult.allow) {
552
+ yield { type: "tool_result", id: modifiedBlock.id, name: modifiedBlock.name, output: "Tool denied by hook", isError: true };
553
+ messages.push({ role: "assistant", content: response.content });
554
+ messages.push({ role: "tool", tool_use_id: modifiedBlock.id, content: "Tool denied by hook", is_error: true });
555
+ continue;
556
+ }
557
+ const finalBlock = { ...modifiedBlock, input: hookToolResult.input };
558
+ toolCalls.push(finalBlock);
559
+ inFlightToolCalls.push(finalBlock);
560
+ yield { type: "tool_use", id: finalBlock.id, name: finalBlock.name, input: finalBlock.input };
561
+ }
562
+ }
563
+ const hasNonToolContent = inFlightText.length > 0 || inFlightReasoning.length > 0;
564
+ if (!hasNonToolContent && toolCalls.length === 0) {
565
+ yield finalizeLoop("Assistant produced no content");
566
+ return;
567
+ }
568
+ const assistantContent = response.content.filter((b) => {
569
+ if (b.type !== "tool_use") return true;
570
+ return toolCalls.some((tc) => tc.id === b.id);
571
+ });
572
+ messages.push({ role: "assistant", content: assistantContent });
573
+ turns++;
574
+ consecutiveRetries = 0;
575
+ inFlightText = "";
576
+ inFlightReasoning = "";
577
+ inFlightToolCalls = [];
578
+ await hookRunner.runOnTurnEnd(turns, messages);
579
+ if (response.stopReason !== "tool_use" || toolCalls.length === 0) {
580
+ yield {
581
+ type: "done",
582
+ usage: {
583
+ inputTokens: totalUsage.inputTokens,
584
+ outputTokens: totalUsage.outputTokens,
585
+ cacheReadTokens: totalUsage.cacheReadTokens,
586
+ cacheWriteTokens: totalUsage.cacheWriteTokens
587
+ },
588
+ messages: [...messages]
589
+ };
590
+ return;
591
+ }
592
+ const doomLoopCall = detectDoomLoop(messages);
593
+ if (doomLoopCall) {
594
+ yield { type: "thinking", thinking: `Detected repetitive tool calls: ${doomLoopCall.name} called ${DOOM_LOOP_THRESHOLD} times with identical input. Breaking the loop.` };
595
+ messages.push({ role: "user", content: `STOP. You have called ${doomLoopCall.name} ${DOOM_LOOP_THRESHOLD} times in a row with the same input. This indicates a loop. Try a different approach or tool.` });
596
+ turns++;
597
+ continue;
598
+ }
599
+ const results = await orchestrateTools(
600
+ tools,
601
+ toolCalls,
602
+ context,
603
+ async (name, _input) => {
604
+ const perm = permissionEngine.check(name);
605
+ return perm.allowed;
606
+ },
607
+ abortSignal
608
+ );
609
+ for (const result of results) {
610
+ let output = truncateOutput(result.output, result.name, tools.has("delegate_agent"));
611
+ const toolResult = await hookRunner.runOnToolResult({
612
+ id: result.id,
613
+ name: result.name,
614
+ output: result.output,
615
+ isError: result.isError,
616
+ durationMs: result.durationMs
617
+ });
618
+ output = toolResult;
619
+ messages.push({
620
+ role: "tool",
621
+ tool_use_id: result.id,
622
+ content: output,
623
+ is_error: result.isError
624
+ });
625
+ yield {
626
+ type: "tool_result",
627
+ id: result.id,
628
+ name: result.name,
629
+ output,
630
+ isError: result.isError
631
+ };
632
+ }
633
+ }
634
+ yield { type: "error", error: new Error(`Max turns (${maxTurns}) reached`) };
635
+ function* flushInFlight() {
636
+ if (inFlightText.length > 0) {
637
+ yield { type: "text", text: inFlightText };
638
+ }
639
+ if (inFlightReasoning.length > 0) {
640
+ yield { type: "thinking", thinking: inFlightReasoning };
641
+ }
642
+ for (const tc of inFlightToolCalls) {
643
+ yield { type: "tool_result", id: tc.id, name: tc.name, output: "[Tool execution was interrupted]", isError: true };
644
+ messages.push({ role: "assistant", content: [{ type: "tool_use", id: tc.id, name: tc.name, input: tc.input }] });
645
+ messages.push({ role: "tool", tool_use_id: tc.id, content: "[Tool execution was interrupted]", is_error: true });
646
+ }
647
+ }
648
+ function finalizeLoop(reason) {
649
+ if (totalUsage.inputTokens > 0 || totalUsage.outputTokens > 0) {
650
+ return {
651
+ type: "done",
652
+ usage: {
653
+ inputTokens: totalUsage.inputTokens,
654
+ outputTokens: totalUsage.outputTokens,
655
+ cacheReadTokens: totalUsage.cacheReadTokens,
656
+ cacheWriteTokens: totalUsage.cacheWriteTokens
657
+ },
658
+ messages: [...messages]
659
+ };
660
+ }
661
+ return { type: "error", error: new Error(reason) };
662
+ }
663
+ }
664
+
665
+ // src/tools/registry.ts
666
+ var ToolRegistry = class {
667
+ tools = /* @__PURE__ */ new Map();
668
+ constructor(tools) {
669
+ if (tools) {
670
+ for (const tool of tools) {
671
+ this.add(tool);
672
+ }
673
+ }
674
+ }
675
+ /** Add a tool to the registry. Duplicate names are ignored (first one wins). */
676
+ add(tool) {
677
+ if (this.tools.has(tool.name)) {
678
+ console.warn(`[ToolRegistry] Duplicate tool "${tool.name}" ignored (first one wins)`);
679
+ return;
680
+ }
681
+ this.tools.set(tool.name, tool);
682
+ }
683
+ /** Get a tool by name. */
684
+ get(name) {
685
+ return this.tools.get(name);
686
+ }
687
+ /** Check if a tool exists by name. */
688
+ has(name) {
689
+ return this.tools.has(name);
690
+ }
691
+ /** Find tool by case-insensitive name. Returns undefined if not found. */
692
+ findCaseInsensitive(name) {
693
+ const lower = name.toLowerCase();
694
+ if (this.tools.has(name)) return name;
695
+ for (const key of this.tools.keys()) {
696
+ if (key.toLowerCase() === lower) return key;
697
+ }
698
+ return void 0;
699
+ }
700
+ /** List all registered tool names. */
701
+ listNames() {
702
+ return Array.from(this.tools.keys());
703
+ }
704
+ /** Get all registered tools. */
705
+ getAll() {
706
+ return Array.from(this.tools.values());
707
+ }
708
+ /** Get all tools in API format, sorted by name for cache stability. */
709
+ getAPI() {
710
+ return this.getAll().sort((a, b) => a.name.localeCompare(b.name)).map((t) => t.toAPI());
711
+ }
712
+ /** Number of registered tools. */
713
+ get size() {
714
+ return this.tools.size;
715
+ }
716
+ };
717
+
718
+ // src/tools/tool.ts
719
+ function zodToJsonSchema(schema) {
720
+ const s = schema;
721
+ const def = s._def;
722
+ const typeName = def.typeName;
723
+ const description = def.description;
724
+ const base = {};
725
+ if (description) base.description = description;
726
+ switch (typeName) {
727
+ case "ZodString":
728
+ return { ...base, type: "string" };
729
+ case "ZodNumber":
730
+ return { ...base, type: "number" };
731
+ case "ZodBoolean":
732
+ return { ...base, type: "boolean" };
733
+ case "ZodNull":
734
+ return { ...base, type: "null" };
735
+ case "ZodArray": {
736
+ const items = zodToJsonSchema(def.element);
737
+ return { ...base, type: "array", items };
738
+ }
739
+ case "ZodObject": {
740
+ const shapeFn = def.shape;
741
+ const shape = shapeFn();
742
+ const properties = {};
743
+ const required = [];
744
+ for (const [key, value] of Object.entries(shape)) {
745
+ properties[key] = zodToJsonSchema(value);
746
+ const propDef = value._def;
747
+ if (propDef.typeName !== "ZodOptional" && propDef.typeName !== "ZodNullish" && propDef.typeName !== "ZodDefault") {
748
+ required.push(key);
749
+ }
750
+ }
751
+ const result = { ...base, type: "object", properties };
752
+ if (required.length > 0) result.required = required;
753
+ return result;
754
+ }
755
+ case "ZodEnum":
756
+ return { ...base, enum: def.values };
757
+ case "ZodLiteral":
758
+ return { ...base, const: def.value };
759
+ case "ZodUnion": {
760
+ const options = def.options.map((o) => zodToJsonSchema(o));
761
+ return { ...base, anyOf: options };
762
+ }
763
+ case "ZodDiscriminatedUnion": {
764
+ const options = def.options.map((o) => zodToJsonSchema(o));
765
+ return { ...base, anyOf: options };
766
+ }
767
+ case "ZodOptional":
768
+ return zodToJsonSchema(def.innerType);
769
+ case "ZodNullable": {
770
+ const inner = zodToJsonSchema(def.innerType);
771
+ inner.nullable = true;
772
+ return inner;
773
+ }
774
+ case "ZodNullish": {
775
+ const inner = zodToJsonSchema(def.innerType);
776
+ inner.nullable = true;
777
+ return inner;
778
+ }
779
+ case "ZodDefault":
780
+ return zodToJsonSchema(def.innerType);
781
+ case "ZodRecord": {
782
+ const valueSchema = zodToJsonSchema(def.valueType);
783
+ return { ...base, type: "object", additionalProperties: valueSchema };
784
+ }
785
+ case "ZodTuple": {
786
+ const items = def.items.map((o) => zodToJsonSchema(o));
787
+ return { ...base, type: "array", items, minItems: items.length, maxItems: items.length };
788
+ }
789
+ case "ZodEffects": {
790
+ return zodToJsonSchema(def.innerType);
791
+ }
792
+ case "ZodAny":
793
+ return {};
794
+ case "ZodUnknown":
795
+ return {};
796
+ case "ZodVoid":
797
+ return { ...base, type: "null" };
798
+ case "ZodNever":
799
+ return { ...base, not: {} };
800
+ default:
801
+ return { ...base, type: "string" };
802
+ }
803
+ }
804
+ function defineTool(config) {
805
+ const tool = {
806
+ name: config.name,
807
+ description: config.description,
808
+ inputSchema: config.inputSchema,
809
+ isReadOnly: config.isReadOnly ?? false,
810
+ isConcurrencySafe: config.isConcurrencySafe ?? false,
811
+ execute: config.execute,
812
+ toAPI() {
813
+ return {
814
+ name: config.name,
815
+ description: config.description,
816
+ input_schema: zodToJsonSchema(config.inputSchema)
817
+ };
818
+ },
819
+ toString(input) {
820
+ const entries = Object.entries(input).map(([k, v]) => {
821
+ if (typeof v === "string") return `${k}: "${v}"`;
822
+ if (v === void 0) return `${k}: undefined`;
823
+ if (v === null) return `${k}: null`;
824
+ return `${k}: ${String(v)}`;
825
+ }).join(", ");
826
+ return `${config.name}({ ${entries} })`;
827
+ }
828
+ };
829
+ return tool;
830
+ }
831
+ function defineToolFromClass(ctor) {
832
+ const instance = new ctor();
833
+ return {
834
+ name: instance.name,
835
+ description: instance.description,
836
+ inputSchema: instance.inputSchema,
837
+ isReadOnly: instance.isReadOnly,
838
+ isConcurrencySafe: instance.isConcurrencySafe,
839
+ execute: (input, context) => instance.execute(input, context),
840
+ toAPI: () => instance.toAPI(),
841
+ toString: (input) => instance.toString(input)
842
+ };
843
+ }
844
+
845
+ // src/context/tokenizer.ts
846
+ function estimateTokens(text) {
847
+ const chars = text.length;
848
+ const words = text.split(/\s+/).filter(Boolean).length;
849
+ return Math.max(Math.ceil(chars / 4), Math.ceil(words * 1.3));
850
+ }
851
+ function estimateMessageTokens(message) {
852
+ if (message.role === "user") {
853
+ return estimateTokens(message.content) + 4;
854
+ }
855
+ if (message.role === "tool") {
856
+ return estimateTokens(message.content) + 4;
857
+ }
858
+ let tokens = 0;
859
+ for (const block of message.content) {
860
+ if (block.type === "text") {
861
+ tokens += estimateTokens(block.text) + 4;
862
+ } else if (block.type === "tool_use") {
863
+ tokens += estimateTokens(block.name) + estimateTokens(JSON.stringify(block.input)) + 4;
864
+ } else if (block.type === "thinking") {
865
+ tokens += estimateTokens(block.thinking) + 4;
866
+ }
867
+ }
868
+ return tokens;
869
+ }
870
+ function estimateConversationTokens(messages) {
871
+ const byRole = {};
872
+ let total = 0;
873
+ for (const message of messages) {
874
+ const msgTokens = estimateMessageTokens(message) + 10;
875
+ total += msgTokens;
876
+ byRole[message.role] = (byRole[message.role] ?? 0) + msgTokens;
877
+ }
878
+ return { total, byRole };
879
+ }
880
+
881
+ // src/context/manager.ts
882
+ var PRUNE_MINIMUM = 2e4;
883
+ var PRUNE_PROTECT_TOKENS = 4e4;
884
+ var PROTECTED_TOOL_TYPES = /* @__PURE__ */ new Set(["skill", "delegate_agent"]);
885
+ var RECENT_TURNS_TO_PROTECT = 2;
886
+ var ContextManager = class {
887
+ maxTokens;
888
+ maxOutputTokens;
889
+ compactThreshold;
890
+ constructor(config) {
891
+ this.maxTokens = config?.maxTokens ?? DEFAULT_CONTEXT_WINDOW;
892
+ this.maxOutputTokens = config?.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS;
893
+ this.compactThreshold = config?.compactThreshold ?? DEFAULT_COMPACT_THRESHOLD;
894
+ }
895
+ check(messages) {
896
+ const { total: totalTokens } = estimateConversationTokens(messages);
897
+ const availableTokens = this.maxTokens - totalTokens - this.maxOutputTokens;
898
+ const usagePercent = totalTokens / this.maxTokens;
899
+ const needsCompact = usagePercent >= this.compactThreshold;
900
+ let recommendedMethod;
901
+ if (usagePercent > 0.95) {
902
+ recommendedMethod = "compact";
903
+ } else {
904
+ recommendedMethod = "snip";
905
+ }
906
+ return { totalTokens, availableTokens, usagePercent, needsCompact, recommendedMethod };
907
+ }
908
+ async compact(messages, summaryFn) {
909
+ const contextCheck = this.check(messages);
910
+ if (!contextCheck.needsCompact) {
911
+ return { messages: [...messages], tokensSaved: 0, method: "none" };
912
+ }
913
+ const originalTokens = contextCheck.totalTokens;
914
+ if (summaryFn && contextCheck.recommendedMethod === "compact") {
915
+ const recentCount = Math.max(Math.ceil(messages.length * 0.2), 1);
916
+ const cutoffIndex = messages.length - recentCount;
917
+ const oldMessages = messages.slice(0, cutoffIndex);
918
+ const recentMessages = messages.slice(cutoffIndex);
919
+ const summary = await summaryFn(oldMessages);
920
+ const summaryMessage = {
921
+ role: "user",
922
+ content: `Here is a summary of our previous conversation:
923
+ ${summary}`
924
+ };
925
+ const compacted = [summaryMessage, ...recentMessages];
926
+ const newTokens2 = estimateConversationTokens(compacted).total;
927
+ return {
928
+ messages: compacted,
929
+ tokensSaved: originalTokens - newTokens2,
930
+ method: "compact"
931
+ };
932
+ }
933
+ const keepFirst = 1;
934
+ const keepLast = 20;
935
+ const keepMin = keepFirst + keepLast;
936
+ if (messages.length <= keepMin) {
937
+ return { messages: [...messages], tokensSaved: 0, method: "none" };
938
+ }
939
+ let result = [...messages];
940
+ const targetUsage = 0.6;
941
+ while (result.length > keepMin) {
942
+ const { total } = estimateConversationTokens(result);
943
+ if (total / this.maxTokens < targetUsage) break;
944
+ const removeCount = Math.max(1, Math.floor((result.length - keepMin) * 0.3));
945
+ const removeEnd = Math.min(keepFirst + removeCount, result.length - keepLast);
946
+ result = [result[0], ...result.slice(removeEnd)];
947
+ }
948
+ const newTokens = estimateConversationTokens(result).total;
949
+ return {
950
+ messages: result,
951
+ tokensSaved: originalTokens - newTokens,
952
+ method: "snip"
953
+ };
954
+ }
955
+ pruneToolOutputs(messages) {
956
+ let totalPruned = 0;
957
+ let turnCount = 0;
958
+ const result = [...messages];
959
+ for (let i = result.length - 1; i >= 0; i--) {
960
+ const msg = result[i];
961
+ if (msg.role === "user") {
962
+ turnCount++;
963
+ if (turnCount <= RECENT_TURNS_TO_PROTECT) continue;
964
+ }
965
+ if (msg.role !== "assistant" || !Array.isArray(msg.content)) continue;
966
+ for (const block of msg.content) {
967
+ if (block.type !== "tool_use") continue;
968
+ if (PROTECTED_TOOL_TYPES.has(block.name)) continue;
969
+ const toolResultIdx = result.findIndex(
970
+ (m, j) => j > i && m.role === "tool" && m.tool_use_id === block.id
971
+ );
972
+ if (toolResultIdx === -1) continue;
973
+ const toolResult = result[toolResultIdx];
974
+ const output = toolResult.content;
975
+ const estTokens = Math.ceil(output.length / 4);
976
+ if (totalPruned + estTokens >= PRUNE_MINIMUM && totalPruned >= PRUNE_PROTECT_TOKENS) {
977
+ return result;
978
+ }
979
+ totalPruned += estTokens;
980
+ result[toolResultIdx] = {
981
+ ...toolResult,
982
+ content: `[Output pruned: ${estTokens} tokens saved]`,
983
+ is_error: false
984
+ };
985
+ }
986
+ }
987
+ return result;
988
+ }
989
+ getAvailableTokens(messages) {
990
+ return this.maxTokens - estimateConversationTokens(messages).total - this.maxOutputTokens;
991
+ }
992
+ getUsagePercent(messages) {
993
+ return estimateConversationTokens(messages).total / this.maxTokens;
994
+ }
995
+ };
996
+
997
+ // src/permissions/engine.ts
998
+ function matchPattern(pattern, toolName) {
999
+ if (!pattern.includes("*")) {
1000
+ return pattern === toolName;
1001
+ }
1002
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
1003
+ const regex = new RegExp(`^${escaped}$`);
1004
+ return regex.test(toolName);
1005
+ }
1006
+ var PermissionEngine = class {
1007
+ allowedPatterns;
1008
+ deniedPatterns;
1009
+ askPatterns;
1010
+ defaultAction;
1011
+ constructor(config) {
1012
+ this.allowedPatterns = config?.allowedTools ?? [];
1013
+ this.deniedPatterns = config?.deniedTools ?? [];
1014
+ this.askPatterns = config?.askTools ?? [];
1015
+ this.defaultAction = config?.defaultAction ?? "allow";
1016
+ }
1017
+ check(toolName, _input) {
1018
+ for (const pattern of this.deniedPatterns) {
1019
+ if (matchPattern(pattern, toolName)) {
1020
+ return { allowed: false, reason: `Tool '${toolName}' matches denied pattern '${pattern}'` };
1021
+ }
1022
+ }
1023
+ for (const pattern of this.allowedPatterns) {
1024
+ if (matchPattern(pattern, toolName)) {
1025
+ return { allowed: true };
1026
+ }
1027
+ }
1028
+ for (const pattern of this.askPatterns) {
1029
+ if (matchPattern(pattern, toolName)) {
1030
+ return { allowed: false, reason: "ask" };
1031
+ }
1032
+ }
1033
+ if (this.defaultAction === "deny") {
1034
+ return { allowed: false, reason: `Tool '${toolName}' not explicitly allowed` };
1035
+ }
1036
+ if (this.defaultAction === "ask") {
1037
+ return { allowed: false, reason: "ask" };
1038
+ }
1039
+ return { allowed: true };
1040
+ }
1041
+ addAllowed(pattern) {
1042
+ this.allowedPatterns.push(pattern);
1043
+ }
1044
+ addDenied(pattern) {
1045
+ this.deniedPatterns.push(pattern);
1046
+ }
1047
+ addAsk(pattern) {
1048
+ this.askPatterns.push(pattern);
1049
+ }
1050
+ };
1051
+
1052
+ // src/cost/tracker.ts
1053
+ var DEFAULT_PRICING = {
1054
+ "claude-sonnet-4-20250514": { inputCostPer1k: 3e-3, outputCostPer1k: 0.015 },
1055
+ "claude-3-5-sonnet-20241022": { inputCostPer1k: 3e-3, outputCostPer1k: 0.015 },
1056
+ "claude-3-5-haiku-20241022": { inputCostPer1k: 8e-4, outputCostPer1k: 4e-3 },
1057
+ "claude-opus-4-20250514": { inputCostPer1k: 0.015, outputCostPer1k: 0.075 },
1058
+ "gpt-4o": { inputCostPer1k: 25e-4, outputCostPer1k: 0.01 },
1059
+ "gpt-4o-mini": { inputCostPer1k: 15e-5, outputCostPer1k: 6e-4 },
1060
+ "gpt-4-turbo": { inputCostPer1k: 0.01, outputCostPer1k: 0.03 }
1061
+ };
1062
+ var CostTracker = class {
1063
+ records = [];
1064
+ pricing;
1065
+ constructor(customPricing) {
1066
+ this.pricing = { ...DEFAULT_PRICING, ...customPricing };
1067
+ }
1068
+ record(model, usage, turn, toolName) {
1069
+ const pricing = this.pricing[model] ?? { inputCostPer1k: 1e-3, outputCostPer1k: 3e-3 };
1070
+ this.records.push({
1071
+ model,
1072
+ inputTokens: usage.inputTokens,
1073
+ outputTokens: usage.outputTokens,
1074
+ cacheReadTokens: usage.cacheReadTokens ?? 0,
1075
+ cacheWriteTokens: usage.cacheWriteTokens ?? 0,
1076
+ inputCost: usage.inputTokens / 1e3 * pricing.inputCostPer1k,
1077
+ outputCost: usage.outputTokens / 1e3 * pricing.outputCostPer1k,
1078
+ timestamp: Date.now(),
1079
+ turn,
1080
+ toolName
1081
+ });
1082
+ }
1083
+ getTotal() {
1084
+ let cost = 0;
1085
+ let input = 0;
1086
+ let output = 0;
1087
+ let cacheRead = 0;
1088
+ let cacheWrite = 0;
1089
+ for (const r of this.records) {
1090
+ cost += r.inputCost + r.outputCost;
1091
+ input += r.inputTokens;
1092
+ output += r.outputTokens;
1093
+ cacheRead += r.cacheReadTokens;
1094
+ cacheWrite += r.cacheWriteTokens;
1095
+ }
1096
+ return { cost, inputTokens: input, outputTokens: output, cacheReadTokens: cacheRead, cacheWriteTokens: cacheWrite };
1097
+ }
1098
+ getByModel() {
1099
+ const byModel = {};
1100
+ for (const r of this.records) {
1101
+ if (!byModel[r.model]) byModel[r.model] = { cost: 0, calls: 0 };
1102
+ byModel[r.model].cost += r.inputCost + r.outputCost;
1103
+ byModel[r.model].calls++;
1104
+ }
1105
+ return byModel;
1106
+ }
1107
+ getByTool() {
1108
+ const byTool = {};
1109
+ for (const r of this.records) {
1110
+ const key = r.toolName ?? "llm";
1111
+ if (!byTool[key]) byTool[key] = { cost: 0, calls: 0 };
1112
+ byTool[key].cost += r.inputCost + r.outputCost;
1113
+ byTool[key].calls++;
1114
+ }
1115
+ return byTool;
1116
+ }
1117
+ getRecords() {
1118
+ return [...this.records];
1119
+ }
1120
+ reset() {
1121
+ this.records = [];
1122
+ }
1123
+ setPricing(model, pricing) {
1124
+ this.pricing[model] = pricing;
1125
+ }
1126
+ };
1127
+
1128
+ // src/agent.ts
1129
+ var Agent = class _Agent {
1130
+ model;
1131
+ tools;
1132
+ systemPrompt;
1133
+ maxTurns;
1134
+ contextManager;
1135
+ permissionEngine;
1136
+ permissionConfig;
1137
+ cwd;
1138
+ maxRetries;
1139
+ messages = [];
1140
+ hooks;
1141
+ memory;
1142
+ threadId;
1143
+ costTracker;
1144
+ _loaded = false;
1145
+ _title;
1146
+ _titleFetched = false;
1147
+ _disableTitle = false;
1148
+ constructor(config) {
1149
+ this.model = config.model;
1150
+ this.tools = new ToolRegistry(config.tools ?? []);
1151
+ this.systemPrompt = config.systemPrompt ?? "";
1152
+ this.maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
1153
+ this.contextManager = new ContextManager({
1154
+ maxTokens: config.context?.maxTokens ?? DEFAULT_CONTEXT_WINDOW,
1155
+ maxOutputTokens: config.context?.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS,
1156
+ compactThreshold: config.context?.compactThreshold ?? DEFAULT_COMPACT_THRESHOLD
1157
+ });
1158
+ this.permissionEngine = new PermissionEngine(config.permissions);
1159
+ this.permissionConfig = config.permissions;
1160
+ this.cwd = config.cwd ?? process.cwd();
1161
+ this.maxRetries = config.maxRetries ?? 5;
1162
+ const v2 = config;
1163
+ this.hooks = v2.hooks;
1164
+ this.memory = v2.memory;
1165
+ this.threadId = v2.threadId;
1166
+ this.costTracker = v2.costTracker ?? new CostTracker();
1167
+ this._title = v2.title;
1168
+ this._disableTitle = v2.disableTitle ?? false;
1169
+ }
1170
+ addTool(tool) {
1171
+ this.tools.add(tool);
1172
+ }
1173
+ addMessage(message) {
1174
+ this.messages.push(message);
1175
+ }
1176
+ reset() {
1177
+ this.messages = [];
1178
+ this._titleFetched = false;
1179
+ }
1180
+ getMessages() {
1181
+ return [...this.messages];
1182
+ }
1183
+ getCostTracker() {
1184
+ return this.costTracker;
1185
+ }
1186
+ get title() {
1187
+ return this._title;
1188
+ }
1189
+ async loadMemory() {
1190
+ if (this._loaded || !this.memory || !this.threadId) return;
1191
+ const stored = await this.memory.getThread(this.threadId);
1192
+ if (stored.length > 0) {
1193
+ this.messages = stored;
1194
+ }
1195
+ this._loaded = true;
1196
+ }
1197
+ async saveMemory() {
1198
+ if (!this.memory || !this.threadId) return;
1199
+ await this.memory.saveThread(this.threadId, this.messages);
1200
+ }
1201
+ fork(newThreadId) {
1202
+ const forked = new _Agent({
1203
+ model: this.model,
1204
+ tools: this.tools.getAll(),
1205
+ systemPrompt: this.systemPrompt,
1206
+ maxTurns: this.maxTurns,
1207
+ cwd: this.cwd,
1208
+ maxRetries: this.maxRetries,
1209
+ permissions: this.permissionConfig,
1210
+ hooks: this.hooks,
1211
+ memory: this.memory,
1212
+ threadId: newThreadId,
1213
+ costTracker: this.costTracker,
1214
+ title: this._title,
1215
+ context: {
1216
+ maxTokens: this.contextManager.maxTokens,
1217
+ maxOutputTokens: this.contextManager.maxOutputTokens,
1218
+ compactThreshold: this.contextManager.compactThreshold
1219
+ }
1220
+ });
1221
+ forked.messages = [...this.messages];
1222
+ return forked;
1223
+ }
1224
+ async *run(prompt, options) {
1225
+ await this.loadMemory();
1226
+ const loopMessages = [...this.messages, { role: "user", content: prompt }];
1227
+ if (!this._titleFetched && this.messages.length === 0 && !this._disableTitle) {
1228
+ this._titleFetched = true;
1229
+ this.generateTitle(prompt).catch(() => {
1230
+ });
1231
+ }
1232
+ const loop = agentLoop({
1233
+ model: this.model,
1234
+ tools: this.tools,
1235
+ messages: loopMessages,
1236
+ systemPrompt: this.systemPrompt || void 0,
1237
+ maxTurns: this.maxTurns,
1238
+ contextManager: this.contextManager,
1239
+ permissionEngine: this.permissionEngine,
1240
+ cwd: this.cwd,
1241
+ abortSignal: options?.abortSignal,
1242
+ maxRetries: this.maxRetries,
1243
+ hooks: this.hooks,
1244
+ costTracker: this.costTracker
1245
+ });
1246
+ const finalMessages = [];
1247
+ for await (const event of loop) {
1248
+ yield event;
1249
+ if (event.type === "done") {
1250
+ finalMessages.length = 0;
1251
+ finalMessages.push(...event.messages);
1252
+ }
1253
+ }
1254
+ if (finalMessages.length > 0) {
1255
+ this.messages = finalMessages;
1256
+ await this.saveMemory();
1257
+ }
1258
+ }
1259
+ async chat(prompt, options) {
1260
+ let text = "";
1261
+ let usage = { inputTokens: 0, outputTokens: 0 };
1262
+ for await (const event of this.run(prompt, options)) {
1263
+ if (event.type === "text") {
1264
+ text += event.text;
1265
+ }
1266
+ if (event.type === "done") {
1267
+ usage = {
1268
+ inputTokens: event.usage.inputTokens,
1269
+ outputTokens: event.usage.outputTokens
1270
+ };
1271
+ }
1272
+ }
1273
+ const total = this.costTracker.getTotal();
1274
+ return { text, usage, messages: this.getMessages(), cost: total.cost };
1275
+ }
1276
+ async structured(prompt, schema, options) {
1277
+ const maxRetries = options?.maxRetries ?? 3;
1278
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
1279
+ const currentPrompt = attempt === 0 ? prompt : `${prompt}
1280
+
1281
+ Previous attempt returned invalid JSON. Please fix and respond with valid JSON only.`;
1282
+ const agent = new _Agent({
1283
+ model: this.model,
1284
+ tools: [],
1285
+ systemPrompt: "You must respond with valid JSON matching the requested schema.",
1286
+ maxTurns: 1,
1287
+ cwd: this.cwd,
1288
+ permissions: this.permissionConfig,
1289
+ context: {
1290
+ maxTokens: this.contextManager.maxTokens,
1291
+ maxOutputTokens: this.contextManager.maxOutputTokens,
1292
+ compactThreshold: this.contextManager.compactThreshold
1293
+ },
1294
+ costTracker: this.costTracker,
1295
+ disableTitle: true
1296
+ });
1297
+ const result = await agent.chat(currentPrompt, options);
1298
+ const jsonMatch = result.text.match(/```(?:json)?\s*([\s\S]*?)```/) ?? [null, result.text];
1299
+ const jsonStr = jsonMatch[1] ?? result.text;
1300
+ try {
1301
+ const parsed = JSON.parse(jsonStr);
1302
+ const validated = schema.safeParse(parsed);
1303
+ if (validated.success && validated.data !== void 0) {
1304
+ return validated.data;
1305
+ }
1306
+ } catch {
1307
+ continue;
1308
+ }
1309
+ }
1310
+ throw new Error(`Failed to get valid structured output after ${maxRetries} attempts`);
1311
+ }
1312
+ async structuredViaTool(prompt, schema, options) {
1313
+ const { z: z8 } = await import("zod");
1314
+ const maxRetries = options?.maxRetries ?? 3;
1315
+ let captured;
1316
+ const structTool = defineTool({
1317
+ name: "structured_output",
1318
+ description: "Return your final response in the required structured format.",
1319
+ inputSchema: z8.object({
1320
+ response: z8.unknown().describe("The structured JSON response")
1321
+ }),
1322
+ isReadOnly: true,
1323
+ isConcurrencySafe: true,
1324
+ execute: async (input) => {
1325
+ captured = input.response;
1326
+ return "Structured output captured.";
1327
+ }
1328
+ });
1329
+ const agent = new _Agent({
1330
+ model: this.model,
1331
+ tools: [structTool],
1332
+ systemPrompt: [
1333
+ this.systemPrompt,
1334
+ "You MUST call the structured_output tool with your response. Do not return plain text."
1335
+ ].filter(Boolean).join("\n\n"),
1336
+ maxTurns: 1,
1337
+ cwd: this.cwd,
1338
+ permissions: this.permissionConfig,
1339
+ context: {
1340
+ maxTokens: this.contextManager.maxTokens,
1341
+ maxOutputTokens: this.contextManager.maxOutputTokens,
1342
+ compactThreshold: this.contextManager.compactThreshold
1343
+ },
1344
+ costTracker: this.costTracker,
1345
+ disableTitle: true
1346
+ });
1347
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
1348
+ captured = void 0;
1349
+ const currentPrompt = attempt === 0 ? prompt : `${prompt}
1350
+
1351
+ Previous attempt failed validation. Fix your structured output and call the structured_output tool again.`;
1352
+ await agent.chat(currentPrompt, options);
1353
+ if (captured !== void 0) {
1354
+ const validated = schema.safeParse(captured);
1355
+ if (validated.success && validated.data !== void 0) {
1356
+ return validated.data;
1357
+ }
1358
+ }
1359
+ }
1360
+ throw new Error(`Failed to get valid structured output via tool after ${maxRetries} attempts`);
1361
+ }
1362
+ async asTool(options) {
1363
+ const { z: z8 } = await import("zod");
1364
+ const allowSubAgents = options?.allowSubAgents ?? false;
1365
+ return defineTool({
1366
+ name: options?.name ?? "delegate_agent",
1367
+ description: options?.description ?? `Delegate a task to a sub-agent with system prompt: ${this.systemPrompt?.slice(0, 100) ?? "none"}`,
1368
+ inputSchema: z8.object({
1369
+ prompt: z8.string().describe("The task to delegate")
1370
+ }),
1371
+ isReadOnly: true,
1372
+ isConcurrencySafe: true,
1373
+ execute: async (input) => {
1374
+ const subAgent = this.fork();
1375
+ if (!allowSubAgents) {
1376
+ const forbiddenTools = ["delegate_agent", "task", "todowrite"];
1377
+ const allowed = this.tools.getAll().filter((t) => !forbiddenTools.includes(t.name));
1378
+ for (const tool of allowed) {
1379
+ subAgent.addTool(tool);
1380
+ }
1381
+ }
1382
+ const result = await subAgent.chat(input.prompt);
1383
+ return result.text;
1384
+ }
1385
+ });
1386
+ }
1387
+ async generateTitle(prompt) {
1388
+ try {
1389
+ const firstLine = prompt.split("\n")[0].slice(0, 200);
1390
+ const titleAgent = new _Agent({
1391
+ model: this.model,
1392
+ tools: [],
1393
+ systemPrompt: "Generate a short, descriptive title (max 10 words) for a conversation that starts with this message. Return ONLY the title, nothing else.",
1394
+ maxTurns: 1,
1395
+ cwd: this.cwd,
1396
+ costTracker: this.costTracker,
1397
+ disableTitle: true
1398
+ });
1399
+ const result = await titleAgent.chat(firstLine);
1400
+ const title = result.text.trim().replace(/^["']|["']$/g, "").slice(0, 80);
1401
+ if (title.length > 0) {
1402
+ this._title = title;
1403
+ }
1404
+ } catch {
1405
+ this._title = prompt.slice(0, 50);
1406
+ }
1407
+ }
1408
+ };
1409
+
1410
+ // src/tools/bash.ts
1411
+ var import_node_child_process = require("child_process");
1412
+ var import_zod = require("zod");
1413
+ var MAX_OUTPUT = 50 * 1024;
1414
+ var TRUNCATE_HALF = 25 * 1024;
1415
+ function truncateOutput2(output) {
1416
+ if (output.length <= MAX_OUTPUT) return output;
1417
+ const notice = `
1418
+
1419
+ ... [output truncated: showing first ${TRUNCATE_HALF} and last ${TRUNCATE_HALF} of ${output.length} characters] ...
1420
+
1421
+ `;
1422
+ return output.slice(0, TRUNCATE_HALF) + notice + output.slice(-TRUNCATE_HALF);
1423
+ }
1424
+ var BashTool = defineTool({
1425
+ name: "bash",
1426
+ description: "Execute a shell command. Returns stdout and stderr. Use for running build commands, git operations, package managers, and other CLI tools.",
1427
+ inputSchema: import_zod.z.object({
1428
+ command: import_zod.z.string().describe("The shell command to execute"),
1429
+ timeout: import_zod.z.number().optional().describe("Timeout in milliseconds (default 120000)"),
1430
+ workdir: import_zod.z.string().optional().describe("Working directory for the command")
1431
+ }),
1432
+ isReadOnly: false,
1433
+ isConcurrencySafe: false,
1434
+ execute: async ({ command, timeout = 12e4, workdir }, context) => {
1435
+ return new Promise((resolve) => {
1436
+ let settled = false;
1437
+ const done = (result) => {
1438
+ if (settled) return;
1439
+ settled = true;
1440
+ resolve(result);
1441
+ };
1442
+ if (context.abortSignal?.aborted) {
1443
+ done("Command aborted before execution");
1444
+ return;
1445
+ }
1446
+ const child = (0, import_node_child_process.exec)(
1447
+ command,
1448
+ {
1449
+ cwd: workdir ?? context.cwd,
1450
+ timeout,
1451
+ maxBuffer: 10 * 1024 * 1024,
1452
+ killSignal: "SIGKILL"
1453
+ },
1454
+ (error, stdout, stderr) => {
1455
+ let output = "";
1456
+ if (stdout) output += `STDOUT:
1457
+ ${stdout}
1458
+
1459
+ `;
1460
+ if (stderr) output += `STDERR:
1461
+ ${stderr}
1462
+
1463
+ `;
1464
+ output += `Exit code: ${error?.code ?? 0}`;
1465
+ if (error && error.killed) {
1466
+ output = `Command timed out after ${timeout}ms
1467
+
1468
+ ${output}`;
1469
+ }
1470
+ done(truncateOutput2(output));
1471
+ }
1472
+ );
1473
+ child.on("error", (err) => {
1474
+ done(`Failed to execute command: ${err.message}`);
1475
+ });
1476
+ if (context.abortSignal) {
1477
+ context.abortSignal.addEventListener(
1478
+ "abort",
1479
+ () => {
1480
+ child.kill("SIGKILL");
1481
+ done("Command aborted");
1482
+ },
1483
+ { once: true }
1484
+ );
1485
+ }
1486
+ });
1487
+ }
1488
+ });
1489
+
1490
+ // src/tools/file-read.ts
1491
+ var import_promises = __toESM(require("fs/promises"), 1);
1492
+ var import_node_path = __toESM(require("path"), 1);
1493
+ var import_zod2 = require("zod");
1494
+ var MAX_LINE_LENGTH = 2e3;
1495
+ var FileReadTool = defineTool({
1496
+ name: "file_read",
1497
+ description: "Read the contents of a file. Returns the file content with line numbers. Use GlobTool to find files first.",
1498
+ inputSchema: import_zod2.z.object({
1499
+ path: import_zod2.z.string().describe("Absolute or relative path to the file"),
1500
+ offset: import_zod2.z.number().optional().describe("Line number to start reading from (1-indexed)"),
1501
+ limit: import_zod2.z.number().optional().describe("Maximum number of lines to read")
1502
+ }),
1503
+ isReadOnly: true,
1504
+ isConcurrencySafe: true,
1505
+ execute: async ({ path: filePath, offset, limit }, context) => {
1506
+ try {
1507
+ const resolved = import_node_path.default.isAbsolute(filePath) ? filePath : import_node_path.default.resolve(context.cwd, filePath);
1508
+ const content = await import_promises.default.readFile(resolved, "utf-8");
1509
+ const lines = content.split("\n");
1510
+ const startLine = offset ? Math.max(1, offset) : 1;
1511
+ const startIndex = startLine - 1;
1512
+ const endIndex = limit != null ? startIndex + limit : lines.length;
1513
+ const selected = lines.slice(startIndex, endIndex);
1514
+ const formatted = selected.map((line, i) => {
1515
+ const lineNum = startLine + i;
1516
+ const truncated = line.length > MAX_LINE_LENGTH ? line.slice(0, MAX_LINE_LENGTH) + "..." : line;
1517
+ return `${lineNum}: ${truncated}`;
1518
+ }).join("\n");
1519
+ return formatted;
1520
+ } catch (err) {
1521
+ if (err.code === "ENOENT") {
1522
+ return `Error: File not found: ${filePath}`;
1523
+ }
1524
+ if (err.code === "EISDIR") {
1525
+ return `Error: Path is a directory: ${filePath}`;
1526
+ }
1527
+ return `Error reading file: ${err instanceof Error ? err.message : String(err)}`;
1528
+ }
1529
+ }
1530
+ });
1531
+
1532
+ // src/tools/file-write.ts
1533
+ var import_promises2 = __toESM(require("fs/promises"), 1);
1534
+ var import_node_path2 = __toESM(require("path"), 1);
1535
+ var import_zod3 = require("zod");
1536
+ var FileWriteTool = defineTool({
1537
+ name: "file_write",
1538
+ description: "Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Parent directories are created automatically.",
1539
+ inputSchema: import_zod3.z.object({
1540
+ path: import_zod3.z.string().describe("Path to the file"),
1541
+ content: import_zod3.z.string().describe("Content to write")
1542
+ }),
1543
+ isReadOnly: false,
1544
+ isConcurrencySafe: false,
1545
+ execute: async ({ path: filePath, content }, context) => {
1546
+ try {
1547
+ const resolved = import_node_path2.default.isAbsolute(filePath) ? filePath : import_node_path2.default.resolve(context.cwd, filePath);
1548
+ const dir = import_node_path2.default.dirname(resolved);
1549
+ await import_promises2.default.mkdir(dir, { recursive: true });
1550
+ await import_promises2.default.writeFile(resolved, content, "utf-8");
1551
+ return `Wrote ${content.length} characters to ${filePath}`;
1552
+ } catch (err) {
1553
+ return `Error writing file: ${err instanceof Error ? err.message : String(err)}`;
1554
+ }
1555
+ }
1556
+ });
1557
+
1558
+ // src/tools/file-edit.ts
1559
+ var import_promises3 = __toESM(require("fs/promises"), 1);
1560
+ var import_node_path3 = __toESM(require("path"), 1);
1561
+ var import_zod4 = require("zod");
1562
+
1563
+ // src/tools/fuzzy-edit.ts
1564
+ var FuzzyEditError = class extends Error {
1565
+ constructor(kind, message) {
1566
+ super(message);
1567
+ this.kind = kind;
1568
+ this.name = "FuzzyEditError";
1569
+ }
1570
+ kind;
1571
+ };
1572
+ function levenshtein(a, b) {
1573
+ const m = a.length;
1574
+ const n = b.length;
1575
+ if (m === 0) return n;
1576
+ if (n === 0) return m;
1577
+ const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
1578
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
1579
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
1580
+ for (let i = 1; i <= m; i++) {
1581
+ for (let j = 1; j <= n; j++) {
1582
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
1583
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
1584
+ }
1585
+ }
1586
+ return dp[m][n];
1587
+ }
1588
+ function similarity(a, b) {
1589
+ if (a.length > 1e3 || b.length > 1e3) return a === b ? 1 : 0;
1590
+ const maxLen = Math.max(a.length, b.length);
1591
+ if (maxLen === 0) return 1;
1592
+ return 1 - levenshtein(a, b) / maxLen;
1593
+ }
1594
+ function* simpleReplacer(content, find) {
1595
+ if (content.includes(find)) yield find;
1596
+ }
1597
+ function* lineTrimmedReplacer(content, find) {
1598
+ const lines = find.split("\n").map((l) => l.trim());
1599
+ const contentLines = content.split("\n");
1600
+ for (let i = 0; i <= contentLines.length - lines.length; i++) {
1601
+ if (contentLines.slice(i, i + lines.length).every((cl, idx) => cl.trim() === lines[idx])) {
1602
+ yield contentLines.slice(i, i + lines.length).join("\n");
1603
+ }
1604
+ }
1605
+ }
1606
+ function* blockAnchorReplacer(content, find) {
1607
+ const findLines = find.split("\n");
1608
+ const contentLines = content.split("\n");
1609
+ if (findLines.length < 2) return;
1610
+ const firstLine = findLines[0];
1611
+ const lastLine = findLines[findLines.length - 1];
1612
+ const middleLines = findLines.slice(1, -1);
1613
+ for (let i = 0; i <= contentLines.length - findLines.length; i++) {
1614
+ if (contentLines[i] !== firstLine) continue;
1615
+ const endIdx = i + findLines.length - 1;
1616
+ if (contentLines[endIdx] !== lastLine) continue;
1617
+ const candidates = [];
1618
+ for (let j = i + 1; j < endIdx; j++) {
1619
+ candidates.push({ idx: j, score: 0 });
1620
+ }
1621
+ let totalScore = 0;
1622
+ for (let k = 0; k < middleLines.length; k++) {
1623
+ const contentLine = contentLines[i + 1 + k];
1624
+ const sim = similarity(middleLines[k], contentLine);
1625
+ totalScore += sim;
1626
+ if (candidates[k]) candidates[k].score = sim;
1627
+ }
1628
+ const avgScore = totalScore / middleLines.length;
1629
+ const threshold = candidates.length > 1 ? 0.3 : 0;
1630
+ if (avgScore >= threshold) {
1631
+ yield contentLines.slice(i, i + findLines.length).join("\n");
1632
+ }
1633
+ }
1634
+ }
1635
+ function* whitespaceNormalizedReplacer(content, find) {
1636
+ const normFind = find.replace(/\s+/g, " ").trim();
1637
+ if (!normFind) return;
1638
+ const normContent = content.replace(/\s+/g, " ").trim();
1639
+ const idx = normContent.indexOf(normFind);
1640
+ if (idx === -1) return;
1641
+ const firstWord = normFind.split(" ")[0];
1642
+ const lastWord = normFind.split(" ").filter(Boolean).pop();
1643
+ const firstIdx = content.indexOf(firstWord);
1644
+ const lastIdx = content.lastIndexOf(lastWord);
1645
+ if (firstIdx === -1 || lastIdx === -1 || lastIdx < firstIdx) return;
1646
+ yield content.substring(firstIdx, lastIdx + lastWord.length);
1647
+ }
1648
+ function* indentationFlexibleReplacer(content, find) {
1649
+ const lines = find.split("\n");
1650
+ const minIndent = Math.min(...lines.filter((l) => l.trim().length > 0).map((l) => l.match(/^(\s*)/)?.[1].length ?? 0));
1651
+ const dedented = lines.map((l) => l.substring(minIndent)).join("\n");
1652
+ if (content.includes(dedented)) yield dedented;
1653
+ }
1654
+ function* escapeNormalizedReplacer(content, find) {
1655
+ const normalized = find.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\'/g, "'");
1656
+ if (content.includes(normalized)) yield normalized;
1657
+ }
1658
+ function* trimmedBoundaryReplacer(content, find) {
1659
+ const trimmed = find.trim();
1660
+ if (content.includes(trimmed)) yield trimmed;
1661
+ }
1662
+ function* contextAwareReplacer(content, find) {
1663
+ const findLines = find.split("\n");
1664
+ if (findLines.length < 3) return;
1665
+ const firstLine = findLines[0].trim();
1666
+ const lastLine = findLines[findLines.length - 1].trim();
1667
+ const contentLines = content.split("\n");
1668
+ for (let i = 0; i <= contentLines.length - 3; i++) {
1669
+ if (contentLines[i].trim() !== firstLine) continue;
1670
+ let bestEndIdx = -1;
1671
+ let bestScore = 0;
1672
+ for (let j = i + 2; j < contentLines.length; j++) {
1673
+ if (contentLines[j].trim() === lastLine) {
1674
+ const middleCount = j - i - 1;
1675
+ let matchCount = 0;
1676
+ for (let k = 1; k <= middleCount; k++) {
1677
+ const fIdx = Math.floor(k / (middleCount + 1) * (findLines.length - 2));
1678
+ if (fIdx >= 0 && fIdx < findLines.length - 2) {
1679
+ if (similarity(contentLines[i + k].trim(), findLines[fIdx + 1].trim()) > 0.5) {
1680
+ matchCount++;
1681
+ }
1682
+ }
1683
+ }
1684
+ const score = matchCount / middleCount;
1685
+ if (score > bestScore) {
1686
+ bestScore = score;
1687
+ bestEndIdx = j;
1688
+ }
1689
+ }
1690
+ }
1691
+ if (bestEndIdx !== -1 && bestScore > 0.5) {
1692
+ yield contentLines.slice(i, bestEndIdx + 1).join("\n");
1693
+ }
1694
+ }
1695
+ }
1696
+ var REPLACERS = [
1697
+ simpleReplacer,
1698
+ lineTrimmedReplacer,
1699
+ blockAnchorReplacer,
1700
+ whitespaceNormalizedReplacer,
1701
+ indentationFlexibleReplacer,
1702
+ escapeNormalizedReplacer,
1703
+ trimmedBoundaryReplacer,
1704
+ contextAwareReplacer
1705
+ ];
1706
+ function fuzzyReplace(content, oldString, newString, replaceAll = false) {
1707
+ for (const replacer of REPLACERS) {
1708
+ for (const search of replacer(content, oldString)) {
1709
+ const idx = content.indexOf(search);
1710
+ if (idx === -1) continue;
1711
+ if (replaceAll) {
1712
+ return content.split(search).join(newString);
1713
+ }
1714
+ const firstIdx = content.indexOf(search);
1715
+ const lastIdx = content.lastIndexOf(search);
1716
+ if (firstIdx !== lastIdx) continue;
1717
+ return content.substring(0, firstIdx) + newString + content.substring(firstIdx + search.length);
1718
+ }
1719
+ }
1720
+ throw new FuzzyEditError("not_found", `Could not find match for replacement (after 8 fuzzy strategies)`);
1721
+ }
1722
+
1723
+ // src/tools/file-edit.ts
1724
+ var FileEditTool = defineTool({
1725
+ name: "file_edit",
1726
+ description: "Edit an existing file by replacing a string match with new content. Uses 9-strategy fuzzy matching (exact, line-trimmed, block-anchor with Levenshtein, whitespace-normalized, indentation-flexible, escape-normalized, trimmed-boundary, context-aware, multi-occurrence) so the oldString does not need to be a perfect character-for-character match. For multi-line edits, include enough surrounding context to ensure uniqueness.",
1727
+ inputSchema: import_zod4.z.object({
1728
+ path: import_zod4.z.string().describe("Path to the file"),
1729
+ oldString: import_zod4.z.string().describe("The string to find and replace (fuzzy-matched)"),
1730
+ newString: import_zod4.z.string().describe("The replacement string"),
1731
+ replaceAll: import_zod4.z.boolean().optional().describe("Replace all occurrences instead of just the first")
1732
+ }),
1733
+ isReadOnly: false,
1734
+ isConcurrencySafe: false,
1735
+ execute: async ({ path: filePath, oldString, newString, replaceAll = false }, context) => {
1736
+ try {
1737
+ const resolved = import_node_path3.default.isAbsolute(filePath) ? filePath : import_node_path3.default.resolve(context.cwd, filePath);
1738
+ const content = await import_promises3.default.readFile(resolved, "utf-8");
1739
+ let newContent;
1740
+ let count = 0;
1741
+ if (replaceAll) {
1742
+ const occurrences = content.split(oldString).length - 1;
1743
+ if (occurrences === 0) {
1744
+ try {
1745
+ newContent = fuzzyReplace(content, oldString, newString, true);
1746
+ count = 1;
1747
+ } catch {
1748
+ return `Error: oldString not found in ${filePath} (tried 9 fuzzy strategies)`;
1749
+ }
1750
+ } else {
1751
+ newContent = content.split(oldString).join(newString);
1752
+ count = occurrences;
1753
+ }
1754
+ } else {
1755
+ try {
1756
+ newContent = fuzzyReplace(content, oldString, newString, false);
1757
+ count = 1;
1758
+ } catch (err) {
1759
+ const kind = err.kind;
1760
+ if (kind === "ambiguous") {
1761
+ return `Error: Multiple matches for oldString in ${filePath}. Provide more surrounding context or set replaceAll to true.`;
1762
+ }
1763
+ return `Error: oldString not found in ${filePath} (tried 9 fuzzy strategies)`;
1764
+ }
1765
+ }
1766
+ await import_promises3.default.writeFile(resolved, newContent, "utf-8");
1767
+ return `Replaced ${count} occurrence(s) in ${filePath}`;
1768
+ } catch (err) {
1769
+ if (err.code === "ENOENT") {
1770
+ return `Error: File not found: ${filePath}`;
1771
+ }
1772
+ return `Error editing file: ${err instanceof Error ? err.message : String(err)}`;
1773
+ }
1774
+ }
1775
+ });
1776
+
1777
+ // src/tools/glob.ts
1778
+ var import_promises4 = __toESM(require("fs/promises"), 1);
1779
+ var import_node_path4 = __toESM(require("path"), 1);
1780
+ var import_zod5 = require("zod");
1781
+ init_utils();
1782
+ var MAX_RESULTS = 200;
1783
+ async function walkDir2(dir) {
1784
+ const { walkDir: walk } = await Promise.resolve().then(() => (init_utils(), utils_exports));
1785
+ const entries = await walk(dir);
1786
+ return entries.map((e) => e.path);
1787
+ }
1788
+ function matchInclude(filePath, includePattern) {
1789
+ if (!includePattern) return true;
1790
+ const base = import_node_path4.default.basename(filePath);
1791
+ const re = globToRegExp(includePattern);
1792
+ return re.test(base);
1793
+ }
1794
+ var GlobTool = defineTool({
1795
+ name: "glob",
1796
+ description: "Find files matching a glob pattern. Returns matching file paths sorted by modification time.",
1797
+ inputSchema: import_zod5.z.object({
1798
+ pattern: import_zod5.z.string().describe('Glob pattern (e.g., "**/*.ts", "src/**/*.tsx")'),
1799
+ path: import_zod5.z.string().optional().describe("Directory to search in (default: current directory)"),
1800
+ include: import_zod5.z.string().optional().describe('File extension or pattern to include (e.g., "*.ts")')
1801
+ }),
1802
+ isReadOnly: true,
1803
+ isConcurrencySafe: true,
1804
+ execute: async ({ pattern, path: searchPath, include }, context) => {
1805
+ try {
1806
+ const searchDir = searchPath ? import_node_path4.default.isAbsolute(searchPath) ? searchPath : import_node_path4.default.resolve(context.cwd, searchPath) : context.cwd;
1807
+ const allFiles = await walkDir2(searchDir);
1808
+ const re = globToRegExp(pattern);
1809
+ const matched = [];
1810
+ for (const filePath of allFiles) {
1811
+ const relative = import_node_path4.default.relative(searchDir, filePath);
1812
+ if (re.test(relative) && matchInclude(relative, include)) {
1813
+ try {
1814
+ const stat = await import_promises4.default.stat(filePath);
1815
+ matched.push({ filePath: relative, mtimeMs: stat.mtimeMs });
1816
+ } catch {
1817
+ matched.push({ filePath: relative, mtimeMs: 0 });
1818
+ }
1819
+ }
1820
+ }
1821
+ matched.sort((a, b) => b.mtimeMs - a.mtimeMs);
1822
+ const limited = matched.slice(0, MAX_RESULTS);
1823
+ return limited.map((m) => m.filePath).join("\n");
1824
+ } catch (err) {
1825
+ return `Error searching files: ${err instanceof Error ? err.message : String(err)}`;
1826
+ }
1827
+ }
1828
+ });
1829
+
1830
+ // src/tools/grep.ts
1831
+ var import_promises5 = __toESM(require("fs/promises"), 1);
1832
+ var import_node_path5 = __toESM(require("path"), 1);
1833
+ var import_zod6 = require("zod");
1834
+ init_utils();
1835
+ var MAX_MATCHES = 200;
1836
+ async function walkDir3(dir) {
1837
+ const { walkDir: walk } = await Promise.resolve().then(() => (init_utils(), utils_exports));
1838
+ const entries = await walk(dir);
1839
+ return entries.map((e) => e.path);
1840
+ }
1841
+ var GrepTool = defineTool({
1842
+ name: "grep",
1843
+ description: "Search file contents using a regular expression. Returns matching file paths with line numbers.",
1844
+ inputSchema: import_zod6.z.object({
1845
+ pattern: import_zod6.z.string().describe("Regular expression to search for"),
1846
+ path: import_zod6.z.string().optional().describe("Directory to search in"),
1847
+ include: import_zod6.z.string().optional().describe('File pattern to include (e.g., "*.ts")')
1848
+ }),
1849
+ isReadOnly: true,
1850
+ isConcurrencySafe: true,
1851
+ execute: async ({ pattern, path: searchPath, include }, context) => {
1852
+ try {
1853
+ let regex;
1854
+ try {
1855
+ regex = new RegExp(pattern);
1856
+ } catch {
1857
+ return `Error: Invalid regular expression: ${pattern}`;
1858
+ }
1859
+ const searchDir = searchPath ? import_node_path5.default.isAbsolute(searchPath) ? searchPath : import_node_path5.default.resolve(context.cwd, searchPath) : context.cwd;
1860
+ const includeRe = include ? globToRegExp(include) : null;
1861
+ const allFiles = await walkDir3(searchDir);
1862
+ const matches = [];
1863
+ for (const filePath of allFiles) {
1864
+ if (matches.length >= MAX_MATCHES) break;
1865
+ if (includeRe) {
1866
+ const base = import_node_path5.default.basename(filePath);
1867
+ if (!includeRe.test(base)) continue;
1868
+ }
1869
+ let content;
1870
+ try {
1871
+ content = await import_promises5.default.readFile(filePath, "utf-8");
1872
+ } catch {
1873
+ continue;
1874
+ }
1875
+ const lines = content.split("\n");
1876
+ const relative = import_node_path5.default.relative(searchDir, filePath);
1877
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
1878
+ if (matches.length >= MAX_MATCHES) break;
1879
+ const line = lines[lineIdx];
1880
+ if (regex.test(line)) {
1881
+ matches.push(`${relative}:${lineIdx + 1}: ${line}`);
1882
+ regex.lastIndex = 0;
1883
+ } else {
1884
+ regex.lastIndex = 0;
1885
+ }
1886
+ }
1887
+ }
1888
+ if (matches.length === 0) {
1889
+ return "No matches found.";
1890
+ }
1891
+ return matches.join("\n");
1892
+ } catch (err) {
1893
+ return `Error searching files: ${err instanceof Error ? err.message : String(err)}`;
1894
+ }
1895
+ }
1896
+ });
1897
+
1898
+ // src/tools/web-fetch.ts
1899
+ var import_zod7 = require("zod");
1900
+ var MAX_RESPONSE_SIZE = 100 * 1024;
1901
+ var WebFetchTool = defineTool({
1902
+ name: "web_fetch",
1903
+ description: "Fetch content from a URL. Returns the response body as text. Supports HTTP and HTTPS.",
1904
+ inputSchema: import_zod7.z.object({
1905
+ url: import_zod7.z.string().url().describe("The URL to fetch"),
1906
+ method: import_zod7.z.enum(["GET", "POST", "PUT", "DELETE"]).optional().describe("HTTP method (default: GET)"),
1907
+ headers: import_zod7.z.record(import_zod7.z.string()).optional().describe("Request headers"),
1908
+ body: import_zod7.z.string().optional().describe("Request body (for POST/PUT)")
1909
+ }),
1910
+ isReadOnly: true,
1911
+ isConcurrencySafe: true,
1912
+ execute: async ({ url, method = "GET", headers, body }, context) => {
1913
+ try {
1914
+ if (context.abortSignal?.aborted) {
1915
+ return "Request aborted before execution";
1916
+ }
1917
+ const timeoutSignal = AbortSignal.timeout(3e4);
1918
+ let signal;
1919
+ if (context.abortSignal) {
1920
+ signal = AbortSignal.any([context.abortSignal, timeoutSignal]);
1921
+ } else {
1922
+ signal = timeoutSignal;
1923
+ }
1924
+ const fetchOptions = {
1925
+ method,
1926
+ headers,
1927
+ signal,
1928
+ redirect: "follow"
1929
+ };
1930
+ if (body && method !== "GET") {
1931
+ fetchOptions.body = body;
1932
+ }
1933
+ const response = await fetch(url, fetchOptions);
1934
+ const contentType = response.headers.get("content-type") ?? "unknown";
1935
+ const status = response.status;
1936
+ const text = await response.text();
1937
+ let result = `Status: ${status}
1938
+ Content-Type: ${contentType}
1939
+
1940
+ `;
1941
+ if (text.length > MAX_RESPONSE_SIZE) {
1942
+ const half = Math.floor(MAX_RESPONSE_SIZE / 2);
1943
+ const notice = `
1944
+
1945
+ ... [response truncated: showing first ${half} and last ${half} of ${text.length} characters] ...
1946
+
1947
+ `;
1948
+ result += text.slice(0, half) + notice + text.slice(-half);
1949
+ } else {
1950
+ result += text;
1951
+ }
1952
+ return result;
1953
+ } catch (err) {
1954
+ if (err instanceof Error && err.name === "AbortError") {
1955
+ return "Request aborted";
1956
+ }
1957
+ return `Error fetching URL: ${err instanceof Error ? err.message : String(err)}`;
1958
+ }
1959
+ }
1960
+ });
1961
+
1962
+ // src/llm/provider.ts
1963
+ var RetryableError = class extends Error {
1964
+ cause;
1965
+ constructor(message, cause) {
1966
+ super(message);
1967
+ this.name = "RetryableError";
1968
+ this.cause = cause;
1969
+ }
1970
+ };
1971
+ var BaseProvider = class {
1972
+ model;
1973
+ maxOutputTokens;
1974
+ temperature;
1975
+ constructor(config) {
1976
+ this.model = config.model;
1977
+ this.maxOutputTokens = config.maxOutputTokens;
1978
+ this.temperature = config.temperature;
1979
+ }
1980
+ mapStopReason(reason) {
1981
+ switch (reason) {
1982
+ case "end_turn":
1983
+ case "stop":
1984
+ return "end_turn";
1985
+ case "tool_use":
1986
+ case "tool_calls":
1987
+ return "tool_use";
1988
+ case "max_tokens":
1989
+ case "length":
1990
+ return "max_tokens";
1991
+ case "stop_sequence":
1992
+ return "stop_sequence";
1993
+ default:
1994
+ return "end_turn";
1995
+ }
1996
+ }
1997
+ };
1998
+
1999
+ // src/llm/anthropic.ts
2000
+ var AnthropicProvider = class extends BaseProvider {
2001
+ apiKey;
2002
+ baseURL;
2003
+ dangerouslySkipAuth;
2004
+ enableCaching;
2005
+ client = null;
2006
+ constructor(config) {
2007
+ super(config);
2008
+ this.apiKey = config.apiKey;
2009
+ this.baseURL = config.baseURL;
2010
+ this.dangerouslySkipAuth = config.dangerouslySkipAuth;
2011
+ this.enableCaching = config.enableCaching ?? false;
2012
+ }
2013
+ async chat(request) {
2014
+ const AnthropicSDK = (await import("@anthropic-ai/sdk")).default;
2015
+ if (!this.client) {
2016
+ const opts = { apiKey: this.apiKey };
2017
+ if (this.baseURL) opts.baseURL = this.baseURL;
2018
+ if (this.dangerouslySkipAuth) opts.dangerouslySkipAuth = true;
2019
+ this.client = new AnthropicSDK(opts);
2020
+ }
2021
+ const messages = this.mapMessages(request.messages);
2022
+ const tools = this.mapTools(request.tools);
2023
+ const maxTokens = request.maxOutputTokens ?? this.maxOutputTokens ?? 4096;
2024
+ const temperature = request.temperature ?? this.temperature;
2025
+ const body = {
2026
+ model: this.model,
2027
+ max_tokens: maxTokens,
2028
+ messages
2029
+ };
2030
+ if (request.systemPrompt) {
2031
+ if (this.enableCaching) {
2032
+ body.system = [
2033
+ { type: "text", text: request.systemPrompt, cache_control: { type: "ephemeral" } }
2034
+ ];
2035
+ } else {
2036
+ body.system = request.systemPrompt;
2037
+ }
2038
+ }
2039
+ if (tools) body.tools = tools;
2040
+ if (temperature !== void 0) body.temperature = temperature;
2041
+ const reqOptions = {};
2042
+ if (request.abortSignal) reqOptions.signal = request.abortSignal;
2043
+ let response;
2044
+ try {
2045
+ response = await this.client.messages.create(body, reqOptions);
2046
+ } catch (err) {
2047
+ const status = err?.status;
2048
+ const code = err?.code;
2049
+ const isNetwork = err instanceof TypeError || typeof code === "string" && ["ECONNRESET", "ECONNREFUSED", "ENOTFOUND", "ETIMEDOUT"].includes(code);
2050
+ if (status === 429 || status === 529 || isNetwork) {
2051
+ throw new RetryableError(
2052
+ err instanceof Error ? err.message : String(err),
2053
+ err instanceof Error ? err : void 0
2054
+ );
2055
+ }
2056
+ throw new Error(
2057
+ `Anthropic API error: ${err instanceof Error ? err.message : String(err)}`,
2058
+ err instanceof Error ? { cause: err } : void 0
2059
+ );
2060
+ }
2061
+ const content = [];
2062
+ for (const block of response.content) {
2063
+ if (block.type === "text") {
2064
+ content.push({ type: "text", text: block.text });
2065
+ } else if (block.type === "tool_use") {
2066
+ content.push({ type: "tool_use", id: block.id, name: block.name, input: block.input });
2067
+ } else if (block.type === "thinking") {
2068
+ content.push({ type: "thinking", thinking: block.thinking });
2069
+ }
2070
+ }
2071
+ return {
2072
+ content,
2073
+ usage: this.mapUsage(response.usage),
2074
+ stopReason: this.mapStopReason(response.stop_reason)
2075
+ };
2076
+ }
2077
+ mapMessages(messages) {
2078
+ return messages.map((msg) => {
2079
+ if (msg.role === "tool") {
2080
+ const textContent = typeof msg.content === "string" ? msg.content : msg.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
2081
+ return {
2082
+ role: "user",
2083
+ content: [
2084
+ {
2085
+ type: "tool_result",
2086
+ tool_use_id: msg.tool_use_id ?? "",
2087
+ content: textContent,
2088
+ ...msg.is_error ? { is_error: true } : {}
2089
+ }
2090
+ ]
2091
+ };
2092
+ }
2093
+ if (msg.role === "assistant") {
2094
+ const content = typeof msg.content === "string" ? [{ type: "text", text: msg.content }] : msg.content.map((block) => this.mapOutgoingBlock(block));
2095
+ return { role: "assistant", content };
2096
+ }
2097
+ if (typeof msg.content === "string") {
2098
+ return { role: "user", content: msg.content };
2099
+ }
2100
+ return {
2101
+ role: "user",
2102
+ content: msg.content.map((block) => this.mapOutgoingBlock(block))
2103
+ };
2104
+ });
2105
+ }
2106
+ mapTools(tools) {
2107
+ if (!tools || tools.length === 0) return void 0;
2108
+ return tools.map((tool) => ({
2109
+ name: tool.name,
2110
+ description: tool.description,
2111
+ input_schema: tool.input_schema,
2112
+ type: "tool"
2113
+ }));
2114
+ }
2115
+ mapUsage(providerUsage) {
2116
+ const u = providerUsage;
2117
+ return {
2118
+ inputTokens: u.input_tokens,
2119
+ outputTokens: u.output_tokens,
2120
+ cacheReadTokens: u.cache_read_input_tokens,
2121
+ cacheWriteTokens: u.cache_creation_input_tokens
2122
+ };
2123
+ }
2124
+ mapOutgoingBlock(block) {
2125
+ switch (block.type) {
2126
+ case "text":
2127
+ return { type: "text", text: block.text };
2128
+ case "tool_use":
2129
+ return { type: "tool_use", id: block.id, name: block.name, input: block.input };
2130
+ case "thinking":
2131
+ return { type: "thinking", thinking: block.thinking };
2132
+ default:
2133
+ return { type: "text", text: "" };
2134
+ }
2135
+ }
2136
+ };
2137
+
2138
+ // src/llm/openai.ts
2139
+ var OpenAIProvider = class extends BaseProvider {
2140
+ apiKey;
2141
+ baseURL;
2142
+ organization;
2143
+ client = null;
2144
+ constructor(config) {
2145
+ super(config);
2146
+ this.apiKey = config.apiKey;
2147
+ this.baseURL = config.baseURL;
2148
+ this.organization = config.organization;
2149
+ }
2150
+ async chat(request) {
2151
+ const OpenAI = (await import("openai")).default;
2152
+ if (!this.client) {
2153
+ const opts = { apiKey: this.apiKey };
2154
+ if (this.baseURL) opts.baseURL = this.baseURL;
2155
+ if (this.organization) opts.organization = this.organization;
2156
+ this.client = new OpenAI(opts);
2157
+ }
2158
+ const mappedMessages = this.mapMessages(request.messages);
2159
+ const messages = [];
2160
+ if (request.systemPrompt) {
2161
+ messages.push({ role: "system", content: request.systemPrompt });
2162
+ }
2163
+ messages.push(...mappedMessages);
2164
+ const tools = this.mapTools(request.tools);
2165
+ const maxTokens = request.maxOutputTokens ?? this.maxOutputTokens;
2166
+ const temperature = request.temperature ?? this.temperature;
2167
+ const body = {
2168
+ model: this.model,
2169
+ messages,
2170
+ stream: false
2171
+ };
2172
+ if (tools) body.tools = tools;
2173
+ if (maxTokens !== void 0) body.max_tokens = maxTokens;
2174
+ if (temperature !== void 0) body.temperature = temperature;
2175
+ const reqOptions = {};
2176
+ if (request.abortSignal) reqOptions.signal = request.abortSignal;
2177
+ let response;
2178
+ try {
2179
+ response = await this.client.chat.completions.create(body, reqOptions);
2180
+ } catch (err) {
2181
+ const status = err?.status;
2182
+ const code = err?.code;
2183
+ const isNetwork = err instanceof TypeError || typeof code === "string" && ["ECONNRESET", "ECONNREFUSED", "ENOTFOUND", "ETIMEDOUT"].includes(code);
2184
+ if (status === 429 || isNetwork) {
2185
+ throw new RetryableError(
2186
+ err instanceof Error ? err.message : String(err),
2187
+ err instanceof Error ? err : void 0
2188
+ );
2189
+ }
2190
+ throw new Error(
2191
+ `OpenAI API error: ${err instanceof Error ? err.message : String(err)}`,
2192
+ err instanceof Error ? { cause: err } : void 0
2193
+ );
2194
+ }
2195
+ const choice = response.choices[0];
2196
+ const content = [];
2197
+ if (choice.message.content) {
2198
+ content.push({ type: "text", text: choice.message.content });
2199
+ }
2200
+ if (choice.message.tool_calls) {
2201
+ for (const tc of choice.message.tool_calls) {
2202
+ let input = {};
2203
+ try {
2204
+ input = JSON.parse(tc.function.arguments);
2205
+ } catch {
2206
+ }
2207
+ content.push({
2208
+ type: "tool_use",
2209
+ id: tc.id,
2210
+ name: tc.function.name,
2211
+ input
2212
+ });
2213
+ }
2214
+ }
2215
+ return {
2216
+ content,
2217
+ usage: this.mapUsage(response.usage),
2218
+ stopReason: this.mapStopReason(choice.finish_reason)
2219
+ };
2220
+ }
2221
+ mapMessages(messages) {
2222
+ return messages.map((msg) => {
2223
+ if (msg.role === "tool") {
2224
+ return {
2225
+ role: "tool",
2226
+ tool_call_id: msg.tool_use_id ?? "",
2227
+ content: typeof msg.content === "string" ? msg.content : msg.content.filter((b) => b.type === "text").map((b) => b.text).join("\n")
2228
+ };
2229
+ }
2230
+ if (msg.role === "assistant") {
2231
+ if (typeof msg.content === "string") {
2232
+ return { role: "assistant", content: msg.content };
2233
+ }
2234
+ const textParts = msg.content.filter((b) => b.type === "text").map((b) => b.text).join("");
2235
+ const toolCalls = msg.content.filter((b) => b.type === "tool_use").map((b) => ({
2236
+ id: b.id,
2237
+ type: "function",
2238
+ function: { name: b.name, arguments: JSON.stringify(b.input) }
2239
+ }));
2240
+ return {
2241
+ role: "assistant",
2242
+ content: textParts || null,
2243
+ ...toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
2244
+ };
2245
+ }
2246
+ return {
2247
+ role: "user",
2248
+ content: typeof msg.content === "string" ? msg.content : msg.content.filter((b) => b.type === "text").map((b) => b.text).join("\n")
2249
+ };
2250
+ });
2251
+ }
2252
+ mapTools(tools) {
2253
+ if (!tools || tools.length === 0) return void 0;
2254
+ return tools.map((tool) => ({
2255
+ type: "function",
2256
+ function: {
2257
+ name: tool.name,
2258
+ description: tool.description,
2259
+ parameters: tool.input_schema
2260
+ }
2261
+ }));
2262
+ }
2263
+ mapUsage(providerUsage) {
2264
+ const u = providerUsage;
2265
+ return {
2266
+ inputTokens: u.prompt_tokens,
2267
+ outputTokens: u.completion_tokens,
2268
+ cacheReadTokens: u.prompt_tokens_details?.cached_tokens
2269
+ };
2270
+ }
2271
+ };
2272
+
2273
+ // src/llm/ollama.ts
2274
+ var OllamaProvider = class {
2275
+ model;
2276
+ baseURL;
2277
+ constructor(config) {
2278
+ this.model = config.model;
2279
+ this.baseURL = config.baseURL ?? "http://localhost:11434/v1";
2280
+ }
2281
+ async chat(request) {
2282
+ const messages = [];
2283
+ if (request.systemPrompt) {
2284
+ messages.push({ role: "system", content: request.systemPrompt });
2285
+ }
2286
+ for (const m of request.messages) {
2287
+ if (m.role === "tool") {
2288
+ messages.push({
2289
+ role: "tool",
2290
+ tool_call_id: m.tool_use_id,
2291
+ content: m.content
2292
+ });
2293
+ continue;
2294
+ }
2295
+ if (m.role === "assistant" && Array.isArray(m.content)) {
2296
+ const textParts = m.content.filter((b) => b.type === "text");
2297
+ const toolParts = m.content.filter((b) => b.type === "tool_use");
2298
+ const msg = {};
2299
+ if (textParts.length > 0) {
2300
+ msg.content = textParts.map((p) => p.text).join("");
2301
+ }
2302
+ if (toolParts.length > 0) {
2303
+ msg.tool_calls = toolParts.map((b) => {
2304
+ const tb = b;
2305
+ return {
2306
+ id: tb.id,
2307
+ type: "function",
2308
+ function: { name: tb.name, arguments: JSON.stringify(tb.input) }
2309
+ };
2310
+ });
2311
+ }
2312
+ msg.role = "assistant";
2313
+ messages.push(msg);
2314
+ continue;
2315
+ }
2316
+ messages.push({ role: m.role, content: m.content });
2317
+ }
2318
+ const body = {
2319
+ model: this.model,
2320
+ messages,
2321
+ stream: false
2322
+ };
2323
+ if (request.maxOutputTokens) {
2324
+ body.max_tokens = request.maxOutputTokens;
2325
+ }
2326
+ if (request.tools?.length) {
2327
+ body.tools = request.tools.map((t) => ({
2328
+ type: "function",
2329
+ function: {
2330
+ name: t.name,
2331
+ description: t.description,
2332
+ parameters: t.input_schema
2333
+ }
2334
+ }));
2335
+ }
2336
+ let response;
2337
+ try {
2338
+ response = await fetch(`${this.baseURL}/chat/completions`, {
2339
+ method: "POST",
2340
+ headers: { "Content-Type": "application/json" },
2341
+ body: JSON.stringify(body),
2342
+ signal: request.abortSignal
2343
+ });
2344
+ } catch (err) {
2345
+ if (err instanceof RetryableError) throw err;
2346
+ throw new RetryableError(
2347
+ `Ollama connection failed: ${err instanceof Error ? err.message : String(err)}`
2348
+ );
2349
+ }
2350
+ if (!response.ok) {
2351
+ const text = await response.text();
2352
+ if (response.status === 429 || response.status === 503 || response.status === 529) {
2353
+ throw new RetryableError(`Ollama API error ${response.status}: ${text.slice(0, 200)}`);
2354
+ }
2355
+ throw new Error(`Ollama API error ${response.status}: ${text.slice(0, 200)}`);
2356
+ }
2357
+ const data = await response.json();
2358
+ const choice = data.choices?.[0];
2359
+ if (!choice) {
2360
+ throw new Error("Ollama returned no choices");
2361
+ }
2362
+ const content = [];
2363
+ let stopReason = "end_turn";
2364
+ if (choice.message?.content) {
2365
+ let text = choice.message.content;
2366
+ text = text.replace(/<think[^>]*>[\s\S]*?<\/think>/gi, "").trim();
2367
+ text = text.replace(/<thinking>[\s\S]*?<\/thinking>/gi, "").trim();
2368
+ text = text.replace(/\[Thinking[^\]]*\]/gi, "").trim();
2369
+ if (text.length > 0) {
2370
+ content.push({ type: "text", text });
2371
+ }
2372
+ }
2373
+ if (choice.message?.tool_calls?.length) {
2374
+ stopReason = "tool_use";
2375
+ for (const tc of choice.message.tool_calls) {
2376
+ let input;
2377
+ try {
2378
+ input = JSON.parse(tc.function.arguments);
2379
+ } catch {
2380
+ input = {};
2381
+ }
2382
+ content.push({
2383
+ type: "tool_use",
2384
+ id: tc.id,
2385
+ name: tc.function.name,
2386
+ input
2387
+ });
2388
+ }
2389
+ }
2390
+ return {
2391
+ content,
2392
+ stopReason,
2393
+ usage: {
2394
+ inputTokens: data.usage?.prompt_tokens ?? 0,
2395
+ outputTokens: data.usage?.completion_tokens ?? 0,
2396
+ cacheReadTokens: 0,
2397
+ cacheWriteTokens: 0
2398
+ }
2399
+ };
2400
+ }
2401
+ };
2402
+
2403
+ // src/llm/index.ts
2404
+ function anthropic(config) {
2405
+ return new AnthropicProvider(config);
2406
+ }
2407
+ function openai(config) {
2408
+ return new OpenAIProvider(config);
2409
+ }
2410
+ function ollama(config) {
2411
+ return new OllamaProvider(config);
2412
+ }
2413
+ var CustomProvider = class {
2414
+ model;
2415
+ chatFn;
2416
+ constructor(config) {
2417
+ this.model = config.model;
2418
+ this.chatFn = config.chat;
2419
+ }
2420
+ chat(request) {
2421
+ return this.chatFn(request);
2422
+ }
2423
+ };
2424
+ function createProvider(config) {
2425
+ switch (config.provider) {
2426
+ case "anthropic":
2427
+ return new AnthropicProvider(config);
2428
+ case "openai":
2429
+ return new OpenAIProvider(config);
2430
+ case "ollama":
2431
+ return new OllamaProvider(config);
2432
+ case "custom":
2433
+ return new CustomProvider(config);
2434
+ }
2435
+ }
2436
+
2437
+ // src/stream.ts
2438
+ function createStreamAggregator() {
2439
+ let text = "";
2440
+ const toolCalls = /* @__PURE__ */ new Map();
2441
+ let usage;
2442
+ let stopReason = "end_turn";
2443
+ function push(event) {
2444
+ switch (event.type) {
2445
+ case "text_delta":
2446
+ text += event.text ?? "";
2447
+ break;
2448
+ case "tool_use_start":
2449
+ if (event.id) {
2450
+ toolCalls.set(event.id, { id: event.id, name: event.name ?? "", input: event.input ?? "" });
2451
+ }
2452
+ stopReason = "tool_use";
2453
+ break;
2454
+ case "tool_use_delta":
2455
+ if (event.id && toolCalls.has(event.id)) {
2456
+ toolCalls.get(event.id).input += event.input ?? "";
2457
+ }
2458
+ break;
2459
+ case "done":
2460
+ usage = event.usage;
2461
+ break;
2462
+ case "tool_result":
2463
+ break;
2464
+ case "error":
2465
+ break;
2466
+ }
2467
+ }
2468
+ function getResponse() {
2469
+ return { text, toolCalls: Array.from(toolCalls.values()), usage, stopReason };
2470
+ }
2471
+ return { push, getResponse };
2472
+ }
2473
+
2474
+ // src/memory/store.ts
2475
+ var InMemoryStore = class {
2476
+ threads = /* @__PURE__ */ new Map();
2477
+ async getThread(threadId) {
2478
+ return this.threads.get(threadId)?.messages ?? [];
2479
+ }
2480
+ async saveThread(threadId, messages) {
2481
+ this.threads.set(threadId, { messages, updatedAt: Date.now() });
2482
+ }
2483
+ async appendMessage(threadId, message) {
2484
+ const thread = this.threads.get(threadId);
2485
+ if (thread) {
2486
+ thread.messages.push(message);
2487
+ thread.updatedAt = Date.now();
2488
+ } else {
2489
+ this.threads.set(threadId, { messages: [message], updatedAt: Date.now() });
2490
+ }
2491
+ }
2492
+ async listThreads(opts) {
2493
+ let entries = Array.from(this.threads.entries()).map(([id, data]) => ({ id, updatedAt: data.updatedAt, messageCount: data.messages.length })).sort((a, b) => b.updatedAt - a.updatedAt);
2494
+ if (opts?.before) entries = entries.filter((e) => e.updatedAt < parseInt(opts.before, 10));
2495
+ return entries.slice(0, opts?.limit ?? 100);
2496
+ }
2497
+ async deleteThread(threadId) {
2498
+ this.threads.delete(threadId);
2499
+ }
2500
+ };
2501
+
2502
+ // src/memory/sqlite.ts
2503
+ function jsonSerialize(messages) {
2504
+ return JSON.stringify(messages, (_, value) => {
2505
+ if (value === void 0) return "__UNDEFINED__";
2506
+ return value;
2507
+ });
2508
+ }
2509
+ function jsonDeserialize(str) {
2510
+ return JSON.parse(str, (_, value) => {
2511
+ if (value === "__UNDEFINED__") return void 0;
2512
+ return value;
2513
+ });
2514
+ }
2515
+ var SQLiteStore = class {
2516
+ store;
2517
+ constructor(dbPath) {
2518
+ try {
2519
+ const Database = require("better-sqlite3");
2520
+ const db = new Database(dbPath ?? "./synth-memory.db");
2521
+ db.pragma("journal_mode = WAL");
2522
+ db.exec(`
2523
+ CREATE TABLE IF NOT EXISTS threads (
2524
+ id TEXT PRIMARY KEY,
2525
+ messages TEXT NOT NULL,
2526
+ updated_at INTEGER NOT NULL,
2527
+ message_count INTEGER NOT NULL DEFAULT 0
2528
+ )
2529
+ `);
2530
+ try {
2531
+ db.exec("ALTER TABLE threads ADD COLUMN message_count INTEGER NOT NULL DEFAULT 0");
2532
+ } catch {
2533
+ }
2534
+ this.store = {
2535
+ async getThread(threadId) {
2536
+ const row = db.prepare("SELECT messages FROM threads WHERE id = ?").get(threadId);
2537
+ if (!row) return [];
2538
+ return jsonDeserialize(row.messages);
2539
+ },
2540
+ async saveThread(threadId, messages) {
2541
+ const now = Date.now();
2542
+ const json = jsonSerialize(messages);
2543
+ const msgCount = messages.length;
2544
+ const exists = db.prepare("SELECT 1 FROM threads WHERE id = ?").get(threadId);
2545
+ const stmt = exists ? db.prepare("UPDATE threads SET messages = ?, updated_at = ?, message_count = ? WHERE id = ?") : db.prepare("INSERT INTO threads (id, messages, updated_at, message_count) VALUES (?, ?, ?, ?)");
2546
+ stmt.run(json, now, msgCount, threadId);
2547
+ },
2548
+ async appendMessage(threadId, message) {
2549
+ const row = db.prepare("SELECT messages FROM threads WHERE id = ?").get(threadId);
2550
+ const messages = row ? jsonDeserialize(row.messages) : [];
2551
+ messages.push(message);
2552
+ const json = jsonSerialize(messages);
2553
+ const now = Date.now();
2554
+ db.prepare("INSERT INTO threads (id, messages, updated_at, message_count) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET messages = excluded.messages, updated_at = excluded.updated_at, message_count = excluded.message_count").run(threadId, json, now, messages.length);
2555
+ },
2556
+ async listThreads(opts) {
2557
+ const rows = db.prepare("SELECT id, updated_at, message_count FROM threads ORDER BY updated_at DESC").all();
2558
+ let results = rows.map((r) => ({ id: r.id, updatedAt: r.updated_at, messageCount: r.message_count }));
2559
+ if (opts?.before) results = results.filter((e) => e.updatedAt < parseInt(opts.before, 10));
2560
+ return results.slice(0, opts?.limit ?? 100);
2561
+ },
2562
+ async deleteThread(threadId) {
2563
+ db.prepare("DELETE FROM threads WHERE id = ?").run(threadId);
2564
+ }
2565
+ };
2566
+ } catch {
2567
+ console.warn("[synth] better-sqlite3 not available, falling back to in-memory store. Install it with: npm install better-sqlite3");
2568
+ this.store = new InMemoryStore();
2569
+ }
2570
+ }
2571
+ async getThread(threadId) {
2572
+ return this.store.getThread(threadId);
2573
+ }
2574
+ async saveThread(threadId, messages) {
2575
+ return this.store.saveThread(threadId, messages);
2576
+ }
2577
+ async appendMessage(threadId, message) {
2578
+ return this.store.appendMessage(threadId, message);
2579
+ }
2580
+ async listThreads(opts) {
2581
+ return this.store.listThreads(opts);
2582
+ }
2583
+ async deleteThread(threadId) {
2584
+ return this.store.deleteThread(threadId);
2585
+ }
2586
+ };
2587
+
2588
+ // src/mcp/client.ts
2589
+ var MCPClient = class {
2590
+ config;
2591
+ tools = [];
2592
+ proc = null;
2593
+ initialized = false;
2594
+ nextId = 1;
2595
+ messageBuffer = "";
2596
+ pendingHandlers = /* @__PURE__ */ new Map();
2597
+ constructor(config) {
2598
+ this.config = config;
2599
+ }
2600
+ send(proc, method, params, id) {
2601
+ const msgId = id ?? this.nextId++;
2602
+ const msg = JSON.stringify({ jsonrpc: "2.0", id: msgId, method, params });
2603
+ proc.stdin.write(msg + "\n");
2604
+ return msgId;
2605
+ }
2606
+ setupMessageHandler(proc) {
2607
+ proc.stdout.on("data", (data) => {
2608
+ this.messageBuffer += data.toString();
2609
+ const lines = this.messageBuffer.split("\n");
2610
+ this.messageBuffer = lines.pop() ?? "";
2611
+ for (const line of lines) {
2612
+ if (!line.trim()) continue;
2613
+ try {
2614
+ const msg = JSON.parse(line);
2615
+ if (msg.id !== void 0 && this.pendingHandlers.has(msg.id)) {
2616
+ const handler = this.pendingHandlers.get(msg.id);
2617
+ this.pendingHandlers.delete(msg.id);
2618
+ handler(msg);
2619
+ } else if (msg.method === "initialize" && msg.id) {
2620
+ proc.stdin.write(JSON.stringify({
2621
+ jsonrpc: "2.0",
2622
+ id: msg.id,
2623
+ result: { protocolVersion: "2024-11-05", capabilities: {}, serverInfo: { name: "synthcode-mcp-proxy", version: "0.6.0" } }
2624
+ }) + "\n");
2625
+ }
2626
+ } catch {
2627
+ }
2628
+ }
2629
+ });
2630
+ proc.stderr.on("data", () => {
2631
+ });
2632
+ }
2633
+ async connect() {
2634
+ if (this.config.type === "stdio") {
2635
+ const { spawn } = await import("child_process");
2636
+ const proc = spawn(this.config.command, this.config.args ?? [], {
2637
+ env: { ...process.env, ...this.config.env },
2638
+ stdio: ["pipe", "pipe", "pipe"]
2639
+ });
2640
+ proc.on("error", (err) => {
2641
+ throw err;
2642
+ });
2643
+ proc.on("close", () => {
2644
+ this.initialized = false;
2645
+ });
2646
+ this.proc = proc;
2647
+ this.setupMessageHandler(proc);
2648
+ await new Promise((resolve) => {
2649
+ const handler = (_data) => {
2650
+ proc.stdout.off("data", handler);
2651
+ resolve();
2652
+ };
2653
+ proc.stdout.on("data", handler);
2654
+ setTimeout(() => {
2655
+ proc.stdout.off("data", handler);
2656
+ resolve();
2657
+ }, 2e3);
2658
+ });
2659
+ this.initialized = true;
2660
+ }
2661
+ if (this.config.type === "sse") {
2662
+ if (!this.config.url) throw new Error("SSE MCP server requires a url");
2663
+ this.initialized = true;
2664
+ }
2665
+ }
2666
+ async listTools() {
2667
+ if (this.config.type === "stdio" && this.proc) {
2668
+ const id = this.nextId++;
2669
+ const tools = await new Promise((resolve, reject) => {
2670
+ const timeout = setTimeout(() => {
2671
+ this.pendingHandlers.delete(id);
2672
+ reject(new Error("MCP listTools timeout"));
2673
+ }, 1e4);
2674
+ this.pendingHandlers.set(id, (msg) => {
2675
+ clearTimeout(timeout);
2676
+ const m = msg;
2677
+ if (m.result?.tools) {
2678
+ resolve(
2679
+ m.result.tools.map((t) => ({
2680
+ name: t.name,
2681
+ description: t.description ?? "",
2682
+ inputSchema: t.inputSchema ?? { type: "object", properties: {} }
2683
+ }))
2684
+ );
2685
+ } else {
2686
+ resolve([]);
2687
+ }
2688
+ });
2689
+ });
2690
+ this.send(this.proc, "tools/list", {}, id);
2691
+ this.tools = tools;
2692
+ return tools;
2693
+ }
2694
+ return [];
2695
+ }
2696
+ async callTool(name, input) {
2697
+ if (this.config.type === "stdio" && this.proc) {
2698
+ const id = this.nextId++;
2699
+ return new Promise((resolve, reject) => {
2700
+ const timeout = setTimeout(() => {
2701
+ this.pendingHandlers.delete(id);
2702
+ reject(new Error("MCP callTool timeout"));
2703
+ }, 3e4);
2704
+ this.pendingHandlers.set(id, (msg) => {
2705
+ clearTimeout(timeout);
2706
+ const m = msg;
2707
+ if (m.result) {
2708
+ resolve(typeof m.result === "string" ? m.result : JSON.stringify(m.result));
2709
+ } else if (m.error) {
2710
+ reject(
2711
+ new Error(
2712
+ m.error.message ?? "MCP tool error"
2713
+ )
2714
+ );
2715
+ }
2716
+ });
2717
+ this.send(this.proc, "tools/call", { name, arguments: input }, id);
2718
+ });
2719
+ }
2720
+ throw new Error("MCP callTool not implemented for SSE type");
2721
+ }
2722
+ async disconnect() {
2723
+ if (this.proc) {
2724
+ this.proc.kill();
2725
+ this.proc = null;
2726
+ }
2727
+ }
2728
+ getTools() {
2729
+ return this.tools;
2730
+ }
2731
+ };
2732
+
2733
+ // src/mcp/adapter.ts
2734
+ async function loadMCPTools(config) {
2735
+ const { z: z8 } = await import("zod");
2736
+ const client = new MCPClient(config);
2737
+ await client.connect();
2738
+ const definitions = await client.listTools();
2739
+ const tools = definitions.map(
2740
+ (def) => defineTool({
2741
+ name: def.name,
2742
+ description: def.description,
2743
+ inputSchema: z8.object({}).passthrough(),
2744
+ isReadOnly: true,
2745
+ isConcurrencySafe: true,
2746
+ execute: async (input, _context) => {
2747
+ const result = await client.callTool(def.name, input);
2748
+ return result;
2749
+ }
2750
+ })
2751
+ );
2752
+ return { tools, client };
2753
+ }
2754
+
2755
+ // src/cli/init.ts
2756
+ var fs7 = __toESM(require("fs"), 1);
2757
+ var path7 = __toESM(require("path"), 1);
2758
+ var import_node_child_process2 = require("child_process");
2759
+ var PACKAGE_JSON = JSON.stringify(
2760
+ {
2761
+ name: "my-agent",
2762
+ version: "1.0.0",
2763
+ type: "module",
2764
+ scripts: {
2765
+ start: "npx tsx src/index.ts",
2766
+ test: "npx vitest run",
2767
+ typecheck: "tsc --noEmit"
2768
+ },
2769
+ dependencies: {
2770
+ "@avasis-ai/synthcode": "^0.6.0",
2771
+ zod: "^3.24.0"
2772
+ },
2773
+ devDependencies: {
2774
+ typescript: "^5.7.0",
2775
+ tsx: "^4.19.0",
2776
+ vitest: "^2.1.0"
2777
+ }
2778
+ },
2779
+ null,
2780
+ 2
2781
+ );
2782
+ var TSCONFIG_JSON = JSON.stringify(
2783
+ {
2784
+ compilerOptions: {
2785
+ target: "ES2022",
2786
+ module: "ESNext",
2787
+ moduleResolution: "bundler",
2788
+ strict: true,
2789
+ esModuleInterop: true,
2790
+ skipLibCheck: true,
2791
+ outDir: "./dist",
2792
+ rootDir: "./src",
2793
+ declaration: true
2794
+ },
2795
+ include: ["src"]
2796
+ },
2797
+ null,
2798
+ 2
2799
+ );
2800
+ var ENV_EXAMPLE = `# Pick one provider and set its key:
2801
+ # ANTHROPIC_API_KEY=your-key-here
2802
+ # OPENAI_API_KEY=your-key-here
2803
+ # For Ollama (local, zero API costs): no key needed
2804
+ `;
2805
+ var INDEX_TS = `import { Agent, BashTool, FileReadTool, FileWriteTool, FileEditTool, GlobTool, GrepTool, WebFetchTool } from "@avasis-ai/synthcode";
2806
+ import { defineTool } from "@avasis-ai/synthcode/tools";
2807
+ import { z } from "zod";
2808
+
2809
+ const agent = new Agent({
2810
+ model: process.env.OLLAMA_MODEL
2811
+ ? await import("@avasis-ai/synthcode/llm").then(m => new m.OllamaProvider({ model: process.env.OLLAMA_MODEL }))
2812
+ : process.env.OPENAI_API_KEY
2813
+ ? await import("@avasis-ai/synthcode/llm").then(m => new m.OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY }))
2814
+ : await import("@avasis-ai/synthcode/llm").then(m => new m.AnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY! })),
2815
+ tools: [
2816
+ BashTool,
2817
+ FileReadTool,
2818
+ FileWriteTool,
2819
+ FileEditTool,
2820
+ GlobTool,
2821
+ GrepTool,
2822
+ WebFetchTool,
2823
+ ],
2824
+ systemPrompt: "You are a helpful AI coding assistant with shell and file access.",
2825
+ });
2826
+
2827
+ const prompt = process.argv[2] || "Hello! What can I help you with?";
2828
+
2829
+ for await (const event of agent.run(prompt)) {
2830
+ if (event.type === "text") process.stdout.write(event.text);
2831
+ if (event.type === "tool_use") console.log(\`\\n [\${event.name}]\`);
2832
+ if (event.type === "tool_result") {
2833
+ if (event.isError) console.log(\`\\n [\${event.name}] FAILED\`);
2834
+ }
2835
+ if (event.type === "thinking") process.stderr.write(\`\\x1b[90m\${event.thinking}\\x1b[0m\`);
2836
+ if (event.type === "done") console.log(\`\\n\\nTokens: \${event.usage.inputTokens} in, \${event.usage.outputTokens} out\`);
2837
+ if (event.type === "error") {
2838
+ console.error(\`\\nError: \${event.error.message}\`);
2839
+ process.exit(1);
2840
+ }
2841
+ }
2842
+ `;
2843
+ async function init(opts) {
2844
+ const name = opts?.projectName ?? "my-agent";
2845
+ const cwd = opts?.cwd ?? process.cwd();
2846
+ const dir = path7.join(cwd, name);
2847
+ fs7.mkdirSync(path7.join(dir, "src", "tools"), { recursive: true });
2848
+ fs7.mkdirSync(path7.join(dir, "tests"), { recursive: true });
2849
+ fs7.writeFileSync(path7.join(dir, "package.json"), PACKAGE_JSON, "utf-8");
2850
+ fs7.writeFileSync(path7.join(dir, "tsconfig.json"), TSCONFIG_JSON, "utf-8");
2851
+ fs7.writeFileSync(path7.join(dir, ".env.example"), ENV_EXAMPLE, "utf-8");
2852
+ fs7.writeFileSync(path7.join(dir, "src", "index.ts"), INDEX_TS, "utf-8");
2853
+ fs7.writeFileSync(path7.join(dir, "tests", "agent.test.ts"), `import { describe, it, expect } from "vitest";
2854
+ describe("Agent", () => { it("should have tools registered", () => { expect(true).toBe(true); }); });
2855
+ `, "utf-8");
2856
+ if (!opts?.skipInstall) {
2857
+ console.log("Installing dependencies...");
2858
+ (0, import_node_child_process2.execSync)("npm install", { cwd: dir, stdio: "inherit" });
2859
+ }
2860
+ console.log(`
2861
+ Created ${name}/`);
2862
+ console.log(` cd ${name} && npm start "your prompt here"
2863
+ `);
2864
+ }
2865
+ // Annotate the CommonJS export names for ESM import in node:
2866
+ 0 && (module.exports = {
2867
+ Agent,
2868
+ AnthropicProvider,
2869
+ BaseProvider,
2870
+ BashTool,
2871
+ ContextManager,
2872
+ CostTracker,
2873
+ DEFAULT_COMPACT_THRESHOLD,
2874
+ DEFAULT_CONTEXT_WINDOW,
2875
+ DEFAULT_MAX_OUTPUT_TOKENS,
2876
+ DEFAULT_MAX_TURNS,
2877
+ DEFAULT_PRICING,
2878
+ FileEditTool,
2879
+ FileReadTool,
2880
+ FileWriteTool,
2881
+ GlobTool,
2882
+ GrepTool,
2883
+ HookRunner,
2884
+ InMemoryStore,
2885
+ MAX_CONCURRENT_TOOLS,
2886
+ MCPClient,
2887
+ OllamaProvider,
2888
+ OpenAIProvider,
2889
+ PermissionEngine,
2890
+ RetryableError,
2891
+ SQLiteStore,
2892
+ ToolRegistry,
2893
+ WebFetchTool,
2894
+ agentLoop,
2895
+ anthropic,
2896
+ createProvider,
2897
+ createStreamAggregator,
2898
+ defineTool,
2899
+ defineToolFromClass,
2900
+ estimateConversationTokens,
2901
+ estimateMessageTokens,
2902
+ estimateTokens,
2903
+ init,
2904
+ loadMCPTools,
2905
+ ollama,
2906
+ openai,
2907
+ orchestrateTools
2908
+ });
2909
+ //# sourceMappingURL=index.cjs.map