fix(oauth): repair qwen + codebuddy-cn device-code endpoints (#7517)

Both device-code providers failed at the upstream request (surfacing in the
companion extension as 'ошибка сервера OmniRoute'). Diagnosed by calling the
upstream endpoints directly:

- qwen: QWEN_CONFIG used the bare host qwen.ai, whose /api/v1/oauth2/device/code
  and /token paths return 404 Not Found. The working qwen-code device flow lives
  at chat.qwen.ai (verified: 200 + a valid device_code/user_code). Point both
  URLs at chat.qwen.ai.
- codebuddy-cn: the Tencent state endpoint reads 'platform' from the QUERY
  string, not the JSON body. Sending it only in the body returned
  400 {"code":10001,"msg":"platform is empty"}. Passing ?platform=CLI
  returned 200 with {code:0, data:{state, authUrl}}. Build the stateUrl with the
  platform query param (body kept as-is).

Validation: live upstream calls returned 200 for both corrected requests (device
flow can't be hit from CI). Regression guard: tests/unit/oauth-device-code-endpoints.test.ts.
This commit is contained in:
danscMax
2026-07-18 17:35:09 +03:00
committed by GitHub
parent 7ff2e5c0b5
commit 858d762918
3 changed files with 40 additions and 3 deletions

View File

@@ -74,8 +74,10 @@ export const CODEX_CONFIG = {
// Qwen OAuth Configuration (Device Code Flow with PKCE)
export const QWEN_CONFIG = {
clientId: resolvePublicCred("qwen_id", "QWEN_OAUTH_CLIENT_ID"),
deviceCodeUrl: "https://qwen.ai/api/v1/oauth2/device/code",
tokenUrl: "https://qwen.ai/api/v1/oauth2/token",
// Host is chat.qwen.ai — the bare qwen.ai host 404s on these paths (verified: the qwen-code
// device flow lives at chat.qwen.ai, returning a valid device_code; qwen.ai returns Not Found).
deviceCodeUrl: "https://chat.qwen.ai/api/v1/oauth2/device/code",
tokenUrl: "https://chat.qwen.ai/api/v1/oauth2/token",
scope: "openid profile email model.completion",
codeChallengeMethod: "S256",
};

View File

@@ -38,7 +38,10 @@ export const codebuddyCn = {
flowType: "device_code" as const,
requestDeviceCode: async (config: CodeBuddyConfig): Promise<CodeBuddyDeviceCodeResponse> => {
const response = await fetch(config.stateUrl, {
// CodeBuddy reads `platform` from the QUERY string, not the JSON body — sending it only in the
// body returns 400 "platform is empty" (verified). Pass it as a query param; body kept as-is.
const stateUrl = `${config.stateUrl}?platform=${encodeURIComponent(config.platform)}`;
const response = await fetch(stateUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",

View File

@@ -0,0 +1,32 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
// Regression guards for the two device-code providers that returned "ошибка сервера OmniRoute":
//
// qwen — QWEN_CONFIG pointed at the bare host qwen.ai, whose device/token paths 404.
// The working qwen-code device flow lives at chat.qwen.ai (verified live: 200 +
// a valid device_code). Guard: the config uses chat.qwen.ai, never bare qwen.ai.
// codebuddy-cn— the Tencent state endpoint reads `platform` from the QUERY string, not the JSON
// body; body-only returned 400 "platform is empty" (verified live). The fix passes
// it as a query param. Guard: requestDeviceCode builds the URL with ?platform=.
//
// Source-level: the real validation is the live upstream 200 (can't be hit from CI); these pin
// the exact change so a revert to the broken host / body-only platform fails here.
const here = dirname(fileURLToPath(import.meta.url));
const read = (p: string) => readFileSync(resolve(here, "../..", p), "utf8");
test("qwen device-code config uses chat.qwen.ai (not the 404ing bare qwen.ai)", () => {
const oauth = read("src/lib/oauth/constants/oauth.ts");
const qwenBlock = oauth.slice(oauth.indexOf("QWEN_CONFIG"), oauth.indexOf("QWEN_CONFIG") + 700);
assert.match(qwenBlock, /chat\.qwen\.ai\/api\/v1\/oauth2\/device\/code/, "deviceCodeUrl host");
assert.match(qwenBlock, /chat\.qwen\.ai\/api\/v1\/oauth2\/token/, "tokenUrl host");
assert.doesNotMatch(qwenBlock, /"https:\/\/qwen\.ai\//, "must not use the bare qwen.ai host");
});
test("codebuddy-cn device-code sends platform as a query param (not body-only)", () => {
const cb = read("src/lib/oauth/providers/codebuddy-cn.ts");
assert.match(cb, /\?platform=\$\{encodeURIComponent\(config\.platform\)\}/, "platform query param");
});