@jsonstudio/llms 0.6.3214 → 0.6.3271

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.
Files changed (38) hide show
  1. package/dist/conversion/bridge-actions.js +37 -322
  2. package/dist/conversion/bridge-instructions.js +12 -109
  3. package/dist/conversion/codecs/anthropic-openai-codec.js +1 -1
  4. package/dist/conversion/compat/actions/deepseek-web-request.js +43 -110
  5. package/dist/conversion/compat/actions/deepseek-web-response.d.ts +3 -0
  6. package/dist/conversion/compat/actions/deepseek-web-response.js +150 -11
  7. package/dist/conversion/hub/operation-table/semantic-mappers/archive/chat-mapper.archive.d.ts +8 -0
  8. package/dist/conversion/hub/operation-table/semantic-mappers/archive/chat-mapper.archive.js +404 -0
  9. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +5 -384
  10. package/dist/conversion/hub/response/response-runtime.d.ts +1 -0
  11. package/dist/conversion/hub/response/response-runtime.js +26 -0
  12. package/dist/conversion/hub/snapshot-recorder.js +2 -91
  13. package/dist/conversion/hub/tool-governance/engine.d.ts +1 -1
  14. package/dist/conversion/hub/tool-governance/engine.js +17 -127
  15. package/dist/conversion/shared/anthropic-message-utils.d.ts +3 -1
  16. package/dist/conversion/shared/anthropic-message-utils.js +23 -15
  17. package/dist/conversion/shared/openai-finalizer.d.ts +0 -3
  18. package/dist/conversion/shared/openai-finalizer.js +11 -169
  19. package/dist/conversion/shared/openai-message-normalize.js +11 -72
  20. package/dist/conversion/shared/tool-mapping.js +5 -0
  21. package/dist/native/router_hotpath_napi.node +0 -0
  22. package/dist/router/virtual-router/bootstrap/provider-normalization.js +11 -3
  23. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +20 -0
  24. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +71 -0
  25. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-governance-semantics.d.ts +8 -0
  26. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-governance-semantics.js +48 -0
  27. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.d.ts +1 -0
  28. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js +30 -0
  29. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-semantic-mappers.d.ts +2 -0
  30. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-semantic-mappers.js +83 -0
  31. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +11 -0
  32. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +2 -0
  33. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +61 -0
  34. package/dist/router/virtual-router/engine-selection/native-snapshot-hooks.d.ts +1 -0
  35. package/dist/router/virtual-router/engine-selection/native-snapshot-hooks.js +40 -0
  36. package/dist/router/virtual-router/engine.js +58 -1
  37. package/dist/router/virtual-router/types.d.ts +1 -1
  38. package/package.json +1 -1
@@ -1,9 +1,9 @@
1
+ import { applyToolTextRequestGuidance } from './tool-text-request-guidance.js';
1
2
  const DEFAULT_OPTIONS = {
2
3
  strictToolRequired: true,
3
- textToolFallback: true
4
+ toolProtocol: undefined
4
5
  };
5
6
  const SEARCH_ROUTE_PREFIXES = ['web_search', 'search'];
6
- const TOOL_TEXT_GUIDANCE_MARKER = 'Tool-call output contract (STRICT)';
7
7
  const readString = (value) => {
8
8
  if (typeof value !== 'string') {
9
9
  return undefined;
@@ -27,6 +27,31 @@ function readBoolean(input, fallback) {
27
27
  }
28
28
  return fallback;
29
29
  }
30
+ function readOptionalBoolean(input) {
31
+ if (typeof input === 'boolean') {
32
+ return input;
33
+ }
34
+ if (typeof input === 'string') {
35
+ const normalized = input.trim().toLowerCase();
36
+ if (['true', '1', 'yes', 'on'].includes(normalized)) {
37
+ return true;
38
+ }
39
+ if (['false', '0', 'no', 'off'].includes(normalized)) {
40
+ return false;
41
+ }
42
+ }
43
+ return undefined;
44
+ }
45
+ function readToolProtocol(input) {
46
+ if (typeof input !== 'string') {
47
+ return undefined;
48
+ }
49
+ const normalized = input.trim().toLowerCase();
50
+ if (normalized === 'text' || normalized === 'native') {
51
+ return normalized;
52
+ }
53
+ return undefined;
54
+ }
30
55
  function resolveOptions(adapterContext) {
31
56
  const context = (adapterContext ?? {});
32
57
  const deepseekNode = isRecord(context.deepseek)
@@ -37,9 +62,12 @@ function resolveOptions(adapterContext) {
37
62
  if (!deepseekNode) {
38
63
  return { ...DEFAULT_OPTIONS };
39
64
  }
65
+ const legacyFallback = readOptionalBoolean(deepseekNode.textToolFallback);
66
+ const toolProtocol = readToolProtocol(deepseekNode.toolProtocol) ??
67
+ (legacyFallback !== undefined ? (legacyFallback ? 'text' : 'native') : undefined);
40
68
  return {
41
69
  strictToolRequired: readBoolean(deepseekNode.strictToolRequired, DEFAULT_OPTIONS.strictToolRequired),
42
- textToolFallback: readBoolean(deepseekNode.textToolFallback, DEFAULT_OPTIONS.textToolFallback)
70
+ toolProtocol
43
71
  };
44
72
  }
45
73
  function normalizeContentToText(content) {
@@ -127,90 +155,6 @@ function normalizeToolCallsAsText(toolCallsRaw) {
127
155
  return '';
128
156
  }
129
157
  }
130
- function summarizeToolSchema(tool) {
131
- const fn = isRecord(tool.function) ? tool.function : tool;
132
- const name = readString(fn.name) ?? readString(tool.name) ?? 'unknown_tool';
133
- const description = readString(fn.description) ?? 'No description';
134
- const params = isRecord(fn.parameters) ? fn.parameters : undefined;
135
- const properties = isRecord(params?.properties) ? params?.properties : undefined;
136
- const required = Array.isArray(params?.required)
137
- ? params.required.map((item) => readString(item)).filter((item) => Boolean(item))
138
- : [];
139
- const lines = [`Tool: ${name}`, `Description: ${description}`];
140
- if (properties) {
141
- const fields = [];
142
- for (const [propName, propValue] of Object.entries(properties)) {
143
- const type = isRecord(propValue) ? (readString(propValue.type) ?? 'string') : 'string';
144
- const marker = required.includes(propName) ? ' (required)' : '';
145
- fields.push(` - ${propName}: ${type}${marker}`);
146
- }
147
- if (fields.length) {
148
- lines.push('Parameters:', ...fields);
149
- }
150
- }
151
- return lines.join('\n');
152
- }
153
- function buildToolFallbackInstruction(toolsRaw, requireToolCall) {
154
- if (!Array.isArray(toolsRaw) || toolsRaw.length === 0) {
155
- return '';
156
- }
157
- const schemas = toolsRaw
158
- .filter((item) => isRecord(item))
159
- .map((item) => summarizeToolSchema(item));
160
- if (!schemas.length) {
161
- return '';
162
- }
163
- return [
164
- 'You have access to these tools:',
165
- '',
166
- schemas.join('\n\n'),
167
- '',
168
- `${TOOL_TEXT_GUIDANCE_MARKER}:`,
169
- '1) If you call a tool, your ENTIRE assistant output must be a single JSON object.',
170
- '2) Use this exact top-level shape (and key names):',
171
- '{"tool_calls":[{"name":"tool_name","input":{"arg":"value"}}]}',
172
- '3) Use only `name` + `input` for each tool call. Do NOT emit `arguments`, `parameters`, or custom wrappers.',
173
- '4) Do NOT include markdown fences, prose, progress logs, or shell transcript around the JSON.',
174
- '5) Do NOT output pseudo tool results in text (forbidden examples: {"exec_command":...}, <function_results>...</function_results>).',
175
- '6) If multiple tools are needed, append multiple entries in `tool_calls`.',
176
- requireToolCall
177
- ? '7) tool_choice is required for this turn: return at least one tool call.'
178
- : '7) If no tool is needed, plain text is allowed.',
179
- '',
180
- 'Valid example:',
181
- '{"tool_calls":[{"name":"exec_command","input":{"cmd":"pnpm -v","workdir":"/workspace"}}]}'
182
- ].join('\n');
183
- }
184
- function hasToolGuidanceMarker(messages) {
185
- for (const item of messages) {
186
- if (typeof item?.text !== 'string') {
187
- continue;
188
- }
189
- if (item.text.includes(TOOL_TEXT_GUIDANCE_MARKER)) {
190
- return true;
191
- }
192
- }
193
- return false;
194
- }
195
- function isToolChoiceRequired(root) {
196
- const toolChoice = root.tool_choice;
197
- if (typeof toolChoice === 'string') {
198
- const normalized = toolChoice.trim().toLowerCase();
199
- if (normalized === 'required') {
200
- return true;
201
- }
202
- if (normalized === 'none' || normalized === 'auto') {
203
- return false;
204
- }
205
- }
206
- if (isRecord(toolChoice)) {
207
- const type = readString(toolChoice.type)?.toLowerCase();
208
- if (type === 'function') {
209
- return true;
210
- }
211
- }
212
- return false;
213
- }
214
158
  function toPromptMessages(root, options) {
215
159
  const messagesRaw = Array.isArray(root.messages) ? root.messages : [];
216
160
  const messages = [];
@@ -235,20 +179,6 @@ function toPromptMessages(root, options) {
235
179
  const text = parts.filter(Boolean).join('\n').trim();
236
180
  messages.push({ role, text });
237
181
  }
238
- if (options.textToolFallback) {
239
- const instruction = hasToolGuidanceMarker(messages)
240
- ? ''
241
- : buildToolFallbackInstruction(root.tools, isToolChoiceRequired(root));
242
- if (instruction) {
243
- const first = messages[0];
244
- if (first && first.role === 'system') {
245
- first.text = [first.text, instruction].filter(Boolean).join('\n\n');
246
- }
247
- else {
248
- messages.unshift({ role: 'system', text: instruction });
249
- }
250
- }
251
- }
252
182
  return messages;
253
183
  }
254
184
  function mergeByRole(messages) {
@@ -337,27 +267,30 @@ export function applyDeepSeekWebRequestTransform(payload, adapterContext) {
337
267
  }
338
268
  const root = structuredClone(payload);
339
269
  const options = resolveOptions(adapterContext);
340
- const model = resolveModel(root, adapterContext);
270
+ const guidedRoot = options.toolProtocol === 'text'
271
+ ? applyToolTextRequestGuidance(root, { enabled: true })
272
+ : root;
273
+ const model = resolveModel(guidedRoot, adapterContext);
341
274
  const flags = resolveThinkingSearchFlags(model);
342
- const forceSearch = shouldForceSearch(root, adapterContext);
343
- const prompt = buildPromptFromMessages(toPromptMessages(root, options));
275
+ const forceSearch = shouldForceSearch(guidedRoot, adapterContext);
276
+ const prompt = buildPromptFromMessages(toPromptMessages(guidedRoot, options));
344
277
  const next = {
345
- ...(readString(root.chat_session_id) ? { chat_session_id: readString(root.chat_session_id) } : {}),
346
- parent_message_id: readString(root.parent_message_id) ?? null,
278
+ ...(readString(guidedRoot.chat_session_id) ? { chat_session_id: readString(guidedRoot.chat_session_id) } : {}),
279
+ parent_message_id: readString(guidedRoot.parent_message_id) ?? null,
347
280
  prompt,
348
- ref_file_ids: Array.isArray(root.ref_file_ids) ? root.ref_file_ids : [],
281
+ ref_file_ids: Array.isArray(guidedRoot.ref_file_ids) ? guidedRoot.ref_file_ids : [],
349
282
  thinking_enabled: flags.thinking,
350
283
  search_enabled: forceSearch ? true : flags.search
351
284
  };
352
- if (root.stream === true) {
285
+ if (guidedRoot.stream === true) {
353
286
  next.stream = true;
354
287
  }
355
288
  // Preserve a tiny runtime hint surface for response-side strict/fallback checks.
356
289
  next.metadata = {
357
- ...(isRecord(root.metadata) ? root.metadata : {}),
290
+ ...(isRecord(guidedRoot.metadata) ? guidedRoot.metadata : {}),
358
291
  deepseek: {
359
292
  strictToolRequired: options.strictToolRequired,
360
- textToolFallback: options.textToolFallback
293
+ ...(options.toolProtocol ? { toolProtocol: options.toolProtocol } : {})
361
294
  }
362
295
  };
363
296
  return next;
@@ -1,8 +1,11 @@
1
1
  import type { AdapterContext } from '../../hub/types/chat-envelope.js';
2
2
  import type { JsonObject } from '../../hub/types/json.js';
3
3
  import { type TextMarkupNormalizeOptions } from '../../shared/text-markup-normalizer.js';
4
+ type DeepSeekToolProtocol = 'native' | 'text';
4
5
  export interface DeepSeekWebResponseConfig {
5
6
  strictToolRequired?: boolean;
6
7
  textNormalizer?: TextMarkupNormalizeOptions;
8
+ toolProtocol?: DeepSeekToolProtocol;
7
9
  }
8
10
  export declare function applyDeepSeekWebResponseTransform(payload: JsonObject, adapterContext?: AdapterContext, config?: DeepSeekWebResponseConfig): JsonObject;
11
+ export {};
@@ -1,8 +1,10 @@
1
1
  import { validateToolCall } from '../../../tools/tool-registry.js';
2
2
  import { encoding_for_model, get_encoding } from 'tiktoken';
3
+ import { normalizeFunctionCallId } from '../../bridge-id-utils.js';
3
4
  const DEFAULT_OPTIONS = {
4
5
  strictToolRequired: true,
5
- textNormalizer: undefined
6
+ textNormalizer: undefined,
7
+ toolProtocol: undefined
6
8
  };
7
9
  const SHELL_LIKE_TOOL_NAMES = new Set(['exec_command', 'shell_command', 'shell', 'bash', 'terminal']);
8
10
  const SHELL_TOOL_NAME_ALIASES = new Map([
@@ -29,6 +31,52 @@ const readString = (value) => {
29
31
  const trimmed = value.trim();
30
32
  return trimmed.length ? trimmed : undefined;
31
33
  };
34
+ function flattenMessageContent(content) {
35
+ if (typeof content === 'string') {
36
+ return content.trim();
37
+ }
38
+ if (Array.isArray(content)) {
39
+ const parts = [];
40
+ for (const part of content) {
41
+ if (!isRecord(part)) {
42
+ continue;
43
+ }
44
+ const text = readString(part.text) ?? readString(part.content) ?? readString(part.value);
45
+ if (text) {
46
+ parts.push(text);
47
+ }
48
+ }
49
+ return parts.join('\n').trim();
50
+ }
51
+ if (isRecord(content)) {
52
+ const direct = readString(content.text) ?? readString(content.content);
53
+ if (direct) {
54
+ return direct;
55
+ }
56
+ if (Array.isArray(content.content)) {
57
+ return flattenMessageContent(content.content);
58
+ }
59
+ }
60
+ return '';
61
+ }
62
+ function extractResponsesMessageText(item) {
63
+ const parts = [];
64
+ const content = Array.isArray(item.content) ? item.content : [];
65
+ for (const part of content) {
66
+ if (!isRecord(part)) {
67
+ continue;
68
+ }
69
+ const text = readString(part.text) ?? readString(part.content) ?? readString(part.value);
70
+ if (text) {
71
+ parts.push(text);
72
+ }
73
+ }
74
+ const outputText = readString(item.output_text);
75
+ if (outputText) {
76
+ parts.push(outputText);
77
+ }
78
+ return parts.join('\n').trim();
79
+ }
32
80
  function readNumber(value) {
33
81
  if (typeof value === 'number' && Number.isFinite(value)) {
34
82
  return value;
@@ -75,6 +123,16 @@ function readOptionalBoolean(input) {
75
123
  }
76
124
  return undefined;
77
125
  }
126
+ function readToolProtocol(input) {
127
+ if (typeof input !== 'string') {
128
+ return undefined;
129
+ }
130
+ const normalized = input.trim().toLowerCase();
131
+ if (normalized === 'text' || normalized === 'native') {
132
+ return normalized;
133
+ }
134
+ return undefined;
135
+ }
78
136
  function getTokenEncoder(modelHint) {
79
137
  const normalized = typeof modelHint === 'string' ? modelHint.trim() : '';
80
138
  if (normalized) {
@@ -116,19 +174,27 @@ function resolveOptions(adapterContext, config) {
116
174
  ? context.__rt.deepseek
117
175
  : undefined;
118
176
  if (!deepseekNode) {
177
+ const toolProtocolOverride = readToolProtocol(config?.toolProtocol);
119
178
  return {
120
179
  ...DEFAULT_OPTIONS,
121
180
  strictToolRequired: readOptionalBoolean(config?.strictToolRequired) ?? DEFAULT_OPTIONS.strictToolRequired,
122
- textNormalizer: config?.textNormalizer
181
+ textNormalizer: config?.textNormalizer,
182
+ toolProtocol: toolProtocolOverride ?? DEFAULT_OPTIONS.toolProtocol
123
183
  };
124
184
  }
125
185
  const strictOverride = readOptionalBoolean(config?.strictToolRequired);
186
+ const toolProtocolOverride = readToolProtocol(config?.toolProtocol);
187
+ const legacyFallback = readOptionalBoolean(deepseekNode.textToolFallback);
188
+ const toolProtocol = toolProtocolOverride ??
189
+ readToolProtocol(deepseekNode.toolProtocol) ??
190
+ (legacyFallback !== undefined ? (legacyFallback ? 'text' : 'native') : undefined);
126
191
  return {
127
192
  strictToolRequired: strictOverride ??
128
193
  readBoolean(deepseekNode.strictToolRequired, DEFAULT_OPTIONS.strictToolRequired),
129
194
  textNormalizer: config?.textNormalizer && typeof config.textNormalizer === 'object'
130
195
  ? config.textNormalizer
131
- : undefined
196
+ : undefined,
197
+ toolProtocol
132
198
  };
133
199
  }
134
200
  function resolveCapturedRequest(adapterContext) {
@@ -686,7 +752,7 @@ function applyUsageEstimate(root, choices, adapterContext, captured) {
686
752
  function normalizeChoice(choice, options, allowedToolNames, callIdPrefix) {
687
753
  const message = isRecord(choice.message) ? choice.message : undefined;
688
754
  if (!message) {
689
- return { hasNative: false, hasFallback: false, harvestedFunctionResults: false };
755
+ return { hasNative: false, hasText: false, harvestedFunctionResults: false };
690
756
  }
691
757
  const native = normalizeMessageToolCalls(message, allowedToolNames, callIdPrefix);
692
758
  if (native) {
@@ -694,14 +760,83 @@ function normalizeChoice(choice, options, allowedToolNames, callIdPrefix) {
694
760
  if (!finish || finish === 'stop') {
695
761
  choice.finish_reason = 'tool_calls';
696
762
  }
697
- return { hasNative: true, hasFallback: false, harvestedFunctionResults: false };
763
+ return { hasNative: true, hasText: false, harvestedFunctionResults: false };
764
+ }
765
+ if (options.toolProtocol === 'text') {
766
+ const contentText = flattenMessageContent(message.content);
767
+ if (contentText && (contentText.startsWith('{') || contentText.startsWith('['))) {
768
+ message.tool_calls = contentText;
769
+ }
770
+ const text = normalizeMessageToolCalls(message, allowedToolNames, callIdPrefix);
771
+ if (text) {
772
+ const finish = readString(choice.finish_reason)?.toLowerCase();
773
+ if (!finish || finish === 'stop') {
774
+ choice.finish_reason = 'tool_calls';
775
+ }
776
+ return { hasNative: false, hasText: true, harvestedFunctionResults: false };
777
+ }
698
778
  }
699
779
  return {
700
780
  hasNative: false,
701
- hasFallback: false,
781
+ hasText: false,
702
782
  harvestedFunctionResults: false
703
783
  };
704
784
  }
785
+ function harvestResponsesTextToolCalls(root, options, allowedToolNames) {
786
+ if (options.toolProtocol !== 'text') {
787
+ return { harvested: false };
788
+ }
789
+ const output = Array.isArray(root.output) ? root.output : [];
790
+ if (!output.length) {
791
+ return { harvested: false };
792
+ }
793
+ let harvested = false;
794
+ const nextOutput = [];
795
+ let callIndex = 0;
796
+ for (const item of output) {
797
+ if (!isRecord(item)) {
798
+ nextOutput.push(item);
799
+ continue;
800
+ }
801
+ const type = readString(item.type)?.toLowerCase() ?? '';
802
+ if (type !== 'message') {
803
+ nextOutput.push(item);
804
+ continue;
805
+ }
806
+ const role = readString(item.role)?.toLowerCase() ?? 'assistant';
807
+ if (role !== 'assistant') {
808
+ nextOutput.push(item);
809
+ continue;
810
+ }
811
+ const text = extractResponsesMessageText(item);
812
+ if (!text || (!text.trim().startsWith('{') && !text.trim().startsWith('['))) {
813
+ nextOutput.push(item);
814
+ continue;
815
+ }
816
+ const normalizedCalls = normalizeToolCalls(text.trim(), allowedToolNames, 'deepseek_resp_call');
817
+ if (!normalizedCalls.length) {
818
+ nextOutput.push(item);
819
+ continue;
820
+ }
821
+ harvested = true;
822
+ for (const call of normalizedCalls) {
823
+ callIndex += 1;
824
+ const callId = readString(call.id) ?? `deepseek_resp_call_${callIndex}`;
825
+ const itemId = normalizeFunctionCallId({ callId, fallback: `fc_${callId}` });
826
+ nextOutput.push({
827
+ type: 'function_call',
828
+ id: itemId,
829
+ call_id: callId,
830
+ name: call.function.name,
831
+ arguments: call.function.arguments
832
+ });
833
+ }
834
+ }
835
+ if (harvested) {
836
+ root.output = nextOutput;
837
+ }
838
+ return { harvested };
839
+ }
705
840
  function writeCompatState(root, state, source, harvestedFunctionResults) {
706
841
  const metadata = isRecord(root.metadata) ? root.metadata : {};
707
842
  metadata.deepseek = {
@@ -724,7 +859,7 @@ export function applyDeepSeekWebResponseTransform(payload, adapterContext, confi
724
859
  const toolChoiceRequired = isToolChoiceRequired(captured);
725
860
  const choices = Array.isArray(root.choices) ? root.choices : [];
726
861
  let hasNative = false;
727
- let hasFallback = false;
862
+ let hasText = false;
728
863
  let harvestedFunctionResults = false;
729
864
  for (const [index, choice] of choices.entries()) {
730
865
  if (!isRecord(choice)) {
@@ -732,18 +867,22 @@ export function applyDeepSeekWebResponseTransform(payload, adapterContext, confi
732
867
  }
733
868
  const result = normalizeChoice(choice, options, allowedToolNames, `deepseek_call_${index + 1}`);
734
869
  hasNative = hasNative || result.hasNative;
735
- hasFallback = hasFallback || result.hasFallback;
870
+ hasText = hasText || result.hasText;
736
871
  harvestedFunctionResults = harvestedFunctionResults || result.harvestedFunctionResults;
737
872
  }
873
+ if (!choices.length) {
874
+ const harvested = harvestResponsesTextToolCalls(root, options, allowedToolNames);
875
+ hasText = hasText || harvested.harvested;
876
+ }
738
877
  const state = hasNative
739
878
  ? 'native_tool_calls'
740
- : hasFallback
879
+ : hasText
741
880
  ? 'text_tool_calls'
742
881
  : 'no_tool_calls';
743
- const source = hasNative ? 'native' : hasFallback ? 'fallback' : 'none';
882
+ const source = hasNative ? 'native' : hasText ? 'text' : 'none';
744
883
  writeCompatState(root, state, source, harvestedFunctionResults);
745
884
  applyUsageEstimate(root, choices, adapterContext, captured);
746
- if (toolChoiceRequired && options.strictToolRequired && !hasNative && !hasFallback) {
885
+ if (toolChoiceRequired && options.strictToolRequired && !hasNative && !hasText) {
747
886
  const error = new Error('DeepSeek tool_choice=required but no valid tool call was produced');
748
887
  error.code = 'DEEPSEEK_TOOL_REQUIRED_MISSING';
749
888
  error.details = {
@@ -0,0 +1,8 @@
1
+ import type { SemanticMapper } from '../../../format-adapters/index.js';
2
+ import type { AdapterContext, ChatEnvelope } from '../../../types/chat-envelope.js';
3
+ import type { FormatEnvelope } from '../../../types/format-envelope.js';
4
+ export declare function maybeAugmentApplyPatchErrorContent(content: string, toolName?: string): string;
5
+ export declare class ChatSemanticMapper implements SemanticMapper {
6
+ toChat(format: FormatEnvelope, ctx: AdapterContext): Promise<ChatEnvelope>;
7
+ fromChat(chat: ChatEnvelope, ctx: AdapterContext): Promise<FormatEnvelope>;
8
+ }