@iamtoricool/opencool-qwen-auth 0.0.6-alpha → 0.1.0-alpha
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/index.d.ts +4 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +104 -250
- package/dist/index.js.map +1 -1
- package/dist/lib/auth/auth.d.ts +2 -62
- package/dist/lib/auth/auth.d.ts.map +1 -1
- package/dist/lib/auth/auth.js +1 -187
- package/dist/lib/auth/auth.js.map +1 -1
- package/dist/lib/constants.d.ts +6 -7
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +6 -7
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/request/fetch-helpers.d.ts +1 -1
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +14 -23
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,27 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Qwen AI
|
|
3
|
-
*
|
|
4
|
-
* This plugin enables opencode to use Qwen's AI models via OAuth authentication
|
|
5
|
-
* with chat.qwen.ai, allowing users to leverage their Qwen subscription.
|
|
6
|
-
*
|
|
7
|
-
* @license MIT
|
|
2
|
+
* Qwen AI Authentication Plugin for opencode
|
|
3
|
+
* Uses Qwen's official OAuth 2.0 Device Authorization Flow
|
|
8
4
|
*/
|
|
9
5
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
10
6
|
/**
|
|
11
|
-
* Qwen AI
|
|
12
|
-
*
|
|
13
|
-
* This plugin enables opencode to use Qwen AI models via OAuth authentication,
|
|
14
|
-
* allowing users to leverage their Qwen Chat subscription.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```json
|
|
18
|
-
* {
|
|
19
|
-
* "plugin": ["opencool-qwen-auth"],
|
|
20
|
-
* "model": "qwen/qwen3-max"
|
|
21
|
-
* }
|
|
22
|
-
* ```
|
|
7
|
+
* Qwen AI Authentication Plugin
|
|
23
8
|
*/
|
|
24
9
|
export declare const QwenAuthPlugin: Plugin;
|
|
25
10
|
export default QwenAuthPlugin;
|
|
26
|
-
export type { UserConfig,
|
|
11
|
+
export type { UserConfig, RequestBody, TokenResult, } from "./lib/types.js";
|
|
27
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAgJ/D;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,MAwF5B,CAAC;AAEF,eAAe,cAAc,CAAC;AAE9B,YAAY,EACV,UAAU,EACV,WAAW,EACX,WAAW,GACZ,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,137 +1,128 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Qwen AI
|
|
3
|
-
*
|
|
4
|
-
* This plugin enables opencode to use Qwen's AI models via OAuth authentication
|
|
5
|
-
* with chat.qwen.ai, allowing users to leverage their Qwen subscription.
|
|
6
|
-
*
|
|
7
|
-
* @license MIT
|
|
2
|
+
* Qwen AI Authentication Plugin for opencode
|
|
3
|
+
* Uses Qwen's official OAuth 2.0 Device Authorization Flow
|
|
8
4
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { openBrowserUrl } from "./lib/auth/browser.js";
|
|
12
|
-
import { startLocalOAuthServer } from "./lib/auth/server.js";
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { QWEN_BASE_URL, DUMMY_API_KEY, LOG_STAGES, PLUGIN_NAME, PROVIDER_ID, QWEN_OAUTH_DEVICE_CODE_ENDPOINT, QWEN_OAUTH_TOKEN_ENDPOINT, QWEN_OAUTH_CLIENT_ID, QWEN_OAUTH_SCOPE, } from "./lib/constants.js";
|
|
13
7
|
import { loadPluginConfig, parseUserConfig, initLogger } from "./lib/config.js";
|
|
14
|
-
import { AUTH_LABELS, QWEN_BASE_URL, DUMMY_API_KEY, LOG_STAGES, PLUGIN_NAME, PROVIDER_ID, } from "./lib/constants.js";
|
|
15
8
|
import { logRequest, logDebug } from "./lib/logger.js";
|
|
16
|
-
import { createQwenHeaders, extractRequestUrl, handleErrorResponse, handleSuccessResponse,
|
|
9
|
+
import { createQwenHeaders, extractRequestUrl, handleErrorResponse, handleSuccessResponse, rewriteUrlForQwen, transformRequestForQwen, } from "./lib/request/fetch-helpers.js";
|
|
17
10
|
/**
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
11
|
+
* Generate PKCE code verifier
|
|
12
|
+
*/
|
|
13
|
+
function generateCodeVerifier() {
|
|
14
|
+
return randomBytes(32).toString("base64url").slice(0, 43);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Generate PKCE code challenge from verifier
|
|
18
|
+
*/
|
|
19
|
+
async function generateCodeChallenge(verifier) {
|
|
20
|
+
const encoder = new TextEncoder();
|
|
21
|
+
const data = encoder.encode(verifier);
|
|
22
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
23
|
+
return btoa(String.fromCharCode(...new Uint8Array(hash)))
|
|
24
|
+
.replace(/\+/g, "-")
|
|
25
|
+
.replace(/\//g, "_")
|
|
26
|
+
.replace(/=/g, "");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Initiate device authorization flow
|
|
30
|
+
*/
|
|
31
|
+
async function initiateDeviceAuth() {
|
|
32
|
+
const codeVerifier = generateCodeVerifier();
|
|
33
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
34
|
+
const response = await fetch(QWEN_OAUTH_DEVICE_CODE_ENDPOINT, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: {
|
|
37
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
38
|
+
Accept: "application/json",
|
|
39
|
+
},
|
|
40
|
+
body: new URLSearchParams({
|
|
41
|
+
client_id: QWEN_OAUTH_CLIENT_ID,
|
|
42
|
+
scope: QWEN_OAUTH_SCOPE,
|
|
43
|
+
code_challenge: codeChallenge,
|
|
44
|
+
code_challenge_method: "S256",
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
const err = await response.text();
|
|
49
|
+
throw new Error(`OAuth initiation failed: ${err}`);
|
|
50
|
+
}
|
|
51
|
+
const data = await response.json();
|
|
52
|
+
return { ...data, code_verifier: codeVerifier };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Poll for access token
|
|
56
|
+
*/
|
|
57
|
+
async function pollForToken(deviceCode, interval, maxAttempts = 120) {
|
|
58
|
+
let attempts = 0;
|
|
59
|
+
while (attempts < maxAttempts) {
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, interval * 1000));
|
|
61
|
+
attempts++;
|
|
62
|
+
const response = await fetch(QWEN_OAUTH_TOKEN_ENDPOINT, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: {
|
|
65
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
66
|
+
Accept: "application/json",
|
|
67
|
+
},
|
|
68
|
+
body: new URLSearchParams({
|
|
69
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
70
|
+
client_id: QWEN_OAUTH_CLIENT_ID,
|
|
71
|
+
device_code: deviceCode,
|
|
72
|
+
}),
|
|
73
|
+
});
|
|
74
|
+
const data = await response.json();
|
|
75
|
+
if (data.access_token) {
|
|
76
|
+
return {
|
|
77
|
+
access_token: data.access_token,
|
|
78
|
+
refresh_token: data.refresh_token,
|
|
79
|
+
expires_in: data.expires_in || 86400,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (data.error === "expired_token") {
|
|
83
|
+
throw new Error("Device code expired. Please try again.");
|
|
84
|
+
}
|
|
85
|
+
if (data.error && data.error !== "authorization_pending") {
|
|
86
|
+
throw new Error(`OAuth error: ${data.error_description || data.error}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
throw new Error("Authentication timed out. Please try again.");
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Qwen AI Authentication Plugin
|
|
30
93
|
*/
|
|
31
94
|
export const QwenAuthPlugin = async ({ client }) => {
|
|
32
|
-
// Initialize logger with plugin config
|
|
33
95
|
const pluginConfig = loadPluginConfig();
|
|
34
96
|
initLogger(pluginConfig);
|
|
35
|
-
/**
|
|
36
|
-
* Build manual OAuth flow for fallback when auto browser open fails
|
|
37
|
-
*/
|
|
38
|
-
const buildManualOAuthFlow = (pkce, url) => ({
|
|
39
|
-
url,
|
|
40
|
-
method: "code",
|
|
41
|
-
instructions: AUTH_LABELS.INSTRUCTIONS_MANUAL,
|
|
42
|
-
callback: async (input) => {
|
|
43
|
-
const parsed = parseAuthorizationInput(input);
|
|
44
|
-
if (!parsed.code) {
|
|
45
|
-
return { type: "failed" };
|
|
46
|
-
}
|
|
47
|
-
const tokens = await exchangeAuthorizationCode(parsed.code, pkce.verifier, REDIRECT_URI);
|
|
48
|
-
return tokens?.type === "success" ? tokens : { type: "failed" };
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
97
|
return {
|
|
52
98
|
auth: {
|
|
53
99
|
provider: PROVIDER_ID,
|
|
54
|
-
/**
|
|
55
|
-
* Loader function that configures OAuth authentication and request handling
|
|
56
|
-
*
|
|
57
|
-
* This function:
|
|
58
|
-
* 1. Validates OAuth authentication
|
|
59
|
-
* 2. Extracts user info from access token
|
|
60
|
-
* 3. Loads user configuration from opencode.json
|
|
61
|
-
* 4. Returns SDK configuration with custom fetch implementation
|
|
62
|
-
*
|
|
63
|
-
* @param getAuth - Function to retrieve current auth state
|
|
64
|
-
* @param provider - Provider configuration from opencode.json
|
|
65
|
-
* @returns SDK configuration object or empty object for non-OAuth auth
|
|
66
|
-
*/
|
|
67
100
|
async loader(getAuth, provider) {
|
|
68
101
|
const auth = await getAuth();
|
|
69
|
-
// Only handle OAuth auth type, skip API key auth
|
|
70
102
|
if (auth.type !== "oauth") {
|
|
71
103
|
return {};
|
|
72
104
|
}
|
|
73
|
-
|
|
74
|
-
const decoded = decodeJWT(auth.access);
|
|
75
|
-
if (!decoded) {
|
|
76
|
-
logDebug(`[${PLUGIN_NAME}] Failed to decode access token (skipping plugin)`);
|
|
77
|
-
return {};
|
|
78
|
-
}
|
|
79
|
-
logDebug(`[${PLUGIN_NAME}] Authenticated as:`, decoded.email || decoded.sub);
|
|
80
|
-
// Extract user configuration (global + per-model options)
|
|
105
|
+
logDebug(`[${PLUGIN_NAME}] Using OAuth authentication`);
|
|
81
106
|
const userConfig = parseUserConfig(provider);
|
|
82
|
-
// Return SDK configuration
|
|
83
107
|
return {
|
|
84
108
|
apiKey: DUMMY_API_KEY,
|
|
85
109
|
baseURL: QWEN_BASE_URL,
|
|
86
|
-
/**
|
|
87
|
-
* Custom fetch implementation for Qwen API
|
|
88
|
-
*
|
|
89
|
-
* Handles:
|
|
90
|
-
* - Token refresh when expired
|
|
91
|
-
* - URL rewriting for Qwen backend
|
|
92
|
-
* - Request body transformation
|
|
93
|
-
* - OAuth header injection
|
|
94
|
-
* - SSE to JSON conversion for non-streaming requests
|
|
95
|
-
* - Error handling and logging
|
|
96
|
-
*
|
|
97
|
-
* @param input - Request URL or Request object
|
|
98
|
-
* @param init - Request options
|
|
99
|
-
* @returns Response from Qwen API
|
|
100
|
-
*/
|
|
101
110
|
async fetch(input, init) {
|
|
102
|
-
|
|
103
|
-
let currentAuth = await getAuth();
|
|
104
|
-
if (shouldRefreshToken(currentAuth)) {
|
|
105
|
-
currentAuth = await refreshAndUpdateToken(currentAuth, client);
|
|
106
|
-
}
|
|
107
|
-
// Step 2: Extract and rewrite URL for Qwen backend
|
|
111
|
+
const currentAuth = await getAuth();
|
|
108
112
|
const originalUrl = extractRequestUrl(input);
|
|
109
113
|
const url = rewriteUrlForQwen(originalUrl);
|
|
110
|
-
|
|
111
|
-
// Capture original stream value before transformation
|
|
112
|
-
const originalBody = init?.body
|
|
113
|
-
? JSON.parse(init.body)
|
|
114
|
-
: {};
|
|
114
|
+
const originalBody = init?.body ? JSON.parse(init.body) : {};
|
|
115
115
|
const isStreaming = originalBody.stream === true;
|
|
116
116
|
const transformation = await transformRequestForQwen(init, url, userConfig);
|
|
117
117
|
const requestInit = transformation?.updatedInit ?? init;
|
|
118
|
-
// Step 4: Create headers with OAuth token
|
|
119
118
|
const accessToken = currentAuth.type === "oauth" ? currentAuth.access : "";
|
|
120
119
|
const headers = createQwenHeaders(requestInit, accessToken);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const response = await fetch(url, {
|
|
124
|
-
...requestInit,
|
|
125
|
-
headers,
|
|
126
|
-
});
|
|
127
|
-
// Step 6: Log response
|
|
120
|
+
logDebug(`[${PLUGIN_NAME}] Request to:`, url);
|
|
121
|
+
const response = await fetch(url, { ...requestInit, headers });
|
|
128
122
|
logRequest(LOG_STAGES.RESPONSE, {
|
|
129
123
|
status: response.status,
|
|
130
124
|
ok: response.ok,
|
|
131
|
-
statusText: response.statusText,
|
|
132
|
-
headers: Object.fromEntries(response.headers.entries()),
|
|
133
125
|
});
|
|
134
|
-
// Step 7: Handle error or success response
|
|
135
126
|
if (!response.ok) {
|
|
136
127
|
return await handleErrorResponse(response);
|
|
137
128
|
}
|
|
@@ -141,169 +132,32 @@ export const QwenAuthPlugin = async ({ client }) => {
|
|
|
141
132
|
},
|
|
142
133
|
methods: [
|
|
143
134
|
{
|
|
144
|
-
label: "
|
|
135
|
+
label: "Qwen.ai OAuth",
|
|
145
136
|
type: "oauth",
|
|
146
137
|
authorize: async () => {
|
|
147
|
-
|
|
148
|
-
const url = "https://chat.qwen.ai";
|
|
149
|
-
openBrowserUrl(url);
|
|
138
|
+
const deviceAuth = await initiateDeviceAuth();
|
|
150
139
|
return {
|
|
151
|
-
url,
|
|
140
|
+
url: deviceAuth.verification_uri,
|
|
152
141
|
method: "code",
|
|
153
|
-
instructions:
|
|
154
|
-
callback: async (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
return { type: "failed" };
|
|
158
|
-
}
|
|
159
|
-
// Validate it's a JWT
|
|
160
|
-
const decoded = decodeJWT(token);
|
|
161
|
-
if (!decoded) {
|
|
162
|
-
console.error(`[${PLUGIN_NAME}] Invalid JWT token format`);
|
|
163
|
-
return { type: "failed" };
|
|
164
|
-
}
|
|
165
|
-
// Check expiration
|
|
166
|
-
const exp = typeof decoded.exp === "number" ? decoded.exp : undefined;
|
|
167
|
-
if (exp && exp * 1000 < Date.now()) {
|
|
168
|
-
console.error(`[${PLUGIN_NAME}] Token already expired`);
|
|
169
|
-
return { type: "failed" };
|
|
170
|
-
}
|
|
171
|
-
return {
|
|
172
|
-
type: "success",
|
|
173
|
-
access: token,
|
|
174
|
-
refresh: token,
|
|
175
|
-
expires: exp ? exp * 1000 : Date.now() + 3600000,
|
|
176
|
-
};
|
|
177
|
-
},
|
|
178
|
-
};
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
label: "Qwen AI OAuth (Google - may not work)",
|
|
183
|
-
type: "oauth",
|
|
184
|
-
authorize: async () => {
|
|
185
|
-
const { pkce, state, url } = await createAuthorizationFlow();
|
|
186
|
-
const serverInfo = await startLocalOAuthServer({ state });
|
|
187
|
-
// Attempt to open browser automatically
|
|
188
|
-
openBrowserUrl(url);
|
|
189
|
-
if (!serverInfo.ready) {
|
|
190
|
-
serverInfo.close();
|
|
191
|
-
return {
|
|
192
|
-
url,
|
|
193
|
-
method: "code",
|
|
194
|
-
instructions: "A browser window should have opened.\n\n1. Sign in with your Google account on Qwen\n2. After login, copy the JWT token from the URL\n3. Paste it below\n\nNote: Qwen OAuth requires the official web client. If this doesn't work, use 'Paste JWT Token' method instead.",
|
|
195
|
-
callback: async (input) => {
|
|
196
|
-
const token = input?.trim();
|
|
197
|
-
if (!token)
|
|
198
|
-
return { type: "failed" };
|
|
199
|
-
const decoded = decodeJWT(token);
|
|
200
|
-
if (!decoded)
|
|
201
|
-
return { type: "failed" };
|
|
202
|
-
const exp = typeof decoded.exp === "number" ? decoded.exp : undefined;
|
|
203
|
-
if (exp && exp * 1000 < Date.now())
|
|
204
|
-
return { type: "failed" };
|
|
142
|
+
instructions: `Please authenticate with Qwen:\n\n1. Go to: ${deviceAuth.verification_uri}\n2. Enter code: ${deviceAuth.user_code}\n3. Login with your Qwen account\n4. Return here when done`,
|
|
143
|
+
callback: async () => {
|
|
144
|
+
try {
|
|
145
|
+
const token = await pollForToken(deviceAuth.device_code, deviceAuth.interval);
|
|
205
146
|
return {
|
|
206
147
|
type: "success",
|
|
207
|
-
access: token,
|
|
208
|
-
refresh: token,
|
|
209
|
-
expires:
|
|
148
|
+
access: token.access_token,
|
|
149
|
+
refresh: token.refresh_token || token.access_token,
|
|
150
|
+
expires: Date.now() + token.expires_in * 1000,
|
|
210
151
|
};
|
|
211
|
-
},
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
return {
|
|
215
|
-
url,
|
|
216
|
-
method: "auto",
|
|
217
|
-
instructions: "A browser window should have opened. Sign in with Google to authenticate with Qwen.",
|
|
218
|
-
callback: async () => {
|
|
219
|
-
const result = await serverInfo.waitForCode(state);
|
|
220
|
-
serverInfo.close();
|
|
221
|
-
if (!result) {
|
|
222
|
-
return { type: "failed" };
|
|
223
|
-
}
|
|
224
|
-
const tokens = await exchangeAuthorizationCode(result.code, pkce.verifier, REDIRECT_URI);
|
|
225
|
-
return tokens?.type === "success"
|
|
226
|
-
? tokens
|
|
227
|
-
: { type: "failed" };
|
|
228
|
-
},
|
|
229
|
-
};
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
label: "Extract from Browser Session (Firefox/Chrome)",
|
|
234
|
-
type: "oauth",
|
|
235
|
-
authorize: async () => {
|
|
236
|
-
// Try to extract session from browser
|
|
237
|
-
const token = await extractBrowserSession();
|
|
238
|
-
if (token) {
|
|
239
|
-
// Validate and exchange the token
|
|
240
|
-
const tokens = await exchangeAuthorizationCode(token);
|
|
241
|
-
if (tokens.type === "success") {
|
|
242
|
-
return {
|
|
243
|
-
url: "https://chat.qwen.ai",
|
|
244
|
-
method: "code",
|
|
245
|
-
instructions: "Browser session authenticated successfully!",
|
|
246
|
-
callback: async () => tokens,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
// If browser session failed, provide manual instructions
|
|
251
|
-
const available = await isBrowserSessionAvailable();
|
|
252
|
-
if (!available) {
|
|
253
|
-
return {
|
|
254
|
-
url: "https://chat.qwen.ai",
|
|
255
|
-
method: "code",
|
|
256
|
-
instructions: "Browser session extraction requires better-sqlite3.\n\nInstall it with:\n cd ~/.config/opencode && npm install better-sqlite3\n\nThen try again, or use API key authentication instead.",
|
|
257
|
-
callback: async () => ({ type: "failed" }),
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
return {
|
|
261
|
-
url: "https://chat.qwen.ai",
|
|
262
|
-
method: "code",
|
|
263
|
-
instructions: "No Qwen session found in browser cookies.\n\nTo authenticate:\n1. Open Firefox or Chrome\n2. Go to https://chat.qwen.ai\n3. Sign in with your account\n4. Run this auth command again\n\nOr use API key authentication.",
|
|
264
|
-
callback: async () => ({ type: "failed" }),
|
|
265
|
-
};
|
|
266
|
-
},
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
label: "Paste JWT Token from Browser",
|
|
270
|
-
type: "oauth",
|
|
271
|
-
authorize: async () => {
|
|
272
|
-
return {
|
|
273
|
-
url: "https://chat.qwen.ai",
|
|
274
|
-
method: "code",
|
|
275
|
-
instructions: "To get your JWT token:\n\n1. Open Firefox/Chrome and go to https://chat.qwen.ai\n2. Sign in to your account\n3. Open DevTools (F12) → Application → Cookies\n4. Find the 'token' cookie for .qwen.ai\n5. Copy the cookie value (it's a JWT token)\n\nPaste the token below:",
|
|
276
|
-
callback: async (input) => {
|
|
277
|
-
const token = input?.trim();
|
|
278
|
-
if (!token) {
|
|
279
|
-
return { type: "failed" };
|
|
280
152
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (!decoded) {
|
|
284
|
-
console.error(`[${PLUGIN_NAME}] Invalid JWT token format`);
|
|
153
|
+
catch (error) {
|
|
154
|
+
console.error(`[${PLUGIN_NAME}] Auth failed:`, error);
|
|
285
155
|
return { type: "failed" };
|
|
286
156
|
}
|
|
287
|
-
// Check expiration
|
|
288
|
-
const exp = typeof decoded.exp === "number" ? decoded.exp : undefined;
|
|
289
|
-
if (exp && exp * 1000 < Date.now()) {
|
|
290
|
-
console.error(`[${PLUGIN_NAME}] Token already expired`);
|
|
291
|
-
return { type: "failed" };
|
|
292
|
-
}
|
|
293
|
-
return {
|
|
294
|
-
type: "success",
|
|
295
|
-
access: token,
|
|
296
|
-
refresh: token,
|
|
297
|
-
expires: exp ? exp * 1000 : Date.now() + 3600000,
|
|
298
|
-
};
|
|
299
157
|
},
|
|
300
158
|
};
|
|
301
159
|
},
|
|
302
160
|
},
|
|
303
|
-
{
|
|
304
|
-
label: AUTH_LABELS.API_KEY,
|
|
305
|
-
type: "api",
|
|
306
|
-
},
|
|
307
161
|
],
|
|
308
162
|
},
|
|
309
163
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/auth/auth.d.ts
CHANGED
|
@@ -1,72 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OAuth authentication
|
|
3
|
-
* Handles Google OAuth flow and JWT token extraction
|
|
2
|
+
* OAuth authentication utilities for Qwen
|
|
4
3
|
*/
|
|
5
|
-
import type {
|
|
6
|
-
/**
|
|
7
|
-
* Generate a random state value for OAuth flow
|
|
8
|
-
* @returns Random hex string
|
|
9
|
-
*/
|
|
10
|
-
export declare function createState(): string;
|
|
11
|
-
/**
|
|
12
|
-
* Parse authorization token from URL fragment
|
|
13
|
-
* Qwen returns token in format: https://chat.qwen.ai/auth#token=JWT
|
|
14
|
-
* @param url - URL with fragment containing token
|
|
15
|
-
* @returns The JWT token or undefined
|
|
16
|
-
*/
|
|
17
|
-
export declare function parseTokenFromFragment(url: string): string | undefined;
|
|
18
|
-
/**
|
|
19
|
-
* Parse authorization input from user
|
|
20
|
-
* Handles: full URL with fragment, just fragment, or just token
|
|
21
|
-
* @param input - User input
|
|
22
|
-
* @returns Parsed authorization data
|
|
23
|
-
*/
|
|
24
|
-
export declare function parseAuthorizationInput(input: string): ParsedAuthInput;
|
|
25
|
-
/**
|
|
26
|
-
* Create Google OAuth authorization flow for Qwen
|
|
27
|
-
* @returns Authorization flow details
|
|
28
|
-
*/
|
|
29
|
-
export declare function createAuthorizationFlow(): Promise<AuthorizationFlow>;
|
|
30
|
-
/**
|
|
31
|
-
* Exchange authorization code for access and refresh tokens
|
|
32
|
-
* For Qwen, the "code" is actually a JWT token obtained from URL fragment
|
|
33
|
-
* @param code - JWT token from Qwen auth
|
|
34
|
-
* @param _verifier - PKCE verifier (not used for Qwen but kept for interface)
|
|
35
|
-
* @param _redirectUri - OAuth redirect URI (not used but kept for interface)
|
|
36
|
-
* @returns Token result
|
|
37
|
-
*/
|
|
38
|
-
export declare function exchangeAuthorizationCode(code: string, _verifier?: string, _redirectUri?: string): Promise<TokenResult>;
|
|
39
|
-
/**
|
|
40
|
-
* Refresh access token using refresh token
|
|
41
|
-
* For Qwen, we just check if the token is still valid
|
|
42
|
-
* @param refreshToken - Refresh token (same as access token for Qwen)
|
|
43
|
-
* @returns Token result
|
|
44
|
-
*/
|
|
45
|
-
export declare function refreshAccessToken(refreshToken: string): Promise<TokenResult>;
|
|
4
|
+
import type { JWTPayload } from "../types.js";
|
|
46
5
|
/**
|
|
47
6
|
* Decode a JWT token to extract payload (without verification)
|
|
48
7
|
* @param token - JWT token to decode
|
|
49
8
|
* @returns Decoded payload or null if invalid
|
|
50
9
|
*/
|
|
51
10
|
export declare function decodeJWT(token: string): JWTPayload | null;
|
|
52
|
-
/**
|
|
53
|
-
* Get the Qwen OAuth login URL
|
|
54
|
-
* This is the simpler approach - open Qwen's login page directly
|
|
55
|
-
* @returns Qwen login URL
|
|
56
|
-
*/
|
|
57
|
-
export declare function getQwenLoginUrl(): string;
|
|
58
|
-
/**
|
|
59
|
-
* Build manual OAuth flow for Qwen
|
|
60
|
-
* This opens the Google OAuth directly with the Qwen callback
|
|
61
|
-
* @returns Manual OAuth flow configuration
|
|
62
|
-
*/
|
|
63
|
-
export declare function buildManualOAuthFlow(): Promise<{
|
|
64
|
-
url: string;
|
|
65
|
-
method: "code";
|
|
66
|
-
instructions: "Token not found in URL fragment";
|
|
67
|
-
callback: (input: string) => Promise<import("../types.js").TokenSuccess | {
|
|
68
|
-
type: "failed";
|
|
69
|
-
}>;
|
|
70
|
-
}>;
|
|
71
11
|
export { extractBrowserSession, isBrowserSessionAvailable, getBrowserSessionInstructions, findFirefoxCookiePath, } from "./browser-session.js";
|
|
72
12
|
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../lib/auth/auth.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../lib/auth/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAc1D;AAGD,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,sBAAsB,CAAC"}
|
package/dist/lib/auth/auth.js
CHANGED
|
@@ -1,162 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OAuth authentication
|
|
3
|
-
* Handles Google OAuth flow and JWT token extraction
|
|
2
|
+
* OAuth authentication utilities for Qwen
|
|
4
3
|
*/
|
|
5
|
-
import { generatePKCE } from "@openauthjs/openauth/pkce";
|
|
6
|
-
import { randomBytes } from "node:crypto";
|
|
7
|
-
import { GOOGLE_CLIENT_ID, GOOGLE_AUTHORIZE_URL, QWEN_OAUTH_LOGIN, QWEN_OAUTH_CALLBACK, GOOGLE_SCOPE, PLUGIN_NAME, ERROR_MESSAGES, } from "../constants.js";
|
|
8
|
-
/**
|
|
9
|
-
* Generate a random state value for OAuth flow
|
|
10
|
-
* @returns Random hex string
|
|
11
|
-
*/
|
|
12
|
-
export function createState() {
|
|
13
|
-
return randomBytes(16).toString("hex");
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Parse authorization token from URL fragment
|
|
17
|
-
* Qwen returns token in format: https://chat.qwen.ai/auth#token=JWT
|
|
18
|
-
* @param url - URL with fragment containing token
|
|
19
|
-
* @returns The JWT token or undefined
|
|
20
|
-
*/
|
|
21
|
-
export function parseTokenFromFragment(url) {
|
|
22
|
-
try {
|
|
23
|
-
const urlObj = new URL(url);
|
|
24
|
-
const fragment = urlObj.hash;
|
|
25
|
-
if (!fragment || !fragment.startsWith("#")) {
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
// Remove leading # and parse as query string
|
|
29
|
-
const params = new URLSearchParams(fragment.substring(1));
|
|
30
|
-
return params.get("token") ?? undefined;
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
// If not a valid URL, try parsing as fragment directly
|
|
34
|
-
if (url.startsWith("#")) {
|
|
35
|
-
const params = new URLSearchParams(url.substring(1));
|
|
36
|
-
return params.get("token") ?? undefined;
|
|
37
|
-
}
|
|
38
|
-
if (url.includes("token=")) {
|
|
39
|
-
const params = new URLSearchParams(url);
|
|
40
|
-
return params.get("token") ?? undefined;
|
|
41
|
-
}
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Parse authorization input from user
|
|
47
|
-
* Handles: full URL with fragment, just fragment, or just token
|
|
48
|
-
* @param input - User input
|
|
49
|
-
* @returns Parsed authorization data
|
|
50
|
-
*/
|
|
51
|
-
export function parseAuthorizationInput(input) {
|
|
52
|
-
const value = (input || "").trim();
|
|
53
|
-
if (!value)
|
|
54
|
-
return {};
|
|
55
|
-
// Try to extract token from URL fragment
|
|
56
|
-
const token = parseTokenFromFragment(value);
|
|
57
|
-
if (token) {
|
|
58
|
-
return { code: token }; // Use 'code' field for compatibility
|
|
59
|
-
}
|
|
60
|
-
// Handle direct token paste (if it looks like a JWT)
|
|
61
|
-
if (value.split(".").length === 3) {
|
|
62
|
-
return { code: value };
|
|
63
|
-
}
|
|
64
|
-
return {};
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Create Google OAuth authorization flow for Qwen
|
|
68
|
-
* @returns Authorization flow details
|
|
69
|
-
*/
|
|
70
|
-
export async function createAuthorizationFlow() {
|
|
71
|
-
const pkce = (await generatePKCE());
|
|
72
|
-
const state = createState();
|
|
73
|
-
// Build Google OAuth URL with Qwen callback
|
|
74
|
-
const url = new URL(GOOGLE_AUTHORIZE_URL);
|
|
75
|
-
url.searchParams.set("response_type", "code");
|
|
76
|
-
url.searchParams.set("client_id", GOOGLE_CLIENT_ID);
|
|
77
|
-
url.searchParams.set("redirect_uri", QWEN_OAUTH_CALLBACK);
|
|
78
|
-
url.searchParams.set("scope", GOOGLE_SCOPE);
|
|
79
|
-
url.searchParams.set("state", state);
|
|
80
|
-
url.searchParams.set("code_challenge", pkce.challenge);
|
|
81
|
-
url.searchParams.set("code_challenge_method", "S256");
|
|
82
|
-
url.searchParams.set("nonce", createState());
|
|
83
|
-
return { pkce, state, url: url.toString() };
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Exchange authorization code for access and refresh tokens
|
|
87
|
-
* For Qwen, the "code" is actually a JWT token obtained from URL fragment
|
|
88
|
-
* @param code - JWT token from Qwen auth
|
|
89
|
-
* @param _verifier - PKCE verifier (not used for Qwen but kept for interface)
|
|
90
|
-
* @param _redirectUri - OAuth redirect URI (not used but kept for interface)
|
|
91
|
-
* @returns Token result
|
|
92
|
-
*/
|
|
93
|
-
export async function exchangeAuthorizationCode(code, _verifier, _redirectUri) {
|
|
94
|
-
try {
|
|
95
|
-
// For Qwen, the "code" is actually a JWT token
|
|
96
|
-
// We verify it's a valid JWT and extract expiration
|
|
97
|
-
const decoded = decodeJWT(code);
|
|
98
|
-
if (!decoded) {
|
|
99
|
-
console.error(`[${PLUGIN_NAME}] Invalid JWT token provided`);
|
|
100
|
-
return { type: "failed" };
|
|
101
|
-
}
|
|
102
|
-
// Check token expiration
|
|
103
|
-
const exp = typeof decoded.exp === "number" ? decoded.exp : undefined;
|
|
104
|
-
const expiresIn = exp
|
|
105
|
-
? exp * 1000 - Date.now()
|
|
106
|
-
: 3600 * 1000; // Default 1 hour if no exp
|
|
107
|
-
if (expiresIn <= 0) {
|
|
108
|
-
console.error(`[${PLUGIN_NAME}] Token already expired`);
|
|
109
|
-
return { type: "failed" };
|
|
110
|
-
}
|
|
111
|
-
// For Qwen, the access token IS the JWT, and we use it as refresh too
|
|
112
|
-
return {
|
|
113
|
-
type: "success",
|
|
114
|
-
access: code,
|
|
115
|
-
refresh: code, // Qwen tokens are self-contained JWTs
|
|
116
|
-
expires: Date.now() + expiresIn,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
catch (error) {
|
|
120
|
-
console.error(`[${PLUGIN_NAME}] Token exchange error:`, error);
|
|
121
|
-
return { type: "failed" };
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Refresh access token using refresh token
|
|
126
|
-
* For Qwen, we just check if the token is still valid
|
|
127
|
-
* @param refreshToken - Refresh token (same as access token for Qwen)
|
|
128
|
-
* @returns Token result
|
|
129
|
-
*/
|
|
130
|
-
export async function refreshAccessToken(refreshToken) {
|
|
131
|
-
try {
|
|
132
|
-
// Decode and check if still valid
|
|
133
|
-
const decoded = decodeJWT(refreshToken);
|
|
134
|
-
if (!decoded) {
|
|
135
|
-
console.error(`[${PLUGIN_NAME}] Invalid refresh token`);
|
|
136
|
-
return { type: "failed" };
|
|
137
|
-
}
|
|
138
|
-
// Check expiration
|
|
139
|
-
const exp = typeof decoded.exp === "number" ? decoded.exp : undefined;
|
|
140
|
-
if (exp && exp * 1000 < Date.now()) {
|
|
141
|
-
console.error(`[${PLUGIN_NAME}] Refresh token expired`);
|
|
142
|
-
return { type: "failed" };
|
|
143
|
-
}
|
|
144
|
-
// Token is still valid, return it
|
|
145
|
-
const expiresIn = exp
|
|
146
|
-
? exp * 1000 - Date.now()
|
|
147
|
-
: 3600 * 1000;
|
|
148
|
-
return {
|
|
149
|
-
type: "success",
|
|
150
|
-
access: refreshToken,
|
|
151
|
-
refresh: refreshToken,
|
|
152
|
-
expires: Date.now() + expiresIn,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
console.error(`[${PLUGIN_NAME}] Token refresh error:`, error);
|
|
157
|
-
return { type: "failed" };
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
4
|
/**
|
|
161
5
|
* Decode a JWT token to extract payload (without verification)
|
|
162
6
|
* @param token - JWT token to decode
|
|
@@ -168,7 +12,6 @@ export function decodeJWT(token) {
|
|
|
168
12
|
if (parts.length !== 3)
|
|
169
13
|
return null;
|
|
170
14
|
const payload = parts[1];
|
|
171
|
-
// Add padding if needed
|
|
172
15
|
const padding = 4 - (payload.length % 4);
|
|
173
16
|
const paddedPayload = padding === 4 ? payload : payload + "=".repeat(padding);
|
|
174
17
|
const decoded = Buffer.from(paddedPayload, "base64").toString("utf-8");
|
|
@@ -178,35 +21,6 @@ export function decodeJWT(token) {
|
|
|
178
21
|
return null;
|
|
179
22
|
}
|
|
180
23
|
}
|
|
181
|
-
/**
|
|
182
|
-
* Get the Qwen OAuth login URL
|
|
183
|
-
* This is the simpler approach - open Qwen's login page directly
|
|
184
|
-
* @returns Qwen login URL
|
|
185
|
-
*/
|
|
186
|
-
export function getQwenLoginUrl() {
|
|
187
|
-
return QWEN_OAUTH_LOGIN;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Build manual OAuth flow for Qwen
|
|
191
|
-
* This opens the Google OAuth directly with the Qwen callback
|
|
192
|
-
* @returns Manual OAuth flow configuration
|
|
193
|
-
*/
|
|
194
|
-
export async function buildManualOAuthFlow() {
|
|
195
|
-
const flow = await createAuthorizationFlow();
|
|
196
|
-
return {
|
|
197
|
-
url: flow.url,
|
|
198
|
-
method: "code",
|
|
199
|
-
instructions: ERROR_MESSAGES.TOKEN_NOT_FOUND,
|
|
200
|
-
callback: async (input) => {
|
|
201
|
-
const parsed = parseAuthorizationInput(input);
|
|
202
|
-
if (!parsed.code) {
|
|
203
|
-
return { type: "failed" };
|
|
204
|
-
}
|
|
205
|
-
const tokens = await exchangeAuthorizationCode(parsed.code);
|
|
206
|
-
return tokens?.type === "success" ? tokens : { type: "failed" };
|
|
207
|
-
},
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
24
|
// Re-export browser session functions
|
|
211
25
|
export { extractBrowserSession, isBrowserSessionAvailable, getBrowserSessionInstructions, findFirefoxCookiePath, } from "./browser-session.js";
|
|
212
26
|
//# sourceMappingURL=auth.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../lib/auth/auth.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../lib/auth/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAEpC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACzC,MAAM,aAAa,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAE9E,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACvE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAe,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,sCAAsC;AACtC,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,sBAAsB,CAAC"}
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -10,13 +10,12 @@ export declare const QWEN_BASE_URL = "https://chat.qwen.ai/api";
|
|
|
10
10
|
export declare const DUMMY_API_KEY = "qwen-oauth";
|
|
11
11
|
/** Provider ID for opencode configuration */
|
|
12
12
|
export declare const PROVIDER_ID = "qwen";
|
|
13
|
-
/** OAuth
|
|
14
|
-
export declare const
|
|
15
|
-
|
|
16
|
-
export declare const
|
|
17
|
-
export declare const
|
|
18
|
-
|
|
19
|
-
export declare const GOOGLE_AUTHORIZE_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
13
|
+
/** Qwen OAuth Constants (from @opencode-qwen-auth/) */
|
|
14
|
+
export declare const QWEN_OAUTH_BASE_URL = "https://chat.qwen.ai";
|
|
15
|
+
export declare const QWEN_OAUTH_DEVICE_CODE_ENDPOINT = "https://chat.qwen.ai/api/v1/oauth2/device/code";
|
|
16
|
+
export declare const QWEN_OAUTH_TOKEN_ENDPOINT = "https://chat.qwen.ai/api/v1/oauth2/token";
|
|
17
|
+
export declare const QWEN_OAUTH_CLIENT_ID = "f0304373b74a44d2b584a3fb70ca9e56";
|
|
18
|
+
export declare const QWEN_OAUTH_SCOPE = "openid profile email model.completion";
|
|
20
19
|
/** OAuth redirect URI for local callback */
|
|
21
20
|
export declare const REDIRECT_URI = "http://localhost:1455/auth/callback";
|
|
22
21
|
/** OAuth scope for Google */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../lib/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,uDAAuD;AACvD,eAAO,MAAM,WAAW,uBAAuB,CAAC;AAEhD,yCAAyC;AACzC,eAAO,MAAM,aAAa,6BAA6B,CAAC;AAExD,8DAA8D;AAC9D,eAAO,MAAM,aAAa,eAAe,CAAC;AAE1C,6CAA6C;AAC7C,eAAO,MAAM,WAAW,SAAS,CAAC;AAElC,uDAAuD;AACvD,eAAO,MAAM,
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../lib/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,uDAAuD;AACvD,eAAO,MAAM,WAAW,uBAAuB,CAAC;AAEhD,yCAAyC;AACzC,eAAO,MAAM,aAAa,6BAA6B,CAAC;AAExD,8DAA8D;AAC9D,eAAO,MAAM,aAAa,eAAe,CAAC;AAE1C,6CAA6C;AAC7C,eAAO,MAAM,WAAW,SAAS,CAAC;AAElC,uDAAuD;AACvD,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAC1D,eAAO,MAAM,+BAA+B,mDAAqD,CAAC;AAClG,eAAO,MAAM,yBAAyB,6CAA+C,CAAC;AACtF,eAAO,MAAM,oBAAoB,qCAAqC,CAAC;AACvE,eAAO,MAAM,gBAAgB,0CAA0C,CAAC;AAExE,4CAA4C;AAC5C,eAAO,MAAM,YAAY,wCAAwC,CAAC;AAElE,6BAA6B;AAC7B,eAAO,MAAM,YAAY,yBAAyB,CAAC;AAEnD,wBAAwB;AACxB,eAAO,MAAM,WAAW;;;;;CAKd,CAAC;AAEX,4BAA4B;AAC5B,eAAO,MAAM,YAAY;;;;CAIf,CAAC;AAEX,wBAAwB;AACxB,eAAO,MAAM,SAAS;;;;CAIZ,CAAC;AAEX,qBAAqB;AACrB,eAAO,MAAM,cAAc;;;;;;;;CAQjB,CAAC;AAEX,qCAAqC;AACrC,eAAO,MAAM,UAAU;;;;;CAKb,CAAC;AAEX,gDAAgD;AAChD,eAAO,MAAM,gBAAgB;;;;CAInB,CAAC;AAEX,iCAAiC;AACjC,eAAO,MAAM,WAAW;;;;;;CAQd,CAAC;AAEX,6BAA6B;AAC7B,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAiCxC,CAAC;AAGX,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/lib/constants.js
CHANGED
|
@@ -10,13 +10,12 @@ export const QWEN_BASE_URL = "https://chat.qwen.ai/api";
|
|
|
10
10
|
export const DUMMY_API_KEY = "qwen-oauth";
|
|
11
11
|
/** Provider ID for opencode configuration */
|
|
12
12
|
export const PROVIDER_ID = "qwen";
|
|
13
|
-
/** OAuth
|
|
14
|
-
export const
|
|
15
|
-
|
|
16
|
-
export const
|
|
17
|
-
export const
|
|
18
|
-
|
|
19
|
-
export const GOOGLE_AUTHORIZE_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
13
|
+
/** Qwen OAuth Constants (from @opencode-qwen-auth/) */
|
|
14
|
+
export const QWEN_OAUTH_BASE_URL = "https://chat.qwen.ai";
|
|
15
|
+
export const QWEN_OAUTH_DEVICE_CODE_ENDPOINT = `${QWEN_OAUTH_BASE_URL}/api/v1/oauth2/device/code`;
|
|
16
|
+
export const QWEN_OAUTH_TOKEN_ENDPOINT = `${QWEN_OAUTH_BASE_URL}/api/v1/oauth2/token`;
|
|
17
|
+
export const QWEN_OAUTH_CLIENT_ID = "f0304373b74a44d2b584a3fb70ca9e56";
|
|
18
|
+
export const QWEN_OAUTH_SCOPE = "openid profile email model.completion";
|
|
20
19
|
/** OAuth redirect URI for local callback */
|
|
21
20
|
export const REDIRECT_URI = "http://localhost:1455/auth/callback";
|
|
22
21
|
/** OAuth scope for Google */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../lib/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,uDAAuD;AACvD,MAAM,CAAC,MAAM,WAAW,GAAG,oBAAoB,CAAC;AAEhD,yCAAyC;AACzC,MAAM,CAAC,MAAM,aAAa,GAAG,0BAA0B,CAAC;AAExD,8DAA8D;AAC9D,MAAM,CAAC,MAAM,aAAa,GAAG,YAAY,CAAC;AAE1C,6CAA6C;AAC7C,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC;AAElC,uDAAuD;AACvD,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../lib/constants.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,7 +17,7 @@ export declare function shouldRefreshToken(auth: Auth): boolean;
|
|
|
17
17
|
* @param client - Opencode client for updating stored credentials
|
|
18
18
|
* @returns Updated auth (throws on failure)
|
|
19
19
|
*/
|
|
20
|
-
export declare function refreshAndUpdateToken(currentAuth: Auth,
|
|
20
|
+
export declare function refreshAndUpdateToken(currentAuth: Auth, _client: OpencodeClient): Promise<Auth>;
|
|
21
21
|
/**
|
|
22
22
|
* Extracts URL string from various request input types
|
|
23
23
|
* @param input - Request input (string, URL, or Request object)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-helpers.d.ts","sourceRoot":"","sources":["../../../lib/request/fetch-helpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAKvD,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAW3D;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAEtD;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,IAAI,EACjB,
|
|
1
|
+
{"version":3,"file":"fetch-helpers.d.ts","sourceRoot":"","sources":["../../../lib/request/fetch-helpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAKvD,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAW3D;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAEtD;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,IAAI,EACjB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,GAAG,GAAG,MAAM,CAIvE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAgBrD;AAED;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,WAAW,GAAG,SAAS,EAC7B,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,WAAW,EAAE,WAAW,CAAA;CAAE,GAAG,SAAS,CAAC,CA2CtE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,WAAW,GAAG,SAAS,EAC7B,WAAW,EAAE,MAAM,GAClB,OAAO,CAsBT;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,QAAQ,CAAC,CAkBnB;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,QAAQ,CAAC,CAcnB"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Helper functions for the custom fetch implementation
|
|
3
3
|
* These functions break down the complex fetch logic into manageable, testable units
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { decodeJWT } from "../auth/auth.js";
|
|
6
6
|
import { logRequest, logDebug } from "../logger.js";
|
|
7
7
|
import { transformRequestBody, normalizeModel } from "./request-transformer.js";
|
|
8
8
|
import { convertSseToJson, ensureContentType } from "./response-handler.js";
|
|
@@ -21,29 +21,20 @@ export function shouldRefreshToken(auth) {
|
|
|
21
21
|
* @param client - Opencode client for updating stored credentials
|
|
22
22
|
* @returns Updated auth (throws on failure)
|
|
23
23
|
*/
|
|
24
|
-
export async function refreshAndUpdateToken(currentAuth,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
export async function refreshAndUpdateToken(currentAuth, _client) {
|
|
25
|
+
// Qwen tokens are JWTs - check if still valid
|
|
26
|
+
if (currentAuth.type === "oauth" && currentAuth.access) {
|
|
27
|
+
const decoded = decodeJWT(currentAuth.access);
|
|
28
|
+
if (decoded?.exp && typeof decoded.exp === "number") {
|
|
29
|
+
const expiresAt = decoded.exp * 1000;
|
|
30
|
+
if (expiresAt > Date.now()) {
|
|
31
|
+
// Token still valid, update expires
|
|
32
|
+
currentAuth.expires = expiresAt;
|
|
33
|
+
return currentAuth;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
29
36
|
}
|
|
30
|
-
|
|
31
|
-
await client.auth.set({
|
|
32
|
-
path: { id: "qwen" },
|
|
33
|
-
body: {
|
|
34
|
-
type: "oauth",
|
|
35
|
-
access: refreshResult.access,
|
|
36
|
-
refresh: refreshResult.refresh,
|
|
37
|
-
expires: refreshResult.expires,
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
// Update current auth reference if it's OAuth type
|
|
41
|
-
if (currentAuth.type === "oauth") {
|
|
42
|
-
currentAuth.access = refreshResult.access;
|
|
43
|
-
currentAuth.refresh = refreshResult.refresh;
|
|
44
|
-
currentAuth.expires = refreshResult.expires;
|
|
45
|
-
}
|
|
46
|
-
return currentAuth;
|
|
37
|
+
throw new Error(ERROR_MESSAGES.TOKEN_REFRESH_FAILED);
|
|
47
38
|
}
|
|
48
39
|
/**
|
|
49
40
|
* Extracts URL string from various request input types
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-helpers.js","sourceRoot":"","sources":["../../../lib/request/fetch-helpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"fetch-helpers.js","sourceRoot":"","sources":["../../../lib/request/fetch-helpers.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iamtoricool/opencool-qwen-auth",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0-alpha",
|
|
4
4
|
"description": "Qwen AI OAuth Authentication Plugin for opencode - Access Qwen models via chat.qwen.ai subscription",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|