@jsonstudio/llms 0.6.568 → 0.6.626
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/profiles/chat-gemini.json +15 -15
- package/dist/conversion/compat/profiles/chat-glm.json +194 -194
- package/dist/conversion/compat/profiles/chat-iflow.json +199 -199
- package/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
- package/dist/conversion/compat/profiles/chat-qwen.json +20 -20
- package/dist/conversion/compat/profiles/responses-c4m.json +42 -42
- package/dist/conversion/compat/profiles/responses-output2choices-test.json +9 -10
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +0 -1
- package/dist/conversion/hub/pipeline/hub-pipeline.js +68 -69
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +0 -34
- package/dist/conversion/hub/process/chat-process.js +37 -16
- package/dist/conversion/hub/response/provider-response.js +0 -8
- package/dist/conversion/hub/response/response-runtime.js +47 -1
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +59 -4
- package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +8 -0
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +93 -12
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +208 -31
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +280 -14
- package/dist/conversion/hub/standardized-bridge.js +11 -2
- package/dist/conversion/hub/types/chat-envelope.d.ts +10 -0
- package/dist/conversion/hub/types/standardized.d.ts +2 -1
- package/dist/conversion/responses/responses-openai-bridge.d.ts +3 -2
- package/dist/conversion/responses/responses-openai-bridge.js +1 -13
- package/dist/conversion/shared/text-markup-normalizer.d.ts +20 -0
- package/dist/conversion/shared/text-markup-normalizer.js +84 -5
- package/dist/conversion/shared/tool-filter-pipeline.d.ts +1 -1
- package/dist/conversion/shared/tool-filter-pipeline.js +54 -29
- package/dist/filters/index.d.ts +1 -0
- package/dist/filters/special/response-apply-patch-toon-decode.js +15 -7
- package/dist/filters/special/response-tool-arguments-toon-decode.js +108 -22
- package/dist/guidance/index.js +2 -0
- package/dist/router/virtual-router/classifier.js +16 -12
- package/dist/router/virtual-router/engine.js +45 -4
- package/dist/router/virtual-router/tool-signals.d.ts +2 -1
- package/dist/router/virtual-router/tool-signals.js +293 -134
- package/dist/router/virtual-router/types.d.ts +1 -1
- package/dist/router/virtual-router/types.js +1 -1
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +28 -4
- package/dist/sse/json-to-sse/event-generators/responses.js +9 -2
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +7 -3
- package/dist/tools/apply-patch-structured.js +4 -3
- package/package.json +2 -2
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
"id": "responses:output2choices-test",
|
|
3
|
+
"protocol": "openai-responses",
|
|
4
|
+
"response": {
|
|
5
|
+
"mappings": [
|
|
6
|
+
{
|
|
7
|
+
"action": "convert_responses_output_to_choices"
|
|
8
|
+
}
|
|
9
|
+
]
|
|
10
|
+
}
|
|
11
11
|
}
|
|
12
|
-
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Readable } from 'node:stream';
|
|
2
|
+
import { isJsonObject } from '../types/json.js';
|
|
2
3
|
import { VirtualRouterEngine } from '../../../router/virtual-router/engine.js';
|
|
3
4
|
import { providerErrorCenter } from '../../../router/virtual-router/error-center.js';
|
|
4
5
|
import { defaultSseCodecRegistry } from '../../../sse/index.js';
|
|
@@ -22,6 +23,7 @@ import { runReqOutboundStage1SemanticMap } from './stages/req_outbound/req_outbo
|
|
|
22
23
|
import { runReqOutboundStage2FormatBuild } from './stages/req_outbound/req_outbound_stage2_format_build/index.js';
|
|
23
24
|
import { runReqOutboundStage3Compat } from './stages/req_outbound/req_outbound_stage3_compat/index.js';
|
|
24
25
|
import { extractSessionIdentifiersFromMetadata } from './session-identifiers.js';
|
|
26
|
+
import { computeRequestTokens } from '../../../router/virtual-router/token-estimator.js';
|
|
25
27
|
export class HubPipeline {
|
|
26
28
|
routerEngine;
|
|
27
29
|
config;
|
|
@@ -119,6 +121,18 @@ export class HubPipeline {
|
|
|
119
121
|
}
|
|
120
122
|
}
|
|
121
123
|
const workingRequest = processedRequest ?? standardizedRequest;
|
|
124
|
+
// 使用与 VirtualRouter 一致的 tiktoken 计数逻辑,对标准化请求进行一次
|
|
125
|
+
// 上下文 token 估算,供后续 usage 归一化与统计使用。
|
|
126
|
+
try {
|
|
127
|
+
const estimatedTokens = computeRequestTokens(workingRequest, '');
|
|
128
|
+
if (typeof estimatedTokens === 'number' && Number.isFinite(estimatedTokens) && estimatedTokens > 0) {
|
|
129
|
+
normalized.metadata = normalized.metadata || {};
|
|
130
|
+
normalized.metadata.estimatedInputTokens = estimatedTokens;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// 估算失败不应影响主流程
|
|
135
|
+
}
|
|
122
136
|
const normalizedMeta = normalized.metadata;
|
|
123
137
|
const responsesResume = normalizedMeta && typeof normalizedMeta.responsesResume === 'object'
|
|
124
138
|
? normalizedMeta.responsesResume
|
|
@@ -182,66 +196,42 @@ export class HubPipeline {
|
|
|
182
196
|
const outboundEndpoint = resolveEndpointForProviderProtocol(outboundAdapterContext.providerProtocol);
|
|
183
197
|
const outboundRecorder = this.maybeCreateStageRecorder(outboundAdapterContext, outboundEndpoint);
|
|
184
198
|
const outboundStart = Date.now();
|
|
185
|
-
const isResponsesSubmit = normalized.entryEndpoint === '/v1/responses.submit_tool_outputs' &&
|
|
186
|
-
outboundProtocol === 'openai-responses';
|
|
187
199
|
let providerPayload;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
formatAdapter: outboundFormatAdapter,
|
|
221
|
-
stageRecorder: outboundRecorder
|
|
222
|
-
});
|
|
223
|
-
formattedPayload = await runReqOutboundStage3Compat({
|
|
224
|
-
payload: formattedPayload,
|
|
225
|
-
adapterContext: outboundAdapterContext,
|
|
226
|
-
stageRecorder: outboundRecorder
|
|
227
|
-
});
|
|
228
|
-
providerPayload = formattedPayload;
|
|
229
|
-
const outboundEnd = Date.now();
|
|
230
|
-
nodeResults.push({
|
|
231
|
-
id: 'req_outbound',
|
|
232
|
-
success: true,
|
|
233
|
-
metadata: {
|
|
234
|
-
node: 'req_outbound',
|
|
235
|
-
executionTime: outboundEnd - outboundStart,
|
|
236
|
-
startTime: outboundStart,
|
|
237
|
-
endTime: outboundEnd,
|
|
238
|
-
dataProcessed: {
|
|
239
|
-
messages: workingRequest.messages.length,
|
|
240
|
-
tools: workingRequest.tools?.length ?? 0
|
|
241
|
-
}
|
|
200
|
+
const outboundStage1 = await runReqOutboundStage1SemanticMap({
|
|
201
|
+
request: workingRequest,
|
|
202
|
+
adapterContext: outboundAdapterContext,
|
|
203
|
+
semanticMapper: outboundSemanticMapper,
|
|
204
|
+
contextSnapshot: outboundContextSnapshot,
|
|
205
|
+
contextMetadataKey: outboundContextMetadataKey,
|
|
206
|
+
stageRecorder: outboundRecorder
|
|
207
|
+
});
|
|
208
|
+
let formattedPayload = await runReqOutboundStage2FormatBuild({
|
|
209
|
+
formatEnvelope: outboundStage1.formatEnvelope,
|
|
210
|
+
adapterContext: outboundAdapterContext,
|
|
211
|
+
formatAdapter: outboundFormatAdapter,
|
|
212
|
+
stageRecorder: outboundRecorder
|
|
213
|
+
});
|
|
214
|
+
formattedPayload = await runReqOutboundStage3Compat({
|
|
215
|
+
payload: formattedPayload,
|
|
216
|
+
adapterContext: outboundAdapterContext,
|
|
217
|
+
stageRecorder: outboundRecorder
|
|
218
|
+
});
|
|
219
|
+
providerPayload = formattedPayload;
|
|
220
|
+
const outboundEnd = Date.now();
|
|
221
|
+
nodeResults.push({
|
|
222
|
+
id: 'req_outbound',
|
|
223
|
+
success: true,
|
|
224
|
+
metadata: {
|
|
225
|
+
node: 'req_outbound',
|
|
226
|
+
executionTime: outboundEnd - outboundStart,
|
|
227
|
+
startTime: outboundStart,
|
|
228
|
+
endTime: outboundEnd,
|
|
229
|
+
dataProcessed: {
|
|
230
|
+
messages: workingRequest.messages.length,
|
|
231
|
+
tools: workingRequest.tools?.length ?? 0
|
|
242
232
|
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
233
|
+
}
|
|
234
|
+
});
|
|
245
235
|
// 为响应侧 servertool/web_search 提供一次性 Chat 请求快照,便于在 Hub 内部实现
|
|
246
236
|
// 第三跳(将工具结果注入消息历史后重新调用主模型)。
|
|
247
237
|
//
|
|
@@ -322,7 +312,11 @@ export class HubPipeline {
|
|
|
322
312
|
const contextNode = metadataNode && metadataNode.context && typeof metadataNode.context === 'object'
|
|
323
313
|
? metadataNode.context
|
|
324
314
|
: undefined;
|
|
325
|
-
|
|
315
|
+
const fromContextNode = coerceAliasMap(contextNode?.anthropicToolNameMap);
|
|
316
|
+
if (fromContextNode) {
|
|
317
|
+
return fromContextNode;
|
|
318
|
+
}
|
|
319
|
+
return readAliasMapFromSemantics(chatEnvelope);
|
|
326
320
|
}
|
|
327
321
|
resolveProtocolHooks(protocol) {
|
|
328
322
|
switch (protocol) {
|
|
@@ -409,6 +403,11 @@ export class HubPipeline {
|
|
|
409
403
|
if (responsesResume) {
|
|
410
404
|
adapterContext.responsesResume = responsesResume;
|
|
411
405
|
}
|
|
406
|
+
// 透传 gemini_empty_reply_continue 的重试计数,便于在多次空回复后终止自动续写。
|
|
407
|
+
const emptyReplyCount = metadata.geminiEmptyReplyCount;
|
|
408
|
+
if (typeof emptyReplyCount === 'number' && Number.isFinite(emptyReplyCount)) {
|
|
409
|
+
adapterContext.geminiEmptyReplyCount = emptyReplyCount;
|
|
410
|
+
}
|
|
412
411
|
if (target?.compatibilityProfile && typeof target.compatibilityProfile === 'string') {
|
|
413
412
|
adapterContext.compatibilityProfile = target.compatibilityProfile;
|
|
414
413
|
}
|
|
@@ -432,16 +431,6 @@ export class HubPipeline {
|
|
|
432
431
|
}
|
|
433
432
|
return value;
|
|
434
433
|
}
|
|
435
|
-
pickRawRequestBody(metadata) {
|
|
436
|
-
if (!metadata || typeof metadata !== 'object') {
|
|
437
|
-
return undefined;
|
|
438
|
-
}
|
|
439
|
-
const raw = metadata.__raw_request_body;
|
|
440
|
-
if (!raw || typeof raw !== 'object') {
|
|
441
|
-
return undefined;
|
|
442
|
-
}
|
|
443
|
-
return raw;
|
|
444
|
-
}
|
|
445
434
|
async normalizeRequest(request) {
|
|
446
435
|
if (!request || typeof request !== 'object') {
|
|
447
436
|
throw new Error('HubPipeline requires request payload');
|
|
@@ -695,3 +684,13 @@ function coerceAliasMap(candidate) {
|
|
|
695
684
|
}
|
|
696
685
|
return Object.keys(normalized).length ? normalized : undefined;
|
|
697
686
|
}
|
|
687
|
+
function readAliasMapFromSemantics(chatEnvelope) {
|
|
688
|
+
if (!chatEnvelope?.semantics || typeof chatEnvelope.semantics !== 'object') {
|
|
689
|
+
return undefined;
|
|
690
|
+
}
|
|
691
|
+
const node = chatEnvelope.semantics.anthropic;
|
|
692
|
+
if (!node || !isJsonObject(node)) {
|
|
693
|
+
return undefined;
|
|
694
|
+
}
|
|
695
|
+
return coerceAliasMap(node.toolAliasMap);
|
|
696
|
+
}
|
package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js
CHANGED
|
@@ -15,7 +15,6 @@ export function runRespOutboundStage1ClientRemap(options) {
|
|
|
15
15
|
clientPayload = buildResponsesPayloadFromChat(options.payload, {
|
|
16
16
|
requestId: options.requestId
|
|
17
17
|
});
|
|
18
|
-
mergeOriginalResponsesPayload(clientPayload, options.adapterContext);
|
|
19
18
|
}
|
|
20
19
|
recordStage(options.stageRecorder, 'resp_outbound_stage1_client_remap', clientPayload);
|
|
21
20
|
return clientPayload;
|
|
@@ -42,36 +41,3 @@ function resolveAliasMapFromContext(adapterContext) {
|
|
|
42
41
|
}
|
|
43
42
|
return Object.keys(map).length ? map : undefined;
|
|
44
43
|
}
|
|
45
|
-
function mergeOriginalResponsesPayload(payload, adapterContext) {
|
|
46
|
-
if (!adapterContext) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
const raw = adapterContext.__raw_responses_payload;
|
|
50
|
-
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
try {
|
|
54
|
-
if (payload.required_action == null && raw.required_action != null) {
|
|
55
|
-
payload.required_action = JSON.parse(JSON.stringify(raw.required_action));
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
/* ignore clone errors */
|
|
60
|
-
}
|
|
61
|
-
const rawStatus = typeof raw.status === 'string' ? raw.status : undefined;
|
|
62
|
-
if (rawStatus === 'requires_action') {
|
|
63
|
-
payload.status = 'requires_action';
|
|
64
|
-
}
|
|
65
|
-
// 如果桥接后的 payload 没有 usage,而原始 Responses 载荷带有 usage,则回填原始 usage,
|
|
66
|
-
// 确保 token usage 不在工具/桥接路径中丢失。
|
|
67
|
-
const payloadUsage = payload.usage;
|
|
68
|
-
const rawUsage = raw.usage;
|
|
69
|
-
if ((payloadUsage == null || typeof payloadUsage !== 'object') && rawUsage && typeof rawUsage === 'object') {
|
|
70
|
-
try {
|
|
71
|
-
payload.usage = JSON.parse(JSON.stringify(rawUsage));
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
payload.usage = rawUsage;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { runChatRequestToolFilters } from '../../shared/tool-filter-pipeline.js';
|
|
2
2
|
import { ToolGovernanceEngine } from '../tool-governance/index.js';
|
|
3
|
-
import { detectLastAssistantToolCategory } from '../../../router/virtual-router/tool-signals.js';
|
|
4
3
|
import { ensureApplyPatchSchema } from '../../shared/tool-mapping.js';
|
|
4
|
+
import { isJsonObject } from '../types/json.js';
|
|
5
5
|
const toolGovernanceEngine = new ToolGovernanceEngine();
|
|
6
6
|
export async function runHubChatProcess(options) {
|
|
7
7
|
const startTime = Date.now();
|
|
@@ -404,24 +404,21 @@ function maybeInjectWebSearchTool(request, metadata) {
|
|
|
404
404
|
if (!rawConfig || !Array.isArray(rawConfig.engines) || rawConfig.engines.length === 0) {
|
|
405
405
|
return request;
|
|
406
406
|
}
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
407
|
+
const semanticsWebSearch = extractWebSearchSemantics(request.semantics);
|
|
408
|
+
if (semanticsWebSearch?.disable === true) {
|
|
409
|
+
return request;
|
|
410
|
+
}
|
|
411
|
+
const injectPolicy = semanticsWebSearch?.force === true
|
|
412
|
+
? 'always'
|
|
413
|
+
: rawConfig.injectPolicy === 'always' || rawConfig.injectPolicy === 'selective'
|
|
414
|
+
? rawConfig.injectPolicy
|
|
415
|
+
: 'selective';
|
|
410
416
|
const intent = detectWebSearchIntent(request);
|
|
411
417
|
if (injectPolicy === 'selective') {
|
|
418
|
+
// 仅当当前这一轮用户输入明确表达“联网搜索”意图时才注入 web_search。
|
|
419
|
+
// 不再依赖上一轮工具分类(read/search/websearch),避免形成隐式续写语义。
|
|
412
420
|
if (!intent.hasIntent) {
|
|
413
|
-
|
|
414
|
-
// 如果上一轮 assistant 的工具调用已经属于搜索类(如 web_search),
|
|
415
|
-
// 则仍然视为 web_search 续写场景,强制注入 web_search 工具,
|
|
416
|
-
// 以便在后续路由中按 servertool 逻辑跳过不适配的 Provider(例如 serverToolsDisabled 的 crs)。
|
|
417
|
-
const assistantMessages = Array.isArray(request.messages)
|
|
418
|
-
? request.messages.filter((msg) => msg && msg.role === 'assistant')
|
|
419
|
-
: [];
|
|
420
|
-
const lastTool = detectLastAssistantToolCategory(assistantMessages);
|
|
421
|
-
const hasSearchToolContext = lastTool?.category === 'search';
|
|
422
|
-
if (!hasSearchToolContext) {
|
|
423
|
-
return request;
|
|
424
|
-
}
|
|
421
|
+
return request;
|
|
425
422
|
}
|
|
426
423
|
}
|
|
427
424
|
const existingTools = Array.isArray(request.tools) ? request.tools : [];
|
|
@@ -522,6 +519,30 @@ function maybeInjectWebSearchTool(request, metadata) {
|
|
|
522
519
|
tools: [...existingTools, webSearchTool]
|
|
523
520
|
};
|
|
524
521
|
}
|
|
522
|
+
function extractWebSearchSemantics(semantics) {
|
|
523
|
+
if (!semantics || typeof semantics !== 'object') {
|
|
524
|
+
return undefined;
|
|
525
|
+
}
|
|
526
|
+
const extras = semantics.providerExtras;
|
|
527
|
+
if (!extras || !isJsonObject(extras)) {
|
|
528
|
+
return undefined;
|
|
529
|
+
}
|
|
530
|
+
const hint = extras.webSearch;
|
|
531
|
+
if (typeof hint === 'boolean') {
|
|
532
|
+
return hint ? { force: true } : { disable: true };
|
|
533
|
+
}
|
|
534
|
+
if (isJsonObject(hint)) {
|
|
535
|
+
const normalized = {};
|
|
536
|
+
if (hint.force === true) {
|
|
537
|
+
normalized.force = true;
|
|
538
|
+
}
|
|
539
|
+
if (hint.disable === true) {
|
|
540
|
+
normalized.disable = true;
|
|
541
|
+
}
|
|
542
|
+
return Object.keys(normalized).length ? normalized : undefined;
|
|
543
|
+
}
|
|
544
|
+
return undefined;
|
|
545
|
+
}
|
|
525
546
|
function detectWebSearchIntent(request) {
|
|
526
547
|
const messages = Array.isArray(request.messages) ? request.messages : [];
|
|
527
548
|
if (!messages.length) {
|
|
@@ -130,14 +130,6 @@ export async function convertProviderResponse(options) {
|
|
|
130
130
|
catch {
|
|
131
131
|
// ignore conversation capture errors
|
|
132
132
|
}
|
|
133
|
-
if (formatEnvelope.payload && typeof formatEnvelope.payload === 'object') {
|
|
134
|
-
try {
|
|
135
|
-
options.context.__raw_responses_payload = JSON.parse(JSON.stringify(formatEnvelope.payload));
|
|
136
|
-
}
|
|
137
|
-
catch {
|
|
138
|
-
/* best-effort clone */
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
133
|
}
|
|
142
134
|
formatEnvelope.payload = runRespInboundStageCompatResponse({
|
|
143
135
|
payload: formatEnvelope.payload,
|
|
@@ -21,6 +21,15 @@ function flattenAnthropicContent(content) {
|
|
|
21
21
|
}
|
|
22
22
|
return '';
|
|
23
23
|
}
|
|
24
|
+
function sanitizeAnthropicToolUseId(raw) {
|
|
25
|
+
if (typeof raw === 'string') {
|
|
26
|
+
const trimmed = raw.trim();
|
|
27
|
+
if (trimmed && /^[A-Za-z0-9_-]+$/.test(trimmed)) {
|
|
28
|
+
return trimmed;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return `call_${Math.random().toString(36).slice(2, 10)}`;
|
|
32
|
+
}
|
|
24
33
|
function createToolNameResolver(options) {
|
|
25
34
|
const reverse = new Map();
|
|
26
35
|
const aliasMap = options?.aliasMap;
|
|
@@ -281,6 +290,39 @@ export function buildOpenAIChatFromAnthropicMessage(payload, options) {
|
|
|
281
290
|
}
|
|
282
291
|
return chatResponse;
|
|
283
292
|
}
|
|
293
|
+
function mapShellCommandArgsForAnthropic(raw) {
|
|
294
|
+
const result = {};
|
|
295
|
+
const source = (raw && typeof raw === 'object' && !Array.isArray(raw)) ? raw : {};
|
|
296
|
+
const commandRaw = typeof source.command === 'string' && source.command.trim().length
|
|
297
|
+
? source.command
|
|
298
|
+
: typeof source.cmd === 'string' && source.cmd.trim().length
|
|
299
|
+
? source.cmd
|
|
300
|
+
: '';
|
|
301
|
+
const command = commandRaw.trim();
|
|
302
|
+
if (command) {
|
|
303
|
+
result.command = command;
|
|
304
|
+
}
|
|
305
|
+
const timeoutRaw = source.timeout_ms ?? source.timeout;
|
|
306
|
+
if (typeof timeoutRaw === 'number' && Number.isFinite(timeoutRaw)) {
|
|
307
|
+
result.timeout = timeoutRaw;
|
|
308
|
+
}
|
|
309
|
+
else if (typeof timeoutRaw === 'string' && timeoutRaw.trim().length) {
|
|
310
|
+
const parsed = Number(timeoutRaw.trim());
|
|
311
|
+
if (Number.isFinite(parsed)) {
|
|
312
|
+
result.timeout = parsed;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (typeof source.description === 'string' && source.description.trim().length) {
|
|
316
|
+
result.description = source.description;
|
|
317
|
+
}
|
|
318
|
+
if (typeof source.run_in_background === 'boolean') {
|
|
319
|
+
result.run_in_background = source.run_in_background;
|
|
320
|
+
}
|
|
321
|
+
if (typeof source.dangerouslyDisableSandbox === 'boolean') {
|
|
322
|
+
result.dangerouslyDisableSandbox = source.dangerouslyDisableSandbox;
|
|
323
|
+
}
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
284
326
|
export function buildAnthropicResponseFromChat(chatResponse, options) {
|
|
285
327
|
const choice = Array.isArray(chatResponse?.choices) ? chatResponse.choices[0] : undefined;
|
|
286
328
|
const message = choice && typeof choice === 'object' ? choice.message : undefined;
|
|
@@ -327,6 +369,7 @@ export function buildAnthropicResponseFromChat(chatResponse, options) {
|
|
|
327
369
|
const fn = call.function || {};
|
|
328
370
|
if (typeof fn?.name !== 'string')
|
|
329
371
|
continue;
|
|
372
|
+
const canonicalName = normalizeAnthropicToolName(fn.name) ?? fn.name;
|
|
330
373
|
const serializedName = outboundAliasSerializer(fn.name);
|
|
331
374
|
let parsedArgs = {};
|
|
332
375
|
const args = fn.arguments;
|
|
@@ -341,9 +384,12 @@ export function buildAnthropicResponseFromChat(chatResponse, options) {
|
|
|
341
384
|
else {
|
|
342
385
|
parsedArgs = args ?? {};
|
|
343
386
|
}
|
|
387
|
+
if ((canonicalName || '').trim() === 'shell_command') {
|
|
388
|
+
parsedArgs = mapShellCommandArgsForAnthropic(parsedArgs);
|
|
389
|
+
}
|
|
344
390
|
contentBlocks.push({
|
|
345
391
|
type: 'tool_use',
|
|
346
|
-
id:
|
|
392
|
+
id: sanitizeAnthropicToolUseId(call.id),
|
|
347
393
|
name: serializedName,
|
|
348
394
|
input: parsedArgs
|
|
349
395
|
});
|
|
@@ -34,6 +34,43 @@ const ANTHROPIC_TOP_LEVEL_FIELDS = new Set([
|
|
|
34
34
|
]);
|
|
35
35
|
const PASSTHROUGH_METADATA_PREFIX = 'rcc_passthrough_';
|
|
36
36
|
const PASSTHROUGH_PARAMETERS = ['tool_choice'];
|
|
37
|
+
function ensureSemantics(chat) {
|
|
38
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
39
|
+
chat.semantics = {};
|
|
40
|
+
}
|
|
41
|
+
return chat.semantics;
|
|
42
|
+
}
|
|
43
|
+
function ensureAnthropicSemanticsNode(chat) {
|
|
44
|
+
const semantics = ensureSemantics(chat);
|
|
45
|
+
if (!semantics.anthropic || !isJsonObject(semantics.anthropic)) {
|
|
46
|
+
semantics.anthropic = {};
|
|
47
|
+
}
|
|
48
|
+
return semantics.anthropic;
|
|
49
|
+
}
|
|
50
|
+
function markExplicitEmptyTools(chat) {
|
|
51
|
+
const semantics = ensureSemantics(chat);
|
|
52
|
+
if (!semantics.tools || !isJsonObject(semantics.tools)) {
|
|
53
|
+
semantics.tools = {};
|
|
54
|
+
}
|
|
55
|
+
semantics.tools.explicitEmpty = true;
|
|
56
|
+
}
|
|
57
|
+
function readAnthropicSemantics(chat) {
|
|
58
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
const node = chat.semantics.anthropic;
|
|
62
|
+
return node && isJsonObject(node) ? node : undefined;
|
|
63
|
+
}
|
|
64
|
+
function hasExplicitEmptyToolsSemantics(chat) {
|
|
65
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const toolsNode = chat.semantics.tools;
|
|
69
|
+
if (!toolsNode || !isJsonObject(toolsNode)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return Boolean(toolsNode.explicitEmpty);
|
|
73
|
+
}
|
|
37
74
|
function sanitizeAnthropicPayload(payload) {
|
|
38
75
|
for (const key of Object.keys(payload)) {
|
|
39
76
|
if (!ANTHROPIC_TOP_LEVEL_FIELDS.has(key)) {
|
|
@@ -91,6 +128,7 @@ export class AnthropicSemanticMapper {
|
|
|
91
128
|
const metadata = chatEnvelope.metadata ?? { context: canonicalContext };
|
|
92
129
|
chatEnvelope.metadata = metadata;
|
|
93
130
|
metadata.context = canonicalContext;
|
|
131
|
+
let semanticsNode;
|
|
94
132
|
const resolveExtraFields = () => {
|
|
95
133
|
if (!isJsonObject(metadata.extraFields)) {
|
|
96
134
|
metadata.extraFields = {};
|
|
@@ -101,10 +139,13 @@ export class AnthropicSemanticMapper {
|
|
|
101
139
|
const systemBlocks = cloneAnthropicSystemBlocks(payload.system);
|
|
102
140
|
if (systemBlocks) {
|
|
103
141
|
protocolState.systemBlocks = systemBlocks;
|
|
142
|
+
semanticsNode = semanticsNode ?? ensureAnthropicSemanticsNode(chatEnvelope);
|
|
143
|
+
semanticsNode.systemBlocks = jsonClone(systemBlocks);
|
|
104
144
|
}
|
|
105
145
|
if (payload.tools && Array.isArray(payload.tools) && payload.tools.length === 0) {
|
|
106
146
|
metadata.toolsFieldPresent = true;
|
|
107
147
|
resolveExtraFields().toolsFieldPresent = true;
|
|
148
|
+
markExplicitEmptyTools(chatEnvelope);
|
|
108
149
|
}
|
|
109
150
|
const aliasMap = buildAnthropicToolAliasMap(payload.tools);
|
|
110
151
|
if (aliasMap) {
|
|
@@ -113,6 +154,8 @@ export class AnthropicSemanticMapper {
|
|
|
113
154
|
canonicalContext.anthropicToolNameMap = aliasMap;
|
|
114
155
|
metadata.anthropicToolNameMap = aliasMap;
|
|
115
156
|
extraFields.anthropicToolNameMap = aliasMap;
|
|
157
|
+
semanticsNode = semanticsNode ?? ensureAnthropicSemanticsNode(chatEnvelope);
|
|
158
|
+
semanticsNode.toolAliasMap = jsonClone(aliasMap);
|
|
116
159
|
}
|
|
117
160
|
if (Array.isArray(payload.messages) && payload.messages.length) {
|
|
118
161
|
const shapes = payload.messages.map((entry) => {
|
|
@@ -137,6 +180,8 @@ export class AnthropicSemanticMapper {
|
|
|
137
180
|
: {};
|
|
138
181
|
mirrorNode.messageContentShape = shapes;
|
|
139
182
|
extraFields.anthropicMirror = mirrorNode;
|
|
183
|
+
semanticsNode = semanticsNode ?? ensureAnthropicSemanticsNode(chatEnvelope);
|
|
184
|
+
semanticsNode.mirror = jsonClone(mirrorNode);
|
|
140
185
|
}
|
|
141
186
|
if (missing.length) {
|
|
142
187
|
metadata.missingFields = Array.isArray(metadata.missingFields)
|
|
@@ -147,6 +192,8 @@ export class AnthropicSemanticMapper {
|
|
|
147
192
|
(payload.metadata && isJsonObject(payload.metadata) ? jsonClone(payload.metadata) : undefined);
|
|
148
193
|
if (providerMetadata) {
|
|
149
194
|
metadata.providerMetadata = providerMetadata;
|
|
195
|
+
semanticsNode = semanticsNode ?? ensureAnthropicSemanticsNode(chatEnvelope);
|
|
196
|
+
semanticsNode.providerMetadata = jsonClone(providerMetadata);
|
|
150
197
|
}
|
|
151
198
|
const mergedParameters = { ...(chatEnvelope.parameters ?? {}) };
|
|
152
199
|
const mergeParameters = (source) => {
|
|
@@ -217,6 +264,8 @@ export class AnthropicSemanticMapper {
|
|
|
217
264
|
messages: chat.messages,
|
|
218
265
|
tools: chat.tools
|
|
219
266
|
};
|
|
267
|
+
const semanticsNode = readAnthropicSemantics(chat);
|
|
268
|
+
const explicitEmptyTools = (chat.metadata?.toolsFieldPresent === true) || hasExplicitEmptyToolsSemantics(chat);
|
|
220
269
|
const trimmedParameters = chat.parameters && typeof chat.parameters === 'object' ? chat.parameters : undefined;
|
|
221
270
|
if (trimmedParameters) {
|
|
222
271
|
for (const [key, value] of Object.entries(trimmedParameters)) {
|
|
@@ -245,16 +294,19 @@ export class AnthropicSemanticMapper {
|
|
|
245
294
|
if (baseRequest.max_output_tokens && !baseRequest.max_tokens) {
|
|
246
295
|
baseRequest.max_tokens = baseRequest.max_output_tokens;
|
|
247
296
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (
|
|
297
|
+
// 出站阶段不再直接透传其它协议的 providerMetadata,避免跨协议打洞;
|
|
298
|
+
// Anthropic 自身入口的 metadata 已在入站阶段通过 collectParameters/encodeMetadataPassthrough
|
|
299
|
+
// 按白名单收集,这里仅依赖这些显式映射结果。
|
|
300
|
+
if (explicitEmptyTools && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
252
301
|
baseRequest.tools = [];
|
|
253
302
|
}
|
|
254
303
|
const protocolState = getProtocolState(chat.metadata, 'anthropic');
|
|
255
304
|
if (protocolState?.systemBlocks !== undefined) {
|
|
256
305
|
baseRequest.system = jsonClone(protocolState.systemBlocks);
|
|
257
306
|
}
|
|
307
|
+
else if (semanticsNode?.systemBlocks !== undefined) {
|
|
308
|
+
baseRequest.system = jsonClone(semanticsNode.systemBlocks);
|
|
309
|
+
}
|
|
258
310
|
if (chat.metadata &&
|
|
259
311
|
typeof chat.metadata === 'object' &&
|
|
260
312
|
chat.metadata.extraFields &&
|
|
@@ -262,6 +314,9 @@ export class AnthropicSemanticMapper {
|
|
|
262
314
|
chat.metadata.extraFields.anthropicMirror) {
|
|
263
315
|
baseRequest.__anthropicMirror = jsonClone(chat.metadata.extraFields.anthropicMirror ?? {});
|
|
264
316
|
}
|
|
317
|
+
else if (semanticsNode?.mirror && isJsonObject(semanticsNode.mirror)) {
|
|
318
|
+
baseRequest.__anthropicMirror = jsonClone(semanticsNode.mirror);
|
|
319
|
+
}
|
|
265
320
|
const payloadSource = buildAnthropicRequestFromOpenAIChat(baseRequest);
|
|
266
321
|
const payload = sanitizeAnthropicPayload(JSON.parse(JSON.stringify(payloadSource)));
|
|
267
322
|
if (chat.metadata?.toolsFieldPresent && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SemanticMapper } from '../format-adapters/index.js';
|
|
2
|
+
import type { AdapterContext, ChatEnvelope } from '../types/chat-envelope.js';
|
|
3
|
+
import type { FormatEnvelope } from '../types/format-envelope.js';
|
|
4
|
+
export declare function maybeAugmentApplyPatchErrorContent(content: string, toolName?: string): string;
|
|
5
|
+
export declare class ChatSemanticMapper implements SemanticMapper {
|
|
6
|
+
toChat(format: FormatEnvelope, ctx: AdapterContext): Promise<ChatEnvelope>;
|
|
7
|
+
fromChat(chat: ChatEnvelope, ctx: AdapterContext): Promise<FormatEnvelope>;
|
|
8
|
+
}
|