opencode-qwen-cli-auth 2.2.5 → 2.2.7

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.
Files changed (2) hide show
  1. package/dist/index.js +115 -20
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -15,10 +15,12 @@ import { PROVIDER_ID, AUTH_LABELS, DEVICE_FLOW, PORTAL_HEADERS } from "./lib/con
15
15
  import { logError, logInfo, logWarn, LOGGING_ENABLED } from "./lib/logger.js";
16
16
  const CHAT_REQUEST_TIMEOUT_MS = 30000;
17
17
  const CHAT_MAX_RETRIES = 0;
18
+ const CHAT_MAX_TOKENS_CAP = 2048;
18
19
  const MAX_CONSECUTIVE_POLL_FAILURES = 3;
19
20
  const QUOTA_DEGRADE_MAX_TOKENS = 1024;
20
- const CLI_FALLBACK_TIMEOUT_MS = 45000;
21
+ const CLI_FALLBACK_TIMEOUT_MS = 8000;
21
22
  const CLI_FALLBACK_MAX_BUFFER_CHARS = 1024 * 1024;
23
+ const ENABLE_CLI_FALLBACK = process.env.OPENCODE_QWEN_ENABLE_CLI_FALLBACK === "1";
22
24
  const PLUGIN_USER_AGENT = "opencode-qwen-cli-auth/2.2.1";
23
25
  const CLIENT_ONLY_BODY_FIELDS = new Set([
24
26
  "providerID",
@@ -178,6 +180,14 @@ function sanitizeOutgoingPayload(payload) {
178
180
  delete sanitized.stream_options;
179
181
  changed = true;
180
182
  }
183
+ if (typeof sanitized.max_tokens === "number" && sanitized.max_tokens > CHAT_MAX_TOKENS_CAP) {
184
+ sanitized.max_tokens = CHAT_MAX_TOKENS_CAP;
185
+ changed = true;
186
+ }
187
+ if (typeof sanitized.max_completion_tokens === "number" && sanitized.max_completion_tokens > CHAT_MAX_TOKENS_CAP) {
188
+ sanitized.max_completion_tokens = CHAT_MAX_TOKENS_CAP;
189
+ changed = true;
190
+ }
181
191
  return changed ? sanitized : payload;
182
192
  }
183
193
  function createQuotaDegradedPayload(payload) {
@@ -309,7 +319,10 @@ function extractQwenCliText(events) {
309
319
  }
310
320
  return null;
311
321
  }
312
- function makeQwenCliCompletionResponse(model, content, context) {
322
+ function createSseResponseChunk(data) {
323
+ return `data: ${JSON.stringify(data)}\n\n`;
324
+ }
325
+ function makeQwenCliCompletionResponse(model, content, context, streamMode) {
313
326
  if (LOGGING_ENABLED) {
314
327
  logInfo("Qwen CLI fallback returned completion", {
315
328
  request_id: context.requestId,
@@ -317,6 +330,51 @@ function makeQwenCliCompletionResponse(model, content, context) {
317
330
  modelID: model,
318
331
  });
319
332
  }
333
+ if (streamMode) {
334
+ const completionId = `chatcmpl-${randomUUID()}`;
335
+ const created = Math.floor(Date.now() / 1000);
336
+ const encoder = new TextEncoder();
337
+ const stream = new ReadableStream({
338
+ start(controller) {
339
+ controller.enqueue(encoder.encode(createSseResponseChunk({
340
+ id: completionId,
341
+ object: "chat.completion.chunk",
342
+ created,
343
+ model,
344
+ choices: [
345
+ {
346
+ index: 0,
347
+ delta: { role: "assistant", content },
348
+ finish_reason: null,
349
+ },
350
+ ],
351
+ })));
352
+ controller.enqueue(encoder.encode(createSseResponseChunk({
353
+ id: completionId,
354
+ object: "chat.completion.chunk",
355
+ created,
356
+ model,
357
+ choices: [
358
+ {
359
+ index: 0,
360
+ delta: {},
361
+ finish_reason: "stop",
362
+ },
363
+ ],
364
+ })));
365
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
366
+ controller.close();
367
+ },
368
+ });
369
+ return new Response(stream, {
370
+ status: 200,
371
+ headers: {
372
+ "content-type": "text/event-stream; charset=utf-8",
373
+ "cache-control": "no-cache",
374
+ "x-qwen-cli-fallback": "1",
375
+ },
376
+ });
377
+ }
320
378
  const body = {
321
379
  id: `chatcmpl-${randomUUID()}`,
322
380
  object: "chat.completion",
@@ -346,8 +404,9 @@ function makeQwenCliCompletionResponse(model, content, context) {
346
404
  },
347
405
  });
348
406
  }
349
- async function runQwenCliFallback(payload, context) {
407
+ async function runQwenCliFallback(payload, context, abortSignal) {
350
408
  const model = typeof payload?.model === "string" && payload.model.length > 0 ? payload.model : "coder-model";
409
+ const streamMode = payload?.stream === true;
351
410
  const prompt = buildQwenCliPrompt(payload);
352
411
  const args = [prompt, "-o", "json", "--max-session-turns", "1", "--model", model];
353
412
  if (LOGGING_ENABLED) {
@@ -363,6 +422,8 @@ async function runQwenCliFallback(payload, context) {
363
422
  let stdout = "";
364
423
  let stderr = "";
365
424
  let timer = null;
425
+ let child = undefined;
426
+ let abortHandler = undefined;
366
427
  const useShell = shouldUseShell(QWEN_CLI_COMMAND);
367
428
  const finalize = (result) => {
368
429
  if (settled) {
@@ -372,9 +433,18 @@ async function runQwenCliFallback(payload, context) {
372
433
  if (timer) {
373
434
  clearTimeout(timer);
374
435
  }
436
+ if (abortSignal && abortHandler) {
437
+ abortSignal.removeEventListener("abort", abortHandler);
438
+ }
375
439
  resolve(result);
376
440
  };
377
- let child;
441
+ if (abortSignal?.aborted) {
442
+ finalize({
443
+ ok: false,
444
+ reason: "cli_aborted",
445
+ });
446
+ return;
447
+ }
378
448
  try {
379
449
  child = spawn(QWEN_CLI_COMMAND, args, {
380
450
  shell: useShell,
@@ -389,6 +459,20 @@ async function runQwenCliFallback(payload, context) {
389
459
  });
390
460
  return;
391
461
  }
462
+ if (abortSignal) {
463
+ abortHandler = () => {
464
+ try {
465
+ child?.kill();
466
+ }
467
+ catch (_killError) {
468
+ }
469
+ finalize({
470
+ ok: false,
471
+ reason: "cli_aborted",
472
+ });
473
+ };
474
+ abortSignal.addEventListener("abort", abortHandler, { once: true });
475
+ }
392
476
  timer = setTimeout(() => {
393
477
  try {
394
478
  child.kill();
@@ -418,7 +502,7 @@ async function runQwenCliFallback(payload, context) {
418
502
  if (content) {
419
503
  finalize({
420
504
  ok: true,
421
- response: makeQwenCliCompletionResponse(model, content, context),
505
+ response: makeQwenCliCompletionResponse(model, content, context, streamMode),
422
506
  });
423
507
  return;
424
508
  }
@@ -470,6 +554,7 @@ async function sendWithTimeout(input, requestInit) {
470
554
  }
471
555
  async function failFastFetch(input, init) {
472
556
  const requestInit = init ? { ...init } : {};
557
+ const sourceSignal = requestInit.signal;
473
558
  const rawPayload = parseJsonRequestBody(requestInit);
474
559
  const sessionID = typeof rawPayload?.sessionID === "string" ? rawPayload.sessionID : undefined;
475
560
  let payload = rawPayload;
@@ -532,10 +617,34 @@ async function failFastFetch(input, init) {
532
617
  return response;
533
618
  }
534
619
  const fallbackBody = await response.text().catch(() => "");
535
- const cliFallback = await runQwenCliFallback(payload, context);
620
+ if (ENABLE_CLI_FALLBACK) {
621
+ const cliFallback = await runQwenCliFallback(payload, context, sourceSignal);
622
+ if (cliFallback.ok) {
623
+ return cliFallback.response;
624
+ }
625
+ if (cliFallback.reason === "cli_aborted") {
626
+ return makeFailFastErrorResponse(400, "request_aborted", "Qwen request was aborted");
627
+ }
628
+ if (LOGGING_ENABLED) {
629
+ logWarn("Qwen CLI fallback failed", {
630
+ request_id: context.requestId,
631
+ sessionID: context.sessionID,
632
+ modelID: context.modelID,
633
+ reason: cliFallback.reason,
634
+ stderr: cliFallback.stderr,
635
+ });
636
+ }
637
+ }
638
+ return makeQuotaFailFastResponse(fallbackBody, response.headers, context);
639
+ }
640
+ if (ENABLE_CLI_FALLBACK) {
641
+ const cliFallback = await runQwenCliFallback(payload, context, sourceSignal);
536
642
  if (cliFallback.ok) {
537
643
  return cliFallback.response;
538
644
  }
645
+ if (cliFallback.reason === "cli_aborted") {
646
+ return makeFailFastErrorResponse(400, "request_aborted", "Qwen request was aborted");
647
+ }
539
648
  if (LOGGING_ENABLED) {
540
649
  logWarn("Qwen CLI fallback failed", {
541
650
  request_id: context.requestId,
@@ -545,20 +654,6 @@ async function failFastFetch(input, init) {
545
654
  stderr: cliFallback.stderr,
546
655
  });
547
656
  }
548
- return makeQuotaFailFastResponse(fallbackBody, response.headers, context);
549
- }
550
- const cliFallback = await runQwenCliFallback(payload, context);
551
- if (cliFallback.ok) {
552
- return cliFallback.response;
553
- }
554
- if (LOGGING_ENABLED) {
555
- logWarn("Qwen CLI fallback failed", {
556
- request_id: context.requestId,
557
- sessionID: context.sessionID,
558
- modelID: context.modelID,
559
- reason: cliFallback.reason,
560
- stderr: cliFallback.stderr,
561
- });
562
657
  }
563
658
  }
564
659
  return makeQuotaFailFastResponse(firstBody, response.headers, context);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-qwen-cli-auth",
3
- "version": "2.2.5",
3
+ "version": "2.2.7",
4
4
  "description": "Qwen OAuth authentication plugin for opencode - use your Qwen account instead of API keys",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",