opencode-qwen-cli-auth 2.3.1 → 2.3.3

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/README.md CHANGED
@@ -67,7 +67,7 @@ The plugin stores each successful login in the multi-account store and can auto-
67
67
  | `ENABLE_PLUGIN_REQUEST_LOGGING=1` | Enable request logging to file | Optional |
68
68
  | `OPENCODE_QWEN_ENABLE_CLI_FALLBACK=1` | Enable CLI fallback on quota error | Optional |
69
69
  | `OPENCODE_QWEN_ACCOUNTS_PATH` | Override multi-account store path (must be inside `~/.qwen`) | Optional |
70
- | `OPENCODE_QWEN_QUOTA_COOLDOWN_MS` | Cooldown for exhausted accounts | Default: `1800000` (30 min) |
70
+ | `OPENCODE_QWEN_QUOTA_COOLDOWN_MS` | Cooldown for exhausted accounts | Default: `86400000` (24h) |
71
71
 
72
72
  ### Debug & Logging
73
73
 
package/README.vi.md CHANGED
@@ -67,7 +67,7 @@ Plugin sẽ lưu từng lần đăng nhập thành công vào kho đa tài kho
67
67
  | `ENABLE_PLUGIN_REQUEST_LOGGING=1` | Bật ghi log request ra file | Tùy chọn |
68
68
  | `OPENCODE_QWEN_ENABLE_CLI_FALLBACK=1` | Bật tính năng gọi CLI khi hết quota | Tùy chọn |
69
69
  | `OPENCODE_QWEN_ACCOUNTS_PATH` | Ghi đè đường dẫn kho đa tài khoản (phải nằm trong `~/.qwen`) | Tùy chọn |
70
- | `OPENCODE_QWEN_QUOTA_COOLDOWN_MS` | Thời gian cooldown cho tài khoản đã hết quota | Mặc định: `1800000` (30 phút) |
70
+ | `OPENCODE_QWEN_QUOTA_COOLDOWN_MS` | Thời gian cooldown cho tài khoản đã hết quota | Mặc định: `86400000` (24 giờ) |
71
71
 
72
72
  ### Debug & Logging
73
73
 
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ import { PROVIDER_ID, AUTH_LABELS, DEVICE_FLOW, PORTAL_HEADERS } from "./lib/con
22
22
  import { logError, logInfo, logWarn, LOGGING_ENABLED } from "./lib/logger.js";
23
23
 
24
24
  /** Request timeout for chat completions in milliseconds */
25
- const CHAT_REQUEST_TIMEOUT_MS = 30000;
25
+ const CHAT_REQUEST_TIMEOUT_MS = 120000;
26
26
  /** Maximum number of retry attempts for failed requests */
27
27
  const CHAT_MAX_RETRIES = 3;
28
28
  /** Output token cap for coder-model (64K tokens) */
@@ -415,19 +415,19 @@ function sanitizeOutgoingPayload(payload) {
415
415
  function createQuotaDegradedPayload(payload) {
416
416
  const degraded = { ...payload };
417
417
  let changed = false;
418
- // Remove tool-related fields
419
- if ("tools" in degraded) {
420
- delete degraded.tools;
421
- changed = true;
422
- }
423
- if ("tool_choice" in degraded) {
424
- delete degraded.tool_choice;
425
- changed = true;
426
- }
427
- if ("parallel_tool_calls" in degraded) {
428
- delete degraded.parallel_tool_calls;
429
- changed = true;
430
- }
418
+ // Remove tool-related fields (skip removing tools so Agents don't break)
419
+ // if ("tools" in degraded) {
420
+ // delete degraded.tools;
421
+ // changed = true;
422
+ // }
423
+ // if ("tool_choice" in degraded) {
424
+ // delete degraded.tool_choice;
425
+ // changed = true;
426
+ // }
427
+ // if ("parallel_tool_calls" in degraded) {
428
+ // delete degraded.parallel_tool_calls;
429
+ // changed = true;
430
+ // }
431
431
  // Disable streaming
432
432
  if (degraded.stream !== false) {
433
433
  degraded.stream = false;
@@ -595,7 +595,7 @@ function createSseResponseChunk(data) {
595
595
  * @param {boolean} streamMode - Whether to return streaming response
596
596
  * @returns {Response} Formatted completion response
597
597
  */
598
- function makeQwenCliCompletionResponse(model, content, context, streamMode) {
598
+ function makeQwenCliCompletionResponse(model, content, context, streamMode, abortSignal) {
599
599
  if (LOGGING_ENABLED) {
600
600
  logInfo("Qwen CLI fallback returned completion", {
601
601
  request_id: context.requestId,
@@ -609,7 +609,7 @@ function makeQwenCliCompletionResponse(model, content, context, streamMode) {
609
609
  const encoder = new TextEncoder();
610
610
  const stream = new ReadableStream({
611
611
  start(controller) {
612
- // Send first chunk with content
612
+ // Send first chunk with empty content
613
613
  controller.enqueue(encoder.encode(createSseResponseChunk({
614
614
  id: completionId,
615
615
  object: "chat.completion.chunk",
@@ -618,28 +618,51 @@ function makeQwenCliCompletionResponse(model, content, context, streamMode) {
618
618
  choices: [
619
619
  {
620
620
  index: 0,
621
- delta: { role: "assistant", content },
621
+ delta: { role: "assistant", content: "" },
622
622
  finish_reason: null,
623
623
  },
624
624
  ],
625
625
  })));
626
- // Send stop chunk
627
- controller.enqueue(encoder.encode(createSseResponseChunk({
628
- id: completionId,
629
- object: "chat.completion.chunk",
630
- created,
631
- model,
632
- choices: [
633
- {
634
- index: 0,
635
- delta: {},
636
- finish_reason: "stop",
637
- },
638
- ],
639
- })));
640
- // Send DONE marker
641
- controller.enqueue(encoder.encode("data: [DONE]\n\n"));
642
- controller.close();
626
+
627
+ const CHUNK_SIZE = 15;
628
+ const DELAY_MS = 20;
629
+ let position = 0;
630
+
631
+ function pushNextChunk() {
632
+ if (abortSignal?.aborted) {
633
+ try { controller.close(); } catch (e) { }
634
+ return;
635
+ }
636
+
637
+ if (position >= content.length) {
638
+ // Send stop chunk
639
+ controller.enqueue(encoder.encode(createSseResponseChunk({
640
+ id: completionId,
641
+ object: "chat.completion.chunk",
642
+ created,
643
+ model,
644
+ choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
645
+ })));
646
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
647
+ try { controller.close(); } catch (e) { }
648
+ return;
649
+ }
650
+
651
+ const nextSlice = content.slice(position, position + CHUNK_SIZE);
652
+ position += CHUNK_SIZE;
653
+
654
+ controller.enqueue(encoder.encode(createSseResponseChunk({
655
+ id: completionId,
656
+ object: "chat.completion.chunk",
657
+ created,
658
+ model,
659
+ choices: [{ index: 0, delta: { content: nextSlice }, finish_reason: null }],
660
+ })));
661
+
662
+ setTimeout(pushNextChunk, DELAY_MS);
663
+ }
664
+
665
+ pushNextChunk();
643
666
  },
644
667
  });
645
668
  return new Response(stream, {
@@ -701,12 +724,8 @@ async function runQwenCliFallback(payload, context, abortSignal) {
701
724
  command: QWEN_CLI_COMMAND,
702
725
  });
703
726
  }
704
- if (requiresShellExecution(QWEN_CLI_COMMAND)) {
705
- return {
706
- ok: false,
707
- reason: "cli_shell_execution_blocked_for_security",
708
- };
709
- }
727
+ // Use secure spawn logic across ALL OS, allowing .cmd locally on Windows by injecting shell correctly.
728
+ const isShellRequired = requiresShellExecution(QWEN_CLI_COMMAND);
710
729
  return await new Promise((resolve) => {
711
730
  let settled = false;
712
731
  let stdout = "";
@@ -736,7 +755,7 @@ async function runQwenCliFallback(payload, context, abortSignal) {
736
755
  }
737
756
  try {
738
757
  child = spawn(QWEN_CLI_COMMAND, args, {
739
- shell: false,
758
+ shell: isShellRequired,
740
759
  windowsHide: true,
741
760
  stdio: ["ignore", "pipe", "pipe"],
742
761
  });
@@ -791,7 +810,7 @@ async function runQwenCliFallback(payload, context, abortSignal) {
791
810
  if (content) {
792
811
  finalize({
793
812
  ok: true,
794
- response: makeQwenCliCompletionResponse(model, content, context, streamMode),
813
+ response: makeQwenCliCompletionResponse(model, content, context, streamMode, abortSignal),
795
814
  });
796
815
  return;
797
816
  }
@@ -964,7 +983,7 @@ async function failFastFetch(input, init) {
964
983
  attempt: retryAttempt + 1,
965
984
  });
966
985
  }
967
- const RETRYABLE_STATUS_CODES = [429, 500, 502, 503, 504];
986
+ const RETRYABLE_STATUS_CODES = [429, 500, 502, 503, 504];
968
987
  if (RETRYABLE_STATUS_CODES.includes(response.status)) {
969
988
  if (response.status === 429) {
970
989
  const firstBody = await response.text().catch(() => "");
@@ -30,7 +30,7 @@ const LOCK_MAX_ATTEMPTS = 20;
30
30
  /** Account schema version for ~/.qwen/oauth_accounts.json */
31
31
  const ACCOUNT_STORE_VERSION = 1;
32
32
  /** Default cooldown when account hits insufficient_quota */
33
- const DEFAULT_QUOTA_COOLDOWN_MS = 30 * 60 * 1000;
33
+ const DEFAULT_QUOTA_COOLDOWN_MS = 24 * 60 * 60 * 1000;
34
34
 
35
35
  /**
36
36
  * Checks if an error is an AbortError (from AbortController)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-qwen-cli-auth",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
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",