opencode-deepseek-auth 1.0.0

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