@sashabogi/argus-mcp 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +323 -0
- package/dist/cli.mjs +2796 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +241 -0
- package/dist/index.mjs +1048 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mcp.mjs +1467 -0
- package/dist/mcp.mjs.map +1 -0
- package/package.json +74 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1048 @@
|
|
|
1
|
+
// src/core/config.ts
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
var DEFAULT_CONFIG = {
|
|
6
|
+
provider: "ollama",
|
|
7
|
+
providers: {
|
|
8
|
+
ollama: {
|
|
9
|
+
baseUrl: "http://localhost:11434",
|
|
10
|
+
model: "qwen2.5-coder:7b"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
defaults: {
|
|
14
|
+
maxTurns: 15,
|
|
15
|
+
turnTimeoutMs: 6e4,
|
|
16
|
+
snapshotExtensions: ["ts", "tsx", "js", "jsx", "rs", "py", "go", "java", "rb", "php", "swift", "kt", "scala", "c", "cpp", "h", "hpp", "cs", "md"],
|
|
17
|
+
excludePatterns: [
|
|
18
|
+
"node_modules",
|
|
19
|
+
".git",
|
|
20
|
+
"target",
|
|
21
|
+
"dist",
|
|
22
|
+
"build",
|
|
23
|
+
".next",
|
|
24
|
+
"coverage",
|
|
25
|
+
"__pycache__",
|
|
26
|
+
".venv",
|
|
27
|
+
"vendor"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
function getConfigDir() {
|
|
32
|
+
return join(homedir(), ".argus");
|
|
33
|
+
}
|
|
34
|
+
function getConfigPath() {
|
|
35
|
+
return join(getConfigDir(), "config.json");
|
|
36
|
+
}
|
|
37
|
+
function ensureConfigDir() {
|
|
38
|
+
const dir = getConfigDir();
|
|
39
|
+
if (!existsSync(dir)) {
|
|
40
|
+
mkdirSync(dir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function loadConfig() {
|
|
44
|
+
const configPath = getConfigPath();
|
|
45
|
+
if (!existsSync(configPath)) {
|
|
46
|
+
return DEFAULT_CONFIG;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const content = readFileSync(configPath, "utf-8");
|
|
50
|
+
const loaded = JSON.parse(content);
|
|
51
|
+
return {
|
|
52
|
+
...DEFAULT_CONFIG,
|
|
53
|
+
...loaded,
|
|
54
|
+
providers: {
|
|
55
|
+
...DEFAULT_CONFIG.providers,
|
|
56
|
+
...loaded.providers
|
|
57
|
+
},
|
|
58
|
+
defaults: {
|
|
59
|
+
...DEFAULT_CONFIG.defaults,
|
|
60
|
+
...loaded.defaults
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
} catch {
|
|
64
|
+
return DEFAULT_CONFIG;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function saveConfig(config) {
|
|
68
|
+
ensureConfigDir();
|
|
69
|
+
const configPath = getConfigPath();
|
|
70
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
71
|
+
}
|
|
72
|
+
function getProviderConfig(config) {
|
|
73
|
+
const providerConfig = config.providers[config.provider];
|
|
74
|
+
if (!providerConfig) {
|
|
75
|
+
throw new Error(`No configuration found for provider: ${config.provider}`);
|
|
76
|
+
}
|
|
77
|
+
return providerConfig;
|
|
78
|
+
}
|
|
79
|
+
function validateConfig(config) {
|
|
80
|
+
const errors = [];
|
|
81
|
+
const providerConfig = config.providers[config.provider];
|
|
82
|
+
if (!providerConfig) {
|
|
83
|
+
errors.push(`Provider "${config.provider}" is not configured`);
|
|
84
|
+
return errors;
|
|
85
|
+
}
|
|
86
|
+
if (config.provider !== "ollama" && !providerConfig.apiKey) {
|
|
87
|
+
errors.push(`API key is required for provider "${config.provider}"`);
|
|
88
|
+
}
|
|
89
|
+
if (!providerConfig.model) {
|
|
90
|
+
errors.push(`Model is required for provider "${config.provider}"`);
|
|
91
|
+
}
|
|
92
|
+
return errors;
|
|
93
|
+
}
|
|
94
|
+
var PROVIDER_DEFAULTS = {
|
|
95
|
+
zai: {
|
|
96
|
+
baseUrl: "https://api.z.ai/api/coding/paas/v4",
|
|
97
|
+
model: "glm-4.7"
|
|
98
|
+
},
|
|
99
|
+
anthropic: {
|
|
100
|
+
baseUrl: "https://api.anthropic.com",
|
|
101
|
+
model: "claude-sonnet-4-20250514"
|
|
102
|
+
},
|
|
103
|
+
openai: {
|
|
104
|
+
baseUrl: "https://api.openai.com/v1",
|
|
105
|
+
model: "gpt-4o"
|
|
106
|
+
},
|
|
107
|
+
deepseek: {
|
|
108
|
+
baseUrl: "https://api.deepseek.com",
|
|
109
|
+
model: "deepseek-chat"
|
|
110
|
+
},
|
|
111
|
+
ollama: {
|
|
112
|
+
baseUrl: "http://localhost:11434",
|
|
113
|
+
model: "qwen2.5-coder:7b"
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// src/core/snapshot.ts
|
|
118
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
119
|
+
import { join as join2, relative, extname } from "path";
|
|
120
|
+
var DEFAULT_OPTIONS = {
|
|
121
|
+
extensions: ["ts", "tsx", "js", "jsx", "rs", "py", "go", "java", "rb", "php", "swift", "kt", "scala", "c", "cpp", "h", "hpp", "cs", "md", "json"],
|
|
122
|
+
excludePatterns: [
|
|
123
|
+
"node_modules",
|
|
124
|
+
".git",
|
|
125
|
+
"target",
|
|
126
|
+
"dist",
|
|
127
|
+
"build",
|
|
128
|
+
".next",
|
|
129
|
+
"coverage",
|
|
130
|
+
"__pycache__",
|
|
131
|
+
".venv",
|
|
132
|
+
"vendor",
|
|
133
|
+
".DS_Store",
|
|
134
|
+
"*.lock",
|
|
135
|
+
"package-lock.json",
|
|
136
|
+
"*.min.js",
|
|
137
|
+
"*.min.css"
|
|
138
|
+
],
|
|
139
|
+
maxFileSize: 1024 * 1024,
|
|
140
|
+
// 1MB
|
|
141
|
+
includeHidden: false
|
|
142
|
+
};
|
|
143
|
+
function shouldExclude(filePath, patterns) {
|
|
144
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
145
|
+
for (const pattern of patterns) {
|
|
146
|
+
if (pattern.startsWith("*")) {
|
|
147
|
+
const suffix = pattern.slice(1);
|
|
148
|
+
if (normalizedPath.endsWith(suffix)) return true;
|
|
149
|
+
} else if (normalizedPath.includes(`/${pattern}/`) || normalizedPath.endsWith(`/${pattern}`) || normalizedPath === pattern) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
function hasValidExtension(filePath, extensions) {
|
|
156
|
+
const ext = extname(filePath).slice(1).toLowerCase();
|
|
157
|
+
return extensions.includes(ext);
|
|
158
|
+
}
|
|
159
|
+
function collectFiles(dir, options, baseDir = dir) {
|
|
160
|
+
const files = [];
|
|
161
|
+
try {
|
|
162
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
const fullPath = join2(dir, entry.name);
|
|
165
|
+
const relativePath = relative(baseDir, fullPath);
|
|
166
|
+
if (!options.includeHidden && entry.name.startsWith(".")) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (shouldExclude(relativePath, options.excludePatterns)) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (entry.isDirectory()) {
|
|
173
|
+
files.push(...collectFiles(fullPath, options, baseDir));
|
|
174
|
+
} else if (entry.isFile()) {
|
|
175
|
+
if (!hasValidExtension(entry.name, options.extensions)) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
const stats = statSync(fullPath);
|
|
180
|
+
if (stats.size > options.maxFileSize) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
files.push(fullPath);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
} catch (error) {
|
|
190
|
+
}
|
|
191
|
+
return files.sort();
|
|
192
|
+
}
|
|
193
|
+
function createSnapshot(projectPath, outputPath, options = {}) {
|
|
194
|
+
const mergedOptions = {
|
|
195
|
+
...DEFAULT_OPTIONS,
|
|
196
|
+
...options
|
|
197
|
+
};
|
|
198
|
+
if (!existsSync2(projectPath)) {
|
|
199
|
+
throw new Error(`Project path does not exist: ${projectPath}`);
|
|
200
|
+
}
|
|
201
|
+
const stats = statSync(projectPath);
|
|
202
|
+
if (!stats.isDirectory()) {
|
|
203
|
+
throw new Error(`Project path is not a directory: ${projectPath}`);
|
|
204
|
+
}
|
|
205
|
+
const files = collectFiles(projectPath, mergedOptions);
|
|
206
|
+
const lines = [];
|
|
207
|
+
lines.push("================================================================================");
|
|
208
|
+
lines.push("CODEBASE SNAPSHOT");
|
|
209
|
+
lines.push(`Project: ${projectPath}`);
|
|
210
|
+
lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
211
|
+
lines.push(`Extensions: ${mergedOptions.extensions.join(", ")}`);
|
|
212
|
+
lines.push(`Files: ${files.length}`);
|
|
213
|
+
lines.push("================================================================================");
|
|
214
|
+
lines.push("");
|
|
215
|
+
for (const filePath of files) {
|
|
216
|
+
const relativePath = relative(projectPath, filePath);
|
|
217
|
+
lines.push("");
|
|
218
|
+
lines.push("================================================================================");
|
|
219
|
+
lines.push(`FILE: ./${relativePath}`);
|
|
220
|
+
lines.push("================================================================================");
|
|
221
|
+
try {
|
|
222
|
+
const content2 = readFileSync2(filePath, "utf-8");
|
|
223
|
+
lines.push(content2);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
lines.push("[Unable to read file]");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const content = lines.join("\n");
|
|
229
|
+
writeFileSync2(outputPath, content);
|
|
230
|
+
const totalLines = content.split("\n").length;
|
|
231
|
+
const totalSize = Buffer.byteLength(content, "utf-8");
|
|
232
|
+
return {
|
|
233
|
+
outputPath,
|
|
234
|
+
fileCount: files.length,
|
|
235
|
+
totalLines,
|
|
236
|
+
totalSize,
|
|
237
|
+
files: files.map((f) => relative(projectPath, f))
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function getSnapshotStats(snapshotPath) {
|
|
241
|
+
if (!existsSync2(snapshotPath)) {
|
|
242
|
+
throw new Error(`Snapshot file does not exist: ${snapshotPath}`);
|
|
243
|
+
}
|
|
244
|
+
const content = readFileSync2(snapshotPath, "utf-8");
|
|
245
|
+
const totalLines = content.split("\n").length;
|
|
246
|
+
const totalSize = Buffer.byteLength(content, "utf-8");
|
|
247
|
+
const fileMatches = content.match(/^FILE: /gm);
|
|
248
|
+
const fileCount = fileMatches ? fileMatches.length : 0;
|
|
249
|
+
return { fileCount, totalLines, totalSize };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/core/engine.ts
|
|
253
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
254
|
+
|
|
255
|
+
// src/core/prompts.ts
|
|
256
|
+
var NUCLEUS_COMMANDS = `
|
|
257
|
+
COMMANDS (output ONE per turn):
|
|
258
|
+
(grep "pattern") - Find lines matching regex
|
|
259
|
+
(grep "pattern" "i") - Case-insensitive search
|
|
260
|
+
(count RESULTS) - Count matches
|
|
261
|
+
(take RESULTS n) - First n results
|
|
262
|
+
(filter RESULTS (lambda (x) (match x.line "pattern" 0))) - Filter results
|
|
263
|
+
(map RESULTS (lambda (x) x.line)) - Extract just the lines
|
|
264
|
+
|
|
265
|
+
VARIABLES: RESULTS = last result, _1 _2 _3 = results from turn 1,2,3
|
|
266
|
+
|
|
267
|
+
TO ANSWER: <<<FINAL>>>your answer<<<END>>>
|
|
268
|
+
`;
|
|
269
|
+
var CODEBASE_ANALYSIS_PROMPT = `You are analyzing a SOFTWARE CODEBASE snapshot to help a developer understand it.
|
|
270
|
+
|
|
271
|
+
The snapshot contains source files concatenated with "FILE: ./path/to/file" markers.
|
|
272
|
+
|
|
273
|
+
${NUCLEUS_COMMANDS}
|
|
274
|
+
|
|
275
|
+
## STRATEGY FOR CODEBASE SNAPSHOTS
|
|
276
|
+
|
|
277
|
+
**To find modules/directories:**
|
|
278
|
+
(grep "FILE:.*src/[^/]+/") - top-level source dirs
|
|
279
|
+
(grep "FILE:.*mod\\.rs") - Rust modules
|
|
280
|
+
(grep "FILE:.*index\\.(ts|js)") - JS/TS modules
|
|
281
|
+
|
|
282
|
+
**To find implementations:**
|
|
283
|
+
(grep "fn function_name") - Rust functions
|
|
284
|
+
(grep "function|const.*=>") - JS functions
|
|
285
|
+
(grep "class ClassName") - Classes
|
|
286
|
+
(grep "struct |type |interface") - Type definitions
|
|
287
|
+
|
|
288
|
+
**To understand structure:**
|
|
289
|
+
(grep "FILE:") - List all files
|
|
290
|
+
(grep "use |import |require") - Find dependencies
|
|
291
|
+
(grep "pub |export") - Public APIs
|
|
292
|
+
|
|
293
|
+
## RULES
|
|
294
|
+
1. Output ONLY a Nucleus command OR a final answer
|
|
295
|
+
2. NO explanations, NO markdown formatting in commands
|
|
296
|
+
3. MUST provide final answer by turn 8
|
|
297
|
+
4. If turn 6+, start summarizing what you found
|
|
298
|
+
|
|
299
|
+
## EXAMPLE SESSION
|
|
300
|
+
Turn 1: (grep "FILE:.*src/[^/]+/mod\\.rs")
|
|
301
|
+
Turn 2: (take RESULTS 15)
|
|
302
|
+
Turn 3: <<<FINAL>>>The codebase has these main modules:
|
|
303
|
+
- src/auth/ - Authentication handling
|
|
304
|
+
- src/api/ - API endpoints
|
|
305
|
+
- src/db/ - Database layer
|
|
306
|
+
...<<<END>>>
|
|
307
|
+
`;
|
|
308
|
+
var ARCHITECTURE_PROMPT = `You are generating an ARCHITECTURE SUMMARY of a codebase.
|
|
309
|
+
|
|
310
|
+
${NUCLEUS_COMMANDS}
|
|
311
|
+
|
|
312
|
+
## YOUR TASK
|
|
313
|
+
Create a summary suitable for CLAUDE.md that helps Claude Code understand this project after context compaction.
|
|
314
|
+
|
|
315
|
+
## SEARCH STRATEGY (do these in order)
|
|
316
|
+
1. (grep "FILE:.*mod\\.rs|FILE:.*index\\.(ts|js)") - Find module entry points
|
|
317
|
+
2. (take RESULTS 20) - Limit results
|
|
318
|
+
3. Based on file paths, provide your summary
|
|
319
|
+
|
|
320
|
+
## OUTPUT FORMAT
|
|
321
|
+
Your final answer should be structured like:
|
|
322
|
+
|
|
323
|
+
## Modules
|
|
324
|
+
- **module_name/** - Brief description based on files found
|
|
325
|
+
|
|
326
|
+
## Key Patterns
|
|
327
|
+
- Pattern observations from the code
|
|
328
|
+
|
|
329
|
+
## Important Files
|
|
330
|
+
- List key files and their apparent purpose
|
|
331
|
+
|
|
332
|
+
PROVIDE FINAL ANSWER BY TURN 6.
|
|
333
|
+
`;
|
|
334
|
+
var IMPLEMENTATION_PROMPT = `You are finding HOW something works in a codebase.
|
|
335
|
+
|
|
336
|
+
${NUCLEUS_COMMANDS}
|
|
337
|
+
|
|
338
|
+
## STRATEGY
|
|
339
|
+
1. (grep "FILE:.*keyword") - Find files related to the concept
|
|
340
|
+
2. (grep "keyword") - Find all mentions
|
|
341
|
+
3. (take RESULTS 30) - Limit if too many results
|
|
342
|
+
4. Look for function definitions, structs, classes
|
|
343
|
+
5. PROVIDE FINAL ANSWER based on file paths and code patterns found
|
|
344
|
+
|
|
345
|
+
## IMPORTANT
|
|
346
|
+
- You have 12 turns maximum
|
|
347
|
+
- By turn 8, START WRITING YOUR FINAL ANSWER
|
|
348
|
+
- Use what you've found - don't keep searching indefinitely
|
|
349
|
+
- It's better to give a partial answer than no answer
|
|
350
|
+
|
|
351
|
+
## OUTPUT FORMAT
|
|
352
|
+
Your final answer should explain:
|
|
353
|
+
- Which files contain the implementation
|
|
354
|
+
- Key functions/structs/classes involved
|
|
355
|
+
- Basic flow of how it works (based on what you found)
|
|
356
|
+
`;
|
|
357
|
+
var COUNT_PROMPT = `You are counting items in a codebase.
|
|
358
|
+
|
|
359
|
+
${NUCLEUS_COMMANDS}
|
|
360
|
+
|
|
361
|
+
## STRATEGY
|
|
362
|
+
1. (grep "pattern")
|
|
363
|
+
2. (count RESULTS)
|
|
364
|
+
3. <<<FINAL>>>There are N items matching the pattern.<<<END>>>
|
|
365
|
+
|
|
366
|
+
THIS SHOULD TAKE 2-3 TURNS MAXIMUM.
|
|
367
|
+
`;
|
|
368
|
+
var SEARCH_PROMPT = `You are searching for specific code.
|
|
369
|
+
|
|
370
|
+
${NUCLEUS_COMMANDS}
|
|
371
|
+
|
|
372
|
+
## STRATEGY
|
|
373
|
+
1. (grep "pattern")
|
|
374
|
+
2. (take RESULTS 20) if too many
|
|
375
|
+
3. Report what you found with file paths
|
|
376
|
+
|
|
377
|
+
PROVIDE FINAL ANSWER BY TURN 4.
|
|
378
|
+
`;
|
|
379
|
+
function selectPrompt(query) {
|
|
380
|
+
const q = query.toLowerCase();
|
|
381
|
+
if (/how many|count|number of|total|how much/.test(q)) {
|
|
382
|
+
return COUNT_PROMPT;
|
|
383
|
+
}
|
|
384
|
+
if (/^(find|search|show|list|where is|locate)\b/.test(q) && q.length < 50) {
|
|
385
|
+
return SEARCH_PROMPT;
|
|
386
|
+
}
|
|
387
|
+
if (/architect|structure|overview|module|organization|main.*component|summar|layout/.test(q)) {
|
|
388
|
+
return ARCHITECTURE_PROMPT;
|
|
389
|
+
}
|
|
390
|
+
if (/how does|how is|implement|work|handle|process|flow/.test(q)) {
|
|
391
|
+
return IMPLEMENTATION_PROMPT;
|
|
392
|
+
}
|
|
393
|
+
return CODEBASE_ANALYSIS_PROMPT;
|
|
394
|
+
}
|
|
395
|
+
function buildSystemPrompt(query) {
|
|
396
|
+
return selectPrompt(query);
|
|
397
|
+
}
|
|
398
|
+
function getTurnLimit(query) {
|
|
399
|
+
const q = query.toLowerCase();
|
|
400
|
+
if (/how many|count/.test(q)) return 5;
|
|
401
|
+
if (/^(find|search|show|list)\b/.test(q) && q.length < 50) return 6;
|
|
402
|
+
if (/architect|overview|structure|module/.test(q)) return 12;
|
|
403
|
+
if (/how does|how is|implement|work/.test(q)) return 12;
|
|
404
|
+
return 12;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/core/engine.ts
|
|
408
|
+
function executeNucleus(command, content, bindings) {
|
|
409
|
+
const parsed = parseSExpression(command);
|
|
410
|
+
if (!parsed) {
|
|
411
|
+
throw new Error(`Failed to parse command: ${command}`);
|
|
412
|
+
}
|
|
413
|
+
return evaluateExpr(parsed, content, bindings);
|
|
414
|
+
}
|
|
415
|
+
function parseSExpression(input) {
|
|
416
|
+
const tokens = tokenize(input.trim());
|
|
417
|
+
if (tokens.length === 0) return null;
|
|
418
|
+
let pos = 0;
|
|
419
|
+
function parse() {
|
|
420
|
+
const token = tokens[pos++];
|
|
421
|
+
if (token === "(") {
|
|
422
|
+
const list = [];
|
|
423
|
+
while (tokens[pos] !== ")" && pos < tokens.length) {
|
|
424
|
+
list.push(parse());
|
|
425
|
+
}
|
|
426
|
+
pos++;
|
|
427
|
+
return list;
|
|
428
|
+
} else if (token.startsWith('"')) {
|
|
429
|
+
return token.slice(1, -1).replace(/\\"/g, '"');
|
|
430
|
+
} else if (/^-?\d+(\.\d+)?$/.test(token)) {
|
|
431
|
+
return token;
|
|
432
|
+
} else {
|
|
433
|
+
return token;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return parse();
|
|
437
|
+
}
|
|
438
|
+
function tokenize(input) {
|
|
439
|
+
const tokens = [];
|
|
440
|
+
let i = 0;
|
|
441
|
+
while (i < input.length) {
|
|
442
|
+
const char = input[i];
|
|
443
|
+
if (/\s/.test(char)) {
|
|
444
|
+
i++;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (char === "(" || char === ")") {
|
|
448
|
+
tokens.push(char);
|
|
449
|
+
i++;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (char === '"') {
|
|
453
|
+
let str = '"';
|
|
454
|
+
i++;
|
|
455
|
+
while (i < input.length && input[i] !== '"') {
|
|
456
|
+
if (input[i] === "\\" && i + 1 < input.length) {
|
|
457
|
+
str += input[i] + input[i + 1];
|
|
458
|
+
i += 2;
|
|
459
|
+
} else {
|
|
460
|
+
str += input[i];
|
|
461
|
+
i++;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
str += '"';
|
|
465
|
+
i++;
|
|
466
|
+
tokens.push(str);
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
let sym = "";
|
|
470
|
+
while (i < input.length && !/[\s()]/.test(input[i])) {
|
|
471
|
+
sym += input[i];
|
|
472
|
+
i++;
|
|
473
|
+
}
|
|
474
|
+
tokens.push(sym);
|
|
475
|
+
}
|
|
476
|
+
return tokens;
|
|
477
|
+
}
|
|
478
|
+
function evaluateExpr(expr, content, bindings) {
|
|
479
|
+
if (typeof expr === "string") {
|
|
480
|
+
if (bindings.has(expr)) {
|
|
481
|
+
return bindings.get(expr);
|
|
482
|
+
}
|
|
483
|
+
if (/^-?\d+(\.\d+)?$/.test(expr)) {
|
|
484
|
+
return parseFloat(expr);
|
|
485
|
+
}
|
|
486
|
+
return expr;
|
|
487
|
+
}
|
|
488
|
+
if (!Array.isArray(expr) || expr.length === 0) {
|
|
489
|
+
return expr;
|
|
490
|
+
}
|
|
491
|
+
const [op, ...args] = expr;
|
|
492
|
+
switch (op) {
|
|
493
|
+
case "grep": {
|
|
494
|
+
const pattern = evaluateExpr(args[0], content, bindings);
|
|
495
|
+
const flags = args[1] ? evaluateExpr(args[1], content, bindings) : "";
|
|
496
|
+
const regex = new RegExp(pattern, flags + "g");
|
|
497
|
+
const lines = content.split("\n");
|
|
498
|
+
const matches = [];
|
|
499
|
+
let charIndex = 0;
|
|
500
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
501
|
+
const line = lines[lineNum];
|
|
502
|
+
let match;
|
|
503
|
+
const lineRegex = new RegExp(pattern, flags + "g");
|
|
504
|
+
while ((match = lineRegex.exec(line)) !== null) {
|
|
505
|
+
matches.push({
|
|
506
|
+
match: match[0],
|
|
507
|
+
line,
|
|
508
|
+
lineNum: lineNum + 1,
|
|
509
|
+
index: charIndex + match.index,
|
|
510
|
+
groups: match.slice(1)
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
charIndex += line.length + 1;
|
|
514
|
+
}
|
|
515
|
+
return matches;
|
|
516
|
+
}
|
|
517
|
+
case "count": {
|
|
518
|
+
const arr = evaluateExpr(args[0], content, bindings);
|
|
519
|
+
if (Array.isArray(arr)) return arr.length;
|
|
520
|
+
return 0;
|
|
521
|
+
}
|
|
522
|
+
case "map": {
|
|
523
|
+
const arr = evaluateExpr(args[0], content, bindings);
|
|
524
|
+
const lambdaExpr = args[1];
|
|
525
|
+
if (!Array.isArray(lambdaExpr) || lambdaExpr[0] !== "lambda") {
|
|
526
|
+
throw new Error("map requires a lambda expression");
|
|
527
|
+
}
|
|
528
|
+
const params = lambdaExpr[1];
|
|
529
|
+
const body = lambdaExpr[2];
|
|
530
|
+
const paramName = Array.isArray(params) ? params[0] : params;
|
|
531
|
+
return arr.map((item) => {
|
|
532
|
+
const localBindings = new Map(bindings);
|
|
533
|
+
localBindings.set(paramName, item);
|
|
534
|
+
return evaluateExpr(body, content, localBindings);
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
case "filter": {
|
|
538
|
+
const arr = evaluateExpr(args[0], content, bindings);
|
|
539
|
+
const lambdaExpr = args[1];
|
|
540
|
+
if (!Array.isArray(lambdaExpr) || lambdaExpr[0] !== "lambda") {
|
|
541
|
+
throw new Error("filter requires a lambda expression");
|
|
542
|
+
}
|
|
543
|
+
const params = lambdaExpr[1];
|
|
544
|
+
const body = lambdaExpr[2];
|
|
545
|
+
const paramName = Array.isArray(params) ? params[0] : params;
|
|
546
|
+
return arr.filter((item) => {
|
|
547
|
+
const localBindings = new Map(bindings);
|
|
548
|
+
localBindings.set(paramName, item);
|
|
549
|
+
return evaluateExpr(body, content, localBindings);
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
case "first": {
|
|
553
|
+
const arr = evaluateExpr(args[0], content, bindings);
|
|
554
|
+
return arr[0];
|
|
555
|
+
}
|
|
556
|
+
case "last": {
|
|
557
|
+
const arr = evaluateExpr(args[0], content, bindings);
|
|
558
|
+
return arr[arr.length - 1];
|
|
559
|
+
}
|
|
560
|
+
case "take": {
|
|
561
|
+
const arr = evaluateExpr(args[0], content, bindings);
|
|
562
|
+
const n = evaluateExpr(args[1], content, bindings);
|
|
563
|
+
return arr.slice(0, n);
|
|
564
|
+
}
|
|
565
|
+
case "sort": {
|
|
566
|
+
const arr = evaluateExpr(args[0], content, bindings);
|
|
567
|
+
const key = evaluateExpr(args[1], content, bindings);
|
|
568
|
+
return [...arr].sort((a, b) => {
|
|
569
|
+
const aVal = a[key];
|
|
570
|
+
const bVal = b[key];
|
|
571
|
+
if (typeof aVal === "number" && typeof bVal === "number") {
|
|
572
|
+
return aVal - bVal;
|
|
573
|
+
}
|
|
574
|
+
return String(aVal).localeCompare(String(bVal));
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
case "match": {
|
|
578
|
+
const str = evaluateExpr(args[0], content, bindings);
|
|
579
|
+
const strValue = typeof str === "object" && str !== null && "line" in str ? str.line : String(str);
|
|
580
|
+
const pattern = evaluateExpr(args[1], content, bindings);
|
|
581
|
+
const group = args[2] ? evaluateExpr(args[2], content, bindings) : 0;
|
|
582
|
+
const regex = new RegExp(pattern);
|
|
583
|
+
const match = strValue.match(regex);
|
|
584
|
+
if (match) {
|
|
585
|
+
return match[group] || null;
|
|
586
|
+
}
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
default:
|
|
590
|
+
throw new Error(`Unknown command: ${op}`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function extractCommand(response) {
|
|
594
|
+
const finalMatch = response.match(/<<<FINAL>>>([\s\S]*?)<<<END>>>/);
|
|
595
|
+
if (finalMatch) {
|
|
596
|
+
return { finalAnswer: finalMatch[1].trim() };
|
|
597
|
+
}
|
|
598
|
+
const sexpMatch = response.match(/\([^)]*(?:\([^)]*\)[^)]*)*\)/);
|
|
599
|
+
if (sexpMatch) {
|
|
600
|
+
return { command: sexpMatch[0] };
|
|
601
|
+
}
|
|
602
|
+
return {};
|
|
603
|
+
}
|
|
604
|
+
async function analyze(provider, documentPath, query, options = {}) {
|
|
605
|
+
const {
|
|
606
|
+
maxTurns = 15,
|
|
607
|
+
verbose = false,
|
|
608
|
+
onProgress
|
|
609
|
+
} = options;
|
|
610
|
+
const dynamicLimit = Math.min(getTurnLimit(query), maxTurns);
|
|
611
|
+
const content = readFileSync3(documentPath, "utf-8");
|
|
612
|
+
const fileCount = (content.match(/^FILE:/gm) || []).length;
|
|
613
|
+
const lineCount = content.split("\n").length;
|
|
614
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
615
|
+
const commands = [];
|
|
616
|
+
const messages = [
|
|
617
|
+
{
|
|
618
|
+
role: "system",
|
|
619
|
+
content: buildSystemPrompt(query)
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
role: "user",
|
|
623
|
+
content: `CODEBASE SNAPSHOT:
|
|
624
|
+
- Total size: ${content.length.toLocaleString()} characters
|
|
625
|
+
- Files: ${fileCount}
|
|
626
|
+
- Lines: ${lineCount.toLocaleString()}
|
|
627
|
+
|
|
628
|
+
Files are marked with "FILE: ./path/to/file" headers.
|
|
629
|
+
|
|
630
|
+
QUERY: ${query}
|
|
631
|
+
|
|
632
|
+
Begin analysis. You have ${dynamicLimit} turns maximum - provide final answer before then.`
|
|
633
|
+
}
|
|
634
|
+
];
|
|
635
|
+
for (let turn = 1; turn <= dynamicLimit; turn++) {
|
|
636
|
+
const isLastTurn = turn === dynamicLimit;
|
|
637
|
+
const isNearEnd = turn >= dynamicLimit - 2;
|
|
638
|
+
if (verbose) {
|
|
639
|
+
console.log(`
|
|
640
|
+
[Turn ${turn}/${dynamicLimit}] Querying LLM...`);
|
|
641
|
+
}
|
|
642
|
+
const result = await provider.complete(messages);
|
|
643
|
+
const response = result.content;
|
|
644
|
+
if (verbose) {
|
|
645
|
+
console.log(`[Turn ${turn}] Response: ${response.slice(0, 200)}...`);
|
|
646
|
+
}
|
|
647
|
+
const extracted = extractCommand(response);
|
|
648
|
+
if (extracted.finalAnswer) {
|
|
649
|
+
return {
|
|
650
|
+
answer: extracted.finalAnswer,
|
|
651
|
+
turns: turn,
|
|
652
|
+
commands,
|
|
653
|
+
success: true
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
if (!extracted.command) {
|
|
657
|
+
messages.push({ role: "assistant", content: response });
|
|
658
|
+
messages.push({ role: "user", content: "Please provide a Nucleus command or final answer." });
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
const command = extracted.command;
|
|
662
|
+
commands.push(command);
|
|
663
|
+
if (verbose) {
|
|
664
|
+
console.log(`[Turn ${turn}] Command: ${command}`);
|
|
665
|
+
}
|
|
666
|
+
try {
|
|
667
|
+
const cmdResult = executeNucleus(command, content, bindings);
|
|
668
|
+
bindings.set("RESULTS", cmdResult);
|
|
669
|
+
bindings.set(`_${turn}`, cmdResult);
|
|
670
|
+
const resultStr = JSON.stringify(cmdResult, null, 2);
|
|
671
|
+
const truncatedResult = resultStr.length > 2e3 ? resultStr.slice(0, 2e3) + "...[truncated]" : resultStr;
|
|
672
|
+
if (verbose) {
|
|
673
|
+
console.log(`[Turn ${turn}] Result: ${truncatedResult.slice(0, 500)}...`);
|
|
674
|
+
}
|
|
675
|
+
onProgress?.(turn, command, cmdResult);
|
|
676
|
+
messages.push({ role: "assistant", content: command });
|
|
677
|
+
let userMessage = `Result:
|
|
678
|
+
${truncatedResult}`;
|
|
679
|
+
if (isNearEnd && !isLastTurn) {
|
|
680
|
+
userMessage += `
|
|
681
|
+
|
|
682
|
+
\u26A0\uFE0F ${dynamicLimit - turn} turns remaining. Start forming your final answer.`;
|
|
683
|
+
}
|
|
684
|
+
messages.push({ role: "user", content: userMessage });
|
|
685
|
+
if (isLastTurn) {
|
|
686
|
+
messages.push({
|
|
687
|
+
role: "user",
|
|
688
|
+
content: "STOP SEARCHING. Based on everything you found, provide your final answer NOW using <<<FINAL>>>your answer<<<END>>>"
|
|
689
|
+
});
|
|
690
|
+
const finalResult = await provider.complete(messages);
|
|
691
|
+
const finalExtracted = extractCommand(finalResult.content);
|
|
692
|
+
if (finalExtracted.finalAnswer) {
|
|
693
|
+
return {
|
|
694
|
+
answer: finalExtracted.finalAnswer,
|
|
695
|
+
turns: turn,
|
|
696
|
+
commands,
|
|
697
|
+
success: true
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
answer: finalResult.content,
|
|
702
|
+
turns: turn,
|
|
703
|
+
commands,
|
|
704
|
+
success: true
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
} catch (error) {
|
|
708
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
709
|
+
if (verbose) {
|
|
710
|
+
console.log(`[Turn ${turn}] Error: ${errMsg}`);
|
|
711
|
+
}
|
|
712
|
+
messages.push({ role: "assistant", content: command });
|
|
713
|
+
messages.push({ role: "user", content: `Error executing command: ${errMsg}` });
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return {
|
|
717
|
+
answer: "Maximum turns reached without final answer",
|
|
718
|
+
turns: dynamicLimit,
|
|
719
|
+
commands,
|
|
720
|
+
success: false,
|
|
721
|
+
error: "Max turns reached"
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
function searchDocument(documentPath, pattern, options = {}) {
|
|
725
|
+
const content = readFileSync3(documentPath, "utf-8");
|
|
726
|
+
const flags = options.caseInsensitive ? "gi" : "g";
|
|
727
|
+
const regex = new RegExp(pattern, flags);
|
|
728
|
+
const lines = content.split("\n");
|
|
729
|
+
const matches = [];
|
|
730
|
+
let charIndex = 0;
|
|
731
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
732
|
+
const line = lines[lineNum];
|
|
733
|
+
let match;
|
|
734
|
+
const lineRegex = new RegExp(pattern, flags);
|
|
735
|
+
while ((match = lineRegex.exec(line)) !== null) {
|
|
736
|
+
matches.push({
|
|
737
|
+
match: match[0],
|
|
738
|
+
line,
|
|
739
|
+
lineNum: lineNum + 1,
|
|
740
|
+
index: charIndex + match.index,
|
|
741
|
+
groups: match.slice(1)
|
|
742
|
+
});
|
|
743
|
+
if (options.maxResults && matches.length >= options.maxResults) {
|
|
744
|
+
return matches;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
charIndex += line.length + 1;
|
|
748
|
+
}
|
|
749
|
+
return matches;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// src/providers/openai-compatible.ts
|
|
753
|
+
var OpenAICompatibleProvider = class {
|
|
754
|
+
name;
|
|
755
|
+
config;
|
|
756
|
+
constructor(name, config) {
|
|
757
|
+
this.name = name;
|
|
758
|
+
this.config = config;
|
|
759
|
+
if (!config.apiKey) {
|
|
760
|
+
throw new Error(`API key is required for ${name} provider`);
|
|
761
|
+
}
|
|
762
|
+
if (!config.baseUrl) {
|
|
763
|
+
throw new Error(`Base URL is required for ${name} provider`);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
async complete(messages, options) {
|
|
767
|
+
const endpoint = `${this.config.baseUrl}/chat/completions`;
|
|
768
|
+
const body = {
|
|
769
|
+
model: this.config.model,
|
|
770
|
+
messages: messages.map((m) => ({
|
|
771
|
+
role: m.role,
|
|
772
|
+
content: m.content
|
|
773
|
+
})),
|
|
774
|
+
temperature: options?.temperature ?? this.config.options?.temperature ?? 0.2,
|
|
775
|
+
max_tokens: options?.maxTokens ?? this.config.options?.max_tokens ?? 4096,
|
|
776
|
+
...options?.stopSequences && { stop: options.stopSequences }
|
|
777
|
+
};
|
|
778
|
+
const response = await fetch(endpoint, {
|
|
779
|
+
method: "POST",
|
|
780
|
+
headers: {
|
|
781
|
+
"Content-Type": "application/json",
|
|
782
|
+
"Authorization": `Bearer ${this.config.apiKey}`
|
|
783
|
+
},
|
|
784
|
+
body: JSON.stringify(body)
|
|
785
|
+
});
|
|
786
|
+
if (!response.ok) {
|
|
787
|
+
const errorText = await response.text();
|
|
788
|
+
throw new Error(`${this.name} API error (${response.status}): ${errorText}`);
|
|
789
|
+
}
|
|
790
|
+
const data = await response.json();
|
|
791
|
+
const choice = data.choices[0];
|
|
792
|
+
return {
|
|
793
|
+
content: choice.message.content || "",
|
|
794
|
+
finishReason: choice.finish_reason === "stop" ? "stop" : choice.finish_reason === "length" ? "length" : "error",
|
|
795
|
+
usage: data.usage ? {
|
|
796
|
+
promptTokens: data.usage.prompt_tokens,
|
|
797
|
+
completionTokens: data.usage.completion_tokens,
|
|
798
|
+
totalTokens: data.usage.total_tokens
|
|
799
|
+
} : void 0
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
async healthCheck() {
|
|
803
|
+
try {
|
|
804
|
+
const result = await this.complete([
|
|
805
|
+
{ role: "user", content: 'Say "ok"' }
|
|
806
|
+
], { maxTokens: 10 });
|
|
807
|
+
return result.content.length > 0;
|
|
808
|
+
} catch {
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
function createZAIProvider(config) {
|
|
814
|
+
return new OpenAICompatibleProvider("ZAI", {
|
|
815
|
+
...config,
|
|
816
|
+
baseUrl: config.baseUrl || "https://api.z.ai/api/coding/paas/v4",
|
|
817
|
+
model: config.model || "glm-4.7"
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
function createOpenAIProvider(config) {
|
|
821
|
+
return new OpenAICompatibleProvider("OpenAI", {
|
|
822
|
+
...config,
|
|
823
|
+
baseUrl: config.baseUrl || "https://api.openai.com/v1",
|
|
824
|
+
model: config.model || "gpt-4o"
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
function createDeepSeekProvider(config) {
|
|
828
|
+
return new OpenAICompatibleProvider("DeepSeek", {
|
|
829
|
+
...config,
|
|
830
|
+
baseUrl: config.baseUrl || "https://api.deepseek.com",
|
|
831
|
+
model: config.model || "deepseek-chat"
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// src/providers/ollama.ts
|
|
836
|
+
var OllamaProvider = class {
|
|
837
|
+
name = "Ollama";
|
|
838
|
+
config;
|
|
839
|
+
constructor(config) {
|
|
840
|
+
this.config = {
|
|
841
|
+
...config,
|
|
842
|
+
baseUrl: config.baseUrl || "http://localhost:11434",
|
|
843
|
+
model: config.model || "qwen2.5-coder:7b"
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
async complete(messages, options) {
|
|
847
|
+
const endpoint = `${this.config.baseUrl}/api/chat`;
|
|
848
|
+
const body = {
|
|
849
|
+
model: this.config.model,
|
|
850
|
+
messages: messages.map((m) => ({
|
|
851
|
+
role: m.role,
|
|
852
|
+
content: m.content
|
|
853
|
+
})),
|
|
854
|
+
stream: false,
|
|
855
|
+
options: {
|
|
856
|
+
temperature: options?.temperature ?? this.config.options?.temperature ?? 0.2,
|
|
857
|
+
num_ctx: this.config.options?.num_ctx ?? 8192
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
const response = await fetch(endpoint, {
|
|
861
|
+
method: "POST",
|
|
862
|
+
headers: {
|
|
863
|
+
"Content-Type": "application/json"
|
|
864
|
+
},
|
|
865
|
+
body: JSON.stringify(body)
|
|
866
|
+
});
|
|
867
|
+
if (!response.ok) {
|
|
868
|
+
const errorText = await response.text();
|
|
869
|
+
throw new Error(`Ollama API error (${response.status}): ${errorText}`);
|
|
870
|
+
}
|
|
871
|
+
const data = await response.json();
|
|
872
|
+
return {
|
|
873
|
+
content: data.message.content || "",
|
|
874
|
+
finishReason: data.done ? "stop" : "error",
|
|
875
|
+
usage: data.eval_count ? {
|
|
876
|
+
promptTokens: data.prompt_eval_count || 0,
|
|
877
|
+
completionTokens: data.eval_count,
|
|
878
|
+
totalTokens: (data.prompt_eval_count || 0) + data.eval_count
|
|
879
|
+
} : void 0
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
async healthCheck() {
|
|
883
|
+
try {
|
|
884
|
+
const response = await fetch(`${this.config.baseUrl}/api/tags`);
|
|
885
|
+
if (!response.ok) return false;
|
|
886
|
+
const data = await response.json();
|
|
887
|
+
const hasModel = data.models.some(
|
|
888
|
+
(m) => m.name === this.config.model || m.name.startsWith(this.config.model + ":")
|
|
889
|
+
);
|
|
890
|
+
return hasModel;
|
|
891
|
+
} catch {
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* List available models
|
|
897
|
+
*/
|
|
898
|
+
async listModels() {
|
|
899
|
+
try {
|
|
900
|
+
const response = await fetch(`${this.config.baseUrl}/api/tags`);
|
|
901
|
+
if (!response.ok) return [];
|
|
902
|
+
const data = await response.json();
|
|
903
|
+
return data.models.map((m) => m.name);
|
|
904
|
+
} catch {
|
|
905
|
+
return [];
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
function createOllamaProvider(config) {
|
|
910
|
+
return new OllamaProvider(config);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// src/providers/anthropic.ts
|
|
914
|
+
var AnthropicProvider = class {
|
|
915
|
+
name = "Anthropic";
|
|
916
|
+
config;
|
|
917
|
+
constructor(config) {
|
|
918
|
+
if (!config.apiKey) {
|
|
919
|
+
throw new Error("API key is required for Anthropic provider");
|
|
920
|
+
}
|
|
921
|
+
this.config = {
|
|
922
|
+
...config,
|
|
923
|
+
baseUrl: config.baseUrl || "https://api.anthropic.com",
|
|
924
|
+
model: config.model || "claude-sonnet-4-20250514"
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
async complete(messages, options) {
|
|
928
|
+
const endpoint = `${this.config.baseUrl}/v1/messages`;
|
|
929
|
+
const systemMessage = messages.find((m) => m.role === "system");
|
|
930
|
+
const nonSystemMessages = messages.filter((m) => m.role !== "system");
|
|
931
|
+
const body = {
|
|
932
|
+
model: this.config.model,
|
|
933
|
+
max_tokens: options?.maxTokens ?? this.config.options?.max_tokens ?? 4096,
|
|
934
|
+
...systemMessage && { system: systemMessage.content },
|
|
935
|
+
messages: nonSystemMessages.map((m) => ({
|
|
936
|
+
role: m.role,
|
|
937
|
+
content: m.content
|
|
938
|
+
})),
|
|
939
|
+
...options?.temperature !== void 0 && { temperature: options.temperature },
|
|
940
|
+
...options?.stopSequences && { stop_sequences: options.stopSequences }
|
|
941
|
+
};
|
|
942
|
+
const response = await fetch(endpoint, {
|
|
943
|
+
method: "POST",
|
|
944
|
+
headers: {
|
|
945
|
+
"Content-Type": "application/json",
|
|
946
|
+
"x-api-key": this.config.apiKey,
|
|
947
|
+
"anthropic-version": "2023-06-01"
|
|
948
|
+
},
|
|
949
|
+
body: JSON.stringify(body)
|
|
950
|
+
});
|
|
951
|
+
if (!response.ok) {
|
|
952
|
+
const errorText = await response.text();
|
|
953
|
+
throw new Error(`Anthropic API error (${response.status}): ${errorText}`);
|
|
954
|
+
}
|
|
955
|
+
const data = await response.json();
|
|
956
|
+
const textContent = data.content.filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
957
|
+
return {
|
|
958
|
+
content: textContent,
|
|
959
|
+
finishReason: data.stop_reason === "end_turn" ? "stop" : data.stop_reason === "max_tokens" ? "length" : "error",
|
|
960
|
+
usage: {
|
|
961
|
+
promptTokens: data.usage.input_tokens,
|
|
962
|
+
completionTokens: data.usage.output_tokens,
|
|
963
|
+
totalTokens: data.usage.input_tokens + data.usage.output_tokens
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
async healthCheck() {
|
|
968
|
+
try {
|
|
969
|
+
const result = await this.complete([
|
|
970
|
+
{ role: "user", content: 'Say "ok"' }
|
|
971
|
+
], { maxTokens: 10 });
|
|
972
|
+
return result.content.length > 0;
|
|
973
|
+
} catch {
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
function createAnthropicProvider(config) {
|
|
979
|
+
return new AnthropicProvider(config);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// src/providers/index.ts
|
|
983
|
+
function createProvider(config) {
|
|
984
|
+
const providerType = config.provider;
|
|
985
|
+
const providerConfig = config.providers[providerType];
|
|
986
|
+
if (!providerConfig) {
|
|
987
|
+
throw new Error(`No configuration found for provider: ${providerType}`);
|
|
988
|
+
}
|
|
989
|
+
return createProviderByType(providerType, providerConfig);
|
|
990
|
+
}
|
|
991
|
+
function createProviderByType(type, config) {
|
|
992
|
+
switch (type) {
|
|
993
|
+
case "zai":
|
|
994
|
+
return createZAIProvider(config);
|
|
995
|
+
case "openai":
|
|
996
|
+
return createOpenAIProvider(config);
|
|
997
|
+
case "deepseek":
|
|
998
|
+
return createDeepSeekProvider(config);
|
|
999
|
+
case "ollama":
|
|
1000
|
+
return createOllamaProvider(config);
|
|
1001
|
+
case "anthropic":
|
|
1002
|
+
return createAnthropicProvider(config);
|
|
1003
|
+
default:
|
|
1004
|
+
throw new Error(`Unknown provider type: ${type}`);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
function getProviderDisplayName(type) {
|
|
1008
|
+
switch (type) {
|
|
1009
|
+
case "zai":
|
|
1010
|
+
return "ZAI (GLM)";
|
|
1011
|
+
case "openai":
|
|
1012
|
+
return "OpenAI";
|
|
1013
|
+
case "deepseek":
|
|
1014
|
+
return "DeepSeek";
|
|
1015
|
+
case "ollama":
|
|
1016
|
+
return "Ollama (Local)";
|
|
1017
|
+
case "anthropic":
|
|
1018
|
+
return "Anthropic (Claude)";
|
|
1019
|
+
default:
|
|
1020
|
+
return type;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
function listProviderTypes() {
|
|
1024
|
+
return ["zai", "anthropic", "openai", "deepseek", "ollama"];
|
|
1025
|
+
}
|
|
1026
|
+
export {
|
|
1027
|
+
PROVIDER_DEFAULTS,
|
|
1028
|
+
analyze,
|
|
1029
|
+
createAnthropicProvider,
|
|
1030
|
+
createDeepSeekProvider,
|
|
1031
|
+
createOllamaProvider,
|
|
1032
|
+
createOpenAIProvider,
|
|
1033
|
+
createProvider,
|
|
1034
|
+
createProviderByType,
|
|
1035
|
+
createSnapshot,
|
|
1036
|
+
createZAIProvider,
|
|
1037
|
+
ensureConfigDir,
|
|
1038
|
+
getConfigPath,
|
|
1039
|
+
getProviderConfig,
|
|
1040
|
+
getProviderDisplayName,
|
|
1041
|
+
getSnapshotStats,
|
|
1042
|
+
listProviderTypes,
|
|
1043
|
+
loadConfig,
|
|
1044
|
+
saveConfig,
|
|
1045
|
+
searchDocument,
|
|
1046
|
+
validateConfig
|
|
1047
|
+
};
|
|
1048
|
+
//# sourceMappingURL=index.mjs.map
|