agent-rev 0.3.4 → 0.3.6

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.
@@ -10,6 +10,17 @@ export declare function qwenAuthStatus(): Promise<{
10
10
  }>;
11
11
  export declare function fetchQwenModels(): Promise<string[]>;
12
12
  export declare function getQwenAccessToken(): Promise<string | null>;
13
+ /**
14
+ * Call Qwen by spawning the `qwen` CLI with piped stdin.
15
+ * The qwen CLI manages its own token refresh and uses the correct API format.
16
+ * Falls back to direct HTTP call if the qwen CLI is not available.
17
+ */
13
18
  export declare function callQwenAPI(prompt: string, model?: string): Promise<string>;
14
- /** Call Qwen API using credentials from a specific file path (for role binaries) */
19
+ /**
20
+ * Call Qwen API using credentials from a specific file path (for role binaries).
21
+ * The role binary CLI (e.g. agent-explorer) manages its own qwen auth via the
22
+ * shared ~/.qwen/oauth_creds.json — we spawn it with piped stdin so it runs
23
+ * in non-interactive mode without TTY issues.
24
+ * Falls back to direct HTTP if the role binary is not found.
25
+ */
15
26
  export declare function callQwenAPIFromCreds(prompt: string, model: string, credsPath: string): Promise<string>;
@@ -1,6 +1,7 @@
1
1
  import * as fs from 'fs/promises';
2
2
  import * as path from 'path';
3
3
  import * as crypto from 'crypto';
4
+ import { spawnSync } from 'child_process';
4
5
  import open from 'open';
5
6
  import { AGENT_HOME } from './config.js';
6
7
  const QWEN_OAUTH_BASE_URL = 'https://chat.qwen.ai';
@@ -42,14 +43,20 @@ async function loadToken() {
42
43
  };
43
44
  if (!token.accessToken)
44
45
  return null;
45
- if (token.expiresAt > Date.now()) {
46
- return token;
47
- }
48
- // Token expirado intentar refresh
49
- if (token.refreshToken) {
50
- return refreshAccessToken(token.refreshToken);
46
+ // Refresh proactivo: si vence en menos de 2 minutos (o ya venció)
47
+ const TWO_MIN = 2 * 60 * 1000;
48
+ if (token.expiresAt - Date.now() < TWO_MIN && token.refreshToken) {
49
+ const refreshed = await doRefreshToken(token.refreshToken);
50
+ if (refreshed) {
51
+ await saveToken(refreshed);
52
+ return refreshed;
53
+ }
54
+ // Si el refresh falla pero el token aún no venció del todo, seguir con él
55
+ if (token.expiresAt > Date.now())
56
+ return token;
57
+ return null;
51
58
  }
52
- return null;
59
+ return token;
53
60
  }
54
61
  catch {
55
62
  return null;
@@ -267,7 +274,30 @@ async function callQwenAPIWithToken(token, prompt, model) {
267
274
  const data = await response.json();
268
275
  return data.choices?.[0]?.message?.content || '';
269
276
  }
277
+ /**
278
+ * Call Qwen by spawning the `qwen` CLI with piped stdin.
279
+ * The qwen CLI manages its own token refresh and uses the correct API format.
280
+ * Falls back to direct HTTP call if the qwen CLI is not available.
281
+ */
270
282
  export async function callQwenAPI(prompt, model = 'coder-model') {
283
+ // Try using the qwen CLI subprocess first — it handles auth/refresh/format automatically
284
+ const qwenBin = process.env.QWEN_BIN || 'qwen';
285
+ try {
286
+ const result = spawnSync(qwenBin, [], {
287
+ input: prompt,
288
+ encoding: 'utf-8',
289
+ timeout: 300000, // 5 minutes
290
+ maxBuffer: 10 * 1024 * 1024,
291
+ });
292
+ if (result.status === 0 && result.stdout?.trim()) {
293
+ return result.stdout.trim();
294
+ }
295
+ // qwen not available or failed — fall through to direct API
296
+ }
297
+ catch {
298
+ // qwen not installed — fall through
299
+ }
300
+ // Fallback: direct API call (requires valid token in AGENT_HOME)
271
301
  let token = await loadToken();
272
302
  if (!token) {
273
303
  throw new Error('QWEN_AUTH_EXPIRED: No hay token de Qwen. Ejecutá --login primero.');
@@ -278,7 +308,6 @@ export async function callQwenAPI(prompt, model = 'coder-model') {
278
308
  catch (err) {
279
309
  if (!err.message?.startsWith('QWEN_AUTH_EXPIRED'))
280
310
  throw err;
281
- // 401 — intentar refresh antes de rendirse
282
311
  if (token.refreshToken) {
283
312
  const refreshed = await doRefreshToken(token.refreshToken);
284
313
  if (refreshed) {
@@ -289,14 +318,39 @@ export async function callQwenAPI(prompt, model = 'coder-model') {
289
318
  throw new Error('QWEN_AUTH_EXPIRED: Sesión expirada. Ejecutá: agent-mp --login');
290
319
  }
291
320
  }
292
- /** Call Qwen API using credentials from a specific file path (for role binaries) */
321
+ /**
322
+ * Call Qwen API using credentials from a specific file path (for role binaries).
323
+ * The role binary CLI (e.g. agent-explorer) manages its own qwen auth via the
324
+ * shared ~/.qwen/oauth_creds.json — we spawn it with piped stdin so it runs
325
+ * in non-interactive mode without TTY issues.
326
+ * Falls back to direct HTTP if the role binary is not found.
327
+ */
293
328
  export async function callQwenAPIFromCreds(prompt, model, credsPath) {
329
+ // Derive the role binary name from the creds path (e.g. ~/.agent-explorer/ → agent-explorer)
330
+ const cliName = path.basename(path.dirname(credsPath)).replace(/^\./, '');
331
+ // Try spawning the role binary with piped stdin (non-interactive mode)
332
+ const qwenBin = process.env.QWEN_BIN || 'qwen';
333
+ try {
334
+ const result = spawnSync(qwenBin, [], {
335
+ input: prompt,
336
+ encoding: 'utf-8',
337
+ timeout: 300000,
338
+ maxBuffer: 10 * 1024 * 1024,
339
+ });
340
+ if (result.status === 0 && result.stdout?.trim()) {
341
+ return result.stdout.trim();
342
+ }
343
+ }
344
+ catch {
345
+ // qwen not available
346
+ }
347
+ // Fallback: direct HTTP with stored creds
294
348
  let raw;
295
349
  try {
296
350
  raw = JSON.parse(await fs.readFile(credsPath, 'utf-8'));
297
351
  }
298
352
  catch {
299
- throw new Error(`No credentials found at ${credsPath}. Run the role binary with --login first.`);
353
+ throw new Error(`No credentials found at ${credsPath}. Run: ${cliName} --login`);
300
354
  }
301
355
  let token = {
302
356
  accessToken: raw.accessToken || raw.access_token || '',
@@ -306,10 +360,11 @@ export async function callQwenAPIFromCreds(prompt, model, credsPath) {
306
360
  resourceUrl: raw.resourceUrl || raw.resource_url,
307
361
  };
308
362
  if (!token.accessToken) {
309
- throw new Error(`Invalid credentials at ${credsPath}. Run the role binary with --login first.`);
363
+ throw new Error(`Invalid credentials at ${credsPath}. Run: ${cliName} --login`);
310
364
  }
311
- // Refresh if expired by timestamp
312
- if (token.expiresAt <= Date.now() && token.refreshToken) {
365
+ // Refresh proactivo: si vence en menos de 2 minutos (o ya venció)
366
+ const TWO_MIN = 2 * 60 * 1000;
367
+ if (token.expiresAt - Date.now() < TWO_MIN && token.refreshToken) {
313
368
  const refreshed = await doRefreshToken(token.refreshToken);
314
369
  if (refreshed) {
315
370
  token = refreshed;
@@ -330,7 +385,6 @@ export async function callQwenAPIFromCreds(prompt, model, credsPath) {
330
385
  return callQwenAPIWithToken(refreshed, prompt, model);
331
386
  }
332
387
  }
333
- const cliName = path.basename(path.dirname(credsPath)).replace(/^\./, '');
334
388
  throw new Error(`QWEN_AUTH_EXPIRED: Sesión expirada. Ejecutá: ${cliName} --login`);
335
389
  }
336
390
  }
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"agent-rev","version":"0.3.4","description":"agent-rev agent","type":"module","main":"./dist/index.js","files":["dist/"],"bin":{"agent-rev":"dist/index.js"},"scripts":{"build":"tsc"},"keywords":["ai","agent","cli"],"license":"MIT","dependencies":{"@anthropic-ai/sdk":"^0.39.0","@google/generative-ai":"^0.24.0","chalk":"^5.4.1","commander":"^13.1.0","open":"^11.0.0","openai":"^4.91.0"},"engines":{"node":">=18.0.0"}}
1
+ {"name":"agent-rev","version":"0.3.6","description":"agent-rev agent","type":"module","main":"./dist/index.js","files":["dist/"],"bin":{"agent-rev":"dist/index.js"},"scripts":{"build":"tsc"},"keywords":["ai","agent","cli"],"license":"MIT","dependencies":{"@anthropic-ai/sdk":"^0.39.0","@google/generative-ai":"^0.24.0","chalk":"^5.4.1","commander":"^13.1.0","open":"^11.0.0","openai":"^4.91.0"},"engines":{"node":">=18.0.0"}}