@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.
@@ -0,0 +1,445 @@
1
+ /**
2
+ * Qwen OAuth and plugin types
3
+ * Based on revibe/core/llm/backend/qwen implementation
4
+ */
5
+ interface QwenOAuthCredentials {
6
+ access_token: string;
7
+ refresh_token: string;
8
+ token_type: string;
9
+ expiry_date: number;
10
+ resource_url?: string;
11
+ }
12
+ interface QwenTokenResponse {
13
+ access_token: string;
14
+ refresh_token?: string;
15
+ token_type: string;
16
+ expires_in: number;
17
+ error?: string;
18
+ error_description?: string;
19
+ }
20
+ interface QwenModelInfo {
21
+ id: string;
22
+ name: string;
23
+ context_window?: number;
24
+ max_output?: number;
25
+ input_price?: number;
26
+ output_price?: number;
27
+ supports_native_tools?: boolean;
28
+ supports_thinking?: boolean;
29
+ }
30
+ interface ChatMessage {
31
+ role: "system" | "user" | "assistant" | "tool";
32
+ content?: string;
33
+ reasoning_content?: string;
34
+ tool_calls?: ToolCall[];
35
+ tool_call_id?: string;
36
+ name?: string;
37
+ }
38
+ interface ToolCall {
39
+ id?: string;
40
+ index?: number;
41
+ type?: "function";
42
+ function: FunctionCall;
43
+ }
44
+ interface FunctionCall {
45
+ name?: string;
46
+ arguments?: string;
47
+ }
48
+ interface Tool {
49
+ type: "function";
50
+ function: {
51
+ name: string;
52
+ description?: string;
53
+ parameters?: Record<string, unknown>;
54
+ };
55
+ }
56
+ type ToolChoice = "none" | "auto" | "required" | {
57
+ type: "function";
58
+ function: {
59
+ name: string;
60
+ };
61
+ };
62
+ interface ChatCompletionRequest {
63
+ model: string;
64
+ messages: ChatMessage[];
65
+ temperature?: number;
66
+ max_tokens?: number;
67
+ tools?: Tool[];
68
+ tool_choice?: ToolChoice;
69
+ stream?: boolean;
70
+ stream_options?: {
71
+ include_usage?: boolean;
72
+ };
73
+ }
74
+ interface ChatCompletionChoice {
75
+ index: number;
76
+ message?: ChatMessage;
77
+ delta?: Partial<ChatMessage>;
78
+ finish_reason?: string | null;
79
+ }
80
+ interface ChatCompletionUsage {
81
+ prompt_tokens: number;
82
+ completion_tokens: number;
83
+ total_tokens?: number;
84
+ }
85
+ interface ChatCompletionResponse {
86
+ id: string;
87
+ object: string;
88
+ created: number;
89
+ model: string;
90
+ choices: ChatCompletionChoice[];
91
+ usage?: ChatCompletionUsage;
92
+ error?: {
93
+ message: string;
94
+ type?: string;
95
+ code?: string;
96
+ };
97
+ }
98
+ interface AuthDetails {
99
+ type: "oauth" | "api";
100
+ }
101
+ interface OAuthAuthDetails extends AuthDetails {
102
+ type: "oauth";
103
+ access?: string;
104
+ refresh: string;
105
+ expires?: number;
106
+ }
107
+ interface RefreshParts {
108
+ refreshToken: string;
109
+ resourceUrl?: string;
110
+ }
111
+ interface LoaderResult {
112
+ apiKey?: string;
113
+ baseUrl?: string;
114
+ fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
115
+ }
116
+ interface Provider {
117
+ models?: Record<string, any>;
118
+ }
119
+ type GetAuth = () => Promise<AuthDetails>;
120
+ interface PluginContext {
121
+ client: {
122
+ auth: {
123
+ set: (options: {
124
+ path: {
125
+ id: string;
126
+ };
127
+ body: Record<string, unknown>;
128
+ }) => Promise<void>;
129
+ };
130
+ tui: {
131
+ showToast: (options: {
132
+ body: {
133
+ message: string;
134
+ variant: "info" | "success" | "warning" | "error";
135
+ };
136
+ }) => Promise<void>;
137
+ };
138
+ log?: {
139
+ debug: (message: string, meta?: Record<string, unknown>) => void;
140
+ info: (message: string, meta?: Record<string, unknown>) => void;
141
+ warn: (message: string, meta?: Record<string, unknown>) => void;
142
+ error: (message: string, meta?: Record<string, unknown>) => void;
143
+ };
144
+ };
145
+ }
146
+ interface AuthorizeResult {
147
+ url: string;
148
+ instructions?: string;
149
+ method: "auto" | "manual";
150
+ callback: () => Promise<TokenExchangeResult>;
151
+ }
152
+ interface TokenExchangeResult {
153
+ type: "success" | "failed";
154
+ refresh?: string;
155
+ access?: string;
156
+ expires?: number;
157
+ error?: string;
158
+ resourceUrl?: string;
159
+ }
160
+ interface AuthMethod {
161
+ label: string;
162
+ type: "oauth" | "api";
163
+ authorize?: () => Promise<AuthorizeResult>;
164
+ prompts?: Array<{
165
+ type: "text" | "password";
166
+ message: string;
167
+ key: string;
168
+ }>;
169
+ }
170
+ interface PluginResult {
171
+ auth?: {
172
+ provider: string;
173
+ loader: (getAuth: GetAuth, provider: Provider) => Promise<LoaderResult | Record<string, unknown>>;
174
+ methods: AuthMethod[];
175
+ };
176
+ tool?: Record<string, unknown>;
177
+ }
178
+
179
+ /**
180
+ * Main Qwen OAuth Plugin for OpenCode
181
+ * Provides authentication with Qwen/Alibaba Cloud DashScope API
182
+ */
183
+
184
+ /**
185
+ * Main Qwen OAuth Plugin export.
186
+ *
187
+ * This plugin provides:
188
+ * - OAuth authentication with Qwen/Alibaba Cloud
189
+ * - Automatic token refresh
190
+ * - Request handling with proper authentication headers
191
+ */
192
+ declare function QwenOAuthPlugin({ client, }: PluginContext): Promise<PluginResult>;
193
+
194
+ /**
195
+ * Qwen OAuth Manager
196
+ * Handles OAuth token management for Qwen Code API
197
+ * Based on revibe/core/llm/backend/qwen/oauth.py
198
+ */
199
+
200
+ /**
201
+ * Get the path to the cached OAuth credentials file.
202
+ */
203
+ declare function getQwenCachedCredentialPath(customPath?: string): string;
204
+ /**
205
+ * Manages OAuth authentication for Qwen Code API.
206
+ */
207
+ declare class QwenOAuthManager {
208
+ private oauthPath?;
209
+ private credentials;
210
+ private refreshLock;
211
+ constructor(oauthPath?: string);
212
+ /**
213
+ * Load OAuth credentials from the cached file.
214
+ */
215
+ private loadCachedCredentials;
216
+ /**
217
+ * Refresh the OAuth access token.
218
+ */
219
+ refreshAccessToken(credentials: QwenOAuthCredentials): Promise<QwenOAuthCredentials>;
220
+ /**
221
+ * Save credentials to the cache file.
222
+ */
223
+ private saveCredentials;
224
+ /**
225
+ * Check if the access token is still valid.
226
+ */
227
+ private isTokenValid;
228
+ /**
229
+ * Invalidate cached credentials to force a refresh on next request.
230
+ */
231
+ invalidateCredentials(): void;
232
+ /**
233
+ * Ensure we have valid authentication credentials.
234
+ * @returns Tuple of [access_token, base_url]
235
+ */
236
+ ensureAuthenticated(forceRefresh?: boolean): Promise<[string, string]>;
237
+ /**
238
+ * Get the API base URL from credentials.
239
+ */
240
+ private getBaseUrl;
241
+ /**
242
+ * Get the current credentials, refreshing if needed.
243
+ */
244
+ getCredentials(): Promise<QwenOAuthCredentials>;
245
+ }
246
+ /**
247
+ * Check if OAuth credentials exist.
248
+ */
249
+ declare function hasQwenCredentials(customPath?: string): boolean;
250
+
251
+ /**
252
+ * Thinking Block Parser for Qwen
253
+ * Handles <think>...</think> tags in the stream to separate
254
+ * reasoning content from regular text content.
255
+ *
256
+ * Based on revibe/core/llm/backend/qwen/handler.py
257
+ */
258
+ interface ParseResult {
259
+ /** Regular content (outside thinking blocks) */
260
+ regularContent: string;
261
+ /** Reasoning/thinking content (inside thinking blocks) */
262
+ reasoningContent: string;
263
+ }
264
+ /**
265
+ * Parser for Qwen's thinking/reasoning blocks.
266
+ *
267
+ * Handles `<think>...</think>` tags in the stream to separate
268
+ * reasoning content from regular text content.
269
+ *
270
+ * This parser is stateful and designed for streaming scenarios
271
+ * where tags might be split across multiple chunks.
272
+ */
273
+ declare class ThinkingBlockParser {
274
+ private inThinkingBlock;
275
+ private buffer;
276
+ /**
277
+ * Reset the parser state.
278
+ * Call this when starting a new message/response.
279
+ */
280
+ reset(): void;
281
+ /**
282
+ * Check if currently inside a thinking block.
283
+ */
284
+ isInThinkingBlock(): boolean;
285
+ /**
286
+ * Parse text and separate thinking content from regular content.
287
+ *
288
+ * This method is designed for streaming - call it with each chunk
289
+ * of text as it arrives, and it will return the parsed content.
290
+ *
291
+ * @param text - The text chunk to parse
292
+ * @returns Object containing regular and reasoning content
293
+ */
294
+ parse(text: string): ParseResult;
295
+ /**
296
+ * Find if there's a partial opening tag at the end of the buffer.
297
+ * Returns the length of the partial tag, or 0 if none found.
298
+ */
299
+ private findPartialOpenTag;
300
+ /**
301
+ * Flush any remaining content in the buffer.
302
+ * Call this when the stream is complete.
303
+ *
304
+ * @returns Any remaining content based on current state
305
+ */
306
+ flush(): ParseResult;
307
+ }
308
+ /**
309
+ * Parse complete text for thinking blocks (non-streaming).
310
+ *
311
+ * Use this for non-streaming responses where the entire text is available.
312
+ *
313
+ * @param text - The complete text to parse
314
+ * @returns Object containing regular and reasoning content
315
+ */
316
+ declare function parseThinkingBlocks(text: string): ParseResult;
317
+ /**
318
+ * Remove thinking blocks from text, returning only regular content.
319
+ *
320
+ * @param text - The text to process
321
+ * @returns Text with thinking blocks removed
322
+ */
323
+ declare function stripThinkingBlocks(text: string): string;
324
+ /**
325
+ * Extract only thinking/reasoning content from text.
326
+ *
327
+ * @param text - The text to process
328
+ * @returns Only the content inside thinking blocks
329
+ */
330
+ declare function extractThinkingContent(text: string): string;
331
+ /**
332
+ * Check if text contains any thinking blocks.
333
+ *
334
+ * @param text - The text to check
335
+ * @returns True if text contains <think> tags
336
+ */
337
+ declare function hasThinkingBlocks(text: string): boolean;
338
+
339
+ /**
340
+ * Auth utilities for the Qwen OpenCode plugin
341
+ * Based on opencode-google-antigravity-auth/src/plugin/auth.ts
342
+ */
343
+
344
+ /**
345
+ * Type guard to check if auth details are OAuth type.
346
+ */
347
+ declare function isOAuthAuth(auth: AuthDetails): auth is OAuthAuthDetails;
348
+ /**
349
+ * Parse refresh token string into its constituent parts.
350
+ * Format: "refreshToken|resourceUrl"
351
+ */
352
+ declare function parseRefreshParts(refresh: string): RefreshParts;
353
+ /**
354
+ * Serialize refresh parts back into stored string format.
355
+ */
356
+ declare function formatRefreshParts(parts: RefreshParts): string;
357
+ /**
358
+ * Check if an access token is expired or missing.
359
+ * Includes a buffer for clock skew to trigger refresh slightly early.
360
+ */
361
+ declare function accessTokenExpired(auth: OAuthAuthDetails): boolean;
362
+ /**
363
+ * Create auth details object for storage.
364
+ */
365
+ declare function createAuthDetails(accessToken: string, refreshToken: string, expiresAt: number, resourceUrl?: string): OAuthAuthDetails;
366
+
367
+ /**
368
+ * Token refresh utilities for the Qwen OpenCode plugin
369
+ * Handles refreshing OAuth access tokens when they expire
370
+ */
371
+
372
+ /**
373
+ * Refresh an OAuth access token using the refresh token.
374
+ *
375
+ * @param auth - Current OAuth auth details with refresh token
376
+ * @param client - Plugin client for showing toasts
377
+ * @returns Updated auth details with new access token, or null on failure
378
+ */
379
+ declare function refreshAccessToken(auth: OAuthAuthDetails, client: PluginContext["client"]): Promise<OAuthAuthDetails | null>;
380
+ /**
381
+ * Check if an access token needs refresh (expired or about to expire).
382
+ */
383
+ declare function needsTokenRefresh(auth: OAuthAuthDetails): boolean;
384
+ /**
385
+ * Ensure we have a valid access token, refreshing if necessary.
386
+ *
387
+ * @param auth - Current auth details
388
+ * @param client - Plugin client for showing toasts
389
+ * @returns Updated auth details, or null if refresh failed
390
+ */
391
+ declare function ensureValidToken(auth: OAuthAuthDetails, client: PluginContext["client"]): Promise<OAuthAuthDetails | null>;
392
+
393
+ /**
394
+ * Qwen Code Models Configuration
395
+ * Defines available models and their specifications for the OpenCode plugin
396
+ */
397
+
398
+ /**
399
+ * Available Qwen Code models accessible through OAuth authentication.
400
+ * All models are free when using OAuth.
401
+ */
402
+ declare const QWEN_CODE_MODELS: Record<string, QwenModelInfo>;
403
+ /**
404
+ * Model aliases mapping friendly names to actual model IDs.
405
+ */
406
+ declare const MODEL_ALIASES: Record<string, string>;
407
+ /**
408
+ * Default model to use when none specified.
409
+ */
410
+ declare const DEFAULT_MODEL = "qwen3-coder-plus";
411
+ /**
412
+ * Get model info by ID or alias.
413
+ *
414
+ * @param modelIdOrAlias - Model ID or alias name
415
+ * @returns Model info or undefined if not found
416
+ */
417
+ declare function getModelInfo(modelIdOrAlias: string): QwenModelInfo | undefined;
418
+ /**
419
+ * Resolve model alias to actual model ID.
420
+ *
421
+ * @param modelIdOrAlias - Model ID or alias name
422
+ * @returns Resolved model ID or the original string if not found
423
+ */
424
+ declare function resolveModelId(modelIdOrAlias: string): string;
425
+ /**
426
+ * Get all available model IDs (including aliases).
427
+ */
428
+ declare function getAllModelIds(): string[];
429
+ /**
430
+ * Check if a model ID or alias is valid.
431
+ */
432
+ declare function isValidModel(modelIdOrAlias: string): boolean;
433
+ /**
434
+ * Generate OpenCode provider configuration for Qwen Code models.
435
+ * Use this to configure models in opencode.json.
436
+ */
437
+ declare function generateProviderConfig(): Record<string, unknown>;
438
+
439
+ declare const QWEN_OAUTH_BASE_URL = "https://chat.qwen.ai";
440
+ declare const QWEN_DEFAULT_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1";
441
+ declare const QWEN_INTL_BASE_URL = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
442
+ declare const QWEN_PORTAL_BASE_URL = "https://portal.qwen.ai/v1";
443
+ declare const QWEN_PROVIDER_ID = "qwencode";
444
+
445
+ export { type AuthDetails, type ChatCompletionRequest, type ChatCompletionResponse, type ChatMessage, DEFAULT_MODEL, MODEL_ALIASES, type OAuthAuthDetails, type PluginContext, type PluginResult, QWEN_CODE_MODELS, QWEN_DEFAULT_BASE_URL, QWEN_INTL_BASE_URL, QWEN_OAUTH_BASE_URL, QWEN_PORTAL_BASE_URL, QWEN_PROVIDER_ID, type QwenModelInfo, type QwenOAuthCredentials, QwenOAuthManager, QwenOAuthPlugin, type QwenTokenResponse, ThinkingBlockParser, type TokenExchangeResult, type Tool, type ToolCall, type ToolChoice, accessTokenExpired, createAuthDetails, QwenOAuthPlugin as default, ensureValidToken, extractThinkingContent, formatRefreshParts, generateProviderConfig, getAllModelIds, getModelInfo, getQwenCachedCredentialPath, hasQwenCredentials, hasThinkingBlocks, isOAuthAuth, isValidModel, needsTokenRefresh, parseRefreshParts, parseThinkingBlocks, refreshAccessToken, resolveModelId, stripThinkingBlocks };