agent-mp 0.4.15 → 0.5.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/commands/repl.js +66 -9
- package/dist/index.js +1 -1
- package/dist/utils/qwen-auth.js +23 -25
- package/package.json +1 -1
- package/dist/api/qwen.d.ts +0 -12
- package/dist/api/qwen.js +0 -150
- package/dist/commands/auth.d.ts +0 -31
- package/dist/commands/auth.js +0 -255
- package/dist/commands/config.d.ts +0 -2
- package/dist/commands/config.js +0 -42
- package/dist/core/prompts.d.ts +0 -11
- package/dist/core/prompts.js +0 -126
- package/dist/providers/index.d.ts +0 -2
- package/dist/providers/index.js +0 -120
package/dist/commands/repl.js
CHANGED
|
@@ -1136,11 +1136,11 @@ export async function runRepl(resumeSession) {
|
|
|
1136
1136
|
* Prompt estricto: cada rol solo hace lo que debe, sin extras.
|
|
1137
1137
|
*/
|
|
1138
1138
|
export async function runRole(role, arg, model) {
|
|
1139
|
-
const
|
|
1140
|
-
if (
|
|
1141
|
-
|
|
1142
|
-
const
|
|
1143
|
-
const
|
|
1139
|
+
const { PKG_NAME, getConfigDir } = await import('../utils/config.js');
|
|
1140
|
+
// Detect if running a native role binary directly (agent-orch, agent-impl, etc.)
|
|
1141
|
+
// In that case, skip REPL and run headless — no coordinator needed
|
|
1142
|
+
const ROLE_BINS = new Set(['agent-orch', 'agent-impl', 'agent-rev', 'agent-explorer']);
|
|
1143
|
+
const isNativeBin = ROLE_BINS.has(PKG_NAME);
|
|
1144
1144
|
const dir = process.cwd();
|
|
1145
1145
|
let config;
|
|
1146
1146
|
try {
|
|
@@ -1148,13 +1148,11 @@ export async function runRole(role, arg, model) {
|
|
|
1148
1148
|
}
|
|
1149
1149
|
catch {
|
|
1150
1150
|
console.log(chalk.red(' No project config found. Run /setup first.'));
|
|
1151
|
-
rl.close();
|
|
1152
1151
|
process.exit(1);
|
|
1153
1152
|
}
|
|
1154
1153
|
// Validate roles are configured
|
|
1155
1154
|
if (!config.roles?.orchestrator?.cli || !config.roles?.implementor?.cli || !config.roles?.reviewer?.cli) {
|
|
1156
1155
|
console.log(chalk.red(' Roles not configured. Run setup first.'));
|
|
1157
|
-
rl.close();
|
|
1158
1156
|
process.exit(1);
|
|
1159
1157
|
}
|
|
1160
1158
|
// Override model if passed via --model flag
|
|
@@ -1170,7 +1168,60 @@ export async function runRole(role, arg, model) {
|
|
|
1170
1168
|
r.cmd = r.cmd.replace(/(-m|--model)\s+\S+/, `$1 ${model}`);
|
|
1171
1169
|
}
|
|
1172
1170
|
}
|
|
1173
|
-
console.log(chalk.bold.cyan(`\n
|
|
1171
|
+
console.log(chalk.bold.cyan(`\n ${PKG_NAME} — Rol: ${role.toUpperCase()}${model ? ` (${model})` : ''}\n`));
|
|
1172
|
+
if (isNativeBin) {
|
|
1173
|
+
// Headless mode: create a dummy RL (no stdin), run engine directly
|
|
1174
|
+
const { Readable } = await import('stream');
|
|
1175
|
+
const emptyStream = new Readable({ read() { this.push(null); } });
|
|
1176
|
+
const dummyRl = readline.createInterface({ input: emptyStream, output: process.stdout });
|
|
1177
|
+
const engine = new AgentEngine(config, dir, '', dummyRl);
|
|
1178
|
+
try {
|
|
1179
|
+
switch (role.toLowerCase()) {
|
|
1180
|
+
case 'orchestrator':
|
|
1181
|
+
case 'orch': {
|
|
1182
|
+
const result = await engine.runOrchestrator(arg);
|
|
1183
|
+
log.ok(`Task ID: ${result.taskId}`);
|
|
1184
|
+
break;
|
|
1185
|
+
}
|
|
1186
|
+
case 'implementor':
|
|
1187
|
+
case 'impl': {
|
|
1188
|
+
const taskDir = path.join(dir, '.agent', 'tasks', arg);
|
|
1189
|
+
const plan = await readJson(path.join(taskDir, 'plan.json'));
|
|
1190
|
+
await engine.runImplementor(arg, plan);
|
|
1191
|
+
break;
|
|
1192
|
+
}
|
|
1193
|
+
case 'reviewer':
|
|
1194
|
+
case 'rev': {
|
|
1195
|
+
const taskDir = path.join(dir, '.agent', 'tasks', arg);
|
|
1196
|
+
const plan = await readJson(path.join(taskDir, 'plan.json'));
|
|
1197
|
+
const progress = await readJson(path.join(taskDir, 'progress.json'));
|
|
1198
|
+
await engine.runReviewer(arg, plan, progress);
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
case 'explorer':
|
|
1202
|
+
case 'exp': {
|
|
1203
|
+
const result = await engine.runExplorer(arg);
|
|
1204
|
+
log.ok(`Explorer: ${result.substring(0, 100)}...`);
|
|
1205
|
+
break;
|
|
1206
|
+
}
|
|
1207
|
+
default:
|
|
1208
|
+
console.log(chalk.red(` Unknown role: ${role}. Use: orchestrator, implementor, reviewer, explorer`));
|
|
1209
|
+
process.exit(1);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
catch (err) {
|
|
1213
|
+
console.log(chalk.red(` Error: ${err.message}`));
|
|
1214
|
+
process.exit(1);
|
|
1215
|
+
}
|
|
1216
|
+
dummyRl.close();
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
// REPL mode: needs coordinator
|
|
1220
|
+
const init = await initCoordinator();
|
|
1221
|
+
if (!init)
|
|
1222
|
+
process.exit(1);
|
|
1223
|
+
const { coordinatorCmd } = init;
|
|
1224
|
+
const rl = init.rl;
|
|
1174
1225
|
const engine = new AgentEngine(config, dir, coordinatorCmd, rl);
|
|
1175
1226
|
try {
|
|
1176
1227
|
switch (role.toLowerCase()) {
|
|
@@ -1195,6 +1246,12 @@ export async function runRole(role, arg, model) {
|
|
|
1195
1246
|
await engine.runReviewer(arg, plan, progress);
|
|
1196
1247
|
break;
|
|
1197
1248
|
}
|
|
1249
|
+
case 'explorer':
|
|
1250
|
+
case 'exp': {
|
|
1251
|
+
const result = await engine.runExplorer(arg);
|
|
1252
|
+
log.ok(`Explorer: ${result.substring(0, 100)}...`);
|
|
1253
|
+
break;
|
|
1254
|
+
}
|
|
1198
1255
|
case 'coordinator':
|
|
1199
1256
|
case 'coord': {
|
|
1200
1257
|
console.log(chalk.yellow(' Coordinator mode requires interactive REPL.'));
|
|
@@ -1202,7 +1259,7 @@ export async function runRole(role, arg, model) {
|
|
|
1202
1259
|
process.exit(1);
|
|
1203
1260
|
}
|
|
1204
1261
|
default:
|
|
1205
|
-
console.log(chalk.red(` Unknown role: ${role}. Use: orchestrator, implementor, reviewer`));
|
|
1262
|
+
console.log(chalk.red(` Unknown role: ${role}. Use: orchestrator, implementor, reviewer, explorer`));
|
|
1206
1263
|
process.exit(1);
|
|
1207
1264
|
}
|
|
1208
1265
|
}
|
package/dist/index.js
CHANGED
|
@@ -137,7 +137,7 @@ if (nativeRole) {
|
|
|
137
137
|
if (modelIdx !== -1 && args[modelIdx + 1]) {
|
|
138
138
|
model = args[modelIdx + 1];
|
|
139
139
|
}
|
|
140
|
-
const taskArg = args.filter((a, i) => !a.startsWith('-') && i !== modelIdx + 1).join(' ').trim();
|
|
140
|
+
const taskArg = args.filter((a, i) => !a.startsWith('-') && (modelIdx === -1 || i !== modelIdx + 1)).join(' ').trim();
|
|
141
141
|
if (!taskArg) {
|
|
142
142
|
console.log(chalk.bold.cyan(`\n ${PKG_NAME} — ${nativeRole} agent\n`));
|
|
143
143
|
console.log(chalk.dim(` Usage: ${PKG_NAME} [--model <model>] "<task>"`));
|
package/dist/utils/qwen-auth.js
CHANGED
|
@@ -42,9 +42,10 @@ async function loadToken() {
|
|
|
42
42
|
};
|
|
43
43
|
if (!token.accessToken)
|
|
44
44
|
return null;
|
|
45
|
-
// Refresh proactivo: si vence en menos de
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
// Refresh proactivo: si vence en menos de 5 minutos (o ya venció)
|
|
46
|
+
// Mismo threshold que el CLI original de qwen
|
|
47
|
+
const FIVE_MIN = 5 * 60 * 1000;
|
|
48
|
+
if (token.expiresAt - Date.now() < FIVE_MIN && token.refreshToken) {
|
|
48
49
|
const refreshed = await doRefreshToken(token.refreshToken);
|
|
49
50
|
if (refreshed) {
|
|
50
51
|
await saveToken(refreshed);
|
|
@@ -253,45 +254,41 @@ export async function getQwenAccessToken() {
|
|
|
253
254
|
return token?.accessToken || null;
|
|
254
255
|
}
|
|
255
256
|
async function callQwenAPIWithToken(token, prompt, model, onData) {
|
|
256
|
-
//
|
|
257
|
+
// Build base URL from token's resource_url (same logic as qwen CLI original)
|
|
257
258
|
const rawHost = token.resourceUrl || 'dashscope.aliyuncs.com/compatible-mode';
|
|
258
|
-
const
|
|
259
|
-
|
|
259
|
+
const baseUrl = rawHost.startsWith('http')
|
|
260
|
+
? (rawHost.endsWith('/v1') ? rawHost : rawHost.replace(/\/$/, '') + '/v1')
|
|
261
|
+
: `https://${rawHost}/v1`;
|
|
260
262
|
const useStream = !!onData;
|
|
261
|
-
|
|
262
|
-
//
|
|
263
|
-
const
|
|
263
|
+
// portal.qwen.ai requires: content as array of {type, text} objects + system message
|
|
264
|
+
// DashScope accepts: plain string content
|
|
265
|
+
const isPortalQwen = rawHost.includes('portal.qwen.ai') || rawHost.includes('chat.qwen.ai');
|
|
266
|
+
const toContent = (text) => isPortalQwen ? [{ type: 'text', text }] : text;
|
|
264
267
|
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
265
268
|
method: 'POST',
|
|
266
269
|
headers: {
|
|
267
270
|
'Authorization': `Bearer ${token.accessToken}`,
|
|
268
271
|
'Content-Type': 'application/json',
|
|
269
272
|
'Accept': 'application/json',
|
|
270
|
-
'User-Agent':
|
|
271
|
-
'
|
|
272
|
-
'
|
|
273
|
-
'
|
|
274
|
-
'x-stainless-lang': 'js',
|
|
275
|
-
'x-stainless-package-version': '5.11.0',
|
|
276
|
-
'x-stainless-os': process.platform,
|
|
277
|
-
'x-stainless-arch': process.arch,
|
|
278
|
-
'x-stainless-runtime': 'node',
|
|
279
|
-
'x-stainless-runtime-version': process.version,
|
|
280
|
-
'x-stainless-retry-count': '0',
|
|
273
|
+
'User-Agent': `QwenCode/0.14.2 (${process.platform}; ${process.arch})`,
|
|
274
|
+
'X-DashScope-AuthType': 'qwen-oauth',
|
|
275
|
+
'X-DashScope-CacheControl': 'enable',
|
|
276
|
+
'X-DashScope-UserAgent': `QwenCode/0.14.2 (${process.platform}; ${process.arch})`,
|
|
281
277
|
},
|
|
282
278
|
body: JSON.stringify({
|
|
283
279
|
model: model || 'coder-model',
|
|
284
280
|
messages: [
|
|
285
|
-
{ role: 'system', content:
|
|
286
|
-
{ role: 'user', content:
|
|
281
|
+
{ role: 'system', content: toContent('You are a helpful coding assistant.') },
|
|
282
|
+
{ role: 'user', content: toContent(prompt) },
|
|
287
283
|
],
|
|
288
284
|
stream: useStream,
|
|
289
285
|
}),
|
|
290
286
|
});
|
|
291
287
|
if (!response.ok) {
|
|
292
288
|
const errorText = await response.text();
|
|
293
|
-
if (response.status === 401)
|
|
289
|
+
if (response.status === 401 || (response.status === 429 && errorText.includes('insufficient_quota'))) {
|
|
294
290
|
throw new Error(`QWEN_AUTH_EXPIRED: ${errorText}`);
|
|
291
|
+
}
|
|
295
292
|
throw new Error(`Qwen API error: ${response.status} - ${errorText}`);
|
|
296
293
|
}
|
|
297
294
|
if (!useStream) {
|
|
@@ -380,8 +377,9 @@ export async function callQwenAPIFromCreds(prompt, model, credsPath, onData) {
|
|
|
380
377
|
if (!token.accessToken) {
|
|
381
378
|
throw new Error(`Invalid credentials at ${credsPath}. Run: ${cliName} --login`);
|
|
382
379
|
}
|
|
383
|
-
|
|
384
|
-
|
|
380
|
+
// Refresh proactivo: mismo threshold de 5 minutos que el CLI original
|
|
381
|
+
const FIVE_MIN = 5 * 60 * 1000;
|
|
382
|
+
if (token.expiresAt - Date.now() < FIVE_MIN && token.refreshToken) {
|
|
385
383
|
const refreshed = await doRefreshToken(token.refreshToken);
|
|
386
384
|
if (refreshed) {
|
|
387
385
|
token = refreshed;
|
package/package.json
CHANGED
package/dist/api/qwen.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
interface QwenMessage {
|
|
2
|
-
role: 'system' | 'user' | 'assistant';
|
|
3
|
-
content: string;
|
|
4
|
-
}
|
|
5
|
-
export declare function qwenLogin(): Promise<boolean>;
|
|
6
|
-
export declare function qwenAuthStatus(): Promise<{
|
|
7
|
-
authenticated: boolean;
|
|
8
|
-
email?: string;
|
|
9
|
-
}>;
|
|
10
|
-
export declare function qwenChat(messages: QwenMessage[], model?: string): Promise<string>;
|
|
11
|
-
export declare function qwenAsk(prompt: string, model?: string): Promise<string>;
|
|
12
|
-
export {};
|
package/dist/api/qwen.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
import * as fs from 'fs/promises';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import * as os from 'os';
|
|
5
|
-
import open from 'open';
|
|
6
|
-
const QWEN_API_BASE = 'https://dashscope-intl.aliyuncs.com/api/v1';
|
|
7
|
-
const QWEN_AUTH_URL = 'https://oauth.aliyun.com/v1/oauth/authorize';
|
|
8
|
-
const CLIENT_ID = 'your_client_id'; // Reemplazar con el client ID real de Qwen
|
|
9
|
-
const REDIRECT_URI = 'http://localhost:3000/callback';
|
|
10
|
-
let tokenCache = null;
|
|
11
|
-
async function getTokenPath() {
|
|
12
|
-
const homeDir = os.homedir();
|
|
13
|
-
const dir = path.join(homeDir, '.agent');
|
|
14
|
-
await fs.mkdir(dir, { recursive: true });
|
|
15
|
-
return path.join(dir, 'qwen-token.json');
|
|
16
|
-
}
|
|
17
|
-
async function loadToken() {
|
|
18
|
-
try {
|
|
19
|
-
const tokenPath = await getTokenPath();
|
|
20
|
-
const content = await fs.readFile(tokenPath, 'utf-8');
|
|
21
|
-
const token = JSON.parse(content);
|
|
22
|
-
if (token.expiresAt > Date.now()) {
|
|
23
|
-
return token;
|
|
24
|
-
}
|
|
25
|
-
// Token expirado, intentar refresh
|
|
26
|
-
if (token.refreshToken) {
|
|
27
|
-
return refreshAccessToken(token.refreshToken);
|
|
28
|
-
}
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
async function saveToken(token) {
|
|
36
|
-
const tokenPath = await getTokenPath();
|
|
37
|
-
await fs.writeFile(tokenPath, JSON.stringify(token, null, 2), 'utf-8');
|
|
38
|
-
}
|
|
39
|
-
async function refreshAccessToken(refreshToken) {
|
|
40
|
-
try {
|
|
41
|
-
const response = await axios.post('https://oauth.aliyun.com/v1/oauth/token', new URLSearchParams({
|
|
42
|
-
grant_type: 'refresh_token',
|
|
43
|
-
refresh_token: refreshToken,
|
|
44
|
-
client_id: CLIENT_ID,
|
|
45
|
-
}), {
|
|
46
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
47
|
-
});
|
|
48
|
-
const token = {
|
|
49
|
-
accessToken: response.data.access_token,
|
|
50
|
-
refreshToken: response.data.refresh_token || refreshToken,
|
|
51
|
-
expiresAt: Date.now() + (response.data.expires_in || 3600) * 1000,
|
|
52
|
-
};
|
|
53
|
-
await saveToken(token);
|
|
54
|
-
return token;
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
console.error('Error refreshing token:', error);
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
export async function qwenLogin() {
|
|
62
|
-
const authUrl = `${QWEN_AUTH_URL}?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&response_type=code&scope=api`;
|
|
63
|
-
console.log('Abriendo navegador para autenticación con Qwen...');
|
|
64
|
-
console.log('URL:', authUrl);
|
|
65
|
-
await open(authUrl);
|
|
66
|
-
console.log('');
|
|
67
|
-
console.log('Por favor completá la autenticación en el navegador.');
|
|
68
|
-
console.log('Cuando obtengas el código de autorización, ingresalo abajo:');
|
|
69
|
-
return new Promise((resolve) => {
|
|
70
|
-
const readline = await import('readline');
|
|
71
|
-
const rl = readline.createInterface({
|
|
72
|
-
input: process.stdin,
|
|
73
|
-
output: process.stdout,
|
|
74
|
-
});
|
|
75
|
-
rl.question('Código de autorización: ', async (code) => {
|
|
76
|
-
try {
|
|
77
|
-
const response = await axios.post('https://oauth.aliyun.com/v1/oauth/token', new URLSearchParams({
|
|
78
|
-
grant_type: 'authorization_code',
|
|
79
|
-
code,
|
|
80
|
-
redirect_uri: REDIRECT_URI,
|
|
81
|
-
client_id: CLIENT_ID,
|
|
82
|
-
}), {
|
|
83
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
84
|
-
});
|
|
85
|
-
const token = {
|
|
86
|
-
accessToken: response.data.access_token,
|
|
87
|
-
refreshToken: response.data.refresh_token,
|
|
88
|
-
expiresAt: Date.now() + (response.data.expires_in || 3600) * 1000,
|
|
89
|
-
};
|
|
90
|
-
await saveToken(token);
|
|
91
|
-
console.log('¡Autenticación exitosa!');
|
|
92
|
-
rl.close();
|
|
93
|
-
resolve(true);
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
console.error('Error en autenticación:', error);
|
|
97
|
-
rl.close();
|
|
98
|
-
resolve(false);
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
export async function qwenAuthStatus() {
|
|
104
|
-
const token = await loadToken();
|
|
105
|
-
if (token) {
|
|
106
|
-
return { authenticated: true };
|
|
107
|
-
}
|
|
108
|
-
return { authenticated: false };
|
|
109
|
-
}
|
|
110
|
-
export async function qwenChat(messages, model = 'qwen-coder') {
|
|
111
|
-
const token = await loadToken();
|
|
112
|
-
if (!token) {
|
|
113
|
-
throw new Error('No hay token de Qwen. Ejecutá /qwen-login primero.');
|
|
114
|
-
}
|
|
115
|
-
try {
|
|
116
|
-
const response = await axios.post(`${QWEN_API_BASE}/services/aigc/text-generation/generation`, {
|
|
117
|
-
model,
|
|
118
|
-
input: {
|
|
119
|
-
messages,
|
|
120
|
-
},
|
|
121
|
-
parameters: {
|
|
122
|
-
result_format: 'text',
|
|
123
|
-
},
|
|
124
|
-
}, {
|
|
125
|
-
headers: {
|
|
126
|
-
'Authorization': `Bearer ${token.accessToken}`,
|
|
127
|
-
'Content-Type': 'application/json',
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
return response.data.output.text;
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
if (error.response?.status === 401) {
|
|
134
|
-
// Token inválido, intentar refresh
|
|
135
|
-
const newToken = token.refreshToken ? await refreshAccessToken(token.refreshToken) : null;
|
|
136
|
-
if (newToken) {
|
|
137
|
-
// Reintentar con el nuevo token
|
|
138
|
-
return qwenChat(messages, model);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
throw error;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
export async function qwenAsk(prompt, model = 'qwen-coder') {
|
|
145
|
-
const messages = [
|
|
146
|
-
{ role: 'system', content: 'Sos un asistente útil de programación.' },
|
|
147
|
-
{ role: 'user', content: prompt },
|
|
148
|
-
];
|
|
149
|
-
return qwenChat(messages, model);
|
|
150
|
-
}
|
package/dist/commands/auth.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
declare const CONFIG_DIR: string;
|
|
3
|
-
declare const CONFIG_FILE: string;
|
|
4
|
-
interface AuthEntry {
|
|
5
|
-
provider: string;
|
|
6
|
-
method: 'oauth' | 'apikey';
|
|
7
|
-
accessToken?: string;
|
|
8
|
-
refreshToken?: string;
|
|
9
|
-
expiresAt?: number;
|
|
10
|
-
apiKey?: string;
|
|
11
|
-
email?: string;
|
|
12
|
-
}
|
|
13
|
-
interface AuthStore {
|
|
14
|
-
entries: AuthEntry[];
|
|
15
|
-
activeProvider?: string;
|
|
16
|
-
}
|
|
17
|
-
interface CliConfig {
|
|
18
|
-
roles: Record<string, {
|
|
19
|
-
provider: string;
|
|
20
|
-
model: string;
|
|
21
|
-
}>;
|
|
22
|
-
deliberation: {
|
|
23
|
-
enabled: boolean;
|
|
24
|
-
max_rounds: number;
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
declare function loadAuth(): Promise<AuthStore>;
|
|
28
|
-
declare function loadCliConfig(): Promise<CliConfig>;
|
|
29
|
-
declare function saveCliConfig(cfg: CliConfig): Promise<void>;
|
|
30
|
-
export declare function authCommand(program: Command): void;
|
|
31
|
-
export { loadAuth, loadCliConfig, saveCliConfig, CONFIG_DIR, CONFIG_FILE };
|
package/dist/commands/auth.js
DELETED
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import * as readline from 'readline';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import * as fs from 'fs/promises';
|
|
4
|
-
import { createServer } from 'http';
|
|
5
|
-
import { createHash, randomBytes } from 'crypto';
|
|
6
|
-
import open from 'open';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
import { writeJson, ensureDir } from '../utils/fs.js';
|
|
9
|
-
const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '', '.agent-cli');
|
|
10
|
-
const AUTH_FILE = path.join(CONFIG_DIR, 'auth.json');
|
|
11
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
12
|
-
// ── OAuth providers ────────────────────────────────────────────────────────
|
|
13
|
-
const OAUTH_PROVIDERS = {
|
|
14
|
-
opencode: {
|
|
15
|
-
authorizeUrl: 'https://accounts.opencode.ai/oauth2/auth',
|
|
16
|
-
tokenUrl: 'https://accounts.opencode.ai/oauth2/token',
|
|
17
|
-
clientId: 'claw_code',
|
|
18
|
-
scopes: 'openid email profile offline_access',
|
|
19
|
-
name: 'Opencode',
|
|
20
|
-
},
|
|
21
|
-
google: {
|
|
22
|
-
authorizeUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
23
|
-
tokenUrl: 'https://oauth2.googleapis.com/token',
|
|
24
|
-
clientId: '',
|
|
25
|
-
scopes: 'openid email profile',
|
|
26
|
-
name: 'Google',
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
async function loadAuth() {
|
|
30
|
-
try {
|
|
31
|
-
const content = await fs.readFile(AUTH_FILE, 'utf-8');
|
|
32
|
-
return JSON.parse(content);
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
return { entries: [] };
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
async function saveAuth(auth) {
|
|
39
|
-
await ensureDir(CONFIG_DIR);
|
|
40
|
-
await writeJson(AUTH_FILE, auth);
|
|
41
|
-
}
|
|
42
|
-
async function loadCliConfig() {
|
|
43
|
-
try {
|
|
44
|
-
const content = await fs.readFile(CONFIG_FILE, 'utf-8');
|
|
45
|
-
return JSON.parse(content);
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
return { roles: {}, deliberation: { enabled: false, max_rounds: 4 } };
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
async function saveCliConfig(cfg) {
|
|
52
|
-
await ensureDir(CONFIG_DIR);
|
|
53
|
-
await writeJson(CONFIG_FILE, cfg);
|
|
54
|
-
}
|
|
55
|
-
function base64url(buf) {
|
|
56
|
-
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
57
|
-
}
|
|
58
|
-
async function oauthFlow(providerId) {
|
|
59
|
-
const provider = OAUTH_PROVIDERS[providerId];
|
|
60
|
-
if (!provider)
|
|
61
|
-
throw new Error(`Unknown OAuth provider: ${providerId}`);
|
|
62
|
-
const port = 9800 + Math.floor(Math.random() * 1000);
|
|
63
|
-
const state = base64url(randomBytes(32));
|
|
64
|
-
const codeVerifier = base64url(randomBytes(32));
|
|
65
|
-
const codeChallenge = base64url(createHash('sha256').update(codeVerifier).digest());
|
|
66
|
-
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
67
|
-
const authUrl = new URL(provider.authorizeUrl);
|
|
68
|
-
authUrl.searchParams.set('response_type', 'code');
|
|
69
|
-
authUrl.searchParams.set('client_id', provider.clientId);
|
|
70
|
-
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
71
|
-
authUrl.searchParams.set('scope', provider.scopes);
|
|
72
|
-
authUrl.searchParams.set('state', state);
|
|
73
|
-
authUrl.searchParams.set('code_challenge', codeChallenge);
|
|
74
|
-
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
75
|
-
console.log(chalk.dim(` Opening browser for ${provider.name} login...`));
|
|
76
|
-
await open(authUrl.toString());
|
|
77
|
-
return new Promise((resolve, reject) => {
|
|
78
|
-
const server = createServer((req, res) => {
|
|
79
|
-
const url = new URL(req.url, `http://127.0.0.1:${port}`);
|
|
80
|
-
if (url.pathname !== '/callback') {
|
|
81
|
-
res.writeHead(404);
|
|
82
|
-
res.end();
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const returnedState = url.searchParams.get('state');
|
|
86
|
-
const code = url.searchParams.get('code');
|
|
87
|
-
const error = url.searchParams.get('error');
|
|
88
|
-
if (error) {
|
|
89
|
-
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
90
|
-
res.end(`<h1>Login failed</h1><p>${error}</p>`);
|
|
91
|
-
server.close();
|
|
92
|
-
reject(new Error(`OAuth error: ${error}`));
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
if (returnedState !== state) {
|
|
96
|
-
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
97
|
-
res.end('<h1>Invalid state</h1>');
|
|
98
|
-
server.close();
|
|
99
|
-
reject(new Error('State mismatch'));
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (!code) {
|
|
103
|
-
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
104
|
-
res.end('<h1>No code</h1>');
|
|
105
|
-
server.close();
|
|
106
|
-
reject(new Error('No authorization code'));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
110
|
-
res.end(`
|
|
111
|
-
<html><body style="font-family:system-ui;text-align:center;padding:60px">
|
|
112
|
-
<h1 style="color:#06b6d4">✓ Login successful</h1>
|
|
113
|
-
<p>You can close this tab and return to the terminal.</p>
|
|
114
|
-
</body></html>
|
|
115
|
-
`);
|
|
116
|
-
server.close();
|
|
117
|
-
// Exchange code for tokens
|
|
118
|
-
exchangeToken(provider, code, redirectUri, codeVerifier).then(resolve).catch(reject);
|
|
119
|
-
});
|
|
120
|
-
server.listen(port, '127.0.0.1', () => {
|
|
121
|
-
console.log(chalk.blue(` → Waiting for callback on port ${port}...`));
|
|
122
|
-
});
|
|
123
|
-
server.on('error', (err) => {
|
|
124
|
-
if (err.code === 'EADDRINUSE') {
|
|
125
|
-
reject(new Error(`Port ${port} is in use. Try again.`));
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
reject(err);
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
async function exchangeToken(provider, code, redirectUri, codeVerifier) {
|
|
134
|
-
const params = new URLSearchParams({
|
|
135
|
-
grant_type: 'authorization_code',
|
|
136
|
-
client_id: provider.clientId,
|
|
137
|
-
code,
|
|
138
|
-
redirect_uri: redirectUri,
|
|
139
|
-
code_verifier: codeVerifier,
|
|
140
|
-
});
|
|
141
|
-
const res = await fetch(provider.tokenUrl, {
|
|
142
|
-
method: 'POST',
|
|
143
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
144
|
-
body: params.toString(),
|
|
145
|
-
});
|
|
146
|
-
if (!res.ok) {
|
|
147
|
-
const text = await res.text();
|
|
148
|
-
throw new Error(`Token exchange failed (${res.status}): ${text}`);
|
|
149
|
-
}
|
|
150
|
-
const data = await res.json();
|
|
151
|
-
// Decode email from id_token if available
|
|
152
|
-
let email;
|
|
153
|
-
if (data.id_token) {
|
|
154
|
-
try {
|
|
155
|
-
const payload = JSON.parse(Buffer.from(data.id_token.split('.')[1], 'base64').toString());
|
|
156
|
-
email = payload.email;
|
|
157
|
-
}
|
|
158
|
-
catch { }
|
|
159
|
-
}
|
|
160
|
-
return {
|
|
161
|
-
provider: 'opencode',
|
|
162
|
-
method: 'oauth',
|
|
163
|
-
accessToken: data.access_token,
|
|
164
|
-
refreshToken: data.refresh_token,
|
|
165
|
-
expiresAt: data.expires_in ? Date.now() + data.expires_in * 1000 : undefined,
|
|
166
|
-
email,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
function ask(rl, q) {
|
|
170
|
-
return new Promise((resolve) => rl.question(q, resolve));
|
|
171
|
-
}
|
|
172
|
-
export function authCommand(program) {
|
|
173
|
-
const auth = program.command('auth')
|
|
174
|
-
.description('Manage authentication');
|
|
175
|
-
auth
|
|
176
|
-
.command('login')
|
|
177
|
-
.description('Login to a provider (opens browser)')
|
|
178
|
-
.option('-p, --provider <name>', 'Provider name (opencode)')
|
|
179
|
-
.action(async (opts) => {
|
|
180
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
181
|
-
console.log(chalk.bold.cyan('\n Agent CLI — Login\n'));
|
|
182
|
-
let providerId = opts.provider;
|
|
183
|
-
if (!providerId) {
|
|
184
|
-
console.log(chalk.dim(' Available providers:'));
|
|
185
|
-
for (const [id, p] of Object.entries(OAUTH_PROVIDERS)) {
|
|
186
|
-
console.log(chalk.dim(` ${id} — ${p.name}`));
|
|
187
|
-
}
|
|
188
|
-
providerId = await ask(rl, '\n Provider: ');
|
|
189
|
-
}
|
|
190
|
-
providerId = providerId.trim();
|
|
191
|
-
try {
|
|
192
|
-
const entry = await oauthFlow(providerId);
|
|
193
|
-
const auth = await loadAuth();
|
|
194
|
-
// Remove existing entry for same provider
|
|
195
|
-
auth.entries = auth.entries.filter((e) => e.provider !== entry.provider);
|
|
196
|
-
auth.entries.push(entry);
|
|
197
|
-
auth.activeProvider = entry.provider;
|
|
198
|
-
await saveAuth(auth);
|
|
199
|
-
console.log(chalk.green(`\n ✓ Logged in as ${entry.email || 'user'} via ${entry.provider}`));
|
|
200
|
-
console.log(chalk.dim(' Auth saved to ~/.agent-cli/auth.json'));
|
|
201
|
-
}
|
|
202
|
-
catch (err) {
|
|
203
|
-
console.log(chalk.red(`\n ✗ Login failed: ${err.message}`));
|
|
204
|
-
}
|
|
205
|
-
rl.close();
|
|
206
|
-
});
|
|
207
|
-
auth
|
|
208
|
-
.command('logout')
|
|
209
|
-
.description('Logout from current provider')
|
|
210
|
-
.action(async () => {
|
|
211
|
-
const auth = await loadAuth();
|
|
212
|
-
if (!auth.activeProvider || auth.entries.length === 0) {
|
|
213
|
-
console.log(chalk.yellow(' Not logged in'));
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
auth.entries = auth.entries.filter((e) => e.provider !== auth.activeProvider);
|
|
217
|
-
auth.activeProvider = undefined;
|
|
218
|
-
await saveAuth(auth);
|
|
219
|
-
console.log(chalk.green(' ✓ Logged out'));
|
|
220
|
-
});
|
|
221
|
-
auth
|
|
222
|
-
.command('status')
|
|
223
|
-
.description('Show current auth status')
|
|
224
|
-
.action(async () => {
|
|
225
|
-
const auth = await loadAuth();
|
|
226
|
-
console.log(chalk.bold('\n Auth Status'));
|
|
227
|
-
console.log(chalk.dim(' ──────────────────────────'));
|
|
228
|
-
if (auth.entries.length === 0) {
|
|
229
|
-
console.log(chalk.yellow(' Not logged in to any provider'));
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
for (const entry of auth.entries) {
|
|
233
|
-
const active = entry.provider === auth.activeProvider ? chalk.green(' (active)') : '';
|
|
234
|
-
const email = entry.email ? ` — ${entry.email}` : '';
|
|
235
|
-
const expired = entry.expiresAt && entry.expiresAt < Date.now() ? chalk.red(' (expired)') : '';
|
|
236
|
-
console.log(` ${entry.provider} (${entry.method})${email}${active}${expired}`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
console.log('');
|
|
240
|
-
});
|
|
241
|
-
auth
|
|
242
|
-
.command('apikey')
|
|
243
|
-
.description('Set API key for a provider')
|
|
244
|
-
.argument('<provider>', 'Provider name')
|
|
245
|
-
.argument('<key>', 'API key')
|
|
246
|
-
.action(async (provider, key) => {
|
|
247
|
-
const auth = await loadAuth();
|
|
248
|
-
auth.entries = auth.entries.filter((e) => e.provider !== provider);
|
|
249
|
-
auth.entries.push({ provider, method: 'apikey', apiKey: key });
|
|
250
|
-
auth.activeProvider = provider;
|
|
251
|
-
await saveAuth(auth);
|
|
252
|
-
console.log(chalk.green(` ✓ API key saved for ${provider}`));
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
export { loadAuth, loadCliConfig, saveCliConfig, CONFIG_DIR, CONFIG_FILE };
|
package/dist/commands/config.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { loadCliConfig, saveCliConfig } from '../utils/config.js';
|
|
3
|
-
export function configCommand(program) {
|
|
4
|
-
program
|
|
5
|
-
.command('config')
|
|
6
|
-
.description('View or edit CLI configuration')
|
|
7
|
-
.option('--show', 'Show current configuration')
|
|
8
|
-
.option('--set <key=value>', 'Set a config value')
|
|
9
|
-
.action(async (opts) => {
|
|
10
|
-
const config = await loadCliConfig();
|
|
11
|
-
if (opts.show) {
|
|
12
|
-
console.log(chalk.bold('\n Agent CLI Configuration'));
|
|
13
|
-
console.log(chalk.dim(' ──────────────────────────'));
|
|
14
|
-
console.log(chalk.yellow(' Providers:'), Object.keys(config.apiKeys).join(', ') || 'none');
|
|
15
|
-
for (const [role, r] of Object.entries(config.roles)) {
|
|
16
|
-
if (r)
|
|
17
|
-
console.log(chalk.yellow(` ${role}:`), `${r.provider}/${r.model}`);
|
|
18
|
-
}
|
|
19
|
-
if (config.deliberation?.enabled) {
|
|
20
|
-
console.log(chalk.yellow(' Deliberation:'), `enabled (${config.deliberation.max_rounds} rounds)`);
|
|
21
|
-
}
|
|
22
|
-
console.log('');
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
if (opts.set) {
|
|
26
|
-
const [key, ...valParts] = opts.set.split('=');
|
|
27
|
-
const value = valParts.join('=');
|
|
28
|
-
const parts = key.split('.');
|
|
29
|
-
let obj = config;
|
|
30
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
31
|
-
if (!obj[parts[i]])
|
|
32
|
-
obj[parts[i]] = {};
|
|
33
|
-
obj = obj[parts[i]];
|
|
34
|
-
}
|
|
35
|
-
obj[parts[parts.length - 1]] = value;
|
|
36
|
-
await saveCliConfig(config);
|
|
37
|
-
console.log(chalk.green(` ✓ Set ${key} = ${value}`));
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
console.log('Use --show or --set key=value');
|
|
41
|
-
});
|
|
42
|
-
}
|
package/dist/core/prompts.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { TaskPlan } from '../types.js';
|
|
2
|
-
export declare function orchestratorSystem(): string;
|
|
3
|
-
export declare function orchestratorUser(taskId: string, task: string, context: string): string;
|
|
4
|
-
export declare function implementorSystem(approved: string[], forbidden: string[]): string;
|
|
5
|
-
export declare function implementorUser(taskId: string, plan: TaskPlan): string;
|
|
6
|
-
export declare function reviewerSystem(): string;
|
|
7
|
-
export declare function reviewerUser(taskId: string, plan: TaskPlan, progressSummary: string): string;
|
|
8
|
-
export declare function proposerSystem(): string;
|
|
9
|
-
export declare function proposerUser(task: string, question: string, prevRounds: string): string;
|
|
10
|
-
export declare function criticSystem(): string;
|
|
11
|
-
export declare function criticUser(task: string, question: string, proposal: string, prevCritiques: string): string;
|
package/dist/core/prompts.js
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
export function orchestratorSystem() {
|
|
2
|
-
return `Sos el ORCHESTRATOR de un sistema multi-agente. Tu UNICO trabajo es planificar.
|
|
3
|
-
|
|
4
|
-
REGLAS:
|
|
5
|
-
- NO ejecutar codigo
|
|
6
|
-
- NO crear archivos fuera de .agent/tasks/
|
|
7
|
-
- NO instalar dependencias
|
|
8
|
-
- NO modificar archivos existentes
|
|
9
|
-
- Solo planificar y escribir JSON
|
|
10
|
-
|
|
11
|
-
Tu output debe ser SOLO JSON valido, sin markdown, sin explicaciones.`;
|
|
12
|
-
}
|
|
13
|
-
export function orchestratorUser(taskId, task, context) {
|
|
14
|
-
return `TAREA: ${task}
|
|
15
|
-
ID: ${taskId}
|
|
16
|
-
|
|
17
|
-
CONTEXTO:
|
|
18
|
-
${context}
|
|
19
|
-
|
|
20
|
-
Responde SOLO con este JSON:
|
|
21
|
-
{
|
|
22
|
-
"plan": {
|
|
23
|
-
"task_id": "${taskId}",
|
|
24
|
-
"description": "${task}",
|
|
25
|
-
"steps": [
|
|
26
|
-
{"num": 1, "description": "paso detallado", "files": ["ruta/archivo"], "status": "pending"}
|
|
27
|
-
],
|
|
28
|
-
"acceptance_criteria": ["criterio verificable 1", "criterio verificable 2"],
|
|
29
|
-
"deliberation": {"needed": false, "question": ""}
|
|
30
|
-
},
|
|
31
|
-
"progress": {
|
|
32
|
-
"task_id": "${taskId}",
|
|
33
|
-
"created_at": "${new Date().toISOString()}",
|
|
34
|
-
"steps": [],
|
|
35
|
-
"status": "planned"
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
Cada step debe ser atomico. Los criterios deben ser verificables.`;
|
|
40
|
-
}
|
|
41
|
-
export function implementorSystem(approved, forbidden) {
|
|
42
|
-
return `Sos el IMPLEMENTOR. Tu UNICO trabajo es ejecutar el plan.
|
|
43
|
-
|
|
44
|
-
REGLAS:
|
|
45
|
-
- NO modificar plan.json
|
|
46
|
-
- NO crear archivos no contemplados en el plan
|
|
47
|
-
- Seguir el plan exactamente
|
|
48
|
-
- Actualizar progress.json con timestamps
|
|
49
|
-
|
|
50
|
-
APROBADOS: ${approved.join(', ')}
|
|
51
|
-
PROHIBIDOS: ${forbidden.join(', ')}
|
|
52
|
-
|
|
53
|
-
Tu output debe ser SOLO JSON valido con el progress actualizado.`;
|
|
54
|
-
}
|
|
55
|
-
export function implementorUser(taskId, plan) {
|
|
56
|
-
return `EJECUTA ESTE PLAN:
|
|
57
|
-
Task: ${taskId}
|
|
58
|
-
${JSON.stringify(plan, null, 2)}
|
|
59
|
-
|
|
60
|
-
Por cada step completado, cambia status a "completed" y agrega completed_at.
|
|
61
|
-
Al terminar todos los steps, status = "completed".
|
|
62
|
-
|
|
63
|
-
Responde SOLO con el JSON de progress actualizado.`;
|
|
64
|
-
}
|
|
65
|
-
export function reviewerSystem() {
|
|
66
|
-
return `Sos el REVIEWER. Tu UNICO trabajo es validar.
|
|
67
|
-
|
|
68
|
-
REGLAS:
|
|
69
|
-
- NO modificar codigo
|
|
70
|
-
- NO implementar correcciones
|
|
71
|
-
- Solo validar y reportar
|
|
72
|
-
|
|
73
|
-
Tu output debe ser SOLO JSON valido.`;
|
|
74
|
-
}
|
|
75
|
-
export function reviewerUser(taskId, plan, progressSummary) {
|
|
76
|
-
return `VALIDA ESTA TAREA:
|
|
77
|
-
Task: ${taskId}
|
|
78
|
-
|
|
79
|
-
PLAN:
|
|
80
|
-
${JSON.stringify({ description: plan.description, criteria: plan.acceptance_criteria }, null, 2)}
|
|
81
|
-
|
|
82
|
-
PROGRESO:
|
|
83
|
-
${progressSummary}
|
|
84
|
-
|
|
85
|
-
Responde SOLO con:
|
|
86
|
-
{
|
|
87
|
-
"result": {
|
|
88
|
-
"task_id": "${taskId}",
|
|
89
|
-
"criteria": [{"criterion": "...", "status": "PASS", "notes": "..."}],
|
|
90
|
-
"verdict": "PASS",
|
|
91
|
-
"explanation": "..."
|
|
92
|
-
},
|
|
93
|
-
"validation": {
|
|
94
|
-
"status": "PASS",
|
|
95
|
-
"reviewed_at": "${new Date().toISOString()}",
|
|
96
|
-
"reviewer": "reviewer"
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
Verdict = "PASS" si TODOS los criterios se cumplen, "FAIL" si alguno falla.`;
|
|
101
|
-
}
|
|
102
|
-
export function proposerSystem() {
|
|
103
|
-
return `Sos el PROPOSER en un debate tecnico. Generas propuestas con justificacion tecnica.
|
|
104
|
-
Responde SOLO con JSON valido.`;
|
|
105
|
-
}
|
|
106
|
-
export function proposerUser(task, question, prevRounds) {
|
|
107
|
-
return `TAREA: ${task}
|
|
108
|
-
PREGUNTA: ${question}
|
|
109
|
-
${prevRounds ? 'RONDAS ANTERIORES:\n' + prevRounds : 'Primera ronda.'}
|
|
110
|
-
|
|
111
|
-
Responde SOLO con:
|
|
112
|
-
{"proposal": "...", "justification": "...", "trade_offs": ["..."], "accept": false}`;
|
|
113
|
-
}
|
|
114
|
-
export function criticSystem() {
|
|
115
|
-
return `Sos el CRITIC en un debate tecnico. Buscas debilidades, edge cases y errores.
|
|
116
|
-
Responde SOLO con JSON valido.`;
|
|
117
|
-
}
|
|
118
|
-
export function criticUser(task, question, proposal, prevCritiques) {
|
|
119
|
-
return `TAREA: ${task}
|
|
120
|
-
PREGUNTA: ${question}
|
|
121
|
-
PROPUESTA: ${proposal}
|
|
122
|
-
${prevCritiques ? 'CRITICAS ANTERIORES:\n' + prevCritiques : ''}
|
|
123
|
-
|
|
124
|
-
Responde SOLO con:
|
|
125
|
-
{"analysis": "...", "weaknesses": ["..."], "edge_cases": ["..."], "suggestions": ["..."], "verdict": "ACCEPT"}`;
|
|
126
|
-
}
|
package/dist/providers/index.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import OpenAI from 'openai';
|
|
2
|
-
import { Anthropic } from '@anthropic-ai/sdk';
|
|
3
|
-
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
4
|
-
import { loadAuth } from '../commands/auth.js';
|
|
5
|
-
function detectProvider(model) {
|
|
6
|
-
const m = model.toLowerCase();
|
|
7
|
-
if (m.startsWith('gpt') || m.startsWith('o1') || m.startsWith('o3') || m.startsWith('o4'))
|
|
8
|
-
return 'openai';
|
|
9
|
-
if (m.startsWith('claude'))
|
|
10
|
-
return 'anthropic';
|
|
11
|
-
if (m.startsWith('gemini'))
|
|
12
|
-
return 'google';
|
|
13
|
-
if (m.includes('/'))
|
|
14
|
-
return 'opencode';
|
|
15
|
-
return 'openai';
|
|
16
|
-
}
|
|
17
|
-
async function getAuthToken(provider) {
|
|
18
|
-
// 1. Check env vars
|
|
19
|
-
const envKey = process.env[`${provider.toUpperCase()}_API_KEY`];
|
|
20
|
-
if (envKey)
|
|
21
|
-
return envKey;
|
|
22
|
-
// 2. Check OAuth auth store
|
|
23
|
-
try {
|
|
24
|
-
const auth = await loadAuth();
|
|
25
|
-
const entry = auth.entries.find((e) => e.provider === provider) ||
|
|
26
|
-
auth.entries.find((e) => e.provider === auth.activeProvider);
|
|
27
|
-
if (entry?.accessToken)
|
|
28
|
-
return entry.accessToken;
|
|
29
|
-
if (entry?.apiKey)
|
|
30
|
-
return entry.apiKey;
|
|
31
|
-
}
|
|
32
|
-
catch { }
|
|
33
|
-
return undefined;
|
|
34
|
-
}
|
|
35
|
-
async function buildClient(role) {
|
|
36
|
-
const provider = role.provider || detectProvider(role.model);
|
|
37
|
-
const token = await getAuthToken(provider);
|
|
38
|
-
switch (provider) {
|
|
39
|
-
case 'openai':
|
|
40
|
-
return { type: 'openai', client: new OpenAI({ apiKey: token }) };
|
|
41
|
-
case 'anthropic':
|
|
42
|
-
return { type: 'anthropic', client: new Anthropic({ apiKey: token }) };
|
|
43
|
-
case 'google':
|
|
44
|
-
return { type: 'google', client: new GoogleGenerativeAI(token || '') };
|
|
45
|
-
case 'opencode': {
|
|
46
|
-
// Opencode uses an OpenAI-compatible API
|
|
47
|
-
const baseURL = process.env.OPENCODE_BASE_URL || 'https://api.opencode.ai/v1';
|
|
48
|
-
return { type: 'opencode', client: new OpenAI({ apiKey: token || '', baseURL }) };
|
|
49
|
-
}
|
|
50
|
-
default:
|
|
51
|
-
throw new Error(`Unknown provider: ${provider}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
export async function chat(role, systemPrompt, userPrompt) {
|
|
55
|
-
const client = await buildClient(role);
|
|
56
|
-
switch (client.type) {
|
|
57
|
-
case 'openai':
|
|
58
|
-
case 'opencode':
|
|
59
|
-
return chatOpenai(client.client, role.model, systemPrompt, userPrompt);
|
|
60
|
-
case 'anthropic':
|
|
61
|
-
return chatAnthropic(client.client, role.model, systemPrompt, userPrompt);
|
|
62
|
-
case 'google':
|
|
63
|
-
return chatGoogle(client.client, role.model, systemPrompt, userPrompt);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
async function chatOpenai(client, model, system, user) {
|
|
67
|
-
const res = await client.chat.completions.create({
|
|
68
|
-
model,
|
|
69
|
-
messages: [
|
|
70
|
-
{ role: 'system', content: system },
|
|
71
|
-
{ role: 'user', content: user },
|
|
72
|
-
],
|
|
73
|
-
});
|
|
74
|
-
const content = res.choices[0]?.message?.content || '';
|
|
75
|
-
const usage = res.usage;
|
|
76
|
-
return {
|
|
77
|
-
content,
|
|
78
|
-
usage: {
|
|
79
|
-
input_tokens: usage?.prompt_tokens || 0,
|
|
80
|
-
output_tokens: usage?.completion_tokens || 0,
|
|
81
|
-
cache_read_input_tokens: 0,
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
async function chatAnthropic(client, model, system, user) {
|
|
86
|
-
const res = await client.messages.create({
|
|
87
|
-
model,
|
|
88
|
-
system,
|
|
89
|
-
max_tokens: 8192,
|
|
90
|
-
messages: [{ role: 'user', content: user }],
|
|
91
|
-
});
|
|
92
|
-
const content = res.content
|
|
93
|
-
.filter((b) => b.type === 'text')
|
|
94
|
-
.map((b) => b.text)
|
|
95
|
-
.join('\n');
|
|
96
|
-
return {
|
|
97
|
-
content,
|
|
98
|
-
usage: {
|
|
99
|
-
input_tokens: res.usage?.input_tokens || 0,
|
|
100
|
-
output_tokens: res.usage?.output_tokens || 0,
|
|
101
|
-
cache_read_input_tokens: res.usage?.cache_read_input_tokens || 0,
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
async function chatGoogle(client, model, system, user) {
|
|
106
|
-
const genModel = client.getGenerativeModel({ model });
|
|
107
|
-
const result = await genModel.generateContent({
|
|
108
|
-
contents: [{ role: 'user', parts: [{ text: `${system}\n\n---\n\n${user}` }] }],
|
|
109
|
-
});
|
|
110
|
-
const text = result.response.text();
|
|
111
|
-
const usageMetadata = result.response.usageMetadata;
|
|
112
|
-
return {
|
|
113
|
-
content: text,
|
|
114
|
-
usage: {
|
|
115
|
-
input_tokens: usageMetadata?.promptTokenCount || 0,
|
|
116
|
-
output_tokens: usageMetadata?.candidatesTokenCount || 0,
|
|
117
|
-
cache_read_input_tokens: 0,
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
}
|