agent-mp 0.5.8 → 0.5.10
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 +55 -0
- package/dist/core/engine.js +4 -0
- package/dist/index.js +34 -0
- package/dist/utils/qwen-auth.d.ts +15 -0
- package/dist/utils/qwen-auth.js +89 -1
- package/package.json +1 -1
package/dist/commands/repl.js
CHANGED
|
@@ -683,6 +683,57 @@ async function cmdAuthStatus(fi) {
|
|
|
683
683
|
}));
|
|
684
684
|
fi.println(renderSectionBox('Auth Status', rows));
|
|
685
685
|
}
|
|
686
|
+
async function cmdUsage(fi) {
|
|
687
|
+
const { qwenUsage } = await import('../utils/qwen-auth.js');
|
|
688
|
+
const { PKG_NAME, getConfigDir } = await import('../utils/config.js');
|
|
689
|
+
const { loadProjectConfig } = await import('../utils/config.js');
|
|
690
|
+
fi.println(chalk.bold.cyan('\n Uso / Quota\n'));
|
|
691
|
+
// Check each role CLI
|
|
692
|
+
const dir = await resolveProjectDir(process.cwd());
|
|
693
|
+
let config;
|
|
694
|
+
try {
|
|
695
|
+
config = await loadProjectConfig(dir);
|
|
696
|
+
}
|
|
697
|
+
catch {
|
|
698
|
+
config = null;
|
|
699
|
+
}
|
|
700
|
+
const checks = [];
|
|
701
|
+
// Check main PKG_NAME (this CLI's own credentials)
|
|
702
|
+
const mainResult = await qwenUsage();
|
|
703
|
+
const mainLabel = `${PKG_NAME} (${mainResult.token ? 'coder-model' : '?'})`;
|
|
704
|
+
if (mainResult.ok) {
|
|
705
|
+
const exp = mainResult.token ? new Date(mainResult.token.expiresAt).toLocaleTimeString() : '?';
|
|
706
|
+
checks.push({ key: mainLabel, value: chalk.green(`✓ quota OK — expires ${exp}`) });
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
checks.push({ key: mainLabel, value: chalk.red(`✗ ${mainResult.error}`) });
|
|
710
|
+
}
|
|
711
|
+
// Check each configured role CLI
|
|
712
|
+
if (config?.roles) {
|
|
713
|
+
for (const [roleName, roleCfg] of Object.entries(config.roles)) {
|
|
714
|
+
const r = roleCfg;
|
|
715
|
+
if (!r?.cli)
|
|
716
|
+
continue;
|
|
717
|
+
const homeDir = path.join(os.homedir(), '.' + r.cli);
|
|
718
|
+
const credsPath = path.join(homeDir, 'oauth_creds.json');
|
|
719
|
+
if (await fileExists(credsPath)) {
|
|
720
|
+
const result = await qwenUsage(credsPath);
|
|
721
|
+
const exp = result.token ? new Date(result.token.expiresAt).toLocaleTimeString() : '?';
|
|
722
|
+
if (result.ok) {
|
|
723
|
+
checks.push({ key: `${r.cli} (${roleName})`, value: chalk.green(`✓ quota OK — expires ${exp}`) });
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
checks.push({ key: `${r.cli} (${roleName})`, value: chalk.red(`✗ ${result.error}`) });
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
checks.push({ key: `${r.cli} (${roleName})`, value: chalk.yellow('not logged in') });
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
fi.println(renderSectionBox('Quota Status', checks));
|
|
735
|
+
fi.println('');
|
|
736
|
+
}
|
|
686
737
|
function cmdHelp(fi) {
|
|
687
738
|
const commands = [
|
|
688
739
|
{ key: '/setup', value: 'Full interactive setup wizard' },
|
|
@@ -703,6 +754,7 @@ function cmdHelp(fi) {
|
|
|
703
754
|
{ key: '/login', value: 'Login (Qwen OAuth or CLI auth)' },
|
|
704
755
|
{ key: '/logout', value: 'Logout and clear credentials' },
|
|
705
756
|
{ key: '/auth-status', value: 'Show authentication status' },
|
|
757
|
+
{ key: '/usage', value: 'Check quota status for all role CLIs' },
|
|
706
758
|
{ key: '/tasks', value: 'List all tasks' },
|
|
707
759
|
{ key: '/clear', value: 'Clear the screen' },
|
|
708
760
|
{ key: '/help', value: 'Show this help' },
|
|
@@ -1067,6 +1119,9 @@ export async function runRepl(resumeSession) {
|
|
|
1067
1119
|
case 'auth-status':
|
|
1068
1120
|
await cmdAuthStatus(fi);
|
|
1069
1121
|
break;
|
|
1122
|
+
case 'usage':
|
|
1123
|
+
await cmdUsage(fi);
|
|
1124
|
+
break;
|
|
1070
1125
|
case 'tasks':
|
|
1071
1126
|
await cmdTasks(fi);
|
|
1072
1127
|
break;
|
package/dist/core/engine.js
CHANGED
|
@@ -545,6 +545,10 @@ INSTRUCCIONES:
|
|
|
545
545
|
log.warn(`${cliName} session expired — using fallback`);
|
|
546
546
|
return null;
|
|
547
547
|
}
|
|
548
|
+
if (err.message?.startsWith('QWEN_QUOTA_EXCEEDED')) {
|
|
549
|
+
log.warn(`${cliName} quota exhausted — using fallback`);
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
548
552
|
log.warn(`${cliName} direct API call failed: ${err.message}`);
|
|
549
553
|
return null;
|
|
550
554
|
}
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,7 @@ program
|
|
|
38
38
|
.option('--model [cli]', 'Alias for --models')
|
|
39
39
|
.option('--reset-coordinator', 'Clear coordinator selection (re-pick on next run)')
|
|
40
40
|
.option('--reset-auth', 'Wipe all auth credentials and start fresh')
|
|
41
|
+
.option('--usage', 'Check quota status for all role CLIs')
|
|
41
42
|
.addHelpText('after', `
|
|
42
43
|
Setup subcommands:
|
|
43
44
|
agent-mp setup init Full interactive wizard (login + roles + project)
|
|
@@ -131,6 +132,22 @@ if (nativeRole) {
|
|
|
131
132
|
}
|
|
132
133
|
process.exit(0);
|
|
133
134
|
}
|
|
135
|
+
// --usage: check quota status with test API call
|
|
136
|
+
if (args.includes('--usage') || args.includes('usage')) {
|
|
137
|
+
const { qwenUsage, getTokenPath } = await import('./utils/qwen-auth.js');
|
|
138
|
+
const result = await qwenUsage();
|
|
139
|
+
const exp = result.token ? new Date(result.token.expiresAt).toLocaleString() : 'N/A';
|
|
140
|
+
if (result.ok) {
|
|
141
|
+
console.log(chalk.green(` ✓ ${PKG_NAME}: quota OK`));
|
|
142
|
+
console.log(chalk.dim(` Token expires: ${exp}`));
|
|
143
|
+
console.log(chalk.dim(` Model: ${result.model}`));
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
console.log(chalk.red(` ✗ ${PKG_NAME}: ${result.error}`));
|
|
147
|
+
console.log(chalk.dim(` Token expires: ${exp}`));
|
|
148
|
+
}
|
|
149
|
+
process.exit(result.ok ? 0 : 1);
|
|
150
|
+
}
|
|
134
151
|
// Parse --model flag if provided
|
|
135
152
|
const modelIdx = args.findIndex(a => a === '--model' || a === '-m');
|
|
136
153
|
let model;
|
|
@@ -150,6 +167,23 @@ if (nativeRole) {
|
|
|
150
167
|
}
|
|
151
168
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
152
169
|
program.action(async (task, options) => {
|
|
170
|
+
// --usage: check quota status
|
|
171
|
+
if (options.usage) {
|
|
172
|
+
const { qwenUsage } = await import('./utils/qwen-auth.js');
|
|
173
|
+
const { PKG_NAME } = await import('./utils/config.js');
|
|
174
|
+
const result = await qwenUsage();
|
|
175
|
+
const exp = result.token ? new Date(result.token.expiresAt).toLocaleString() : 'N/A';
|
|
176
|
+
if (result.ok) {
|
|
177
|
+
console.log(chalk.green(` ✓ ${PKG_NAME}: quota OK`));
|
|
178
|
+
console.log(chalk.dim(` Token expires: ${exp}`));
|
|
179
|
+
console.log(chalk.dim(` Model: ${result.model}`));
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
console.log(chalk.red(` ✗ ${PKG_NAME}: ${result.error}`));
|
|
183
|
+
console.log(chalk.dim(` Token expires: ${exp}`));
|
|
184
|
+
}
|
|
185
|
+
process.exit(result.ok ? 0 : 1);
|
|
186
|
+
}
|
|
153
187
|
if (options.resetCoordinator || options.resetAuth) {
|
|
154
188
|
const authFile = path.join(AGENT_HOME, 'auth.json');
|
|
155
189
|
const qwenCreds = path.join(AGENT_HOME, 'oauth_creds.json');
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
export declare const QWEN_AGENT_HOME: string;
|
|
3
3
|
/** Dynamic getter (recommended for runtime changes) */
|
|
4
4
|
export declare function getQwenHome(): string;
|
|
5
|
+
interface QwenToken {
|
|
6
|
+
accessToken: string;
|
|
7
|
+
refreshToken: string;
|
|
8
|
+
expiresAt: number;
|
|
9
|
+
idToken?: string;
|
|
10
|
+
resourceUrl?: string;
|
|
11
|
+
}
|
|
5
12
|
export declare function getTokenPath(): Promise<string>;
|
|
6
13
|
export declare function qwenLogin(): Promise<boolean>;
|
|
7
14
|
export declare function qwenAuthStatus(): Promise<{
|
|
@@ -20,3 +27,11 @@ export declare function callQwenAPI(prompt: string, model?: string, onData?: (ch
|
|
|
20
27
|
* Calls the Qwen REST API directly — no dependency on any qwen CLI binary.
|
|
21
28
|
*/
|
|
22
29
|
export declare function callQwenAPIFromCreds(prompt: string, model: string, credsPath: string, onData?: (chunk: string) => void): Promise<string>;
|
|
30
|
+
/** Check quota status by making a minimal test API call */
|
|
31
|
+
export declare function qwenUsage(credsPath?: string): Promise<{
|
|
32
|
+
ok: boolean;
|
|
33
|
+
token: QwenToken | null;
|
|
34
|
+
error?: string;
|
|
35
|
+
model?: string;
|
|
36
|
+
}>;
|
|
37
|
+
export {};
|
package/dist/utils/qwen-auth.js
CHANGED
|
@@ -286,9 +286,14 @@ async function callQwenAPIWithToken(token, prompt, model, onData) {
|
|
|
286
286
|
});
|
|
287
287
|
if (!response.ok) {
|
|
288
288
|
const errorText = await response.text();
|
|
289
|
-
|
|
289
|
+
// 401 = auth error, refresh may help
|
|
290
|
+
if (response.status === 401) {
|
|
290
291
|
throw new Error(`QWEN_AUTH_EXPIRED: ${errorText}`);
|
|
291
292
|
}
|
|
293
|
+
// 429 insufficient_quota = daily quota exhausted, refresh won't help
|
|
294
|
+
if (response.status === 429 && errorText.includes('insufficient_quota')) {
|
|
295
|
+
throw new Error(`QWEN_QUOTA_EXCEEDED: ${errorText}`);
|
|
296
|
+
}
|
|
292
297
|
throw new Error(`Qwen API error: ${response.status} - ${errorText}`);
|
|
293
298
|
}
|
|
294
299
|
if (!useStream) {
|
|
@@ -342,6 +347,9 @@ export async function callQwenAPI(prompt, model = 'coder-model', onData) {
|
|
|
342
347
|
return await callQwenAPIWithToken(token, prompt, model, onData);
|
|
343
348
|
}
|
|
344
349
|
catch (err) {
|
|
350
|
+
// Quota errors: refresh won't help, propagate immediately
|
|
351
|
+
if (err.message?.startsWith('QWEN_QUOTA_EXCEEDED'))
|
|
352
|
+
throw err;
|
|
345
353
|
if (!err.message?.startsWith('QWEN_AUTH_EXPIRED'))
|
|
346
354
|
throw err;
|
|
347
355
|
if (token.refreshToken) {
|
|
@@ -390,6 +398,9 @@ export async function callQwenAPIFromCreds(prompt, model, credsPath, onData) {
|
|
|
390
398
|
return await callQwenAPIWithToken(token, prompt, model, onData);
|
|
391
399
|
}
|
|
392
400
|
catch (err) {
|
|
401
|
+
// Quota errors: refresh won't help, propagate immediately
|
|
402
|
+
if (err.message?.startsWith('QWEN_QUOTA_EXCEEDED'))
|
|
403
|
+
throw err;
|
|
393
404
|
if (!err.message?.startsWith('QWEN_AUTH_EXPIRED'))
|
|
394
405
|
throw err;
|
|
395
406
|
if (token.refreshToken) {
|
|
@@ -402,3 +413,80 @@ export async function callQwenAPIFromCreds(prompt, model, credsPath, onData) {
|
|
|
402
413
|
throw new Error(`QWEN_AUTH_EXPIRED: Sesión expirada. Ejecutá: ${cliName} --login`);
|
|
403
414
|
}
|
|
404
415
|
}
|
|
416
|
+
/** Check quota status by making a minimal test API call */
|
|
417
|
+
export async function qwenUsage(credsPath) {
|
|
418
|
+
let token;
|
|
419
|
+
if (credsPath) {
|
|
420
|
+
try {
|
|
421
|
+
const raw = JSON.parse(await fs.readFile(credsPath, 'utf-8'));
|
|
422
|
+
token = {
|
|
423
|
+
accessToken: raw.accessToken || raw.access_token || '',
|
|
424
|
+
refreshToken: raw.refreshToken || raw.refresh_token || '',
|
|
425
|
+
expiresAt: raw.expiresAt || raw.expiry_date || 0,
|
|
426
|
+
idToken: raw.idToken || raw.id_token,
|
|
427
|
+
resourceUrl: raw.resourceUrl || raw.resource_url,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
return { ok: false, token: null, error: 'No credentials found' };
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
token = await loadToken();
|
|
436
|
+
}
|
|
437
|
+
if (!token || !token.accessToken) {
|
|
438
|
+
return { ok: false, token, error: 'No access token — run --login' };
|
|
439
|
+
}
|
|
440
|
+
if (token.expiresAt < Date.now()) {
|
|
441
|
+
// Try refresh
|
|
442
|
+
if (token.refreshToken) {
|
|
443
|
+
const refreshed = await doRefreshToken(token.refreshToken);
|
|
444
|
+
if (refreshed) {
|
|
445
|
+
token = refreshed;
|
|
446
|
+
if (!credsPath)
|
|
447
|
+
await saveToken(refreshed);
|
|
448
|
+
else
|
|
449
|
+
await fs.writeFile(credsPath, JSON.stringify(refreshed, null, 2), 'utf-8');
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
return { ok: false, token, error: 'Token expired and refresh failed' };
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
return { ok: false, token, error: 'Token expired, no refresh token' };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// Make a minimal test call to check quota
|
|
460
|
+
try {
|
|
461
|
+
const result = await callQwenAPIWithToken(token, 'ping', 'coder-model');
|
|
462
|
+
return { ok: true, token, model: 'coder-model' };
|
|
463
|
+
}
|
|
464
|
+
catch (err) {
|
|
465
|
+
// If auth failed, try refresh even if expiresAt hasn't passed yet
|
|
466
|
+
if (err.message?.startsWith('QWEN_AUTH_EXPIRED') && token.refreshToken) {
|
|
467
|
+
const refreshed = await doRefreshToken(token.refreshToken);
|
|
468
|
+
if (refreshed) {
|
|
469
|
+
token = refreshed;
|
|
470
|
+
if (!credsPath)
|
|
471
|
+
await saveToken(refreshed);
|
|
472
|
+
else
|
|
473
|
+
await fs.writeFile(credsPath, JSON.stringify(refreshed, null, 2), 'utf-8');
|
|
474
|
+
// Retry with new token
|
|
475
|
+
try {
|
|
476
|
+
await callQwenAPIWithToken(refreshed, 'ping', 'coder-model');
|
|
477
|
+
return { ok: true, token: refreshed, model: 'coder-model' };
|
|
478
|
+
}
|
|
479
|
+
catch (err2) {
|
|
480
|
+
if (err2.message?.startsWith('QWEN_QUOTA_EXCEEDED')) {
|
|
481
|
+
return { ok: false, token: refreshed, error: 'Daily quota exhausted — wait or --login with different account' };
|
|
482
|
+
}
|
|
483
|
+
return { ok: false, token: refreshed, error: err2.message };
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (err.message?.startsWith('QWEN_QUOTA_EXCEEDED')) {
|
|
488
|
+
return { ok: false, token, error: 'Daily quota exhausted — wait or --login with different account' };
|
|
489
|
+
}
|
|
490
|
+
return { ok: false, token, error: err.message };
|
|
491
|
+
}
|
|
492
|
+
}
|