agent-mp 0.5.7 → 0.5.9
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 +106 -81
- package/dist/index.js +34 -0
- package/dist/utils/qwen-auth.d.ts +15 -0
- package/dist/utils/qwen-auth.js +67 -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
|
@@ -985,27 +985,32 @@ INSTRUCCIONES:
|
|
|
985
985
|
let fsSnapshot = '';
|
|
986
986
|
try {
|
|
987
987
|
const { execSync } = await import('child_process');
|
|
988
|
-
// Use find but exclude .agent, node_modules, .git, dist, __pycache__, .venv, target, build
|
|
989
988
|
fsSnapshot = execSync(`find "${this.projectDir}" -maxdepth 3 -not -path '*/.agent/*' -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' -not -path '*/__pycache__/*' -not -path '*/.venv/*' -not -path '*/target/*' -not -path '*/build/*' -not -name '.git' | sort | head -300`, { encoding: 'utf-8', timeout: 10000 });
|
|
990
989
|
}
|
|
991
990
|
catch { /* ignore */ }
|
|
992
|
-
// Read existing architecture
|
|
993
|
-
const
|
|
994
|
-
let
|
|
991
|
+
// Read existing main architecture doc
|
|
992
|
+
const mainArchPath = path.join(contextDir, 'architecture.md');
|
|
993
|
+
let existingMainArch = '';
|
|
995
994
|
try {
|
|
996
|
-
|
|
995
|
+
existingMainArch = await readFile(mainArchPath);
|
|
997
996
|
}
|
|
998
|
-
catch { /* new
|
|
999
|
-
// Read existing
|
|
1000
|
-
|
|
1001
|
-
let existingModules = '';
|
|
997
|
+
catch { /* new */ }
|
|
998
|
+
// Read existing per-component docs
|
|
999
|
+
let existingComponentDocs = '';
|
|
1002
1000
|
try {
|
|
1003
|
-
const
|
|
1004
|
-
for (const
|
|
1005
|
-
|
|
1001
|
+
const ctxEntries = await fs.readdir(contextDir);
|
|
1002
|
+
for (const entry of ctxEntries.filter(e => e !== 'architecture.md' && e !== 'explorer-last.md' && e !== 'modules')) {
|
|
1003
|
+
const entryPath = path.join(contextDir, entry);
|
|
1004
|
+
const stat = await fs.stat(entryPath);
|
|
1005
|
+
if (stat.isDirectory()) {
|
|
1006
|
+
const compArch = path.join(entryPath, 'architecture.md');
|
|
1007
|
+
if (await fileExists(compArch)) {
|
|
1008
|
+
existingComponentDocs += `\n=== EXISTING: ${entry}/architecture.md ===\n${(await readFile(compArch)).slice(0, 1500)}\n`;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1006
1011
|
}
|
|
1007
1012
|
}
|
|
1008
|
-
catch { /*
|
|
1013
|
+
catch { /* ignore */ }
|
|
1009
1014
|
const effectiveTask = task || 'Explorar y documentar todas las aplicaciones y servicios del proyecto';
|
|
1010
1015
|
const prompt = `TAREA DE EXPLORACION: ${effectiveTask}
|
|
1011
1016
|
DIRECTORIO_TRABAJO: ${this.projectDir}
|
|
@@ -1015,114 +1020,124 @@ STACK: ${this.config.stack}
|
|
|
1015
1020
|
ESTRUCTURA DE ARCHIVOS DEL PROYECTO (excluyendo .agent, node_modules, .git, dist, __pycache__, .venv, target, build):
|
|
1016
1021
|
${fsSnapshot}
|
|
1017
1022
|
|
|
1018
|
-
${
|
|
1019
|
-
${
|
|
1023
|
+
${existingMainArch ? `=== DOCUMENTACION EXISTENTE: architecture.md (principal) ===\n${existingMainArch.slice(0, 2000)}\n` : ''}
|
|
1024
|
+
${existingComponentDocs ? `=== DOCUMENTACION EXISTENTE por componente ===\n${existingComponentDocs.slice(0, 3000)}\n` : ''}
|
|
1020
1025
|
|
|
1021
1026
|
INSTRUCCIONES:
|
|
1022
|
-
Analiza la estructura de archivos y genera documentación
|
|
1023
|
-
|
|
1027
|
+
Analiza la estructura de archivos y genera documentación ESCALONADA en DOS NIVELES:
|
|
1028
|
+
|
|
1029
|
+
NIVEL 1: architecture.md (raiz de .agent/context/)
|
|
1030
|
+
- Lista TODOS los directorios al nivel del proyecto
|
|
1031
|
+
- Indica si son independientes o conectados entre si (ej: front consume back, dos APIs se consumen entre si)
|
|
1032
|
+
- Prerequisitos generales para levantar el proyecto
|
|
1033
|
+
- Comandos globales de desarrollo
|
|
1024
1034
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1035
|
+
NIVEL 2: Para CADA directorio que sea un servicio/API/app/funcionalidad:
|
|
1036
|
+
- Crea subcarpeta: [nombre-del-directorio]/architecture.md → explica como se estructura internamente
|
|
1037
|
+
- Crea subcarpeta: [nombre-del-directorio]/modules/[modulo].md → detalle de cada modulo interno
|
|
1038
|
+
- Si un directorio es trivial (solo scripts, docs, configs), NO crees subcarpeta, solo mencionarlo en el architecture.md principal
|
|
1027
1039
|
|
|
1028
|
-
|
|
1040
|
+
FORMATO EXACTO de los archivos a devolver (separados por marcadores):
|
|
1029
1041
|
|
|
1030
|
-
===
|
|
1031
|
-
# ${this.config.project} — Architecture
|
|
1042
|
+
=== architecture.md ===
|
|
1043
|
+
# ${this.config.project} — Architecture Overview
|
|
1032
1044
|
|
|
1033
|
-
|
|
1045
|
+
Descripcion general del proyecto.
|
|
1034
1046
|
|
|
1035
|
-
##
|
|
1047
|
+
## Componentes del Proyecto
|
|
1036
1048
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1049
|
+
| Componente | Tipo | Stack | Proposito | Entry Point |
|
|
1050
|
+
|------------|------|-------|-----------|-------------|
|
|
1051
|
+
| datamart-data-access-api | API Backend | NestJS | Acceso a datos | src/main.ts |
|
|
1052
|
+
| nexus-core-api | API Backend | NestJS | Core business logic | src/main.ts |
|
|
1039
1053
|
|
|
1040
|
-
## Como se
|
|
1054
|
+
## Como se Relacionan los Componentes
|
|
1041
1055
|
|
|
1042
|
-
Explica
|
|
1043
|
-
-
|
|
1044
|
-
-
|
|
1045
|
-
-
|
|
1046
|
-
|
|
1056
|
+
Explica las conexiones entre componentes:
|
|
1057
|
+
- Si un front consume un back, explicar como
|
|
1058
|
+
- Si dos APIs se consumen entre sí (fetch, HTTP), explicar el flujo
|
|
1059
|
+
- Si son independientes, indicar que cada uno funciona por su cuenta
|
|
1060
|
+
|
|
1061
|
+
### Diagrama de Flujo (texto)
|
|
1062
|
+
\`\`\`
|
|
1063
|
+
[Frontend] --fetch--> [API 1] --HTTP--> [API 2]
|
|
1064
|
+
[API 1] --> [Database]
|
|
1065
|
+
\`\`\`
|
|
1047
1066
|
|
|
1048
1067
|
## Prerequisitos Generales
|
|
1049
|
-
-
|
|
1050
|
-
-
|
|
1051
|
-
- Servicios externos necesarios (BD, cache, etc.)
|
|
1068
|
+
- Node.js x.x, Python x.x, Docker, etc.
|
|
1069
|
+
- Bases de datos, servicios externos
|
|
1052
1070
|
|
|
1053
1071
|
## Comandos de Desarrollo Globales
|
|
1054
|
-
|
|
1055
1072
|
| Comando | Descripcion | Directorio |
|
|
1056
1073
|
|---------|-------------|------------|
|
|
1057
1074
|
| ... | ... | ... |
|
|
1058
1075
|
|
|
1059
|
-
===
|
|
1060
|
-
# [nombre] —
|
|
1076
|
+
=== [nombre]/architecture.md ===
|
|
1077
|
+
# [nombre] — Arquitectura Interna
|
|
1061
1078
|
|
|
1062
|
-
Descripcion
|
|
1079
|
+
Descripcion de este componente y su rol en el proyecto.
|
|
1063
1080
|
|
|
1064
|
-
## Estructura
|
|
1081
|
+
## Estructura Interna
|
|
1065
1082
|
\`\`\`
|
|
1066
1083
|
src/
|
|
1067
|
-
├── main.
|
|
1068
|
-
├──
|
|
1069
|
-
|
|
1084
|
+
├── main.ts # entry point
|
|
1085
|
+
├── config/ # configuracion
|
|
1086
|
+
├── controllers/ # endpoints
|
|
1087
|
+
└── services/ # logica de negocio
|
|
1070
1088
|
\`\`\`
|
|
1071
1089
|
|
|
1072
1090
|
## Modulos Internos
|
|
1073
1091
|
| Modulo | Archivo | Descripcion |
|
|
1074
1092
|
|--------|---------|-------------|
|
|
1075
|
-
|
|
|
1076
|
-
|
|
|
1093
|
+
| config | modules/config.md | Configuracion |
|
|
1094
|
+
| controller | modules/controller.md | Endpoints |
|
|
1077
1095
|
|
|
1078
1096
|
## Como Levantar
|
|
1079
|
-
- **
|
|
1097
|
+
- **Instalar deps:** comando
|
|
1080
1098
|
- **Servicios necesarios:** BD, redis, etc.
|
|
1081
|
-
- **Variables de entorno:**
|
|
1082
|
-
- **Comando para iniciar:**
|
|
1099
|
+
- **Variables de entorno:** listarlas con su proposito
|
|
1100
|
+
- **Comando para iniciar:** \`...\`
|
|
1083
1101
|
|
|
1084
1102
|
## Comandos Disponibles
|
|
1085
|
-
|
|
1086
1103
|
| Comando | Que hace |
|
|
1087
1104
|
|---------|----------|
|
|
1088
1105
|
| ... | ... |
|
|
1089
1106
|
|
|
1090
|
-
|
|
1107
|
+
## Comunicacion con Otros Componentes
|
|
1108
|
+
- A que APIs llama o que APIs lo llaman
|
|
1109
|
+
- Protocolo (fetch, HTTP, gRPC, etc.)
|
|
1110
|
+
|
|
1111
|
+
=== [nombre]/modules/[modulo].md ===
|
|
1091
1112
|
# [nombre-del-modulo]
|
|
1092
1113
|
|
|
1093
1114
|
## Funcion
|
|
1094
|
-
Que hace este modulo
|
|
1115
|
+
Que hace este modulo especifico.
|
|
1095
1116
|
|
|
1096
1117
|
## Archivo Principal
|
|
1097
|
-
- **Ruta:** path relativo
|
|
1098
|
-
- **Funciones/Clases principales
|
|
1118
|
+
- **Ruta:** path relativo
|
|
1119
|
+
- **Funciones/Clases principales**
|
|
1099
1120
|
|
|
1100
1121
|
## Endpoints / Interfaces Publicas
|
|
1101
|
-
Si es un
|
|
1102
|
-
- \`GET /
|
|
1103
|
-
- \`POST /
|
|
1104
|
-
|
|
1105
|
-
Si es otro tipo de modulo:
|
|
1106
|
-
- Funciones publicas exportadas
|
|
1107
|
-
- Interfaces que implementa
|
|
1122
|
+
Si es un controller/router:
|
|
1123
|
+
- \`GET /ruta\` — descripcion
|
|
1124
|
+
- \`POST /ruta\` — descripcion
|
|
1108
1125
|
|
|
1109
1126
|
## Dependencias
|
|
1110
|
-
- **Internas:**
|
|
1111
|
-
- **Externas:** librerias de terceros
|
|
1112
|
-
|
|
1113
|
-
## Notas
|
|
1114
|
-
Cualquier detalle relevante especifico de este modulo.
|
|
1127
|
+
- **Internas:** modulos del mismo componente
|
|
1128
|
+
- **Externas:** librerias de terceros
|
|
1115
1129
|
|
|
1116
1130
|
REGLAS:
|
|
1117
|
-
- Devuelve UNICAMENTE los archivos separados por ===
|
|
1118
|
-
- NO incluyas
|
|
1119
|
-
-
|
|
1120
|
-
- Para
|
|
1131
|
+
- Devuelve UNICAMENTE los archivos separados por === path/file.md ===
|
|
1132
|
+
- NO incluyas texto explicativo fuera de los archivos
|
|
1133
|
+
- El archivo principal SIEMPRE es === architecture.md ===
|
|
1134
|
+
- Para cada componente con modulos internos: === [nombre]/architecture.md === + === [nombre]/modules/[modulo].md ===
|
|
1135
|
+
- Para componentes triviales (scripts, docs): solo mencionarlos en architecture.md principal
|
|
1121
1136
|
- Documenta TODOS los directorios al nivel de DIRECTORIO_TRABAJO (excluyendo .agent)
|
|
1122
1137
|
- Si hay documentacion existente, actualizala con los cambios detectados`;
|
|
1123
1138
|
const res = await this.runWithFallback('explorer', prompt, 'Exploracion');
|
|
1124
1139
|
const text = extractCliText(res);
|
|
1125
|
-
// Parse
|
|
1140
|
+
// Parse sections separated by === path/file.md === markers
|
|
1126
1141
|
const sections = text.split(/===\s+(.+?\.md)\s*===/).slice(1);
|
|
1127
1142
|
let filesWritten = 0;
|
|
1128
1143
|
for (let i = 0; i < sections.length; i += 2) {
|
|
@@ -1133,20 +1148,30 @@ REGLAS:
|
|
|
1133
1148
|
// Clean up code fences if present
|
|
1134
1149
|
content = content.replace(/^```markdown\s*/i, '').replace(/^```\s*$/gm, '').trim();
|
|
1135
1150
|
try {
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1151
|
+
let targetPath;
|
|
1152
|
+
if (fileName === 'architecture.md' || fileName === 'ARCHITECTURE.md') {
|
|
1153
|
+
// Main architecture doc
|
|
1154
|
+
targetPath = mainArchPath;
|
|
1139
1155
|
}
|
|
1140
|
-
else
|
|
1141
|
-
//
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1156
|
+
else {
|
|
1157
|
+
// Component doc: [component]/architecture.md or [component]/modules/[mod].md
|
|
1158
|
+
// fileName could be: "datamart-data-access-api/architecture.md" or "datamart-data-access-api/modules/config.md"
|
|
1159
|
+
const compMatch = fileName.match(/^(.+?)\/architecture\.md$/i);
|
|
1160
|
+
const modMatch = fileName.match(/^(.+?)\/modules\/(.+\.md)$/i);
|
|
1161
|
+
if (compMatch) {
|
|
1162
|
+
targetPath = path.join(contextDir, compMatch[1], 'architecture.md');
|
|
1163
|
+
}
|
|
1164
|
+
else if (modMatch) {
|
|
1165
|
+
targetPath = path.join(contextDir, modMatch[1], 'modules', modMatch[2]);
|
|
1166
|
+
}
|
|
1167
|
+
else {
|
|
1168
|
+
// Fallback: treat as modules file in legacy modules/ dir
|
|
1169
|
+
targetPath = path.join(contextDir, 'modules', fileName.replace(/.*\//, ''));
|
|
1148
1170
|
}
|
|
1149
1171
|
}
|
|
1172
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
1173
|
+
await writeFile(targetPath, content);
|
|
1174
|
+
filesWritten++;
|
|
1150
1175
|
}
|
|
1151
1176
|
catch (err) {
|
|
1152
1177
|
log.warn(`Failed to write ${fileName}: ${err.message}`);
|
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,58 @@ 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 (err.message?.startsWith('QWEN_QUOTA_EXCEEDED')) {
|
|
466
|
+
return { ok: false, token, error: 'Daily quota exhausted — wait or --login with different account' };
|
|
467
|
+
}
|
|
468
|
+
return { ok: false, token, error: err.message };
|
|
469
|
+
}
|
|
470
|
+
}
|