@compilr-dev/agents 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +20 -0
- package/dist/agent.js +16 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +4 -0
- package/dist/providers/gemini.d.ts +91 -0
- package/dist/providers/gemini.js +140 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +7 -3
- package/dist/providers/ollama.d.ts +87 -0
- package/dist/providers/ollama.js +133 -0
- package/dist/providers/openai-compatible.d.ts +182 -0
- package/dist/providers/openai-compatible.js +359 -0
- package/dist/providers/openai.d.ts +93 -0
- package/dist/providers/openai.js +133 -0
- package/dist/tools/builtin/glob.d.ts +11 -0
- package/dist/tools/builtin/glob.js +44 -2
- package/dist/tools/builtin/grep.d.ts +11 -1
- package/dist/tools/builtin/grep.js +38 -2
- package/package.json +2 -2
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI LLM Provider
|
|
3
|
+
*
|
|
4
|
+
* Implements LLMProvider interface for OpenAI models (GPT-4o, GPT-4o-mini, etc.)
|
|
5
|
+
* Extends OpenAICompatibleProvider for shared functionality.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const provider = createOpenAIProvider({
|
|
10
|
+
* model: 'gpt-4o',
|
|
11
|
+
* apiKey: process.env.OPENAI_API_KEY
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* - Requires valid OpenAI API key
|
|
17
|
+
* - Default model is gpt-4o
|
|
18
|
+
* - Extended thinking is not supported (Claude-specific feature)
|
|
19
|
+
*/
|
|
20
|
+
import { ProviderError } from '../errors.js';
|
|
21
|
+
import { OpenAICompatibleProvider } from './openai-compatible.js';
|
|
22
|
+
// Default configuration
|
|
23
|
+
const DEFAULT_MODEL = 'gpt-4o';
|
|
24
|
+
const DEFAULT_BASE_URL = 'https://api.openai.com';
|
|
25
|
+
/**
|
|
26
|
+
* OpenAI LLM Provider
|
|
27
|
+
*
|
|
28
|
+
* Provides streaming chat completion using OpenAI models.
|
|
29
|
+
* Supports GPT-4o, GPT-4o-mini, and other compatible models.
|
|
30
|
+
*/
|
|
31
|
+
export class OpenAIProvider extends OpenAICompatibleProvider {
|
|
32
|
+
name = 'openai';
|
|
33
|
+
apiKey;
|
|
34
|
+
organization;
|
|
35
|
+
constructor(config = {}) {
|
|
36
|
+
const apiKey = config.apiKey ?? process.env.OPENAI_API_KEY;
|
|
37
|
+
if (!apiKey) {
|
|
38
|
+
throw new ProviderError('OpenAI API key not found. Set OPENAI_API_KEY environment variable or pass apiKey in config.', 'openai');
|
|
39
|
+
}
|
|
40
|
+
const baseConfig = {
|
|
41
|
+
baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
|
|
42
|
+
model: config.model ?? DEFAULT_MODEL,
|
|
43
|
+
maxTokens: config.maxTokens,
|
|
44
|
+
timeout: config.timeout,
|
|
45
|
+
};
|
|
46
|
+
super(baseConfig);
|
|
47
|
+
this.apiKey = apiKey;
|
|
48
|
+
this.organization = config.organization;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* OpenAI authentication with Bearer token
|
|
52
|
+
*/
|
|
53
|
+
getAuthHeaders() {
|
|
54
|
+
const headers = {
|
|
55
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
56
|
+
};
|
|
57
|
+
if (this.organization) {
|
|
58
|
+
headers['OpenAI-Organization'] = this.organization;
|
|
59
|
+
}
|
|
60
|
+
return headers;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* OpenAI chat completions endpoint
|
|
64
|
+
*/
|
|
65
|
+
getEndpointPath() {
|
|
66
|
+
return '/v1/chat/completions';
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* OpenAI uses standard body format (no provider-specific extensions needed)
|
|
70
|
+
*/
|
|
71
|
+
buildProviderSpecificBody(_options) {
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Map HTTP errors with OpenAI-specific messages
|
|
76
|
+
*/
|
|
77
|
+
mapHttpError(status, body, _model) {
|
|
78
|
+
let message = `OpenAI error (${String(status)})`;
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(body);
|
|
81
|
+
if (parsed.error?.message) {
|
|
82
|
+
message = parsed.error.message;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
message = body || message;
|
|
87
|
+
}
|
|
88
|
+
switch (status) {
|
|
89
|
+
case 401:
|
|
90
|
+
return new ProviderError('Invalid OpenAI API key. Check your OPENAI_API_KEY.', 'openai', 401);
|
|
91
|
+
case 403:
|
|
92
|
+
return new ProviderError('Access denied. Check your OpenAI API key permissions.', 'openai', 403);
|
|
93
|
+
case 429:
|
|
94
|
+
return new ProviderError('OpenAI rate limit exceeded. Please wait and try again.', 'openai', 429);
|
|
95
|
+
case 500:
|
|
96
|
+
case 502:
|
|
97
|
+
case 503:
|
|
98
|
+
return new ProviderError('OpenAI service temporarily unavailable. Please try again later.', 'openai', status);
|
|
99
|
+
default:
|
|
100
|
+
return new ProviderError(message, 'openai', status);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Map connection errors with OpenAI-specific messages
|
|
105
|
+
*/
|
|
106
|
+
mapConnectionError(_error) {
|
|
107
|
+
return new ProviderError('Failed to connect to OpenAI API. Check your internet connection.', 'openai');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Create an OpenAI provider instance
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```typescript
|
|
115
|
+
* // Using environment variable (OPENAI_API_KEY)
|
|
116
|
+
* const provider = createOpenAIProvider();
|
|
117
|
+
*
|
|
118
|
+
* // With explicit API key
|
|
119
|
+
* const provider = createOpenAIProvider({ apiKey: 'sk-...' });
|
|
120
|
+
*
|
|
121
|
+
* // With custom model
|
|
122
|
+
* const provider = createOpenAIProvider({ model: 'gpt-4o-mini' });
|
|
123
|
+
*
|
|
124
|
+
* // With organization
|
|
125
|
+
* const provider = createOpenAIProvider({
|
|
126
|
+
* apiKey: 'sk-...',
|
|
127
|
+
* organization: 'org-...'
|
|
128
|
+
* });
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export function createOpenAIProvider(config = {}) {
|
|
132
|
+
return new OpenAIProvider(config);
|
|
133
|
+
}
|
|
@@ -38,6 +38,12 @@ export interface GlobInput {
|
|
|
38
38
|
* Return absolute paths instead of relative (default: false)
|
|
39
39
|
*/
|
|
40
40
|
absolute?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Directory names to exclude from search.
|
|
43
|
+
* Default excludes: node_modules, .git, dist, build, etc.
|
|
44
|
+
* Set to empty array [] to include all directories.
|
|
45
|
+
*/
|
|
46
|
+
excludeDirs?: string[];
|
|
41
47
|
}
|
|
42
48
|
/**
|
|
43
49
|
* Glob tool definition
|
|
@@ -59,4 +65,9 @@ export declare function createGlobTool(options?: {
|
|
|
59
65
|
* Always include these patterns regardless of ignore
|
|
60
66
|
*/
|
|
61
67
|
alwaysInclude?: string[];
|
|
68
|
+
/**
|
|
69
|
+
* Override default excluded directories.
|
|
70
|
+
* Defaults to: node_modules, .git, dist, build, etc.
|
|
71
|
+
*/
|
|
72
|
+
excludeDirs?: string[];
|
|
62
73
|
}): Tool<GlobInput>;
|
|
@@ -5,6 +5,29 @@ import { readdir } from 'node:fs/promises';
|
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { defineTool, createSuccessResult, createErrorResult } from '../define.js';
|
|
7
7
|
import { isNodeError } from './utils.js';
|
|
8
|
+
/**
|
|
9
|
+
* Default directories to exclude from glob searches.
|
|
10
|
+
* These are typically large, generated, or not relevant for code search.
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_EXCLUDE_DIRS = [
|
|
13
|
+
'node_modules',
|
|
14
|
+
'.git',
|
|
15
|
+
'dist',
|
|
16
|
+
'build',
|
|
17
|
+
'.next',
|
|
18
|
+
'.nuxt',
|
|
19
|
+
'.output',
|
|
20
|
+
'coverage',
|
|
21
|
+
'.nyc_output',
|
|
22
|
+
'__pycache__',
|
|
23
|
+
'.pytest_cache',
|
|
24
|
+
'venv',
|
|
25
|
+
'.venv',
|
|
26
|
+
'target', // Rust/Java
|
|
27
|
+
'vendor', // Go/PHP
|
|
28
|
+
'.cargo',
|
|
29
|
+
'.cache',
|
|
30
|
+
];
|
|
8
31
|
/**
|
|
9
32
|
* Glob tool definition
|
|
10
33
|
*/
|
|
@@ -48,6 +71,11 @@ export const globTool = defineTool({
|
|
|
48
71
|
type: 'boolean',
|
|
49
72
|
description: 'Return absolute paths instead of relative',
|
|
50
73
|
},
|
|
74
|
+
excludeDirs: {
|
|
75
|
+
type: 'array',
|
|
76
|
+
items: { type: 'string' },
|
|
77
|
+
description: 'Directory names to exclude (default: node_modules, .git, dist, build, etc.)',
|
|
78
|
+
},
|
|
51
79
|
},
|
|
52
80
|
required: ['pattern'],
|
|
53
81
|
},
|
|
@@ -57,7 +85,7 @@ export const globTool = defineTool({
|
|
|
57
85
|
* Execute the glob tool
|
|
58
86
|
*/
|
|
59
87
|
async function executeGlob(input) {
|
|
60
|
-
const { pattern, path: basePath = '.', includeHidden = false, onlyDirectories = false, onlyFiles = !onlyDirectories, maxDepth, maxResults = 1000, absolute = false, } = input;
|
|
88
|
+
const { pattern, path: basePath = '.', includeHidden = false, onlyDirectories = false, onlyFiles = !onlyDirectories, maxDepth, maxResults = 1000, absolute = false, excludeDirs = DEFAULT_EXCLUDE_DIRS, } = input;
|
|
61
89
|
// Check if base path exists
|
|
62
90
|
try {
|
|
63
91
|
await readdir(basePath);
|
|
@@ -91,6 +119,7 @@ async function executeGlob(input) {
|
|
|
91
119
|
maxResults,
|
|
92
120
|
matches,
|
|
93
121
|
basePath,
|
|
122
|
+
excludeDirs,
|
|
94
123
|
});
|
|
95
124
|
// Format output
|
|
96
125
|
const results = absolute ? matches.map((m) => join(basePath, m)) : matches;
|
|
@@ -117,6 +146,7 @@ async function searchDirectory(basePath, relativePath, depth, options) {
|
|
|
117
146
|
return;
|
|
118
147
|
}
|
|
119
148
|
const currentPath = relativePath ? join(basePath, relativePath) : basePath;
|
|
149
|
+
const excludeSet = new Set(options.excludeDirs);
|
|
120
150
|
let entries;
|
|
121
151
|
try {
|
|
122
152
|
entries = await readdir(currentPath, { withFileTypes: true });
|
|
@@ -133,8 +163,12 @@ async function searchDirectory(basePath, relativePath, depth, options) {
|
|
|
133
163
|
if (!options.includeHidden && entry.name.startsWith('.')) {
|
|
134
164
|
continue;
|
|
135
165
|
}
|
|
136
|
-
const entryRelativePath = relativePath ? join(relativePath, entry.name) : entry.name;
|
|
137
166
|
const isDir = entry.isDirectory();
|
|
167
|
+
// Skip excluded directories
|
|
168
|
+
if (isDir && excludeSet.has(entry.name)) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const entryRelativePath = relativePath ? join(relativePath, entry.name) : entry.name;
|
|
138
172
|
const isFile = entry.isFile();
|
|
139
173
|
// Check if this entry matches the pattern
|
|
140
174
|
if (options.pattern.test(entryRelativePath)) {
|
|
@@ -226,6 +260,11 @@ export function createGlobTool(options) {
|
|
|
226
260
|
type: 'boolean',
|
|
227
261
|
description: 'Return absolute paths instead of relative',
|
|
228
262
|
},
|
|
263
|
+
excludeDirs: {
|
|
264
|
+
type: 'array',
|
|
265
|
+
items: { type: 'string' },
|
|
266
|
+
description: 'Directory names to exclude (default: node_modules, .git, dist, build, etc.)',
|
|
267
|
+
},
|
|
229
268
|
},
|
|
230
269
|
required: ['pattern'],
|
|
231
270
|
},
|
|
@@ -235,9 +274,12 @@ export function createGlobTool(options) {
|
|
|
235
274
|
if (options?.baseDir && !searchPath.startsWith('/')) {
|
|
236
275
|
searchPath = join(options.baseDir, searchPath);
|
|
237
276
|
}
|
|
277
|
+
// Apply custom excludeDirs from factory options if specified and input doesn't override
|
|
278
|
+
const excludeDirs = input.excludeDirs ?? options?.excludeDirs ?? DEFAULT_EXCLUDE_DIRS;
|
|
238
279
|
return executeGlob({
|
|
239
280
|
...input,
|
|
240
281
|
path: searchPath,
|
|
282
|
+
excludeDirs,
|
|
241
283
|
});
|
|
242
284
|
},
|
|
243
285
|
});
|
|
@@ -50,6 +50,12 @@ export interface GrepInput {
|
|
|
50
50
|
* Search recursively in directories (default: true)
|
|
51
51
|
*/
|
|
52
52
|
recursive?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Directory names to exclude from search.
|
|
55
|
+
* Default excludes: node_modules, .git, dist, build, etc.
|
|
56
|
+
* Set to empty array [] to include all directories.
|
|
57
|
+
*/
|
|
58
|
+
excludeDirs?: string[];
|
|
53
59
|
}
|
|
54
60
|
/**
|
|
55
61
|
* Grep tool definition
|
|
@@ -60,7 +66,6 @@ export declare const grepTool: Tool<GrepInput>;
|
|
|
60
66
|
*
|
|
61
67
|
* TODO: Future enhancements could include:
|
|
62
68
|
* - maxFileSize: Skip files larger than specified size
|
|
63
|
-
* - excludeDirs: Exclude directories by name (e.g., node_modules, .git)
|
|
64
69
|
*/
|
|
65
70
|
export declare function createGrepTool(options?: {
|
|
66
71
|
/**
|
|
@@ -71,4 +76,9 @@ export declare function createGrepTool(options?: {
|
|
|
71
76
|
* Default file extensions to search
|
|
72
77
|
*/
|
|
73
78
|
defaultExtensions?: string[];
|
|
79
|
+
/**
|
|
80
|
+
* Override default excluded directories.
|
|
81
|
+
* Defaults to: node_modules, .git, dist, build, etc.
|
|
82
|
+
*/
|
|
83
|
+
excludeDirs?: string[];
|
|
74
84
|
}): Tool<GrepInput>;
|
|
@@ -6,6 +6,29 @@ import { readdir, stat } from 'node:fs/promises';
|
|
|
6
6
|
import { join, relative } from 'node:path';
|
|
7
7
|
import { defineTool, createSuccessResult, createErrorResult } from '../define.js';
|
|
8
8
|
import { isNodeError, isExtensionAllowed } from './utils.js';
|
|
9
|
+
/**
|
|
10
|
+
* Default directories to exclude from grep searches.
|
|
11
|
+
* These are typically large, generated, or not relevant for code search.
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_EXCLUDE_DIRS = [
|
|
14
|
+
'node_modules',
|
|
15
|
+
'.git',
|
|
16
|
+
'dist',
|
|
17
|
+
'build',
|
|
18
|
+
'.next',
|
|
19
|
+
'.nuxt',
|
|
20
|
+
'.output',
|
|
21
|
+
'coverage',
|
|
22
|
+
'.nyc_output',
|
|
23
|
+
'__pycache__',
|
|
24
|
+
'.pytest_cache',
|
|
25
|
+
'venv',
|
|
26
|
+
'.venv',
|
|
27
|
+
'target', // Rust/Java
|
|
28
|
+
'vendor', // Go/PHP
|
|
29
|
+
'.cargo',
|
|
30
|
+
'.cache',
|
|
31
|
+
];
|
|
9
32
|
/**
|
|
10
33
|
* Grep tool definition
|
|
11
34
|
*/
|
|
@@ -103,7 +126,7 @@ function safeRegexTest(regex, text) {
|
|
|
103
126
|
*/
|
|
104
127
|
async function executeGrep(input) {
|
|
105
128
|
try {
|
|
106
|
-
const { pattern, path, ignoreCase = false, lineNumbers = true, before = 0, after = 0, filesOnly = false, includeHidden = false, extensions, maxMatches = 100, recursive = true, } = input;
|
|
129
|
+
const { pattern, path, ignoreCase = false, lineNumbers = true, before = 0, after = 0, filesOnly = false, includeHidden = false, extensions, maxMatches = 100, recursive = true, excludeDirs = DEFAULT_EXCLUDE_DIRS, } = input;
|
|
107
130
|
// Check for potentially dangerous regex patterns
|
|
108
131
|
if (isPotentiallyDangerousRegex(pattern)) {
|
|
109
132
|
return createErrorResult(`Potentially dangerous regex pattern detected (ReDoS risk). ` +
|
|
@@ -128,6 +151,7 @@ async function executeGrep(input) {
|
|
|
128
151
|
recursive,
|
|
129
152
|
includeHidden,
|
|
130
153
|
extensions,
|
|
154
|
+
excludeDirs,
|
|
131
155
|
});
|
|
132
156
|
}
|
|
133
157
|
else {
|
|
@@ -189,6 +213,7 @@ async function executeGrep(input) {
|
|
|
189
213
|
*/
|
|
190
214
|
async function collectFiles(dir, files, options) {
|
|
191
215
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
216
|
+
const excludeSet = new Set(options.excludeDirs ?? []);
|
|
192
217
|
for (const entry of entries) {
|
|
193
218
|
// Skip hidden files unless included
|
|
194
219
|
if (!options.includeHidden && entry.name.startsWith('.')) {
|
|
@@ -205,6 +230,10 @@ async function collectFiles(dir, files, options) {
|
|
|
205
230
|
files.push(fullPath);
|
|
206
231
|
}
|
|
207
232
|
else if (entry.isDirectory() && options.recursive) {
|
|
233
|
+
// Skip excluded directories
|
|
234
|
+
if (excludeSet.has(entry.name)) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
208
237
|
await collectFiles(fullPath, files, options);
|
|
209
238
|
}
|
|
210
239
|
}
|
|
@@ -286,7 +315,6 @@ function formatMatches(matches, options) {
|
|
|
286
315
|
*
|
|
287
316
|
* TODO: Future enhancements could include:
|
|
288
317
|
* - maxFileSize: Skip files larger than specified size
|
|
289
|
-
* - excludeDirs: Exclude directories by name (e.g., node_modules, .git)
|
|
290
318
|
*/
|
|
291
319
|
export function createGrepTool(options) {
|
|
292
320
|
return defineTool({
|
|
@@ -342,6 +370,11 @@ export function createGrepTool(options) {
|
|
|
342
370
|
type: 'boolean',
|
|
343
371
|
description: 'Search recursively in directories (default: true)',
|
|
344
372
|
},
|
|
373
|
+
excludeDirs: {
|
|
374
|
+
type: 'array',
|
|
375
|
+
items: { type: 'string' },
|
|
376
|
+
description: 'Directory names to exclude (default: node_modules, .git, dist, build, etc.)',
|
|
377
|
+
},
|
|
345
378
|
},
|
|
346
379
|
required: ['pattern', 'path'],
|
|
347
380
|
},
|
|
@@ -353,10 +386,13 @@ export function createGrepTool(options) {
|
|
|
353
386
|
}
|
|
354
387
|
// Apply default extensions if none specified
|
|
355
388
|
const extensions = input.extensions ?? options?.defaultExtensions;
|
|
389
|
+
// Apply custom excludeDirs from factory options if specified and input doesn't override
|
|
390
|
+
const excludeDirs = input.excludeDirs ?? options?.excludeDirs ?? DEFAULT_EXCLUDE_DIRS;
|
|
356
391
|
return executeGrep({
|
|
357
392
|
...input,
|
|
358
393
|
path: searchPath,
|
|
359
394
|
extensions,
|
|
395
|
+
excludeDirs,
|
|
360
396
|
});
|
|
361
397
|
},
|
|
362
398
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@compilr-dev/agents",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Lightweight multi-LLM agent library for building CLI AI assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@anthropic-ai/sdk": "^0.30.
|
|
67
|
+
"@anthropic-ai/sdk": "^0.30.1",
|
|
68
68
|
"@eslint/js": "^9.39.1",
|
|
69
69
|
"@modelcontextprotocol/sdk": "^1.23.0",
|
|
70
70
|
"@types/node": "^24.10.1",
|