opencode-deepseek-auth 2.0.0 → 2.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/README.md +65 -15
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +119 -35
- package/dist/plugin.js.map +1 -1
- package/dist/types.d.ts +11 -1
- package/dist/types.d.ts.map +1 -1
- package/example-config.json +18 -0
- package/package.json +1 -1
- package/src/constants.js +43 -0
- package/src/deepseek/auth.js +93 -0
- package/src/plugin.js +166 -0
- package/src/plugin.ts +140 -45
- package/src/types.js +5 -0
- package/src/types.ts +6 -1
- package/test_auth_flow.js +122 -0
- package/test_auth_flow.ts +142 -0
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ Add the plugin to your OpenCode configuration file
|
|
|
25
25
|
|
|
26
26
|
## Usage
|
|
27
27
|
|
|
28
|
+
### Method 1: Email/Password Authentication (Recommended)
|
|
29
|
+
|
|
28
30
|
1. **Login**: Run the authentication command in your terminal:
|
|
29
31
|
|
|
30
32
|
```bash
|
|
@@ -36,7 +38,28 @@ Add the plugin to your OpenCode configuration file
|
|
|
36
38
|
- Enter your email and password when prompted
|
|
37
39
|
- Your credentials will be stored securely for future use
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
### Method 2: Direct Access Token
|
|
42
|
+
|
|
43
|
+
If you have an existing DeepSeek access token, you can set it directly:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
opencode auth set --provider deepseek --api-key "your-deepseek-access-token"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Method 3: Config-based Authentication
|
|
50
|
+
|
|
51
|
+
You can also put DeepSeek credentials directly in your OpenCode config file:
|
|
52
|
+
|
|
53
|
+
**File:** `~/.config/opencode/opencode.json`
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"provider": {
|
|
57
|
+
"deepseek": {
|
|
58
|
+
"apiKey": "email:password" // Replace with your actual email and password
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
40
63
|
|
|
41
64
|
## Configuration
|
|
42
65
|
|
|
@@ -67,29 +90,51 @@ OpenCode config.
|
|
|
67
90
|
|
|
68
91
|
## Features
|
|
69
92
|
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
93
|
+
- **Dual Authentication Support**: Supports both email:password flow and direct access token authentication
|
|
94
|
+
- **Smart Format Recognition**: Automatically detects between email:password and regular API keys
|
|
95
|
+
- **Secure Storage**: Securely manages your DeepSeek credentials
|
|
96
|
+
- **Automatic Token Refresh**: Handles token lifecycle and refreshing when needed
|
|
97
|
+
- **OpenCode Integration**: Seamless integration with OpenCode's authentication system
|
|
98
|
+
- **Conflict Resolution**: Properly separates OpenCode's own API keys from DeepSeek authentication
|
|
99
|
+
- **Free Usage**: Leverage your existing DeepSeek account quota and features
|
|
75
100
|
|
|
76
101
|
## How It Works
|
|
77
102
|
|
|
78
|
-
The plugin
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
103
|
+
The plugin now supports two authentication modes:
|
|
104
|
+
|
|
105
|
+
**Email/Password Mode**:
|
|
106
|
+
1. Authenticate directly with DeepSeek's API using your email and password
|
|
107
|
+
2. Obtain an access token from DeepSeek
|
|
108
|
+
3. Use this token to make authenticated requests to DeepSeek's API
|
|
109
|
+
4. Manage token lifecycle and refresh when needed
|
|
110
|
+
|
|
111
|
+
**Direct Token Mode**:
|
|
112
|
+
1. Accept a direct access token as the API key
|
|
113
|
+
2. Use this token directly with DeepSeek's API
|
|
114
|
+
3. Handle request preparation and response transformation
|
|
115
|
+
|
|
116
|
+
## Authentication Format Detection
|
|
83
117
|
|
|
84
|
-
|
|
118
|
+
The plugin intelligently determines the authentication method:
|
|
119
|
+
|
|
120
|
+
- **Email:Password Detection**: If `apiKey` contains exactly one colon (`:`) and the first part contains `@` (email) or appears to be a phone number, it treats it as email:password
|
|
121
|
+
- **Explicit Provider Marking**: If the auth object has `provider: "deepseek"` or `type: "deepseek-email-password"`, it always treats the key as email:password format
|
|
122
|
+
- **Regular API Key**: Otherwise, treats the value as a direct access token
|
|
85
123
|
|
|
86
124
|
## Troubleshooting
|
|
87
125
|
|
|
88
|
-
###
|
|
126
|
+
### Authentication Issues
|
|
127
|
+
|
|
128
|
+
- **OpenCode API key conflict**: If OpenCode reports your API key is invalid, ensure you're using the correct format for your intended authentication method
|
|
129
|
+
- **Email:Password Format**: Must be in `email:password` or `phone:password` format. Special characters in passwords may require URL encoding
|
|
130
|
+
- **Direct Token**: If using a direct access token, ensure it's a valid DeepSeek access token
|
|
131
|
+
|
|
132
|
+
### Common Solutions
|
|
89
133
|
|
|
90
134
|
- Verify that your email and password are correct
|
|
91
|
-
- Check that your account is active and not suspended
|
|
92
|
-
-
|
|
135
|
+
- Check that your DeepSeek account is active and not suspended
|
|
136
|
+
- Ensure you're not hitting rate limits
|
|
137
|
+
- If experiencing conflicts, try explicit authentication method via the config file
|
|
93
138
|
|
|
94
139
|
### Token Expiration
|
|
95
140
|
|
|
@@ -98,6 +143,11 @@ The plugin handles token refresh automatically, but if you encounter authenticat
|
|
|
98
143
|
opencode auth login
|
|
99
144
|
```
|
|
100
145
|
|
|
146
|
+
Or clear the stored credentials:
|
|
147
|
+
```bash
|
|
148
|
+
opencode auth clear --provider deepseek
|
|
149
|
+
```
|
|
150
|
+
|
|
101
151
|
## Development
|
|
102
152
|
|
|
103
153
|
To develop on this plugin locally:
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAGV,aAAa,EACb,YAAY,EAEb,MAAM,SAAS,CAAC;AA8CjB;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC7B,YAAY,aAAa,KACxB,OAAO,CAAC,YAAY,
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAGV,aAAa,EACb,YAAY,EAEb,MAAM,SAAS,CAAC;AA8CjB;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC7B,YAAY,aAAa,KACxB,OAAO,CAAC,YAAY,CAsLrB,CAAC"}
|
package/dist/plugin.js
CHANGED
|
@@ -48,45 +48,101 @@ const DeepSeekAuthPlugin = async ({ client }) => ({
|
|
|
48
48
|
if (!auth?.apiKey) {
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
|
-
//
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
51
|
+
// Determine if this is a DeepSeek-specific authentication by checking the auth metadata
|
|
52
|
+
const isDeepSeekAuth = auth.provider === 'deepseek' || auth.type === 'deepseek-email-password';
|
|
53
|
+
const apiKeyValue = auth.apiKey;
|
|
54
|
+
// If explicitly marked as DeepSeek auth, process as email:password
|
|
55
|
+
if (isDeepSeekAuth) {
|
|
56
|
+
const parts = apiKeyValue.split(':');
|
|
57
|
+
if (parts.length === 2) {
|
|
58
|
+
const [identifier, password] = parts;
|
|
59
|
+
// Convert credentials to DeepSeek access token
|
|
60
|
+
const result = await (0, auth_1.loginDeepSeek)(identifier, password);
|
|
61
|
+
if (result.type !== "success") {
|
|
62
|
+
console.error(`Failed to authenticate with provided credentials: ${result.error}`);
|
|
63
|
+
throw new Error(`Authentication failed: ${result.error}`);
|
|
64
|
+
}
|
|
65
|
+
// Successfully obtained access token from credentials
|
|
66
|
+
const accessToken = result.access;
|
|
67
|
+
// If models are defined in the provider, set cost to 0 to indicate free usage
|
|
68
|
+
if (provider.models) {
|
|
69
|
+
for (const model of Object.values(provider.models)) {
|
|
70
|
+
if (model) {
|
|
71
|
+
model.cost = { input: 0, output: 0 };
|
|
72
|
+
}
|
|
68
73
|
}
|
|
69
74
|
}
|
|
75
|
+
return {
|
|
76
|
+
apiKey: accessToken,
|
|
77
|
+
async fetch(input, init) {
|
|
78
|
+
// If this isn't a DeepSeek request, pass through normally
|
|
79
|
+
if (!isDeepSeekRequest(input)) {
|
|
80
|
+
return fetch(input, init);
|
|
81
|
+
}
|
|
82
|
+
// Prepare the request with proper headers
|
|
83
|
+
const { request, init: transformedInit } = prepareDeepSeekRequest(input, init, accessToken);
|
|
84
|
+
// Make the API call
|
|
85
|
+
const response = await fetch(request, transformedInit);
|
|
86
|
+
// Transform response if needed
|
|
87
|
+
return transformDeepSeekResponse(response);
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
// If explicitly marked as DeepSeek auth but not in correct format, throw error
|
|
93
|
+
throw new Error(`DeepSeek authentication requires email:password format`);
|
|
70
94
|
}
|
|
71
|
-
return {
|
|
72
|
-
apiKey: accessToken,
|
|
73
|
-
async fetch(input, init) {
|
|
74
|
-
// If this isn't a DeepSeek request, pass through normally
|
|
75
|
-
if (!isDeepSeekRequest(input)) {
|
|
76
|
-
return fetch(input, init);
|
|
77
|
-
}
|
|
78
|
-
// Prepare the request with proper headers
|
|
79
|
-
const { request, init: transformedInit } = prepareDeepSeekRequest(input, init, accessToken);
|
|
80
|
-
// Make the API call
|
|
81
|
-
const response = await fetch(request, transformedInit);
|
|
82
|
-
// Transform response if needed
|
|
83
|
-
return transformDeepSeekResponse(response);
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
95
|
}
|
|
87
96
|
else {
|
|
88
|
-
//
|
|
89
|
-
const
|
|
97
|
+
// Otherwise, check if this looks like email:password format but isn't explicitly marked
|
|
98
|
+
const parts = apiKeyValue.split(':');
|
|
99
|
+
if (parts.length === 2) {
|
|
100
|
+
const [potentialIdentifier, potentialPassword] = parts;
|
|
101
|
+
// Heuristic: Check if it looks like an email or phone number format, and password seems reasonable
|
|
102
|
+
const isEmailFormat = potentialIdentifier.includes('@');
|
|
103
|
+
const isPhoneFormat = /^[0-9+\-\s()]+$/.test(potentialIdentifier.trim());
|
|
104
|
+
const isPasswordReasonableLength = potentialPassword.length >= 6;
|
|
105
|
+
// Only attempt email:password processing if both parts seem valid
|
|
106
|
+
if ((isEmailFormat || isPhoneFormat) && isPasswordReasonableLength) {
|
|
107
|
+
try {
|
|
108
|
+
// Try to use as email:password
|
|
109
|
+
const result = await (0, auth_1.loginDeepSeek)(potentialIdentifier, potentialPassword);
|
|
110
|
+
if (result.type === "success") {
|
|
111
|
+
// Successfully authenticated with email:password
|
|
112
|
+
const accessToken = result.access;
|
|
113
|
+
// If models are defined in the provider, set cost to 0 to indicate free usage
|
|
114
|
+
if (provider.models) {
|
|
115
|
+
for (const model of Object.values(provider.models)) {
|
|
116
|
+
if (model) {
|
|
117
|
+
model.cost = { input: 0, output: 0 };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
apiKey: accessToken,
|
|
123
|
+
async fetch(input, init) {
|
|
124
|
+
// If this isn't a DeepSeek request, pass through normally
|
|
125
|
+
if (!isDeepSeekRequest(input)) {
|
|
126
|
+
return fetch(input, init);
|
|
127
|
+
}
|
|
128
|
+
// Prepare the request with proper headers
|
|
129
|
+
const { request, init: transformedInit } = prepareDeepSeekRequest(input, init, accessToken);
|
|
130
|
+
// Make the API call
|
|
131
|
+
const response = await fetch(request, transformedInit);
|
|
132
|
+
// Transform response if needed
|
|
133
|
+
return transformDeepSeekResponse(response);
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
console.warn("Email:password authentication failed, treating as regular API key:", error);
|
|
140
|
+
// Fall through to treat as regular API key
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Default: treat as direct DeepSeek access token or other API key
|
|
145
|
+
const accessToken = apiKeyValue;
|
|
90
146
|
return {
|
|
91
147
|
apiKey: accessToken,
|
|
92
148
|
async fetch(input, init) {
|
|
@@ -104,7 +160,35 @@ const DeepSeekAuthPlugin = async ({ client }) => ({
|
|
|
104
160
|
};
|
|
105
161
|
}
|
|
106
162
|
},
|
|
107
|
-
methods: [
|
|
163
|
+
methods: [{
|
|
164
|
+
label: "Login with DeepSeek Account",
|
|
165
|
+
type: "custom",
|
|
166
|
+
provider: "deepseek",
|
|
167
|
+
prompts: [
|
|
168
|
+
{ name: "apiKey", label: "Email:Password", type: "text", required: true }
|
|
169
|
+
],
|
|
170
|
+
authorize: async () => {
|
|
171
|
+
return {
|
|
172
|
+
url: "Configuration required",
|
|
173
|
+
instructions: "Enter your DeepSeek account credentials in email:password format",
|
|
174
|
+
method: "CONFIG",
|
|
175
|
+
form: {
|
|
176
|
+
fields: [
|
|
177
|
+
{
|
|
178
|
+
name: "apiKey",
|
|
179
|
+
label: "Email:Password",
|
|
180
|
+
type: "text",
|
|
181
|
+
required: true
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
callback: async (params) => {
|
|
186
|
+
// This will be handled by the loader function when credentials are provided
|
|
187
|
+
return params;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}],
|
|
108
192
|
},
|
|
109
193
|
});
|
|
110
194
|
exports.DeepSeekAuthPlugin = DeepSeekAuthPlugin;
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":";;;AAEA,2CAAiG;AACjG,0CAEyB;AAWzB;;GAEG;AACH,SAAS,sBAAsB,CAC7B,KAAkC,EAClC,IAA6B,EAC7B,WAAmB;IAEnB,+CAA+C;IAC/C,MAAM,eAAe,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAEpC,2BAA2B;IAC3B,eAAe,CAAC,OAAO,GAAG;QACxB,GAAG,eAAe,CAAC,OAAO;QAC1B,GAAG,iCAAqB;QACxB,eAAe,EAAE,UAAU,WAAW,EAAE;KACzC,CAAC;IAEF,wBAAwB;IACxB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,KAAoB,EAAE,eAAe,CAAC,CAAC;IAEnE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;AAC5C,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,yBAAyB,CACtC,QAAkB;IAElB,0CAA0C;IAC1C,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB,CAAC,KAAkC;IAC3D,MAAM,SAAS,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACpC,KAAK,YAAY,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;YACxC,KAAiB,CAAC,GAAG,IAAI,EAAE,CAAC;IAC9C,OAAO,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAC;AAC1F,CAAC;AAED;;;GAGG;AACI,MAAM,kBAAkB,GAAG,KAAK,EACrC,EAAE,MAAM,EAAiB,EACF,EAAE,CAAC,CAAC;IAC3B,IAAI,EAAE;QACJ,QAAQ,EAAE,gCAAoB;QAC9B,MAAM,EAAE,KAAK,EAAE,OAAgB,EAAE,QAAkB,EAAgC,EAAE;YACnF,MAAM,IAAI,GAAG,MAAM,OAAO,EAAE,CAAC;YAE7B,kDAAkD;YAClD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;gBAClB,OAAO,IAAI,CAAC;YACd,CAAC;YAED,
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -21,7 +21,11 @@ export interface PluginResult {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
export interface GetAuth {
|
|
24
|
-
(): Promise<
|
|
24
|
+
(): Promise<{
|
|
25
|
+
apiKey?: string;
|
|
26
|
+
type?: string;
|
|
27
|
+
provider?: string;
|
|
28
|
+
}>;
|
|
25
29
|
}
|
|
26
30
|
export interface LoaderResult {
|
|
27
31
|
apiKey: string;
|
|
@@ -36,6 +40,12 @@ export interface AuthMethod {
|
|
|
36
40
|
label: string;
|
|
37
41
|
type: string;
|
|
38
42
|
provider?: string;
|
|
43
|
+
prompts?: Array<{
|
|
44
|
+
name: string;
|
|
45
|
+
label: string;
|
|
46
|
+
type: string;
|
|
47
|
+
required: boolean;
|
|
48
|
+
}>;
|
|
39
49
|
authorize?: () => Promise<{
|
|
40
50
|
url: string;
|
|
41
51
|
instructions: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB;AAED,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,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB;AAED,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;QACV,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC,CAAC;CACJ;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,OAAO,CAAC,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAC,CAAC,CAAC;IAChF,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC;QACxB,GAAG,EAAE,MAAM,CAAC;QACZ,YAAY,EAAE,MAAM,CAAC;QACrB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,UAAU,CAAC;QAClB,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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/config.json",
|
|
3
|
+
"plugin": ["opencode-deepseek-auth@latest"],
|
|
4
|
+
"provider": {
|
|
5
|
+
"deepseek": {
|
|
6
|
+
"models": {
|
|
7
|
+
"deepseek-chat": {
|
|
8
|
+
"displayName": "DeepSeek Chat",
|
|
9
|
+
"maxTokens": 8192
|
|
10
|
+
},
|
|
11
|
+
"deepseek-reasoner": {
|
|
12
|
+
"displayName": "DeepSeek Reasoner",
|
|
13
|
+
"maxTokens": 8192
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
package/package.json
CHANGED
package/src/constants.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
];
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
}
|
package/src/plugin.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
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
|
+
/**
|
|
7
|
+
* Prepares the request to DeepSeek API by setting appropriate headers and transforming the payload
|
|
8
|
+
*/
|
|
9
|
+
function prepareDeepSeekRequest(input, init, accessToken) {
|
|
10
|
+
// Clone the original init to avoid mutating it
|
|
11
|
+
const transformedInit = { ...init };
|
|
12
|
+
// Set authorization header
|
|
13
|
+
transformedInit.headers = {
|
|
14
|
+
...transformedInit.headers,
|
|
15
|
+
...constants_1.DEEPSEEK_BASE_HEADERS,
|
|
16
|
+
"authorization": `Bearer ${accessToken}`
|
|
17
|
+
};
|
|
18
|
+
// Construct the request
|
|
19
|
+
const request = new Request(input, transformedInit);
|
|
20
|
+
return { request, init: transformedInit };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Transforms the DeepSeek API response to match expected format
|
|
24
|
+
*/
|
|
25
|
+
async function transformDeepSeekResponse(response) {
|
|
26
|
+
// For now, just pass through the response
|
|
27
|
+
return response;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Validates if this is a DeepSeek API request
|
|
31
|
+
*/
|
|
32
|
+
function isDeepSeekRequest(input) {
|
|
33
|
+
const urlString = typeof input === 'string' ? input :
|
|
34
|
+
input instanceof URL ? input.toString() :
|
|
35
|
+
input.url || '';
|
|
36
|
+
return urlString.includes('deepseek.com') || urlString.includes('/v1/chat/completions');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Registers the DeepSeek simple API key provider for Opencode.
|
|
40
|
+
* Users can configure their credentials using `opencode connect` with format: email:password or phone:password
|
|
41
|
+
*/
|
|
42
|
+
const DeepSeekAuthPlugin = async ({ client }) => ({
|
|
43
|
+
auth: {
|
|
44
|
+
provider: constants_1.DEEPSEEK_PROVIDER_ID,
|
|
45
|
+
loader: async (getAuth, provider) => {
|
|
46
|
+
const auth = await getAuth();
|
|
47
|
+
// This plugin only handles API key authentication
|
|
48
|
+
if (!auth?.apiKey) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
// Determine if this is a DeepSeek-specific authentication by checking the auth metadata
|
|
52
|
+
const isDeepSeekAuth = auth.provider === 'deepseek' || auth.type === 'deepseek-email-password';
|
|
53
|
+
const apiKeyValue = auth.apiKey;
|
|
54
|
+
// If explicitly marked as DeepSeek auth, process as email:password
|
|
55
|
+
if (isDeepSeekAuth) {
|
|
56
|
+
const parts = apiKeyValue.split(':');
|
|
57
|
+
if (parts.length === 2) {
|
|
58
|
+
const [identifier, password] = parts;
|
|
59
|
+
// Convert credentials to DeepSeek access token
|
|
60
|
+
const result = await (0, auth_1.loginDeepSeek)(identifier, password);
|
|
61
|
+
if (result.type !== "success") {
|
|
62
|
+
console.error(`Failed to authenticate with provided credentials: ${result.error}`);
|
|
63
|
+
throw new Error(`Authentication failed: ${result.error}`);
|
|
64
|
+
}
|
|
65
|
+
// Successfully obtained access token from credentials
|
|
66
|
+
const accessToken = result.access;
|
|
67
|
+
// If models are defined in the provider, set cost to 0 to indicate free usage
|
|
68
|
+
if (provider.models) {
|
|
69
|
+
for (const model of Object.values(provider.models)) {
|
|
70
|
+
if (model) {
|
|
71
|
+
model.cost = { input: 0, output: 0 };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
apiKey: accessToken,
|
|
77
|
+
async fetch(input, init) {
|
|
78
|
+
// If this isn't a DeepSeek request, pass through normally
|
|
79
|
+
if (!isDeepSeekRequest(input)) {
|
|
80
|
+
return fetch(input, init);
|
|
81
|
+
}
|
|
82
|
+
// Prepare the request with proper headers
|
|
83
|
+
const { request, init: transformedInit } = prepareDeepSeekRequest(input, init, accessToken);
|
|
84
|
+
// Make the API call
|
|
85
|
+
const response = await fetch(request, transformedInit);
|
|
86
|
+
// Transform response if needed
|
|
87
|
+
return transformDeepSeekResponse(response);
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
// If explicitly marked as DeepSeek auth but not in correct format, throw error
|
|
93
|
+
throw new Error(`DeepSeek authentication requires email:password format`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// Otherwise, check if this looks like email:password format but isn't explicitly marked
|
|
98
|
+
const parts = apiKeyValue.split(':');
|
|
99
|
+
if (parts.length === 2) {
|
|
100
|
+
const [potentialIdentifier, potentialPassword] = parts;
|
|
101
|
+
// Heuristic: Check if it looks like an email or phone number format, and password seems reasonable
|
|
102
|
+
const isEmailFormat = potentialIdentifier.includes('@');
|
|
103
|
+
const isPhoneFormat = /^[0-9+\-\s()]+$/.test(potentialIdentifier.trim());
|
|
104
|
+
const isPasswordReasonableLength = potentialPassword.length >= 6;
|
|
105
|
+
// Only attempt email:password processing if both parts seem valid
|
|
106
|
+
if ((isEmailFormat || isPhoneFormat) && isPasswordReasonableLength) {
|
|
107
|
+
try {
|
|
108
|
+
// Try to use as email:password
|
|
109
|
+
const result = await (0, auth_1.loginDeepSeek)(potentialIdentifier, potentialPassword);
|
|
110
|
+
if (result.type === "success") {
|
|
111
|
+
// Successfully authenticated with email:password
|
|
112
|
+
const accessToken = result.access;
|
|
113
|
+
// If models are defined in the provider, set cost to 0 to indicate free usage
|
|
114
|
+
if (provider.models) {
|
|
115
|
+
for (const model of Object.values(provider.models)) {
|
|
116
|
+
if (model) {
|
|
117
|
+
model.cost = { input: 0, output: 0 };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
apiKey: accessToken,
|
|
123
|
+
async fetch(input, init) {
|
|
124
|
+
// If this isn't a DeepSeek request, pass through normally
|
|
125
|
+
if (!isDeepSeekRequest(input)) {
|
|
126
|
+
return fetch(input, init);
|
|
127
|
+
}
|
|
128
|
+
// Prepare the request with proper headers
|
|
129
|
+
const { request, init: transformedInit } = prepareDeepSeekRequest(input, init, accessToken);
|
|
130
|
+
// Make the API call
|
|
131
|
+
const response = await fetch(request, transformedInit);
|
|
132
|
+
// Transform response if needed
|
|
133
|
+
return transformDeepSeekResponse(response);
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
console.warn("Email:password authentication failed, treating as regular API key:", error);
|
|
140
|
+
// Fall through to treat as regular API key
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Default: treat as direct DeepSeek access token or other API key
|
|
145
|
+
const accessToken = apiKeyValue;
|
|
146
|
+
return {
|
|
147
|
+
apiKey: accessToken,
|
|
148
|
+
async fetch(input, init) {
|
|
149
|
+
// If this isn't a DeepSeek request, pass through normally
|
|
150
|
+
if (!isDeepSeekRequest(input)) {
|
|
151
|
+
return fetch(input, init);
|
|
152
|
+
}
|
|
153
|
+
// Prepare the request with proper headers
|
|
154
|
+
const { request, init: transformedInit } = prepareDeepSeekRequest(input, init, accessToken);
|
|
155
|
+
// Make the API call
|
|
156
|
+
const response = await fetch(request, transformedInit);
|
|
157
|
+
// Transform response if needed
|
|
158
|
+
return transformDeepSeekResponse(response);
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
methods: [], // Empty methods array since we're using config-based auth
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
exports.DeepSeekAuthPlugin = DeepSeekAuthPlugin;
|
package/src/plugin.ts
CHANGED
|
@@ -75,55 +75,122 @@ export const DeepSeekAuthPlugin = async (
|
|
|
75
75
|
return null;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
//
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
78
|
+
// Determine if this is a DeepSeek-specific authentication by checking the auth metadata
|
|
79
|
+
const isDeepSeekAuth = auth.provider === 'deepseek' || auth.type === 'deepseek-email-password';
|
|
80
|
+
const apiKeyValue = auth.apiKey;
|
|
81
|
+
|
|
82
|
+
// If explicitly marked as DeepSeek auth, process as email:password
|
|
83
|
+
if (isDeepSeekAuth) {
|
|
84
|
+
const parts = apiKeyValue.split(':');
|
|
85
|
+
if (parts.length === 2) {
|
|
86
|
+
const [identifier, password] = parts;
|
|
87
|
+
|
|
88
|
+
// Convert credentials to DeepSeek access token
|
|
89
|
+
const result = await loginDeepSeek(identifier, password);
|
|
90
|
+
if (result.type !== "success") {
|
|
91
|
+
console.error(`Failed to authenticate with provided credentials: ${result.error}`);
|
|
92
|
+
throw new Error(`Authentication failed: ${result.error}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Successfully obtained access token from credentials
|
|
96
|
+
const accessToken = result.access;
|
|
97
|
+
|
|
98
|
+
// If models are defined in the provider, set cost to 0 to indicate free usage
|
|
99
|
+
if (provider.models) {
|
|
100
|
+
for (const model of Object.values(provider.models)) {
|
|
101
|
+
if (model) {
|
|
102
|
+
model.cost = { input: 0, output: 0 };
|
|
103
|
+
}
|
|
98
104
|
}
|
|
99
105
|
}
|
|
100
|
-
}
|
|
101
106
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
107
|
+
return {
|
|
108
|
+
apiKey: accessToken,
|
|
109
|
+
async fetch(input: Parameters<typeof fetch>[0], init?: RequestInit) {
|
|
110
|
+
// If this isn't a DeepSeek request, pass through normally
|
|
111
|
+
if (!isDeepSeekRequest(input)) {
|
|
112
|
+
return fetch(input as RequestInfo, init);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Prepare the request with proper headers
|
|
116
|
+
const { request, init: transformedInit } = prepareDeepSeekRequest(
|
|
117
|
+
input,
|
|
118
|
+
init,
|
|
119
|
+
accessToken
|
|
120
|
+
);
|
|
116
121
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
// Make the API call
|
|
123
|
+
const response = await fetch(request, transformedInit);
|
|
124
|
+
|
|
125
|
+
// Transform response if needed
|
|
126
|
+
return transformDeepSeekResponse(response);
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
} else {
|
|
130
|
+
// If explicitly marked as DeepSeek auth but not in correct format, throw error
|
|
131
|
+
throw new Error(`DeepSeek authentication requires email:password format`);
|
|
132
|
+
}
|
|
124
133
|
} else {
|
|
125
|
-
//
|
|
126
|
-
const
|
|
134
|
+
// Otherwise, check if this looks like email:password format but isn't explicitly marked
|
|
135
|
+
const parts = apiKeyValue.split(':');
|
|
136
|
+
if (parts.length === 2) {
|
|
137
|
+
const [potentialIdentifier, potentialPassword] = parts;
|
|
138
|
+
|
|
139
|
+
// Heuristic: Check if it looks like an email or phone number format, and password seems reasonable
|
|
140
|
+
const isEmailFormat = potentialIdentifier.includes('@');
|
|
141
|
+
const isPhoneFormat = /^[0-9+\-\s()]+$/.test(potentialIdentifier.trim());
|
|
142
|
+
const isPasswordReasonableLength = potentialPassword.length >= 6;
|
|
143
|
+
|
|
144
|
+
// Only attempt email:password processing if both parts seem valid
|
|
145
|
+
if ((isEmailFormat || isPhoneFormat) && isPasswordReasonableLength) {
|
|
146
|
+
try {
|
|
147
|
+
// Try to use as email:password
|
|
148
|
+
const result = await loginDeepSeek(potentialIdentifier, potentialPassword);
|
|
149
|
+
if (result.type === "success") {
|
|
150
|
+
// Successfully authenticated with email:password
|
|
151
|
+
const accessToken = result.access;
|
|
152
|
+
|
|
153
|
+
// If models are defined in the provider, set cost to 0 to indicate free usage
|
|
154
|
+
if (provider.models) {
|
|
155
|
+
for (const model of Object.values(provider.models)) {
|
|
156
|
+
if (model) {
|
|
157
|
+
model.cost = { input: 0, output: 0 };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
apiKey: accessToken,
|
|
164
|
+
async fetch(input: Parameters<typeof fetch>[0], init?: RequestInit) {
|
|
165
|
+
// If this isn't a DeepSeek request, pass through normally
|
|
166
|
+
if (!isDeepSeekRequest(input)) {
|
|
167
|
+
return fetch(input as RequestInfo, init);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Prepare the request with proper headers
|
|
171
|
+
const { request, init: transformedInit } = prepareDeepSeekRequest(
|
|
172
|
+
input,
|
|
173
|
+
init,
|
|
174
|
+
accessToken
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Make the API call
|
|
178
|
+
const response = await fetch(request, transformedInit);
|
|
179
|
+
|
|
180
|
+
// Transform response if needed
|
|
181
|
+
return transformDeepSeekResponse(response);
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.warn("Email:password authentication failed, treating as regular API key:", error);
|
|
187
|
+
// Fall through to treat as regular API key
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Default: treat as direct DeepSeek access token or other API key
|
|
193
|
+
const accessToken = apiKeyValue;
|
|
127
194
|
|
|
128
195
|
return {
|
|
129
196
|
apiKey: accessToken,
|
|
@@ -149,6 +216,34 @@ export const DeepSeekAuthPlugin = async (
|
|
|
149
216
|
};
|
|
150
217
|
}
|
|
151
218
|
},
|
|
152
|
-
methods: [
|
|
219
|
+
methods: [{
|
|
220
|
+
label: "Login with DeepSeek Account",
|
|
221
|
+
type: "custom",
|
|
222
|
+
provider: "deepseek",
|
|
223
|
+
prompts: [
|
|
224
|
+
{ name: "apiKey", label: "Email:Password", type: "text", required: true }
|
|
225
|
+
],
|
|
226
|
+
authorize: async () => {
|
|
227
|
+
return {
|
|
228
|
+
url: "Configuration required",
|
|
229
|
+
instructions: "Enter your DeepSeek account credentials in email:password format",
|
|
230
|
+
method: "CONFIG",
|
|
231
|
+
form: {
|
|
232
|
+
fields: [
|
|
233
|
+
{
|
|
234
|
+
name: "apiKey",
|
|
235
|
+
label: "Email:Password",
|
|
236
|
+
type: "text",
|
|
237
|
+
required: true
|
|
238
|
+
}
|
|
239
|
+
]
|
|
240
|
+
},
|
|
241
|
+
callback: async (params?: any) => {
|
|
242
|
+
// This will be handled by the loader function when credentials are provided
|
|
243
|
+
return params;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}],
|
|
153
248
|
},
|
|
154
249
|
});
|
package/src/types.js
ADDED
package/src/types.ts
CHANGED
|
@@ -26,7 +26,11 @@ export interface PluginResult {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export interface GetAuth {
|
|
29
|
-
(): Promise<
|
|
29
|
+
(): Promise<{
|
|
30
|
+
apiKey?: string;
|
|
31
|
+
type?: string; // Additional field to specify the type of authentication
|
|
32
|
+
provider?: string; // Additional field to specify which provider this auth is for
|
|
33
|
+
}>;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
export interface LoaderResult {
|
|
@@ -44,6 +48,7 @@ export interface AuthMethod {
|
|
|
44
48
|
label: string;
|
|
45
49
|
type: string;
|
|
46
50
|
provider?: string;
|
|
51
|
+
prompts?: Array<{name: string, label: string, type: string, required: boolean}>; // Added to support OpenCode's expected format
|
|
47
52
|
authorize?: () => Promise<{
|
|
48
53
|
url: string;
|
|
49
54
|
instructions: string;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Test script to validate authentication flow in the DeepSeek Auth plugin
|
|
4
|
+
* This script simulates different authentication scenarios to ensure
|
|
5
|
+
* the plugin correctly handles various input formats
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
// Test scenarios
|
|
9
|
+
async function runTests() {
|
|
10
|
+
console.log("Running authentication flow tests...\n");
|
|
11
|
+
// Test 1: Email:password format (should trigger login flow)
|
|
12
|
+
console.log("Test 1: Valid email:password format");
|
|
13
|
+
try {
|
|
14
|
+
const mockAuth = {
|
|
15
|
+
apiKey: "test@example.com:mypassword123",
|
|
16
|
+
type: "deepseek-email-password",
|
|
17
|
+
provider: "deepseek"
|
|
18
|
+
};
|
|
19
|
+
// Simulate the authentication logic
|
|
20
|
+
const parts = mockAuth.apiKey.split(':');
|
|
21
|
+
if (parts.length === 2) {
|
|
22
|
+
const [identifier, password] = parts;
|
|
23
|
+
console.log(` - Detected email:password format: ${identifier}:${password.replace(/./g, '*')}`);
|
|
24
|
+
// Check if looks like valid email format
|
|
25
|
+
const isEmailFormat = identifier.includes('@');
|
|
26
|
+
const isPasswordReasonable = password.length >= 6;
|
|
27
|
+
if (isEmailFormat && isPasswordReasonable) {
|
|
28
|
+
console.log(" ✓ Correctly identified as email:password format");
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.log(" ✗ Incorrectly rejected as email:password format");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.log(` ✗ Error: ${error}`);
|
|
37
|
+
}
|
|
38
|
+
console.log();
|
|
39
|
+
// Test 2: Regular API key (should pass through directly)
|
|
40
|
+
console.log("Test 2: Regular API key format");
|
|
41
|
+
try {
|
|
42
|
+
const mockAuth = {
|
|
43
|
+
apiKey: "sk-deepseek-actual-api-token-here",
|
|
44
|
+
type: "standard-api-key"
|
|
45
|
+
};
|
|
46
|
+
const parts = mockAuth.apiKey.split(':');
|
|
47
|
+
if (parts.length === 1) {
|
|
48
|
+
console.log(` - Detected as regular API key: ${mockAuth.apiKey.substring(0, 15)}...`);
|
|
49
|
+
console.log(" ✓ Correctly treated as direct API key");
|
|
50
|
+
}
|
|
51
|
+
else if (parts.length === 2) {
|
|
52
|
+
// Check if it's actually email:password
|
|
53
|
+
const [identifier, password] = parts;
|
|
54
|
+
const isEmailFormat = identifier.includes('@');
|
|
55
|
+
if (!isEmailFormat) {
|
|
56
|
+
console.log(` - Found colon-separated key but not email format: ${mockAuth.apiKey}`);
|
|
57
|
+
console.log(" ✓ Correctly identified as non-email:password format API key");
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
console.log(" ⚠ May be incorrectly treated as email:password");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.log(` ✗ Error: ${error}`);
|
|
66
|
+
}
|
|
67
|
+
console.log();
|
|
68
|
+
// Test 3: Invalid email:password (wrong format)
|
|
69
|
+
console.log("Test 3: Invalid email:password format");
|
|
70
|
+
try {
|
|
71
|
+
const mockAuth = {
|
|
72
|
+
apiKey: "not-an-email:simple",
|
|
73
|
+
type: "deepseek-email-password",
|
|
74
|
+
provider: "deepseek"
|
|
75
|
+
};
|
|
76
|
+
const parts = mockAuth.apiKey.split(':');
|
|
77
|
+
if (parts.length === 2) {
|
|
78
|
+
const [identifier, password] = parts;
|
|
79
|
+
const isEmailFormat = identifier.includes('@');
|
|
80
|
+
const isPasswordReasonable = password.length >= 6;
|
|
81
|
+
if (isEmailFormat && isPasswordReasonable) {
|
|
82
|
+
console.log(" ✓ Correctly validated format");
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
console.log(" ✓ Correctly rejected invalid format");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
console.log(` ✗ Error: ${error}`);
|
|
91
|
+
}
|
|
92
|
+
console.log();
|
|
93
|
+
// Test 4: Phone:password format
|
|
94
|
+
console.log("Test 4: Phone:password format");
|
|
95
|
+
try {
|
|
96
|
+
const mockAuth = {
|
|
97
|
+
apiKey: "+1234567890:myphonepass",
|
|
98
|
+
type: "deepseek-email-password",
|
|
99
|
+
provider: "deepseek"
|
|
100
|
+
};
|
|
101
|
+
const parts = mockAuth.apiKey.split(':');
|
|
102
|
+
if (parts.length === 2) {
|
|
103
|
+
const [identifier, password] = parts;
|
|
104
|
+
const isPhoneFormat = /^[0-9+\-\s()]+$/.test(identifier);
|
|
105
|
+
const isPasswordReasonable = password.length >= 6;
|
|
106
|
+
if (isPhoneFormat && isPasswordReasonable) {
|
|
107
|
+
console.log(` - Identified as phone:password format: ${identifier}:${password.replace(/./g, '*')}`);
|
|
108
|
+
console.log(" ✓ Correctly identified phone:password format");
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
console.log(" ✓ Correctly rejected invalid phone:password format");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
console.log(` ✗ Error: ${error}`);
|
|
117
|
+
}
|
|
118
|
+
console.log();
|
|
119
|
+
console.log("Authentication flow tests completed.");
|
|
120
|
+
}
|
|
121
|
+
// Run the tests
|
|
122
|
+
runTests().catch(console.error);
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test script to validate authentication flow in the DeepSeek Auth plugin
|
|
3
|
+
* This script simulates different authentication scenarios to ensure
|
|
4
|
+
* the plugin correctly handles various input formats
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { DeepSeekAuthPlugin } from './src/plugin';
|
|
8
|
+
import { loginDeepSeek } from './src/deepseek/auth';
|
|
9
|
+
|
|
10
|
+
// Mock plugin context and types for testing
|
|
11
|
+
interface MockAuth {
|
|
12
|
+
apiKey?: string;
|
|
13
|
+
type?: string;
|
|
14
|
+
provider?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface MockProvider {
|
|
18
|
+
models?: Record<string, any>;
|
|
19
|
+
id?: string;
|
|
20
|
+
options?: Record<string, any>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Test scenarios
|
|
24
|
+
async function runTests() {
|
|
25
|
+
console.log("Running authentication flow tests...\n");
|
|
26
|
+
|
|
27
|
+
// Test 1: Email:password format (should trigger login flow)
|
|
28
|
+
console.log("Test 1: Valid email:password format");
|
|
29
|
+
try {
|
|
30
|
+
const mockAuth: MockAuth = {
|
|
31
|
+
apiKey: "test@example.com:mypassword123",
|
|
32
|
+
type: "deepseek-email-password",
|
|
33
|
+
provider: "deepseek"
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Simulate the authentication logic
|
|
37
|
+
const parts = mockAuth.apiKey!.split(':');
|
|
38
|
+
if (parts.length === 2) {
|
|
39
|
+
const [identifier, password] = parts;
|
|
40
|
+
console.log(` - Detected email:password format: ${identifier}:${password.replace(/./g, '*')}`);
|
|
41
|
+
|
|
42
|
+
// Check if looks like valid email format
|
|
43
|
+
const isEmailFormat = identifier.includes('@');
|
|
44
|
+
const isPasswordReasonable = password.length >= 6;
|
|
45
|
+
|
|
46
|
+
if (isEmailFormat && isPasswordReasonable) {
|
|
47
|
+
console.log(" ✓ Correctly identified as email:password format");
|
|
48
|
+
} else {
|
|
49
|
+
console.log(" ✗ Incorrectly rejected as email:password format");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.log(` ✗ Error: ${error}`);
|
|
54
|
+
}
|
|
55
|
+
console.log();
|
|
56
|
+
|
|
57
|
+
// Test 2: Regular API key (should pass through directly)
|
|
58
|
+
console.log("Test 2: Regular API key format");
|
|
59
|
+
try {
|
|
60
|
+
const mockAuth: MockAuth = {
|
|
61
|
+
apiKey: "sk-deepseek-actual-api-token-here",
|
|
62
|
+
type: "standard-api-key"
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const parts = mockAuth.apiKey!.split(':');
|
|
66
|
+
if (parts.length === 1) {
|
|
67
|
+
console.log(` - Detected as regular API key: ${mockAuth.apiKey!.substring(0, 15)}...`);
|
|
68
|
+
console.log(" ✓ Correctly treated as direct API key");
|
|
69
|
+
} else if (parts.length === 2) {
|
|
70
|
+
// Check if it's actually email:password
|
|
71
|
+
const [identifier, password] = parts;
|
|
72
|
+
const isEmailFormat = identifier.includes('@');
|
|
73
|
+
if (!isEmailFormat) {
|
|
74
|
+
console.log(` - Found colon-separated key but not email format: ${mockAuth.apiKey}`);
|
|
75
|
+
console.log(" ✓ Correctly identified as non-email:password format API key");
|
|
76
|
+
} else {
|
|
77
|
+
console.log(" ⚠ May be incorrectly treated as email:password");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.log(` ✗ Error: ${error}`);
|
|
82
|
+
}
|
|
83
|
+
console.log();
|
|
84
|
+
|
|
85
|
+
// Test 3: Invalid email:password (wrong format)
|
|
86
|
+
console.log("Test 3: Invalid email:password format");
|
|
87
|
+
try {
|
|
88
|
+
const mockAuth: MockAuth = {
|
|
89
|
+
apiKey: "not-an-email:simple",
|
|
90
|
+
type: "deepseek-email-password",
|
|
91
|
+
provider: "deepseek"
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const parts = mockAuth.apiKey!.split(':');
|
|
95
|
+
if (parts.length === 2) {
|
|
96
|
+
const [identifier, password] = parts;
|
|
97
|
+
const isEmailFormat = identifier.includes('@');
|
|
98
|
+
const isPasswordReasonable = password.length >= 6;
|
|
99
|
+
|
|
100
|
+
if (isEmailFormat && isPasswordReasonable) {
|
|
101
|
+
console.log(" ✓ Correctly validated format");
|
|
102
|
+
} else {
|
|
103
|
+
console.log(" ✓ Correctly rejected invalid format");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.log(` ✗ Error: ${error}`);
|
|
108
|
+
}
|
|
109
|
+
console.log();
|
|
110
|
+
|
|
111
|
+
// Test 4: Phone:password format
|
|
112
|
+
console.log("Test 4: Phone:password format");
|
|
113
|
+
try {
|
|
114
|
+
const mockAuth: MockAuth = {
|
|
115
|
+
apiKey: "+1234567890:myphonepass",
|
|
116
|
+
type: "deepseek-email-password",
|
|
117
|
+
provider: "deepseek"
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const parts = mockAuth.apiKey!.split(':');
|
|
121
|
+
if (parts.length === 2) {
|
|
122
|
+
const [identifier, password] = parts;
|
|
123
|
+
const isPhoneFormat = /^[0-9+\-\s()]+$/.test(identifier);
|
|
124
|
+
const isPasswordReasonable = password.length >= 6;
|
|
125
|
+
|
|
126
|
+
if (isPhoneFormat && isPasswordReasonable) {
|
|
127
|
+
console.log(` - Identified as phone:password format: ${identifier}:${password.replace(/./g, '*')}`);
|
|
128
|
+
console.log(" ✓ Correctly identified phone:password format");
|
|
129
|
+
} else {
|
|
130
|
+
console.log(" ✓ Correctly rejected invalid phone:password format");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.log(` ✗ Error: ${error}`);
|
|
135
|
+
}
|
|
136
|
+
console.log();
|
|
137
|
+
|
|
138
|
+
console.log("Authentication flow tests completed.");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Run the tests
|
|
142
|
+
runTests().catch(console.error);
|