@hotmanxp/opencode-qwen-login-plugin 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,188 @@
1
+ /**
2
+ * 配置合并测试 - 集成测试
3
+ *
4
+ * 测试 saveToOpencodeConfig 函数的合并行为
5
+ */
6
+ import { describe, it, beforeEach, afterEach } from 'node:test';
7
+ import { writeFile, mkdir, rm, readFile } from 'node:fs/promises';
8
+ import { join } from 'node:path';
9
+ import assert from 'node:assert';
10
+ import { saveToOpencodeConfig } from './qwen-oauth.js';
11
+ describe('saveToOpencodeConfig - 配置合并', () => {
12
+ let testConfigDir;
13
+ let testConfigPath;
14
+ let originalHOME;
15
+ beforeEach(async () => {
16
+ originalHOME = process.env.HOME;
17
+ testConfigDir = await mkdtemp('opencode-config-test-');
18
+ testConfigPath = join(testConfigDir, '.config', 'opencode');
19
+ await mkdir(testConfigPath, { recursive: true });
20
+ process.env.HOME = testConfigDir;
21
+ });
22
+ afterEach(async () => {
23
+ process.env.HOME = originalHOME;
24
+ await rm(testConfigDir, { recursive: true, force: true }).catch(() => { });
25
+ });
26
+ it('应该保留现有的 plugin 数组', async () => {
27
+ // 创建初始配置
28
+ const initialConfig = {
29
+ plugin: ['oh-my-opencode', 'another-plugin'],
30
+ agent: {
31
+ build: {
32
+ max_steps: 10
33
+ }
34
+ }
35
+ };
36
+ const configPath = join(testConfigPath, 'opencode.json');
37
+ await writeFile(configPath, JSON.stringify(initialConfig, null, 2));
38
+ // 调用 saveToOpencodeConfig
39
+ await saveToOpencodeConfig({
40
+ apiKey: 'Bearer test-token',
41
+ baseURL: 'https://portal.qwen.ai/v1',
42
+ headers: {
43
+ 'user-agent': 'QwenCode/test'
44
+ }
45
+ });
46
+ // 读取更新后的配置
47
+ const updatedConfig = JSON.parse(await readFile(configPath, 'utf-8'));
48
+ // 验证 plugin 数组被保留
49
+ assert.ok(updatedConfig.plugin.includes('oh-my-opencode'), 'Should contain oh-my-opencode');
50
+ assert.ok(updatedConfig.plugin.includes('another-plugin'), 'Should contain another-plugin');
51
+ // 验证 agent 配置被保留
52
+ assert.ok(updatedConfig.agent, 'Should have agent config');
53
+ assert.ok(updatedConfig.agent.build, 'Should have build agent');
54
+ assert.strictEqual(updatedConfig.agent.build.max_steps, 10);
55
+ // 验证 qwen provider 被添加
56
+ assert.ok(updatedConfig.provider, 'Should have provider config');
57
+ assert.ok(updatedConfig.provider.qwen, 'Should have qwen provider');
58
+ });
59
+ it('应该保留现有的其他 provider', async () => {
60
+ // 创建初始配置,包含其他 provider
61
+ const initialConfig = {
62
+ provider: {
63
+ anthropic: {
64
+ options: {
65
+ apiKey: 'sk-ant-xxx'
66
+ }
67
+ },
68
+ openai: {
69
+ options: {
70
+ apiKey: 'sk-openai-xxx'
71
+ }
72
+ }
73
+ },
74
+ model: 'anthropic/claude-sonnet-4-20250514'
75
+ };
76
+ const configPath = join(testConfigPath, 'opencode.json');
77
+ await writeFile(configPath, JSON.stringify(initialConfig, null, 2));
78
+ // 调用 saveToOpencodeConfig
79
+ await saveToOpencodeConfig({
80
+ apiKey: 'Bearer test-token',
81
+ baseURL: 'https://portal.qwen.ai/v1',
82
+ headers: {}
83
+ });
84
+ // 读取更新后的配置
85
+ const updatedConfig = JSON.parse(await readFile(configPath, 'utf-8'));
86
+ // 验证其他 provider 被保留
87
+ assert.ok(updatedConfig.provider.anthropic, "should be defined");
88
+ assert.strictEqual(updatedConfig.provider.anthropic.options.apiKey, 'sk-ant-xxx');
89
+ assert.ok(updatedConfig.provider.openai, "should be defined");
90
+ assert.strictEqual(updatedConfig.provider.openai.options.apiKey, 'sk-openai-xxx');
91
+ // 验证 qwen provider 被添加
92
+ assert.ok(updatedConfig.provider.qwen, "should be defined");
93
+ assert.strictEqual(updatedConfig.provider.qwen.options.apiKey, 'Bearer test-token');
94
+ });
95
+ it('不应该覆盖现有的 model 配置', async () => {
96
+ // 创建初始配置,包含自定义 model
97
+ const initialConfig = {
98
+ provider: {
99
+ anthropic: {
100
+ options: {
101
+ apiKey: 'sk-ant-xxx'
102
+ }
103
+ }
104
+ },
105
+ model: 'anthropic/claude-sonnet-4-20250514'
106
+ };
107
+ const configPath = join(testConfigPath, 'opencode.json');
108
+ await writeFile(configPath, JSON.stringify(initialConfig, null, 2));
109
+ // 调用 saveToOpencodeConfig
110
+ await saveToOpencodeConfig({
111
+ apiKey: 'Bearer test-token',
112
+ baseURL: 'https://portal.qwen.ai/v1',
113
+ headers: {}
114
+ });
115
+ // 读取更新后的配置
116
+ const updatedConfig = JSON.parse(await readFile(configPath, 'utf-8'));
117
+ // 验证原有 model 被保留(不覆盖)
118
+ assert.strictEqual(updatedConfig.model, 'anthropic/claude-sonnet-4-20250514');
119
+ });
120
+ it('不应该设置默认 model', async () => {
121
+ // 创建初始配置,没有 model
122
+ const initialConfig = {
123
+ plugin: ['test-plugin']
124
+ };
125
+ const configPath = join(testConfigPath, 'opencode.json');
126
+ await writeFile(configPath, JSON.stringify(initialConfig, null, 2));
127
+ // 调用 saveToOpencodeConfig
128
+ await saveToOpencodeConfig({
129
+ apiKey: 'Bearer test-token',
130
+ baseURL: 'https://portal.qwen.ai/v1',
131
+ headers: {}
132
+ });
133
+ // 读取更新后的配置
134
+ const updatedConfig = JSON.parse(await readFile(configPath, 'utf-8'));
135
+ // 验证不设置默认 model(让用户自己配置)
136
+ assert.strictEqual(updatedConfig.model, undefined, 'Should not set default model');
137
+ // 验证 plugin 被保留
138
+ assert.ok(updatedConfig.plugin.includes('test-plugin'), 'Should include test-plugin');
139
+ });
140
+ it('应该保留其他顶层配置', async () => {
141
+ // 创建初始配置,包含各种配置
142
+ const initialConfig = {
143
+ $schema: 'https://opencode.ai/config.json',
144
+ agent: {
145
+ build: { max_steps: 10 },
146
+ plan: { read_only: true }
147
+ },
148
+ mode: {
149
+ default: 'build'
150
+ },
151
+ instructions: ['./instructions.md'],
152
+ plugin: ['plugin-a', 'plugin-b'],
153
+ provider: {
154
+ anthropic: { options: { apiKey: 'sk-ant' } }
155
+ }
156
+ };
157
+ const configPath = join(testConfigPath, 'opencode.json');
158
+ await writeFile(configPath, JSON.stringify(initialConfig, null, 2));
159
+ // 调用 saveToOpencodeConfig
160
+ await saveToOpencodeConfig({
161
+ apiKey: 'Bearer test-token',
162
+ baseURL: 'https://portal.qwen.ai/v1',
163
+ headers: {}
164
+ });
165
+ // 读取更新后的配置
166
+ const updatedConfig = JSON.parse(await readFile(configPath, 'utf-8'));
167
+ // 验证所有配置都被保留
168
+ assert.strictEqual(updatedConfig.$schema, 'https://opencode.ai/config.json');
169
+ assert.strictEqual(updatedConfig.agent.build.max_steps, 10);
170
+ assert.strictEqual(updatedConfig.agent.plan.read_only, true);
171
+ assert.strictEqual(updatedConfig.mode.default, 'build');
172
+ assert.ok(updatedConfig.instructions.includes('./instructions.md'), "should include");
173
+ assert.ok(updatedConfig.plugin.includes('plugin-a'), "should include");
174
+ assert.ok(updatedConfig.plugin.includes('plugin-b'), "should include");
175
+ assert.strictEqual(updatedConfig.provider.anthropic.options.apiKey, 'sk-ant');
176
+ // 验证 qwen 被添加
177
+ assert.ok(updatedConfig.provider.qwen, "should be defined");
178
+ });
179
+ });
180
+ // 辅助函数
181
+ async function mkdtemp(template) {
182
+ const fs = await import('node:fs/promises');
183
+ const path = await import('node:path');
184
+ const os = await import('node:os');
185
+ const dir = path.join(os.tmpdir(), `${template}${Date.now()}-${Math.random().toString(36).slice(2)}`);
186
+ await fs.mkdir(dir, { recursive: true });
187
+ return dir;
188
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Qwen Login Plugin - 通义千问认证插件
3
+ *
4
+ * 为 opencode 提供 Qwen 模型认证支持
5
+ * 自动从 qwen-code OAuth 配置导入认证信息
6
+ */
7
+ /**
8
+ * Qwen 认证插件主函数
9
+ */
10
+ declare function plugin(pluginInput: any): Promise<{
11
+ config: (config: any) => Promise<void>;
12
+ }>;
13
+ export default plugin;
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;GAEG;AACH,iBAAe,MAAM,CAAC,WAAW,EAAE,GAAG;qBAUX,GAAG;GAgE7B;AAGD,eAAe,MAAM,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Qwen Login Plugin - 通义千问认证插件
3
+ *
4
+ * 为 opencode 提供 Qwen 模型认证支持
5
+ * 自动从 qwen-code OAuth 配置导入认证信息
6
+ */
7
+ import { configureOpencodeFromQwenOAuth, readOAuthCredentials, isTokenValid, getApiConfigFromOAuth } from "./qwen-oauth.js";
8
+ /**
9
+ * Qwen 认证插件主函数
10
+ */
11
+ async function plugin(pluginInput) {
12
+ // 插件加载时立即写入配置文件
13
+ try {
14
+ await configureOpencodeFromQwenOAuth();
15
+ }
16
+ catch (error) {
17
+ // 静默失败,使用其他认证方式
18
+ }
19
+ return {
20
+ // 配置钩子 - 修改运行时配置
21
+ config: async (config) => {
22
+ try {
23
+ // 1. 读取 OAuth 凭证
24
+ const creds = await readOAuthCredentials();
25
+ if (!creds) {
26
+ return;
27
+ }
28
+ // 2. 检查 token 是否有效
29
+ if (!isTokenValid(creds)) {
30
+ return;
31
+ }
32
+ // 3. 生成 API 配置
33
+ const apiConfig = getApiConfigFromOAuth(creds);
34
+ // 4. 按照 opencode provider schema 配置
35
+ if (!config.provider) {
36
+ config.provider = {};
37
+ }
38
+ config.provider.qwen = {
39
+ name: "Qwen (通义千问)",
40
+ npm: "@ai-sdk/openai-compatible",
41
+ env: [],
42
+ models: {
43
+ "coder-model": {
44
+ name: "Qwen Coder Model",
45
+ tool_call: true,
46
+ reasoning: false,
47
+ attachment: true,
48
+ temperature: true,
49
+ interleaved: true,
50
+ limit: {
51
+ context: 256000,
52
+ output: 8192
53
+ }
54
+ },
55
+ "qwen-plus": {
56
+ name: "Qwen Plus",
57
+ tool_call: true,
58
+ reasoning: false,
59
+ attachment: true,
60
+ temperature: true,
61
+ interleaved: true,
62
+ limit: {
63
+ context: 131072,
64
+ output: 8192
65
+ }
66
+ }
67
+ },
68
+ options: {
69
+ apiKey: apiConfig.apiKey,
70
+ baseURL: apiConfig.baseURL,
71
+ headers: apiConfig.headers
72
+ }
73
+ };
74
+ }
75
+ catch (error) {
76
+ // 静默失败
77
+ }
78
+ }
79
+ };
80
+ }
81
+ // 只导出默认函数
82
+ export default plugin;
83
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,8BAA8B,EAAE,oBAAoB,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAE3H;;GAEG;AACH,KAAK,UAAU,MAAM,CAAC,WAAgB;IACpC,gBAAgB;IAChB,IAAI,CAAC;QACH,MAAM,8BAA8B,EAAE,CAAA;IACxC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,gBAAgB;IAClB,CAAC;IAED,OAAO;QACL,iBAAiB;QACjB,MAAM,EAAE,KAAK,EAAE,MAAW,EAAE,EAAE;YAC5B,IAAI,CAAC;gBACH,iBAAiB;gBACjB,MAAM,KAAK,GAAG,MAAM,oBAAoB,EAAE,CAAA;gBAE1C,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,OAAM;gBACR,CAAC;gBAED,mBAAmB;gBACnB,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAM;gBACR,CAAC;gBAED,eAAe;gBACf,MAAM,SAAS,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAA;gBAE9C,oCAAoC;gBACpC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;oBACrB,MAAM,CAAC,QAAQ,GAAG,EAAE,CAAA;gBACtB,CAAC;gBAED,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG;oBACrB,IAAI,EAAE,aAAa;oBACnB,GAAG,EAAE,2BAA2B;oBAChC,GAAG,EAAE,EAAE;oBACP,MAAM,EAAE;wBACN,aAAa,EAAE;4BACb,IAAI,EAAE,kBAAkB;4BACxB,SAAS,EAAE,IAAI;4BACf,SAAS,EAAE,KAAK;4BAChB,UAAU,EAAE,IAAI;4BAChB,WAAW,EAAE,IAAI;4BACjB,WAAW,EAAE,IAAI;4BACjB,KAAK,EAAE;gCACL,OAAO,EAAE,MAAM;gCACf,MAAM,EAAE,IAAI;6BACb;yBACF;wBACD,WAAW,EAAE;4BACX,IAAI,EAAE,WAAW;4BACjB,SAAS,EAAE,IAAI;4BACf,SAAS,EAAE,KAAK;4BAChB,UAAU,EAAE,IAAI;4BAChB,WAAW,EAAE,IAAI;4BACjB,WAAW,EAAE,IAAI;4BACjB,KAAK,EAAE;gCACL,OAAO,EAAE,MAAM;gCACf,MAAM,EAAE,IAAI;6BACb;yBACF;qBACF;oBACD,OAAO,EAAE;wBACP,MAAM,EAAE,SAAS,CAAC,MAAM;wBACxB,OAAO,EAAE,SAAS,CAAC,OAAO;wBAC1B,OAAO,EAAE,SAAS,CAAC,OAAO;qBAC3B;iBACF,CAAA;YAEH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO;YACT,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED,UAAU;AACV,eAAe,MAAM,CAAA"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Qwen Login Plugin - 通义千问认证插件
3
+ *
4
+ * 为 opencode 提供 Qwen 模型认证支持
5
+ * 自动从 qwen-code OAuth 配置导入认证信息
6
+ */
7
+ /**
8
+ * Qwen 认证插件主函数
9
+ */
10
+ declare function plugin(pluginInput: any): Promise<{
11
+ config: (config: any) => Promise<void>;
12
+ }>;
13
+ export default plugin;
14
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;GAEG;AACH,iBAAe,MAAM,CAAC,WAAW,EAAE,GAAG;qBAUX,GAAG;GAgE7B;AAGD,eAAe,MAAM,CAAA"}
package/dist/plugin.js ADDED
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Qwen Login Plugin - 通义千问认证插件
3
+ *
4
+ * 为 opencode 提供 Qwen 模型认证支持
5
+ * 自动从 qwen-code OAuth 配置导入认证信息
6
+ */
7
+ import { configureOpencodeFromQwenOAuth, readOAuthCredentials, isTokenValid, getApiConfigFromOAuth } from "./qwen-oauth.js";
8
+ /**
9
+ * Qwen 认证插件主函数
10
+ */
11
+ async function plugin(pluginInput) {
12
+ // 插件加载时立即写入配置文件
13
+ try {
14
+ await configureOpencodeFromQwenOAuth();
15
+ }
16
+ catch (error) {
17
+ // 静默失败,使用其他认证方式
18
+ }
19
+ return {
20
+ // 配置钩子 - 修改运行时配置
21
+ config: async (config) => {
22
+ try {
23
+ // 1. 读取 OAuth 凭证
24
+ const creds = await readOAuthCredentials();
25
+ if (!creds) {
26
+ return;
27
+ }
28
+ // 2. 检查 token 是否有效
29
+ if (!isTokenValid(creds)) {
30
+ return;
31
+ }
32
+ // 3. 生成 API 配置
33
+ const apiConfig = getApiConfigFromOAuth(creds);
34
+ // 4. 按照 opencode provider schema 配置
35
+ if (!config.provider) {
36
+ config.provider = {};
37
+ }
38
+ config.provider.qwen = {
39
+ name: "Qwen (通义千问)",
40
+ npm: "@ai-sdk/openai-compatible",
41
+ env: [],
42
+ models: {
43
+ "coder-model": {
44
+ name: "Qwen Coder Model",
45
+ tool_call: true,
46
+ reasoning: false,
47
+ attachment: true,
48
+ temperature: true,
49
+ interleaved: true,
50
+ limit: {
51
+ context: 256000,
52
+ output: 8192
53
+ }
54
+ },
55
+ "qwen-plus": {
56
+ name: "Qwen Plus",
57
+ tool_call: true,
58
+ reasoning: false,
59
+ attachment: true,
60
+ temperature: true,
61
+ interleaved: true,
62
+ limit: {
63
+ context: 131072,
64
+ output: 8192
65
+ }
66
+ }
67
+ },
68
+ options: {
69
+ apiKey: apiConfig.apiKey,
70
+ baseURL: apiConfig.baseURL,
71
+ headers: apiConfig.headers
72
+ }
73
+ };
74
+ }
75
+ catch (error) {
76
+ // 静默失败
77
+ }
78
+ }
79
+ };
80
+ }
81
+ // 只导出默认函数
82
+ export default plugin;
83
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,8BAA8B,EAAE,oBAAoB,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAE3H;;GAEG;AACH,KAAK,UAAU,MAAM,CAAC,WAAgB;IACpC,gBAAgB;IAChB,IAAI,CAAC;QACH,MAAM,8BAA8B,EAAE,CAAA;IACxC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,gBAAgB;IAClB,CAAC;IAED,OAAO;QACL,iBAAiB;QACjB,MAAM,EAAE,KAAK,EAAE,MAAW,EAAE,EAAE;YAC5B,IAAI,CAAC;gBACH,iBAAiB;gBACjB,MAAM,KAAK,GAAG,MAAM,oBAAoB,EAAE,CAAA;gBAE1C,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,OAAM;gBACR,CAAC;gBAED,mBAAmB;gBACnB,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAM;gBACR,CAAC;gBAED,eAAe;gBACf,MAAM,SAAS,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAA;gBAE9C,oCAAoC;gBACpC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;oBACrB,MAAM,CAAC,QAAQ,GAAG,EAAE,CAAA;gBACtB,CAAC;gBAED,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG;oBACrB,IAAI,EAAE,aAAa;oBACnB,GAAG,EAAE,2BAA2B;oBAChC,GAAG,EAAE,EAAE;oBACP,MAAM,EAAE;wBACN,aAAa,EAAE;4BACb,IAAI,EAAE,kBAAkB;4BACxB,SAAS,EAAE,IAAI;4BACf,SAAS,EAAE,KAAK;4BAChB,UAAU,EAAE,IAAI;4BAChB,WAAW,EAAE,IAAI;4BACjB,WAAW,EAAE,IAAI;4BACjB,KAAK,EAAE;gCACL,OAAO,EAAE,MAAM;gCACf,MAAM,EAAE,IAAI;6BACb;yBACF;wBACD,WAAW,EAAE;4BACX,IAAI,EAAE,WAAW;4BACjB,SAAS,EAAE,IAAI;4BACf,SAAS,EAAE,KAAK;4BAChB,UAAU,EAAE,IAAI;4BAChB,WAAW,EAAE,IAAI;4BACjB,WAAW,EAAE,IAAI;4BACjB,KAAK,EAAE;gCACL,OAAO,EAAE,MAAM;gCACf,MAAM,EAAE,IAAI;6BACb;yBACF;qBACF;oBACD,OAAO,EAAE;wBACP,MAAM,EAAE,SAAS,CAAC,MAAM;wBACxB,OAAO,EAAE,SAAS,CAAC,OAAO;wBAC1B,OAAO,EAAE,SAAS,CAAC,OAAO;qBAC3B;iBACF,CAAA;YAEH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO;YACT,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED,UAAU;AACV,eAAe,MAAM,CAAA"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Qwen-code 配置读取工具
3
+ *
4
+ * 从 ~/.qwen/ 目录读取 qwen-code 的配置文件
5
+ */
6
+ /**
7
+ * Qwen-code 配置文件接口
8
+ */
9
+ export interface QwenConfig {
10
+ security?: {
11
+ auth?: {
12
+ apiKey?: string;
13
+ };
14
+ };
15
+ modelProviders?: {
16
+ openai?: Array<{
17
+ id?: string;
18
+ baseUrl?: string;
19
+ envKey?: string;
20
+ }>;
21
+ };
22
+ model?: {
23
+ name?: string;
24
+ };
25
+ }
26
+ /**
27
+ * 认证信息接口
28
+ */
29
+ export interface AuthConfig {
30
+ apiKey?: string;
31
+ baseURL?: string;
32
+ }
33
+ /**
34
+ * 从 qwen-code 导入配置
35
+ *
36
+ * @returns 认证信息,如果配置不存在或读取失败则返回 null
37
+ */
38
+ export declare function readQwenConfig(): Promise<AuthConfig | null>;
39
+ /**
40
+ * 检查是否存在 qwen-code 配置
41
+ *
42
+ * @returns boolean - 是否存在配置
43
+ */
44
+ export declare function hasQwenConfig(): Promise<boolean>;
45
+ //# sourceMappingURL=qwen-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qwen-config.d.ts","sourceRoot":"","sources":["../src/qwen-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE;YACL,MAAM,CAAC,EAAE,MAAM,CAAA;SAChB,CAAA;KACF,CAAA;IACD,cAAc,CAAC,EAAE;QACf,MAAM,CAAC,EAAE,KAAK,CAAC;YACb,EAAE,CAAC,EAAE,MAAM,CAAA;YACX,OAAO,CAAC,EAAE,MAAM,CAAA;YAChB,MAAM,CAAC,EAAE,MAAM,CAAA;SAChB,CAAC,CAAA;KACH,CAAA;IACD,KAAK,CAAC,EAAE;QACN,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CA6BjE;AAED;;;;GAIG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAGtD"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Qwen-code 配置读取工具
3
+ *
4
+ * 从 ~/.qwen/ 目录读取 qwen-code 的配置文件
5
+ */
6
+ import { readFile } from "node:fs/promises";
7
+ import { join } from "node:path";
8
+ import { homedir } from "node:os";
9
+ /**
10
+ * 从 qwen-code 导入配置
11
+ *
12
+ * @returns 认证信息,如果配置不存在或读取失败则返回 null
13
+ */
14
+ export async function readQwenConfig() {
15
+ try {
16
+ const configPath = join(homedir(), ".qwen", "settings.json");
17
+ const content = await readFile(configPath, "utf-8");
18
+ const config = JSON.parse(content);
19
+ // 提取 API Key
20
+ const apiKey = config.security?.auth?.apiKey;
21
+ // 提取 Base URL
22
+ let baseURL;
23
+ if (config.modelProviders?.openai && config.modelProviders.openai.length > 0) {
24
+ baseURL = config.modelProviders.openai[0].baseUrl;
25
+ }
26
+ // 如果没有配置,返回 null
27
+ if (!apiKey && !baseURL) {
28
+ return null;
29
+ }
30
+ return {
31
+ apiKey,
32
+ baseURL
33
+ };
34
+ }
35
+ catch (error) {
36
+ // 配置文件不存在或读取失败
37
+ // 可能是用户没有使用过 qwen-code,这是正常情况
38
+ return null;
39
+ }
40
+ }
41
+ /**
42
+ * 检查是否存在 qwen-code 配置
43
+ *
44
+ * @returns boolean - 是否存在配置
45
+ */
46
+ export async function hasQwenConfig() {
47
+ const config = await readQwenConfig();
48
+ return config !== null && (config.apiKey !== undefined || config.baseURL !== undefined);
49
+ }
50
+ //# sourceMappingURL=qwen-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qwen-config.js","sourceRoot":"","sources":["../src/qwen-config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AA+BjC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,eAAe,CAAC,CAAA;QAC5D,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAe,CAAA;QAEhD,aAAa;QACb,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAA;QAE5C,cAAc;QACd,IAAI,OAA2B,CAAA;QAC/B,IAAI,MAAM,CAAC,cAAc,EAAE,MAAM,IAAI,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7E,OAAO,GAAG,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;QACnD,CAAC;QAED,iBAAiB;QACjB,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACxB,OAAO,IAAI,CAAA;QACb,CAAC;QAED,OAAO;YACL,MAAM;YACN,OAAO;SACR,CAAA;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,eAAe;QACf,8BAA8B;QAC9B,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,MAAM,GAAG,MAAM,cAAc,EAAE,CAAA;IACrC,OAAO,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC,CAAA;AACzF,CAAC"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Qwen-code OAuth 认证配置工具
3
+ *
4
+ * 从 ~/.qwen/oauth_creds.json 读取 OAuth token
5
+ * 用于配置 opencode 使用 Qwen API
6
+ */
7
+ /**
8
+ * OAuth 凭证接口
9
+ */
10
+ export interface OAuthCredentials {
11
+ access_token: string;
12
+ token_type: string;
13
+ refresh_token: string;
14
+ resource_url: string;
15
+ expiry_date: number;
16
+ }
17
+ /**
18
+ * Qwen API 配置接口
19
+ */
20
+ export interface QwenApiConfig {
21
+ apiKey: string;
22
+ baseURL: string;
23
+ headers?: Record<string, string>;
24
+ }
25
+ /**
26
+ * 默认 Qwen API 基础 URL(阿里云 DashScope)
27
+ */
28
+ export declare const DEFAULT_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1";
29
+ /**
30
+ * Qwen Code 版本信息
31
+ */
32
+ export declare const QWEN_CODE_VERSION = "unknown";
33
+ /**
34
+ * 获取当前平台信息
35
+ */
36
+ export declare function getPlatformInfo(): {
37
+ platform: string;
38
+ arch: string;
39
+ nodeVersion: string;
40
+ };
41
+ /**
42
+ * 构建默认的 Qwen Code User-Agent
43
+ *
44
+ * 格式:QwenCode/<version> (<platform>; <arch>)
45
+ * 示例:QwenCode/unknown (darwin; arm64)
46
+ */
47
+ export declare function buildUserAgent(): string;
48
+ /**
49
+ * 构建 Qwen API 的默认请求头
50
+ *
51
+ * @param token OAuth token
52
+ * @returns 请求头对象
53
+ */
54
+ export declare function buildDefaultHeaders(token: string): Record<string, string>;
55
+ /**
56
+ * 从 resource_url 构建最终的 API endpoint
57
+ *
58
+ * @param resourceUrl resource_url from OAuth credentials (e.g., "portal.qwen.ai")
59
+ * @returns 完整的 API endpoint URL
60
+ */
61
+ export declare function buildBaseUrl(resourceUrl: string): string;
62
+ /**
63
+ * 读取 OAuth 凭证
64
+ *
65
+ * @returns OAuth 凭证,如果文件不存在或读取失败则返回 null
66
+ */
67
+ export declare function readOAuthCredentials(): Promise<OAuthCredentials | null>;
68
+ /**
69
+ * 检查 OAuth token 是否过期
70
+ *
71
+ * @param creds OAuth 凭证
72
+ * @returns boolean - 是否有效(未过期)
73
+ */
74
+ export declare function isTokenValid(creds: OAuthCredentials): boolean;
75
+ /**
76
+ * 从 OAuth 凭证获取 API 配置
77
+ *
78
+ * @param creds OAuth 凭证
79
+ * @returns API 配置(apiKey、baseURL 和 headers)
80
+ */
81
+ export declare function getApiConfigFromOAuth(creds: OAuthCredentials): QwenApiConfig;
82
+ /**
83
+ * 获取 Qwen API 配置(从 OAuth)
84
+ *
85
+ * @returns API 配置,如果 OAuth 凭证无效则返回 null
86
+ */
87
+ export declare function getQwenConfigFromOAuth(): Promise<QwenApiConfig | null>;
88
+ /**
89
+ * 保存配置到 opencode.json
90
+ *
91
+ * @param config API 配置
92
+ */
93
+ export declare function saveToOpencodeConfig(config: QwenApiConfig): Promise<void>;
94
+ /**
95
+ * 主函数:从 qwen-code OAuth 配置 opencode
96
+ *
97
+ * @returns 是否成功配置
98
+ */
99
+ export declare function configureOpencodeFromQwenOAuth(): Promise<boolean>;
100
+ //# sourceMappingURL=qwen-oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qwen-oauth.d.ts","sourceRoot":"","sources":["../src/qwen-oauth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACjC;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB,sDAAsD,CAAA;AAEnF;;GAEG;AACH,eAAO,MAAM,iBAAiB,YAAY,CAAA;AAE1C;;GAEG;AACH,wBAAgB,eAAe;;;;EAM9B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAGvC;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAqBzE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAOxD;AASD;;;;GAIG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAS7E;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAG7D;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,GAAG,aAAa,CAS5E;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAY5E;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA4E/E;AAED;;;;GAIG;AACH,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,OAAO,CAAC,CAwBvE"}