opencode-qwen-oauth 1.0.1 → 1.1.0
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/browser.d.ts +5 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +24 -0
- package/dist/browser.js.map +1 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +10 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -171
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +6 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +37 -0
- package/dist/logger.js.map +1 -0
- package/dist/oauth.d.ts +21 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +91 -0
- package/dist/oauth.js.map +1 -0
- package/dist/pkce.d.ts +8 -0
- package/dist/pkce.d.ts.map +1 -0
- package/dist/pkce.js +17 -0
- package/dist/pkce.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAmB7C"}
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser utilities for opening URLs
|
|
3
|
+
*/
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
export function openBrowser(url) {
|
|
6
|
+
try {
|
|
7
|
+
const platform = process.platform;
|
|
8
|
+
const command = platform === "darwin"
|
|
9
|
+
? "open"
|
|
10
|
+
: platform === "win32"
|
|
11
|
+
? "rundll32"
|
|
12
|
+
: "xdg-open";
|
|
13
|
+
const args = platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
14
|
+
const child = spawn(command, args, {
|
|
15
|
+
stdio: "ignore",
|
|
16
|
+
detached: true,
|
|
17
|
+
});
|
|
18
|
+
child.unref?.();
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Ignore errors
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QAClC,MAAM,OAAO,GACX,QAAQ,KAAK,QAAQ;YACnB,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,QAAQ,KAAK,OAAO;gBACpB,CAAC,CAAC,UAAU;gBACZ,CAAC,CAAC,UAAU,CAAC;QACnB,MAAM,IAAI,GACR,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACtE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE;YACjC,KAAK,EAAE,QAAQ;YACf,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,gBAAgB;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants for Qwen OAuth Plugin
|
|
3
|
+
*/
|
|
4
|
+
export declare const QWEN_OAUTH_BASE_URL = "https://chat.qwen.ai";
|
|
5
|
+
export declare const QWEN_DEVICE_CODE_ENDPOINT = "/api/v1/oauth2/device/code";
|
|
6
|
+
export declare const QWEN_TOKEN_ENDPOINT = "/api/v1/oauth2/token";
|
|
7
|
+
export declare const QWEN_CLIENT_ID = "f0304373b74a44d2b584a3fb70ca9e56";
|
|
8
|
+
export declare const QWEN_SCOPES: string[];
|
|
9
|
+
export declare const QWEN_API_BASE_URL = "https://portal.qwen.ai/v1";
|
|
10
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAC1D,eAAO,MAAM,yBAAyB,+BAA+B,CAAC;AACtE,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAC1D,eAAO,MAAM,cAAc,qCAAqC,CAAC;AACjE,eAAO,MAAM,WAAW,UAAqD,CAAC;AAC9E,eAAO,MAAM,iBAAiB,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants for Qwen OAuth Plugin
|
|
3
|
+
*/
|
|
4
|
+
export const QWEN_OAUTH_BASE_URL = "https://chat.qwen.ai";
|
|
5
|
+
export const QWEN_DEVICE_CODE_ENDPOINT = "/api/v1/oauth2/device/code";
|
|
6
|
+
export const QWEN_TOKEN_ENDPOINT = "/api/v1/oauth2/token";
|
|
7
|
+
export const QWEN_CLIENT_ID = "f0304373b74a44d2b584a3fb70ca9e56";
|
|
8
|
+
export const QWEN_SCOPES = ["openid", "profile", "email", "model.completion"];
|
|
9
|
+
export const QWEN_API_BASE_URL = "https://portal.qwen.ai/v1";
|
|
10
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,sBAAsB,CAAC;AAC1D,MAAM,CAAC,MAAM,yBAAyB,GAAG,4BAA4B,CAAC;AACtE,MAAM,CAAC,MAAM,mBAAmB,GAAG,sBAAsB,CAAC;AAC1D,MAAM,CAAC,MAAM,cAAc,GAAG,kCAAkC,CAAC;AACjE,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,kBAAkB,CAAC,CAAC;AAC9E,MAAM,CAAC,MAAM,iBAAiB,GAAG,2BAA2B,CAAC"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAM/D,eAAO,MAAM,eAAe,EAAE,MAiG7B,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,177 +4,10 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @packageDocumentation
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { homedir } from "node:os";
|
|
12
|
-
// ============================================
|
|
13
|
-
// Constants
|
|
14
|
-
// ============================================
|
|
15
|
-
const QWEN_OAUTH_BASE_URL = "https://chat.qwen.ai";
|
|
16
|
-
const QWEN_DEVICE_CODE_ENDPOINT = "/api/v1/oauth2/device/code";
|
|
17
|
-
const QWEN_TOKEN_ENDPOINT = "/api/v1/oauth2/token";
|
|
18
|
-
const QWEN_CLIENT_ID = "f0304373b74a44d2b584a3fb70ca9e56";
|
|
19
|
-
const QWEN_SCOPES = ["openid", "profile", "email", "model.completion"];
|
|
20
|
-
const QWEN_API_BASE_URL = "https://portal.qwen.ai/v1";
|
|
21
|
-
// ============================================
|
|
22
|
-
// Logging
|
|
23
|
-
// ============================================
|
|
24
|
-
function getLogDir() {
|
|
25
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
26
|
-
return join(xdgConfig, "opencode", "logs");
|
|
27
|
-
}
|
|
28
|
-
function getLogFilePath() {
|
|
29
|
-
return join(getLogDir(), "qwen-oauth.log");
|
|
30
|
-
}
|
|
31
|
-
function ensureLogDir() {
|
|
32
|
-
const logDir = getLogDir();
|
|
33
|
-
if (!existsSync(logDir)) {
|
|
34
|
-
mkdirSync(logDir, { recursive: true, mode: 0o700 });
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
function writeLog(message) {
|
|
38
|
-
try {
|
|
39
|
-
ensureLogDir();
|
|
40
|
-
const timestamp = new Date().toISOString();
|
|
41
|
-
const logLine = `[${timestamp}] ${message}\n`;
|
|
42
|
-
appendFileSync(getLogFilePath(), logLine, { encoding: "utf-8" });
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// Silently ignore log write errors
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
const DEBUG = process.env.QWEN_OAUTH_DEBUG === "true" ||
|
|
49
|
-
process.env.QWEN_OAUTH_DEBUG === "1";
|
|
50
|
-
function debugLog(message, data) {
|
|
51
|
-
const logMessage = data ? `${message} ${JSON.stringify(data)}` : message;
|
|
52
|
-
writeLog(logMessage);
|
|
53
|
-
}
|
|
54
|
-
// ============================================
|
|
55
|
-
// PKCE
|
|
56
|
-
// ============================================
|
|
57
|
-
function base64UrlEncode(buffer) {
|
|
58
|
-
return buffer
|
|
59
|
-
.toString("base64")
|
|
60
|
-
.replace(/\+/g, "-")
|
|
61
|
-
.replace(/\//g, "_")
|
|
62
|
-
.replace(/=+$/, "");
|
|
63
|
-
}
|
|
64
|
-
function createPkcePair() {
|
|
65
|
-
const verifier = base64UrlEncode(randomBytes(32));
|
|
66
|
-
const challenge = base64UrlEncode(createHash("sha256").update(verifier).digest());
|
|
67
|
-
return { verifier, challenge };
|
|
68
|
-
}
|
|
69
|
-
// ============================================
|
|
70
|
-
// Browser
|
|
71
|
-
// ============================================
|
|
72
|
-
function openBrowser(url) {
|
|
73
|
-
try {
|
|
74
|
-
const platform = process.platform;
|
|
75
|
-
const command = platform === "darwin"
|
|
76
|
-
? "open"
|
|
77
|
-
: platform === "win32"
|
|
78
|
-
? "rundll32"
|
|
79
|
-
: "xdg-open";
|
|
80
|
-
const args = platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
81
|
-
const child = spawn(command, args, {
|
|
82
|
-
stdio: "ignore",
|
|
83
|
-
detached: true,
|
|
84
|
-
});
|
|
85
|
-
child.unref?.();
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
// Ignore errors
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
async function authorizeDevice() {
|
|
92
|
-
const { verifier, challenge } = createPkcePair();
|
|
93
|
-
const params = new URLSearchParams({
|
|
94
|
-
client_id: QWEN_CLIENT_ID,
|
|
95
|
-
scope: QWEN_SCOPES.join(" "),
|
|
96
|
-
code_challenge: challenge,
|
|
97
|
-
code_challenge_method: "S256",
|
|
98
|
-
});
|
|
99
|
-
const response = await fetch(`${QWEN_OAUTH_BASE_URL}${QWEN_DEVICE_CODE_ENDPOINT}`, {
|
|
100
|
-
method: "POST",
|
|
101
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
102
|
-
body: params.toString(),
|
|
103
|
-
});
|
|
104
|
-
if (!response.ok) {
|
|
105
|
-
throw new Error(`Failed to start device flow: ${response.statusText}`);
|
|
106
|
-
}
|
|
107
|
-
const data = (await response.json());
|
|
108
|
-
return {
|
|
109
|
-
device_code: data.device_code,
|
|
110
|
-
user_code: data.user_code,
|
|
111
|
-
verification_uri: data.verification_uri,
|
|
112
|
-
verification_uri_complete: data.verification_uri_complete,
|
|
113
|
-
expires_in: data.expires_in,
|
|
114
|
-
interval: data.interval || 5,
|
|
115
|
-
verifier,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
async function pollForToken(deviceCode, codeVerifier, intervalSeconds, expiresIn) {
|
|
119
|
-
const timeoutMs = expiresIn * 1000;
|
|
120
|
-
const startTime = Date.now();
|
|
121
|
-
let currentInterval = intervalSeconds * 1000;
|
|
122
|
-
debugLog("Starting token polling", { timeoutMs, interval: currentInterval });
|
|
123
|
-
while (Date.now() - startTime < timeoutMs) {
|
|
124
|
-
await new Promise((resolve) => setTimeout(resolve, currentInterval));
|
|
125
|
-
const params = new URLSearchParams({
|
|
126
|
-
client_id: QWEN_CLIENT_ID,
|
|
127
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
128
|
-
device_code: deviceCode,
|
|
129
|
-
code_verifier: codeVerifier,
|
|
130
|
-
});
|
|
131
|
-
debugLog("Polling for token...");
|
|
132
|
-
const response = await fetch(`${QWEN_OAUTH_BASE_URL}${QWEN_TOKEN_ENDPOINT}`, {
|
|
133
|
-
method: "POST",
|
|
134
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
135
|
-
body: params.toString(),
|
|
136
|
-
});
|
|
137
|
-
if (response.ok) {
|
|
138
|
-
const data = (await response.json());
|
|
139
|
-
debugLog("Token received successfully");
|
|
140
|
-
return {
|
|
141
|
-
success: true,
|
|
142
|
-
access_token: data.access_token,
|
|
143
|
-
refresh_token: data.refresh_token,
|
|
144
|
-
expires_in: data.expires_in,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
const error = (await response.json().catch(() => ({})));
|
|
148
|
-
if (error.error === "authorization_pending") {
|
|
149
|
-
debugLog("Authorization pending, retrying...");
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
if (error.error === "slow_down") {
|
|
153
|
-
currentInterval += 5000;
|
|
154
|
-
debugLog("Server requested slow down, new interval:", {
|
|
155
|
-
interval: currentInterval,
|
|
156
|
-
});
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
if (error.error === "expired_token") {
|
|
160
|
-
debugLog("Device code expired");
|
|
161
|
-
return {
|
|
162
|
-
success: false,
|
|
163
|
-
error: "Device code expired. Please try again.",
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
debugLog(`Token polling failed: ${error.error_description || "unknown error"}`);
|
|
167
|
-
return {
|
|
168
|
-
success: false,
|
|
169
|
-
error: error.error_description || "Authentication failed",
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
debugLog("Polling timeout exceeded");
|
|
173
|
-
return { success: false, error: "Polling timeout - device code expired" };
|
|
174
|
-
}
|
|
175
|
-
// ============================================
|
|
176
|
-
// Plugin
|
|
177
|
-
// ============================================
|
|
7
|
+
import { QWEN_API_BASE_URL } from "./constants.js";
|
|
8
|
+
import { debugLog } from "./logger.js";
|
|
9
|
+
import { openBrowser } from "./browser.js";
|
|
10
|
+
import { authorizeDevice, pollForToken } from "./oauth.js";
|
|
178
11
|
export const QwenOAuthPlugin = async ({ project, client, $, directory, worktree, }) => {
|
|
179
12
|
debugLog("Plugin initialized", {
|
|
180
13
|
directory,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAiCH,eAAO,MAAM,KAAK,SAEoB,CAAC;AAEvC,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAG9E"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging utilities for Qwen OAuth Plugin
|
|
3
|
+
*/
|
|
4
|
+
import { appendFileSync, mkdirSync, existsSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
function getLogDir() {
|
|
8
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
9
|
+
return join(xdgConfig, "opencode", "logs");
|
|
10
|
+
}
|
|
11
|
+
function getLogFilePath() {
|
|
12
|
+
return join(getLogDir(), "qwen-oauth.log");
|
|
13
|
+
}
|
|
14
|
+
function ensureLogDir() {
|
|
15
|
+
const logDir = getLogDir();
|
|
16
|
+
if (!existsSync(logDir)) {
|
|
17
|
+
mkdirSync(logDir, { recursive: true, mode: 0o700 });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function writeLog(message) {
|
|
21
|
+
try {
|
|
22
|
+
ensureLogDir();
|
|
23
|
+
const timestamp = new Date().toISOString();
|
|
24
|
+
const logLine = `[${timestamp}] ${message}\n`;
|
|
25
|
+
appendFileSync(getLogFilePath(), logLine, { encoding: "utf-8" });
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Silently ignore log write errors
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export const DEBUG = process.env.QWEN_OAUTH_DEBUG === "true" ||
|
|
32
|
+
process.env.QWEN_OAUTH_DEBUG === "1";
|
|
33
|
+
export function debugLog(message, data) {
|
|
34
|
+
const logMessage = data ? `${message} ${JSON.stringify(data)}` : message;
|
|
35
|
+
writeLog(logMessage);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,SAAS,SAAS;IAChB,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IAC5E,OAAO,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;AAC7C,CAAC;AAED,SAAS,cAAc;IACrB,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,gBAAgB,CAAC,CAAC;AAC7C,CAAC;AAED,SAAS,YAAY;IACnB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACtD,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,OAAe;IAC/B,IAAI,CAAC;QACH,YAAY,EAAE,CAAC;QACf,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC3C,MAAM,OAAO,GAAG,IAAI,SAAS,KAAK,OAAO,IAAI,CAAC;QAC9C,cAAc,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,mCAAmC;IACrC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAChB,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,MAAM;IACvC,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,GAAG,CAAC;AAEvC,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,IAA8B;IACtE,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IACzE,QAAQ,CAAC,UAAU,CAAC,CAAC;AACvB,CAAC"}
|
package/dist/oauth.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Device Flow implementation for Qwen
|
|
3
|
+
*/
|
|
4
|
+
export interface DeviceAuthorization {
|
|
5
|
+
device_code: string;
|
|
6
|
+
user_code: string;
|
|
7
|
+
verification_uri: string;
|
|
8
|
+
verification_uri_complete: string;
|
|
9
|
+
expires_in: number;
|
|
10
|
+
interval: number;
|
|
11
|
+
verifier: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function authorizeDevice(): Promise<DeviceAuthorization>;
|
|
14
|
+
export declare function pollForToken(deviceCode: string, codeVerifier: string, intervalSeconds: number, expiresIn: number): Promise<{
|
|
15
|
+
success: boolean;
|
|
16
|
+
access_token?: string;
|
|
17
|
+
refresh_token?: string;
|
|
18
|
+
expires_in?: number;
|
|
19
|
+
error?: string;
|
|
20
|
+
}>;
|
|
21
|
+
//# sourceMappingURL=oauth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,yBAAyB,EAAE,MAAM,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAiBD,wBAAsB,eAAe,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAiCpE;AAED,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,MAAM,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CA4ED"}
|
package/dist/oauth.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Device Flow implementation for Qwen
|
|
3
|
+
*/
|
|
4
|
+
import { QWEN_OAUTH_BASE_URL, QWEN_DEVICE_CODE_ENDPOINT, QWEN_TOKEN_ENDPOINT, QWEN_CLIENT_ID, QWEN_SCOPES, } from "./constants.js";
|
|
5
|
+
import { createPkcePair } from "./pkce.js";
|
|
6
|
+
import { debugLog } from "./logger.js";
|
|
7
|
+
export async function authorizeDevice() {
|
|
8
|
+
const { verifier, challenge } = createPkcePair();
|
|
9
|
+
const params = new URLSearchParams({
|
|
10
|
+
client_id: QWEN_CLIENT_ID,
|
|
11
|
+
scope: QWEN_SCOPES.join(" "),
|
|
12
|
+
code_challenge: challenge,
|
|
13
|
+
code_challenge_method: "S256",
|
|
14
|
+
});
|
|
15
|
+
const response = await fetch(`${QWEN_OAUTH_BASE_URL}${QWEN_DEVICE_CODE_ENDPOINT}`, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
18
|
+
body: params.toString(),
|
|
19
|
+
});
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
throw new Error(`Failed to start device flow: ${response.statusText}`);
|
|
22
|
+
}
|
|
23
|
+
const data = (await response.json());
|
|
24
|
+
return {
|
|
25
|
+
device_code: data.device_code,
|
|
26
|
+
user_code: data.user_code,
|
|
27
|
+
verification_uri: data.verification_uri,
|
|
28
|
+
verification_uri_complete: data.verification_uri_complete,
|
|
29
|
+
expires_in: data.expires_in,
|
|
30
|
+
interval: data.interval || 5,
|
|
31
|
+
verifier,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export async function pollForToken(deviceCode, codeVerifier, intervalSeconds, expiresIn) {
|
|
35
|
+
const timeoutMs = expiresIn * 1000;
|
|
36
|
+
const startTime = Date.now();
|
|
37
|
+
let currentInterval = intervalSeconds * 1000;
|
|
38
|
+
debugLog("Starting token polling", { timeoutMs, interval: currentInterval });
|
|
39
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
40
|
+
await new Promise((resolve) => setTimeout(resolve, currentInterval));
|
|
41
|
+
const params = new URLSearchParams({
|
|
42
|
+
client_id: QWEN_CLIENT_ID,
|
|
43
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
44
|
+
device_code: deviceCode,
|
|
45
|
+
code_verifier: codeVerifier,
|
|
46
|
+
});
|
|
47
|
+
debugLog("Polling for token...");
|
|
48
|
+
const response = await fetch(`${QWEN_OAUTH_BASE_URL}${QWEN_TOKEN_ENDPOINT}`, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
51
|
+
body: params.toString(),
|
|
52
|
+
});
|
|
53
|
+
if (response.ok) {
|
|
54
|
+
const data = (await response.json());
|
|
55
|
+
debugLog("Token received successfully");
|
|
56
|
+
return {
|
|
57
|
+
success: true,
|
|
58
|
+
access_token: data.access_token,
|
|
59
|
+
refresh_token: data.refresh_token,
|
|
60
|
+
expires_in: data.expires_in,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const error = (await response.json().catch(() => ({})));
|
|
64
|
+
if (error.error === "authorization_pending") {
|
|
65
|
+
debugLog("Authorization pending, retrying...");
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (error.error === "slow_down") {
|
|
69
|
+
currentInterval += 5000;
|
|
70
|
+
debugLog("Server requested slow down, new interval:", {
|
|
71
|
+
interval: currentInterval,
|
|
72
|
+
});
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (error.error === "expired_token") {
|
|
76
|
+
debugLog("Device code expired");
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
error: "Device code expired. Please try again.",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
debugLog(`Token polling failed: ${error.error_description || "unknown error"}`);
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
error: error.error_description || "Authentication failed",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
debugLog("Polling timeout exceeded");
|
|
89
|
+
return { success: false, error: "Polling timeout - device code expired" };
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"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"}
|
package/dist/pkce.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../src/pkce.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH,wBAAgB,cAAc,IAAI;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAMxE"}
|
package/dist/pkce.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PKCE (Proof Key for Code Exchange) utilities
|
|
3
|
+
*/
|
|
4
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
5
|
+
function base64UrlEncode(buffer) {
|
|
6
|
+
return buffer
|
|
7
|
+
.toString("base64")
|
|
8
|
+
.replace(/\+/g, "-")
|
|
9
|
+
.replace(/\//g, "_")
|
|
10
|
+
.replace(/=+$/, "");
|
|
11
|
+
}
|
|
12
|
+
export function createPkcePair() {
|
|
13
|
+
const verifier = base64UrlEncode(randomBytes(32));
|
|
14
|
+
const challenge = base64UrlEncode(createHash("sha256").update(verifier).digest());
|
|
15
|
+
return { verifier, challenge };
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=pkce.js.map
|
package/dist/pkce.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../src/pkce.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtD,SAAS,eAAe,CAAC,MAAc;IACrC,OAAO,MAAM;SACV,QAAQ,CAAC,QAAQ,CAAC;SAClB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,MAAM,QAAQ,GAAG,eAAe,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,eAAe,CAC/B,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAC/C,CAAC;IACF,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AACjC,CAAC"}
|
package/package.json
CHANGED