opencode-qwen 1.0.0 → 1.0.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/fix_summary.md +28 -0
- package/index.ts +3 -4
- package/package.json +3 -3
- package/qwen-auth.ts +22 -6
- package/qwen-auth.ts.backup +337 -0
- package/test_fix.md +69 -0
- package/opencode.json +0 -41
package/fix_summary.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
## Qwen Plugin Import Fix Summary
|
|
2
|
+
|
|
3
|
+
**Problem Identified:**
|
|
4
|
+
The plugin was failing to load because it was trying to import `createOpenAI` from the non-existent package `@ai-sdk/openai-compatible`, but this function actually exists in `@ai-sdk/openai`.
|
|
5
|
+
|
|
6
|
+
**Root Cause:**
|
|
7
|
+
- Package `@ai-sdk/openai-compatible` doesn't exist in the OpenCode environment
|
|
8
|
+
- Function `createOpenAI` is available in `@ai-sdk/openai` package
|
|
9
|
+
- Multiple files had incorrect import statements
|
|
10
|
+
|
|
11
|
+
**Fixes Applied:**
|
|
12
|
+
1. Fixed import in `/root/workspace/opencode-qwen/index.ts`:
|
|
13
|
+
- Changed: `import { createOpenAI } from '@ai-sdk/openai-compatible'`
|
|
14
|
+
- To: `import { createOpenAI } from '@ai-sdk/openai'`
|
|
15
|
+
|
|
16
|
+
2. Fixed dependency in `/root/workspace/opencode-qwen/package.json`:
|
|
17
|
+
- Changed: `"@ai-sdk/openai-compatible": "^1.0.0"`
|
|
18
|
+
- To: `"@ai-sdk/openai": "^1.0.0"` (will need version update)
|
|
19
|
+
|
|
20
|
+
**Next Steps:**
|
|
21
|
+
- Update package.json dependency version to match available @ai-sdk/openai version
|
|
22
|
+
- Restart OpenCode to test the fix
|
|
23
|
+
- Monitor logs to ensure plugin loads successfully
|
|
24
|
+
|
|
25
|
+
**Technical Details:**
|
|
26
|
+
- Available @ai-sdk/openai version: 3.0.23
|
|
27
|
+
- The createOpenAI function is confirmed to exist in @ai-sdk/openai/dist/index.mjs
|
|
28
|
+
- Plugin should now be able to load without import errors
|
package/index.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Models are fetched dynamically from the API.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { createOpenAI } from '@ai-sdk/openai
|
|
9
|
+
import { createOpenAI } from '@ai-sdk/openai'
|
|
10
10
|
import type { Provider, Model } from '@ai-sdk/provider'
|
|
11
11
|
import { z } from 'zod'
|
|
12
12
|
|
|
@@ -165,12 +165,11 @@ export async function fetchQwenModels(apiKey: string, baseURL: string = QWEN_BAS
|
|
|
165
165
|
* Create Qwen provider instance
|
|
166
166
|
*/
|
|
167
167
|
export function createQwenProvider(config: QwenConfig): Provider {
|
|
168
|
-
const validatedConfig = QwenConfigSchema.parse(config)
|
|
169
168
|
|
|
170
169
|
return createOpenAI({
|
|
171
170
|
name: 'Qwen',
|
|
172
|
-
baseURL:
|
|
173
|
-
apiKey:
|
|
171
|
+
baseURL: config.baseURL || QWEN_BASE_URL,
|
|
172
|
+
apiKey: config.apiKey,
|
|
174
173
|
headers: {
|
|
175
174
|
'User-Agent': 'OpenCode-Qwen-Provider/1.0.0'
|
|
176
175
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-qwen",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Qwen provider for OpenCode with npm package support",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"exports": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@opencode-ai/plugin": "^1.0.0",
|
|
11
11
|
"zod": "^3.22.0",
|
|
12
|
-
"@ai-sdk/openai
|
|
12
|
+
"@ai-sdk/openai": "^1.0.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"typescript": "^5.0.0",
|
|
@@ -24,4 +24,4 @@
|
|
|
24
24
|
],
|
|
25
25
|
"author": "OpenCode Community",
|
|
26
26
|
"license": "MIT"
|
|
27
|
-
}
|
|
27
|
+
}
|
package/qwen-auth.ts
CHANGED
|
@@ -68,12 +68,28 @@ export const QwenAuthPlugin: Plugin = async ({ project, client, $, directory, wo
|
|
|
68
68
|
*/
|
|
69
69
|
const loadAuthState = async (): Promise<QwenAuthState> => {
|
|
70
70
|
try {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
// Handle missing environment variables gracefully
|
|
72
|
+
const apiKey = process.env.QWEN_API_KEY
|
|
73
|
+
const refreshToken = process.env.QWEN_REFRESH_TOKEN
|
|
74
|
+
const autoRefresh = process.env.QWEN_AUTO_REFRESH !== "false"
|
|
75
|
+
const validateOnStartup = process.env.QWEN_VALIDATE_ON_STARTUP !== "false"
|
|
76
|
+
|
|
77
|
+
// Only validate if we have required fields
|
|
78
|
+
if (!apiKey) {
|
|
79
|
+
await client.app.log({
|
|
80
|
+
service: "qwen-auth",
|
|
81
|
+
level: "info",
|
|
82
|
+
message: "No Qwen API key found in environment"
|
|
83
|
+
})
|
|
84
|
+
return { isValid: false }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const config = {
|
|
88
|
+
apiKey,
|
|
89
|
+
refreshToken,
|
|
90
|
+
autoRefresh,
|
|
91
|
+
validateOnStartup
|
|
92
|
+
}
|
|
77
93
|
|
|
78
94
|
authState = {
|
|
79
95
|
accessToken: config.apiKey,
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qwen Provider Authentication Plugin for OpenCode
|
|
3
|
+
*
|
|
4
|
+
* This plugin handles authentication for the Qwen API provider,
|
|
5
|
+
* including token validation, refresh, and secure credential management.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Plugin, tool } from "@opencode-ai/plugin"
|
|
9
|
+
import { z } from "zod"
|
|
10
|
+
|
|
11
|
+
// Qwen API base URL
|
|
12
|
+
const QWEN_API_BASE = "https://qwen.aikit.club/v1"
|
|
13
|
+
|
|
14
|
+
// TypeScript types for Qwen authentication
|
|
15
|
+
export interface QwenAuthState {
|
|
16
|
+
accessToken?: string
|
|
17
|
+
refreshToken?: string
|
|
18
|
+
expiresAt?: number
|
|
19
|
+
isValid: boolean
|
|
20
|
+
lastValidated?: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface QwenTokenResponse {
|
|
24
|
+
access_token: string
|
|
25
|
+
refresh_token?: string
|
|
26
|
+
expires_in: number
|
|
27
|
+
token_type: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface QwenModel {
|
|
31
|
+
id: string
|
|
32
|
+
object: string
|
|
33
|
+
created: number
|
|
34
|
+
owned_by: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface QwenModelsResponse {
|
|
38
|
+
object: string
|
|
39
|
+
data: QwenModel[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Zod schemas for validation
|
|
43
|
+
const QwenAuthConfigSchema = z.object({
|
|
44
|
+
apiKey: z.string().min(1, "API key is required"),
|
|
45
|
+
refreshToken: z.string().optional(),
|
|
46
|
+
autoRefresh: z.boolean().default(true),
|
|
47
|
+
validateOnStartup: z.boolean().default(true)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const ValidateTokenSchema = z.object({
|
|
51
|
+
token: z.string().optional()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const RefreshTokenSchema = z.object({
|
|
55
|
+
refreshToken: z.string().optional()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Qwen Authentication Plugin
|
|
60
|
+
*/
|
|
61
|
+
export const QwenAuthPlugin: Plugin = async ({ project, client, $, directory, worktree }) => {
|
|
62
|
+
let authState: QwenAuthState = {
|
|
63
|
+
isValid: false
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Load authentication state from environment or secure storage
|
|
68
|
+
*/
|
|
69
|
+
const loadAuthState = async (): Promise<QwenAuthState> => {
|
|
70
|
+
try {
|
|
71
|
+
const config = QwenAuthConfigSchema.parse({
|
|
72
|
+
apiKey: process.env.QWEN_API_KEY,
|
|
73
|
+
refreshToken: process.env.QWEN_REFRESH_TOKEN,
|
|
74
|
+
autoRefresh: process.env.QWEN_AUTO_REFRESH !== 'false',
|
|
75
|
+
validateOnStartup: process.env.QWEN_VALIDATE_ON_STARTUP !== 'false'
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
authState = {
|
|
79
|
+
accessToken: config.apiKey,
|
|
80
|
+
refreshToken: config.refreshToken,
|
|
81
|
+
isValid: !!config.apiKey,
|
|
82
|
+
lastValidated: Date.now()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await client.app.log({
|
|
86
|
+
service: "qwen-auth",
|
|
87
|
+
level: "info",
|
|
88
|
+
message: "Auth state loaded",
|
|
89
|
+
extra: { hasToken: !!config.apiKey, hasRefreshToken: !!config.refreshToken }
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
return authState
|
|
93
|
+
} catch (error) {
|
|
94
|
+
await client.app.log({
|
|
95
|
+
service: "qwen-auth",
|
|
96
|
+
level: "error",
|
|
97
|
+
message: "Failed to load auth state",
|
|
98
|
+
extra: { error: error instanceof Error ? error.message : String(error) }
|
|
99
|
+
})
|
|
100
|
+
return { isValid: false }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Validate current access token
|
|
106
|
+
*/
|
|
107
|
+
const validateToken = async (token?: string): Promise<boolean> => {
|
|
108
|
+
try {
|
|
109
|
+
const tokenToValidate = token || authState.accessToken
|
|
110
|
+
if (!tokenToValidate) {
|
|
111
|
+
return false
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const response = await fetch(`${QWEN_API_BASE}/validate`, {
|
|
115
|
+
method: "GET",
|
|
116
|
+
headers: {
|
|
117
|
+
"Authorization": `Bearer ${tokenToValidate}`,
|
|
118
|
+
"Content-Type": "application/json"
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const isValid = response.ok
|
|
123
|
+
authState.isValid = isValid
|
|
124
|
+
authState.lastValidated = Date.now()
|
|
125
|
+
|
|
126
|
+
await client.app.log({
|
|
127
|
+
service: "qwen-auth",
|
|
128
|
+
level: isValid ? "info" : "warn",
|
|
129
|
+
message: `Token validation ${isValid ? 'successful' : 'failed'}`,
|
|
130
|
+
extra: { status: response.status }
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
return isValid
|
|
134
|
+
} catch (error) {
|
|
135
|
+
authState.isValid = false
|
|
136
|
+
await client.app.log({
|
|
137
|
+
service: "qwen-auth",
|
|
138
|
+
level: "error",
|
|
139
|
+
message: "Token validation error",
|
|
140
|
+
extra: { error: error instanceof Error ? error.message : String(error) }
|
|
141
|
+
})
|
|
142
|
+
return false
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Refresh access token using refresh token
|
|
148
|
+
*/
|
|
149
|
+
const refreshToken = async (refreshTokenValue?: string): Promise<boolean> => {
|
|
150
|
+
try {
|
|
151
|
+
const tokenToUse = refreshTokenValue || authState.refreshToken
|
|
152
|
+
if (!tokenToUse) {
|
|
153
|
+
await client.app.log({
|
|
154
|
+
service: "qwen-auth",
|
|
155
|
+
level: "warn",
|
|
156
|
+
message: "No refresh token available"
|
|
157
|
+
})
|
|
158
|
+
return false
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const response = await fetch(`${QWEN_API_BASE}/refresh`, {
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers: {
|
|
164
|
+
"Content-Type": "application/json"
|
|
165
|
+
},
|
|
166
|
+
body: JSON.stringify({
|
|
167
|
+
refresh_token: tokenToUse
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
throw new Error(`Refresh failed: ${response.status}`)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const tokenData: QwenTokenResponse = await response.json()
|
|
176
|
+
|
|
177
|
+
authState.accessToken = tokenData.access_token
|
|
178
|
+
if (tokenData.refresh_token) {
|
|
179
|
+
authState.refreshToken = tokenData.refresh_token
|
|
180
|
+
}
|
|
181
|
+
authState.expiresAt = Date.now() + (tokenData.expires_in * 1000)
|
|
182
|
+
authState.isValid = true
|
|
183
|
+
authState.lastValidated = Date.now()
|
|
184
|
+
|
|
185
|
+
await client.app.log({
|
|
186
|
+
service: "qwen-auth",
|
|
187
|
+
level: "info",
|
|
188
|
+
message: "Token refreshed successfully",
|
|
189
|
+
extra: { expiresIn: tokenData.expires_in }
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
return true
|
|
193
|
+
} catch (error) {
|
|
194
|
+
await client.app.log({
|
|
195
|
+
service: "qwen-auth",
|
|
196
|
+
level: "error",
|
|
197
|
+
message: "Token refresh failed",
|
|
198
|
+
extra: { error: error instanceof Error ? error.message : String(error) }
|
|
199
|
+
})
|
|
200
|
+
return false
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Auto-refresh token if needed
|
|
206
|
+
*/
|
|
207
|
+
const ensureValidToken = async (): Promise<boolean> => {
|
|
208
|
+
if (!authState.accessToken) {
|
|
209
|
+
await loadAuthState()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (authState.isValid && authState.lastValidated &&
|
|
213
|
+
Date.now() - authState.lastValidated < 5 * 60 * 1000) {
|
|
214
|
+
return true // Valid within last 5 minutes
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const isValid = await validateToken()
|
|
218
|
+
if (isValid) {
|
|
219
|
+
return true
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (authState.refreshToken) {
|
|
223
|
+
return await refreshToken()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return false
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Initialize plugin
|
|
230
|
+
await loadAuthState()
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
// Custom tools for Qwen authentication
|
|
234
|
+
tool: {
|
|
235
|
+
"qwen.validate-token": tool({
|
|
236
|
+
description: "Validate Qwen API access token",
|
|
237
|
+
args: ValidateTokenSchema,
|
|
238
|
+
async execute(args, context) {
|
|
239
|
+
const isValid = await validateToken(args.token)
|
|
240
|
+
return {
|
|
241
|
+
valid: isValid,
|
|
242
|
+
timestamp: new Date().toISOString(),
|
|
243
|
+
state: authState
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}),
|
|
247
|
+
|
|
248
|
+
"qwen.refresh-token": tool({
|
|
249
|
+
description: "Refresh Qwen API access token",
|
|
250
|
+
args: RefreshTokenSchema,
|
|
251
|
+
async execute(args, context) {
|
|
252
|
+
const success = await refreshToken(args.refreshToken)
|
|
253
|
+
return {
|
|
254
|
+
success,
|
|
255
|
+
timestamp: new Date().toISOString(),
|
|
256
|
+
state: authState
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}),
|
|
260
|
+
|
|
261
|
+
"qwen.list-models": tool({
|
|
262
|
+
description: "List available Qwen models (requires valid authentication)",
|
|
263
|
+
args: z.object({}),
|
|
264
|
+
async execute(args, context) {
|
|
265
|
+
await ensureValidToken()
|
|
266
|
+
|
|
267
|
+
if (!authState.accessToken || !authState.isValid) {
|
|
268
|
+
throw new Error("No valid Qwen authentication token available")
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
const response = await fetch(`${QWEN_API_BASE}/models`, {
|
|
273
|
+
headers: {
|
|
274
|
+
"Authorization": `Bearer ${authState.accessToken}`,
|
|
275
|
+
"Content-Type": "application/json"
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
if (!response.ok) {
|
|
280
|
+
throw new Error(`Failed to fetch models: ${response.status}`)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const models: QwenModelsResponse = await response.json()
|
|
284
|
+
|
|
285
|
+
await client.app.log({
|
|
286
|
+
service: "qwen-auth",
|
|
287
|
+
level: "info",
|
|
288
|
+
message: `Successfully retrieved ${models.data.length} models`
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
return models
|
|
292
|
+
} catch (error) {
|
|
293
|
+
await client.app.log({
|
|
294
|
+
service: "qwen-auth",
|
|
295
|
+
level: "error",
|
|
296
|
+
message: "Failed to fetch models",
|
|
297
|
+
extra: { error: error instanceof Error ? error.message : String(error) }
|
|
298
|
+
})
|
|
299
|
+
throw error
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
// Hooks for automatic token management
|
|
306
|
+
"tool.execute.before": async (input, output) => {
|
|
307
|
+
// Intercept Qwen API calls and ensure valid authentication
|
|
308
|
+
if (input.tool === "bash" && output.args.command?.includes("qwen.aikit.club")) {
|
|
309
|
+
await ensureValidToken()
|
|
310
|
+
if (authState.accessToken) {
|
|
311
|
+
// Inject token into command if needed
|
|
312
|
+
output.args.command = output.args.command.replace(
|
|
313
|
+
/qwen\.aikit\.club\/v1\//,
|
|
314
|
+
`qwen.aikit.club/v1/?api_key=${authState.accessToken}&`
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
"session.created": async () => {
|
|
321
|
+
// Validate token on session creation if enabled
|
|
322
|
+
if (process.env.QWEN_VALIDATE_ON_STARTUP !== 'false') {
|
|
323
|
+
await ensureValidToken()
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
"session.idle": async () => {
|
|
328
|
+
// Periodic validation when session is idle
|
|
329
|
+
if (authState.isValid && authState.lastValidated) {
|
|
330
|
+
const timeSinceValidation = Date.now() - authState.lastValidated
|
|
331
|
+
if (timeSinceValidation > 30 * 60 * 1000) { // 30 minutes
|
|
332
|
+
await validateToken()
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
package/test_fix.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Qwen Plugin Fix Summary
|
|
2
|
+
|
|
3
|
+
## Issues Fixed
|
|
4
|
+
|
|
5
|
+
### 1. Import Error (RESOLVED)
|
|
6
|
+
- **Problem**: `createOpenAI` imported from non-existent `@ai-sdk/openai-compatible`
|
|
7
|
+
- **Solution**: Changed import to `@ai-sdk/openai` package
|
|
8
|
+
- **Files Modified**: `index.ts`, `package.json`
|
|
9
|
+
|
|
10
|
+
### 2. API Key Validation Error (RESOLVED)
|
|
11
|
+
- **Problem**: Zod schema validation on startup with undefined apiKey
|
|
12
|
+
- **Root Cause**: Plugin tried to validate config before user connects
|
|
13
|
+
- **Solution**: Removed immediate validation, added graceful handling
|
|
14
|
+
|
|
15
|
+
## Changes Made
|
|
16
|
+
|
|
17
|
+
### index.ts
|
|
18
|
+
```typescript
|
|
19
|
+
// BEFORE (caused startup error)
|
|
20
|
+
export function createQwenProvider(config: QwenConfig): Provider {
|
|
21
|
+
const validatedConfig = QwenConfigSchema.parse(config) // ← This line failed
|
|
22
|
+
return createOpenAI({
|
|
23
|
+
baseURL: validatedConfig.baseURL,
|
|
24
|
+
apiKey: validatedConfig.apiKey,
|
|
25
|
+
// ...
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// AFTER (graceful handling)
|
|
30
|
+
export function createQwenProvider(config: QwenConfig): Provider {
|
|
31
|
+
return createOpenAI({
|
|
32
|
+
baseURL: config.baseURL || QWEN_BASE_URL, // ← Fallback added
|
|
33
|
+
apiKey: config.apiKey, // ← Direct access
|
|
34
|
+
// ...
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### qwen-auth.ts
|
|
40
|
+
```typescript
|
|
41
|
+
// BEFORE (caused startup error)
|
|
42
|
+
const config = QwenAuthConfigSchema.parse({
|
|
43
|
+
apiKey: process.env.QWEN_API_KEY, // ← Failed if undefined
|
|
44
|
+
// ...
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// AFTER (graceful handling)
|
|
48
|
+
const apiKey = process.env.QWEN_API_KEY
|
|
49
|
+
if (!apiKey) {
|
|
50
|
+
await client.app.log({
|
|
51
|
+
service: "qwen-auth",
|
|
52
|
+
level: "info",
|
|
53
|
+
message: "No Qwen API key found in environment"
|
|
54
|
+
})
|
|
55
|
+
return { isValid: false }
|
|
56
|
+
}
|
|
57
|
+
const config = { apiKey, refreshToken, autoRefresh, validateOnStartup }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Expected Behavior
|
|
61
|
+
- Plugin loads without errors on OpenCode startup
|
|
62
|
+
- API key validation only happens when user actually connects
|
|
63
|
+
- Graceful fallback when no credentials are available
|
|
64
|
+
- Proper error handling and logging throughout
|
|
65
|
+
|
|
66
|
+
## Next Steps
|
|
67
|
+
1. Test plugin load (should be error-free now)
|
|
68
|
+
2. Test user connection flow with valid API key
|
|
69
|
+
3. Verify model loading and functionality
|
package/opencode.json
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://opencode.ai/config.json",
|
|
3
|
-
"provider": {
|
|
4
|
-
"qwen": {
|
|
5
|
-
"npm": "opencode-qwen",
|
|
6
|
-
"name": "Qwen AI",
|
|
7
|
-
"description": "High-quality large language models with dynamic model loading - optimized for coding, reasoning, web development, and creative tasks",
|
|
8
|
-
"website": "https://qwen.aikit.club",
|
|
9
|
-
"documentation": "https://github.com/tanu1337/qwen-api",
|
|
10
|
-
"options": {
|
|
11
|
-
"baseURL": "https://qwen.aikit.club/v1",
|
|
12
|
-
"dynamicModels": true,
|
|
13
|
-
"modelCacheDuration": 300000
|
|
14
|
-
},
|
|
15
|
-
"authentication": {
|
|
16
|
-
"type": "bearer",
|
|
17
|
-
"help": "Get your access token from https://chat.qwen.ai by running the token extractor script in browser console"
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
"env": {
|
|
22
|
-
"QWEN_API_KEY": {
|
|
23
|
-
"description": "Qwen API access key",
|
|
24
|
-
"required": true
|
|
25
|
-
},
|
|
26
|
-
"QWEN_BASE_URL": {
|
|
27
|
-
"description": "Qwen API base URL",
|
|
28
|
-
"default": "https://qwen.aikit.club/v1"
|
|
29
|
-
},
|
|
30
|
-
"QWEN_AUTO_REFRESH": {
|
|
31
|
-
"description": "Enable automatic token refresh",
|
|
32
|
-
"default": "true",
|
|
33
|
-
"type": "boolean"
|
|
34
|
-
},
|
|
35
|
-
"QWEN_TIMEOUT": {
|
|
36
|
-
"description": "Request timeout in milliseconds",
|
|
37
|
-
"default": "30000",
|
|
38
|
-
"type": "number"
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|