@arikusi/deepseek-mcp-server 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -16,6 +16,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
16
16
  ### Fixed
17
17
  - Nothing yet
18
18
 
19
+ ## [1.1.0] - 2026-02-10
20
+
21
+ ### Added
22
+ - **Function Calling Support**: Full OpenAI-compatible function calling via `tools` and `tool_choice` parameters
23
+ - Define up to 128 tool definitions with JSON Schema parameters
24
+ - Control tool behavior with `tool_choice`: auto, none, required, or specific function
25
+ - Tool call results formatted in response with call IDs and arguments
26
+ - Streaming + function calling works together (delta accumulation)
27
+ - `tool` message role for sending tool results back
28
+ - **Centralized Config System** (`src/config.ts`)
29
+ - Zod-validated configuration from environment variables
30
+ - `DEEPSEEK_BASE_URL`: Custom API endpoint (default: `https://api.deepseek.com`)
31
+ - `SHOW_COST_INFO`: Toggle cost display in responses (default: true)
32
+ - `REQUEST_TIMEOUT`: API request timeout in ms (default: 60000)
33
+ - `MAX_RETRIES`: Maximum API retry count (default: 2)
34
+ - **Test Suite**: 85 tests with Vitest
35
+ - Config, Cost, Schemas, Client, and Function Calling tests
36
+ - 80%+ code coverage with v8 provider
37
+ - `npm test`, `npm run test:watch`, `npm run test:coverage` scripts
38
+ - **2 New Prompt Templates** (total: 12)
39
+ - `function_call_debug`: Debug function calling issues
40
+ - `create_function_schema`: Generate JSON Schema from natural language
41
+ - CI coverage job in GitHub Actions
42
+
43
+ ### Changed
44
+ - **Project Structure**: Modularized codebase
45
+ - `src/config.ts`: Centralized configuration
46
+ - `src/cost.ts`: Cost calculation (extracted from index.ts)
47
+ - `src/schemas.ts`: Zod validation schemas (extracted from index.ts)
48
+ - `DeepSeekClient` constructor now uses centralized config (no manual apiKey passing)
49
+ - Server version bumped to 1.1.0
50
+ - Updated `deepseek_chat` tool description to mention function calling
51
+
19
52
  ## [1.0.3] - 2025-02-07
20
53
 
21
54
  ### Added
@@ -91,6 +124,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
91
124
 
92
125
  ## Version History
93
126
 
127
+ - **1.1.0** (2026-02-10): Function calling, config system, test suite
94
128
  - **1.0.3** (2025-02-07): Cost tracking and prompt templates
95
129
  - **1.0.0** (2025-01-13): Initial public release
96
130
  - **0.1.0** (Development): Internal development version
@@ -101,7 +135,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
101
135
  - [GitHub repository](https://github.com/arikusi/deepseek-mcp-server)
102
136
  - [Issue tracker](https://github.com/arikusi/deepseek-mcp-server/issues)
103
137
 
104
- [Unreleased]: https://github.com/arikusi/deepseek-mcp-server/compare/v1.0.3...HEAD
138
+ [Unreleased]: https://github.com/arikusi/deepseek-mcp-server/compare/v1.1.0...HEAD
139
+ [1.1.0]: https://github.com/arikusi/deepseek-mcp-server/compare/v1.0.3...v1.1.0
105
140
  [1.0.3]: https://github.com/arikusi/deepseek-mcp-server/releases/tag/v1.0.3
106
141
  [1.0.0]: https://github.com/arikusi/deepseek-mcp-server/releases/tag/v1.0.0
107
142
  [0.1.0]: https://github.com/arikusi/deepseek-mcp-server/releases/tag/v0.1.0
package/README.md CHANGED
@@ -14,9 +14,9 @@ A Model Context Protocol (MCP) server that integrates DeepSeek AI models with MC
14
14
  - Gemini CLI (if MCP support is available)
15
15
  - Any MCP-compatible client
16
16
 
17
- > **⚠️ Note**: This is an unofficial community project and is not affiliated with DeepSeek.
17
+ > **Note**: This is an unofficial community project and is not affiliated with DeepSeek.
18
18
 
19
- ## Quick Start
19
+ ## Quick Start
20
20
 
21
21
  ### For Claude Code
22
22
 
@@ -42,19 +42,22 @@ gemini mcp add deepseek npx @arikusi/deepseek-mcp-server -e DEEPSEEK_API_KEY=you
42
42
 
43
43
  **Get your API key:** [https://platform.deepseek.com](https://platform.deepseek.com)
44
44
 
45
- That's it! Your MCP client can now use DeepSeek models! 🎉
45
+ That's it! Your MCP client can now use DeepSeek models!
46
46
 
47
47
  ---
48
48
 
49
49
  ## Features
50
50
 
51
- - 🤖 **DeepSeek Chat**: Fast and capable general-purpose model
52
- - 🧠 **DeepSeek Reasoner (R1)**: Advanced reasoning with chain-of-thought explanations
53
- - 💰 **Cost Tracking**: Automatic cost calculation for every request (USD)
54
- - 📋 **10 Prompt Templates**: Pre-built templates for debugging, code review, research, and more
55
- - 🔄 **Streaming Support**: Real-time response generation
56
- - 🛡️ **Type-Safe**: Full TypeScript implementation
57
- - 🎯 **MCP Compatible**: Works with any MCP-compatible CLI (Claude Code, Gemini CLI, etc.)
51
+ - **DeepSeek Chat**: Fast and capable general-purpose model
52
+ - **DeepSeek Reasoner (R1)**: Advanced reasoning with chain-of-thought explanations
53
+ - **Function Calling**: OpenAI-compatible tool use with up to 128 tool definitions
54
+ - **Cost Tracking**: Automatic cost calculation for every request (USD)
55
+ - **Configurable**: Environment-based configuration with validation
56
+ - **12 Prompt Templates**: Pre-built templates for debugging, code review, function calling, and more
57
+ - **Streaming Support**: Real-time response generation
58
+ - **Tested**: 85 tests with 80%+ code coverage
59
+ - **Type-Safe**: Full TypeScript implementation
60
+ - **MCP Compatible**: Works with any MCP-compatible CLI (Claude Code, Gemini CLI, etc.)
58
61
 
59
62
  ## Installation
60
63
 
@@ -130,22 +133,26 @@ If your MCP client doesn't support the `add` command, manually add to your confi
130
133
 
131
134
  ### `deepseek_chat`
132
135
 
133
- Chat with DeepSeek AI models with automatic cost tracking.
136
+ Chat with DeepSeek AI models with automatic cost tracking and function calling support.
134
137
 
135
138
  **Parameters:**
136
139
 
137
140
  - `messages` (required): Array of conversation messages
138
- - `role`: "system" | "user" | "assistant"
141
+ - `role`: "system" | "user" | "assistant" | "tool"
139
142
  - `content`: Message text
143
+ - `tool_call_id` (optional): Required for tool role messages
140
144
  - `model` (optional): "deepseek-chat" (default) or "deepseek-reasoner"
141
145
  - `temperature` (optional): 0-2, controls randomness (default: 1.0)
142
146
  - `max_tokens` (optional): Maximum tokens to generate
143
147
  - `stream` (optional): Enable streaming mode (default: false)
148
+ - `tools` (optional): Array of tool definitions for function calling (max 128)
149
+ - `tool_choice` (optional): "auto" | "none" | "required" | `{type: "function", function: {name: "..."}}`
144
150
 
145
151
  **Response includes:**
146
152
  - Content with formatting
153
+ - Function call results (if tools were used)
147
154
  - Request information (tokens, model, cost in USD)
148
- - Structured data with `cost_usd` field
155
+ - Structured data with `cost_usd` and `tool_calls` fields
149
156
 
150
157
  **Example:**
151
158
 
@@ -179,9 +186,44 @@ Chat with DeepSeek AI models with automatic cost tracking.
179
186
 
180
187
  The reasoner model will show its thinking process in `<thinking>` tags followed by the final answer.
181
188
 
189
+ **Function Calling Example:**
190
+
191
+ ```json
192
+ {
193
+ "messages": [
194
+ {
195
+ "role": "user",
196
+ "content": "What's the weather in Istanbul?"
197
+ }
198
+ ],
199
+ "tools": [
200
+ {
201
+ "type": "function",
202
+ "function": {
203
+ "name": "get_weather",
204
+ "description": "Get current weather for a location",
205
+ "parameters": {
206
+ "type": "object",
207
+ "properties": {
208
+ "location": {
209
+ "type": "string",
210
+ "description": "City name"
211
+ }
212
+ },
213
+ "required": ["location"]
214
+ }
215
+ }
216
+ }
217
+ ],
218
+ "tool_choice": "auto"
219
+ }
220
+ ```
221
+
222
+ When the model decides to call a function, the response includes `tool_calls` with the function name and arguments. You can then send the result back using a `tool` role message with the matching `tool_call_id`.
223
+
182
224
  ## Available Prompts
183
225
 
184
- Pre-built prompt templates for common tasks:
226
+ Pre-built prompt templates for common tasks (12 total):
185
227
 
186
228
  ### Core Reasoning
187
229
  - **debug_with_reasoning**: Debug code with step-by-step analysis
@@ -197,6 +239,10 @@ Pre-built prompt templates for common tasks:
197
239
  - **cost_comparison**: Compare LLM costs for tasks
198
240
  - **pair_programming**: Interactive coding with explanations
199
241
 
242
+ ### Function Calling
243
+ - **function_call_debug**: Debug function calling issues with tool definitions and messages
244
+ - **create_function_schema**: Generate JSON Schema for function calling from natural language
245
+
200
246
  Each prompt is optimized for the DeepSeek Reasoner model to provide detailed reasoning.
201
247
 
202
248
  ## Models
@@ -216,6 +262,26 @@ Each prompt is optimized for the DeepSeek Reasoner model to provide detailed rea
216
262
  - **Special**: Provides chain-of-thought reasoning
217
263
  - **Output**: Both reasoning process and final answer
218
264
 
265
+ ## Configuration
266
+
267
+ The server is configured via environment variables. All settings except `DEEPSEEK_API_KEY` are optional.
268
+
269
+ | Variable | Default | Description |
270
+ |----------|---------|-------------|
271
+ | `DEEPSEEK_API_KEY` | (required) | Your DeepSeek API key |
272
+ | `DEEPSEEK_BASE_URL` | `https://api.deepseek.com` | Custom API endpoint |
273
+ | `SHOW_COST_INFO` | `true` | Show cost info in responses |
274
+ | `REQUEST_TIMEOUT` | `60000` | Request timeout in milliseconds |
275
+ | `MAX_RETRIES` | `2` | Maximum retry count for failed requests |
276
+
277
+ **Example with custom config:**
278
+ ```bash
279
+ claude mcp add -s user deepseek npx @arikusi/deepseek-mcp-server \
280
+ -e DEEPSEEK_API_KEY=your-key \
281
+ -e SHOW_COST_INFO=false \
282
+ -e REQUEST_TIMEOUT=30000
283
+ ```
284
+
219
285
  ## Development
220
286
 
221
287
  ### Project Structure
@@ -223,10 +289,19 @@ Each prompt is optimized for the DeepSeek Reasoner model to provide detailed rea
223
289
  ```
224
290
  deepseek-mcp-server/
225
291
  ├── src/
226
- │ ├── index.ts # Main MCP server
227
- │ ├── deepseek-client.ts # DeepSeek API wrapper
228
- └── types.ts # TypeScript definitions
229
- ├── dist/ # Compiled JavaScript
292
+ │ ├── index.ts # Main MCP server, tool & prompt registration
293
+ │ ├── deepseek-client.ts # DeepSeek API wrapper (OpenAI SDK)
294
+ ├── config.ts # Centralized config with Zod validation
295
+ ├── cost.ts # Cost calculation and formatting
296
+ │ ├── schemas.ts # Zod input validation schemas
297
+ │ ├── types.ts # TypeScript type definitions
298
+ │ ├── config.test.ts # Config tests
299
+ │ ├── cost.test.ts # Cost tests
300
+ │ ├── schemas.test.ts # Schema validation tests
301
+ │ ├── deepseek-client.test.ts # Client tests
302
+ │ └── function-calling.test.ts # Function calling tests
303
+ ├── dist/ # Compiled JavaScript
304
+ ├── vitest.config.ts # Test configuration
230
305
  ├── package.json
231
306
  ├── tsconfig.json
232
307
  └── README.md
@@ -244,6 +319,19 @@ npm run build
244
319
  npm run watch
245
320
  ```
246
321
 
322
+ ### Testing
323
+
324
+ ```bash
325
+ # Run all tests
326
+ npm test
327
+
328
+ # Watch mode
329
+ npm run test:watch
330
+
331
+ # With coverage report
332
+ npm run test:coverage
333
+ ```
334
+
247
335
  ### Testing Locally
248
336
 
249
337
  ```bash
@@ -357,10 +445,10 @@ MIT License - see [LICENSE](LICENSE) file for details
357
445
 
358
446
  ## Support
359
447
 
360
- - 📖 [Documentation](https://github.com/arikusi/deepseek-mcp-server#readme)
361
- - 🐛 [Bug Reports](https://github.com/arikusi/deepseek-mcp-server/issues)
362
- - 💬 [Discussions](https://github.com/arikusi/deepseek-mcp-server/discussions)
363
- - 📧 Contact: [GitHub Issues](https://github.com/arikusi/deepseek-mcp-server/issues)
448
+ - [Documentation](https://github.com/arikusi/deepseek-mcp-server#readme)
449
+ - [Bug Reports](https://github.com/arikusi/deepseek-mcp-server/issues)
450
+ - [Discussions](https://github.com/arikusi/deepseek-mcp-server/discussions)
451
+ - Contact: [GitHub Issues](https://github.com/arikusi/deepseek-mcp-server/issues)
364
452
 
365
453
  ## Resources
366
454
 
@@ -376,6 +464,6 @@ MIT License - see [LICENSE](LICENSE) file for details
376
464
 
377
465
  ---
378
466
 
379
- **Made with ❤️ by [@arikusi](https://github.com/arikusi)**
467
+ **Made by [@arikusi](https://github.com/arikusi)**
380
468
 
381
469
  This is an unofficial community project and is not affiliated with DeepSeek.
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Centralized Configuration
3
+ * Loads and validates configuration from environment variables
4
+ */
5
+ import { z } from 'zod';
6
+ declare const ConfigSchema: z.ZodObject<{
7
+ apiKey: z.ZodString;
8
+ baseUrl: z.ZodDefault<z.ZodString>;
9
+ showCostInfo: z.ZodDefault<z.ZodBoolean>;
10
+ requestTimeout: z.ZodDefault<z.ZodNumber>;
11
+ maxRetries: z.ZodDefault<z.ZodNumber>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ apiKey: string;
14
+ baseUrl: string;
15
+ showCostInfo: boolean;
16
+ requestTimeout: number;
17
+ maxRetries: number;
18
+ }, {
19
+ apiKey: string;
20
+ baseUrl?: string | undefined;
21
+ showCostInfo?: boolean | undefined;
22
+ requestTimeout?: number | undefined;
23
+ maxRetries?: number | undefined;
24
+ }>;
25
+ export type Config = z.infer<typeof ConfigSchema>;
26
+ /**
27
+ * Load configuration from environment variables.
28
+ * Validates with Zod and caches the result.
29
+ * Exits process if validation fails (e.g., missing API key).
30
+ */
31
+ export declare function loadConfig(): Config;
32
+ /**
33
+ * Get the cached configuration.
34
+ * Throws if loadConfig() hasn't been called yet.
35
+ */
36
+ export declare function getConfig(): Config;
37
+ /**
38
+ * Reset cached configuration (for testing).
39
+ */
40
+ export declare function resetConfig(): void;
41
+ export {};
42
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;EAMhB,CAAC;AAEH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAIlD;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CA8BnC;AAED;;;GAGG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAKlC;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAElC"}
package/dist/config.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Centralized Configuration
3
+ * Loads and validates configuration from environment variables
4
+ */
5
+ import { z } from 'zod';
6
+ const ConfigSchema = z.object({
7
+ apiKey: z.string().min(1, 'DEEPSEEK_API_KEY is required'),
8
+ baseUrl: z.string().url().default('https://api.deepseek.com'),
9
+ showCostInfo: z.boolean().default(true),
10
+ requestTimeout: z.number().positive().default(60000),
11
+ maxRetries: z.number().min(0).max(10).default(2),
12
+ });
13
+ let cachedConfig = null;
14
+ /**
15
+ * Load configuration from environment variables.
16
+ * Validates with Zod and caches the result.
17
+ * Exits process if validation fails (e.g., missing API key).
18
+ */
19
+ export function loadConfig() {
20
+ const raw = {
21
+ apiKey: process.env.DEEPSEEK_API_KEY || '',
22
+ baseUrl: process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com',
23
+ showCostInfo: process.env.SHOW_COST_INFO !== 'false',
24
+ requestTimeout: process.env.REQUEST_TIMEOUT
25
+ ? parseInt(process.env.REQUEST_TIMEOUT, 10)
26
+ : 60000,
27
+ maxRetries: process.env.MAX_RETRIES
28
+ ? parseInt(process.env.MAX_RETRIES, 10)
29
+ : 2,
30
+ };
31
+ const result = ConfigSchema.safeParse(raw);
32
+ if (!result.success) {
33
+ console.error('Error: Configuration validation failed');
34
+ const issues = result.error.issues;
35
+ for (const issue of issues) {
36
+ console.error(` - ${issue.path.join('.')}: ${issue.message}`);
37
+ }
38
+ if (!raw.apiKey) {
39
+ console.error('Please set your DeepSeek API key:');
40
+ console.error(' export DEEPSEEK_API_KEY="your-api-key-here"');
41
+ }
42
+ process.exit(1);
43
+ }
44
+ cachedConfig = result.data;
45
+ return cachedConfig;
46
+ }
47
+ /**
48
+ * Get the cached configuration.
49
+ * Throws if loadConfig() hasn't been called yet.
50
+ */
51
+ export function getConfig() {
52
+ if (!cachedConfig) {
53
+ throw new Error('Config not loaded. Call loadConfig() first.');
54
+ }
55
+ return cachedConfig;
56
+ }
57
+ /**
58
+ * Reset cached configuration (for testing).
59
+ */
60
+ export function resetConfig() {
61
+ cachedConfig = null;
62
+ }
63
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,8BAA8B,CAAC;IACzD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,0BAA0B,CAAC;IAC7D,YAAY,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACvC,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACpD,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;CACjD,CAAC,CAAC;AAIH,IAAI,YAAY,GAAkB,IAAI,CAAC;AAEvC;;;;GAIG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,GAAG,GAAG;QACV,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE;QAC1C,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,0BAA0B;QACpE,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,OAAO;QACpD,cAAc,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe;YACzC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,EAAE,CAAC;YAC3C,CAAC,CAAC,KAAK;QACT,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW;YACjC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;YACvC,CAAC,CAAC,CAAC;KACN,CAAC;IAEF,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAE3C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;QACnC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACjE,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;YACnD,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC;IAC3B,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS;IACvB,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,YAAY,GAAG,IAAI,CAAC;AACtB,CAAC"}
package/dist/cost.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Cost Calculation Module
3
+ * Handles pricing and cost formatting for DeepSeek API requests
4
+ */
5
+ /** DeepSeek pricing per 1M tokens (USD) */
6
+ export declare const PRICING: {
7
+ readonly 'deepseek-chat': {
8
+ readonly prompt: 0.14;
9
+ readonly completion: 0.28;
10
+ };
11
+ readonly 'deepseek-reasoner': {
12
+ readonly prompt: 0.55;
13
+ readonly completion: 2.19;
14
+ };
15
+ };
16
+ /**
17
+ * Calculate cost for a request based on token usage
18
+ */
19
+ export declare function calculateCost(promptTokens: number, completionTokens: number, model: string): number;
20
+ /**
21
+ * Format cost as readable USD string
22
+ */
23
+ export declare function formatCost(cost: number): string;
24
+ //# sourceMappingURL=cost.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost.d.ts","sourceRoot":"","sources":["../src/cost.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,2CAA2C;AAC3C,eAAO,MAAM,OAAO;;;;;;;;;CASV,CAAC;AAEX;;GAEG;AACH,wBAAgB,aAAa,CAC3B,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,MAAM,EACxB,KAAK,EAAE,MAAM,GACZ,MAAM,CASR;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK/C"}
package/dist/cost.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Cost Calculation Module
3
+ * Handles pricing and cost formatting for DeepSeek API requests
4
+ */
5
+ /** DeepSeek pricing per 1M tokens (USD) */
6
+ export const PRICING = {
7
+ 'deepseek-chat': {
8
+ prompt: 0.14,
9
+ completion: 0.28,
10
+ },
11
+ 'deepseek-reasoner': {
12
+ prompt: 0.55,
13
+ completion: 2.19,
14
+ },
15
+ };
16
+ /**
17
+ * Calculate cost for a request based on token usage
18
+ */
19
+ export function calculateCost(promptTokens, completionTokens, model) {
20
+ const modelPricing = PRICING[model] || PRICING['deepseek-chat'];
21
+ const promptCost = (promptTokens / 1_000_000) * modelPricing.prompt;
22
+ const completionCost = (completionTokens / 1_000_000) * modelPricing.completion;
23
+ return promptCost + completionCost;
24
+ }
25
+ /**
26
+ * Format cost as readable USD string
27
+ */
28
+ export function formatCost(cost) {
29
+ if (cost < 0.01) {
30
+ return `$${cost.toFixed(4)}`;
31
+ }
32
+ return `$${cost.toFixed(2)}`;
33
+ }
34
+ //# sourceMappingURL=cost.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost.js","sourceRoot":"","sources":["../src/cost.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,2CAA2C;AAC3C,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,eAAe,EAAE;QACf,MAAM,EAAE,IAAI;QACZ,UAAU,EAAE,IAAI;KACjB;IACD,mBAAmB,EAAE;QACnB,MAAM,EAAE,IAAI;QACZ,UAAU,EAAE,IAAI;KACjB;CACO,CAAC;AAEX;;GAEG;AACH,MAAM,UAAU,aAAa,CAC3B,YAAoB,EACpB,gBAAwB,EACxB,KAAa;IAEb,MAAM,YAAY,GAChB,OAAO,CAAC,KAA6B,CAAC,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IAErE,MAAM,UAAU,GAAG,CAAC,YAAY,GAAG,SAAS,CAAC,GAAG,YAAY,CAAC,MAAM,CAAC;IACpE,MAAM,cAAc,GAClB,CAAC,gBAAgB,GAAG,SAAS,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC;IAE3D,OAAO,UAAU,GAAG,cAAc,CAAC;AACrC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,IAAI,GAAG,IAAI,EAAE,CAAC;QAChB,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/B,CAAC"}
@@ -5,15 +5,14 @@
5
5
  import type { ChatCompletionParams, ChatCompletionResponse } from './types.js';
6
6
  export declare class DeepSeekClient {
7
7
  private client;
8
- private baseURL;
9
- constructor(apiKey: string);
8
+ constructor();
10
9
  /**
11
10
  * Create a chat completion (non-streaming)
12
11
  */
13
12
  createChatCompletion(params: ChatCompletionParams): Promise<ChatCompletionResponse>;
14
13
  /**
15
14
  * Create a streaming chat completion
16
- * Returns the full text after streaming completes
15
+ * Returns the full text after streaming completes (buffered)
17
16
  */
18
17
  createStreamingChatCompletion(params: ChatCompletionParams): Promise<ChatCompletionResponse>;
19
18
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"deepseek-client.d.ts","sourceRoot":"","sources":["../src/deepseek-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EACV,oBAAoB,EACpB,sBAAsB,EAEvB,MAAM,YAAY,CAAC;AAEpB,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAA8B;gBAEjC,MAAM,EAAE,MAAM;IAW1B;;OAEG;IACG,oBAAoB,CACxB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,sBAAsB,CAAC;IA4ClC;;;OAGG;IACG,6BAA6B,CACjC,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,sBAAsB,CAAC;IAsElC;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;CAazC"}
1
+ {"version":3,"file":"deepseek-client.d.ts","sourceRoot":"","sources":["../src/deepseek-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EACV,oBAAoB,EACpB,sBAAsB,EAEvB,MAAM,YAAY,CAAC;AAEpB,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAS;;IAavB;;OAEG;IACG,oBAAoB,CACxB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,sBAAsB,CAAC;IAkElC;;;OAGG;IACG,6BAA6B,CACjC,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,sBAAsB,CAAC;IAyHlC;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;CAazC"}
@@ -3,16 +3,16 @@
3
3
  * Wrapper around OpenAI SDK for DeepSeek API
4
4
  */
5
5
  import OpenAI from 'openai';
6
+ import { getConfig } from './config.js';
6
7
  export class DeepSeekClient {
7
8
  client;
8
- baseURL = 'https://api.deepseek.com';
9
- constructor(apiKey) {
10
- if (!apiKey) {
11
- throw new Error('DeepSeek API key is required');
12
- }
9
+ constructor() {
10
+ const config = getConfig();
13
11
  this.client = new OpenAI({
14
- apiKey,
15
- baseURL: this.baseURL,
12
+ apiKey: config.apiKey,
13
+ baseURL: config.baseUrl,
14
+ timeout: config.requestTimeout,
15
+ maxRetries: config.maxRetries,
16
16
  });
17
17
  }
18
18
  /**
@@ -20,7 +20,9 @@ export class DeepSeekClient {
20
20
  */
21
21
  async createChatCompletion(params) {
22
22
  try {
23
- const response = await this.client.chat.completions.create({
23
+ // Build request params - using 'any' for OpenAI SDK compatibility
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ const requestParams = {
24
26
  model: params.model,
25
27
  messages: params.messages,
26
28
  temperature: params.temperature ?? 1.0,
@@ -30,7 +32,14 @@ export class DeepSeekClient {
30
32
  presence_penalty: params.presence_penalty,
31
33
  stop: params.stop,
32
34
  stream: false,
33
- });
35
+ };
36
+ if (params.tools?.length) {
37
+ requestParams.tools = params.tools;
38
+ }
39
+ if (params.tool_choice !== undefined) {
40
+ requestParams.tool_choice = params.tool_choice;
41
+ }
42
+ const response = await this.client.chat.completions.create(requestParams);
34
43
  const choice = response.choices[0];
35
44
  if (!choice) {
36
45
  throw new Error('No response from DeepSeek API');
@@ -39,6 +48,15 @@ export class DeepSeekClient {
39
48
  const reasoning_content = 'reasoning_content' in choice.message
40
49
  ? choice.message.reasoning_content
41
50
  : undefined;
51
+ // Extract tool_calls if present
52
+ const tool_calls = choice.message.tool_calls?.map((tc) => ({
53
+ id: tc.id,
54
+ type: 'function',
55
+ function: {
56
+ name: tc.function.name,
57
+ arguments: tc.function.arguments,
58
+ },
59
+ }));
42
60
  return {
43
61
  content: choice.message.content || '',
44
62
  reasoning_content,
@@ -49,6 +67,7 @@ export class DeepSeekClient {
49
67
  total_tokens: response.usage?.total_tokens || 0,
50
68
  },
51
69
  finish_reason: choice.finish_reason || 'stop',
70
+ tool_calls: tool_calls?.length ? tool_calls : undefined,
52
71
  };
53
72
  }
54
73
  catch (error) {
@@ -58,11 +77,13 @@ export class DeepSeekClient {
58
77
  }
59
78
  /**
60
79
  * Create a streaming chat completion
61
- * Returns the full text after streaming completes
80
+ * Returns the full text after streaming completes (buffered)
62
81
  */
63
82
  async createStreamingChatCompletion(params) {
64
83
  try {
65
- const stream = await this.client.chat.completions.create({
84
+ // Build request params - using 'any' for OpenAI SDK compatibility
85
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
+ const requestParams = {
66
87
  model: params.model,
67
88
  messages: params.messages,
68
89
  temperature: params.temperature ?? 1.0,
@@ -72,7 +93,14 @@ export class DeepSeekClient {
72
93
  presence_penalty: params.presence_penalty,
73
94
  stop: params.stop,
74
95
  stream: true,
75
- });
96
+ };
97
+ if (params.tools?.length) {
98
+ requestParams.tools = params.tools;
99
+ }
100
+ if (params.tool_choice !== undefined) {
101
+ requestParams.tool_choice = params.tool_choice;
102
+ }
103
+ const stream = await this.client.chat.completions.create(requestParams);
76
104
  let fullContent = '';
77
105
  let reasoningContent = '';
78
106
  let modelName = params.model;
@@ -82,6 +110,8 @@ export class DeepSeekClient {
82
110
  completion_tokens: 0,
83
111
  total_tokens: 0,
84
112
  };
113
+ // Tool calls accumulation (index-based)
114
+ const toolCallsMap = new Map();
85
115
  // Collect all chunks
86
116
  for await (const chunk of stream) {
87
117
  const choice = chunk.choices[0];
@@ -92,9 +122,31 @@ export class DeepSeekClient {
92
122
  fullContent += choice.delta.content;
93
123
  }
94
124
  // Collect reasoning content (for deepseek-reasoner)
95
- if ('reasoning_content' in choice.delta) {
125
+ if ('reasoning_content' in (choice.delta || {})) {
96
126
  reasoningContent += choice.delta.reasoning_content || '';
97
127
  }
128
+ // Accumulate tool_calls deltas
129
+ if (choice.delta?.tool_calls) {
130
+ for (const tc of choice.delta.tool_calls) {
131
+ const existing = toolCallsMap.get(tc.index);
132
+ if (existing) {
133
+ if (tc.function?.name)
134
+ existing.function.name += tc.function.name;
135
+ if (tc.function?.arguments)
136
+ existing.function.arguments += tc.function.arguments;
137
+ }
138
+ else {
139
+ toolCallsMap.set(tc.index, {
140
+ id: tc.id || '',
141
+ type: 'function',
142
+ function: {
143
+ name: tc.function?.name || '',
144
+ arguments: tc.function?.arguments || '',
145
+ },
146
+ });
147
+ }
148
+ }
149
+ }
98
150
  // Get finish reason
99
151
  if (choice.finish_reason) {
100
152
  finishReason = choice.finish_reason;
@@ -108,12 +160,19 @@ export class DeepSeekClient {
108
160
  usage = chunk.usage;
109
161
  }
110
162
  }
163
+ // Convert tool calls map to sorted array
164
+ const toolCalls = toolCallsMap.size > 0
165
+ ? Array.from(toolCallsMap.entries())
166
+ .sort(([a], [b]) => a - b)
167
+ .map(([, tc]) => tc)
168
+ : undefined;
111
169
  return {
112
170
  content: fullContent,
113
171
  reasoning_content: reasoningContent || undefined,
114
172
  model: modelName,
115
173
  usage,
116
174
  finish_reason: finishReason,
175
+ tool_calls: toolCalls,
117
176
  };
118
177
  }
119
178
  catch (error) {