@oevortex/opencode-qwen-auth 0.1.0 → 0.1.2
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.cjs +1600 -0
- package/dist/index.d.cts +445 -0
- package/dist/index.d.ts +445 -0
- package/dist/index.js +1534 -0
- package/package.json +14 -6
- package/index.ts +0 -82
- package/src/constants.ts +0 -43
- package/src/global.d.ts +0 -257
- package/src/models.ts +0 -148
- package/src/plugin/auth.ts +0 -151
- package/src/plugin/browser.ts +0 -126
- package/src/plugin/fetch-wrapper.ts +0 -460
- package/src/plugin/logger.ts +0 -111
- package/src/plugin/server.ts +0 -364
- package/src/plugin/token.ts +0 -225
- package/src/plugin.ts +0 -444
- package/src/qwen/oauth.ts +0 -271
- package/src/qwen/thinking-parser.ts +0 -190
- package/src/types.ts +0 -292
|
@@ -1,460 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fetch wrapper for Qwen API requests
|
|
3
|
-
* Handles authentication, token refresh, rate limiting, and request transformation
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
QWEN_DEFAULT_BASE_URL,
|
|
8
|
-
QWEN_PORTAL_BASE_URL,
|
|
9
|
-
QWEN_PROVIDER_ID,
|
|
10
|
-
RATE_LIMIT_BACKOFF_BASE_MS,
|
|
11
|
-
RATE_LIMIT_BACKOFF_MAX_MS,
|
|
12
|
-
RATE_LIMIT_MAX_RETRIES,
|
|
13
|
-
HTTP_UNAUTHORIZED,
|
|
14
|
-
} from "../constants";
|
|
15
|
-
|
|
16
|
-
import type {
|
|
17
|
-
OAuthAuthDetails,
|
|
18
|
-
PluginContext,
|
|
19
|
-
GetAuth,
|
|
20
|
-
RateLimitState,
|
|
21
|
-
RateLimitDelay,
|
|
22
|
-
} from "../types";
|
|
23
|
-
|
|
24
|
-
import { isOAuthAuth, accessTokenExpired, parseRefreshParts } from "./auth";
|
|
25
|
-
import { refreshAccessToken } from "./token";
|
|
26
|
-
import { createLogger, printQwenConsole } from "./logger";
|
|
27
|
-
|
|
28
|
-
const log = createLogger("fetch-wrapper");
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Rate limit state tracking per session.
|
|
32
|
-
*/
|
|
33
|
-
const rateLimitState: RateLimitState = {
|
|
34
|
-
consecutive429: 0,
|
|
35
|
-
lastAt: 0,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Compute exponential backoff delay for rate limiting.
|
|
40
|
-
*/
|
|
41
|
-
export function computeExponentialBackoffMs(
|
|
42
|
-
attempt: number,
|
|
43
|
-
baseMs: number = RATE_LIMIT_BACKOFF_BASE_MS,
|
|
44
|
-
maxMs: number = RATE_LIMIT_BACKOFF_MAX_MS
|
|
45
|
-
): number {
|
|
46
|
-
const safeAttempt = Math.max(1, Math.floor(attempt));
|
|
47
|
-
const multiplier = 2 ** (safeAttempt - 1);
|
|
48
|
-
return Math.min(maxMs, Math.max(0, Math.floor(baseMs * multiplier)));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Convert URL or Request to string URL.
|
|
53
|
-
*/
|
|
54
|
-
function toUrlString(value: RequestInfo | URL): string {
|
|
55
|
-
if (value instanceof URL) {
|
|
56
|
-
return value.toString();
|
|
57
|
-
}
|
|
58
|
-
if (typeof value === "string") {
|
|
59
|
-
return value;
|
|
60
|
-
}
|
|
61
|
-
return (value as Request).url ?? value.toString();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Sleep for a specified duration with abort signal support.
|
|
66
|
-
*/
|
|
67
|
-
export function sleep(ms: number, signal?: AbortSignal | null): Promise<void> {
|
|
68
|
-
return new Promise((resolve, reject) => {
|
|
69
|
-
if (signal?.aborted) {
|
|
70
|
-
reject(signal.reason instanceof Error ? signal.reason : new Error("Aborted"));
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const timeout = setTimeout(() => {
|
|
75
|
-
cleanup();
|
|
76
|
-
resolve();
|
|
77
|
-
}, ms);
|
|
78
|
-
|
|
79
|
-
const onAbort = () => {
|
|
80
|
-
cleanup();
|
|
81
|
-
reject(signal?.reason instanceof Error ? signal.reason : new Error("Aborted"));
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const cleanup = () => {
|
|
85
|
-
clearTimeout(timeout);
|
|
86
|
-
signal?.removeEventListener("abort", onAbort);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Sleep with progressive backoff for long waits.
|
|
95
|
-
*/
|
|
96
|
-
export async function sleepWithBackoff(
|
|
97
|
-
totalMs: number,
|
|
98
|
-
signal?: AbortSignal | null
|
|
99
|
-
): Promise<void> {
|
|
100
|
-
const stepsMs = [3000, 5000, 10000, 20000, 30000];
|
|
101
|
-
let remainingMs = Math.max(0, totalMs);
|
|
102
|
-
let stepIndex = 0;
|
|
103
|
-
|
|
104
|
-
while (remainingMs > 0) {
|
|
105
|
-
const stepMs = stepsMs[stepIndex] ?? stepsMs[stepsMs.length - 1] ?? 30000;
|
|
106
|
-
const waitMs = Math.min(remainingMs, stepMs);
|
|
107
|
-
await sleep(waitMs, signal);
|
|
108
|
-
remainingMs -= waitMs;
|
|
109
|
-
stepIndex++;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Format milliseconds as human-readable time string.
|
|
115
|
-
*/
|
|
116
|
-
function formatWaitTimeMs(ms: number): string {
|
|
117
|
-
const totalSeconds = Math.max(1, Math.ceil(ms / 1000));
|
|
118
|
-
const hours = Math.floor(totalSeconds / 3600);
|
|
119
|
-
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
120
|
-
const seconds = totalSeconds % 60;
|
|
121
|
-
|
|
122
|
-
if (hours > 0) {
|
|
123
|
-
return minutes > 0 ? `${hours}h${minutes}m` : `${hours}h`;
|
|
124
|
-
}
|
|
125
|
-
if (minutes > 0) {
|
|
126
|
-
return seconds > 0 ? `${minutes}m${seconds}s` : `${minutes}m`;
|
|
127
|
-
}
|
|
128
|
-
return `${seconds}s`;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Check if a URL is a Qwen API request.
|
|
133
|
-
*/
|
|
134
|
-
function isQwenApiRequest(input: RequestInfo | URL): boolean {
|
|
135
|
-
const urlString = toUrlString(input);
|
|
136
|
-
return (
|
|
137
|
-
urlString.includes("dashscope.aliyuncs.com") ||
|
|
138
|
-
urlString.includes("portal.qwen.ai") ||
|
|
139
|
-
urlString.includes("chat.qwen.ai") ||
|
|
140
|
-
urlString.includes("/v1/chat/completions") ||
|
|
141
|
-
urlString.includes("/compatible-mode/v1")
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Get the base URL for Qwen API requests.
|
|
147
|
-
* Uses OAuth resource URL if available, otherwise default.
|
|
148
|
-
*/
|
|
149
|
-
function getBaseUrl(auth: OAuthAuthDetails): string {
|
|
150
|
-
const parts = parseRefreshParts(auth.refresh);
|
|
151
|
-
let baseUrl = parts.resourceUrl || QWEN_PORTAL_BASE_URL;
|
|
152
|
-
|
|
153
|
-
if (!baseUrl.startsWith("http://") && !baseUrl.startsWith("https://")) {
|
|
154
|
-
baseUrl = `https://${baseUrl}`;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
baseUrl = baseUrl.replace(/\/+$/, "");
|
|
158
|
-
if (!baseUrl.endsWith("/v1")) {
|
|
159
|
-
baseUrl = `${baseUrl}/v1`;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return baseUrl;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Parse Retry-After header from response.
|
|
167
|
-
*/
|
|
168
|
-
function parseRetryAfterMs(response: Response): number | null {
|
|
169
|
-
const retryAfterMsHeader = response.headers.get("retry-after-ms");
|
|
170
|
-
const retryAfterSecondsHeader = response.headers.get("retry-after");
|
|
171
|
-
|
|
172
|
-
if (retryAfterMsHeader) {
|
|
173
|
-
const parsed = parseInt(retryAfterMsHeader, 10);
|
|
174
|
-
if (!Number.isNaN(parsed) && parsed >= 0) {
|
|
175
|
-
return Math.min(parsed, RATE_LIMIT_BACKOFF_MAX_MS);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (retryAfterSecondsHeader) {
|
|
180
|
-
const parsed = parseInt(retryAfterSecondsHeader, 10);
|
|
181
|
-
if (!Number.isNaN(parsed) && parsed >= 0) {
|
|
182
|
-
return Math.min(parsed * 1000, RATE_LIMIT_BACKOFF_MAX_MS);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Get rate limit delay information.
|
|
191
|
-
*/
|
|
192
|
-
function getRateLimitDelay(serverRetryAfterMs: number | null): RateLimitDelay {
|
|
193
|
-
const now = Date.now();
|
|
194
|
-
const attempt = rateLimitState.consecutive429 + 1;
|
|
195
|
-
const backoffMs = computeExponentialBackoffMs(attempt);
|
|
196
|
-
const delayMs = serverRetryAfterMs !== null ? Math.max(serverRetryAfterMs, backoffMs) : backoffMs;
|
|
197
|
-
|
|
198
|
-
rateLimitState.consecutive429 = attempt;
|
|
199
|
-
rateLimitState.lastAt = now;
|
|
200
|
-
|
|
201
|
-
return { attempt, serverRetryAfterMs, delayMs };
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Reset rate limit state after successful request.
|
|
206
|
-
*/
|
|
207
|
-
function resetRateLimitState(): void {
|
|
208
|
-
rateLimitState.consecutive429 = 0;
|
|
209
|
-
rateLimitState.lastAt = 0;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Prepare headers for Qwen API request.
|
|
214
|
-
*/
|
|
215
|
-
function prepareHeaders(
|
|
216
|
-
init: RequestInit | undefined,
|
|
217
|
-
accessToken: string
|
|
218
|
-
): Headers {
|
|
219
|
-
const headers = new Headers(init?.headers);
|
|
220
|
-
|
|
221
|
-
// Set authorization
|
|
222
|
-
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
223
|
-
|
|
224
|
-
// Ensure content type is set
|
|
225
|
-
if (!headers.has("Content-Type")) {
|
|
226
|
-
headers.set("Content-Type", "application/json");
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Set accept header
|
|
230
|
-
if (!headers.has("Accept")) {
|
|
231
|
-
headers.set("Accept", "application/json");
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return headers;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Transform request URL to use correct Qwen base URL.
|
|
239
|
-
*/
|
|
240
|
-
function transformRequestUrl(input: RequestInfo | URL, baseUrl: string): string {
|
|
241
|
-
const urlString = toUrlString(input);
|
|
242
|
-
|
|
243
|
-
// If it's already a full URL with a recognized host, replace the base
|
|
244
|
-
if (urlString.includes("generativelanguage.googleapis.com")) {
|
|
245
|
-
// This is a Google API URL, transform to Qwen
|
|
246
|
-
const path = urlString.split("/v1")[1] || "/chat/completions";
|
|
247
|
-
return `${baseUrl}${path}`;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// If it's a relative path, prepend base URL
|
|
251
|
-
if (urlString.startsWith("/")) {
|
|
252
|
-
return `${baseUrl}${urlString}`;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// If URL already points to Qwen, use as-is
|
|
256
|
-
if (isQwenApiRequest(urlString)) {
|
|
257
|
-
return urlString;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Default: assume it's a chat completions request
|
|
261
|
-
if (!urlString.includes("/chat/completions")) {
|
|
262
|
-
return `${baseUrl}/chat/completions`;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return urlString;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Create a fetch wrapper that handles Qwen API authentication.
|
|
270
|
-
*
|
|
271
|
-
* This wrapper:
|
|
272
|
-
* - Automatically refreshes tokens when expired
|
|
273
|
-
* - Handles rate limiting with exponential backoff
|
|
274
|
-
* - Transforms requests for Qwen API compatibility
|
|
275
|
-
* - Retries on auth failures
|
|
276
|
-
*/
|
|
277
|
-
export function createQwenFetch(
|
|
278
|
-
getAuth: GetAuth,
|
|
279
|
-
client: PluginContext["client"]
|
|
280
|
-
): (input: RequestInfo | URL, init?: RequestInit) => Promise<Response> {
|
|
281
|
-
return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
282
|
-
// Get current auth
|
|
283
|
-
const auth = await getAuth();
|
|
284
|
-
|
|
285
|
-
// If not OAuth auth, pass through to normal fetch
|
|
286
|
-
if (!isOAuthAuth(auth)) {
|
|
287
|
-
return fetch(input, init);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Check if this is a Qwen API request
|
|
291
|
-
const urlString = toUrlString(input);
|
|
292
|
-
if (!isQwenApiRequest(urlString) && !urlString.includes("googleapis.com")) {
|
|
293
|
-
return fetch(input, init);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Ensure we have a valid access token
|
|
297
|
-
let currentAuth = auth;
|
|
298
|
-
if (accessTokenExpired(currentAuth)) {
|
|
299
|
-
log.debug("Access token expired, refreshing...");
|
|
300
|
-
const refreshed = await refreshAccessToken(currentAuth, client);
|
|
301
|
-
if (!refreshed) {
|
|
302
|
-
throw new Error("Failed to refresh access token");
|
|
303
|
-
}
|
|
304
|
-
currentAuth = refreshed;
|
|
305
|
-
|
|
306
|
-
// Save refreshed auth
|
|
307
|
-
try {
|
|
308
|
-
await client.auth.set({
|
|
309
|
-
path: { id: QWEN_PROVIDER_ID },
|
|
310
|
-
body: {
|
|
311
|
-
type: "oauth",
|
|
312
|
-
access: refreshed.access,
|
|
313
|
-
refresh: refreshed.refresh,
|
|
314
|
-
expires: refreshed.expires,
|
|
315
|
-
},
|
|
316
|
-
});
|
|
317
|
-
} catch (saveError) {
|
|
318
|
-
log.warn("Failed to save refreshed auth", {
|
|
319
|
-
error: saveError instanceof Error ? saveError.message : String(saveError),
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const accessToken = currentAuth.access;
|
|
325
|
-
if (!accessToken) {
|
|
326
|
-
throw new Error("No access token available");
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Get base URL and prepare request
|
|
330
|
-
const baseUrl = getBaseUrl(currentAuth);
|
|
331
|
-
const requestUrl = transformRequestUrl(input, baseUrl);
|
|
332
|
-
const headers = prepareHeaders(init, accessToken);
|
|
333
|
-
|
|
334
|
-
const abortSignal = init?.signal;
|
|
335
|
-
|
|
336
|
-
// Retry loop for rate limiting
|
|
337
|
-
let retryCount = 0;
|
|
338
|
-
while (retryCount <= RATE_LIMIT_MAX_RETRIES) {
|
|
339
|
-
try {
|
|
340
|
-
log.debug("Making Qwen API request", {
|
|
341
|
-
url: requestUrl,
|
|
342
|
-
method: init?.method || "GET",
|
|
343
|
-
retry: retryCount,
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
const response = await fetch(requestUrl, {
|
|
347
|
-
...init,
|
|
348
|
-
headers,
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
// Handle 401 Unauthorized - refresh token and retry once
|
|
352
|
-
if (response.status === HTTP_UNAUTHORIZED && retryCount === 0) {
|
|
353
|
-
log.info("Received 401, refreshing token and retrying...");
|
|
354
|
-
|
|
355
|
-
const refreshed = await refreshAccessToken(currentAuth, client);
|
|
356
|
-
if (!refreshed) {
|
|
357
|
-
return response; // Return 401 response if refresh fails
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
currentAuth = refreshed;
|
|
361
|
-
headers.set("Authorization", `Bearer ${refreshed.access}`);
|
|
362
|
-
|
|
363
|
-
// Save refreshed auth
|
|
364
|
-
try {
|
|
365
|
-
await client.auth.set({
|
|
366
|
-
path: { id: QWEN_PROVIDER_ID },
|
|
367
|
-
body: {
|
|
368
|
-
type: "oauth",
|
|
369
|
-
access: refreshed.access,
|
|
370
|
-
refresh: refreshed.refresh,
|
|
371
|
-
expires: refreshed.expires,
|
|
372
|
-
},
|
|
373
|
-
});
|
|
374
|
-
} catch {}
|
|
375
|
-
|
|
376
|
-
retryCount++;
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Handle 429 Rate Limit
|
|
381
|
-
if (response.status === 429) {
|
|
382
|
-
const serverRetryAfterMs = parseRetryAfterMs(response);
|
|
383
|
-
const { attempt, delayMs } = getRateLimitDelay(serverRetryAfterMs);
|
|
384
|
-
|
|
385
|
-
if (attempt > RATE_LIMIT_MAX_RETRIES) {
|
|
386
|
-
log.warn("Max rate limit retries exceeded", { attempt });
|
|
387
|
-
return response;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
printQwenConsole(
|
|
391
|
-
"error",
|
|
392
|
-
`Rate limited (429). Retrying after ${formatWaitTimeMs(delayMs)} (attempt ${attempt})...`
|
|
393
|
-
);
|
|
394
|
-
|
|
395
|
-
await client.tui.showToast({
|
|
396
|
-
body: {
|
|
397
|
-
message: `Rate limited. Retrying in ${formatWaitTimeMs(delayMs)}...`,
|
|
398
|
-
variant: "warning",
|
|
399
|
-
},
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
await sleepWithBackoff(delayMs, abortSignal);
|
|
403
|
-
retryCount++;
|
|
404
|
-
continue;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// Handle 5xx server errors with retry
|
|
408
|
-
if (response.status >= 500 && retryCount < RATE_LIMIT_MAX_RETRIES) {
|
|
409
|
-
log.warn("Server error, retrying...", { status: response.status });
|
|
410
|
-
const delayMs = computeExponentialBackoffMs(retryCount + 1);
|
|
411
|
-
await sleep(delayMs, abortSignal);
|
|
412
|
-
retryCount++;
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Success - reset rate limit state
|
|
417
|
-
if (response.ok) {
|
|
418
|
-
resetRateLimitState();
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
return response;
|
|
422
|
-
} catch (error) {
|
|
423
|
-
// Network errors - retry with backoff
|
|
424
|
-
if (retryCount < RATE_LIMIT_MAX_RETRIES) {
|
|
425
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
426
|
-
log.warn("Request failed, retrying...", { error: errorMessage, retry: retryCount });
|
|
427
|
-
|
|
428
|
-
const delayMs = computeExponentialBackoffMs(retryCount + 1);
|
|
429
|
-
await sleep(delayMs, abortSignal);
|
|
430
|
-
retryCount++;
|
|
431
|
-
continue;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
throw error;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// Should not reach here, but just in case
|
|
439
|
-
throw new Error("Max retries exceeded");
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Create a simple fetch wrapper that just adds auth headers.
|
|
445
|
-
* Use this for non-retrying scenarios.
|
|
446
|
-
*/
|
|
447
|
-
export function createSimpleQwenFetch(
|
|
448
|
-
accessToken: string,
|
|
449
|
-
baseUrl: string = QWEN_DEFAULT_BASE_URL
|
|
450
|
-
): (input: RequestInfo | URL, init?: RequestInit) => Promise<Response> {
|
|
451
|
-
return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
452
|
-
const requestUrl = transformRequestUrl(input, baseUrl);
|
|
453
|
-
const headers = prepareHeaders(init, accessToken);
|
|
454
|
-
|
|
455
|
-
return fetch(requestUrl, {
|
|
456
|
-
...init,
|
|
457
|
-
headers,
|
|
458
|
-
});
|
|
459
|
-
};
|
|
460
|
-
}
|
package/src/plugin/logger.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Logger utility for the Qwen OpenCode plugin
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { ENV_CONSOLE_LOG } from "../constants";
|
|
6
|
-
import type { PluginContext } from "../types";
|
|
7
|
-
|
|
8
|
-
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
9
|
-
|
|
10
|
-
interface Logger {
|
|
11
|
-
debug: (message: string, meta?: Record<string, unknown>) => void;
|
|
12
|
-
info: (message: string, meta?: Record<string, unknown>) => void;
|
|
13
|
-
warn: (message: string, meta?: Record<string, unknown>) => void;
|
|
14
|
-
error: (message: string, meta?: Record<string, unknown>) => void;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
let pluginClient: PluginContext["client"] | null = null;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Initialize the logger with the plugin client.
|
|
21
|
-
* Should be called once when the plugin loads.
|
|
22
|
-
*/
|
|
23
|
-
export function initLogger(client: PluginContext["client"]): void {
|
|
24
|
-
pluginClient = client;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Check if console logging is enabled via environment variable.
|
|
29
|
-
*/
|
|
30
|
-
function isConsoleLogEnabled(): boolean {
|
|
31
|
-
const value = process.env[ENV_CONSOLE_LOG];
|
|
32
|
-
return value === "1" || value === "true";
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Format a log message with optional metadata.
|
|
37
|
-
*/
|
|
38
|
-
function formatMessage(prefix: string, message: string, meta?: Record<string, unknown>): string {
|
|
39
|
-
const metaStr = meta ? ` ${JSON.stringify(meta)}` : "";
|
|
40
|
-
return `[${prefix}] ${message}${metaStr}`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Print to console if console logging is enabled.
|
|
45
|
-
*/
|
|
46
|
-
export function printQwenConsole(level: LogLevel, message: string, meta?: Record<string, unknown>): void {
|
|
47
|
-
if (!isConsoleLogEnabled()) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const formattedMessage = formatMessage("qwen-auth", message, meta);
|
|
52
|
-
|
|
53
|
-
switch (level) {
|
|
54
|
-
case "debug":
|
|
55
|
-
console.debug(formattedMessage);
|
|
56
|
-
break;
|
|
57
|
-
case "info":
|
|
58
|
-
console.info(formattedMessage);
|
|
59
|
-
break;
|
|
60
|
-
case "warn":
|
|
61
|
-
console.warn(formattedMessage);
|
|
62
|
-
break;
|
|
63
|
-
case "error":
|
|
64
|
-
console.error(formattedMessage);
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Create a logger instance with a specific prefix.
|
|
71
|
-
* Uses the plugin client's log methods if available, falls back to console.
|
|
72
|
-
*/
|
|
73
|
-
export function createLogger(prefix: string): Logger {
|
|
74
|
-
const log = (level: LogLevel, message: string, meta?: Record<string, unknown>): void => {
|
|
75
|
-
// Always print to console if enabled
|
|
76
|
-
printQwenConsole(level, `[${prefix}] ${message}`, meta);
|
|
77
|
-
|
|
78
|
-
// Use plugin client logger if available
|
|
79
|
-
if (pluginClient?.log) {
|
|
80
|
-
const clientLog = pluginClient.log;
|
|
81
|
-
const formattedMessage = `[${prefix}] ${message}`;
|
|
82
|
-
|
|
83
|
-
switch (level) {
|
|
84
|
-
case "debug":
|
|
85
|
-
clientLog.debug(formattedMessage, meta);
|
|
86
|
-
break;
|
|
87
|
-
case "info":
|
|
88
|
-
clientLog.info(formattedMessage, meta);
|
|
89
|
-
break;
|
|
90
|
-
case "warn":
|
|
91
|
-
clientLog.warn(formattedMessage, meta);
|
|
92
|
-
break;
|
|
93
|
-
case "error":
|
|
94
|
-
clientLog.error(formattedMessage, meta);
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
debug: (message: string, meta?: Record<string, unknown>) => log("debug", message, meta),
|
|
102
|
-
info: (message: string, meta?: Record<string, unknown>) => log("info", message, meta),
|
|
103
|
-
warn: (message: string, meta?: Record<string, unknown>) => log("warn", message, meta),
|
|
104
|
-
error: (message: string, meta?: Record<string, unknown>) => log("error", message, meta),
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Default logger instance for general use.
|
|
110
|
-
*/
|
|
111
|
-
export const log = createLogger("qwen");
|