@jsonstudio/llms 0.6.938 → 0.6.1164
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/hub/operation-table/operation-table-runner.d.ts +18 -0
- package/dist/conversion/hub/operation-table/operation-table-runner.js +158 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.d.ts +8 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +303 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.d.ts +8 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +413 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.d.ts +7 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +841 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.d.ts +21 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +535 -0
- package/dist/conversion/hub/ops/operations.d.ts +19 -0
- package/dist/conversion/hub/ops/operations.js +126 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +9 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +533 -24
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +6 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +6 -3
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +11 -0
- package/dist/conversion/hub/policy/policy-engine.js +41 -9
- package/dist/conversion/hub/policy/protocol-spec.d.ts +25 -0
- package/dist/conversion/hub/policy/protocol-spec.js +73 -23
- package/dist/conversion/hub/process/chat-process.js +252 -41
- package/dist/conversion/hub/response/provider-response.js +175 -2
- package/dist/conversion/hub/response/response-runtime.js +1 -1
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +1 -8
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +1 -365
- package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +1 -8
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +1 -436
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +1 -7
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +1 -894
- package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +1 -21
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +1 -593
- package/dist/conversion/hub/tool-surface/tool-surface-engine.d.ts +18 -0
- package/dist/conversion/hub/tool-surface/tool-surface-engine.js +571 -0
- package/dist/conversion/responses/responses-openai-bridge.js +14 -2
- package/dist/conversion/shared/bridge-message-utils.js +2 -8
- package/dist/conversion/shared/bridge-policies.js +5 -105
- package/dist/conversion/shared/gemini-tool-utils.js +121 -4
- package/dist/conversion/shared/protocol-field-allowlists.d.ts +7 -0
- package/dist/conversion/shared/protocol-field-allowlists.js +145 -0
- package/dist/conversion/shared/reasoning-tool-normalizer.js +4 -2
- package/dist/conversion/shared/snapshot-hooks.js +166 -3
- package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
- package/dist/conversion/shared/text-markup-normalizer.js +345 -9
- package/dist/conversion/shared/thought-signature-validator.d.ts +52 -0
- package/dist/conversion/shared/thought-signature-validator.js +170 -0
- package/dist/conversion/shared/tool-argument-repairer.d.ts +39 -0
- package/dist/conversion/shared/tool-argument-repairer.js +56 -0
- package/dist/conversion/shared/tool-call-id-manager.d.ts +113 -0
- package/dist/conversion/shared/tool-call-id-manager.js +231 -0
- package/dist/conversion/shared/tool-canonicalizer.js +2 -11
- package/dist/router/virtual-router/bootstrap.js +54 -5
- package/dist/router/virtual-router/engine-selection.js +132 -42
- package/dist/router/virtual-router/engine.d.ts +3 -0
- package/dist/router/virtual-router/engine.js +142 -33
- package/dist/router/virtual-router/health-weighted.d.ts +25 -0
- package/dist/router/virtual-router/health-weighted.js +63 -0
- package/dist/router/virtual-router/load-balancer.d.ts +2 -0
- package/dist/router/virtual-router/load-balancer.js +45 -16
- package/dist/router/virtual-router/routing-instructions.js +17 -1
- package/dist/router/virtual-router/sticky-session-store.js +136 -24
- package/dist/router/virtual-router/stop-message-file-resolver.d.ts +1 -0
- package/dist/router/virtual-router/stop-message-file-resolver.js +74 -0
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +15 -0
- package/dist/router/virtual-router/stop-message-state-sync.js +57 -0
- package/dist/router/virtual-router/types.d.ts +70 -0
- package/dist/servertool/clock/config.d.ts +7 -0
- package/dist/servertool/clock/config.js +27 -0
- package/dist/servertool/clock/daemon.d.ts +3 -0
- package/dist/servertool/clock/daemon.js +79 -0
- package/dist/servertool/clock/io.d.ts +2 -0
- package/dist/servertool/clock/io.js +13 -0
- package/dist/servertool/clock/paths.d.ts +4 -0
- package/dist/servertool/clock/paths.js +25 -0
- package/dist/servertool/clock/session-store.d.ts +3 -0
- package/dist/servertool/clock/session-store.js +56 -0
- package/dist/servertool/clock/state.d.ts +5 -0
- package/dist/servertool/clock/state.js +62 -0
- package/dist/servertool/clock/task-store.d.ts +5 -0
- package/dist/servertool/clock/task-store.js +4 -0
- package/dist/servertool/clock/tasks.d.ts +17 -0
- package/dist/servertool/clock/tasks.js +221 -0
- package/dist/servertool/clock/types.d.ts +36 -0
- package/dist/servertool/clock/types.js +1 -0
- package/dist/servertool/engine.d.ts +2 -0
- package/dist/servertool/engine.js +164 -8
- package/dist/servertool/followup-shadow.d.ts +16 -0
- package/dist/servertool/followup-shadow.js +145 -0
- package/dist/servertool/handlers/apply-patch-guard.js +1 -265
- package/dist/servertool/handlers/clock-auto.d.ts +1 -0
- package/dist/servertool/handlers/clock-auto.js +160 -0
- package/dist/servertool/handlers/clock.d.ts +1 -0
- package/dist/servertool/handlers/clock.js +197 -0
- package/dist/servertool/handlers/exec-command-guard.js +7 -555
- package/dist/servertool/handlers/followup-request-builder.d.ts +15 -7
- package/dist/servertool/handlers/followup-request-builder.js +248 -28
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +62 -169
- package/dist/servertool/handlers/iflow-model-error-retry.js +18 -28
- package/dist/servertool/handlers/recursive-detection-guard.d.ts +1 -0
- package/dist/servertool/handlers/recursive-detection-guard.js +333 -0
- package/dist/servertool/handlers/stop-message-auto.js +47 -175
- package/dist/servertool/handlers/vision.d.ts +7 -1
- package/dist/servertool/handlers/vision.js +61 -117
- package/dist/servertool/handlers/web-search.d.ts +7 -1
- package/dist/servertool/handlers/web-search.js +122 -105
- package/dist/servertool/reenter-backend.d.ts +23 -0
- package/dist/servertool/reenter-backend.js +18 -0
- package/dist/servertool/server-side-tools.d.ts +3 -2
- package/dist/servertool/server-side-tools.js +64 -10
- package/dist/servertool/types.d.ts +92 -3
- package/dist/sse/json-to-sse/event-generators/responses.js +3 -21
- package/dist/sse/shared/serializers/responses-event-serializer.d.ts +8 -0
- package/dist/sse/shared/serializers/responses-event-serializer.js +19 -0
- package/dist/sse/shared/writer.js +24 -7
- package/dist/tools/apply-patch/execution-capturer.js +3 -1
- package/dist/tools/apply-patch/json/parse-loose.d.ts +3 -0
- package/dist/tools/apply-patch/json/parse-loose.js +139 -0
- package/dist/tools/apply-patch/patch-text/context-diff.d.ts +1 -0
- package/dist/tools/apply-patch/patch-text/context-diff.js +173 -0
- package/dist/tools/apply-patch/patch-text/git-diff.d.ts +1 -0
- package/dist/tools/apply-patch/patch-text/git-diff.js +138 -0
- package/dist/tools/apply-patch/patch-text/looks-like-patch.d.ts +1 -0
- package/dist/tools/apply-patch/patch-text/looks-like-patch.js +13 -0
- package/dist/tools/apply-patch/patch-text/normalize.d.ts +3 -0
- package/dist/tools/apply-patch/patch-text/normalize.js +262 -0
- package/dist/tools/apply-patch/structured/coercion.d.ts +3 -0
- package/dist/tools/apply-patch/structured/coercion.js +82 -0
- package/dist/tools/apply-patch/validation/shared.d.ts +3 -0
- package/dist/tools/apply-patch/validation/shared.js +6 -0
- package/dist/tools/apply-patch/validator.d.ts +2 -2
- package/dist/tools/apply-patch/validator.js +6 -556
- package/package.json +1 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AdapterContext, ChatEnvelope } from '../types/chat-envelope.js';
|
|
2
|
+
import type { FormatEnvelope } from '../types/format-envelope.js';
|
|
3
|
+
import type { JsonObject } from '../types/json.js';
|
|
4
|
+
export declare function applyHubOperationTableInbound(options: {
|
|
5
|
+
formatEnvelope: FormatEnvelope<JsonObject>;
|
|
6
|
+
chatEnvelope: ChatEnvelope;
|
|
7
|
+
adapterContext: AdapterContext;
|
|
8
|
+
}): void;
|
|
9
|
+
export declare function applyHubOperationTableOutboundPreMap(options: {
|
|
10
|
+
protocol: string;
|
|
11
|
+
chatEnvelope: ChatEnvelope;
|
|
12
|
+
adapterContext: AdapterContext;
|
|
13
|
+
}): Promise<void>;
|
|
14
|
+
export declare function applyHubOperationTableOutboundPostMap(options: {
|
|
15
|
+
chatEnvelope: ChatEnvelope;
|
|
16
|
+
formatEnvelope: FormatEnvelope<JsonObject>;
|
|
17
|
+
adapterContext: AdapterContext;
|
|
18
|
+
}): void;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { createBridgeActionState, runBridgeActionPipeline } from '../../shared/bridge-actions.js';
|
|
2
|
+
import { resolveBridgePolicy, resolvePolicyActions } from '../../shared/bridge-policies.js';
|
|
3
|
+
const INBOUND_BRIDGE_SPECS = {
|
|
4
|
+
'openai-chat': { protocol: 'openai-chat', stage: 'request_inbound', messages: 'chat_envelope' },
|
|
5
|
+
// Keep parity with the legacy semantic mapper behavior: do not pass messages[] into the action state
|
|
6
|
+
// for openai-responses, since the bridge actions here are used as metadata hooks only.
|
|
7
|
+
'openai-responses': { protocol: 'openai-responses', stage: 'request_inbound', messages: 'none', includeCapturedToolResults: true, moduleType: 'openai-responses' },
|
|
8
|
+
'anthropic-messages': { protocol: 'anthropic-messages', stage: 'request_inbound', messages: 'chat_envelope' },
|
|
9
|
+
'gemini-chat': { protocol: 'gemini-chat', stage: 'request_inbound', messages: 'chat_envelope' }
|
|
10
|
+
};
|
|
11
|
+
const OUTBOUND_BRIDGE_SPECS = {
|
|
12
|
+
'openai-chat': { protocol: 'openai-chat', stage: 'request_outbound', messages: 'format_payload_messages', includeCapturedToolResults: true },
|
|
13
|
+
// Keep parity: openai-responses outbound actions should not touch normalized messages.
|
|
14
|
+
'openai-responses': { protocol: 'openai-responses', stage: 'request_outbound', messages: 'none', moduleType: 'openai-responses' },
|
|
15
|
+
'anthropic-messages': { protocol: 'anthropic-messages', stage: 'request_outbound', messages: 'format_payload_messages', includeCapturedToolResults: true },
|
|
16
|
+
// Keep parity with legacy gemini mapper: outbound hooks operate on ChatEnvelope.messages (not Gemini contents).
|
|
17
|
+
'gemini-chat': { protocol: 'gemini-chat', stage: 'request_outbound', messages: 'chat_envelope', includeCapturedToolResults: true }
|
|
18
|
+
};
|
|
19
|
+
function isSupportedProtocol(protocol) {
|
|
20
|
+
return protocol === 'openai-chat' ||
|
|
21
|
+
protocol === 'openai-responses' ||
|
|
22
|
+
protocol === 'anthropic-messages' ||
|
|
23
|
+
protocol === 'gemini-chat';
|
|
24
|
+
}
|
|
25
|
+
function extractPayloadMessages(payload) {
|
|
26
|
+
const value = payload?.messages;
|
|
27
|
+
if (!Array.isArray(value)) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function buildCapturedToolResults(toolOutputs) {
|
|
33
|
+
if (!Array.isArray(toolOutputs) || toolOutputs.length === 0) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
return toolOutputs.map((entry) => ({
|
|
37
|
+
tool_call_id: entry.tool_call_id,
|
|
38
|
+
output: entry.content,
|
|
39
|
+
name: entry.name
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
function applyBridgePolicy(spec, options) {
|
|
43
|
+
const bridgePolicy = resolveBridgePolicy({ protocol: spec.protocol, moduleType: spec.moduleType ?? spec.protocol });
|
|
44
|
+
const actions = resolvePolicyActions(bridgePolicy, spec.stage);
|
|
45
|
+
if (!actions?.length) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const metadata = options.chatEnvelope.metadata;
|
|
49
|
+
const messages = spec.messages === 'chat_envelope'
|
|
50
|
+
? options.chatEnvelope.messages
|
|
51
|
+
: spec.messages === 'format_payload_messages'
|
|
52
|
+
? extractPayloadMessages(options.payload)
|
|
53
|
+
: undefined;
|
|
54
|
+
const capturedToolResults = spec.includeCapturedToolResults
|
|
55
|
+
? buildCapturedToolResults(options.chatEnvelope.toolOutputs)
|
|
56
|
+
: undefined;
|
|
57
|
+
const actionState = createBridgeActionState({
|
|
58
|
+
...(messages ? { messages } : {}),
|
|
59
|
+
rawRequest: options.payload,
|
|
60
|
+
metadata,
|
|
61
|
+
...(capturedToolResults ? { capturedToolResults } : {})
|
|
62
|
+
});
|
|
63
|
+
runBridgeActionPipeline({
|
|
64
|
+
stage: spec.stage,
|
|
65
|
+
actions,
|
|
66
|
+
protocol: bridgePolicy?.protocol ?? spec.protocol,
|
|
67
|
+
moduleType: bridgePolicy?.moduleType ?? spec.moduleType ?? spec.protocol,
|
|
68
|
+
requestId: options.requestId,
|
|
69
|
+
state: actionState
|
|
70
|
+
});
|
|
71
|
+
if (spec.messages === 'chat_envelope') {
|
|
72
|
+
options.chatEnvelope.messages = actionState.messages;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function normalizeToolContent(value) {
|
|
76
|
+
if (typeof value === 'string')
|
|
77
|
+
return value;
|
|
78
|
+
if (value == null)
|
|
79
|
+
return '';
|
|
80
|
+
try {
|
|
81
|
+
return JSON.stringify(value);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return String(value ?? '');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function rebuildGeminiToolOutputsFromMessages(chatEnvelope) {
|
|
88
|
+
const messages = Array.isArray(chatEnvelope.messages) ? chatEnvelope.messages : [];
|
|
89
|
+
const missing = Array.isArray(chatEnvelope.metadata?.missingFields) ? chatEnvelope.metadata.missingFields : undefined;
|
|
90
|
+
const outputs = [];
|
|
91
|
+
messages.forEach((msg, index) => {
|
|
92
|
+
if (!msg || typeof msg !== 'object')
|
|
93
|
+
return;
|
|
94
|
+
if (msg.role !== 'tool')
|
|
95
|
+
return;
|
|
96
|
+
const callId = msg.tool_call_id || msg.id;
|
|
97
|
+
if (typeof callId !== 'string' || !callId.trim()) {
|
|
98
|
+
if (missing) {
|
|
99
|
+
missing.push({ path: `messages[${index}].tool_call_id`, reason: 'missing_tool_call_id' });
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
outputs.push({
|
|
104
|
+
tool_call_id: callId.trim(),
|
|
105
|
+
content: normalizeToolContent(msg.content),
|
|
106
|
+
name: typeof msg.name === 'string' ? msg.name : undefined
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
chatEnvelope.toolOutputs = outputs.length ? outputs : undefined;
|
|
110
|
+
}
|
|
111
|
+
export function applyHubOperationTableInbound(options) {
|
|
112
|
+
const protocol = options.formatEnvelope.protocol;
|
|
113
|
+
if (!isSupportedProtocol(protocol)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const payload = (options.formatEnvelope.payload ?? {});
|
|
117
|
+
const spec = INBOUND_BRIDGE_SPECS[protocol];
|
|
118
|
+
applyBridgePolicy(spec, {
|
|
119
|
+
requestId: options.adapterContext.requestId,
|
|
120
|
+
chatEnvelope: options.chatEnvelope,
|
|
121
|
+
payload,
|
|
122
|
+
adapterContext: options.adapterContext
|
|
123
|
+
});
|
|
124
|
+
if (protocol === 'gemini-chat') {
|
|
125
|
+
// Keep parity: gemini mapper rebuilds toolOutputs after inbound policy adjustments.
|
|
126
|
+
rebuildGeminiToolOutputsFromMessages(options.chatEnvelope);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export function applyHubOperationTableOutboundPreMap(options) {
|
|
130
|
+
if (options.protocol !== 'anthropic-messages' && options.protocol !== 'gemini-chat') {
|
|
131
|
+
return Promise.resolve();
|
|
132
|
+
}
|
|
133
|
+
// Ensure tool_use/tool_result ordering and per-session history for protocols that depend on it.
|
|
134
|
+
return (async () => {
|
|
135
|
+
try {
|
|
136
|
+
const { applyToolSessionCompat } = await import('../tool-session-compat.js');
|
|
137
|
+
await applyToolSessionCompat(options.chatEnvelope, options.adapterContext);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// best-effort compat; never block outbound mapping
|
|
141
|
+
}
|
|
142
|
+
})();
|
|
143
|
+
}
|
|
144
|
+
export function applyHubOperationTableOutboundPostMap(options) {
|
|
145
|
+
const protocol = options.formatEnvelope.protocol;
|
|
146
|
+
if (!isSupportedProtocol(protocol)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const payload = (options.formatEnvelope.payload ?? {});
|
|
150
|
+
const spec = OUTBOUND_BRIDGE_SPECS[protocol];
|
|
151
|
+
applyBridgePolicy(spec, {
|
|
152
|
+
requestId: options.adapterContext.requestId,
|
|
153
|
+
chatEnvelope: options.chatEnvelope,
|
|
154
|
+
payload,
|
|
155
|
+
adapterContext: options.adapterContext
|
|
156
|
+
});
|
|
157
|
+
options.formatEnvelope.payload = payload;
|
|
158
|
+
}
|
|
@@ -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 class AnthropicSemanticMapper implements SemanticMapper {
|
|
5
|
+
private readonly chatMapper;
|
|
6
|
+
toChat(format: FormatEnvelope, ctx: AdapterContext): Promise<ChatEnvelope>;
|
|
7
|
+
fromChat(chat: ChatEnvelope, ctx: AdapterContext): Promise<FormatEnvelope>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { isJsonObject, jsonClone } from '../../types/json.js';
|
|
2
|
+
import { buildOpenAIChatFromAnthropic, buildAnthropicRequestFromOpenAIChat } from '../../../codecs/anthropic-openai-codec.js';
|
|
3
|
+
import { encodeMetadataPassthrough, extractMetadataPassthrough } from '../../../shared/metadata-passthrough.js';
|
|
4
|
+
import { buildAnthropicToolAliasMap } from '../../../shared/anthropic-message-utils.js';
|
|
5
|
+
import { ChatSemanticMapper } from './chat-mapper.js';
|
|
6
|
+
import { ensureProtocolState, getProtocolState } from '../../../shared/protocol-state.js';
|
|
7
|
+
const ANTHROPIC_PARAMETER_KEYS = [
|
|
8
|
+
'model',
|
|
9
|
+
'temperature',
|
|
10
|
+
'top_p',
|
|
11
|
+
'top_k',
|
|
12
|
+
'max_tokens',
|
|
13
|
+
'max_output_tokens',
|
|
14
|
+
'metadata',
|
|
15
|
+
'stream',
|
|
16
|
+
'tool_choice'
|
|
17
|
+
];
|
|
18
|
+
const ANTHROPIC_TOP_LEVEL_FIELDS = new Set([
|
|
19
|
+
'model',
|
|
20
|
+
'messages',
|
|
21
|
+
'tools',
|
|
22
|
+
'system',
|
|
23
|
+
'stop_sequences',
|
|
24
|
+
'temperature',
|
|
25
|
+
'top_p',
|
|
26
|
+
'top_k',
|
|
27
|
+
'max_tokens',
|
|
28
|
+
'max_output_tokens',
|
|
29
|
+
'metadata',
|
|
30
|
+
'stream',
|
|
31
|
+
'tool_choice'
|
|
32
|
+
]);
|
|
33
|
+
const PASSTHROUGH_METADATA_PREFIX = 'rcc_passthrough_';
|
|
34
|
+
const PASSTHROUGH_PARAMETERS = ['tool_choice'];
|
|
35
|
+
function ensureSemantics(chat) {
|
|
36
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
37
|
+
chat.semantics = {};
|
|
38
|
+
}
|
|
39
|
+
return chat.semantics;
|
|
40
|
+
}
|
|
41
|
+
function ensureAnthropicSemanticsNode(chat) {
|
|
42
|
+
const semantics = ensureSemantics(chat);
|
|
43
|
+
if (!semantics.anthropic || !isJsonObject(semantics.anthropic)) {
|
|
44
|
+
semantics.anthropic = {};
|
|
45
|
+
}
|
|
46
|
+
return semantics.anthropic;
|
|
47
|
+
}
|
|
48
|
+
function markExplicitEmptyTools(chat) {
|
|
49
|
+
const semantics = ensureSemantics(chat);
|
|
50
|
+
if (!semantics.tools || !isJsonObject(semantics.tools)) {
|
|
51
|
+
semantics.tools = {};
|
|
52
|
+
}
|
|
53
|
+
semantics.tools.explicitEmpty = true;
|
|
54
|
+
}
|
|
55
|
+
function readAnthropicSemantics(chat) {
|
|
56
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const node = chat.semantics.anthropic;
|
|
60
|
+
return node && isJsonObject(node) ? node : undefined;
|
|
61
|
+
}
|
|
62
|
+
function hasExplicitEmptyToolsSemantics(chat) {
|
|
63
|
+
if (!chat.semantics || typeof chat.semantics !== 'object') {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const toolsNode = chat.semantics.tools;
|
|
67
|
+
if (!toolsNode || !isJsonObject(toolsNode)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return Boolean(toolsNode.explicitEmpty);
|
|
71
|
+
}
|
|
72
|
+
function sanitizeAnthropicPayload(payload) {
|
|
73
|
+
for (const key of Object.keys(payload)) {
|
|
74
|
+
if (!ANTHROPIC_TOP_LEVEL_FIELDS.has(key)) {
|
|
75
|
+
delete payload[key];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return payload;
|
|
79
|
+
}
|
|
80
|
+
function collectParameters(payload) {
|
|
81
|
+
const params = {};
|
|
82
|
+
for (const key of ANTHROPIC_PARAMETER_KEYS) {
|
|
83
|
+
if (payload[key] !== undefined) {
|
|
84
|
+
params[key] = payload[key];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(payload.stop_sequences)) {
|
|
88
|
+
params.stop = payload.stop_sequences;
|
|
89
|
+
}
|
|
90
|
+
return Object.keys(params).length ? params : undefined;
|
|
91
|
+
}
|
|
92
|
+
function cloneAnthropicSystemBlocks(value) {
|
|
93
|
+
if (value === undefined || value === null) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
const blocks = Array.isArray(value) ? value : [value];
|
|
97
|
+
if (!blocks.length) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
return blocks.map((entry) => jsonClone(entry));
|
|
101
|
+
}
|
|
102
|
+
export class AnthropicSemanticMapper {
|
|
103
|
+
chatMapper = new ChatSemanticMapper();
|
|
104
|
+
async toChat(format, ctx) {
|
|
105
|
+
const payload = (format.payload ?? {});
|
|
106
|
+
const missing = [];
|
|
107
|
+
if (!Array.isArray(payload.messages))
|
|
108
|
+
missing.push({ path: 'messages', reason: 'absent' });
|
|
109
|
+
if (typeof payload.model !== 'string')
|
|
110
|
+
missing.push({ path: 'model', reason: 'absent' });
|
|
111
|
+
const passthrough = extractMetadataPassthrough(payload.metadata, {
|
|
112
|
+
prefix: PASSTHROUGH_METADATA_PREFIX,
|
|
113
|
+
keys: PASSTHROUGH_PARAMETERS
|
|
114
|
+
});
|
|
115
|
+
const openaiPayload = buildOpenAIChatFromAnthropic(payload);
|
|
116
|
+
const canonicalContext = {
|
|
117
|
+
...ctx,
|
|
118
|
+
providerProtocol: 'openai-chat',
|
|
119
|
+
entryEndpoint: ctx.entryEndpoint || '/v1/chat/completions'
|
|
120
|
+
};
|
|
121
|
+
const chatEnvelope = await this.chatMapper.toChat({
|
|
122
|
+
protocol: 'openai-chat',
|
|
123
|
+
direction: 'request',
|
|
124
|
+
payload: openaiPayload
|
|
125
|
+
}, canonicalContext);
|
|
126
|
+
const metadata = chatEnvelope.metadata ?? { context: canonicalContext };
|
|
127
|
+
chatEnvelope.metadata = metadata;
|
|
128
|
+
metadata.context = canonicalContext;
|
|
129
|
+
let semanticsNode;
|
|
130
|
+
const resolveExtraFields = () => {
|
|
131
|
+
if (!isJsonObject(metadata.extraFields)) {
|
|
132
|
+
metadata.extraFields = {};
|
|
133
|
+
}
|
|
134
|
+
return metadata.extraFields;
|
|
135
|
+
};
|
|
136
|
+
const protocolState = ensureProtocolState(metadata, 'anthropic');
|
|
137
|
+
const systemBlocks = cloneAnthropicSystemBlocks(payload.system);
|
|
138
|
+
if (systemBlocks) {
|
|
139
|
+
protocolState.systemBlocks = systemBlocks;
|
|
140
|
+
semanticsNode = semanticsNode ?? ensureAnthropicSemanticsNode(chatEnvelope);
|
|
141
|
+
semanticsNode.systemBlocks = jsonClone(systemBlocks);
|
|
142
|
+
}
|
|
143
|
+
if (payload.tools && Array.isArray(payload.tools) && payload.tools.length === 0) {
|
|
144
|
+
metadata.toolsFieldPresent = true;
|
|
145
|
+
resolveExtraFields().toolsFieldPresent = true;
|
|
146
|
+
markExplicitEmptyTools(chatEnvelope);
|
|
147
|
+
}
|
|
148
|
+
const aliasMap = buildAnthropicToolAliasMap(payload.tools);
|
|
149
|
+
if (aliasMap) {
|
|
150
|
+
const extraFields = resolveExtraFields();
|
|
151
|
+
ctx.anthropicToolNameMap = aliasMap;
|
|
152
|
+
canonicalContext.anthropicToolNameMap = aliasMap;
|
|
153
|
+
metadata.anthropicToolNameMap = aliasMap;
|
|
154
|
+
extraFields.anthropicToolNameMap = aliasMap;
|
|
155
|
+
semanticsNode = semanticsNode ?? ensureAnthropicSemanticsNode(chatEnvelope);
|
|
156
|
+
semanticsNode.toolAliasMap = jsonClone(aliasMap);
|
|
157
|
+
}
|
|
158
|
+
if (Array.isArray(payload.messages) && payload.messages.length) {
|
|
159
|
+
const shapes = payload.messages.map((entry) => {
|
|
160
|
+
if (!entry || typeof entry !== 'object') {
|
|
161
|
+
return 'unknown';
|
|
162
|
+
}
|
|
163
|
+
const rawContent = entry.content;
|
|
164
|
+
if (typeof rawContent === 'string') {
|
|
165
|
+
return 'string';
|
|
166
|
+
}
|
|
167
|
+
if (Array.isArray(rawContent)) {
|
|
168
|
+
return 'array';
|
|
169
|
+
}
|
|
170
|
+
if (rawContent === null || rawContent === undefined) {
|
|
171
|
+
return 'null';
|
|
172
|
+
}
|
|
173
|
+
return typeof rawContent;
|
|
174
|
+
});
|
|
175
|
+
const extraFields = resolveExtraFields();
|
|
176
|
+
const mirrorNode = extraFields.anthropicMirror && typeof extraFields.anthropicMirror === 'object'
|
|
177
|
+
? extraFields.anthropicMirror
|
|
178
|
+
: {};
|
|
179
|
+
mirrorNode.messageContentShape = shapes;
|
|
180
|
+
extraFields.anthropicMirror = mirrorNode;
|
|
181
|
+
semanticsNode = semanticsNode ?? ensureAnthropicSemanticsNode(chatEnvelope);
|
|
182
|
+
semanticsNode.mirror = jsonClone(mirrorNode);
|
|
183
|
+
}
|
|
184
|
+
if (missing.length) {
|
|
185
|
+
metadata.missingFields = Array.isArray(metadata.missingFields)
|
|
186
|
+
? [...metadata.missingFields, ...missing]
|
|
187
|
+
: missing;
|
|
188
|
+
}
|
|
189
|
+
const providerMetadata = passthrough.metadata ??
|
|
190
|
+
(payload.metadata && isJsonObject(payload.metadata) ? jsonClone(payload.metadata) : undefined);
|
|
191
|
+
if (providerMetadata) {
|
|
192
|
+
metadata.providerMetadata = providerMetadata;
|
|
193
|
+
semanticsNode = semanticsNode ?? ensureAnthropicSemanticsNode(chatEnvelope);
|
|
194
|
+
semanticsNode.providerMetadata = jsonClone(providerMetadata);
|
|
195
|
+
}
|
|
196
|
+
const mergedParameters = { ...(chatEnvelope.parameters ?? {}) };
|
|
197
|
+
const mergeParameters = (source) => {
|
|
198
|
+
if (!source) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
for (const [key, value] of Object.entries(source)) {
|
|
202
|
+
if (mergedParameters[key] !== undefined) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
mergedParameters[key] = jsonClone(value);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
mergeParameters(collectParameters(payload));
|
|
209
|
+
if (providerMetadata) {
|
|
210
|
+
mergedParameters.metadata = jsonClone(providerMetadata);
|
|
211
|
+
}
|
|
212
|
+
if (passthrough.passthrough) {
|
|
213
|
+
for (const [key, value] of Object.entries(passthrough.passthrough)) {
|
|
214
|
+
mergedParameters[key] = jsonClone(value);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (Object.keys(mergedParameters).length) {
|
|
218
|
+
chatEnvelope.parameters = mergedParameters;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
delete chatEnvelope.parameters;
|
|
222
|
+
}
|
|
223
|
+
return chatEnvelope;
|
|
224
|
+
}
|
|
225
|
+
async fromChat(chat, ctx) {
|
|
226
|
+
const model = chat.parameters?.model;
|
|
227
|
+
if (typeof model !== 'string' || !model.trim()) {
|
|
228
|
+
throw new Error('ChatEnvelope.parameters.model is required for anthropic-messages outbound conversion');
|
|
229
|
+
}
|
|
230
|
+
const baseRequest = {
|
|
231
|
+
model,
|
|
232
|
+
messages: chat.messages,
|
|
233
|
+
tools: chat.tools
|
|
234
|
+
};
|
|
235
|
+
const semanticsNode = readAnthropicSemantics(chat);
|
|
236
|
+
const explicitEmptyTools = (chat.metadata?.toolsFieldPresent === true) || hasExplicitEmptyToolsSemantics(chat);
|
|
237
|
+
const trimmedParameters = chat.parameters && typeof chat.parameters === 'object' ? chat.parameters : undefined;
|
|
238
|
+
if (trimmedParameters) {
|
|
239
|
+
for (const [key, value] of Object.entries(trimmedParameters)) {
|
|
240
|
+
if (ANTHROPIC_TOP_LEVEL_FIELDS.has(key) || key === 'stop') {
|
|
241
|
+
if (key === 'messages' || key === 'tools') {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
baseRequest[key] = value;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const passthroughMetadata = encodeMetadataPassthrough(chat.parameters, {
|
|
249
|
+
prefix: PASSTHROUGH_METADATA_PREFIX,
|
|
250
|
+
keys: PASSTHROUGH_PARAMETERS
|
|
251
|
+
});
|
|
252
|
+
if (passthroughMetadata) {
|
|
253
|
+
const rawMetadata = baseRequest.metadata;
|
|
254
|
+
const existingMetadata = isJsonObject(rawMetadata)
|
|
255
|
+
? jsonClone(rawMetadata)
|
|
256
|
+
: {};
|
|
257
|
+
for (const [key, value] of Object.entries(passthroughMetadata)) {
|
|
258
|
+
existingMetadata[key] = value;
|
|
259
|
+
}
|
|
260
|
+
baseRequest.metadata = existingMetadata;
|
|
261
|
+
}
|
|
262
|
+
if (baseRequest.max_output_tokens && !baseRequest.max_tokens) {
|
|
263
|
+
baseRequest.max_tokens = baseRequest.max_output_tokens;
|
|
264
|
+
}
|
|
265
|
+
// 出站阶段不再直接透传其它协议的 providerMetadata,避免跨协议打洞;
|
|
266
|
+
// Anthropic 自身入口的 metadata 已在入站阶段通过 collectParameters/encodeMetadataPassthrough
|
|
267
|
+
// 按白名单收集,这里仅依赖这些显式映射结果。
|
|
268
|
+
if (explicitEmptyTools && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
269
|
+
baseRequest.tools = [];
|
|
270
|
+
}
|
|
271
|
+
const protocolState = getProtocolState(chat.metadata, 'anthropic');
|
|
272
|
+
if (protocolState?.systemBlocks !== undefined) {
|
|
273
|
+
baseRequest.system = jsonClone(protocolState.systemBlocks);
|
|
274
|
+
}
|
|
275
|
+
else if (semanticsNode?.systemBlocks !== undefined) {
|
|
276
|
+
baseRequest.system = jsonClone(semanticsNode.systemBlocks);
|
|
277
|
+
}
|
|
278
|
+
if (chat.metadata &&
|
|
279
|
+
typeof chat.metadata === 'object' &&
|
|
280
|
+
chat.metadata.extraFields &&
|
|
281
|
+
typeof chat.metadata.extraFields === 'object' &&
|
|
282
|
+
chat.metadata.extraFields.anthropicMirror) {
|
|
283
|
+
baseRequest.__anthropicMirror = jsonClone(chat.metadata.extraFields.anthropicMirror ?? {});
|
|
284
|
+
}
|
|
285
|
+
else if (semanticsNode?.mirror && isJsonObject(semanticsNode.mirror)) {
|
|
286
|
+
baseRequest.__anthropicMirror = jsonClone(semanticsNode.mirror);
|
|
287
|
+
}
|
|
288
|
+
const payloadSource = buildAnthropicRequestFromOpenAIChat(baseRequest);
|
|
289
|
+
const payload = sanitizeAnthropicPayload(JSON.parse(JSON.stringify(payloadSource)));
|
|
290
|
+
if (chat.metadata?.toolsFieldPresent && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
291
|
+
payload.tools = [];
|
|
292
|
+
}
|
|
293
|
+
sanitizeAnthropicPayload(payload);
|
|
294
|
+
return {
|
|
295
|
+
protocol: 'anthropic-messages',
|
|
296
|
+
direction: 'response',
|
|
297
|
+
payload,
|
|
298
|
+
meta: {
|
|
299
|
+
context: ctx
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
@@ -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
|
+
}
|