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 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
- Once authenticated, OpenCode will use your DeepSeek account for requests.
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
- - 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
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 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
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
- This approach bypasses the need for separate API keys, letting you use your existing DeepSeek account quota and features.
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
- ### Credentials Not Working
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
- - Make sure you're not hitting rate limits
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:
@@ -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,CAuFrB,CAAC"}
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
- // Check if API key is in email:password or phone:password format
52
- const credentialParts = auth.apiKey.split(':');
53
- if (credentialParts.length === 2) {
54
- const [identifier, password] = credentialParts;
55
- // Convert credentials to DeepSeek access token
56
- const result = await (0, auth_1.loginDeepSeek)(identifier, password);
57
- if (result.type !== "success") {
58
- console.error(`Failed to authenticate with provided credentials: ${result.error}`);
59
- throw new Error(`Authentication failed: ${result.error}`);
60
- }
61
- // Successfully obtained access token from credentials
62
- const accessToken = result.access;
63
- // If models are defined in the provider, set cost to 0 to indicate free usage
64
- if (provider.models) {
65
- for (const model of Object.values(provider.models)) {
66
- if (model) {
67
- model.cost = { input: 0, output: 0 };
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
- // Assuming it's already a valid access token
89
- const accessToken = auth.apiKey;
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: [], // Empty methods array since we're using config-based auth
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;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
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<any>;
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;
@@ -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,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,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"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-deepseek-auth",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Authenticate the Opencode CLI with your DeepSeek account credentials",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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
- // Check if API key is in email:password or phone:password format
79
- const credentialParts = auth.apiKey.split(':');
80
- if (credentialParts.length === 2) {
81
- const [identifier, password] = credentialParts;
82
-
83
- // Convert credentials to DeepSeek access token
84
- const result = await loginDeepSeek(identifier, password);
85
- if (result.type !== "success") {
86
- console.error(`Failed to authenticate with provided credentials: ${result.error}`);
87
- throw new Error(`Authentication failed: ${result.error}`);
88
- }
89
-
90
- // Successfully obtained access token from credentials
91
- const accessToken = result.access;
92
-
93
- // If models are defined in the provider, set cost to 0 to indicate free usage
94
- if (provider.models) {
95
- for (const model of Object.values(provider.models)) {
96
- if (model) {
97
- model.cost = { input: 0, output: 0 };
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
- return {
103
- apiKey: accessToken,
104
- async fetch(input: Parameters<typeof fetch>[0], init?: RequestInit) {
105
- // If this isn't a DeepSeek request, pass through normally
106
- if (!isDeepSeekRequest(input)) {
107
- return fetch(input as RequestInfo, init);
108
- }
109
-
110
- // Prepare the request with proper headers
111
- const { request, init: transformedInit } = prepareDeepSeekRequest(
112
- input,
113
- init,
114
- accessToken
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
- // Make the API call
118
- const response = await fetch(request, transformedInit);
119
-
120
- // Transform response if needed
121
- return transformDeepSeekResponse(response);
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
- // Assuming it's already a valid access token
126
- const accessToken = auth.apiKey;
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: [], // Empty methods array since we're using config-based auth
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
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * Types for the DeepSeek Auth plugin
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
package/src/types.ts CHANGED
@@ -26,7 +26,11 @@ export interface PluginResult {
26
26
  }
27
27
 
28
28
  export interface GetAuth {
29
- (): Promise<any>;
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);