@asframe/opencode-iflow-auth 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.
- package/README.md +278 -0
- package/dist/constants.d.ts +33 -0
- package/dist/constants.js +303 -0
- package/dist/iflow/apikey.d.ts +6 -0
- package/dist/iflow/apikey.js +17 -0
- package/dist/iflow/oauth.d.ts +20 -0
- package/dist/iflow/oauth.js +113 -0
- package/dist/iflow/proxy.d.ts +23 -0
- package/dist/iflow/proxy.js +435 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/plugin/accounts.d.ts +24 -0
- package/dist/plugin/accounts.js +205 -0
- package/dist/plugin/auth-page.d.ts +3 -0
- package/dist/plugin/auth-page.js +573 -0
- package/dist/plugin/cli.d.ts +11 -0
- package/dist/plugin/cli.js +77 -0
- package/dist/plugin/config/index.d.ts +2 -0
- package/dist/plugin/config/index.js +2 -0
- package/dist/plugin/config/loader.d.ts +3 -0
- package/dist/plugin/config/loader.js +110 -0
- package/dist/plugin/config/schema.d.ts +35 -0
- package/dist/plugin/config/schema.js +22 -0
- package/dist/plugin/errors.d.ts +14 -0
- package/dist/plugin/errors.js +25 -0
- package/dist/plugin/logger.d.ts +8 -0
- package/dist/plugin/logger.js +63 -0
- package/dist/plugin/server.d.ts +7 -0
- package/dist/plugin/server.js +98 -0
- package/dist/plugin/storage.d.ts +4 -0
- package/dist/plugin/storage.js +91 -0
- package/dist/plugin/token.d.ts +3 -0
- package/dist/plugin/token.js +26 -0
- package/dist/plugin/types.d.ts +58 -0
- package/dist/plugin/types.js +1 -0
- package/dist/plugin-iflow.d.ts +2 -0
- package/dist/plugin-iflow.js +141 -0
- package/dist/plugin-proxy.d.ts +2 -0
- package/dist/plugin-proxy.js +155 -0
- package/dist/plugin.d.ts +2 -0
- package/dist/plugin.js +2 -0
- package/package.json +63 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { loadConfig } from "./plugin/config/index.js";
|
|
2
|
+
import { AccountManager, generateAccountId } from "./plugin/accounts.js";
|
|
3
|
+
import { authorizeIFlowOAuth } from "./iflow/oauth.js";
|
|
4
|
+
import { validateApiKey } from "./iflow/apikey.js";
|
|
5
|
+
import { startOAuthServer } from "./plugin/server.js";
|
|
6
|
+
import { promptApiKey, promptEmail, } from "./plugin/cli.js";
|
|
7
|
+
import { IFLOW_CONSTANTS, registerIFlowModels } from "./constants.js";
|
|
8
|
+
const DEBUG = process.env.IFLOW_AUTH_DEBUG === 'true';
|
|
9
|
+
function log(...args) {
|
|
10
|
+
if (DEBUG) {
|
|
11
|
+
console.error('[iflow-auth]', ...args);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const openBrowser = (url) => {
|
|
15
|
+
const platform = process.platform;
|
|
16
|
+
if (platform === "win32") {
|
|
17
|
+
import("node:child_process").then(({ exec }) => {
|
|
18
|
+
exec(`cmd /c start "" "${url}"`);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
export const IFlowPlugin = async (input) => {
|
|
23
|
+
const config = loadConfig();
|
|
24
|
+
const showToast = (message, variant) => {
|
|
25
|
+
input.client.tui.showToast({ body: { message, variant } }).catch(() => { });
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
auth: {
|
|
29
|
+
provider: "iflow",
|
|
30
|
+
loader: async (getAuth, provider) => {
|
|
31
|
+
log('loader called');
|
|
32
|
+
const auth = await getAuth();
|
|
33
|
+
log('auth result:', auth ? 'found' : 'not found');
|
|
34
|
+
const am = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
35
|
+
log('account count:', am.getAccountCount());
|
|
36
|
+
registerIFlowModels(provider);
|
|
37
|
+
const accountCount = am.getAccountCount();
|
|
38
|
+
if (accountCount === 0) {
|
|
39
|
+
log('No accounts found in loader');
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
const firstAccount = am.getCurrentOrNext();
|
|
43
|
+
if (!firstAccount) {
|
|
44
|
+
log('No available account');
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
log('Using account:', firstAccount.email, 'apiKey:', firstAccount.apiKey.substring(0, 10) + '...');
|
|
48
|
+
return {
|
|
49
|
+
apiKey: firstAccount.apiKey,
|
|
50
|
+
baseURL: IFLOW_CONSTANTS.BASE_URL,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
methods: [
|
|
54
|
+
{
|
|
55
|
+
label: "iFlow OAuth 2.0",
|
|
56
|
+
type: "oauth",
|
|
57
|
+
authorize: async (inputs) => new Promise(async (resolve) => {
|
|
58
|
+
try {
|
|
59
|
+
const authData = await authorizeIFlowOAuth(config.auth_server_port_start);
|
|
60
|
+
const { url, waitForAuth } = await startOAuthServer(authData.authUrl, authData.state, authData.redirectUri, config.auth_server_port_start, config.auth_server_port_range);
|
|
61
|
+
openBrowser(url);
|
|
62
|
+
resolve({
|
|
63
|
+
url,
|
|
64
|
+
instructions: `Open this URL to continue: ${url}`,
|
|
65
|
+
method: "auto",
|
|
66
|
+
callback: async () => {
|
|
67
|
+
try {
|
|
68
|
+
const res = await waitForAuth();
|
|
69
|
+
const am = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
70
|
+
const acc = {
|
|
71
|
+
id: generateAccountId(),
|
|
72
|
+
email: res.email,
|
|
73
|
+
authMethod: "oauth",
|
|
74
|
+
refreshToken: res.refreshToken,
|
|
75
|
+
accessToken: res.accessToken,
|
|
76
|
+
expiresAt: res.expiresAt,
|
|
77
|
+
apiKey: res.apiKey,
|
|
78
|
+
rateLimitResetTime: 0,
|
|
79
|
+
isHealthy: true,
|
|
80
|
+
};
|
|
81
|
+
am.addAccount(acc);
|
|
82
|
+
await am.saveToDisk();
|
|
83
|
+
showToast(`Successfully logged in as ${res.email}`, "success");
|
|
84
|
+
return { type: "success", key: res.apiKey };
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
showToast(`Login failed: ${e.message}`, "error");
|
|
88
|
+
return { type: "failed" };
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
resolve({
|
|
95
|
+
url: "",
|
|
96
|
+
instructions: "Authorization failed",
|
|
97
|
+
method: "auto",
|
|
98
|
+
callback: async () => ({ type: "failed" }),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
label: "iFlow API Key",
|
|
105
|
+
type: "api",
|
|
106
|
+
authorize: async (inputs) => {
|
|
107
|
+
if (!inputs) {
|
|
108
|
+
return { type: "failed" };
|
|
109
|
+
}
|
|
110
|
+
const apiKey = await promptApiKey();
|
|
111
|
+
if (!apiKey) {
|
|
112
|
+
return { type: "failed" };
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
await validateApiKey(apiKey);
|
|
116
|
+
const email = await promptEmail();
|
|
117
|
+
const am = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
118
|
+
const acc = {
|
|
119
|
+
id: generateAccountId(),
|
|
120
|
+
email,
|
|
121
|
+
authMethod: "apikey",
|
|
122
|
+
apiKey,
|
|
123
|
+
rateLimitResetTime: 0,
|
|
124
|
+
isHealthy: true,
|
|
125
|
+
};
|
|
126
|
+
am.addAccount(acc);
|
|
127
|
+
await am.saveToDisk();
|
|
128
|
+
return { type: "success", key: apiKey };
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
return { type: "failed" };
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
"chat.headers": async (input, output) => {
|
|
138
|
+
output.headers["User-Agent"] = `${IFLOW_CONSTANTS.USER_AGENT}/2.0`;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { loadConfig } from "./plugin/config/index.js";
|
|
2
|
+
import { AccountManager, generateAccountId } from "./plugin/accounts.js";
|
|
3
|
+
import { authorizeIFlowOAuth } from "./iflow/oauth.js";
|
|
4
|
+
import { validateApiKey } from "./iflow/apikey.js";
|
|
5
|
+
import { startOAuthServer } from "./plugin/server.js";
|
|
6
|
+
import { startProxy } from "./iflow/proxy.js";
|
|
7
|
+
import { promptApiKey, promptEmail, } from "./plugin/cli.js";
|
|
8
|
+
import { IFLOW_CONSTANTS, registerIFlowModels } from "./constants.js";
|
|
9
|
+
const DEBUG = process.env.IFLOW_PROXY_DEBUG === 'true';
|
|
10
|
+
function log(...args) {
|
|
11
|
+
if (DEBUG) {
|
|
12
|
+
console.error('[iflow-proxy]', ...args);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const openBrowser = (url) => {
|
|
16
|
+
const platform = process.platform;
|
|
17
|
+
if (platform === "win32") {
|
|
18
|
+
import("node:child_process").then(({ exec }) => {
|
|
19
|
+
exec(`cmd /c start "" "${url}"`);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
export const IFlowProxyPlugin = async (input) => {
|
|
24
|
+
const config = loadConfig();
|
|
25
|
+
const showToast = (message, variant) => {
|
|
26
|
+
input.client.tui.showToast({ body: { message, variant } }).catch(() => { });
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
auth: {
|
|
30
|
+
provider: "iflow-proxy",
|
|
31
|
+
loader: async (getAuth, provider) => {
|
|
32
|
+
log('loader called');
|
|
33
|
+
const auth = await getAuth();
|
|
34
|
+
log('auth result:', auth ? 'found' : 'not found');
|
|
35
|
+
const am = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
36
|
+
log('account count:', am.getAccountCount());
|
|
37
|
+
registerIFlowModels(provider);
|
|
38
|
+
const accountCount = am.getAccountCount();
|
|
39
|
+
if (accountCount === 0) {
|
|
40
|
+
log('No accounts found in loader');
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
const firstAccount = am.getCurrentOrNext();
|
|
44
|
+
if (!firstAccount) {
|
|
45
|
+
log('No available account');
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
log('Using account:', firstAccount.email, 'apiKey:', firstAccount.apiKey.substring(0, 10) + '...');
|
|
49
|
+
try {
|
|
50
|
+
log('Starting CLI proxy...');
|
|
51
|
+
const proxy = await startProxy();
|
|
52
|
+
const proxyUrl = proxy.getBaseUrl();
|
|
53
|
+
log('CLI proxy started at:', proxyUrl);
|
|
54
|
+
return {
|
|
55
|
+
apiKey: firstAccount.apiKey,
|
|
56
|
+
baseURL: proxyUrl,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
log('Failed to start proxy, using direct API:', err.message);
|
|
61
|
+
return {
|
|
62
|
+
apiKey: firstAccount.apiKey,
|
|
63
|
+
baseURL: IFLOW_CONSTANTS.BASE_URL,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
methods: [
|
|
68
|
+
{
|
|
69
|
+
label: "iFlow OAuth 2.0",
|
|
70
|
+
type: "oauth",
|
|
71
|
+
authorize: async (inputs) => new Promise(async (resolve) => {
|
|
72
|
+
try {
|
|
73
|
+
const authData = await authorizeIFlowOAuth(config.auth_server_port_start);
|
|
74
|
+
const { url, waitForAuth } = await startOAuthServer(authData.authUrl, authData.state, authData.redirectUri, config.auth_server_port_start, config.auth_server_port_range);
|
|
75
|
+
openBrowser(url);
|
|
76
|
+
resolve({
|
|
77
|
+
url,
|
|
78
|
+
instructions: `Open this URL to continue: ${url}`,
|
|
79
|
+
method: "auto",
|
|
80
|
+
callback: async () => {
|
|
81
|
+
try {
|
|
82
|
+
const res = await waitForAuth();
|
|
83
|
+
const am = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
84
|
+
const acc = {
|
|
85
|
+
id: generateAccountId(),
|
|
86
|
+
email: res.email,
|
|
87
|
+
authMethod: "oauth",
|
|
88
|
+
refreshToken: res.refreshToken,
|
|
89
|
+
accessToken: res.accessToken,
|
|
90
|
+
expiresAt: res.expiresAt,
|
|
91
|
+
apiKey: res.apiKey,
|
|
92
|
+
rateLimitResetTime: 0,
|
|
93
|
+
isHealthy: true,
|
|
94
|
+
};
|
|
95
|
+
am.addAccount(acc);
|
|
96
|
+
await am.saveToDisk();
|
|
97
|
+
showToast(`Successfully logged in as ${res.email}`, "success");
|
|
98
|
+
return { type: "success", key: res.apiKey };
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
showToast(`Login failed: ${e.message}`, "error");
|
|
102
|
+
return { type: "failed" };
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
resolve({
|
|
109
|
+
url: "",
|
|
110
|
+
instructions: "Authorization failed",
|
|
111
|
+
method: "auto",
|
|
112
|
+
callback: async () => ({ type: "failed" }),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}),
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
label: "iFlow API Key",
|
|
119
|
+
type: "api",
|
|
120
|
+
authorize: async (inputs) => {
|
|
121
|
+
if (!inputs) {
|
|
122
|
+
return { type: "failed" };
|
|
123
|
+
}
|
|
124
|
+
const apiKey = await promptApiKey();
|
|
125
|
+
if (!apiKey) {
|
|
126
|
+
return { type: "failed" };
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
await validateApiKey(apiKey);
|
|
130
|
+
const email = await promptEmail();
|
|
131
|
+
const am = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
132
|
+
const acc = {
|
|
133
|
+
id: generateAccountId(),
|
|
134
|
+
email,
|
|
135
|
+
authMethod: "apikey",
|
|
136
|
+
apiKey,
|
|
137
|
+
rateLimitResetTime: 0,
|
|
138
|
+
isHealthy: true,
|
|
139
|
+
};
|
|
140
|
+
am.addAccount(acc);
|
|
141
|
+
await am.saveToDisk();
|
|
142
|
+
return { type: "success", key: apiKey };
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
return { type: "failed" };
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
"chat.headers": async (input, output) => {
|
|
152
|
+
output.headers["User-Agent"] = `${IFLOW_CONSTANTS.USER_AGENT}/2.0`;
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
};
|
package/dist/plugin.d.ts
ADDED
package/dist/plugin.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@asframe/opencode-iflow-auth",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenCode plugin for iFlow.cn - Access Qwen, DeepSeek, Kimi, GLM-5 models with OAuth 2.0, API Key, and CLI Proxy support",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc -p tsconfig.build.json",
|
|
10
|
+
"format": "prettier --write --no-config --no-semi --single-quote --trailing-comma none --print-width 100 'src/**/*.ts'",
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"opencode",
|
|
16
|
+
"plugin",
|
|
17
|
+
"iflow",
|
|
18
|
+
"glm-5",
|
|
19
|
+
"qwen",
|
|
20
|
+
"deepseek",
|
|
21
|
+
"kimi",
|
|
22
|
+
"ai",
|
|
23
|
+
"auth",
|
|
24
|
+
"oauth",
|
|
25
|
+
"apikey",
|
|
26
|
+
"cli-proxy"
|
|
27
|
+
],
|
|
28
|
+
"author": "asframe",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/asframe/opencode-iflow-auth.git"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@opencode-ai/plugin": "^1.2.6",
|
|
39
|
+
"proper-lockfile": "^4.1.2",
|
|
40
|
+
"tiktoken": "^1.0.17",
|
|
41
|
+
"zod": "^3.24.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^20.0.0",
|
|
45
|
+
"@types/proper-lockfile": "^4.1.4",
|
|
46
|
+
"prettier": "^3.4.2",
|
|
47
|
+
"prettier-plugin-organize-imports": "^4.1.0",
|
|
48
|
+
"typescript": "^5.7.3"
|
|
49
|
+
},
|
|
50
|
+
"opencode": {
|
|
51
|
+
"type": "plugin",
|
|
52
|
+
"hooks": [
|
|
53
|
+
"auth",
|
|
54
|
+
"event",
|
|
55
|
+
"chat.headers"
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
"files": [
|
|
59
|
+
"dist",
|
|
60
|
+
"package.json",
|
|
61
|
+
"README.md"
|
|
62
|
+
]
|
|
63
|
+
}
|