@galaxy-yearn/codex-deepseek-gateway 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/protocol.js CHANGED
@@ -2,9 +2,7 @@ import { createHash } from 'node:crypto';
2
2
 
3
3
  import { generateId, isObject, normalizeRole, parseJsonObject, safeJsonParse, toText } from './common.js';
4
4
  import {
5
- DEFAULT_MODEL_ALIASES,
6
5
  deepseekReasoningPayload,
7
- isDeprecatedModel,
8
6
  resolveModelAlias,
9
7
  } from './model-map.js';
10
8
 
@@ -51,17 +49,16 @@ const UNSUPPORTED_HOSTED_TOOL_TYPES = new Set([
51
49
  const DEEPSEEK_TOOL_INSTRUCTIONS_MARKER = 'The tools in this request are real callable functions available now.';
52
50
  const DEEPSEEK_TOOL_INSTRUCTIONS = [
53
51
  DEEPSEEK_TOOL_INSTRUCTIONS_MARKER,
54
- 'When a tool is needed, respond with Chat Completions tool_calls and JSON arguments matching the schema.',
55
- 'Do not write pseudo XML, DSML, or JSON tool calls in assistant text.',
56
- 'Do not claim a listed function is unavailable because its name is unfamiliar.',
52
+ 'When needed, use Chat Completions tool_calls with JSON arguments that match the schema.',
53
+ 'Never write tool calls in assistant text, including as XML, DSML, or JSON, or claim a listed function is unavailable merely because its name is unfamiliar.',
57
54
  ].join(' ');
58
55
  export const INTERNAL_COMMENTARY_TOOL = 'commentary';
59
- const DEEPSEEK_COMMENTARY_INSTRUCTIONS = 'The user cannot see your thinking; commentary is the only progress they see between tool batches. Include one commentary call, first, in every tool_calls batch, with one or two sentences on what you are doing. Never call it alone; never put the final answer in it deliver the final answer as plain assistant text with no tool calls.';
56
+ const DEEPSEEK_COMMENTARY_INSTRUCTIONS = 'The user cannot see your thinking; commentary is the progress they see during tool work. In every tool-calling reply, include one commentary call first in the tool_calls array with a one- or two-sentence update. Never call it alone or use it for the final answer.';
60
57
  const INTERNAL_COMMENTARY_TOOL_DEFINITION = {
61
58
  type: 'function',
62
59
  function: {
63
60
  name: INTERNAL_COMMENTARY_TOOL,
64
- description: "Post a short progress update the user sees immediately. The user cannot see your thinking; this is the only progress they see between tool batches. Include one commentary call, first, in every tool_calls batch, e.g. commentary(text: 'Config loader read; now checking how sessions persist.'). One or two sentences. Never call it alone; never put the final answer in it.",
61
+ description: 'Post the user-visible progress update. Include this function first in every tool_calls array with other tool calls. One or two sentences; never call it alone or use it for the final answer.',
65
62
  parameters: {
66
63
  type: 'object',
67
64
  properties: {
@@ -77,10 +74,9 @@ const INTERNAL_COMMENTARY_TOOL_DEFINITION = {
77
74
  };
78
75
  const CHAT_FUNCTION_NAME_MAX_CHARS = 64;
79
76
  const TOOL_NAME_HASH_CHARS = 8;
80
- const DEEPSEEK_TOOL_DESCRIPTION_CHARS = 900;
81
- const DEEPSEEK_SCHEMA_DESCRIPTION_CHARS = 480;
82
- const CODEX_REASONING_EFFORTS = ['low', 'medium', 'high', 'xhigh'];
83
- const CODEX_SPAWN_AGENT_TOOL_NAME = 'multi_agent_v1__spawn_agent';
77
+ const DEEPSEEK_TOOL_DESCRIPTION_SOFT_CHARS = 900;
78
+ const DEEPSEEK_SCHEMA_DESCRIPTION_SOFT_CHARS = 480;
79
+ const DEEPSEEK_MAX_FUNCTION_TOOLS = 128;
84
80
  const CODEX_TOOL_SEARCH_TOOL_NAME = 'tool_search';
85
81
 
86
82
  function jsonString(value) {
@@ -172,6 +168,16 @@ function compactText(value) {
172
168
  return String(value ?? '').replace(/\s+/g, ' ').trim();
173
169
  }
174
170
 
171
+ function compactStructuredText(value, softChars) {
172
+ const lines = String(value ?? '').replace(/\r\n?/g, '\n').split('\n');
173
+ while (lines.length && !lines[0].trim()) lines.shift();
174
+ while (lines.length && !lines.at(-1).trim()) lines.pop();
175
+ const structured = lines.join('\n').replace(/\n(?:[ \t]*\n){2,}/g, '\n\n');
176
+ if (!structured) return '';
177
+ const compact = compactText(structured);
178
+ return compact.length <= softChars ? compact : structured;
179
+ }
180
+
175
181
  function shortenText(value, maxChars) {
176
182
  const text = compactText(value);
177
183
  if (!text || text.length <= maxChars) return text;
@@ -631,8 +637,6 @@ function extractMessagesFromResponsesInput(input) {
631
637
  }
632
638
 
633
639
  const CUSTOM_TOOL_INPUT_HINT = 'Pass the complete raw tool input as the "input" string argument. Do not wrap it in extra JSON, quotes, or markdown fences.';
634
- const CUSTOM_TOOL_GRAMMAR_CHARS = 1600;
635
- const CUSTOM_TOOL_DESCRIPTION_CHARS = 3600;
636
640
 
637
641
  function truncateRawText(value, maxChars) {
638
642
  const text = String(value ?? '');
@@ -658,25 +662,25 @@ function customToolShim(tool) {
658
662
  const format = isObject(tool.format) ? tool.format : {};
659
663
  const baseName = toolBaseName(tool, 'custom_tool');
660
664
  const chunks = [
661
- compactText(tool.description) || `Freeform tool ${baseName}.`,
665
+ compactStructuredText(tool.description, DEEPSEEK_TOOL_DESCRIPTION_SOFT_CHARS) || `Freeform tool ${baseName}.`,
662
666
  CUSTOM_TOOL_INPUT_HINT,
663
667
  ];
664
668
  if (format.syntax) chunks.push(`Input syntax: ${format.syntax}.`);
665
669
  if (typeof format.definition === 'string' && format.definition.trim()) {
666
- chunks.push(`Input grammar:\n${truncateRawText(format.definition.trim(), CUSTOM_TOOL_GRAMMAR_CHARS)}`);
670
+ chunks.push(`Input grammar:\n${format.definition.trim()}`);
667
671
  }
668
672
  const parameters = customToolShimParameters();
669
673
  const sourceSchema = isObject(tool.input_schema) ? tool.input_schema : isObject(tool.parameters) ? tool.parameters : null;
670
674
  const sourceInputDescription = sourceSchema?.properties?.input?.description;
671
675
  if (typeof sourceInputDescription === 'string' && sourceInputDescription) {
672
- parameters.properties.input.description = sourceInputDescription;
676
+ parameters.properties.input.description = compactStructuredText(sourceInputDescription, DEEPSEEK_SCHEMA_DESCRIPTION_SOFT_CHARS);
673
677
  }
674
678
  return {
675
679
  type: 'function',
676
680
  gateway_custom_tool: true,
677
681
  function: {
678
682
  name: encodeToolName(toolNamespace(tool), baseName),
679
- description: truncateRawText(chunks.join('\n'), CUSTOM_TOOL_DESCRIPTION_CHARS),
683
+ description: chunks.join('\n'),
680
684
  parameters,
681
685
  },
682
686
  };
@@ -1127,7 +1131,7 @@ function simplifyJsonSchemaDescriptions(schema) {
1127
1131
  const next = {};
1128
1132
  for (const [key, value] of Object.entries(schema)) {
1129
1133
  if (key === 'description' && typeof value === 'string') {
1130
- const description = shortenText(value, DEEPSEEK_SCHEMA_DESCRIPTION_CHARS);
1134
+ const description = compactStructuredText(value, DEEPSEEK_SCHEMA_DESCRIPTION_SOFT_CHARS);
1131
1135
  if (description) next.description = description;
1132
1136
  continue;
1133
1137
  }
@@ -1150,83 +1154,15 @@ function defaultDeepSeekToolDescription(name) {
1150
1154
  return compactText(`Call ${readable || name || 'this function'} when needed.`);
1151
1155
  }
1152
1156
 
1153
- function deepSeekGatewayModelAliases(config = {}) {
1154
- const aliases = isObject(config.modelAliases) && Object.keys(config.modelAliases).length
1155
- ? config.modelAliases
1156
- : DEFAULT_MODEL_ALIASES;
1157
- const spawnConfig = { ...config, upstreamProvider: 'deepseek', modelAliases: aliases };
1158
- const names = [];
1159
- for (const name of Object.keys(aliases)) {
1160
- if (!name || isDeprecatedModel(name)) continue;
1161
- const alias = resolveModelAlias(name, spawnConfig);
1162
- if (isDeprecatedModel(alias.upstreamModel)) continue;
1163
- names.push(name);
1164
- }
1165
- return [...new Set(names)];
1166
- }
1167
-
1168
- function enumStringProperty(property, values, description) {
1169
- const source = isObject(property) ? property : {};
1170
- const next = { ...source };
1171
- delete next.const;
1172
- delete next.oneOf;
1173
- delete next.anyOf;
1174
- delete next.allOf;
1175
- delete next.examples;
1176
- if (next.default !== undefined && !values.includes(next.default)) delete next.default;
1177
- next.type = 'string';
1178
- next.enum = values;
1179
- next.description = description;
1180
- return next;
1181
- }
1182
-
1183
- function deepSeekSpawnAgentParameters(parameters, config = {}) {
1184
- const next = normalizeJsonSchemaObject(parameters);
1185
- const properties = isObject(next.properties) ? { ...next.properties } : {};
1186
- const modelAliases = deepSeekGatewayModelAliases(config);
1187
- properties.model = enumStringProperty(
1188
- properties.model,
1189
- modelAliases,
1190
- 'Model for the sub-agent. Omit to inherit.',
1191
- );
1192
- properties.reasoning_effort = enumStringProperty(
1193
- properties.reasoning_effort,
1194
- CODEX_REASONING_EFFORTS,
1195
- 'Codex reasoning effort for the sub-agent. Omit to inherit.',
1196
- );
1197
- return {
1198
- ...next,
1199
- properties,
1200
- };
1201
- }
1202
-
1203
- function deepSeekSpawnAgentDescription() {
1204
- const description = [
1205
- 'Spawn a Codex-native sub-agent.',
1206
- 'Omit model/reasoning_effort to inherit.',
1207
- 'Valid values are enforced by the schema.',
1208
- ].filter(Boolean).join(' ');
1209
- return shortenText(description, DEEPSEEK_TOOL_DESCRIPTION_CHARS);
1210
- }
1211
-
1212
- function isCodexSpawnAgentTool(name) {
1213
- return name === CODEX_SPAWN_AGENT_TOOL_NAME;
1214
- }
1215
-
1216
- function simplifyToolForDeepSeek(tool, config = {}) {
1157
+ function simplifyToolForDeepSeek(tool) {
1217
1158
  if (tool?.type !== 'function' || !isObject(tool.function)) return tool;
1218
1159
  if (tool.gateway_custom_tool) return tool;
1219
1160
  const fn = tool.function;
1220
- let parameters = simplifyJsonSchemaDescriptions(fn.parameters);
1221
- if (isCodexSpawnAgentTool(fn.name)) {
1222
- parameters = deepSeekSpawnAgentParameters(parameters, config);
1223
- }
1161
+ const parameters = simplifyJsonSchemaDescriptions(fn.parameters);
1224
1162
  const requiredText = requiredParametersText(parameters);
1225
- const descriptionBudget = Math.max(32, DEEPSEEK_TOOL_DESCRIPTION_CHARS - requiredText.length);
1226
- const baseDescription = isCodexSpawnAgentTool(fn.name)
1227
- ? deepSeekSpawnAgentDescription()
1228
- : shortenText(fn.description, descriptionBudget) || defaultDeepSeekToolDescription(fn.name);
1229
- const description = shortenText(`${baseDescription}${requiredText}`, DEEPSEEK_TOOL_DESCRIPTION_CHARS);
1163
+ const baseDescription = compactStructuredText(fn.description, DEEPSEEK_TOOL_DESCRIPTION_SOFT_CHARS)
1164
+ || defaultDeepSeekToolDescription(fn.name);
1165
+ const description = `${baseDescription}${requiredText}`;
1230
1166
  return {
1231
1167
  ...tool,
1232
1168
  function: omitUndefined({
@@ -1266,7 +1202,7 @@ function adaptToolsForProvider(tools, provider, config = {}) {
1266
1202
  if (provider !== 'deepseek' || !Array.isArray(tools)) return tools;
1267
1203
  const keepStrict = isDeepSeekBetaBaseUrl(config.upstreamBaseUrl);
1268
1204
  return tools.map((tool) => {
1269
- const simplified = simplifyToolForDeepSeek(tool, config);
1205
+ const simplified = simplifyToolForDeepSeek(tool);
1270
1206
  if (!keepStrict && simplified?.type === 'function' && isObject(simplified.function) && simplified.function.strict !== undefined) {
1271
1207
  const { strict, ...fn } = simplified.function;
1272
1208
  return { ...simplified, function: fn };
@@ -1275,6 +1211,17 @@ function adaptToolsForProvider(tools, provider, config = {}) {
1275
1211
  });
1276
1212
  }
1277
1213
 
1214
+ function assertDeepSeekToolCapacity(tools) {
1215
+ const count = Array.isArray(tools)
1216
+ ? tools.filter((tool) => tool?.type === 'function' && isObject(tool.function)).length
1217
+ : 0;
1218
+ if (count <= DEEPSEEK_MAX_FUNCTION_TOOLS) return;
1219
+ const error = new RangeError(`DeepSeek supports at most ${DEEPSEEK_MAX_FUNCTION_TOOLS} function tools; received ${count} after gateway adaptation.`);
1220
+ error.statusCode = 400;
1221
+ error.code = 'too_many_tools';
1222
+ throw error;
1223
+ }
1224
+
1278
1225
  function normalizeToolCallArguments(name, argumentsText, toolSchema) {
1279
1226
  if (!argumentsText) return '';
1280
1227
  if (!isObject(toolSchema) || !isObject(toolSchema.properties)) return argumentsText;
@@ -1297,54 +1244,16 @@ function normalizeToolCallArguments(name, argumentsText, toolSchema) {
1297
1244
  }
1298
1245
  }
1299
1246
 
1300
- function normalizeReasoningEffortName(value) {
1301
- return value == null ? '' : String(value).toLowerCase().replaceAll('_', '-');
1302
- }
1303
-
1304
- function normalizeCodexSpawnAgentArguments(encodedName, argumentsText, config = {}) {
1305
- if (!isCodexSpawnAgentTool(encodedName) || !argumentsText) return argumentsText;
1306
- let parsed;
1307
- try {
1308
- parsed = JSON.parse(argumentsText);
1309
- } catch {
1310
- return argumentsText;
1311
- }
1312
- if (!isObject(parsed)) return argumentsText;
1313
- const next = { ...parsed };
1314
- let changed = false;
1315
- const allowedModels = deepSeekGatewayModelAliases(config);
1316
-
1317
- if (typeof next.model === 'string') {
1318
- const requestedModel = next.model;
1319
- if (allowedModels.length && !allowedModels.includes(requestedModel)) {
1320
- delete next.model;
1321
- changed = true;
1322
- }
1323
- }
1324
-
1325
- if (typeof next.reasoning_effort === 'string') {
1326
- const requestedEffort = normalizeReasoningEffortName(next.reasoning_effort);
1327
- if (!CODEX_REASONING_EFFORTS.includes(requestedEffort)) {
1328
- delete next.reasoning_effort;
1329
- changed = true;
1330
- return JSON.stringify(next);
1331
- }
1332
- }
1333
-
1334
- return changed ? JSON.stringify(next) : argumentsText;
1335
- }
1336
-
1337
- function normalizeFunctionCallItemArguments(item, toolSchemas, config = {}) {
1247
+ function normalizeFunctionCallItemArguments(item, toolSchemas) {
1338
1248
  if (!item || item.type !== 'function_call') return;
1339
1249
  const encodedName = encodeToolName(item.namespace, item.name);
1340
1250
  item.arguments = normalizeToolCallArguments(encodedName, item.arguments, toolSchemas?.get(encodedName));
1341
- item.arguments = normalizeCodexSpawnAgentArguments(encodedName, item.arguments, config);
1342
1251
  }
1343
1252
 
1344
- function functionCallItemNeedsArgumentNormalization(item, toolSchemas, config = {}) {
1253
+ function functionCallItemNeedsArgumentNormalization(item, toolSchemas) {
1345
1254
  if (!item || item.type !== 'function_call') return false;
1346
1255
  const encodedName = encodeToolName(item.namespace, item.name);
1347
- return Boolean(toolSchemas?.has(encodedName) || isCodexSpawnAgentTool(encodedName));
1256
+ return Boolean(toolSchemas?.has(encodedName));
1348
1257
  }
1349
1258
 
1350
1259
  function hasToolCallFunctionName(toolCall) {
@@ -1467,7 +1376,19 @@ function sanitizeMessagesForChatCompletion(messages, provider = 'generic') {
1467
1376
  return result;
1468
1377
  }
1469
1378
 
1470
- const DEEPSEEK_DEFAULT_MAX_TOKENS = 65536;
1379
+ const DEEPSEEK_CONTEXT_WINDOW = 1000000;
1380
+ const DEEPSEEK_MAX_OUTPUT_TOKENS = 384000;
1381
+ const DEEPSEEK_AUTO_COMPACT_TOKEN_LIMIT = 900000;
1382
+
1383
+ function jointOutputTokenBudget(contextWindow, maxOutputTokens, inputTokenLimit) {
1384
+ return Math.max(1, Math.min(maxOutputTokens, contextWindow - inputTokenLimit));
1385
+ }
1386
+
1387
+ const DEEPSEEK_DEFAULT_MAX_TOKENS = jointOutputTokenBudget(
1388
+ DEEPSEEK_CONTEXT_WINDOW,
1389
+ DEEPSEEK_MAX_OUTPUT_TOKENS,
1390
+ DEEPSEEK_AUTO_COMPACT_TOKEN_LIMIT,
1391
+ );
1471
1392
 
1472
1393
  function deepseekDefaultMaxTokens(config = {}) {
1473
1394
  const value = Number(config.upstreamMaxTokens);
@@ -1475,7 +1396,7 @@ function deepseekDefaultMaxTokens(config = {}) {
1475
1396
  return DEEPSEEK_DEFAULT_MAX_TOKENS;
1476
1397
  }
1477
1398
 
1478
- const DEEPSEEK_JSON_SCHEMA_INSTRUCTIONS_MARKER = 'Respond with a single valid json object';
1399
+ const DEEPSEEK_JSON_SCHEMA_INSTRUCTIONS_MARKER = 'Return only one valid JSON object';
1479
1400
 
1480
1401
  function jsonSchemaFromResponseFormat(responseFormat) {
1481
1402
  if (!isObject(responseFormat)) return null;
@@ -1490,10 +1411,10 @@ function addDeepSeekJsonSchemaInstructions(messages, responseFormat) {
1490
1411
  const schema = jsonSchemaFromResponseFormat(responseFormat);
1491
1412
  const schemaText = schema ? truncateRawText(JSON.stringify(schema), 6000) : '';
1492
1413
  const instructions = [
1493
- `${DEEPSEEK_JSON_SCHEMA_INSTRUCTIONS_MARKER} and nothing else: no prose, no markdown fences.`,
1414
+ `${DEEPSEEK_JSON_SCHEMA_INSTRUCTIONS_MARKER}, with no prose or Markdown.`,
1494
1415
  schemaText
1495
- ? `The json object must conform to this JSON Schema:\n${schemaText}`
1496
- : 'Use the json object shape requested in the conversation.',
1416
+ ? `It must match this JSON Schema:\n${schemaText}`
1417
+ : 'Use the object shape requested in the conversation.',
1497
1418
  ].join('\n');
1498
1419
  if (!messages.length || messages[0]?.role !== 'system') {
1499
1420
  return [{ role: 'system', content: instructions }, ...messages];
@@ -1656,12 +1577,14 @@ function normalizeInstructions(instructions) {
1656
1577
  return toText(instructions);
1657
1578
  }
1658
1579
 
1659
- export function normalizeResponsesRequest(request) {
1580
+ export function normalizeResponsesRequest(request, { restoreDiscoveredTools = true } = {}) {
1660
1581
  const messages = extractMessagesFromResponsesInput(request.input ?? request);
1661
1582
  const model = request.model || request.model_id || request.upstream_model || '';
1662
1583
  const instructions = normalizeInstructions(request.instructions);
1663
1584
  const responseFormat = request.response_format || request.text?.format;
1664
- const tools = mergeToolsWithToolSearchOutput(request.tools, request.input ?? request);
1585
+ const tools = restoreDiscoveredTools
1586
+ ? mergeToolsWithToolSearchOutput(request.tools, request.input ?? request)
1587
+ : request.tools;
1665
1588
  return {
1666
1589
  model,
1667
1590
  messages,
@@ -1683,10 +1606,8 @@ export function normalizeResponsesRequest(request) {
1683
1606
  text: request.text,
1684
1607
  truncation: request.truncation,
1685
1608
  user: request.user,
1686
- previous_response_id: request.previous_response_id,
1687
1609
  store: request.store,
1688
1610
  include: request.include,
1689
- conversation: request.conversation,
1690
1611
  };
1691
1612
  }
1692
1613
 
@@ -1848,7 +1769,10 @@ export function toProviderChatCompletionsRequest(chatRequest, config = {}) {
1848
1769
  if (provider === 'deepseek') {
1849
1770
  delete request.reasoning_effort;
1850
1771
  delete request.parallel_tool_calls;
1772
+ delete request.frequency_penalty;
1773
+ delete request.presence_penalty;
1851
1774
  request.tools = addInternalCommentaryTool(adaptToolsForProvider(request.tools, provider, config), chatRequest.tool_choice);
1775
+ assertDeepSeekToolCapacity(request.tools);
1852
1776
  request.messages = addDeepSeekToolInstructions(request.messages, request.tools);
1853
1777
  if (request.user !== undefined) {
1854
1778
  request.user_id = request.user;
@@ -1899,6 +1823,7 @@ function createBaseResponse({
1899
1823
  normalized,
1900
1824
  completedAt = null,
1901
1825
  incompleteReason = null,
1826
+ error = null,
1902
1827
  }) {
1903
1828
  return {
1904
1829
  id,
@@ -1907,7 +1832,7 @@ function createBaseResponse({
1907
1832
  completed_at: completedAt == null ? null : Math.floor(completedAt),
1908
1833
  status,
1909
1834
  background: false,
1910
- error: null,
1835
+ error,
1911
1836
  incomplete_details: incompleteReason ? { reason: incompleteReason } : null,
1912
1837
  instructions: normalized?.instructions || null,
1913
1838
  max_output_tokens: normalized?.max_tokens ?? null,
@@ -1930,6 +1855,37 @@ function createBaseResponse({
1930
1855
  };
1931
1856
  }
1932
1857
 
1858
+ function deepSeekFinishReasonOutcome(finishReason) {
1859
+ if (finishReason === 'stop' || finishReason === 'tool_calls') {
1860
+ return { status: 'completed', incompleteReason: null, error: null };
1861
+ }
1862
+ if (finishReason === 'length') {
1863
+ return { status: 'incomplete', incompleteReason: 'max_output_tokens', error: null };
1864
+ }
1865
+ if (finishReason === 'content_filter') {
1866
+ return { status: 'incomplete', incompleteReason: 'content_filter', error: null };
1867
+ }
1868
+ if (finishReason === 'insufficient_system_resource') {
1869
+ return {
1870
+ status: 'failed',
1871
+ incompleteReason: null,
1872
+ error: {
1873
+ code: 'server_is_overloaded',
1874
+ message: 'DeepSeek ended the response because upstream resources were insufficient.',
1875
+ },
1876
+ };
1877
+ }
1878
+ const label = finishReason == null || finishReason === '' ? 'missing' : String(finishReason);
1879
+ return {
1880
+ status: 'failed',
1881
+ incompleteReason: null,
1882
+ error: {
1883
+ code: 'upstream_error',
1884
+ message: `DeepSeek returned an unsupported finish_reason: ${label}.`,
1885
+ },
1886
+ };
1887
+ }
1888
+
1933
1889
  function normalizeResponsesUsage(usage) {
1934
1890
  if (!isObject(usage)) return null;
1935
1891
  const inputTokens = usage.input_tokens ?? usage.prompt_tokens ?? 0;
@@ -1991,7 +1947,7 @@ export function createResponseEnvelope({
1991
1947
  const PARALLEL_TOOL_WRAPPER_NAMES = new Set(['multi_tool_use.parallel', 'multi_tool_use_parallel']);
1992
1948
  const EMITTED_TOOL_NAME_PREFIX = 'functions.';
1993
1949
 
1994
- export function isParallelToolWrapperName(name) {
1950
+ function isParallelToolWrapperName(name) {
1995
1951
  const raw = String(name || '').toLowerCase();
1996
1952
  const stripped = raw.startsWith(EMITTED_TOOL_NAME_PREFIX) ? raw.slice(EMITTED_TOOL_NAME_PREFIX.length) : raw;
1997
1953
  return PARALLEL_TOOL_WRAPPER_NAMES.has(stripped);
@@ -2001,7 +1957,7 @@ function toolNameMatchKey(name) {
2001
1957
  return String(name || '').toLowerCase().replace(/\./g, '__');
2002
1958
  }
2003
1959
 
2004
- export function resolveEmittedToolName(name, knownNames) {
1960
+ function resolveEmittedToolName(name, knownNames) {
2005
1961
  const raw = String(name || '');
2006
1962
  if (!raw || !knownNames) return name;
2007
1963
  const known = knownNames instanceof Set ? knownNames : new Set(knownNames);
@@ -2074,7 +2030,7 @@ function parallelToolUsesFromArguments(argumentsText) {
2074
2030
  return calls;
2075
2031
  }
2076
2032
 
2077
- export function expandParallelToolCalls(toolCalls) {
2033
+ function expandParallelToolCalls(toolCalls) {
2078
2034
  if (!Array.isArray(toolCalls)) return toolCalls;
2079
2035
  if (!toolCalls.some((toolCall) => isParallelToolWrapperName(toolCall?.function?.name))) return toolCalls;
2080
2036
  const expanded = [];
@@ -2120,6 +2076,7 @@ export function convertChatCompletionToResponses({ completion: rawCompletion, mo
2120
2076
  );
2121
2077
  const createdAt = completion.created || Date.now() / 1000;
2122
2078
  const choice = Array.isArray(completion.choices) ? completion.choices[0] : null;
2079
+ const outcome = deepSeekFinishReasonOutcome(choice?.finish_reason);
2123
2080
  const message = choice?.message || {};
2124
2081
  const content = chatContentToResponseOutputParts(message.content);
2125
2082
  const messageHasToolCalls = hasChatToolCalls(message);
@@ -2133,7 +2090,7 @@ export function convertChatCompletionToResponses({ completion: rawCompletion, mo
2133
2090
  id: generateId('msg'),
2134
2091
  role: 'assistant',
2135
2092
  content,
2136
- status: 'completed',
2093
+ status: outcome.status,
2137
2094
  phase: message.phase || (messageHasToolCalls ? 'commentary' : 'final_answer'),
2138
2095
  });
2139
2096
  }
@@ -2156,6 +2113,7 @@ export function convertChatCompletionToResponses({ completion: rawCompletion, mo
2156
2113
  continue;
2157
2114
  }
2158
2115
  const item = responseItemFromChatToolCall(toolCall, toolNames, customToolNames);
2116
+ item.status = outcome.status;
2159
2117
  normalizeFunctionCallItemArguments(item, toolSchemas, config);
2160
2118
  output.push(item);
2161
2119
  }
@@ -2170,7 +2128,7 @@ export function convertChatCompletionToResponses({ completion: rawCompletion, mo
2170
2128
  content: [],
2171
2129
  reasoning_content: reasoningText,
2172
2130
  encrypted_content: encodeGatewayReasoning(reasoningText),
2173
- status: 'completed',
2131
+ status: outcome.status,
2174
2132
  });
2175
2133
  }
2176
2134
 
@@ -2178,13 +2136,14 @@ export function convertChatCompletionToResponses({ completion: rawCompletion, mo
2178
2136
  id: responseId,
2179
2137
  model,
2180
2138
  createdAt,
2181
- status: choice?.finish_reason === 'length' ? 'incomplete' : 'completed',
2139
+ status: outcome.status,
2182
2140
  output,
2183
2141
  previousResponseId: previousResponseId ?? null,
2184
2142
  usage: normalizeResponsesUsage(completion.usage),
2185
2143
  normalized,
2186
2144
  completedAt: Date.now() / 1000,
2187
- incompleteReason: choice?.finish_reason === 'length' ? 'max_output_tokens' : null,
2145
+ incompleteReason: outcome.incompleteReason,
2146
+ error: outcome.error,
2188
2147
  });
2189
2148
  }
2190
2149
 
@@ -2224,6 +2183,7 @@ export class ResponsesStreamMapper {
2224
2183
  this.pendingUsage = null;
2225
2184
  this.completedAt = null;
2226
2185
  this.finalized = false;
2186
+ this.terminalStatus = null;
2227
2187
  this.messageItemAdded = false;
2228
2188
  this.messageContentAdded = false;
2229
2189
  this.messageItemClosed = false;
@@ -2253,6 +2213,7 @@ export class ResponsesStreamMapper {
2253
2213
  }
2254
2214
 
2255
2215
  response(status = 'in_progress') {
2216
+ const outcome = deepSeekFinishReasonOutcome(this.finishReason);
2256
2217
  return createBaseResponse({
2257
2218
  id: this.responseId,
2258
2219
  model: this.model,
@@ -2263,7 +2224,8 @@ export class ResponsesStreamMapper {
2263
2224
  usage: this.usage,
2264
2225
  normalized: this.normalized,
2265
2226
  completedAt: this.completedAt,
2266
- incompleteReason: this.finishReason === 'length' ? 'max_output_tokens' : null,
2227
+ incompleteReason: status === 'in_progress' ? null : outcome.incompleteReason,
2228
+ error: status === 'failed' ? outcome.error : null,
2267
2229
  });
2268
2230
  }
2269
2231
 
@@ -2279,7 +2241,13 @@ export class ResponsesStreamMapper {
2279
2241
  return {
2280
2242
  type: 'response.in_progress',
2281
2243
  sequence_number: this.nextSequence(),
2282
- response: this.response('in_progress'),
2244
+ response: createBaseResponse({
2245
+ id: this.responseId,
2246
+ model: this.model,
2247
+ createdAt: this.createdAt,
2248
+ status: 'in_progress',
2249
+ previousResponseId: this.previousResponseId,
2250
+ }),
2283
2251
  };
2284
2252
  }
2285
2253
 
@@ -2761,7 +2729,9 @@ export class ResponsesStreamMapper {
2761
2729
  if (this.finalized) return [];
2762
2730
  this.finalized = true;
2763
2731
  this.expandParallelToolItems();
2764
- const status = finishReason === 'length' ? 'incomplete' : 'completed';
2732
+ const outcome = deepSeekFinishReasonOutcome(finishReason);
2733
+ const status = outcome.status;
2734
+ this.terminalStatus = status;
2765
2735
  this.finishReason = finishReason;
2766
2736
  this.usage = normalizeResponsesUsage(usage);
2767
2737
  this.completedAt = Date.now() / 1000;
@@ -2876,10 +2846,11 @@ export class ResponsesStreamMapper {
2876
2846
  usage: normalizeResponsesUsage(usage),
2877
2847
  normalized: this.normalized,
2878
2848
  completedAt: Date.now() / 1000,
2879
- incompleteReason: finishReason === 'length' ? 'max_output_tokens' : null,
2849
+ incompleteReason: outcome.incompleteReason,
2850
+ error: outcome.error,
2880
2851
  });
2881
2852
  events.push({
2882
- type: status === 'incomplete' ? 'response.incomplete' : 'response.completed',
2853
+ type: status === 'completed' ? 'response.completed' : status === 'incomplete' ? 'response.incomplete' : 'response.failed',
2883
2854
  sequence_number: this.nextSequence(),
2884
2855
  response,
2885
2856
  });
@@ -3060,6 +3031,7 @@ export class ResponsesStreamMapper {
3060
3031
  streamFailed(message) {
3061
3032
  if (this.finalized) return [];
3062
3033
  this.finalized = true;
3034
+ this.terminalStatus = 'failed';
3063
3035
  this.completedAt = Date.now() / 1000;
3064
3036
  const response = this.response('failed');
3065
3037
  response.error = { code: 'upstream_error', message: String(message || 'upstream stream failed') };