coding-agent-adapters 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/README.md +222 -0
- package/dist/index.cjs +807 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +270 -0
- package/dist/index.d.ts +270 -0
- package/dist/index.js +795 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ptyManager = require('pty-manager');
|
|
4
|
+
|
|
5
|
+
// src/base-coding-adapter.ts
|
|
6
|
+
var BaseCodingAdapter = class extends ptyManager.BaseCLIAdapter {
|
|
7
|
+
/**
|
|
8
|
+
* Get credentials from config
|
|
9
|
+
*/
|
|
10
|
+
getCredentials(config) {
|
|
11
|
+
const adapterConfig = config.adapterConfig;
|
|
12
|
+
return adapterConfig || {};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Override detectExit to include installation instructions
|
|
16
|
+
*/
|
|
17
|
+
detectExit(output) {
|
|
18
|
+
if (output.includes("Command not found") || output.includes("command not found")) {
|
|
19
|
+
return {
|
|
20
|
+
exited: true,
|
|
21
|
+
code: 127,
|
|
22
|
+
error: `${this.displayName} CLI not found. Install with: ${this.installation.command}
|
|
23
|
+
Docs: ${this.installation.docsUrl}`
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return super.detectExit(output);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get formatted installation instructions
|
|
30
|
+
*/
|
|
31
|
+
getInstallInstructions() {
|
|
32
|
+
const lines = [
|
|
33
|
+
`${this.displayName} Installation`,
|
|
34
|
+
`${"=".repeat(this.displayName.length + 13)}`,
|
|
35
|
+
"",
|
|
36
|
+
`Primary: ${this.installation.command}`
|
|
37
|
+
];
|
|
38
|
+
if (this.installation.alternatives?.length) {
|
|
39
|
+
lines.push("");
|
|
40
|
+
lines.push("Alternatives:");
|
|
41
|
+
for (const alt of this.installation.alternatives) {
|
|
42
|
+
lines.push(` - ${alt}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
lines.push("");
|
|
46
|
+
lines.push(`Docs: ${this.installation.docsUrl}`);
|
|
47
|
+
if (this.installation.minVersion) {
|
|
48
|
+
lines.push(`Minimum version: ${this.installation.minVersion}`);
|
|
49
|
+
}
|
|
50
|
+
return lines.join("\n");
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if response appears complete based on common patterns
|
|
54
|
+
*/
|
|
55
|
+
isResponseComplete(output) {
|
|
56
|
+
const completionIndicators = [
|
|
57
|
+
/\n>\s*$/,
|
|
58
|
+
// Ends with prompt
|
|
59
|
+
/\n\s*$/,
|
|
60
|
+
// Ends with newline
|
|
61
|
+
/Done\./i,
|
|
62
|
+
// Explicit done
|
|
63
|
+
/completed/i,
|
|
64
|
+
// Task completed
|
|
65
|
+
/finished/i,
|
|
66
|
+
// Finished
|
|
67
|
+
/```\s*$/
|
|
68
|
+
// Code block ended
|
|
69
|
+
];
|
|
70
|
+
return completionIndicators.some((pattern) => pattern.test(output));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Extract the main content from CLI output, removing common artifacts
|
|
74
|
+
*/
|
|
75
|
+
extractContent(output, promptPattern) {
|
|
76
|
+
let content = output;
|
|
77
|
+
content = content.replace(promptPattern, "");
|
|
78
|
+
content = content.replace(/^(Thinking|Working|Reading|Writing|Processing|Generating)\.+$/gm, "");
|
|
79
|
+
content = content.trim();
|
|
80
|
+
return content;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// src/claude-adapter.ts
|
|
85
|
+
var ClaudeAdapter = class extends BaseCodingAdapter {
|
|
86
|
+
adapterType = "claude";
|
|
87
|
+
displayName = "Claude Code";
|
|
88
|
+
installation = {
|
|
89
|
+
command: "npm install -g @anthropic-ai/claude-code",
|
|
90
|
+
alternatives: [
|
|
91
|
+
"npx @anthropic-ai/claude-code (run without installing)",
|
|
92
|
+
"brew install claude-code (macOS with Homebrew)"
|
|
93
|
+
],
|
|
94
|
+
docsUrl: "https://docs.anthropic.com/en/docs/claude-code",
|
|
95
|
+
minVersion: "1.0.0"
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Auto-response rules for Claude Code CLI.
|
|
99
|
+
* These handle common prompts that can be safely auto-responded.
|
|
100
|
+
*/
|
|
101
|
+
autoResponseRules = [
|
|
102
|
+
{
|
|
103
|
+
pattern: /update available.*\[y\/n\]/i,
|
|
104
|
+
type: "update",
|
|
105
|
+
response: "n",
|
|
106
|
+
description: "Decline Claude Code update to continue execution",
|
|
107
|
+
safe: true
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
pattern: /new version.*available.*\[y\/n\]/i,
|
|
111
|
+
type: "update",
|
|
112
|
+
response: "n",
|
|
113
|
+
description: "Decline version upgrade prompt",
|
|
114
|
+
safe: true
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
pattern: /would you like to enable.*telemetry.*\[y\/n\]/i,
|
|
118
|
+
type: "config",
|
|
119
|
+
response: "n",
|
|
120
|
+
description: "Decline telemetry prompt",
|
|
121
|
+
safe: true
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
pattern: /send anonymous usage data.*\[y\/n\]/i,
|
|
125
|
+
type: "config",
|
|
126
|
+
response: "n",
|
|
127
|
+
description: "Decline anonymous usage data",
|
|
128
|
+
safe: true
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
pattern: /continue without.*\[y\/n\]/i,
|
|
132
|
+
type: "config",
|
|
133
|
+
response: "y",
|
|
134
|
+
description: "Continue without optional feature",
|
|
135
|
+
safe: true
|
|
136
|
+
}
|
|
137
|
+
];
|
|
138
|
+
getCommand() {
|
|
139
|
+
return "claude";
|
|
140
|
+
}
|
|
141
|
+
getArgs(config) {
|
|
142
|
+
const args = [];
|
|
143
|
+
args.push("--print");
|
|
144
|
+
if (config.workdir) {
|
|
145
|
+
args.push("--cwd", config.workdir);
|
|
146
|
+
}
|
|
147
|
+
return args;
|
|
148
|
+
}
|
|
149
|
+
getEnv(config) {
|
|
150
|
+
const env = {};
|
|
151
|
+
const credentials = this.getCredentials(config);
|
|
152
|
+
if (credentials.anthropicKey) {
|
|
153
|
+
env.ANTHROPIC_API_KEY = credentials.anthropicKey;
|
|
154
|
+
}
|
|
155
|
+
if (config.env?.ANTHROPIC_MODEL) {
|
|
156
|
+
env.ANTHROPIC_MODEL = config.env.ANTHROPIC_MODEL;
|
|
157
|
+
}
|
|
158
|
+
env.CLAUDE_CODE_DISABLE_INTERACTIVE = "true";
|
|
159
|
+
return env;
|
|
160
|
+
}
|
|
161
|
+
detectLogin(output) {
|
|
162
|
+
const stripped = this.stripAnsi(output);
|
|
163
|
+
if (stripped.includes("API key not found") || stripped.includes("ANTHROPIC_API_KEY") || stripped.includes("authentication required") || stripped.includes("Please sign in") || stripped.includes("Invalid API key")) {
|
|
164
|
+
return {
|
|
165
|
+
required: true,
|
|
166
|
+
type: "api_key",
|
|
167
|
+
instructions: "Set ANTHROPIC_API_KEY environment variable or provide credentials in adapterConfig"
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (stripped.includes("Open this URL") || stripped.includes("browser to authenticate")) {
|
|
171
|
+
const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
|
|
172
|
+
return {
|
|
173
|
+
required: true,
|
|
174
|
+
type: "browser",
|
|
175
|
+
url: urlMatch ? urlMatch[0] : void 0,
|
|
176
|
+
instructions: "Browser authentication required"
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return { required: false };
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Detect blocking prompts specific to Claude Code CLI
|
|
183
|
+
*/
|
|
184
|
+
detectBlockingPrompt(output) {
|
|
185
|
+
const stripped = this.stripAnsi(output);
|
|
186
|
+
const loginDetection = this.detectLogin(output);
|
|
187
|
+
if (loginDetection.required) {
|
|
188
|
+
return {
|
|
189
|
+
detected: true,
|
|
190
|
+
type: "login",
|
|
191
|
+
prompt: loginDetection.instructions,
|
|
192
|
+
url: loginDetection.url,
|
|
193
|
+
canAutoRespond: false,
|
|
194
|
+
instructions: loginDetection.instructions
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (/choose.*model|select.*model|available models/i.test(stripped) && /\d+\)|claude-/i.test(stripped)) {
|
|
198
|
+
return {
|
|
199
|
+
detected: true,
|
|
200
|
+
type: "model_select",
|
|
201
|
+
prompt: "Claude model selection",
|
|
202
|
+
canAutoRespond: false,
|
|
203
|
+
instructions: "Please select a Claude model or set ANTHROPIC_MODEL env var"
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (/which.*tier|select.*plan|api.*tier/i.test(stripped)) {
|
|
207
|
+
return {
|
|
208
|
+
detected: true,
|
|
209
|
+
type: "config",
|
|
210
|
+
prompt: "API tier selection",
|
|
211
|
+
canAutoRespond: false,
|
|
212
|
+
instructions: "Please select an API tier"
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
if (/welcome to claude|first time setup|initial configuration/i.test(stripped)) {
|
|
216
|
+
return {
|
|
217
|
+
detected: true,
|
|
218
|
+
type: "config",
|
|
219
|
+
prompt: "First-time setup",
|
|
220
|
+
canAutoRespond: false,
|
|
221
|
+
instructions: "Claude Code requires initial configuration"
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
if (/allow.*access|grant.*permission|access to .* files/i.test(stripped) && /\[y\/n\]/i.test(stripped)) {
|
|
225
|
+
return {
|
|
226
|
+
detected: true,
|
|
227
|
+
type: "permission",
|
|
228
|
+
prompt: "File/directory access permission",
|
|
229
|
+
options: ["y", "n"],
|
|
230
|
+
suggestedResponse: "y",
|
|
231
|
+
canAutoRespond: true,
|
|
232
|
+
instructions: "Claude Code requesting file access permission"
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return super.detectBlockingPrompt(output);
|
|
236
|
+
}
|
|
237
|
+
detectReady(output) {
|
|
238
|
+
const stripped = this.stripAnsi(output);
|
|
239
|
+
return stripped.includes("Claude Code") || stripped.includes("How can I help") || stripped.includes("What would you like") || // Check for the typical prompt pattern
|
|
240
|
+
/>\s*$/.test(stripped) || // Or a clear ready indicator
|
|
241
|
+
stripped.includes("Ready");
|
|
242
|
+
}
|
|
243
|
+
parseOutput(output) {
|
|
244
|
+
const stripped = this.stripAnsi(output);
|
|
245
|
+
const isComplete = this.isResponseComplete(stripped);
|
|
246
|
+
if (!isComplete) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
const isQuestion = this.containsQuestion(stripped);
|
|
250
|
+
const content = this.extractContent(stripped, /^.*>\s*/gm);
|
|
251
|
+
return {
|
|
252
|
+
type: isQuestion ? "question" : "response",
|
|
253
|
+
content,
|
|
254
|
+
isComplete: true,
|
|
255
|
+
isQuestion,
|
|
256
|
+
metadata: {
|
|
257
|
+
raw: output
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
getPromptPattern() {
|
|
262
|
+
return /(?:claude|>)\s*$/i;
|
|
263
|
+
}
|
|
264
|
+
getHealthCheckCommand() {
|
|
265
|
+
return "claude --version";
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// src/gemini-adapter.ts
|
|
270
|
+
var GeminiAdapter = class extends BaseCodingAdapter {
|
|
271
|
+
adapterType = "gemini";
|
|
272
|
+
displayName = "Google Gemini";
|
|
273
|
+
installation = {
|
|
274
|
+
command: "npm install -g @anthropics/gemini-cli",
|
|
275
|
+
alternatives: [
|
|
276
|
+
"See documentation for latest installation method"
|
|
277
|
+
],
|
|
278
|
+
docsUrl: "https://github.com/anthropics/gemini-cli#installation"
|
|
279
|
+
};
|
|
280
|
+
getCommand() {
|
|
281
|
+
return "gemini";
|
|
282
|
+
}
|
|
283
|
+
getArgs(config) {
|
|
284
|
+
const args = [];
|
|
285
|
+
args.push("--non-interactive");
|
|
286
|
+
if (config.workdir) {
|
|
287
|
+
args.push("--cwd", config.workdir);
|
|
288
|
+
}
|
|
289
|
+
args.push("--output-format", "text");
|
|
290
|
+
return args;
|
|
291
|
+
}
|
|
292
|
+
getEnv(config) {
|
|
293
|
+
const env = {};
|
|
294
|
+
const credentials = this.getCredentials(config);
|
|
295
|
+
if (credentials.googleKey) {
|
|
296
|
+
env.GOOGLE_API_KEY = credentials.googleKey;
|
|
297
|
+
env.GEMINI_API_KEY = credentials.googleKey;
|
|
298
|
+
}
|
|
299
|
+
if (config.env?.GEMINI_MODEL) {
|
|
300
|
+
env.GEMINI_MODEL = config.env.GEMINI_MODEL;
|
|
301
|
+
}
|
|
302
|
+
env.NO_COLOR = "1";
|
|
303
|
+
return env;
|
|
304
|
+
}
|
|
305
|
+
detectLogin(output) {
|
|
306
|
+
const stripped = this.stripAnsi(output);
|
|
307
|
+
if (stripped.includes("API key not found") || stripped.includes("GOOGLE_API_KEY") || stripped.includes("GEMINI_API_KEY") || stripped.includes("authentication required") || stripped.includes("Invalid API key") || stripped.includes("API key is not valid")) {
|
|
308
|
+
return {
|
|
309
|
+
required: true,
|
|
310
|
+
type: "api_key",
|
|
311
|
+
instructions: "Set GOOGLE_API_KEY or GEMINI_API_KEY environment variable"
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
if (stripped.includes("Sign in with Google") || stripped.includes("OAuth") || stripped.includes("accounts.google.com")) {
|
|
315
|
+
const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
|
|
316
|
+
return {
|
|
317
|
+
required: true,
|
|
318
|
+
type: "oauth",
|
|
319
|
+
url: urlMatch ? urlMatch[0] : "https://accounts.google.com",
|
|
320
|
+
instructions: "Google OAuth authentication required"
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
if (stripped.includes("Application Default Credentials") || stripped.includes("gcloud auth")) {
|
|
324
|
+
return {
|
|
325
|
+
required: true,
|
|
326
|
+
type: "browser",
|
|
327
|
+
instructions: "Run: gcloud auth application-default login"
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
return { required: false };
|
|
331
|
+
}
|
|
332
|
+
detectBlockingPrompt(output) {
|
|
333
|
+
const stripped = this.stripAnsi(output);
|
|
334
|
+
const loginDetection = this.detectLogin(output);
|
|
335
|
+
if (loginDetection.required) {
|
|
336
|
+
return {
|
|
337
|
+
detected: true,
|
|
338
|
+
type: "login",
|
|
339
|
+
prompt: loginDetection.instructions,
|
|
340
|
+
url: loginDetection.url,
|
|
341
|
+
canAutoRespond: false,
|
|
342
|
+
instructions: loginDetection.instructions
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
if (/select.*model|choose.*model|gemini-/i.test(stripped) && /\d+\)/i.test(stripped)) {
|
|
346
|
+
return {
|
|
347
|
+
detected: true,
|
|
348
|
+
type: "model_select",
|
|
349
|
+
prompt: "Gemini model selection",
|
|
350
|
+
canAutoRespond: false,
|
|
351
|
+
instructions: "Please select a model or set GEMINI_MODEL env var"
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
if (/select.*project|choose.*project|google cloud project/i.test(stripped)) {
|
|
355
|
+
return {
|
|
356
|
+
detected: true,
|
|
357
|
+
type: "project_select",
|
|
358
|
+
prompt: "Google Cloud project selection",
|
|
359
|
+
canAutoRespond: false,
|
|
360
|
+
instructions: "Please select a Google Cloud project"
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
if (/safety.*filter|content.*blocked|unsafe.*content/i.test(stripped)) {
|
|
364
|
+
return {
|
|
365
|
+
detected: true,
|
|
366
|
+
type: "unknown",
|
|
367
|
+
prompt: "Safety filter triggered",
|
|
368
|
+
canAutoRespond: false,
|
|
369
|
+
instructions: "Content was blocked by safety filters"
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
return super.detectBlockingPrompt(output);
|
|
373
|
+
}
|
|
374
|
+
detectReady(output) {
|
|
375
|
+
const stripped = this.stripAnsi(output);
|
|
376
|
+
return stripped.includes("Gemini") || stripped.includes("Ready") || stripped.includes("How can I help") || stripped.includes("What would you like") || /(?:gemini|>)\s*$/i.test(stripped);
|
|
377
|
+
}
|
|
378
|
+
parseOutput(output) {
|
|
379
|
+
const stripped = this.stripAnsi(output);
|
|
380
|
+
const isComplete = this.isResponseComplete(stripped);
|
|
381
|
+
if (!isComplete) {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
const isQuestion = this.containsQuestion(stripped);
|
|
385
|
+
let content = this.extractContent(stripped, /^.*(?:gemini|>)\s*/gim);
|
|
386
|
+
content = content.replace(/^\[Safety[^\]]*\].*$/gm, "");
|
|
387
|
+
return {
|
|
388
|
+
type: isQuestion ? "question" : "response",
|
|
389
|
+
content,
|
|
390
|
+
isComplete: true,
|
|
391
|
+
isQuestion,
|
|
392
|
+
metadata: {
|
|
393
|
+
raw: output
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
getPromptPattern() {
|
|
398
|
+
return /(?:gemini|>)\s*$/i;
|
|
399
|
+
}
|
|
400
|
+
getHealthCheckCommand() {
|
|
401
|
+
return "gemini --version";
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
// src/codex-adapter.ts
|
|
406
|
+
var CodexAdapter = class extends BaseCodingAdapter {
|
|
407
|
+
adapterType = "codex";
|
|
408
|
+
displayName = "OpenAI Codex";
|
|
409
|
+
installation = {
|
|
410
|
+
command: "npm install -g @openai/codex",
|
|
411
|
+
alternatives: [
|
|
412
|
+
"pip install openai (Python SDK)"
|
|
413
|
+
],
|
|
414
|
+
docsUrl: "https://github.com/openai/codex"
|
|
415
|
+
};
|
|
416
|
+
/**
|
|
417
|
+
* Auto-response rules for OpenAI Codex CLI.
|
|
418
|
+
*/
|
|
419
|
+
autoResponseRules = [
|
|
420
|
+
{
|
|
421
|
+
pattern: /update available.*\[y\/n\]/i,
|
|
422
|
+
type: "update",
|
|
423
|
+
response: "n",
|
|
424
|
+
description: "Decline Codex update to continue execution",
|
|
425
|
+
safe: true
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
pattern: /new version.*\[y\/n\]/i,
|
|
429
|
+
type: "update",
|
|
430
|
+
response: "n",
|
|
431
|
+
description: "Decline version upgrade",
|
|
432
|
+
safe: true
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
pattern: /send.*telemetry.*\[y\/n\]/i,
|
|
436
|
+
type: "config",
|
|
437
|
+
response: "n",
|
|
438
|
+
description: "Decline telemetry",
|
|
439
|
+
safe: true
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
pattern: /enable.*beta.*features.*\[y\/n\]/i,
|
|
443
|
+
type: "config",
|
|
444
|
+
response: "n",
|
|
445
|
+
description: "Decline beta features",
|
|
446
|
+
safe: true
|
|
447
|
+
}
|
|
448
|
+
];
|
|
449
|
+
getCommand() {
|
|
450
|
+
return "codex";
|
|
451
|
+
}
|
|
452
|
+
getArgs(config) {
|
|
453
|
+
const args = [];
|
|
454
|
+
args.push("--quiet");
|
|
455
|
+
if (config.workdir) {
|
|
456
|
+
args.push("--cwd", config.workdir);
|
|
457
|
+
}
|
|
458
|
+
return args;
|
|
459
|
+
}
|
|
460
|
+
getEnv(config) {
|
|
461
|
+
const env = {};
|
|
462
|
+
const credentials = this.getCredentials(config);
|
|
463
|
+
if (credentials.openaiKey) {
|
|
464
|
+
env.OPENAI_API_KEY = credentials.openaiKey;
|
|
465
|
+
}
|
|
466
|
+
if (config.env?.OPENAI_MODEL) {
|
|
467
|
+
env.OPENAI_MODEL = config.env.OPENAI_MODEL;
|
|
468
|
+
}
|
|
469
|
+
env.NO_COLOR = "1";
|
|
470
|
+
return env;
|
|
471
|
+
}
|
|
472
|
+
detectLogin(output) {
|
|
473
|
+
const stripped = this.stripAnsi(output);
|
|
474
|
+
if (stripped.includes("API key not found") || stripped.includes("OPENAI_API_KEY") || stripped.includes("authentication required") || stripped.includes("Invalid API key") || stripped.includes("Unauthorized") || stripped.includes("API key is invalid")) {
|
|
475
|
+
return {
|
|
476
|
+
required: true,
|
|
477
|
+
type: "api_key",
|
|
478
|
+
instructions: "Set OPENAI_API_KEY environment variable or provide credentials in adapterConfig"
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
if (stripped.includes("device code") || stripped.includes("Enter the code")) {
|
|
482
|
+
const codeMatch = stripped.match(/code[:\s]+([A-Z0-9-]+)/i);
|
|
483
|
+
const urlMatch = stripped.match(/https?:\/\/[^\s]+/);
|
|
484
|
+
return {
|
|
485
|
+
required: true,
|
|
486
|
+
type: "device_code",
|
|
487
|
+
url: urlMatch ? urlMatch[0] : void 0,
|
|
488
|
+
instructions: codeMatch ? `Enter code ${codeMatch[1]} at the URL` : "Device code authentication required"
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
return { required: false };
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Detect blocking prompts specific to OpenAI Codex CLI
|
|
495
|
+
*/
|
|
496
|
+
detectBlockingPrompt(output) {
|
|
497
|
+
const stripped = this.stripAnsi(output);
|
|
498
|
+
const loginDetection = this.detectLogin(output);
|
|
499
|
+
if (loginDetection.required) {
|
|
500
|
+
return {
|
|
501
|
+
detected: true,
|
|
502
|
+
type: "login",
|
|
503
|
+
prompt: loginDetection.instructions,
|
|
504
|
+
url: loginDetection.url,
|
|
505
|
+
canAutoRespond: false,
|
|
506
|
+
instructions: loginDetection.instructions
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
if (/select.*model|choose.*model|gpt-4|gpt-3\.5/i.test(stripped) && /\d+\)/i.test(stripped)) {
|
|
510
|
+
return {
|
|
511
|
+
detected: true,
|
|
512
|
+
type: "model_select",
|
|
513
|
+
prompt: "OpenAI model selection",
|
|
514
|
+
canAutoRespond: false,
|
|
515
|
+
instructions: "Please select a model or set OPENAI_MODEL env var"
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
if (/select.*organization|choose.*org|multiple organizations/i.test(stripped)) {
|
|
519
|
+
return {
|
|
520
|
+
detected: true,
|
|
521
|
+
type: "config",
|
|
522
|
+
prompt: "Organization selection",
|
|
523
|
+
canAutoRespond: false,
|
|
524
|
+
instructions: "Please select an OpenAI organization"
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
if (/rate limit|too many requests/i.test(stripped) && /retry|wait/i.test(stripped)) {
|
|
528
|
+
return {
|
|
529
|
+
detected: true,
|
|
530
|
+
type: "unknown",
|
|
531
|
+
prompt: "Rate limit reached",
|
|
532
|
+
canAutoRespond: false,
|
|
533
|
+
instructions: "OpenAI rate limit reached - please wait"
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
return super.detectBlockingPrompt(output);
|
|
537
|
+
}
|
|
538
|
+
detectReady(output) {
|
|
539
|
+
const stripped = this.stripAnsi(output);
|
|
540
|
+
return stripped.includes("Codex") || stripped.includes("Ready") || stripped.includes("How can I help") || /(?:codex|>)\s*$/i.test(stripped);
|
|
541
|
+
}
|
|
542
|
+
parseOutput(output) {
|
|
543
|
+
const stripped = this.stripAnsi(output);
|
|
544
|
+
const isComplete = this.isResponseComplete(stripped);
|
|
545
|
+
if (!isComplete) {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
const isQuestion = this.containsQuestion(stripped);
|
|
549
|
+
const content = this.extractContent(stripped, /^.*(?:codex|>)\s*/gim);
|
|
550
|
+
return {
|
|
551
|
+
type: isQuestion ? "question" : "response",
|
|
552
|
+
content,
|
|
553
|
+
isComplete: true,
|
|
554
|
+
isQuestion,
|
|
555
|
+
metadata: {
|
|
556
|
+
raw: output
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
getPromptPattern() {
|
|
561
|
+
return /(?:codex|>)\s*$/i;
|
|
562
|
+
}
|
|
563
|
+
getHealthCheckCommand() {
|
|
564
|
+
return "codex --version";
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
// src/aider-adapter.ts
|
|
569
|
+
var AiderAdapter = class extends BaseCodingAdapter {
|
|
570
|
+
adapterType = "aider";
|
|
571
|
+
displayName = "Aider";
|
|
572
|
+
installation = {
|
|
573
|
+
command: "pip install aider-chat",
|
|
574
|
+
alternatives: [
|
|
575
|
+
"pipx install aider-chat (isolated install)",
|
|
576
|
+
"brew install aider (macOS with Homebrew)"
|
|
577
|
+
],
|
|
578
|
+
docsUrl: "https://aider.chat/docs/install.html",
|
|
579
|
+
minVersion: "0.50.0"
|
|
580
|
+
};
|
|
581
|
+
/**
|
|
582
|
+
* Auto-response rules for Aider CLI.
|
|
583
|
+
*/
|
|
584
|
+
autoResponseRules = [
|
|
585
|
+
{
|
|
586
|
+
pattern: /Add .+ to the chat\?.*\[y\/n\]/i,
|
|
587
|
+
type: "permission",
|
|
588
|
+
response: "y",
|
|
589
|
+
description: "Allow Aider to add files to chat context",
|
|
590
|
+
safe: true
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
pattern: /Create new file.*\[y\/n\]/i,
|
|
594
|
+
type: "permission",
|
|
595
|
+
response: "y",
|
|
596
|
+
description: "Allow Aider to create new files",
|
|
597
|
+
safe: true
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
pattern: /Apply.*changes.*\[y\/n\]/i,
|
|
601
|
+
type: "permission",
|
|
602
|
+
response: "y",
|
|
603
|
+
description: "Apply proposed changes",
|
|
604
|
+
safe: true
|
|
605
|
+
}
|
|
606
|
+
];
|
|
607
|
+
getCommand() {
|
|
608
|
+
return "aider";
|
|
609
|
+
}
|
|
610
|
+
getArgs(config) {
|
|
611
|
+
const args = [];
|
|
612
|
+
args.push("--auto-commits");
|
|
613
|
+
args.push("--no-pretty");
|
|
614
|
+
args.push("--no-show-diffs");
|
|
615
|
+
const credentials = this.getCredentials(config);
|
|
616
|
+
if (config.env?.AIDER_MODEL) {
|
|
617
|
+
args.push("--model", config.env.AIDER_MODEL);
|
|
618
|
+
}
|
|
619
|
+
if (credentials.anthropicKey && !config.env?.AIDER_MODEL) {
|
|
620
|
+
args.push("--model", "claude-3-5-sonnet-20241022");
|
|
621
|
+
}
|
|
622
|
+
return args;
|
|
623
|
+
}
|
|
624
|
+
getEnv(config) {
|
|
625
|
+
const env = {};
|
|
626
|
+
const credentials = this.getCredentials(config);
|
|
627
|
+
if (credentials.anthropicKey) {
|
|
628
|
+
env.ANTHROPIC_API_KEY = credentials.anthropicKey;
|
|
629
|
+
}
|
|
630
|
+
if (credentials.openaiKey) {
|
|
631
|
+
env.OPENAI_API_KEY = credentials.openaiKey;
|
|
632
|
+
}
|
|
633
|
+
if (credentials.googleKey) {
|
|
634
|
+
env.GOOGLE_API_KEY = credentials.googleKey;
|
|
635
|
+
}
|
|
636
|
+
env.NO_COLOR = "1";
|
|
637
|
+
if (config.env?.AIDER_NO_GIT === "true") {
|
|
638
|
+
env.AIDER_NO_GIT = "true";
|
|
639
|
+
}
|
|
640
|
+
return env;
|
|
641
|
+
}
|
|
642
|
+
detectLogin(output) {
|
|
643
|
+
const stripped = this.stripAnsi(output);
|
|
644
|
+
if (stripped.includes("No API key") || stripped.includes("API key not found") || stripped.includes("ANTHROPIC_API_KEY") || stripped.includes("OPENAI_API_KEY") || stripped.includes("Missing API key")) {
|
|
645
|
+
return {
|
|
646
|
+
required: true,
|
|
647
|
+
type: "api_key",
|
|
648
|
+
instructions: "Set ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable"
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
if (stripped.includes("Invalid API key") || stripped.includes("Authentication failed") || stripped.includes("Unauthorized")) {
|
|
652
|
+
return {
|
|
653
|
+
required: true,
|
|
654
|
+
type: "api_key",
|
|
655
|
+
instructions: "API key is invalid - please check your credentials"
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
return { required: false };
|
|
659
|
+
}
|
|
660
|
+
detectBlockingPrompt(output) {
|
|
661
|
+
const stripped = this.stripAnsi(output);
|
|
662
|
+
const loginDetection = this.detectLogin(output);
|
|
663
|
+
if (loginDetection.required) {
|
|
664
|
+
return {
|
|
665
|
+
detected: true,
|
|
666
|
+
type: "login",
|
|
667
|
+
prompt: loginDetection.instructions,
|
|
668
|
+
canAutoRespond: false,
|
|
669
|
+
instructions: loginDetection.instructions
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
if (/select.*model|choose.*model|which model/i.test(stripped)) {
|
|
673
|
+
return {
|
|
674
|
+
detected: true,
|
|
675
|
+
type: "model_select",
|
|
676
|
+
prompt: "Model selection required",
|
|
677
|
+
canAutoRespond: false,
|
|
678
|
+
instructions: "Please select a model or set AIDER_MODEL env var"
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
if (/not.*git.*repo|git.*not.*found|initialize.*git/i.test(stripped)) {
|
|
682
|
+
return {
|
|
683
|
+
detected: true,
|
|
684
|
+
type: "config",
|
|
685
|
+
prompt: "Git repository required",
|
|
686
|
+
canAutoRespond: false,
|
|
687
|
+
instructions: "Aider requires a git repository. Run git init or use --no-git"
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
if (/delete|remove|overwrite/i.test(stripped) && /\[y\/n\]/i.test(stripped)) {
|
|
691
|
+
return {
|
|
692
|
+
detected: true,
|
|
693
|
+
type: "permission",
|
|
694
|
+
prompt: "Destructive operation confirmation",
|
|
695
|
+
options: ["y", "n"],
|
|
696
|
+
canAutoRespond: false,
|
|
697
|
+
instructions: "Aider is asking to perform a potentially destructive operation"
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
return super.detectBlockingPrompt(output);
|
|
701
|
+
}
|
|
702
|
+
detectReady(output) {
|
|
703
|
+
const stripped = this.stripAnsi(output);
|
|
704
|
+
return stripped.includes("aider>") || stripped.includes("Aider") || /aider.*ready/i.test(stripped) || // Aider shows file list when ready
|
|
705
|
+
/Added.*to the chat/i.test(stripped) || // Or the prompt
|
|
706
|
+
/>\s*$/.test(stripped);
|
|
707
|
+
}
|
|
708
|
+
parseOutput(output) {
|
|
709
|
+
const stripped = this.stripAnsi(output);
|
|
710
|
+
const isComplete = this.isResponseComplete(stripped);
|
|
711
|
+
if (!isComplete) {
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
const isQuestion = this.containsQuestion(stripped);
|
|
715
|
+
let content = this.extractContent(stripped, /^.*aider>\s*/gim);
|
|
716
|
+
content = content.replace(/^(Added|Removed|Created|Updated) .+ (to|from) the chat\.?$/gm, "");
|
|
717
|
+
return {
|
|
718
|
+
type: isQuestion ? "question" : "response",
|
|
719
|
+
content: content.trim(),
|
|
720
|
+
isComplete: true,
|
|
721
|
+
isQuestion,
|
|
722
|
+
metadata: {
|
|
723
|
+
raw: output
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
getPromptPattern() {
|
|
728
|
+
return /(?:aider>|>)\s*$/i;
|
|
729
|
+
}
|
|
730
|
+
getHealthCheckCommand() {
|
|
731
|
+
return "aider --version";
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
// src/index.ts
|
|
736
|
+
function createAllAdapters() {
|
|
737
|
+
return [
|
|
738
|
+
new ClaudeAdapter(),
|
|
739
|
+
new GeminiAdapter(),
|
|
740
|
+
new CodexAdapter(),
|
|
741
|
+
new AiderAdapter()
|
|
742
|
+
];
|
|
743
|
+
}
|
|
744
|
+
var ADAPTER_TYPES = {
|
|
745
|
+
claude: ClaudeAdapter,
|
|
746
|
+
gemini: GeminiAdapter,
|
|
747
|
+
codex: CodexAdapter,
|
|
748
|
+
aider: AiderAdapter
|
|
749
|
+
};
|
|
750
|
+
function createAdapter(type) {
|
|
751
|
+
const AdapterClass = ADAPTER_TYPES[type];
|
|
752
|
+
if (!AdapterClass) {
|
|
753
|
+
throw new Error(`Unknown adapter type: ${type}`);
|
|
754
|
+
}
|
|
755
|
+
return new AdapterClass();
|
|
756
|
+
}
|
|
757
|
+
async function checkAdapters(types) {
|
|
758
|
+
const results = [];
|
|
759
|
+
for (const type of types) {
|
|
760
|
+
const adapter = createAdapter(type);
|
|
761
|
+
const validation = await adapter.validateInstallation();
|
|
762
|
+
results.push({
|
|
763
|
+
adapter: adapter.displayName,
|
|
764
|
+
installed: validation.installed,
|
|
765
|
+
version: validation.version,
|
|
766
|
+
error: validation.error,
|
|
767
|
+
installCommand: adapter.installation.command,
|
|
768
|
+
docsUrl: adapter.installation.docsUrl
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
return results;
|
|
772
|
+
}
|
|
773
|
+
async function checkAllAdapters() {
|
|
774
|
+
return checkAdapters(Object.keys(ADAPTER_TYPES));
|
|
775
|
+
}
|
|
776
|
+
async function printMissingAdapters(types) {
|
|
777
|
+
const results = types ? await checkAdapters(types) : await checkAllAdapters();
|
|
778
|
+
const missing = results.filter((r) => !r.installed);
|
|
779
|
+
if (missing.length === 0) {
|
|
780
|
+
console.log("All CLI tools are installed!");
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
console.log("\nMissing CLI tools:\n");
|
|
784
|
+
for (const m of missing) {
|
|
785
|
+
console.log(`${m.adapter}`);
|
|
786
|
+
console.log(` Install: ${m.installCommand}`);
|
|
787
|
+
console.log(` Docs: ${m.docsUrl}`);
|
|
788
|
+
if (m.error) {
|
|
789
|
+
console.log(` Error: ${m.error}`);
|
|
790
|
+
}
|
|
791
|
+
console.log();
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
exports.ADAPTER_TYPES = ADAPTER_TYPES;
|
|
796
|
+
exports.AiderAdapter = AiderAdapter;
|
|
797
|
+
exports.BaseCodingAdapter = BaseCodingAdapter;
|
|
798
|
+
exports.ClaudeAdapter = ClaudeAdapter;
|
|
799
|
+
exports.CodexAdapter = CodexAdapter;
|
|
800
|
+
exports.GeminiAdapter = GeminiAdapter;
|
|
801
|
+
exports.checkAdapters = checkAdapters;
|
|
802
|
+
exports.checkAllAdapters = checkAllAdapters;
|
|
803
|
+
exports.createAdapter = createAdapter;
|
|
804
|
+
exports.createAllAdapters = createAllAdapters;
|
|
805
|
+
exports.printMissingAdapters = printMissingAdapters;
|
|
806
|
+
//# sourceMappingURL=index.cjs.map
|
|
807
|
+
//# sourceMappingURL=index.cjs.map
|