@jsonstudio/llms 0.6.1643 → 0.6.1739
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/harvest-tool-calls-from-text.d.ts +10 -0
- package/dist/conversion/compat/actions/harvest-tool-calls-from-text.js +121 -0
- package/dist/conversion/compat/actions/iflow-kimi-cli-defaults.d.ts +10 -0
- package/dist/conversion/compat/actions/iflow-kimi-cli-defaults.js +80 -0
- package/dist/conversion/compat/actions/iflow-kimi-history-media-placeholder.d.ts +7 -0
- package/dist/conversion/compat/actions/iflow-kimi-history-media-placeholder.js +161 -0
- package/dist/conversion/compat/actions/iflow-kimi-thinking-reasoning-fill.d.ts +12 -0
- package/dist/conversion/compat/actions/iflow-kimi-thinking-reasoning-fill.js +67 -0
- package/dist/conversion/compat/actions/iflow-response-body-unwrap.d.ts +9 -0
- package/dist/conversion/compat/actions/iflow-response-body-unwrap.js +140 -0
- package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.d.ts +10 -0
- package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.js +59 -0
- package/dist/conversion/compat/actions/lmstudio-responses-input-stringify.d.ts +14 -0
- package/dist/conversion/compat/actions/lmstudio-responses-input-stringify.js +125 -0
- package/dist/conversion/compat/actions/normalize-tool-call-ids.d.ts +11 -0
- package/dist/conversion/compat/actions/normalize-tool-call-ids.js +140 -0
- package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.d.ts +2 -0
- package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.js +152 -0
- package/dist/conversion/compat/antigravity-session-signature.d.ts +1 -1
- package/dist/conversion/compat/antigravity-session-signature.js +5 -4
- package/dist/conversion/compat/profiles/chat-iflow.json +6 -0
- package/dist/conversion/compat/profiles/chat-lmstudio.json +7 -1
- package/dist/conversion/hub/operation-table/operation-table-runner.js +1 -1
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +19 -2
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +101 -5
- package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.d.ts +2 -0
- package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +63 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +18 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +1 -1
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +8 -5
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +5 -1
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +113 -0
- package/dist/conversion/hub/pipeline/target-utils.js +3 -0
- package/dist/conversion/hub/response/provider-response.js +27 -1
- package/dist/conversion/responses/responses-openai-bridge.js +32 -6
- package/dist/conversion/shared/anthropic-message-utils.js +20 -5
- package/dist/conversion/shared/bridge-id-utils.d.ts +2 -0
- package/dist/conversion/shared/bridge-id-utils.js +52 -15
- package/dist/conversion/shared/responses-conversation-store.js +40 -5
- package/dist/conversion/shared/responses-output-builder.js +23 -7
- package/dist/conversion/shared/responses-tool-utils.d.ts +1 -0
- package/dist/conversion/shared/responses-tool-utils.js +30 -13
- package/dist/conversion/shared/text-markup-normalizer.d.ts +1 -0
- package/dist/conversion/shared/text-markup-normalizer.js +269 -1
- package/dist/router/virtual-router/bootstrap.js +31 -7
- package/dist/router/virtual-router/classifier.js +1 -1
- package/dist/router/virtual-router/engine/antigravity/alias-lease.d.ts +33 -0
- package/dist/router/virtual-router/engine/antigravity/alias-lease.js +247 -0
- package/dist/router/virtual-router/engine/health/index.d.ts +23 -0
- package/dist/router/virtual-router/engine/health/index.js +720 -0
- package/dist/router/virtual-router/engine/provider-key/parse.d.ts +6 -0
- package/dist/router/virtual-router/engine/provider-key/parse.js +43 -0
- package/dist/router/virtual-router/engine/routing-pools/index.d.ts +13 -0
- package/dist/router/virtual-router/engine/routing-pools/index.js +225 -0
- package/dist/router/virtual-router/engine/routing-state/keys.d.ts +3 -0
- package/dist/router/virtual-router/engine/routing-state/keys.js +30 -0
- package/dist/router/virtual-router/engine/routing-state/metadata.d.ts +6 -0
- package/dist/router/virtual-router/engine/routing-state/metadata.js +132 -0
- package/dist/router/virtual-router/engine/routing-state/store.d.ts +11 -0
- package/dist/router/virtual-router/engine/routing-state/store.js +107 -0
- package/dist/router/virtual-router/engine-health.d.ts +1 -23
- package/dist/router/virtual-router/engine-health.js +1 -720
- package/dist/router/virtual-router/engine-selection/route-utils.js +57 -0
- package/dist/router/virtual-router/engine-selection/tier-selection-select.js +8 -48
- package/dist/router/virtual-router/engine-selection/tier-selection.js +34 -17
- package/dist/router/virtual-router/engine-selection.d.ts +1 -13
- package/dist/router/virtual-router/engine-selection.js +1 -225
- package/dist/router/virtual-router/engine.d.ts +2 -23
- package/dist/router/virtual-router/engine.js +130 -603
- package/dist/router/virtual-router/message-utils.js +15 -5
- package/dist/servertool/engine.js +4 -4
- package/dist/servertool/handlers/followup-request-builder.js +46 -0
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +48 -47
- package/dist/servertool/handlers/stop-message-auto.js +64 -7
- package/dist/servertool/handlers/vision.js +10 -0
- package/dist/servertool/types.d.ts +3 -0
- package/dist/sse/sse-to-json/builders/response-builder.js +6 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +32 -2
- package/dist/sse/sse-to-json/parsers/sse-parser.js +34 -0
- package/dist/sse/sse-to-json/responses-sse-to-json-converter.d.ts +1 -0
- package/dist/sse/sse-to-json/responses-sse-to-json-converter.js +33 -1
- package/dist/tools/apply-patch/args-normalizer/default-actions.d.ts +2 -0
- package/dist/tools/apply-patch/args-normalizer/default-actions.js +12 -0
- package/dist/tools/apply-patch/args-normalizer/extract-patch.d.ts +2 -0
- package/dist/tools/apply-patch/args-normalizer/extract-patch.js +15 -0
- package/dist/tools/apply-patch/args-normalizer/index.d.ts +2 -0
- package/dist/tools/apply-patch/args-normalizer/index.js +164 -0
- package/dist/tools/apply-patch/args-normalizer/structured-builders.d.ts +7 -0
- package/dist/tools/apply-patch/args-normalizer/structured-builders.js +85 -0
- package/dist/tools/apply-patch/args-normalizer/types.d.ts +54 -0
- package/dist/tools/apply-patch/args-normalizer/types.js +1 -0
- package/dist/tools/apply-patch/patch-text/looks-like-patch.js +1 -0
- package/dist/tools/apply-patch/patch-text/normalize.js +104 -5
- package/dist/tools/apply-patch/structured/coercion.js +28 -4
- package/dist/tools/apply-patch/validator.js +7 -146
- package/package.json +1 -1
|
@@ -14,6 +14,10 @@ import { applyGlmWebSearchRequestTransform } from '../../../compat/actions/glm-w
|
|
|
14
14
|
import { applyGeminiWebSearchCompat } from '../../../compat/actions/gemini-web-search.js';
|
|
15
15
|
import { applyIflowWebSearchRequestTransform } from '../../../compat/actions/iflow-web-search.js';
|
|
16
16
|
import { applyIflowToolTextFallback } from '../../../compat/actions/iflow-tool-text-fallback.js';
|
|
17
|
+
import { unwrapIflowResponseBodyEnvelope } from '../../../compat/actions/iflow-response-body-unwrap.js';
|
|
18
|
+
import { applyIflowKimiHistoryMediaPlaceholder } from '../../../compat/actions/iflow-kimi-history-media-placeholder.js';
|
|
19
|
+
import { applyIflowKimiCliDefaults } from '../../../compat/actions/iflow-kimi-cli-defaults.js';
|
|
20
|
+
import { fillIflowKimiThinkingReasoningContent } from '../../../compat/actions/iflow-kimi-thinking-reasoning-fill.js';
|
|
17
21
|
import { applyGlmImageContentTransform } from '../../../compat/actions/glm-image-content.js';
|
|
18
22
|
import { applyGlmVisionPromptTransform } from '../../../compat/actions/glm-vision-prompt.js';
|
|
19
23
|
import { applyClaudeThinkingToolSchemaCompat } from '../../../compat/actions/claude-thinking-tools.js';
|
|
@@ -21,6 +25,11 @@ import { wrapGeminiCliRequest } from '../../../compat/actions/gemini-cli-request
|
|
|
21
25
|
import { prepareAntigravityThoughtSignatureForGeminiRequest } from '../../../compat/actions/antigravity-thought-signature-prepare.js';
|
|
22
26
|
import { cacheAntigravityThoughtSignatureFromGeminiResponse } from '../../../compat/actions/antigravity-thought-signature-cache.js';
|
|
23
27
|
import { applyAnthropicClaudeCodeSystemPromptCompat } from '../../../compat/actions/anthropic-claude-code-system-prompt.js';
|
|
28
|
+
import { normalizeToolCallIdsInPlace } from '../../../compat/actions/normalize-tool-call-ids.js';
|
|
29
|
+
import { harvestToolCallsFromText } from '../../../compat/actions/harvest-tool-calls-from-text.js';
|
|
30
|
+
import { stripOrphanFunctionCallsTag } from '../../../compat/actions/strip-orphan-function-calls-tag.js';
|
|
31
|
+
import { stringifyLmstudioResponsesInput } from '../../../compat/actions/lmstudio-responses-input-stringify.js';
|
|
32
|
+
import { enforceLmstudioResponsesFcToolCallIds } from '../../../compat/actions/lmstudio-responses-fc-ids.js';
|
|
24
33
|
const RATE_LIMIT_ERROR = 'ERR_COMPAT_RATE_LIMIT_DETECTED';
|
|
25
34
|
const INTERNAL_STATE = Symbol('compat.internal_state');
|
|
26
35
|
export function runRequestCompatPipeline(profileId, payload, options) {
|
|
@@ -145,6 +154,19 @@ function applyMapping(root, mapping, state) {
|
|
|
145
154
|
case 'normalize_tool_choice':
|
|
146
155
|
normalizeToolChoice(root, mapping);
|
|
147
156
|
break;
|
|
157
|
+
case 'normalize_tool_call_ids':
|
|
158
|
+
normalizeToolCallIdsInPlace(root);
|
|
159
|
+
break;
|
|
160
|
+
case 'lmstudio_responses_fc_ids':
|
|
161
|
+
if (state.direction === 'request') {
|
|
162
|
+
replaceRoot(root, enforceLmstudioResponsesFcToolCallIds(root));
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
case 'lmstudio_responses_input_stringify':
|
|
166
|
+
if (state.direction === 'request') {
|
|
167
|
+
replaceRoot(root, stringifyLmstudioResponsesInput(root, state.adapterContext));
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
148
170
|
case 'inject_instruction':
|
|
149
171
|
injectInstruction(root, mapping);
|
|
150
172
|
break;
|
|
@@ -154,6 +176,31 @@ function applyMapping(root, mapping, state) {
|
|
|
154
176
|
case 'extract_glm_tool_markup':
|
|
155
177
|
extractGlmToolMarkup(root);
|
|
156
178
|
break;
|
|
179
|
+
case 'harvest_tool_calls_from_text':
|
|
180
|
+
if (state.direction === 'response') {
|
|
181
|
+
replaceRoot(root, harvestToolCallsFromText(root));
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
case 'strip_orphan_function_calls_tag':
|
|
185
|
+
if (state.direction === 'response') {
|
|
186
|
+
replaceRoot(root, stripOrphanFunctionCallsTag(root));
|
|
187
|
+
}
|
|
188
|
+
break;
|
|
189
|
+
case 'iflow_kimi_thinking_reasoning_fill':
|
|
190
|
+
if (state.direction === 'request') {
|
|
191
|
+
replaceRoot(root, fillIflowKimiThinkingReasoningContent(root));
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
case 'iflow_kimi_history_media_placeholder':
|
|
195
|
+
if (state.direction === 'request') {
|
|
196
|
+
replaceRoot(root, applyIflowKimiHistoryMediaPlaceholder(root));
|
|
197
|
+
}
|
|
198
|
+
break;
|
|
199
|
+
case 'iflow_kimi_cli_defaults':
|
|
200
|
+
if (state.direction === 'request') {
|
|
201
|
+
replaceRoot(root, applyIflowKimiCliDefaults(root));
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
157
204
|
case 'dto_unwrap':
|
|
158
205
|
dtoUnwrap(root, state);
|
|
159
206
|
break;
|
|
@@ -215,6 +262,11 @@ function applyMapping(root, mapping, state) {
|
|
|
215
262
|
replaceRoot(root, applyIflowToolTextFallback(root, { models: mapping.models }));
|
|
216
263
|
}
|
|
217
264
|
break;
|
|
265
|
+
case 'iflow_response_body_unwrap':
|
|
266
|
+
if (state.direction === 'response') {
|
|
267
|
+
replaceRoot(root, unwrapIflowResponseBodyEnvelope(root));
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
218
270
|
case 'claude_thinking_tool_schema':
|
|
219
271
|
if (state.direction === 'request') {
|
|
220
272
|
replaceRoot(root, applyClaudeThinkingToolSchemaCompat(root, state.adapterContext));
|
|
@@ -290,13 +342,24 @@ function replaceRoot(target, source) {
|
|
|
290
342
|
}
|
|
291
343
|
function dtoUnwrap(root, state) {
|
|
292
344
|
const original = structuredClone(root);
|
|
293
|
-
|
|
345
|
+
const data = original.data;
|
|
346
|
+
if (isRecord(data)) {
|
|
294
347
|
state.dtoEnvelope = { original, isDto: true };
|
|
295
|
-
replaceRoot(root,
|
|
348
|
+
replaceRoot(root, data);
|
|
349
|
+
return;
|
|
296
350
|
}
|
|
297
|
-
|
|
298
|
-
|
|
351
|
+
// Transport compatibility: some upstreams return JSON bodies but mislabel the content-type,
|
|
352
|
+
// causing HTTP clients to surface `data` as a string. When a compat profile opts into dto_unwrap,
|
|
353
|
+
// best-effort parse JSON strings so downstream semantic mapping sees canonical objects.
|
|
354
|
+
if (typeof data === 'string') {
|
|
355
|
+
const parsed = tryParseJsonRecord(data);
|
|
356
|
+
if (parsed) {
|
|
357
|
+
state.dtoEnvelope = { original, isDto: true };
|
|
358
|
+
replaceRoot(root, parsed);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
299
361
|
}
|
|
362
|
+
state.dtoEnvelope = { original, isDto: false };
|
|
300
363
|
}
|
|
301
364
|
function dtoRewrap(root, state) {
|
|
302
365
|
const envelope = state.dtoEnvelope;
|
|
@@ -312,6 +375,35 @@ function dtoRewrap(root, state) {
|
|
|
312
375
|
replaceRoot(root, rebuilt);
|
|
313
376
|
state.dtoEnvelope = undefined;
|
|
314
377
|
}
|
|
378
|
+
function tryParseJsonRecord(raw) {
|
|
379
|
+
try {
|
|
380
|
+
let text = typeof raw === 'string' ? raw.trim() : '';
|
|
381
|
+
if (!text)
|
|
382
|
+
return null;
|
|
383
|
+
// anti-XSSI prefix: ")]}',\n{...}"
|
|
384
|
+
text = text.replace(/^\)\]\}',?\s*/u, '');
|
|
385
|
+
// single-line SSE-like wrapper sometimes returned as plain text
|
|
386
|
+
text = text.replace(/^data:\s*/iu, '');
|
|
387
|
+
// Quick structural guards to avoid parsing random prose.
|
|
388
|
+
const first = text.charAt(0);
|
|
389
|
+
const last = text.charAt(text.length - 1);
|
|
390
|
+
if (first !== '{' || last !== '}') {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
// Guard: avoid unbounded parsing for pathological payloads.
|
|
394
|
+
if (text.length > 10 * 1024 * 1024) {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
const parsed = JSON.parse(text);
|
|
398
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
return parsed;
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
315
407
|
function applyShapeFilterMapping(root, mapping, state) {
|
|
316
408
|
const target = mapping.target ?? state.direction;
|
|
317
409
|
const filter = new UniversalShapeFilter(mapping.config);
|
|
@@ -765,9 +857,13 @@ function convertOutputEntryToChoice(entry, index, root) {
|
|
|
765
857
|
(typeof entry.status === 'string' && entry.status) ||
|
|
766
858
|
(typeof root.status === 'string' && root.status) ||
|
|
767
859
|
'stop';
|
|
860
|
+
const hasToolCalls = message &&
|
|
861
|
+
typeof message === 'object' &&
|
|
862
|
+
Array.isArray(message.tool_calls) &&
|
|
863
|
+
message.tool_calls.length > 0;
|
|
768
864
|
return {
|
|
769
865
|
index,
|
|
770
|
-
finish_reason: normalizeFinishReason(finishReasonCandidate),
|
|
866
|
+
finish_reason: hasToolCalls ? 'tool_calls' : normalizeFinishReason(finishReasonCandidate),
|
|
771
867
|
message
|
|
772
868
|
};
|
|
773
869
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const PROFILE_BY_PROVIDER_FAMILY = {
|
|
2
|
+
iflow: 'chat:iflow',
|
|
3
|
+
lmstudio: 'chat:lmstudio',
|
|
4
|
+
antigravity: 'chat:gemini-cli',
|
|
5
|
+
'gemini-cli': 'chat:gemini-cli'
|
|
6
|
+
};
|
|
7
|
+
function readNonEmptyString(value) {
|
|
8
|
+
if (typeof value !== 'string') {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
const trimmed = value.trim();
|
|
12
|
+
return trimmed.length ? trimmed : undefined;
|
|
13
|
+
}
|
|
14
|
+
function normalizeProviderFamily(value) {
|
|
15
|
+
const normalized = readNonEmptyString(value)?.toLowerCase();
|
|
16
|
+
if (!normalized) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
if (normalized in PROFILE_BY_PROVIDER_FAMILY) {
|
|
20
|
+
return normalized;
|
|
21
|
+
}
|
|
22
|
+
if (normalized.includes('.')) {
|
|
23
|
+
const firstToken = normalized.split('.')[0]?.trim();
|
|
24
|
+
if (firstToken && firstToken in PROFILE_BY_PROVIDER_FAMILY) {
|
|
25
|
+
return firstToken;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (normalized.includes(':')) {
|
|
29
|
+
const firstToken = normalized.split(':')[0]?.trim();
|
|
30
|
+
if (firstToken && firstToken in PROFILE_BY_PROVIDER_FAMILY) {
|
|
31
|
+
return firstToken;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (normalized.includes('/')) {
|
|
35
|
+
const firstToken = normalized.split('/')[0]?.trim();
|
|
36
|
+
if (firstToken && firstToken in PROFILE_BY_PROVIDER_FAMILY) {
|
|
37
|
+
return firstToken;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
function resolveDefaultProfileFromFamily(adapterContext) {
|
|
43
|
+
const context = adapterContext;
|
|
44
|
+
const providerFamily = normalizeProviderFamily(context.providerFamily);
|
|
45
|
+
if (providerFamily) {
|
|
46
|
+
return PROFILE_BY_PROVIDER_FAMILY[providerFamily];
|
|
47
|
+
}
|
|
48
|
+
for (const key of ['providerId', 'providerKey', 'runtimeKey', 'profileId', 'providerType']) {
|
|
49
|
+
const family = normalizeProviderFamily(context[key]);
|
|
50
|
+
if (family) {
|
|
51
|
+
return PROFILE_BY_PROVIDER_FAMILY[family];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
export function resolveCompatProfileForContext(adapterContext) {
|
|
57
|
+
const context = adapterContext;
|
|
58
|
+
const explicit = readNonEmptyString(context.compatibilityProfile);
|
|
59
|
+
if (explicit) {
|
|
60
|
+
return explicit;
|
|
61
|
+
}
|
|
62
|
+
return resolveDefaultProfileFromFamily(adapterContext);
|
|
63
|
+
}
|
|
@@ -47,6 +47,18 @@ export type MappingInstruction = {
|
|
|
47
47
|
action: 'normalize_tool_choice';
|
|
48
48
|
path?: string;
|
|
49
49
|
objectReplacement?: string;
|
|
50
|
+
} | {
|
|
51
|
+
action: 'normalize_tool_call_ids';
|
|
52
|
+
} | {
|
|
53
|
+
action: 'lmstudio_responses_fc_ids';
|
|
54
|
+
} | {
|
|
55
|
+
action: 'lmstudio_responses_input_stringify';
|
|
56
|
+
} | {
|
|
57
|
+
action: 'iflow_kimi_history_media_placeholder';
|
|
58
|
+
} | {
|
|
59
|
+
action: 'iflow_kimi_cli_defaults';
|
|
60
|
+
} | {
|
|
61
|
+
action: 'iflow_kimi_thinking_reasoning_fill';
|
|
50
62
|
} | {
|
|
51
63
|
action: 'inject_instruction';
|
|
52
64
|
sourcePath: string;
|
|
@@ -63,6 +75,10 @@ export type MappingInstruction = {
|
|
|
63
75
|
action: 'convert_responses_output_to_choices';
|
|
64
76
|
} | {
|
|
65
77
|
action: 'extract_glm_tool_markup';
|
|
78
|
+
} | {
|
|
79
|
+
action: 'harvest_tool_calls_from_text';
|
|
80
|
+
} | {
|
|
81
|
+
action: 'strip_orphan_function_calls_tag';
|
|
66
82
|
} | {
|
|
67
83
|
action: 'dto_unwrap';
|
|
68
84
|
} | {
|
|
@@ -114,6 +130,8 @@ export type MappingInstruction = {
|
|
|
114
130
|
} | {
|
|
115
131
|
action: 'iflow_tool_text_fallback';
|
|
116
132
|
models?: string[];
|
|
133
|
+
} | {
|
|
134
|
+
action: 'iflow_response_body_unwrap';
|
|
117
135
|
} | {
|
|
118
136
|
action: 'claude_thinking_tool_schema';
|
|
119
137
|
} | {
|
|
@@ -543,7 +543,7 @@ export class HubPipeline {
|
|
|
543
543
|
//
|
|
544
544
|
// 注意:这里不再根据 processMode(passthrough/chat) 做分支判断——即使某些
|
|
545
545
|
// route 将 processMode 标记为 passthrough,我们仍然需要保留一次规范化后的
|
|
546
|
-
// Chat 请求快照,供 stopMessage /
|
|
546
|
+
// Chat 请求快照,供 stopMessage / empty_reply_continue 等被动触发型
|
|
547
547
|
// servertool 在响应阶段使用。
|
|
548
548
|
//
|
|
549
549
|
// 之前这里通过 JSON.stringify/parse 做深拷贝,但在部分 Responses/Gemini
|
package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js
CHANGED
|
@@ -131,13 +131,16 @@ function normalizeToolOutput(entry) {
|
|
|
131
131
|
}
|
|
132
132
|
function applyToolCallIdStyleMetadata(chatEnvelope, adapterContext, snapshot) {
|
|
133
133
|
const metadata = chatEnvelope.metadata || (chatEnvelope.metadata = { context: adapterContext });
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
const current = typeof metadata.toolCallIdStyle === 'string'
|
|
135
|
+
? String(metadata.toolCallIdStyle).trim()
|
|
136
|
+
: '';
|
|
137
|
+
const resolved = selectToolCallIdStyle(adapterContext, snapshot);
|
|
138
|
+
if (!resolved) {
|
|
137
139
|
return;
|
|
138
140
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
+
// Always honor the route-selected AdapterContext toolCallIdStyle when present.
|
|
142
|
+
// This prevents cross-provider leakage (e.g. LM Studio "preserve" contaminating OpenAI "fc").
|
|
143
|
+
if (!current || current !== resolved) {
|
|
141
144
|
metadata.toolCallIdStyle = resolved;
|
|
142
145
|
}
|
|
143
146
|
}
|
package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { applyRequestCompat, applyResponseCompat } from '../../../compat/compat-engine.js';
|
|
2
|
+
import { resolveCompatProfileForContext } from '../../../compat/compat-profile-resolver.js';
|
|
2
3
|
function pickCompatProfile(adapterContext) {
|
|
3
4
|
const candidate = adapterContext.compatibilityProfile;
|
|
4
|
-
|
|
5
|
+
if (typeof candidate === 'string' && candidate.trim()) {
|
|
6
|
+
return candidate.trim();
|
|
7
|
+
}
|
|
8
|
+
return resolveCompatProfileForContext(adapterContext);
|
|
5
9
|
}
|
|
6
10
|
export async function runReqOutboundStage3Compat(options) {
|
|
7
11
|
const profile = pickCompatProfile(options.adapterContext);
|
package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js
CHANGED
|
@@ -38,6 +38,19 @@ function resolveProviderType(protocol) {
|
|
|
38
38
|
return undefined;
|
|
39
39
|
}
|
|
40
40
|
export async function runRespInboundStage1SseDecode(options) {
|
|
41
|
+
// Transport compatibility: some HTTP clients return JSON bodies as plain strings when the upstream
|
|
42
|
+
// mislabels `Content-Type`. Best-effort parse JSON text early so downstream format adapters and
|
|
43
|
+
// semantic mappers always see canonical objects.
|
|
44
|
+
const maybeJsonText = tryDecodeJsonBodyFromText(options.payload);
|
|
45
|
+
if (maybeJsonText) {
|
|
46
|
+
recordStage(options.stageRecorder, 'chat_process.resp.stage1.sse_decode', {
|
|
47
|
+
streamDetected: false,
|
|
48
|
+
decoded: false,
|
|
49
|
+
protocol: options.providerProtocol,
|
|
50
|
+
reason: 'text_body_is_json'
|
|
51
|
+
});
|
|
52
|
+
return { payload: maybeJsonText, decodedFromSse: false };
|
|
53
|
+
}
|
|
41
54
|
const wrapperError = extractSseWrapperError(options.payload);
|
|
42
55
|
const stream = extractSseStream(options.payload);
|
|
43
56
|
// 某些 mock-provider / 捕获样本在 SSE 连接被异常终止时会携带 error 标记,
|
|
@@ -67,6 +80,19 @@ export async function runRespInboundStage1SseDecode(options) {
|
|
|
67
80
|
});
|
|
68
81
|
return { payload: options.payload, decodedFromSse: false };
|
|
69
82
|
}
|
|
83
|
+
// Compatibility: when an upstream is asked for streaming but responds with a single JSON body
|
|
84
|
+
// (common for mock servers and some OpenAI-compatible implementations), the provider wrapper may
|
|
85
|
+
// still surface a Readable via `__sse_stream`. In that case we should treat it as JSON, not SSE.
|
|
86
|
+
const maybeJson = await tryDecodeJsonBodyFromStream(stream);
|
|
87
|
+
if (maybeJson) {
|
|
88
|
+
recordStage(options.stageRecorder, 'chat_process.resp.stage1.sse_decode', {
|
|
89
|
+
streamDetected: true,
|
|
90
|
+
decoded: false,
|
|
91
|
+
protocol: options.providerProtocol,
|
|
92
|
+
reason: 'stream_body_is_json'
|
|
93
|
+
});
|
|
94
|
+
return { payload: maybeJson, decodedFromSse: false };
|
|
95
|
+
}
|
|
70
96
|
if (!supportsSseProtocol(options.providerProtocol)) {
|
|
71
97
|
recordStage(options.stageRecorder, 'chat_process.resp.stage1.sse_decode', {
|
|
72
98
|
streamDetected: true,
|
|
@@ -120,6 +146,93 @@ export async function runRespInboundStage1SseDecode(options) {
|
|
|
120
146
|
function supportsSseProtocol(protocol) {
|
|
121
147
|
return protocol === 'openai-chat' || protocol === 'openai-responses' || protocol === 'anthropic-messages' || protocol === 'gemini-chat';
|
|
122
148
|
}
|
|
149
|
+
function tryDecodeJsonBodyFromText(payload) {
|
|
150
|
+
try {
|
|
151
|
+
if (typeof payload !== 'string') {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
// Normalize common prefixes / wrappers from OpenAI-compatible upstreams.
|
|
155
|
+
let text = payload.trimStart();
|
|
156
|
+
// anti-XSSI prefix: ")]}',\n{...}"
|
|
157
|
+
text = text.replace(/^\)\]\}',?\s*/u, '');
|
|
158
|
+
// single-line SSE-like wrapper sometimes returned as plain text
|
|
159
|
+
text = text.replace(/^data:\s*/iu, '');
|
|
160
|
+
const looksLikeJson = text.startsWith('{') || text.startsWith('[');
|
|
161
|
+
if (!looksLikeJson) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
// Guard: avoid unbounded parsing for pathological payloads.
|
|
165
|
+
if (text.length > 10 * 1024 * 1024) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const parsed = JSON.parse(text);
|
|
169
|
+
return (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) ? parsed : null;
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function tryDecodeJsonBodyFromStream(stream) {
|
|
176
|
+
// Peek the first chunk; if it looks like JSON (starts with `{` or `[`), consume full body and parse.
|
|
177
|
+
const iterator = stream[Symbol.asyncIterator]();
|
|
178
|
+
const first = await iterator.next();
|
|
179
|
+
if (first.done || first.value == null) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const firstChunk = first.value;
|
|
183
|
+
let prefix = (typeof firstChunk === 'string' ? firstChunk : firstChunk.toString('utf8')).trimStart();
|
|
184
|
+
prefix = prefix.replace(/^\)\]\}',?\s*/u, '');
|
|
185
|
+
prefix = prefix.replace(/^data:\s*/iu, '');
|
|
186
|
+
const looksLikeJson = prefix.startsWith('{') || prefix.startsWith('[');
|
|
187
|
+
if (!looksLikeJson) {
|
|
188
|
+
// Rewind by re-wrapping the iterator so downstream SSE decoder still sees the first chunk.
|
|
189
|
+
// eslint-disable-next-line no-param-reassign
|
|
190
|
+
stream[Symbol.asyncIterator] = () => replayIterator(firstChunk, iterator);
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
let body = typeof firstChunk === 'string' ? firstChunk : firstChunk.toString('utf8');
|
|
194
|
+
while (true) {
|
|
195
|
+
const next = await iterator.next();
|
|
196
|
+
if (next.done)
|
|
197
|
+
break;
|
|
198
|
+
body += typeof next.value === 'string' ? next.value : Buffer.from(next.value).toString('utf8');
|
|
199
|
+
// Guard: avoid unbounded buffering if the upstream is actually SSE but starts with whitespace.
|
|
200
|
+
if (body.length > 1024 * 1024) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
const parsed = JSON.parse(body);
|
|
206
|
+
return (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) ? parsed : null;
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function replayIterator(firstChunk, iterator) {
|
|
213
|
+
let yieldedFirst = false;
|
|
214
|
+
return {
|
|
215
|
+
async next() {
|
|
216
|
+
if (!yieldedFirst) {
|
|
217
|
+
yieldedFirst = true;
|
|
218
|
+
return { done: false, value: firstChunk };
|
|
219
|
+
}
|
|
220
|
+
return iterator.next();
|
|
221
|
+
},
|
|
222
|
+
async return(value) {
|
|
223
|
+
if (typeof iterator.return === 'function') {
|
|
224
|
+
return iterator.return(value);
|
|
225
|
+
}
|
|
226
|
+
return { done: true, value };
|
|
227
|
+
},
|
|
228
|
+
async throw(err) {
|
|
229
|
+
if (typeof iterator.throw === 'function') {
|
|
230
|
+
return iterator.throw(err);
|
|
231
|
+
}
|
|
232
|
+
throw err;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
123
236
|
function extractSseStream(payload) {
|
|
124
237
|
if (!payload || typeof payload !== 'object') {
|
|
125
238
|
return undefined;
|
|
@@ -64,6 +64,9 @@ export function applyTargetToSubject(subject, target, originalModel) {
|
|
|
64
64
|
metadata.processMode = target.processMode || 'chat';
|
|
65
65
|
subject.metadata = metadata;
|
|
66
66
|
const subjectMeta = metadata;
|
|
67
|
+
if (target.responsesConfig?.toolCallIdStyle) {
|
|
68
|
+
subjectMeta.toolCallIdStyle = target.responsesConfig.toolCallIdStyle;
|
|
69
|
+
}
|
|
67
70
|
if (originalModel && typeof originalModel === 'string' && originalModel.trim()) {
|
|
68
71
|
const trimmed = originalModel.trim();
|
|
69
72
|
if (typeof subjectMeta.originalModelId !== 'string' || !subjectMeta.originalModelId) {
|
|
@@ -118,6 +118,20 @@ function detectProviderResponseShape(payload) {
|
|
|
118
118
|
return 'gemini-chat';
|
|
119
119
|
return 'unknown';
|
|
120
120
|
}
|
|
121
|
+
function inferProviderTypeFromProtocol(protocol) {
|
|
122
|
+
const p = typeof protocol === 'string' ? protocol.trim().toLowerCase() : '';
|
|
123
|
+
if (!p)
|
|
124
|
+
return undefined;
|
|
125
|
+
if (p === 'openai-chat')
|
|
126
|
+
return 'openai';
|
|
127
|
+
if (p === 'openai-responses')
|
|
128
|
+
return 'responses';
|
|
129
|
+
if (p === 'anthropic-messages')
|
|
130
|
+
return 'anthropic';
|
|
131
|
+
if (p === 'gemini-chat')
|
|
132
|
+
return 'gemini';
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
121
135
|
function isCanonicalChatCompletion(payload) {
|
|
122
136
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
123
137
|
return false;
|
|
@@ -138,9 +152,18 @@ async function coerceClientPayloadToCanonicalChatCompletionOrThrow(options) {
|
|
|
138
152
|
}
|
|
139
153
|
const detected = detectProviderResponseShape(options.payload);
|
|
140
154
|
if (detected === 'unknown') {
|
|
155
|
+
const protocol = options.adapterContext?.providerProtocol;
|
|
141
156
|
throw new ProviderProtocolError(`[hub_response] Non-canonical response payload at ${options.scope}`, {
|
|
142
157
|
code: 'MALFORMED_RESPONSE',
|
|
143
|
-
|
|
158
|
+
protocol,
|
|
159
|
+
providerType: inferProviderTypeFromProtocol(protocol),
|
|
160
|
+
details: {
|
|
161
|
+
detected,
|
|
162
|
+
payloadType: typeof options.payload,
|
|
163
|
+
payloadKeys: options.payload && typeof options.payload === 'object' && !Array.isArray(options.payload)
|
|
164
|
+
? Object.keys(options.payload).slice(0, 20)
|
|
165
|
+
: undefined
|
|
166
|
+
}
|
|
144
167
|
});
|
|
145
168
|
}
|
|
146
169
|
const plan = PROVIDER_RESPONSE_REGISTRY[detected];
|
|
@@ -149,8 +172,11 @@ async function coerceClientPayloadToCanonicalChatCompletionOrThrow(options) {
|
|
|
149
172
|
if (isCanonicalChatCompletion(coerced)) {
|
|
150
173
|
return coerced;
|
|
151
174
|
}
|
|
175
|
+
const protocol = options.adapterContext?.providerProtocol;
|
|
152
176
|
throw new ProviderProtocolError(`[hub_response] Failed to canonicalize response payload at ${options.scope}`, {
|
|
153
177
|
code: 'MALFORMED_RESPONSE',
|
|
178
|
+
protocol,
|
|
179
|
+
providerType: inferProviderTypeFromProtocol(protocol),
|
|
154
180
|
details: { detected }
|
|
155
181
|
});
|
|
156
182
|
}
|
|
@@ -5,6 +5,7 @@ import { createToolCallIdTransformer, enforceToolCallIdStyle, resolveToolCallIdS
|
|
|
5
5
|
import { mapBridgeToolsToChat, mapChatToolsToBridge } from '../shared/tool-mapping.js';
|
|
6
6
|
import { ProviderProtocolError } from '../shared/errors.js';
|
|
7
7
|
import { readRuntimeMetadata } from '../shared/runtime-metadata.js';
|
|
8
|
+
import { clampResponsesInputItemId } from '../shared/bridge-id-utils.js';
|
|
8
9
|
// --- Utilities (ported strictly) ---
|
|
9
10
|
import { canonicalizeChatResponseTools } from '../shared/tool-canonicalizer.js';
|
|
10
11
|
import { normalizeMessageReasoningTools } from '../shared/reasoning-tool-normalizer.js';
|
|
@@ -14,15 +15,31 @@ import { buildResponsesOutputFromChat } from '../shared/responses-output-builder
|
|
|
14
15
|
function isObject(v) {
|
|
15
16
|
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
16
17
|
}
|
|
17
|
-
function filterBridgeInputForUpstream(input) {
|
|
18
|
+
function filterBridgeInputForUpstream(input, options) {
|
|
18
19
|
// Upstream `/v1/responses` create only accepts a subset of input item types.
|
|
19
20
|
// In particular, `type:"reasoning"` entries are output-only artifacts (often
|
|
20
21
|
// captured from previous responses) and OpenAI rejects them with schema errors
|
|
21
22
|
// like `input[N].content: array too long (max 0)`.
|
|
22
|
-
return (Array.isArray(input) ? input : []).
|
|
23
|
+
return (Array.isArray(input) ? input : []).flatMap((item) => {
|
|
23
24
|
if (!item || typeof item !== 'object')
|
|
24
|
-
return
|
|
25
|
-
|
|
25
|
+
return [];
|
|
26
|
+
if (item.type === 'reasoning') {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
// OpenAI `/v1/responses` request schema uses `call_id` for tool outputs.
|
|
30
|
+
// Some internal carriers may set `tool_call_id`; strip it before sending upstream
|
|
31
|
+
// to avoid strict schema errors (e.g. "Unknown parameter: input[N].tool_call_id").
|
|
32
|
+
const clone = { ...item };
|
|
33
|
+
if (options?.allowToolCallId !== true && clone.tool_call_id !== undefined) {
|
|
34
|
+
delete clone.tool_call_id;
|
|
35
|
+
}
|
|
36
|
+
// OpenAI /v1/responses enforces max length (64) on input item id fields.
|
|
37
|
+
// Keep this as the single outbound guardrail for all bridged input item types.
|
|
38
|
+
const normalizedId = clampResponsesInputItemId(clone.id);
|
|
39
|
+
if (normalizedId) {
|
|
40
|
+
clone.id = normalizedId;
|
|
41
|
+
}
|
|
42
|
+
return [clone];
|
|
26
43
|
});
|
|
27
44
|
}
|
|
28
45
|
// normalizeTools unified in ../shared/args-mapping.ts
|
|
@@ -356,7 +373,16 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
|
|
|
356
373
|
const envelopeMetadata = ctx?.metadata && typeof ctx.metadata === 'object' ? ctx.metadata : undefined;
|
|
357
374
|
const metadataExtraFields = extractMetadataExtraFields(envelopeMetadata);
|
|
358
375
|
const contextToolCallIdStyle = readToolCallIdStyleFromContext(ctx);
|
|
359
|
-
const
|
|
376
|
+
const envelopeToolCallIdStyle = resolveToolCallIdStyle(envelopeMetadata);
|
|
377
|
+
const requestMetadata = chat && typeof chat === 'object' && chat.metadata && typeof chat.metadata === 'object'
|
|
378
|
+
? chat.metadata
|
|
379
|
+
: undefined;
|
|
380
|
+
const routeToolCallIdStyle = requestMetadata
|
|
381
|
+
? normalizeToolCallIdStyleCandidate(requestMetadata.toolCallIdStyle)
|
|
382
|
+
: undefined;
|
|
383
|
+
// Route-selected toolCallIdStyle must win over captured context to prevent cross-provider leakage
|
|
384
|
+
// (e.g. LM Studio "preserve" contaminating OpenAI "fc").
|
|
385
|
+
const toolCallIdStyle = routeToolCallIdStyle ?? envelopeToolCallIdStyle ?? contextToolCallIdStyle;
|
|
360
386
|
const fallbackHistory = ctx?.input && Array.isArray(ctx.input)
|
|
361
387
|
? {
|
|
362
388
|
input: ctx.input,
|
|
@@ -392,7 +418,7 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
|
|
|
392
418
|
}
|
|
393
419
|
}
|
|
394
420
|
// 不追加 metadata,以便 roundtrip 与原始 payload 对齐;系统提示直接写入 instructions。
|
|
395
|
-
const upstreamInput = filterBridgeInputForUpstream(input);
|
|
421
|
+
const upstreamInput = filterBridgeInputForUpstream(input, { allowToolCallId: toolCallIdStyle === 'preserve' });
|
|
396
422
|
if (upstreamInput.length) {
|
|
397
423
|
out.input = upstreamInput;
|
|
398
424
|
}
|
|
@@ -15,6 +15,21 @@ function safeJson(v) {
|
|
|
15
15
|
return '{}';
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
+
function sanitizeToolUseId(raw) {
|
|
19
|
+
const trimmed = typeof raw === 'string' ? raw.trim() : '';
|
|
20
|
+
if (!trimmed) {
|
|
21
|
+
return `call_${Math.random().toString(36).slice(2, 10)}`;
|
|
22
|
+
}
|
|
23
|
+
if (/^[A-Za-z0-9_-]+$/.test(trimmed)) {
|
|
24
|
+
return trimmed;
|
|
25
|
+
}
|
|
26
|
+
const sanitized = trimmed
|
|
27
|
+
.replace(/[^A-Za-z0-9_-]/g, '_')
|
|
28
|
+
.replace(/_{2,}/g, '_')
|
|
29
|
+
.replace(/^_+/, '')
|
|
30
|
+
.replace(/_+$/, '');
|
|
31
|
+
return sanitized || `call_${Math.random().toString(36).slice(2, 10)}`;
|
|
32
|
+
}
|
|
18
33
|
function flattenAnthropicText(content) {
|
|
19
34
|
if (content == null)
|
|
20
35
|
return '';
|
|
@@ -477,7 +492,7 @@ export function buildAnthropicFromOpenAIChat(oa, options) {
|
|
|
477
492
|
const toolNameResolver = createAnthropicToolNameResolver(options?.toolNameMap ?? extractToolNameMapFromPayload(oa));
|
|
478
493
|
for (const tc of toolCalls) {
|
|
479
494
|
try {
|
|
480
|
-
const id = requireTrimmedString(tc?.id, 'chat.tool_call.id');
|
|
495
|
+
const id = sanitizeToolUseId(requireTrimmedString(tc?.id, 'chat.tool_call.id'));
|
|
481
496
|
const fn = isObject(tc?.function) ? tc.function : {};
|
|
482
497
|
const canonicalName = requireTrimmedString(fn.name, 'chat.tool_call.function.name');
|
|
483
498
|
const name = toolNameResolver ? toolNameResolver(canonicalName) : canonicalName;
|
|
@@ -752,7 +767,7 @@ export function buildAnthropicRequestFromOpenAIChat(chatReq) {
|
|
|
752
767
|
for (const tc of toolCalls) {
|
|
753
768
|
if (!tc || typeof tc !== 'object')
|
|
754
769
|
continue;
|
|
755
|
-
const id = requireTrimmedString(tc.id, 'chat.tool_call.id');
|
|
770
|
+
const id = sanitizeToolUseId(requireTrimmedString(tc.id, 'chat.tool_call.id'));
|
|
756
771
|
knownToolCallIds.add(id);
|
|
757
772
|
}
|
|
758
773
|
}
|
|
@@ -816,7 +831,7 @@ export function buildAnthropicRequestFromOpenAIChat(chatReq) {
|
|
|
816
831
|
continue;
|
|
817
832
|
}
|
|
818
833
|
if (role === 'tool') {
|
|
819
|
-
const toolCallId = requireTrimmedString(m.tool_call_id ?? m.call_id ?? m.tool_use_id ?? m.id, 'tool_result.tool_call_id');
|
|
834
|
+
const toolCallId = sanitizeToolUseId(requireTrimmedString(m.tool_call_id ?? m.call_id ?? m.tool_use_id ?? m.id, 'tool_result.tool_call_id'));
|
|
820
835
|
if (!knownToolCallIds.has(toolCallId)) {
|
|
821
836
|
throw new ProviderProtocolError(`Anthropic bridge constraint violated: tool result ${toolCallId} has no matching tool call`, {
|
|
822
837
|
code: 'TOOL_PROTOCOL_ERROR',
|
|
@@ -896,7 +911,7 @@ export function buildAnthropicRequestFromOpenAIChat(chatReq) {
|
|
|
896
911
|
for (const tc of toolCalls) {
|
|
897
912
|
if (!tc || typeof tc !== 'object')
|
|
898
913
|
continue;
|
|
899
|
-
const id = requireTrimmedString(tc.id, 'chat.tool_call.id');
|
|
914
|
+
const id = sanitizeToolUseId(requireTrimmedString(tc.id, 'chat.tool_call.id'));
|
|
900
915
|
const fn = tc.function || {};
|
|
901
916
|
const name = requireTrimmedString(fn.name, 'chat.tool_call.function.name');
|
|
902
917
|
const argsRaw = fn.arguments;
|
|
@@ -917,7 +932,7 @@ export function buildAnthropicRequestFromOpenAIChat(chatReq) {
|
|
|
917
932
|
if (blocks.length > 0) {
|
|
918
933
|
const hasStructuredBlocks = blocks.some((block) => block && typeof block === 'object' && block.type !== 'text');
|
|
919
934
|
let contentNode = blocks;
|
|
920
|
-
if (targetShape === 'string' ||
|
|
935
|
+
if (!hasStructuredBlocks && (targetShape === 'string' || !targetShape)) {
|
|
921
936
|
contentNode = text;
|
|
922
937
|
}
|
|
923
938
|
messages.push({ role, content: contentNode });
|
|
@@ -4,4 +4,6 @@ type NormalizeOptions = {
|
|
|
4
4
|
};
|
|
5
5
|
export declare function normalizeFunctionCallId(options: NormalizeOptions): string;
|
|
6
6
|
export declare function normalizeFunctionCallOutputId(options: NormalizeOptions): string;
|
|
7
|
+
export declare function normalizeResponsesCallId(options: NormalizeOptions): string;
|
|
8
|
+
export declare function clampResponsesInputItemId(raw: unknown): string | undefined;
|
|
7
9
|
export {};
|