opencode-deepseek-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/.github/workflows/main.yml +23 -0
- package/README.md +126 -0
- package/dist/constants.d.ts +37 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +44 -0
- package/dist/constants.js.map +1 -0
- package/dist/deepseek/auth.d.ts +37 -0
- package/dist/deepseek/auth.d.ts.map +1 -0
- package/dist/deepseek/auth.js +94 -0
- package/dist/deepseek/auth.js.map +1 -0
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +182 -0
- package/dist/plugin.js.map +1 -0
- package/dist/types.d.ts +40 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/index.ts +13 -0
- package/package.json +28 -0
- package/src/constants.ts +46 -0
- package/src/deepseek/auth.ts +123 -0
- package/src/plugin.ts +234 -0
- package/src/types.ts +46 -0
- package/test.js +28 -0
- package/test.ts +23 -0
- package/tsconfig.json +32 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Node.js Package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
publish-npm:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/checkout@v4
|
|
11
|
+
|
|
12
|
+
- uses: actions/setup-node@v4
|
|
13
|
+
with:
|
|
14
|
+
node-version: 20
|
|
15
|
+
registry-url: https://registry.npmjs.org/
|
|
16
|
+
|
|
17
|
+
# BƯỚC QUAN TRỌNG: Phải cài thư viện thì mới build được
|
|
18
|
+
- run: npm ci
|
|
19
|
+
|
|
20
|
+
# Lệnh này sẽ tự động chạy: prepare -> build -> publish
|
|
21
|
+
- run: npm publish
|
|
22
|
+
env:
|
|
23
|
+
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
|
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# DeepSeek Authentication Plugin for OpenCode
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
**Authenticate the OpenCode CLI with your DeepSeek account credentials.** This plugin enables
|
|
7
|
+
you to use your existing DeepSeek account directly within OpenCode, bypassing the need for separate API keys.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
- [OpenCode CLI](https://opencode.ai) installed.
|
|
12
|
+
- A DeepSeek account with access to their service.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
Add the plugin to your OpenCode configuration file
|
|
17
|
+
(`~/.config/opencode/opencode.json` or similar):
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"$schema": "https://opencode.ai/config.json",
|
|
22
|
+
"plugin": ["opencode-deepseek-auth@latest"]
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
1. **Login**: Run the authentication command in your terminal:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
opencode auth login
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
2. **Select Provider**: Choose **DeepSeek** from the list.
|
|
35
|
+
3. **Authenticate**: Select **Login with DeepSeek Account**.
|
|
36
|
+
- Enter your email and password when prompted
|
|
37
|
+
- Your credentials will be stored securely for future use
|
|
38
|
+
|
|
39
|
+
Once authenticated, OpenCode will use your DeepSeek account for requests.
|
|
40
|
+
|
|
41
|
+
## Configuration
|
|
42
|
+
|
|
43
|
+
### Model list
|
|
44
|
+
|
|
45
|
+
Below are example model entries you can add under `provider.deepseek.models` in your
|
|
46
|
+
OpenCode config.
|
|
47
|
+
|
|
48
|
+
**File:** `~/.config/opencode/opencode.json`
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"provider": {
|
|
52
|
+
"deepseek": {
|
|
53
|
+
"models": {
|
|
54
|
+
"deepseek-chat": {
|
|
55
|
+
"displayName": "DeepSeek Chat",
|
|
56
|
+
"maxTokens": 8192
|
|
57
|
+
},
|
|
58
|
+
"deepseek-reasoner": {
|
|
59
|
+
"displayName": "DeepSeek Reasoner",
|
|
60
|
+
"maxTokens": 8192
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Features
|
|
69
|
+
|
|
70
|
+
- Direct authentication using DeepSeek account credentials (email/password)
|
|
71
|
+
- Secure storage of credentials
|
|
72
|
+
- Automatic token refresh when expired
|
|
73
|
+
- Compatibility with OpenCode's authentication system
|
|
74
|
+
- Free usage through your existing DeepSeek account
|
|
75
|
+
|
|
76
|
+
## How It Works
|
|
77
|
+
|
|
78
|
+
The plugin works by:
|
|
79
|
+
1. Authenticating directly with DeepSeek's API using your email and password
|
|
80
|
+
2. Obtaining an access token from DeepSeek
|
|
81
|
+
3. Using this token to make authenticated requests to DeepSeek's API
|
|
82
|
+
4. Managing token lifecycle and refreshing when needed
|
|
83
|
+
|
|
84
|
+
This approach bypasses the need for separate API keys, letting you use your existing DeepSeek account quota and features.
|
|
85
|
+
|
|
86
|
+
## Troubleshooting
|
|
87
|
+
|
|
88
|
+
### Credentials Not Working
|
|
89
|
+
|
|
90
|
+
- Verify that your email and password are correct
|
|
91
|
+
- Check that your account is active and not suspended
|
|
92
|
+
- Make sure you're not hitting rate limits
|
|
93
|
+
|
|
94
|
+
### Token Expiration
|
|
95
|
+
|
|
96
|
+
The plugin handles token refresh automatically, but if you encounter authentication errors, you can re-authenticate with:
|
|
97
|
+
```bash
|
|
98
|
+
opencode auth login
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Development
|
|
102
|
+
|
|
103
|
+
To develop on this plugin locally:
|
|
104
|
+
|
|
105
|
+
1. **Clone**:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
git clone https://github.com/yourusername/opencode-deepseek-auth.git
|
|
109
|
+
cd opencode-deepseek-auth
|
|
110
|
+
npm install
|
|
111
|
+
npm run build
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
2. **Link**:
|
|
115
|
+
Update your OpenCode config to point to your local directory using a
|
|
116
|
+
`file://` URL:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"plugin": ["file:///absolute/path/to/opencode-deepseek-auth"]
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants used for DeepSeek authentication flows and API integration.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* OAuth redirect URI used by the local CLI callback server.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEEPSEEK_REDIRECT_URI = "http://localhost:8085/oauth2callback";
|
|
8
|
+
/**
|
|
9
|
+
* Base endpoint for the DeepSeek API
|
|
10
|
+
*/
|
|
11
|
+
export declare const DEEPSEEK_API_BASE_URL = "https://chat.deepseek.com/api/v0";
|
|
12
|
+
/**
|
|
13
|
+
* Login endpoint
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEEPSEEK_LOGIN_URL = "https://chat.deepseek.com/api/v0/users/login";
|
|
16
|
+
/**
|
|
17
|
+
* Headers used for DeepSeek API requests
|
|
18
|
+
*/
|
|
19
|
+
export declare const DEEPSEEK_BASE_HEADERS: {
|
|
20
|
+
readonly Host: "chat.deepseek.com";
|
|
21
|
+
readonly "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
|
22
|
+
readonly Accept: "application/json";
|
|
23
|
+
readonly "Accept-Encoding": "gzip";
|
|
24
|
+
readonly "Content-Type": "application/json";
|
|
25
|
+
readonly "x-client-platform": "web";
|
|
26
|
+
readonly "x-client-version": "1.5.0";
|
|
27
|
+
readonly "x-client-locale": "en_US";
|
|
28
|
+
readonly "accept-charset": "UTF-8";
|
|
29
|
+
readonly origin: "https://chat.deepseek.com";
|
|
30
|
+
readonly referer: "https://chat.deepseek.com/";
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Provider identifier shared between the plugin loader and credential store.
|
|
34
|
+
*/
|
|
35
|
+
export declare const DEEPSEEK_PROVIDER_ID = "deepseek";
|
|
36
|
+
export declare const DEEPSEEK_AUTH_SCOPES: string[];
|
|
37
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,eAAO,MAAM,qBAAqB,yCAAyC,CAAC;AAE5E;;GAEG;AACH,eAAO,MAAM,qBAAqB,qCAAqC,CAAC;AAExE;;GAEG;AACH,eAAO,MAAM,kBAAkB,iDAAyC,CAAC;AAEzE;;GAEG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;CAYxB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,oBAAoB,aAAa,CAAC;AAE/C,eAAO,MAAM,oBAAoB,UAIhC,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Constants used for DeepSeek authentication flows and API integration.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEEPSEEK_AUTH_SCOPES = exports.DEEPSEEK_PROVIDER_ID = exports.DEEPSEEK_BASE_HEADERS = exports.DEEPSEEK_LOGIN_URL = exports.DEEPSEEK_API_BASE_URL = exports.DEEPSEEK_REDIRECT_URI = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* OAuth redirect URI used by the local CLI callback server.
|
|
9
|
+
*/
|
|
10
|
+
exports.DEEPSEEK_REDIRECT_URI = "http://localhost:8085/oauth2callback";
|
|
11
|
+
/**
|
|
12
|
+
* Base endpoint for the DeepSeek API
|
|
13
|
+
*/
|
|
14
|
+
exports.DEEPSEEK_API_BASE_URL = "https://chat.deepseek.com/api/v0";
|
|
15
|
+
/**
|
|
16
|
+
* Login endpoint
|
|
17
|
+
*/
|
|
18
|
+
exports.DEEPSEEK_LOGIN_URL = `${exports.DEEPSEEK_API_BASE_URL}/users/login`;
|
|
19
|
+
/**
|
|
20
|
+
* Headers used for DeepSeek API requests
|
|
21
|
+
*/
|
|
22
|
+
exports.DEEPSEEK_BASE_HEADERS = {
|
|
23
|
+
"Host": "chat.deepseek.com",
|
|
24
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
25
|
+
"Accept": "application/json",
|
|
26
|
+
"Accept-Encoding": "gzip",
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
"x-client-platform": "web",
|
|
29
|
+
"x-client-version": "1.5.0",
|
|
30
|
+
"x-client-locale": "en_US",
|
|
31
|
+
"accept-charset": "UTF-8",
|
|
32
|
+
"origin": "https://chat.deepseek.com",
|
|
33
|
+
"referer": "https://chat.deepseek.com/"
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Provider identifier shared between the plugin loader and credential store.
|
|
37
|
+
*/
|
|
38
|
+
exports.DEEPSEEK_PROVIDER_ID = "deepseek";
|
|
39
|
+
exports.DEEPSEEK_AUTH_SCOPES = [
|
|
40
|
+
"user:profile",
|
|
41
|
+
"user:chat",
|
|
42
|
+
"user:tokens"
|
|
43
|
+
];
|
|
44
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAEH;;GAEG;AACU,QAAA,qBAAqB,GAAG,sCAAsC,CAAC;AAE5E;;GAEG;AACU,QAAA,qBAAqB,GAAG,kCAAkC,CAAC;AAExE;;GAEG;AACU,QAAA,kBAAkB,GAAG,GAAG,6BAAqB,cAAc,CAAC;AAEzE;;GAEG;AACU,QAAA,qBAAqB,GAAG;IACnC,MAAM,EAAE,mBAAmB;IAC3B,YAAY,EAAE,uHAAuH;IACrI,QAAQ,EAAE,kBAAkB;IAC5B,iBAAiB,EAAE,MAAM;IACzB,cAAc,EAAE,kBAAkB;IAClC,mBAAmB,EAAE,KAAK;IAC1B,kBAAkB,EAAE,OAAO;IAC3B,iBAAiB,EAAE,OAAO;IAC1B,gBAAgB,EAAE,OAAO;IACzB,QAAQ,EAAE,2BAA2B;IACrC,SAAS,EAAE,4BAA4B;CAC/B,CAAC;AAEX;;GAEG;AACU,QAAA,oBAAoB,GAAG,UAAU,CAAC;AAElC,QAAA,oBAAoB,GAAG;IAClC,cAAc;IACd,WAAW;IACX,aAAa;CACd,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result returned to the caller after constructing an auth URL or completing login.
|
|
3
|
+
*/
|
|
4
|
+
export interface DeepSeekAuthorization {
|
|
5
|
+
url?: string;
|
|
6
|
+
verifier: string;
|
|
7
|
+
success?: boolean;
|
|
8
|
+
token?: string;
|
|
9
|
+
}
|
|
10
|
+
interface DeepSeekTokenExchangeSuccess {
|
|
11
|
+
type: "success";
|
|
12
|
+
access: string;
|
|
13
|
+
expires: number;
|
|
14
|
+
email?: string;
|
|
15
|
+
}
|
|
16
|
+
interface DeepSeekTokenExchangeFailure {
|
|
17
|
+
type: "failed";
|
|
18
|
+
error: string;
|
|
19
|
+
}
|
|
20
|
+
export type DeepSeekTokenExchangeResult = DeepSeekTokenExchangeSuccess | DeepSeekTokenExchangeFailure;
|
|
21
|
+
/**
|
|
22
|
+
* Direct login to DeepSeek using email/password
|
|
23
|
+
*/
|
|
24
|
+
export declare function loginDeepSeek(email: string, password: string): Promise<DeepSeekTokenExchangeResult>;
|
|
25
|
+
/**
|
|
26
|
+
* Exchange credentials for tokens (for direct login approach)
|
|
27
|
+
*/
|
|
28
|
+
export declare function exchangeDeepSeek(credentials: {
|
|
29
|
+
email: string;
|
|
30
|
+
password: string;
|
|
31
|
+
}): Promise<DeepSeekTokenExchangeResult>;
|
|
32
|
+
/**
|
|
33
|
+
* Placeholder authorize function for compatibility
|
|
34
|
+
*/
|
|
35
|
+
export declare function authorizeDeepSeek(): Promise<DeepSeekAuthorization>;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/deepseek/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,4BAA4B;IACpC,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,4BAA4B;IACpC,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,2BAA2B,GACnC,4BAA4B,GAC5B,4BAA4B,CAAC;AAEjC;;GAEG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAyEzG;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAE7H;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAQxE"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loginDeepSeek = loginDeepSeek;
|
|
4
|
+
exports.exchangeDeepSeek = exchangeDeepSeek;
|
|
5
|
+
exports.authorizeDeepSeek = authorizeDeepSeek;
|
|
6
|
+
/**
|
|
7
|
+
* Direct login to DeepSeek using email/password
|
|
8
|
+
*/
|
|
9
|
+
async function loginDeepSeek(email, password) {
|
|
10
|
+
try {
|
|
11
|
+
// Prepare login payload
|
|
12
|
+
const payload = {
|
|
13
|
+
email,
|
|
14
|
+
password,
|
|
15
|
+
device_id: "opencode-plugin",
|
|
16
|
+
os: "web"
|
|
17
|
+
};
|
|
18
|
+
// Make login request to DeepSeek API
|
|
19
|
+
const response = await fetch("https://chat.deepseek.com/api/v0/users/login", {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: {
|
|
22
|
+
"Host": "chat.deepseek.com",
|
|
23
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
24
|
+
"Accept": "application/json",
|
|
25
|
+
"Accept-Encoding": "gzip",
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
"x-client-platform": "web",
|
|
28
|
+
"x-client-version": "1.5.0",
|
|
29
|
+
"x-client-locale": "en_US",
|
|
30
|
+
"accept-charset": "UTF-8",
|
|
31
|
+
"origin": "https://chat.deepseek.com",
|
|
32
|
+
"referer": "https://chat.deepseek.com/"
|
|
33
|
+
},
|
|
34
|
+
body: JSON.stringify(payload)
|
|
35
|
+
});
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const errorText = await response.text();
|
|
38
|
+
return {
|
|
39
|
+
type: "failed",
|
|
40
|
+
error: `Login failed with status ${response.status}: ${errorText}`
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const data = await response.json();
|
|
44
|
+
if (!data.data || !data.data.biz_data || !data.data.biz_data.user) {
|
|
45
|
+
return {
|
|
46
|
+
type: "failed",
|
|
47
|
+
error: "Invalid response format from DeepSeek login API"
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const userData = data.data.biz_data.user;
|
|
51
|
+
const token = userData.token;
|
|
52
|
+
if (!token) {
|
|
53
|
+
return {
|
|
54
|
+
type: "failed",
|
|
55
|
+
error: "No token returned from DeepSeek login"
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// Calculate expiration (default to 1 hour if not provided)
|
|
59
|
+
// In the actual DeepSeek API response, look for expires_in field
|
|
60
|
+
const expiresIn = data.data.biz_data.expires_in || 3600;
|
|
61
|
+
const expiresAt = Date.now() + (expiresIn * 1000);
|
|
62
|
+
return {
|
|
63
|
+
type: "success",
|
|
64
|
+
access: token,
|
|
65
|
+
expires: expiresAt,
|
|
66
|
+
email: userData.email || email
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
return {
|
|
71
|
+
type: "failed",
|
|
72
|
+
error: error instanceof Error ? error.message : "Unknown error occurred during login"
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Exchange credentials for tokens (for direct login approach)
|
|
78
|
+
*/
|
|
79
|
+
async function exchangeDeepSeek(credentials) {
|
|
80
|
+
return loginDeepSeek(credentials.email, credentials.password);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Placeholder authorize function for compatibility
|
|
84
|
+
*/
|
|
85
|
+
async function authorizeDeepSeek() {
|
|
86
|
+
// Since DeepSeek doesn't use standard OAuth, we'll return info indicating
|
|
87
|
+
// that direct login should be used
|
|
88
|
+
return {
|
|
89
|
+
verifier: "", // Placeholder since we don't use PKCE
|
|
90
|
+
success: false,
|
|
91
|
+
url: "Direct login required - use email and password"
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/deepseek/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PluginContext, PluginResult } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Registers the DeepSeek Auth provider for Opencode, handling auth, request rewriting,
|
|
4
|
+
* and response normalization for DeepSeek requests.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DeepSeekAuthPlugin: ({ client }: PluginContext) => Promise<PluginResult>;
|
|
7
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAGV,aAAa,EACb,YAAY,EAEb,MAAM,SAAS,CAAC;AAqGjB;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC7B,YAAY,aAAa,KACxB,OAAO,CAAC,YAAY,CA8GrB,CAAC"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DeepSeekAuthPlugin = void 0;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
const auth_1 = require("./deepseek/auth");
|
|
6
|
+
// Keep track of active tokens
|
|
7
|
+
const tokenCache = new Map();
|
|
8
|
+
/**
|
|
9
|
+
* Checks if an access token has expired
|
|
10
|
+
*/
|
|
11
|
+
function accessTokenExpired(authRecord) {
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
return !authRecord.expires || now >= authRecord.expires - 60000; // 1 minute before expiry
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Determines if the auth method is OAuth-based
|
|
17
|
+
*/
|
|
18
|
+
function isOAuthAuth(auth) {
|
|
19
|
+
return auth && auth.type === "oauth";
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Refresh access token if expired
|
|
23
|
+
*/
|
|
24
|
+
async function refreshAccessToken(authRecord, client) {
|
|
25
|
+
// DeepSeek doesn't have refresh tokens, so a full re-login is required
|
|
26
|
+
const email = authRecord.email;
|
|
27
|
+
const password = authRecord.password;
|
|
28
|
+
if (!email || !password) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const result = await (0, auth_1.loginDeepSeek)(email, password);
|
|
32
|
+
if (result.type === "success") {
|
|
33
|
+
// Update the stored credentials
|
|
34
|
+
const newAuth = {
|
|
35
|
+
type: "oauth",
|
|
36
|
+
access: result.access,
|
|
37
|
+
expires: result.expires,
|
|
38
|
+
email: result.email || email,
|
|
39
|
+
password: password // Store password for refresh
|
|
40
|
+
};
|
|
41
|
+
// Update cache
|
|
42
|
+
tokenCache.set(email, {
|
|
43
|
+
token: result.access,
|
|
44
|
+
expires: result.expires,
|
|
45
|
+
email: result.email || email
|
|
46
|
+
});
|
|
47
|
+
return newAuth;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Prepares the request to DeepSeek API by setting appropriate headers and transforming the payload
|
|
53
|
+
*/
|
|
54
|
+
function prepareDeepSeekRequest(input, init, accessToken) {
|
|
55
|
+
// Clone the original init to avoid mutating it
|
|
56
|
+
const transformedInit = { ...init };
|
|
57
|
+
// Set authorization header
|
|
58
|
+
transformedInit.headers = {
|
|
59
|
+
...transformedInit.headers,
|
|
60
|
+
...constants_1.DEEPSEEK_BASE_HEADERS,
|
|
61
|
+
"authorization": `Bearer ${accessToken}`
|
|
62
|
+
};
|
|
63
|
+
// Construct the request
|
|
64
|
+
const request = new Request(input, transformedInit);
|
|
65
|
+
return { request, init: transformedInit };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Transforms the DeepSeek API response to match expected format
|
|
69
|
+
*/
|
|
70
|
+
async function transformDeepSeekResponse(response) {
|
|
71
|
+
// For now, just pass through the response
|
|
72
|
+
// If needed, we could transform to match OpenAI format
|
|
73
|
+
return response;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Validates if this is a DeepSeek API request
|
|
77
|
+
*/
|
|
78
|
+
function isDeepSeekRequest(input) {
|
|
79
|
+
const urlString = typeof input === 'string' ? input :
|
|
80
|
+
input instanceof URL ? input.toString() :
|
|
81
|
+
input.url || '';
|
|
82
|
+
return urlString.includes('deepseek.com') || urlString.includes('/v1/chat/completions');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Registers the DeepSeek Auth provider for Opencode, handling auth, request rewriting,
|
|
86
|
+
* and response normalization for DeepSeek requests.
|
|
87
|
+
*/
|
|
88
|
+
const DeepSeekAuthPlugin = async ({ client }) => ({
|
|
89
|
+
auth: {
|
|
90
|
+
provider: constants_1.DEEPSEEK_PROVIDER_ID,
|
|
91
|
+
loader: async (getAuth, provider) => {
|
|
92
|
+
const auth = await getAuth();
|
|
93
|
+
if (!isOAuthAuth(auth)) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
// If models are defined in the provider, set cost to 0 to indicate free usage
|
|
97
|
+
if (provider.models) {
|
|
98
|
+
for (const model of Object.values(provider.models)) {
|
|
99
|
+
if (model) {
|
|
100
|
+
model.cost = { input: 0, output: 0 };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
apiKey: "",
|
|
106
|
+
async fetch(input, init) {
|
|
107
|
+
// If this isn't a DeepSeek request, pass through normally
|
|
108
|
+
if (!isDeepSeekRequest(input)) {
|
|
109
|
+
return fetch(input, init);
|
|
110
|
+
}
|
|
111
|
+
// Get current auth state
|
|
112
|
+
const latestAuth = await getAuth();
|
|
113
|
+
if (!isOAuthAuth(latestAuth)) {
|
|
114
|
+
return fetch(input, init);
|
|
115
|
+
}
|
|
116
|
+
let authRecord = latestAuth;
|
|
117
|
+
// Check if token has expired and refresh if possible
|
|
118
|
+
if (accessTokenExpired(authRecord)) {
|
|
119
|
+
const refreshed = await refreshAccessToken(authRecord, client);
|
|
120
|
+
if (!refreshed) {
|
|
121
|
+
console.warn("Could not refresh DeepSeek access token");
|
|
122
|
+
return fetch(input, init);
|
|
123
|
+
}
|
|
124
|
+
authRecord = refreshed;
|
|
125
|
+
}
|
|
126
|
+
const accessToken = authRecord.access;
|
|
127
|
+
if (!accessToken) {
|
|
128
|
+
return fetch(input, init);
|
|
129
|
+
}
|
|
130
|
+
// Prepare the request with proper headers
|
|
131
|
+
const { request, init: transformedInit } = prepareDeepSeekRequest(input, init, accessToken);
|
|
132
|
+
// Make the API call
|
|
133
|
+
const response = await fetch(request, transformedInit);
|
|
134
|
+
// Transform response if needed
|
|
135
|
+
return transformDeepSeekResponse(response);
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
methods: [
|
|
140
|
+
{
|
|
141
|
+
label: "Login with DeepSeek Account",
|
|
142
|
+
type: "oauth",
|
|
143
|
+
authorize: async () => {
|
|
144
|
+
// For DeepSeek, we'll implement direct credential-based auth
|
|
145
|
+
// since they don't use standard OAuth
|
|
146
|
+
return {
|
|
147
|
+
url: "",
|
|
148
|
+
instructions: "Please enter your DeepSeek account credentials (email and password). Your credentials will be stored securely and used for authentication.",
|
|
149
|
+
method: "credentials",
|
|
150
|
+
callback: async (credentials) => {
|
|
151
|
+
// Validate inputs
|
|
152
|
+
if (!credentials.email || !credentials.password) {
|
|
153
|
+
return {
|
|
154
|
+
type: "failed",
|
|
155
|
+
error: "Email and password are required for DeepSeek authentication"
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
// Attempt to log in using the provided credentials
|
|
159
|
+
const result = await (0, auth_1.loginDeepSeek)(credentials.email, credentials.password);
|
|
160
|
+
if (result.type === "success") {
|
|
161
|
+
// Cache the token
|
|
162
|
+
tokenCache.set(credentials.email, {
|
|
163
|
+
token: result.access,
|
|
164
|
+
expires: result.expires,
|
|
165
|
+
email: result.email || credentials.email
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
provider: constants_1.DEEPSEEK_PROVIDER_ID,
|
|
175
|
+
label: "Manually enter API Key",
|
|
176
|
+
type: "api",
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
exports.DeepSeekAuthPlugin = DeepSeekAuthPlugin;
|
|
182
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the DeepSeek Auth plugin
|
|
3
|
+
*/
|
|
4
|
+
export interface PluginContext {
|
|
5
|
+
client: any;
|
|
6
|
+
}
|
|
7
|
+
export interface PluginResult {
|
|
8
|
+
auth: {
|
|
9
|
+
provider: string;
|
|
10
|
+
loader: (getAuth: GetAuth, provider: Provider) => Promise<LoaderResult | null>;
|
|
11
|
+
methods: AuthMethod[];
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export interface GetAuth {
|
|
15
|
+
(): Promise<any>;
|
|
16
|
+
}
|
|
17
|
+
export interface LoaderResult {
|
|
18
|
+
apiKey: string;
|
|
19
|
+
fetch: (input: Parameters<typeof fetch>[0], init?: RequestInit) => Promise<Response>;
|
|
20
|
+
}
|
|
21
|
+
export interface Provider {
|
|
22
|
+
id?: string;
|
|
23
|
+
models?: Record<string, any>;
|
|
24
|
+
options?: Record<string, any>;
|
|
25
|
+
}
|
|
26
|
+
export interface AuthMethod {
|
|
27
|
+
label: string;
|
|
28
|
+
type: string;
|
|
29
|
+
provider?: string;
|
|
30
|
+
authorize?: () => Promise<{
|
|
31
|
+
url: string;
|
|
32
|
+
instructions: string;
|
|
33
|
+
method: string;
|
|
34
|
+
callback: (params?: any) => Promise<any>;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
export interface ProjectContextResult {
|
|
38
|
+
effectiveProjectId: string;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,GAAG,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;QAC/E,OAAO,EAAE,UAAU,EAAE,CAAC;KACvB,CAAC;CACH;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CACtF;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC;QACxB,GAAG,EAAE,MAAM,CAAC;QACZ,YAAY,EAAE,MAAM,CAAC;QACrB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;KAC1C,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,oBAAoB;IACnC,kBAAkB,EAAE,MAAM,CAAC;CAC5B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;GAEG"}
|
package/index.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-deepseek-auth",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Authenticate the Opencode CLI with your DeepSeek account credentials",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepublishOnly": "npm run build"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"opencode",
|
|
13
|
+
"deepseek",
|
|
14
|
+
"authentication",
|
|
15
|
+
"oauth",
|
|
16
|
+
"plugin"
|
|
17
|
+
],
|
|
18
|
+
"author": "OpenCode",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"node-fetch": "^2.6.7"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.0.0",
|
|
25
|
+
"@types/node-fetch": "^2.6.4",
|
|
26
|
+
"typescript": "^5.0.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants used for DeepSeek authentication flows and API integration.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* OAuth redirect URI used by the local CLI callback server.
|
|
7
|
+
*/
|
|
8
|
+
export const DEEPSEEK_REDIRECT_URI = "http://localhost:8085/oauth2callback";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Base endpoint for the DeepSeek API
|
|
12
|
+
*/
|
|
13
|
+
export const DEEPSEEK_API_BASE_URL = "https://chat.deepseek.com/api/v0";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Login endpoint
|
|
17
|
+
*/
|
|
18
|
+
export const DEEPSEEK_LOGIN_URL = `${DEEPSEEK_API_BASE_URL}/users/login`;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Headers used for DeepSeek API requests
|
|
22
|
+
*/
|
|
23
|
+
export const DEEPSEEK_BASE_HEADERS = {
|
|
24
|
+
"Host": "chat.deepseek.com",
|
|
25
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
26
|
+
"Accept": "application/json",
|
|
27
|
+
"Accept-Encoding": "gzip",
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
"x-client-platform": "web",
|
|
30
|
+
"x-client-version": "1.5.0",
|
|
31
|
+
"x-client-locale": "en_US",
|
|
32
|
+
"accept-charset": "UTF-8",
|
|
33
|
+
"origin": "https://chat.deepseek.com",
|
|
34
|
+
"referer": "https://chat.deepseek.com/"
|
|
35
|
+
} as const;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Provider identifier shared between the plugin loader and credential store.
|
|
39
|
+
*/
|
|
40
|
+
export const DEEPSEEK_PROVIDER_ID = "deepseek";
|
|
41
|
+
|
|
42
|
+
export const DEEPSEEK_AUTH_SCOPES = [
|
|
43
|
+
"user:profile",
|
|
44
|
+
"user:chat",
|
|
45
|
+
"user:tokens"
|
|
46
|
+
];
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result returned to the caller after constructing an auth URL or completing login.
|
|
3
|
+
*/
|
|
4
|
+
export interface DeepSeekAuthorization {
|
|
5
|
+
url?: string;
|
|
6
|
+
verifier: string; // Required for interface compatibility even if not used
|
|
7
|
+
success?: boolean;
|
|
8
|
+
token?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface DeepSeekTokenExchangeSuccess {
|
|
12
|
+
type: "success";
|
|
13
|
+
access: string;
|
|
14
|
+
expires: number;
|
|
15
|
+
email?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface DeepSeekTokenExchangeFailure {
|
|
19
|
+
type: "failed";
|
|
20
|
+
error: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type DeepSeekTokenExchangeResult =
|
|
24
|
+
| DeepSeekTokenExchangeSuccess
|
|
25
|
+
| DeepSeekTokenExchangeFailure;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Direct login to DeepSeek using email/password
|
|
29
|
+
*/
|
|
30
|
+
export async function loginDeepSeek(email: string, password: string): Promise<DeepSeekTokenExchangeResult> {
|
|
31
|
+
try {
|
|
32
|
+
// Prepare login payload
|
|
33
|
+
const payload = {
|
|
34
|
+
email,
|
|
35
|
+
password,
|
|
36
|
+
device_id: "opencode-plugin",
|
|
37
|
+
os: "web"
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Make login request to DeepSeek API
|
|
41
|
+
const response = await fetch("https://chat.deepseek.com/api/v0/users/login", {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: {
|
|
44
|
+
"Host": "chat.deepseek.com",
|
|
45
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
46
|
+
"Accept": "application/json",
|
|
47
|
+
"Accept-Encoding": "gzip",
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
"x-client-platform": "web",
|
|
50
|
+
"x-client-version": "1.5.0",
|
|
51
|
+
"x-client-locale": "en_US",
|
|
52
|
+
"accept-charset": "UTF-8",
|
|
53
|
+
"origin": "https://chat.deepseek.com",
|
|
54
|
+
"referer": "https://chat.deepseek.com/"
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify(payload)
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
const errorText = await response.text();
|
|
61
|
+
return {
|
|
62
|
+
type: "failed",
|
|
63
|
+
error: `Login failed with status ${response.status}: ${errorText}`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const data: any = await response.json();
|
|
68
|
+
|
|
69
|
+
if (!data.data || !data.data.biz_data || !data.data.biz_data.user) {
|
|
70
|
+
return {
|
|
71
|
+
type: "failed",
|
|
72
|
+
error: "Invalid response format from DeepSeek login API"
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const userData = data.data.biz_data.user;
|
|
77
|
+
const token = userData.token;
|
|
78
|
+
|
|
79
|
+
if (!token) {
|
|
80
|
+
return {
|
|
81
|
+
type: "failed",
|
|
82
|
+
error: "No token returned from DeepSeek login"
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Calculate expiration (default to 1 hour if not provided)
|
|
87
|
+
// In the actual DeepSeek API response, look for expires_in field
|
|
88
|
+
const expiresIn = data.data.biz_data.expires_in || 3600;
|
|
89
|
+
const expiresAt = Date.now() + (expiresIn * 1000);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
type: "success",
|
|
93
|
+
access: token,
|
|
94
|
+
expires: expiresAt,
|
|
95
|
+
email: userData.email || email
|
|
96
|
+
};
|
|
97
|
+
} catch (error) {
|
|
98
|
+
return {
|
|
99
|
+
type: "failed",
|
|
100
|
+
error: error instanceof Error ? error.message : "Unknown error occurred during login"
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Exchange credentials for tokens (for direct login approach)
|
|
107
|
+
*/
|
|
108
|
+
export async function exchangeDeepSeek(credentials: { email: string, password: string }): Promise<DeepSeekTokenExchangeResult> {
|
|
109
|
+
return loginDeepSeek(credentials.email, credentials.password);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Placeholder authorize function for compatibility
|
|
114
|
+
*/
|
|
115
|
+
export async function authorizeDeepSeek(): Promise<DeepSeekAuthorization> {
|
|
116
|
+
// Since DeepSeek doesn't use standard OAuth, we'll return info indicating
|
|
117
|
+
// that direct login should be used
|
|
118
|
+
return {
|
|
119
|
+
verifier: "", // Placeholder since we don't use PKCE
|
|
120
|
+
success: false,
|
|
121
|
+
url: "Direct login required - use email and password"
|
|
122
|
+
};
|
|
123
|
+
}
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
import { DEEPSEEK_PROVIDER_ID, DEEPSEEK_REDIRECT_URI, DEEPSEEK_BASE_HEADERS } from "./constants";
|
|
4
|
+
import {
|
|
5
|
+
authorizeDeepSeek,
|
|
6
|
+
exchangeDeepSeek,
|
|
7
|
+
loginDeepSeek
|
|
8
|
+
} from "./deepseek/auth";
|
|
9
|
+
import type { DeepSeekTokenExchangeResult } from "./deepseek/auth";
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
GetAuth,
|
|
13
|
+
LoaderResult,
|
|
14
|
+
PluginContext,
|
|
15
|
+
PluginResult,
|
|
16
|
+
Provider,
|
|
17
|
+
} from "./types";
|
|
18
|
+
|
|
19
|
+
// Keep track of active tokens
|
|
20
|
+
const tokenCache = new Map<string, { token: string, expires: number, email: string }>();
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Checks if an access token has expired
|
|
24
|
+
*/
|
|
25
|
+
function accessTokenExpired(authRecord: any): boolean {
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
return !authRecord.expires || now >= authRecord.expires - 60000; // 1 minute before expiry
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Determines if the auth method is OAuth-based
|
|
32
|
+
*/
|
|
33
|
+
function isOAuthAuth(auth: any): boolean {
|
|
34
|
+
return auth && auth.type === "oauth";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Refresh access token if expired
|
|
39
|
+
*/
|
|
40
|
+
async function refreshAccessToken(authRecord: any, client: any): Promise<any | null> {
|
|
41
|
+
// DeepSeek doesn't have refresh tokens, so a full re-login is required
|
|
42
|
+
const email = authRecord.email;
|
|
43
|
+
const password = authRecord.password;
|
|
44
|
+
|
|
45
|
+
if (!email || !password) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const result = await loginDeepSeek(email, password);
|
|
50
|
+
if (result.type === "success") {
|
|
51
|
+
// Update the stored credentials
|
|
52
|
+
const newAuth = {
|
|
53
|
+
type: "oauth",
|
|
54
|
+
access: result.access,
|
|
55
|
+
expires: result.expires,
|
|
56
|
+
email: result.email || email,
|
|
57
|
+
password: password // Store password for refresh
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Update cache
|
|
61
|
+
tokenCache.set(email, {
|
|
62
|
+
token: result.access,
|
|
63
|
+
expires: result.expires,
|
|
64
|
+
email: result.email || email
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return newAuth;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Prepares the request to DeepSeek API by setting appropriate headers and transforming the payload
|
|
75
|
+
*/
|
|
76
|
+
function prepareDeepSeekRequest(
|
|
77
|
+
input: Parameters<typeof fetch>[0],
|
|
78
|
+
init: RequestInit | undefined,
|
|
79
|
+
accessToken: string
|
|
80
|
+
): { request: Request, init: RequestInit } {
|
|
81
|
+
// Clone the original init to avoid mutating it
|
|
82
|
+
const transformedInit = { ...init };
|
|
83
|
+
|
|
84
|
+
// Set authorization header
|
|
85
|
+
transformedInit.headers = {
|
|
86
|
+
...transformedInit.headers,
|
|
87
|
+
...DEEPSEEK_BASE_HEADERS,
|
|
88
|
+
"authorization": `Bearer ${accessToken}`
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Construct the request
|
|
92
|
+
const request = new Request(input as RequestInfo, transformedInit);
|
|
93
|
+
|
|
94
|
+
return { request, init: transformedInit };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Transforms the DeepSeek API response to match expected format
|
|
99
|
+
*/
|
|
100
|
+
async function transformDeepSeekResponse(
|
|
101
|
+
response: Response
|
|
102
|
+
): Promise<Response> {
|
|
103
|
+
// For now, just pass through the response
|
|
104
|
+
// If needed, we could transform to match OpenAI format
|
|
105
|
+
return response;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Validates if this is a DeepSeek API request
|
|
110
|
+
*/
|
|
111
|
+
function isDeepSeekRequest(input: Parameters<typeof fetch>[0]): boolean {
|
|
112
|
+
const urlString = typeof input === 'string' ? input :
|
|
113
|
+
input instanceof URL ? input.toString() :
|
|
114
|
+
(input as Request).url || '';
|
|
115
|
+
return urlString.includes('deepseek.com') || urlString.includes('/v1/chat/completions');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Registers the DeepSeek Auth provider for Opencode, handling auth, request rewriting,
|
|
120
|
+
* and response normalization for DeepSeek requests.
|
|
121
|
+
*/
|
|
122
|
+
export const DeepSeekAuthPlugin = async (
|
|
123
|
+
{ client }: PluginContext,
|
|
124
|
+
): Promise<PluginResult> => ({
|
|
125
|
+
auth: {
|
|
126
|
+
provider: DEEPSEEK_PROVIDER_ID,
|
|
127
|
+
loader: async (getAuth: GetAuth, provider: Provider): Promise<LoaderResult | null> => {
|
|
128
|
+
const auth = await getAuth();
|
|
129
|
+
if (!isOAuthAuth(auth)) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// If models are defined in the provider, set cost to 0 to indicate free usage
|
|
134
|
+
if (provider.models) {
|
|
135
|
+
for (const model of Object.values(provider.models)) {
|
|
136
|
+
if (model) {
|
|
137
|
+
model.cost = { input: 0, output: 0 };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
apiKey: "",
|
|
144
|
+
async fetch(input: Parameters<typeof fetch>[0], init?: RequestInit) {
|
|
145
|
+
// If this isn't a DeepSeek request, pass through normally
|
|
146
|
+
if (!isDeepSeekRequest(input)) {
|
|
147
|
+
return fetch(input as RequestInfo, init);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Get current auth state
|
|
151
|
+
const latestAuth = await getAuth();
|
|
152
|
+
if (!isOAuthAuth(latestAuth)) {
|
|
153
|
+
return fetch(input as RequestInfo, init);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let authRecord = latestAuth;
|
|
157
|
+
|
|
158
|
+
// Check if token has expired and refresh if possible
|
|
159
|
+
if (accessTokenExpired(authRecord)) {
|
|
160
|
+
const refreshed = await refreshAccessToken(authRecord, client);
|
|
161
|
+
if (!refreshed) {
|
|
162
|
+
console.warn("Could not refresh DeepSeek access token");
|
|
163
|
+
return fetch(input as RequestInfo, init);
|
|
164
|
+
}
|
|
165
|
+
authRecord = refreshed;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const accessToken = authRecord.access;
|
|
169
|
+
if (!accessToken) {
|
|
170
|
+
return fetch(input as RequestInfo, init);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Prepare the request with proper headers
|
|
174
|
+
const { request, init: transformedInit } = prepareDeepSeekRequest(
|
|
175
|
+
input,
|
|
176
|
+
init,
|
|
177
|
+
accessToken
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// Make the API call
|
|
181
|
+
const response = await fetch(request, transformedInit);
|
|
182
|
+
|
|
183
|
+
// Transform response if needed
|
|
184
|
+
return transformDeepSeekResponse(response);
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
methods: [
|
|
189
|
+
{
|
|
190
|
+
label: "Login with DeepSeek Account",
|
|
191
|
+
type: "oauth",
|
|
192
|
+
authorize: async () => {
|
|
193
|
+
// For DeepSeek, we'll implement direct credential-based auth
|
|
194
|
+
// since they don't use standard OAuth
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
url: "",
|
|
198
|
+
instructions:
|
|
199
|
+
"Please enter your DeepSeek account credentials (email and password). Your credentials will be stored securely and used for authentication.",
|
|
200
|
+
method: "credentials",
|
|
201
|
+
callback: async (credentials: { email: string, password: string }): Promise<DeepSeekTokenExchangeResult> => {
|
|
202
|
+
// Validate inputs
|
|
203
|
+
if (!credentials.email || !credentials.password) {
|
|
204
|
+
return {
|
|
205
|
+
type: "failed",
|
|
206
|
+
error: "Email and password are required for DeepSeek authentication"
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Attempt to log in using the provided credentials
|
|
211
|
+
const result = await loginDeepSeek(credentials.email, credentials.password);
|
|
212
|
+
|
|
213
|
+
if (result.type === "success") {
|
|
214
|
+
// Cache the token
|
|
215
|
+
tokenCache.set(credentials.email, {
|
|
216
|
+
token: result.access,
|
|
217
|
+
expires: result.expires,
|
|
218
|
+
email: result.email || credentials.email
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return result;
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
provider: DEEPSEEK_PROVIDER_ID,
|
|
229
|
+
label: "Manually enter API Key",
|
|
230
|
+
type: "api",
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the DeepSeek Auth plugin
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface PluginContext {
|
|
6
|
+
client: any;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface PluginResult {
|
|
10
|
+
auth: {
|
|
11
|
+
provider: string;
|
|
12
|
+
loader: (getAuth: GetAuth, provider: Provider) => Promise<LoaderResult | null>;
|
|
13
|
+
methods: AuthMethod[];
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface GetAuth {
|
|
18
|
+
(): Promise<any>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LoaderResult {
|
|
22
|
+
apiKey: string;
|
|
23
|
+
fetch: (input: Parameters<typeof fetch>[0], init?: RequestInit) => Promise<Response>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface Provider {
|
|
27
|
+
id?: string;
|
|
28
|
+
models?: Record<string, any>;
|
|
29
|
+
options?: Record<string, any>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AuthMethod {
|
|
33
|
+
label: string;
|
|
34
|
+
type: string;
|
|
35
|
+
provider?: string;
|
|
36
|
+
authorize?: () => Promise<{
|
|
37
|
+
url: string;
|
|
38
|
+
instructions: string;
|
|
39
|
+
method: string;
|
|
40
|
+
callback: (params?: any) => Promise<any>;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ProjectContextResult {
|
|
45
|
+
effectiveProjectId: string;
|
|
46
|
+
}
|
package/test.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test script to verify the DeepSeek Auth plugin functionality
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { loginDeepSeek } = require('./dist/deepseek/auth');
|
|
6
|
+
|
|
7
|
+
console.log('Testing DeepSeek authentication functionality...');
|
|
8
|
+
|
|
9
|
+
// Example test - this would require actual credentials to work properly
|
|
10
|
+
async function testDeepSeekLogin() {
|
|
11
|
+
console.log('Testing login function initialization...');
|
|
12
|
+
|
|
13
|
+
// Just calling the function to make sure it's available
|
|
14
|
+
console.log('Function loginDeepSeek is available:', typeof loginDeepSeek);
|
|
15
|
+
|
|
16
|
+
// Testing with mock credentials (would fail without real credentials)
|
|
17
|
+
try {
|
|
18
|
+
console.log('Attempting to call loginDeepSeek with mock credentials...');
|
|
19
|
+
const result = await loginDeepSeek('test@example.com', 'mock_password');
|
|
20
|
+
console.log('Expected failure result for mock credentials:', result);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.log('Caught error (expected for mock credentials):', error.message);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log('Test completed successfully!');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
testDeepSeekLogin().catch(console.error);
|
package/test.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test file for DeepSeek Auth plugin
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { loginDeepSeek } from './src/deepseek/auth';
|
|
6
|
+
|
|
7
|
+
// Example test - in real usage, you would test with actual credentials
|
|
8
|
+
async function testDeepSeekLogin() {
|
|
9
|
+
console.log('Testing DeepSeek authentication...');
|
|
10
|
+
|
|
11
|
+
// Note: This would require actual credentials to work properly
|
|
12
|
+
// This is just a demonstration of how the function is structured
|
|
13
|
+
try {
|
|
14
|
+
// This would fail without real credentials, but demonstrates the API
|
|
15
|
+
const result = await loginDeepSeek('test@example.com', 'password');
|
|
16
|
+
console.log('Login result:', result);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error('Test failed as expected (no real credentials):', error);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Run test
|
|
23
|
+
testDeepSeekLogin().catch(console.error);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"lib": ["ES2020", "DOM"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": true,
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"removeComments": false,
|
|
16
|
+
"noImplicitAny": true,
|
|
17
|
+
"strictNullChecks": true,
|
|
18
|
+
"strictFunctionTypes": true,
|
|
19
|
+
"noImplicitReturns": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"moduleResolution": "node",
|
|
22
|
+
"allowSyntheticDefaultImports": true,
|
|
23
|
+
"resolveJsonModule": true
|
|
24
|
+
},
|
|
25
|
+
"include": [
|
|
26
|
+
"src/**/*"
|
|
27
|
+
],
|
|
28
|
+
"exclude": [
|
|
29
|
+
"node_modules",
|
|
30
|
+
"dist"
|
|
31
|
+
]
|
|
32
|
+
}
|