@jsonstudio/llms 0.6.1749 → 0.6.1890
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/conversion/compat/actions/deepseek-web-request.d.ts +3 -0
- package/dist/conversion/compat/actions/deepseek-web-request.js +350 -0
- package/dist/conversion/compat/actions/deepseek-web-response.d.ts +3 -0
- package/dist/conversion/compat/actions/deepseek-web-response.js +886 -0
- package/dist/conversion/compat/actions/gemini-cli-request.js +3 -1
- package/dist/conversion/compat/profiles/chat-deepseek-web.json +18 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +166 -2
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +169 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +6 -0
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +12 -0
- package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +1 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +4 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +365 -144
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +9 -0
- package/dist/conversion/hub/policy/policy-engine.d.ts +2 -0
- package/dist/conversion/hub/policy/policy-engine.js +8 -0
- package/dist/conversion/hub/process/chat-process.js +466 -16
- package/dist/conversion/hub/response/provider-response.js +0 -35
- package/dist/conversion/responses/responses-openai-bridge.d.ts +2 -0
- package/dist/conversion/responses/responses-openai-bridge.js +166 -8
- package/dist/conversion/shared/anthropic-message-utils.js +10 -1
- package/dist/conversion/shared/protocol-field-allowlists.d.ts +2 -2
- package/dist/conversion/shared/protocol-field-allowlists.js +4 -0
- package/dist/conversion/shared/tool-governor.js +102 -0
- package/dist/guidance/index.js +17 -0
- package/dist/router/virtual-router/bootstrap.js +46 -1
- package/dist/router/virtual-router/classifier.js +59 -4
- package/dist/router/virtual-router/engine/health/index.js +6 -6
- package/dist/router/virtual-router/engine/routing-state/store.js +16 -3
- package/dist/router/virtual-router/engine-logging.js +62 -24
- package/dist/router/virtual-router/engine-selection/route-utils.js +20 -20
- package/dist/router/virtual-router/engine-selection/tier-selection.js +2 -2
- package/dist/router/virtual-router/engine.d.ts +3 -1
- package/dist/router/virtual-router/engine.js +325 -38
- package/dist/router/virtual-router/features.js +2 -1
- package/dist/router/virtual-router/pre-command-file-resolver.d.ts +2 -0
- package/dist/router/virtual-router/pre-command-file-resolver.js +90 -0
- package/dist/router/virtual-router/provider-registry.js +3 -1
- package/dist/router/virtual-router/routing-instructions.d.ts +11 -1
- package/dist/router/virtual-router/routing-instructions.js +101 -183
- package/dist/router/virtual-router/routing-pre-command-actions.d.ts +3 -0
- package/dist/router/virtual-router/routing-pre-command-actions.js +26 -0
- package/dist/router/virtual-router/routing-pre-command-parser.d.ts +2 -0
- package/dist/router/virtual-router/routing-pre-command-parser.js +85 -0
- package/dist/router/virtual-router/routing-pre-command-state-codec.d.ts +3 -0
- package/dist/router/virtual-router/routing-pre-command-state-codec.js +24 -0
- package/dist/router/virtual-router/routing-stop-message-actions.d.ts +2 -0
- package/dist/router/virtual-router/routing-stop-message-actions.js +96 -0
- package/dist/router/virtual-router/routing-stop-message-parser.d.ts +3 -0
- package/dist/router/virtual-router/routing-stop-message-parser.js +142 -0
- package/dist/router/virtual-router/routing-stop-message-state-codec.d.ts +4 -0
- package/dist/router/virtual-router/routing-stop-message-state-codec.js +85 -0
- package/dist/router/virtual-router/sticky-session-store.js +206 -57
- package/dist/router/virtual-router/stop-message-stage-template-files.d.ts +12 -0
- package/dist/router/virtual-router/stop-message-stage-template-files.js +67 -0
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +1 -1
- package/dist/router/virtual-router/stop-message-state-sync.js +1 -0
- package/dist/router/virtual-router/token-file-scanner.d.ts +9 -0
- package/dist/router/virtual-router/token-file-scanner.js +64 -3
- package/dist/router/virtual-router/tool-signals.d.ts +5 -0
- package/dist/router/virtual-router/tool-signals.js +42 -3
- package/dist/router/virtual-router/types.d.ts +15 -1
- package/dist/router/virtual-router/types.js +1 -0
- package/dist/servertool/clock/config.d.ts +1 -1
- package/dist/servertool/clock/config.js +27 -4
- package/dist/servertool/clock/state.js +41 -2
- package/dist/servertool/clock/task-store.d.ts +2 -2
- package/dist/servertool/clock/task-store.js +1 -1
- package/dist/servertool/clock/tasks.d.ts +3 -1
- package/dist/servertool/clock/tasks.js +209 -18
- package/dist/servertool/clock/types.d.ts +17 -0
- package/dist/servertool/continue-execution/log.d.ts +3 -0
- package/dist/servertool/continue-execution/log.js +13 -0
- package/dist/servertool/engine.js +414 -68
- package/dist/servertool/handlers/antigravity-thought-signature-bootstrap.js +6 -6
- package/dist/servertool/handlers/clock-auto.js +54 -71
- package/dist/servertool/handlers/clock.js +121 -6
- package/dist/servertool/handlers/continue-execution.d.ts +1 -0
- package/dist/servertool/handlers/continue-execution.js +91 -0
- package/dist/servertool/handlers/followup-request-builder.js +13 -0
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +1 -1
- package/dist/servertool/handlers/iflow-model-error-retry.js +1 -1
- package/dist/servertool/handlers/recursive-detection-guard.js +1 -1
- package/dist/servertool/handlers/stop-message-auto.js +352 -257
- package/dist/servertool/handlers/stop-message-stage-policy.d.ts +22 -1
- package/dist/servertool/handlers/stop-message-stage-policy.js +472 -60
- package/dist/servertool/handlers/vision.js +1 -1
- package/dist/servertool/log/progress-file.d.ts +14 -0
- package/dist/servertool/log/progress-file.js +88 -0
- package/dist/servertool/pre-command-hooks.d.ts +17 -0
- package/dist/servertool/pre-command-hooks.js +491 -0
- package/dist/servertool/registry.d.ts +23 -6
- package/dist/servertool/registry.js +66 -1
- package/dist/servertool/server-side-tools.d.ts +1 -0
- package/dist/servertool/server-side-tools.js +216 -14
- package/dist/servertool/stop-gateway-context.d.ts +14 -0
- package/dist/servertool/stop-gateway-context.js +167 -0
- package/dist/servertool/stop-message-compare-context.d.ts +24 -0
- package/dist/servertool/stop-message-compare-context.js +133 -0
- package/dist/servertool/types.d.ts +12 -0
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -0
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +36 -1
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +3 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +3 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +118 -1
- package/dist/tools/apply-patch/args-normalizer/default-actions.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,886 @@
|
|
|
1
|
+
import { normalizeAssistantTextToToolCalls } from '../../shared/text-markup-normalizer.js';
|
|
2
|
+
import { validateToolCall } from '../../../tools/tool-registry.js';
|
|
3
|
+
import { encoding_for_model, get_encoding } from 'tiktoken';
|
|
4
|
+
const DEFAULT_OPTIONS = {
|
|
5
|
+
strictToolRequired: true,
|
|
6
|
+
textToolFallback: true
|
|
7
|
+
};
|
|
8
|
+
const DEFAULT_TOKEN_ENCODING = 'cl100k_base';
|
|
9
|
+
const tokenEncoderCache = new Map();
|
|
10
|
+
let defaultTokenEncoder = null;
|
|
11
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
12
|
+
const readString = (value) => {
|
|
13
|
+
if (typeof value !== 'string') {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const trimmed = value.trim();
|
|
17
|
+
return trimmed.length ? trimmed : undefined;
|
|
18
|
+
};
|
|
19
|
+
function readNumber(value) {
|
|
20
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
if (typeof value === 'string') {
|
|
24
|
+
const trimmed = value.trim();
|
|
25
|
+
if (!trimmed.length) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const parsed = Number(trimmed);
|
|
29
|
+
if (Number.isFinite(parsed)) {
|
|
30
|
+
return parsed;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
function readBoolean(input, fallback) {
|
|
36
|
+
if (typeof input === 'boolean') {
|
|
37
|
+
return input;
|
|
38
|
+
}
|
|
39
|
+
if (typeof input === 'string') {
|
|
40
|
+
const normalized = input.trim().toLowerCase();
|
|
41
|
+
if (['true', '1', 'yes', 'on'].includes(normalized)) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (['false', '0', 'no', 'off'].includes(normalized)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return fallback;
|
|
49
|
+
}
|
|
50
|
+
function getTokenEncoder(modelHint) {
|
|
51
|
+
const normalized = typeof modelHint === 'string' ? modelHint.trim() : '';
|
|
52
|
+
if (normalized) {
|
|
53
|
+
const cached = tokenEncoderCache.get(normalized);
|
|
54
|
+
if (cached) {
|
|
55
|
+
return cached;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const encoder = encoding_for_model(normalized);
|
|
59
|
+
tokenEncoderCache.set(normalized, encoder);
|
|
60
|
+
return encoder;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// fall through to default encoder
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!defaultTokenEncoder) {
|
|
67
|
+
defaultTokenEncoder = get_encoding(DEFAULT_TOKEN_ENCODING);
|
|
68
|
+
}
|
|
69
|
+
return defaultTokenEncoder;
|
|
70
|
+
}
|
|
71
|
+
function countTextTokens(text, modelHint) {
|
|
72
|
+
const trimmed = text.trim();
|
|
73
|
+
if (!trimmed.length) {
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
return getTokenEncoder(modelHint).encode(trimmed).length;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function resolveOptions(adapterContext) {
|
|
84
|
+
const context = (adapterContext ?? {});
|
|
85
|
+
const deepseekNode = isRecord(context.deepseek)
|
|
86
|
+
? context.deepseek
|
|
87
|
+
: isRecord(context.__rt) && isRecord(context.__rt.deepseek)
|
|
88
|
+
? context.__rt.deepseek
|
|
89
|
+
: undefined;
|
|
90
|
+
if (!deepseekNode) {
|
|
91
|
+
return { ...DEFAULT_OPTIONS };
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
strictToolRequired: readBoolean(deepseekNode.strictToolRequired, DEFAULT_OPTIONS.strictToolRequired),
|
|
95
|
+
textToolFallback: readBoolean(deepseekNode.textToolFallback, DEFAULT_OPTIONS.textToolFallback)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function resolveCapturedRequest(adapterContext) {
|
|
99
|
+
const context = (adapterContext ?? {});
|
|
100
|
+
if (isRecord(context.capturedChatRequest)) {
|
|
101
|
+
return context.capturedChatRequest;
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
function resolveEstimatedInputTokens(adapterContext) {
|
|
106
|
+
const context = (adapterContext ?? {});
|
|
107
|
+
const candidates = [
|
|
108
|
+
context.estimatedInputTokens,
|
|
109
|
+
context.estimated_tokens,
|
|
110
|
+
context.estimatedTokens
|
|
111
|
+
];
|
|
112
|
+
for (const candidate of candidates) {
|
|
113
|
+
const value = readNumber(candidate);
|
|
114
|
+
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
|
115
|
+
return Math.max(1, Math.round(value));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
function resolveModelHint(root, captured, adapterContext) {
|
|
121
|
+
return (readString(root.model) ??
|
|
122
|
+
readString(captured?.model) ??
|
|
123
|
+
readString(adapterContext?.modelId) ??
|
|
124
|
+
readString(adapterContext?.clientModelId) ??
|
|
125
|
+
readString(adapterContext?.originalModelId));
|
|
126
|
+
}
|
|
127
|
+
function looksLikeKnownProviderResponseShape(value) {
|
|
128
|
+
if (!isRecord(value))
|
|
129
|
+
return false;
|
|
130
|
+
if (Array.isArray(value.choices))
|
|
131
|
+
return true;
|
|
132
|
+
if (Array.isArray(value.output) || value.object === 'response')
|
|
133
|
+
return true;
|
|
134
|
+
if (typeof value.type === 'string' && String(value.type).toLowerCase() === 'message' && Array.isArray(value.content)) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
if (Array.isArray(value.candidates))
|
|
138
|
+
return true;
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
function tryUnwrapKnownShape(value, depth) {
|
|
142
|
+
if (depth < 0) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
if (looksLikeKnownProviderResponseShape(value)) {
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
if (!isRecord(value)) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
for (const key of ['data', 'body', 'response', 'payload', 'result', 'biz_data']) {
|
|
152
|
+
const nested = value[key];
|
|
153
|
+
const unwrapped = tryUnwrapKnownShape(nested, depth - 1);
|
|
154
|
+
if (unwrapped) {
|
|
155
|
+
return unwrapped;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
function isLikelyRateLimitMessage(message) {
|
|
161
|
+
return /(rate.?limit|quota|too many|exceed|exceeded|limit reached|message count)/i.test(message);
|
|
162
|
+
}
|
|
163
|
+
function throwDeepSeekBusinessEnvelopeError(root, adapterContext) {
|
|
164
|
+
const dataNode = isRecord(root.data) ? root.data : undefined;
|
|
165
|
+
const upstreamCode = readNumber(root.code);
|
|
166
|
+
const bizCode = readNumber(dataNode?.biz_code);
|
|
167
|
+
const bizMsg = readString(dataNode?.biz_msg);
|
|
168
|
+
const topMsg = readString(root.msg);
|
|
169
|
+
const message = bizMsg ?? topMsg ?? 'DeepSeek returned a non-chat business envelope';
|
|
170
|
+
const error = new Error(`[deepseek-web] upstream business error: ${message}`);
|
|
171
|
+
error.code = 'DEEPSEEK_BIZ_ERROR';
|
|
172
|
+
if ((typeof bizCode === 'number' && bizCode === 3) || isLikelyRateLimitMessage(message)) {
|
|
173
|
+
error.statusCode = 429;
|
|
174
|
+
}
|
|
175
|
+
error.details = {
|
|
176
|
+
requestId: readString(root.request_id) ??
|
|
177
|
+
readString(adapterContext?.requestId),
|
|
178
|
+
upstreamCode,
|
|
179
|
+
bizCode,
|
|
180
|
+
bizMsg,
|
|
181
|
+
upstreamMsg: topMsg,
|
|
182
|
+
payloadKeys: Object.keys(root).slice(0, 20),
|
|
183
|
+
phase: 'chat_process.resp.stage3.compat'
|
|
184
|
+
};
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
function normalizeDeepSeekBusinessEnvelope(root, adapterContext) {
|
|
188
|
+
if (looksLikeKnownProviderResponseShape(root)) {
|
|
189
|
+
return root;
|
|
190
|
+
}
|
|
191
|
+
const hasEnvelopeKeys = Object.prototype.hasOwnProperty.call(root, 'code')
|
|
192
|
+
&& Object.prototype.hasOwnProperty.call(root, 'data');
|
|
193
|
+
if (!hasEnvelopeKeys) {
|
|
194
|
+
return root;
|
|
195
|
+
}
|
|
196
|
+
const unwrapped = tryUnwrapKnownShape(root.data, 6);
|
|
197
|
+
if (unwrapped) {
|
|
198
|
+
return unwrapped;
|
|
199
|
+
}
|
|
200
|
+
const dataNode = isRecord(root.data) ? root.data : undefined;
|
|
201
|
+
const upstreamCode = readNumber(root.code);
|
|
202
|
+
const bizCode = readNumber(dataNode?.biz_code);
|
|
203
|
+
const bizMsg = readString(dataNode?.biz_msg);
|
|
204
|
+
const topMsg = readString(root.msg);
|
|
205
|
+
if ((typeof upstreamCode === 'number' && upstreamCode !== 0) ||
|
|
206
|
+
(typeof bizCode === 'number' && bizCode !== 0) ||
|
|
207
|
+
Boolean(bizMsg) ||
|
|
208
|
+
Boolean(topMsg)) {
|
|
209
|
+
throwDeepSeekBusinessEnvelopeError(root, adapterContext);
|
|
210
|
+
}
|
|
211
|
+
return root;
|
|
212
|
+
}
|
|
213
|
+
function resolveRequestedToolNames(captured) {
|
|
214
|
+
const names = new Set();
|
|
215
|
+
if (!captured) {
|
|
216
|
+
return names;
|
|
217
|
+
}
|
|
218
|
+
const toolsRaw = Array.isArray(captured.tools) ? captured.tools : [];
|
|
219
|
+
for (const tool of toolsRaw) {
|
|
220
|
+
if (!isRecord(tool)) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const fnNode = isRecord(tool.function) ? tool.function : undefined;
|
|
224
|
+
const name = readString(fnNode?.name) ?? readString(tool.name);
|
|
225
|
+
if (name) {
|
|
226
|
+
names.add(name);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return names;
|
|
230
|
+
}
|
|
231
|
+
function isToolChoiceRequired(captured) {
|
|
232
|
+
if (!captured) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
const toolChoice = captured.tool_choice;
|
|
236
|
+
if (typeof toolChoice === 'string') {
|
|
237
|
+
const normalized = toolChoice.trim().toLowerCase();
|
|
238
|
+
if (normalized === 'required') {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
if (normalized === 'none' || normalized === 'auto') {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (isRecord(toolChoice)) {
|
|
246
|
+
const type = readString(toolChoice.type)?.toLowerCase();
|
|
247
|
+
if (type === 'function') {
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
function tryParseJsonText(value) {
|
|
254
|
+
try {
|
|
255
|
+
return JSON.parse(value);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// Best-effort repair: some upstream payloads place raw newlines/tabs inside JSON strings.
|
|
259
|
+
let out = '';
|
|
260
|
+
let changed = false;
|
|
261
|
+
let inString = false;
|
|
262
|
+
let escaped = false;
|
|
263
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
264
|
+
const ch = value[i] ?? '';
|
|
265
|
+
if (inString) {
|
|
266
|
+
if (escaped) {
|
|
267
|
+
out += ch;
|
|
268
|
+
escaped = false;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (ch === '\\') {
|
|
272
|
+
out += ch;
|
|
273
|
+
escaped = true;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (ch === '"') {
|
|
277
|
+
out += ch;
|
|
278
|
+
inString = false;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (ch === '\n') {
|
|
282
|
+
out += '\\n';
|
|
283
|
+
changed = true;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (ch === '\r') {
|
|
287
|
+
out += '\\n';
|
|
288
|
+
changed = true;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (ch === '\t') {
|
|
292
|
+
out += '\\t';
|
|
293
|
+
changed = true;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const code = ch.charCodeAt(0);
|
|
297
|
+
if (code >= 0 && code < 0x20) {
|
|
298
|
+
out += `\\u${code.toString(16).padStart(4, '0')}`;
|
|
299
|
+
changed = true;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
out += ch;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (ch === '"') {
|
|
306
|
+
inString = true;
|
|
307
|
+
}
|
|
308
|
+
out += ch;
|
|
309
|
+
}
|
|
310
|
+
if (!changed) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
return JSON.parse(out);
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function normalizeToolArguments(value, toolName) {
|
|
322
|
+
if (typeof value === 'string') {
|
|
323
|
+
const trimmed = value.trim();
|
|
324
|
+
if (!trimmed.length) {
|
|
325
|
+
return JSON.stringify({});
|
|
326
|
+
}
|
|
327
|
+
const parsed = tryParseJsonText(trimmed);
|
|
328
|
+
if (parsed !== null) {
|
|
329
|
+
return JSON.stringify(parsed);
|
|
330
|
+
}
|
|
331
|
+
if (toolName) {
|
|
332
|
+
const validation = validateToolCall(toolName, trimmed);
|
|
333
|
+
if (validation.ok && typeof validation.normalizedArgs === 'string' && validation.normalizedArgs.trim().length > 0) {
|
|
334
|
+
return validation.normalizedArgs;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
if (value === undefined || value === null) {
|
|
340
|
+
const empty = JSON.stringify({});
|
|
341
|
+
if (!toolName) {
|
|
342
|
+
return empty;
|
|
343
|
+
}
|
|
344
|
+
const validation = validateToolCall(toolName, empty);
|
|
345
|
+
if (validation.ok && typeof validation.normalizedArgs === 'string' && validation.normalizedArgs.trim().length > 0) {
|
|
346
|
+
return validation.normalizedArgs;
|
|
347
|
+
}
|
|
348
|
+
return empty;
|
|
349
|
+
}
|
|
350
|
+
try {
|
|
351
|
+
const serialized = JSON.stringify(value);
|
|
352
|
+
if (!toolName) {
|
|
353
|
+
return serialized;
|
|
354
|
+
}
|
|
355
|
+
const validation = validateToolCall(toolName, serialized);
|
|
356
|
+
if (validation.ok && typeof validation.normalizedArgs === 'string' && validation.normalizedArgs.trim().length > 0) {
|
|
357
|
+
return validation.normalizedArgs;
|
|
358
|
+
}
|
|
359
|
+
return serialized;
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function coerceToolCallsValue(toolCallsRaw) {
|
|
366
|
+
if (Array.isArray(toolCallsRaw)) {
|
|
367
|
+
return toolCallsRaw;
|
|
368
|
+
}
|
|
369
|
+
if (typeof toolCallsRaw !== 'string') {
|
|
370
|
+
return [];
|
|
371
|
+
}
|
|
372
|
+
const parsed = tryParseJsonText(toolCallsRaw.trim());
|
|
373
|
+
if (Array.isArray(parsed)) {
|
|
374
|
+
return parsed;
|
|
375
|
+
}
|
|
376
|
+
if (isRecord(parsed) && Array.isArray(parsed.tool_calls)) {
|
|
377
|
+
return parsed.tool_calls;
|
|
378
|
+
}
|
|
379
|
+
return [];
|
|
380
|
+
}
|
|
381
|
+
function normalizeToolCallEntry(entry, allowedToolNames, callIdPrefix, index) {
|
|
382
|
+
const fn = isRecord(entry.function) ? entry.function : undefined;
|
|
383
|
+
const name = readString(entry.name) ?? readString(fn?.name);
|
|
384
|
+
if (!name) {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
if (allowedToolNames.size > 0 && !allowedToolNames.has(name)) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
const argsSource = entry.input !== undefined
|
|
391
|
+
? entry.input
|
|
392
|
+
: entry.arguments !== undefined
|
|
393
|
+
? entry.arguments
|
|
394
|
+
: fn?.arguments;
|
|
395
|
+
const normalizedArgs = normalizeToolArguments(argsSource, name);
|
|
396
|
+
if (!normalizedArgs) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
const id = readString(entry.id) ??
|
|
400
|
+
readString(entry.call_id) ??
|
|
401
|
+
`${callIdPrefix}_${index + 1}`;
|
|
402
|
+
return {
|
|
403
|
+
id,
|
|
404
|
+
type: 'function',
|
|
405
|
+
function: {
|
|
406
|
+
name,
|
|
407
|
+
arguments: normalizedArgs
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function normalizeToolCalls(toolCallsRaw, allowedToolNames, callIdPrefix) {
|
|
412
|
+
const toolCalls = coerceToolCallsValue(toolCallsRaw);
|
|
413
|
+
const normalized = [];
|
|
414
|
+
for (const [index, entry] of toolCalls.entries()) {
|
|
415
|
+
if (!isRecord(entry)) {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
const call = normalizeToolCallEntry(entry, allowedToolNames, callIdPrefix, index);
|
|
419
|
+
if (call) {
|
|
420
|
+
normalized.push(call);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return normalized;
|
|
424
|
+
}
|
|
425
|
+
function normalizeMessageToolCalls(message, allowedToolNames, callIdPrefix) {
|
|
426
|
+
const nextCalls = normalizeToolCalls(message.tool_calls, allowedToolNames, callIdPrefix);
|
|
427
|
+
if (!nextCalls.length) {
|
|
428
|
+
delete message.tool_calls;
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
message.tool_calls = nextCalls;
|
|
432
|
+
message.content = null;
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
function collectMessageTextCandidates(message) {
|
|
436
|
+
const candidates = [];
|
|
437
|
+
const content = message.content;
|
|
438
|
+
if (typeof content === 'string' && content.trim().length) {
|
|
439
|
+
candidates.push(content);
|
|
440
|
+
}
|
|
441
|
+
else if (Array.isArray(content)) {
|
|
442
|
+
for (const part of content) {
|
|
443
|
+
if (!isRecord(part)) {
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
const text = readString(part.text) ?? readString(part.content);
|
|
447
|
+
if (text) {
|
|
448
|
+
candidates.push(text);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const reasoning = readString(message.reasoning) ?? readString(message.reasoning_content);
|
|
453
|
+
if (reasoning) {
|
|
454
|
+
candidates.push(reasoning);
|
|
455
|
+
}
|
|
456
|
+
return candidates;
|
|
457
|
+
}
|
|
458
|
+
function normalizeFunctionResultsMarkupText(value) {
|
|
459
|
+
let changed = false;
|
|
460
|
+
const replaced = value.replace(/<function_results>\s*([\s\S]*?)\s*<\/function_results>/gi, (_match, inner) => {
|
|
461
|
+
changed = true;
|
|
462
|
+
const payload = typeof inner === 'string' ? inner.trim() : '';
|
|
463
|
+
if (!payload.length) {
|
|
464
|
+
return '';
|
|
465
|
+
}
|
|
466
|
+
return `\n\`\`\`json\n${payload}\n\`\`\`\n`;
|
|
467
|
+
});
|
|
468
|
+
if (!changed) {
|
|
469
|
+
return { text: value, changed: false };
|
|
470
|
+
}
|
|
471
|
+
return {
|
|
472
|
+
text: replaced.replace(/\n{3,}/g, '\n\n').trim(),
|
|
473
|
+
changed: true
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
function harvestFunctionResultsMarkup(message) {
|
|
477
|
+
let harvested = false;
|
|
478
|
+
const apply = (input) => {
|
|
479
|
+
if (typeof input !== 'string') {
|
|
480
|
+
return undefined;
|
|
481
|
+
}
|
|
482
|
+
const normalized = normalizeFunctionResultsMarkupText(input);
|
|
483
|
+
if (normalized.changed) {
|
|
484
|
+
harvested = true;
|
|
485
|
+
}
|
|
486
|
+
return normalized.text;
|
|
487
|
+
};
|
|
488
|
+
if (typeof message.content === 'string') {
|
|
489
|
+
const next = apply(message.content);
|
|
490
|
+
if (typeof next === 'string') {
|
|
491
|
+
message.content = next;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
else if (Array.isArray(message.content)) {
|
|
495
|
+
for (const part of message.content) {
|
|
496
|
+
if (!isRecord(part)) {
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
if (typeof part.text === 'string') {
|
|
500
|
+
const next = apply(part.text);
|
|
501
|
+
if (typeof next === 'string') {
|
|
502
|
+
part.text = next;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (typeof part.content === 'string') {
|
|
506
|
+
const next = apply(part.content);
|
|
507
|
+
if (typeof next === 'string') {
|
|
508
|
+
part.content = next;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (typeof message.reasoning === 'string') {
|
|
514
|
+
const next = apply(message.reasoning);
|
|
515
|
+
if (typeof next === 'string') {
|
|
516
|
+
message.reasoning = next;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (typeof message.reasoning_content === 'string') {
|
|
520
|
+
const next = apply(message.reasoning_content);
|
|
521
|
+
if (typeof next === 'string') {
|
|
522
|
+
message.reasoning_content = next;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return harvested;
|
|
526
|
+
}
|
|
527
|
+
function extractToolCallsFromDeepSeekJsonText(text, allowedToolNames, callIdPrefix) {
|
|
528
|
+
const normalized = [];
|
|
529
|
+
const trimmed = text.trim();
|
|
530
|
+
if (!trimmed.length) {
|
|
531
|
+
return normalized;
|
|
532
|
+
}
|
|
533
|
+
const parseCandidate = (candidate) => {
|
|
534
|
+
if (!isRecord(candidate)) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const calls = coerceToolCallsValue(candidate.tool_calls);
|
|
538
|
+
for (const entry of calls) {
|
|
539
|
+
if (!isRecord(entry)) {
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
const call = normalizeToolCallEntry(entry, allowedToolNames, callIdPrefix, normalized.length);
|
|
543
|
+
if (call) {
|
|
544
|
+
normalized.push(call);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
const parsedRoot = tryParseJsonText(trimmed);
|
|
549
|
+
if (parsedRoot !== null) {
|
|
550
|
+
parseCandidate(parsedRoot);
|
|
551
|
+
}
|
|
552
|
+
if (normalized.length > 0) {
|
|
553
|
+
return normalized;
|
|
554
|
+
}
|
|
555
|
+
const toolCallPattern = /\{\s*["']tool_calls["']\s*:\s*\[(.*?)\]\s*\}/gs;
|
|
556
|
+
let match;
|
|
557
|
+
while ((match = toolCallPattern.exec(trimmed)) !== null) {
|
|
558
|
+
const segment = match[1];
|
|
559
|
+
const parsed = tryParseJsonText(`{"tool_calls":[${segment}]}`);
|
|
560
|
+
if (parsed !== null) {
|
|
561
|
+
parseCandidate(parsed);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (normalized.length === 0) {
|
|
565
|
+
const hasToolCallsMarker = /tool_calls/i.test(trimmed);
|
|
566
|
+
const hasApplyPatchMarker = /apply_patch/i.test(trimmed);
|
|
567
|
+
if (hasToolCallsMarker && hasApplyPatchMarker) {
|
|
568
|
+
const patchMatch = trimmed.match(/\*\*\*\s*Begin Patch[\s\S]*?\*\*\*\s*End Patch/);
|
|
569
|
+
const patchText = readString(patchMatch?.[0]);
|
|
570
|
+
if (patchText && (allowedToolNames.size === 0 || allowedToolNames.has('apply_patch'))) {
|
|
571
|
+
const validation = validateToolCall('apply_patch', patchText);
|
|
572
|
+
if (validation.ok && typeof validation.normalizedArgs === 'string' && validation.normalizedArgs.trim().length > 0) {
|
|
573
|
+
normalized.push({
|
|
574
|
+
id: `${callIdPrefix}_repair_1`,
|
|
575
|
+
type: 'function',
|
|
576
|
+
function: {
|
|
577
|
+
name: 'apply_patch',
|
|
578
|
+
arguments: validation.normalizedArgs
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return normalized;
|
|
586
|
+
}
|
|
587
|
+
function normalizeMessageTextToolCalls(message, allowedToolNames, callIdPrefix) {
|
|
588
|
+
const candidates = collectMessageTextCandidates(message);
|
|
589
|
+
if (!candidates.length) {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
for (const text of candidates) {
|
|
593
|
+
const calls = extractToolCallsFromDeepSeekJsonText(text, allowedToolNames, callIdPrefix);
|
|
594
|
+
if (!calls.length) {
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
message.tool_calls = calls;
|
|
598
|
+
message.content = null;
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
function looksLikeShellCommand(value) {
|
|
604
|
+
return /^(?:pnpm|npm|yarn|node|git|rg|ls|cat|find|sed|head|tail|rm|cp|mv|mkdir|bash|sh|zsh)\b/i.test(value.trim());
|
|
605
|
+
}
|
|
606
|
+
function parseJsonArrayCommand(value) {
|
|
607
|
+
const trimmed = value.trim();
|
|
608
|
+
if (!trimmed.startsWith('[') || !trimmed.endsWith(']')) {
|
|
609
|
+
return undefined;
|
|
610
|
+
}
|
|
611
|
+
try {
|
|
612
|
+
const parsed = JSON.parse(trimmed);
|
|
613
|
+
if (!Array.isArray(parsed)) {
|
|
614
|
+
return undefined;
|
|
615
|
+
}
|
|
616
|
+
const tokens = parsed
|
|
617
|
+
.map((item) => (item == null ? '' : String(item)))
|
|
618
|
+
.filter((item) => item.length > 0);
|
|
619
|
+
if (!tokens.length) {
|
|
620
|
+
return undefined;
|
|
621
|
+
}
|
|
622
|
+
return tokens.join(' ');
|
|
623
|
+
}
|
|
624
|
+
catch {
|
|
625
|
+
return undefined;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function normalizeCommandLine(raw) {
|
|
629
|
+
return raw.trim().replace(/^\$\s*/, '').trim();
|
|
630
|
+
}
|
|
631
|
+
function extractCommandCandidates(text) {
|
|
632
|
+
const commands = [];
|
|
633
|
+
const seen = new Set();
|
|
634
|
+
const push = (value) => {
|
|
635
|
+
const normalized = value.trim();
|
|
636
|
+
if (!normalized || seen.has(normalized)) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
seen.add(normalized);
|
|
640
|
+
commands.push(normalized);
|
|
641
|
+
};
|
|
642
|
+
const blockPattern = /```(?:bash|sh|zsh)?\s*\n([\s\S]*?)```/gi;
|
|
643
|
+
let blockMatch;
|
|
644
|
+
while ((blockMatch = blockPattern.exec(text)) !== null) {
|
|
645
|
+
const block = (blockMatch[1] ?? '').toString();
|
|
646
|
+
const lines = block
|
|
647
|
+
.split(/\r?\n/)
|
|
648
|
+
.map((line) => normalizeCommandLine(line))
|
|
649
|
+
.filter((line) => line.length > 0 && !line.startsWith('#'));
|
|
650
|
+
if (!lines.length) {
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
const first = lines[0];
|
|
654
|
+
if (!looksLikeShellCommand(first)) {
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
push(lines.join('\n'));
|
|
658
|
+
}
|
|
659
|
+
const linePattern = text.split(/\r?\n/);
|
|
660
|
+
for (const raw of linePattern) {
|
|
661
|
+
const line = raw.trim();
|
|
662
|
+
if (!line.length || line.startsWith('```')) {
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
const ran = line.match(/^(?:[•*+-]\s*)?Ran\s+(.+)$/i);
|
|
666
|
+
if (ran?.[1]) {
|
|
667
|
+
const payload = ran[1].trim();
|
|
668
|
+
const parsedArray = parseJsonArrayCommand(payload);
|
|
669
|
+
if (parsedArray && looksLikeShellCommand(parsedArray)) {
|
|
670
|
+
push(parsedArray);
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
if (looksLikeShellCommand(payload)) {
|
|
674
|
+
push(payload);
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
const normalized = normalizeCommandLine(line);
|
|
679
|
+
if (looksLikeShellCommand(normalized)) {
|
|
680
|
+
push(normalized);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return commands;
|
|
684
|
+
}
|
|
685
|
+
function normalizeMessageCommandToolCall(message, allowedToolNames, callIdPrefix) {
|
|
686
|
+
if (allowedToolNames.size > 0 && !allowedToolNames.has('exec_command')) {
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
const candidates = collectMessageTextCandidates(message);
|
|
690
|
+
if (!candidates.length) {
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
for (const text of candidates) {
|
|
694
|
+
const commands = extractCommandCandidates(text);
|
|
695
|
+
if (!commands.length) {
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
const cmd = commands[commands.length - 1];
|
|
699
|
+
message.tool_calls = [
|
|
700
|
+
{
|
|
701
|
+
id: `${callIdPrefix}_cmd_1`,
|
|
702
|
+
type: 'function',
|
|
703
|
+
function: {
|
|
704
|
+
name: 'exec_command',
|
|
705
|
+
arguments: JSON.stringify({ cmd })
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
];
|
|
709
|
+
message.content = null;
|
|
710
|
+
return true;
|
|
711
|
+
}
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
714
|
+
function normalizeUsageSnapshot(raw) {
|
|
715
|
+
if (!isRecord(raw)) {
|
|
716
|
+
return {};
|
|
717
|
+
}
|
|
718
|
+
const prompt = readNumber(raw.prompt_tokens) ?? readNumber(raw.input_tokens);
|
|
719
|
+
const completion = readNumber(raw.completion_tokens) ?? readNumber(raw.output_tokens);
|
|
720
|
+
const total = readNumber(raw.total_tokens);
|
|
721
|
+
return {
|
|
722
|
+
...(typeof prompt === 'number' ? { prompt: Math.max(0, Math.round(prompt)) } : {}),
|
|
723
|
+
...(typeof completion === 'number' ? { completion: Math.max(0, Math.round(completion)) } : {}),
|
|
724
|
+
...(typeof total === 'number' ? { total: Math.max(0, Math.round(total)) } : {})
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
function estimateCompletionTokensFromChoices(choices, modelHint) {
|
|
728
|
+
const segments = [];
|
|
729
|
+
for (const choice of choices) {
|
|
730
|
+
if (!isRecord(choice)) {
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
const message = isRecord(choice.message) ? choice.message : undefined;
|
|
734
|
+
if (!message) {
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
const candidates = collectMessageTextCandidates(message);
|
|
738
|
+
if (candidates.length) {
|
|
739
|
+
segments.push(...candidates);
|
|
740
|
+
}
|
|
741
|
+
if (Array.isArray(message.tool_calls) && message.tool_calls.length) {
|
|
742
|
+
try {
|
|
743
|
+
segments.push(JSON.stringify(message.tool_calls));
|
|
744
|
+
}
|
|
745
|
+
catch {
|
|
746
|
+
// ignore serialization failure
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (!segments.length) {
|
|
751
|
+
return 0;
|
|
752
|
+
}
|
|
753
|
+
const joined = segments.join('\n');
|
|
754
|
+
return countTextTokens(joined, modelHint);
|
|
755
|
+
}
|
|
756
|
+
function applyUsageEstimate(root, choices, adapterContext, captured) {
|
|
757
|
+
const current = normalizeUsageSnapshot(root.usage);
|
|
758
|
+
const prompt = current.prompt ?? resolveEstimatedInputTokens(adapterContext);
|
|
759
|
+
const completion = current.completion ??
|
|
760
|
+
estimateCompletionTokensFromChoices(choices, resolveModelHint(root, captured, adapterContext));
|
|
761
|
+
const total = current.total ??
|
|
762
|
+
(typeof prompt === 'number' && typeof completion === 'number' ? prompt + completion : undefined);
|
|
763
|
+
if (typeof prompt !== 'number' &&
|
|
764
|
+
typeof completion !== 'number' &&
|
|
765
|
+
typeof total !== 'number') {
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
const usage = isRecord(root.usage) ? { ...root.usage } : {};
|
|
769
|
+
if (typeof prompt === 'number') {
|
|
770
|
+
usage.prompt_tokens = prompt;
|
|
771
|
+
usage.input_tokens = prompt;
|
|
772
|
+
}
|
|
773
|
+
if (typeof completion === 'number') {
|
|
774
|
+
usage.completion_tokens = completion;
|
|
775
|
+
usage.output_tokens = completion;
|
|
776
|
+
}
|
|
777
|
+
if (typeof total === 'number') {
|
|
778
|
+
usage.total_tokens = total;
|
|
779
|
+
}
|
|
780
|
+
root.usage = usage;
|
|
781
|
+
}
|
|
782
|
+
function normalizeChoice(choice, options, allowedToolNames, callIdPrefix) {
|
|
783
|
+
const message = isRecord(choice.message) ? choice.message : undefined;
|
|
784
|
+
if (!message) {
|
|
785
|
+
return { hasNative: false, hasFallback: false, harvestedFunctionResults: false };
|
|
786
|
+
}
|
|
787
|
+
const native = normalizeMessageToolCalls(message, allowedToolNames, callIdPrefix);
|
|
788
|
+
if (native) {
|
|
789
|
+
const finish = readString(choice.finish_reason)?.toLowerCase();
|
|
790
|
+
if (!finish || finish === 'stop') {
|
|
791
|
+
choice.finish_reason = 'tool_calls';
|
|
792
|
+
}
|
|
793
|
+
return { hasNative: true, hasFallback: false, harvestedFunctionResults: false };
|
|
794
|
+
}
|
|
795
|
+
if (!options.textToolFallback) {
|
|
796
|
+
return {
|
|
797
|
+
hasNative: false,
|
|
798
|
+
hasFallback: false,
|
|
799
|
+
harvestedFunctionResults: harvestFunctionResultsMarkup(message)
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
const transformed = normalizeAssistantTextToToolCalls(message);
|
|
803
|
+
if (transformed !== message) {
|
|
804
|
+
choice.message = transformed;
|
|
805
|
+
}
|
|
806
|
+
const fallbackMessage = isRecord(choice.message) ? choice.message : message;
|
|
807
|
+
let fallback = normalizeMessageToolCalls(fallbackMessage, allowedToolNames, callIdPrefix);
|
|
808
|
+
if (!fallback) {
|
|
809
|
+
fallback = normalizeMessageTextToolCalls(fallbackMessage, allowedToolNames, callIdPrefix);
|
|
810
|
+
}
|
|
811
|
+
if (fallback) {
|
|
812
|
+
const finish = readString(choice.finish_reason)?.toLowerCase();
|
|
813
|
+
if (!finish || finish === 'stop') {
|
|
814
|
+
choice.finish_reason = 'tool_calls';
|
|
815
|
+
}
|
|
816
|
+
return { hasNative: false, hasFallback: true, harvestedFunctionResults: false };
|
|
817
|
+
}
|
|
818
|
+
const commandFallback = normalizeMessageCommandToolCall(fallbackMessage, allowedToolNames, callIdPrefix);
|
|
819
|
+
if (commandFallback) {
|
|
820
|
+
const finish = readString(choice.finish_reason)?.toLowerCase();
|
|
821
|
+
if (!finish || finish === 'stop') {
|
|
822
|
+
choice.finish_reason = 'tool_calls';
|
|
823
|
+
}
|
|
824
|
+
return { hasNative: false, hasFallback: true, harvestedFunctionResults: false };
|
|
825
|
+
}
|
|
826
|
+
return {
|
|
827
|
+
hasNative: false,
|
|
828
|
+
hasFallback: false,
|
|
829
|
+
harvestedFunctionResults: harvestFunctionResultsMarkup(fallbackMessage)
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
function writeCompatState(root, state, source, harvestedFunctionResults) {
|
|
833
|
+
const metadata = isRecord(root.metadata) ? root.metadata : {};
|
|
834
|
+
metadata.deepseek = {
|
|
835
|
+
...(isRecord(metadata.deepseek) ? metadata.deepseek : {}),
|
|
836
|
+
toolCallState: state,
|
|
837
|
+
toolCallSource: source,
|
|
838
|
+
...(harvestedFunctionResults ? { functionResultsTextHarvested: true } : {})
|
|
839
|
+
};
|
|
840
|
+
root.metadata = metadata;
|
|
841
|
+
}
|
|
842
|
+
export function applyDeepSeekWebResponseTransform(payload, adapterContext) {
|
|
843
|
+
if (!payload || typeof payload !== 'object') {
|
|
844
|
+
return payload;
|
|
845
|
+
}
|
|
846
|
+
const cloned = structuredClone(payload);
|
|
847
|
+
const root = normalizeDeepSeekBusinessEnvelope(cloned, adapterContext);
|
|
848
|
+
const options = resolveOptions(adapterContext);
|
|
849
|
+
const captured = resolveCapturedRequest(adapterContext);
|
|
850
|
+
const allowedToolNames = resolveRequestedToolNames(captured);
|
|
851
|
+
const toolChoiceRequired = isToolChoiceRequired(captured);
|
|
852
|
+
const choices = Array.isArray(root.choices) ? root.choices : [];
|
|
853
|
+
let hasNative = false;
|
|
854
|
+
let hasFallback = false;
|
|
855
|
+
let harvestedFunctionResults = false;
|
|
856
|
+
for (const [index, choice] of choices.entries()) {
|
|
857
|
+
if (!isRecord(choice)) {
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
const result = normalizeChoice(choice, options, allowedToolNames, `deepseek_call_${index + 1}`);
|
|
861
|
+
hasNative = hasNative || result.hasNative;
|
|
862
|
+
hasFallback = hasFallback || result.hasFallback;
|
|
863
|
+
harvestedFunctionResults = harvestedFunctionResults || result.harvestedFunctionResults;
|
|
864
|
+
}
|
|
865
|
+
const state = hasNative
|
|
866
|
+
? 'native_tool_calls'
|
|
867
|
+
: hasFallback
|
|
868
|
+
? 'text_tool_calls'
|
|
869
|
+
: 'no_tool_calls';
|
|
870
|
+
const source = hasNative ? 'native' : hasFallback ? 'fallback' : 'none';
|
|
871
|
+
writeCompatState(root, state, source, harvestedFunctionResults);
|
|
872
|
+
applyUsageEstimate(root, choices, adapterContext, captured);
|
|
873
|
+
if (toolChoiceRequired && options.strictToolRequired && !hasNative && !hasFallback) {
|
|
874
|
+
const error = new Error('DeepSeek tool_choice=required but no valid tool call was produced');
|
|
875
|
+
error.code = 'DEEPSEEK_TOOL_REQUIRED_MISSING';
|
|
876
|
+
error.details = {
|
|
877
|
+
requestId: readString(adapterContext?.requestId),
|
|
878
|
+
phase: 'chat_process.resp.stage3.compat',
|
|
879
|
+
state,
|
|
880
|
+
fallbackEnabled: options.textToolFallback,
|
|
881
|
+
strictToolRequired: options.strictToolRequired
|
|
882
|
+
};
|
|
883
|
+
throw error;
|
|
884
|
+
}
|
|
885
|
+
return root;
|
|
886
|
+
}
|