@molecule/api-ai-deepseek 1.0.11 → 1.0.12
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/dist/provider.d.ts +12 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +45 -18
- package/package.json +2 -2
package/dist/provider.d.ts
CHANGED
|
@@ -62,8 +62,20 @@ declare class DeepseekAIProviderImpl implements AIProvider {
|
|
|
62
62
|
* Converts internal `ChatMessage` objects to the OpenAI-compatible message format.
|
|
63
63
|
* System prompt is prepended as a system role message.
|
|
64
64
|
*
|
|
65
|
+
* Thinking mode has one wire obligation that non-thinking mode does not:
|
|
66
|
+
* DeepSeek's thinking-mode guide requires the `reasoning_content` of every
|
|
67
|
+
* retained assistant turn to be sent back on a later request carrying `tools`,
|
|
68
|
+
* including turns where the model made no tool call — omitting it is a 400.
|
|
69
|
+
* So when `replayReasoning` is set (the caller passed a `thinking` block), each
|
|
70
|
+
* assistant turn carries `ChatMessage.reasoning` verbatim. That reasoning is
|
|
71
|
+
* never synthesized: a turn with no recorded reasoning sends `''`, which is
|
|
72
|
+
* the only representation of "this turn ran without thinking" the wire has.
|
|
73
|
+
* A non-thinking request keeps the historical shape (no `reasoning_content`);
|
|
74
|
+
* the guide says a non-thinking request ignores the field anyway.
|
|
75
|
+
*
|
|
65
76
|
* @param messages - The chat messages to format.
|
|
66
77
|
* @param system - Optional system prompt to prepend.
|
|
78
|
+
* @param replayReasoning - Whether thinking mode is on for this request.
|
|
67
79
|
* @returns The formatted messages array for the DeepSeek API.
|
|
68
80
|
*/
|
|
69
81
|
private formatMessages;
|
package/dist/provider.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EACV,UAAU,EAIV,SAAS,EAET,UAAU,EAGX,MAAM,kBAAkB,CAAA;AASzB,OAAO,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EACV,UAAU,EAIV,SAAS,EAET,UAAU,EAGX,MAAM,kBAAkB,CAAA;AASzB,OAAO,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAwGhD,8DAA8D;AAC9D,eAAO,MAAM,8BAA8B,QAAS,CAAA;AAgGpD;;;;GAIG;AACH,cAAM,sBAAuB,YAAW,UAAU;IAChD,QAAQ,CAAC,IAAI,cAAa;IAC1B,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,WAAW,CAAC,CAAqB;IACzC,OAAO,CAAC,aAAa,CAAC,CAAuB;IAC7C,OAAO,CAAC,mBAAmB,CAAQ;IACnC,OAAO,CAAC,sBAAsB,CAAQ;gBAE1B,MAAM,GAAE,cAAmB;IAwBvC;;;;;OAKG;IACI,IAAI,CAAC,MAAM,EAAE,UAAU,GAAG,aAAa,CAAC,SAAS,CAAC;IAsRzD;;;;;;;;;OASG;YACW,2BAA2B;IA0EzC;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IA2BzB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,cAAc;IA8FtB;;;;;OAKG;IACH,OAAO,CAAC,WAAW;IAWnB;;;;;OAKG;IACH,OAAO,CAAE,yBAAyB;IAuDlC;;;;;;;;;;;;OAYG;YACY,sBAAsB;IA2GrC;;;;;;;;;;OAUG;IACH,OAAO,CAAE,eAAe;CA0LzB;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,+BAAyB,CAAA;AAExD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,UAAU,CAElE"}
|
package/dist/provider.js
CHANGED
|
@@ -17,9 +17,11 @@ const logger = getLogger();
|
|
|
17
17
|
// (not through the package barrel).
|
|
18
18
|
import './secrets.js';
|
|
19
19
|
/**
|
|
20
|
-
* Map thinking budget tokens to a DeepSeek reasoning_effort level
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* Map thinking budget tokens to a DeepSeek reasoning_effort level, used ONLY as
|
|
21
|
+
* a fallback when the caller supplies a budget without a resolved native level.
|
|
22
|
+
* The native API accepts `low`/`high`/`max`; a caller that resolved one of them
|
|
23
|
+
* (the catalog's `supportedEffortLevels`) sends it verbatim and never reaches
|
|
24
|
+
* this function, so the fallback stays the two coarse buckets.
|
|
23
25
|
*
|
|
24
26
|
* @param budgetTokens - Requested thinking budget in tokens.
|
|
25
27
|
* @returns `'high'` or `'max'` for the upstream effort hint.
|
|
@@ -149,7 +151,7 @@ class DeepseekAIProviderImpl {
|
|
|
149
151
|
const canonicalModel = params.model ?? this.defaultModel;
|
|
150
152
|
const model = this.modelMap[canonicalModel] ?? canonicalModel;
|
|
151
153
|
const maxTokens = params.maxTokens ?? this.maxTokens;
|
|
152
|
-
const messages = this.formatMessages(params.messages, params.system);
|
|
154
|
+
const messages = this.formatMessages(params.messages, params.system, Boolean(params.thinking));
|
|
153
155
|
// Stop sequences, capped at the API's own limit. Verified honoured by the
|
|
154
156
|
// native endpoint on 2026-10-17: with `stop: ['<analysis>']` a reply that
|
|
155
157
|
// would have opened with that tag streamed nothing at all, and one that
|
|
@@ -188,10 +190,11 @@ class DeepseekAIProviderImpl {
|
|
|
188
190
|
// weaker nudge still produces a turn, and the agent loop has its own
|
|
189
191
|
// prose-only backstops, whereas a 400 produces nothing at all.
|
|
190
192
|
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
// is
|
|
193
|
+
// LIVE for an entry that exposes opt-in reasoning (deepseek-flash, whose
|
|
194
|
+
// effort levels include the local `off` default): a project that picks
|
|
195
|
+
// low/high/max gets thinking mode on a discovery turn, and the forced
|
|
196
|
+
// nudge is degraded here instead of 400ing the first turn of the
|
|
197
|
+
// conversation.
|
|
195
198
|
const forcedChoice = params.toolChoice === 'required' ||
|
|
196
199
|
(typeof params.toolChoice === 'object' && params.toolChoice?.type === 'tool');
|
|
197
200
|
if (forcedChoice && params.thinking) {
|
|
@@ -206,15 +209,19 @@ class DeepseekAIProviderImpl {
|
|
|
206
209
|
body.tool_choice = { type: 'function', function: { name: params.toolChoice.name } };
|
|
207
210
|
}
|
|
208
211
|
}
|
|
209
|
-
// DeepSeek V4 defaults `thinking` to ENABLED.
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
212
|
+
// DeepSeek V4 defaults `thinking` to ENABLED. Per DeepSeek's thinking-mode
|
|
213
|
+
// guide (https://api-docs.deepseek.com/guides/thinking_mode/), a
|
|
214
|
+
// thinking-mode request that carries tools must send every retained
|
|
215
|
+
// assistant turn's `reasoning_content` back — including a turn where the
|
|
216
|
+
// model made no tool call — or the API answers 400. `formatMessages` replays
|
|
217
|
+
// `ChatMessage.reasoning` for exactly that reason. The catalog decides
|
|
218
|
+
// whether a turn carries a thinking block: deepseek-flash defaults to the
|
|
219
|
+
// local `off` level (no block → reasoning explicitly disabled below), and
|
|
220
|
+
// only a project that opts into low/high/max sends one.
|
|
214
221
|
if (params.thinking) {
|
|
215
222
|
body.thinking = { type: 'enabled' };
|
|
216
|
-
// Prefer a caller-resolved native value (
|
|
217
|
-
// catalog; fall back to the budget threshold.
|
|
223
|
+
// Prefer a caller-resolved native value (`low` | `high` | `max`) from the
|
|
224
|
+
// model catalog; fall back to the budget threshold.
|
|
218
225
|
body.reasoning_effort = params.thinking.effort ?? budgetToEffort(params.thinking.budgetTokens);
|
|
219
226
|
}
|
|
220
227
|
else {
|
|
@@ -518,11 +525,23 @@ class DeepseekAIProviderImpl {
|
|
|
518
525
|
* Converts internal `ChatMessage` objects to the OpenAI-compatible message format.
|
|
519
526
|
* System prompt is prepended as a system role message.
|
|
520
527
|
*
|
|
528
|
+
* Thinking mode has one wire obligation that non-thinking mode does not:
|
|
529
|
+
* DeepSeek's thinking-mode guide requires the `reasoning_content` of every
|
|
530
|
+
* retained assistant turn to be sent back on a later request carrying `tools`,
|
|
531
|
+
* including turns where the model made no tool call — omitting it is a 400.
|
|
532
|
+
* So when `replayReasoning` is set (the caller passed a `thinking` block), each
|
|
533
|
+
* assistant turn carries `ChatMessage.reasoning` verbatim. That reasoning is
|
|
534
|
+
* never synthesized: a turn with no recorded reasoning sends `''`, which is
|
|
535
|
+
* the only representation of "this turn ran without thinking" the wire has.
|
|
536
|
+
* A non-thinking request keeps the historical shape (no `reasoning_content`);
|
|
537
|
+
* the guide says a non-thinking request ignores the field anyway.
|
|
538
|
+
*
|
|
521
539
|
* @param messages - The chat messages to format.
|
|
522
540
|
* @param system - Optional system prompt to prepend.
|
|
541
|
+
* @param replayReasoning - Whether thinking mode is on for this request.
|
|
523
542
|
* @returns The formatted messages array for the DeepSeek API.
|
|
524
543
|
*/
|
|
525
|
-
formatMessages(messages, system) {
|
|
544
|
+
formatMessages(messages, system, replayReasoning = false) {
|
|
526
545
|
const formatted = [];
|
|
527
546
|
if (system) {
|
|
528
547
|
formatted.push({ role: 'system', content: system });
|
|
@@ -530,8 +549,14 @@ class DeepseekAIProviderImpl {
|
|
|
530
549
|
for (const m of messages) {
|
|
531
550
|
if (m.role === 'system')
|
|
532
551
|
continue; // handled above
|
|
552
|
+
// Only assistant turns have a reasoning channel. `reasoning_content: ''`
|
|
553
|
+
// is deliberate for a retained turn with no recorded reasoning (a
|
|
554
|
+
// non-thinking turn inside a thinking-mode history): the vendor requires
|
|
555
|
+
// the field on every retained assistant turn, and an empty string is the
|
|
556
|
+
// only way to say "nothing was recorded" without inventing content.
|
|
557
|
+
const reasoningField = replayReasoning && m.role === 'assistant' ? { reasoning_content: m.reasoning ?? '' } : {};
|
|
533
558
|
if (typeof m.content === 'string') {
|
|
534
|
-
formatted.push({ role: m.role, content: m.content });
|
|
559
|
+
formatted.push({ role: m.role, content: m.content, ...reasoningField });
|
|
535
560
|
continue;
|
|
536
561
|
}
|
|
537
562
|
// Content blocks — convert to OpenAI format
|
|
@@ -578,13 +603,14 @@ class DeepseekAIProviderImpl {
|
|
|
578
603
|
// screenshot tool) must not be dropped: Chat Completions accepts a user
|
|
579
604
|
// message after the tool messages, so emit them instead of losing them.
|
|
580
605
|
if (parts.length > 0)
|
|
581
|
-
formatted.push({ role: m.role, content: parts });
|
|
606
|
+
formatted.push({ role: m.role, content: parts, ...reasoningField });
|
|
582
607
|
}
|
|
583
608
|
else if (toolCalls.length > 0) {
|
|
584
609
|
// Assistant message with tool calls
|
|
585
610
|
formatted.push({
|
|
586
611
|
role: 'assistant',
|
|
587
612
|
content: parts.length > 0 ? parts : null,
|
|
613
|
+
...reasoningField,
|
|
588
614
|
tool_calls: toolCalls,
|
|
589
615
|
});
|
|
590
616
|
}
|
|
@@ -592,6 +618,7 @@ class DeepseekAIProviderImpl {
|
|
|
592
618
|
formatted.push({
|
|
593
619
|
role: m.role,
|
|
594
620
|
content: parts.length === 1 && parts[0].type === 'text' ? parts[0].text : parts,
|
|
621
|
+
...reasoningField,
|
|
595
622
|
});
|
|
596
623
|
}
|
|
597
624
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@molecule/api-ai-deepseek",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"description": "DeepSeek AI provider for molecule.dev",
|
|
5
5
|
"homepage": "https://www.molecule.dev/packages/api-ai-deepseek",
|
|
6
6
|
"type": "module",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@molecule/api-bond": "1.0.2",
|
|
38
38
|
"@types/node": "26.1.2",
|
|
39
39
|
"typescript": "6.0.3",
|
|
40
|
-
"vitest": "4.1.
|
|
40
|
+
"vitest": "4.1.12"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@molecule/api-ai": "^1.0.1",
|