@jsonstudio/llms 0.6.954 → 0.6.1172
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 +489 -19
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +6 -0
- 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 -467
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +1 -7
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +1 -903
- 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 +89 -15
- 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 +70 -5
- package/dist/router/virtual-router/context-advisor.d.ts +4 -0
- package/dist/router/virtual-router/context-advisor.js +3 -0
- package/dist/router/virtual-router/context-weighted.d.ts +31 -0
- package/dist/router/virtual-router/context-weighted.js +54 -0
- package/dist/router/virtual-router/engine-selection.js +284 -47
- 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 +98 -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 +161 -7
- 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
|
@@ -15,6 +15,7 @@ import { runRespOutboundStage1ClientRemap } from '../pipeline/stages/resp_outbou
|
|
|
15
15
|
import { runRespOutboundStage2SseStream } from '../pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js';
|
|
16
16
|
import { recordResponsesResponse } from '../../shared/responses-conversation-store.js';
|
|
17
17
|
import { runServerToolOrchestration } from '../../../servertool/engine.js';
|
|
18
|
+
import { commitClockReservation, normalizeClockConfig } from '../../../servertool/clock/task-store.js';
|
|
18
19
|
const PROVIDER_RESPONSE_REGISTRY = {
|
|
19
20
|
'openai-chat': {
|
|
20
21
|
createFormatAdapter: () => new ChatFormatAdapter(),
|
|
@@ -52,6 +53,123 @@ function resolveClientProtocol(entryEndpoint) {
|
|
|
52
53
|
return 'anthropic-messages';
|
|
53
54
|
return 'openai-chat';
|
|
54
55
|
}
|
|
56
|
+
function isToolSurfaceShadowEnabled() {
|
|
57
|
+
const raw = String(process.env.ROUTECODEX_HUB_TOOL_SURFACE_MODE || '').trim().toLowerCase();
|
|
58
|
+
if (!raw)
|
|
59
|
+
return false;
|
|
60
|
+
if (raw === 'off' || raw === '0' || raw === 'false')
|
|
61
|
+
return false;
|
|
62
|
+
return raw === 'observe' || raw === 'shadow' || raw === 'enforce';
|
|
63
|
+
}
|
|
64
|
+
function isJsonRecord(value) {
|
|
65
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
66
|
+
}
|
|
67
|
+
function coerceClockReservation(value) {
|
|
68
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const rec = value;
|
|
72
|
+
const reservationId = typeof rec.reservationId === 'string' ? rec.reservationId.trim() : '';
|
|
73
|
+
const sessionId = typeof rec.sessionId === 'string' ? rec.sessionId.trim() : '';
|
|
74
|
+
const taskIdsRaw = Array.isArray(rec.taskIds) ? rec.taskIds : [];
|
|
75
|
+
const taskIds = taskIdsRaw
|
|
76
|
+
.filter((t) => typeof t === 'string' && t.trim().length)
|
|
77
|
+
.map((t) => String(t).trim());
|
|
78
|
+
const reservedAtMs = typeof rec.reservedAtMs === 'number' && Number.isFinite(rec.reservedAtMs)
|
|
79
|
+
? Math.floor(rec.reservedAtMs)
|
|
80
|
+
: Date.now();
|
|
81
|
+
if (!reservationId || !sessionId || taskIds.length === 0) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return { reservationId, sessionId, taskIds, reservedAtMs };
|
|
85
|
+
}
|
|
86
|
+
async function maybeCommitClockReservationFromContext(context) {
|
|
87
|
+
try {
|
|
88
|
+
const clockConfig = normalizeClockConfig(context.clock);
|
|
89
|
+
if (!clockConfig) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const reservation = coerceClockReservation(context.__clockReservation);
|
|
93
|
+
if (!reservation) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
await commitClockReservation(reservation, clockConfig);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// best-effort: never break response conversion due to clock persistence errors
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function detectProviderResponseShape(payload) {
|
|
103
|
+
if (!isJsonRecord(payload))
|
|
104
|
+
return 'unknown';
|
|
105
|
+
const obj = payload;
|
|
106
|
+
if (Array.isArray(obj.choices))
|
|
107
|
+
return 'openai-chat';
|
|
108
|
+
if (Array.isArray(obj.output) || obj.object === 'response')
|
|
109
|
+
return 'openai-responses';
|
|
110
|
+
if (typeof obj.type === 'string' && String(obj.type).toLowerCase() === 'message' && Array.isArray(obj.content)) {
|
|
111
|
+
return 'anthropic-messages';
|
|
112
|
+
}
|
|
113
|
+
if (Array.isArray(obj.candidates))
|
|
114
|
+
return 'gemini-chat';
|
|
115
|
+
return 'unknown';
|
|
116
|
+
}
|
|
117
|
+
function summarizeToolCallsFromProviderResponse(payload) {
|
|
118
|
+
try {
|
|
119
|
+
if (!isJsonRecord(payload))
|
|
120
|
+
return {};
|
|
121
|
+
const obj = payload;
|
|
122
|
+
// openai-chat
|
|
123
|
+
if (Array.isArray(obj.choices)) {
|
|
124
|
+
const msg = obj.choices?.[0]?.message;
|
|
125
|
+
const tcs = Array.isArray(msg?.tool_calls) ? msg.tool_calls : [];
|
|
126
|
+
const names = tcs
|
|
127
|
+
.map((tc) => String(tc?.function?.name || '').trim())
|
|
128
|
+
.filter((s) => s.length)
|
|
129
|
+
.slice(0, 10);
|
|
130
|
+
return { toolCallCount: tcs.length, toolNames: names.length ? names : undefined };
|
|
131
|
+
}
|
|
132
|
+
// openai-responses
|
|
133
|
+
if (Array.isArray(obj.output)) {
|
|
134
|
+
const out = obj.output;
|
|
135
|
+
const fnCalls = out.filter((it) => it && typeof it === 'object' && String(it.type || '').toLowerCase() === 'function_call');
|
|
136
|
+
const names = fnCalls
|
|
137
|
+
.map((it) => String(it?.name || '').trim())
|
|
138
|
+
.filter((s) => s.length)
|
|
139
|
+
.slice(0, 10);
|
|
140
|
+
return { toolCallCount: fnCalls.length, toolNames: names.length ? names : undefined };
|
|
141
|
+
}
|
|
142
|
+
// anthropic-messages
|
|
143
|
+
if (Array.isArray(obj.content)) {
|
|
144
|
+
const blocks = obj.content;
|
|
145
|
+
const uses = blocks.filter((b) => b && typeof b === 'object' && String(b.type || '').toLowerCase() === 'tool_use');
|
|
146
|
+
const names = uses
|
|
147
|
+
.map((b) => String(b?.name || '').trim())
|
|
148
|
+
.filter((s) => s.length)
|
|
149
|
+
.slice(0, 10);
|
|
150
|
+
return { toolCallCount: uses.length, toolNames: names.length ? names : undefined };
|
|
151
|
+
}
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return {};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function stripInternalPolicyDebugFields(payload) {
|
|
159
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const target = payload;
|
|
163
|
+
// These are internal debug/transport markers that must not leak across hub boundaries.
|
|
164
|
+
// They also break hub policy allowlists (Phase 0/1 observation) and confuse tool-surface detection.
|
|
165
|
+
delete target._transformed;
|
|
166
|
+
delete target._originalFormat;
|
|
167
|
+
delete target._targetFormat;
|
|
168
|
+
delete target.__responses_output_text_meta;
|
|
169
|
+
delete target.__responses_reasoning;
|
|
170
|
+
delete target.__responses_payload_snapshot;
|
|
171
|
+
delete target.__responses_passthrough;
|
|
172
|
+
}
|
|
55
173
|
function supportsSseProtocol(protocol) {
|
|
56
174
|
return protocol === 'openai-chat' || protocol === 'openai-responses' || protocol === 'anthropic-messages' || protocol === 'gemini-chat';
|
|
57
175
|
}
|
|
@@ -68,6 +186,16 @@ function extractDisplayModel(context) {
|
|
|
68
186
|
}
|
|
69
187
|
return undefined;
|
|
70
188
|
}
|
|
189
|
+
function extractClientFacingRequestId(context) {
|
|
190
|
+
const contextAny = context;
|
|
191
|
+
const candidates = [contextAny.clientRequestId, contextAny.groupRequestId, context.requestId];
|
|
192
|
+
for (const candidate of candidates) {
|
|
193
|
+
if (typeof candidate === 'string' && candidate.trim()) {
|
|
194
|
+
return candidate.trim();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
71
199
|
function applyModelOverride(payload, model) {
|
|
72
200
|
if (!model || !payload || typeof payload !== 'object') {
|
|
73
201
|
return;
|
|
@@ -107,6 +235,7 @@ export async function convertProviderResponse(options) {
|
|
|
107
235
|
/* logging best-effort */
|
|
108
236
|
}
|
|
109
237
|
const displayModel = extractDisplayModel(options.context);
|
|
238
|
+
const clientFacingRequestId = extractClientFacingRequestId(options.context) ?? options.context.requestId;
|
|
110
239
|
const plan = PROVIDER_RESPONSE_REGISTRY[options.providerProtocol];
|
|
111
240
|
if (!plan) {
|
|
112
241
|
throw new Error(`Unknown provider protocol: ${options.providerProtocol}`);
|
|
@@ -142,6 +271,27 @@ export async function convertProviderResponse(options) {
|
|
|
142
271
|
adapterContext: options.context,
|
|
143
272
|
stageRecorder: options.stageRecorder
|
|
144
273
|
});
|
|
274
|
+
stripInternalPolicyDebugFields(formatEnvelope.payload);
|
|
275
|
+
// Phase 2 (shadow): response tool surface mismatch detection (provider inbound).
|
|
276
|
+
// Only records diffs; does not rewrite payload.
|
|
277
|
+
try {
|
|
278
|
+
if (options.stageRecorder && isToolSurfaceShadowEnabled()) {
|
|
279
|
+
const detected = detectProviderResponseShape(formatEnvelope.payload);
|
|
280
|
+
if (detected !== 'unknown' && detected !== options.providerProtocol) {
|
|
281
|
+
const summary = summarizeToolCallsFromProviderResponse(formatEnvelope.payload);
|
|
282
|
+
options.stageRecorder.record('hub_toolsurface.shadow.provider_inbound', {
|
|
283
|
+
kind: 'provider_inbound',
|
|
284
|
+
expectedProtocol: options.providerProtocol,
|
|
285
|
+
detectedProtocol: detected,
|
|
286
|
+
...(summary.toolCallCount !== undefined ? { toolCallCount: summary.toolCallCount } : {}),
|
|
287
|
+
...(summary.toolNames ? { toolNames: summary.toolNames } : {})
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
// never break response conversion
|
|
294
|
+
}
|
|
145
295
|
// Phase 0/1: observe provider inbound payload violations (best-effort; no rewrites here).
|
|
146
296
|
try {
|
|
147
297
|
if (formatEnvelope.payload && typeof formatEnvelope.payload === 'object' && !Array.isArray(formatEnvelope.payload)) {
|
|
@@ -183,6 +333,7 @@ export async function convertProviderResponse(options) {
|
|
|
183
333
|
requestId: options.context.requestId,
|
|
184
334
|
entryEndpoint: options.entryEndpoint,
|
|
185
335
|
providerProtocol: options.providerProtocol,
|
|
336
|
+
stageRecorder: options.stageRecorder,
|
|
186
337
|
providerInvoker: options.providerInvoker,
|
|
187
338
|
reenterPipeline: options.reenterPipeline
|
|
188
339
|
});
|
|
@@ -229,11 +380,31 @@ export async function convertProviderResponse(options) {
|
|
|
229
380
|
const clientPayload = runRespOutboundStage1ClientRemap({
|
|
230
381
|
payload: finalizeResult.finalizedPayload,
|
|
231
382
|
clientProtocol,
|
|
232
|
-
requestId:
|
|
383
|
+
requestId: clientFacingRequestId,
|
|
233
384
|
adapterContext: options.context,
|
|
234
385
|
stageRecorder: options.stageRecorder
|
|
235
386
|
});
|
|
236
387
|
applyModelOverride(clientPayload, displayModel);
|
|
388
|
+
stripInternalPolicyDebugFields(clientPayload);
|
|
389
|
+
// Phase 2 (shadow): response tool surface mismatch detection (client outbound).
|
|
390
|
+
try {
|
|
391
|
+
if (options.stageRecorder && isToolSurfaceShadowEnabled()) {
|
|
392
|
+
const detected = detectProviderResponseShape(clientPayload);
|
|
393
|
+
if (detected !== 'unknown' && detected !== clientProtocol) {
|
|
394
|
+
const summary = summarizeToolCallsFromProviderResponse(clientPayload);
|
|
395
|
+
options.stageRecorder.record('hub_toolsurface.shadow.client_outbound', {
|
|
396
|
+
kind: 'client_outbound',
|
|
397
|
+
expectedProtocol: clientProtocol,
|
|
398
|
+
detectedProtocol: detected,
|
|
399
|
+
...(summary.toolCallCount !== undefined ? { toolCallCount: summary.toolCallCount } : {}),
|
|
400
|
+
...(summary.toolNames ? { toolNames: summary.toolNames } : {})
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
// never break response conversion
|
|
407
|
+
}
|
|
237
408
|
// Phase 0/1: observe client outbound payload violations (best-effort; no rewrites here).
|
|
238
409
|
try {
|
|
239
410
|
if (clientPayload && typeof clientPayload === 'object' && !Array.isArray(clientPayload)) {
|
|
@@ -252,10 +423,12 @@ export async function convertProviderResponse(options) {
|
|
|
252
423
|
const outbound = await runRespOutboundStage2SseStream({
|
|
253
424
|
clientPayload,
|
|
254
425
|
clientProtocol,
|
|
255
|
-
requestId:
|
|
426
|
+
requestId: clientFacingRequestId,
|
|
256
427
|
wantsStream,
|
|
257
428
|
stageRecorder: options.stageRecorder
|
|
258
429
|
});
|
|
430
|
+
// Commit scheduled-task delivery only after a successful client payload/stream is prepared.
|
|
431
|
+
await maybeCommitClockReservationFromContext(options.context);
|
|
259
432
|
if (outbound.stream) {
|
|
260
433
|
return { __sse_responses: outbound.stream, format: clientProtocol };
|
|
261
434
|
}
|
|
@@ -189,7 +189,7 @@ export function buildOpenAIChatFromAnthropicMessage(payload, options) {
|
|
|
189
189
|
switch (reason) {
|
|
190
190
|
case 'tool_use': return 'tool_calls';
|
|
191
191
|
case 'max_tokens': return 'length';
|
|
192
|
-
case 'stop_sequence': return '
|
|
192
|
+
case 'stop_sequence': return 'stop';
|
|
193
193
|
default: return 'stop';
|
|
194
194
|
}
|
|
195
195
|
};
|
|
@@ -1,8 +1 @@
|
|
|
1
|
-
|
|
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
|
-
}
|
|
1
|
+
export { AnthropicSemanticMapper } from '../operation-table/semantic-mappers/anthropic-mapper.js';
|
|
@@ -1,365 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { buildOpenAIChatFromAnthropic, buildAnthropicRequestFromOpenAIChat } from '../../codecs/anthropic-openai-codec.js';
|
|
3
|
-
import { createBridgeActionState, runBridgeActionPipeline } from '../../shared/bridge-actions.js';
|
|
4
|
-
import { resolveBridgePolicy, resolvePolicyActions } from '../../shared/bridge-policies.js';
|
|
5
|
-
import { encodeMetadataPassthrough, extractMetadataPassthrough } from '../../shared/metadata-passthrough.js';
|
|
6
|
-
import { buildAnthropicToolAliasMap } from '../../shared/anthropic-message-utils.js';
|
|
7
|
-
import { ChatSemanticMapper } from './chat-mapper.js';
|
|
8
|
-
import { ensureProtocolState, getProtocolState } from '../../shared/protocol-state.js';
|
|
9
|
-
const ANTHROPIC_PARAMETER_KEYS = [
|
|
10
|
-
'model',
|
|
11
|
-
'temperature',
|
|
12
|
-
'top_p',
|
|
13
|
-
'top_k',
|
|
14
|
-
'max_tokens',
|
|
15
|
-
'max_output_tokens',
|
|
16
|
-
'metadata',
|
|
17
|
-
'stream',
|
|
18
|
-
'tool_choice'
|
|
19
|
-
];
|
|
20
|
-
const ANTHROPIC_TOP_LEVEL_FIELDS = new Set([
|
|
21
|
-
'model',
|
|
22
|
-
'messages',
|
|
23
|
-
'tools',
|
|
24
|
-
'system',
|
|
25
|
-
'stop_sequences',
|
|
26
|
-
'temperature',
|
|
27
|
-
'top_p',
|
|
28
|
-
'top_k',
|
|
29
|
-
'max_tokens',
|
|
30
|
-
'max_output_tokens',
|
|
31
|
-
'metadata',
|
|
32
|
-
'stream',
|
|
33
|
-
'tool_choice'
|
|
34
|
-
]);
|
|
35
|
-
const PASSTHROUGH_METADATA_PREFIX = 'rcc_passthrough_';
|
|
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
|
-
}
|
|
74
|
-
function sanitizeAnthropicPayload(payload) {
|
|
75
|
-
for (const key of Object.keys(payload)) {
|
|
76
|
-
if (!ANTHROPIC_TOP_LEVEL_FIELDS.has(key)) {
|
|
77
|
-
delete payload[key];
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return payload;
|
|
81
|
-
}
|
|
82
|
-
function collectParameters(payload) {
|
|
83
|
-
const params = {};
|
|
84
|
-
for (const key of ANTHROPIC_PARAMETER_KEYS) {
|
|
85
|
-
if (payload[key] !== undefined) {
|
|
86
|
-
params[key] = payload[key];
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if (Array.isArray(payload.stop_sequences)) {
|
|
90
|
-
params.stop = payload.stop_sequences;
|
|
91
|
-
}
|
|
92
|
-
return Object.keys(params).length ? params : undefined;
|
|
93
|
-
}
|
|
94
|
-
function cloneAnthropicSystemBlocks(value) {
|
|
95
|
-
if (value === undefined || value === null) {
|
|
96
|
-
return undefined;
|
|
97
|
-
}
|
|
98
|
-
const blocks = Array.isArray(value) ? value : [value];
|
|
99
|
-
if (!blocks.length) {
|
|
100
|
-
return undefined;
|
|
101
|
-
}
|
|
102
|
-
return blocks.map((entry) => jsonClone(entry));
|
|
103
|
-
}
|
|
104
|
-
export class AnthropicSemanticMapper {
|
|
105
|
-
chatMapper = new ChatSemanticMapper();
|
|
106
|
-
async toChat(format, ctx) {
|
|
107
|
-
const payload = (format.payload ?? {});
|
|
108
|
-
const missing = [];
|
|
109
|
-
if (!Array.isArray(payload.messages))
|
|
110
|
-
missing.push({ path: 'messages', reason: 'absent' });
|
|
111
|
-
if (typeof payload.model !== 'string')
|
|
112
|
-
missing.push({ path: 'model', reason: 'absent' });
|
|
113
|
-
const passthrough = extractMetadataPassthrough(payload.metadata, {
|
|
114
|
-
prefix: PASSTHROUGH_METADATA_PREFIX,
|
|
115
|
-
keys: PASSTHROUGH_PARAMETERS
|
|
116
|
-
});
|
|
117
|
-
const openaiPayload = buildOpenAIChatFromAnthropic(payload);
|
|
118
|
-
const canonicalContext = {
|
|
119
|
-
...ctx,
|
|
120
|
-
providerProtocol: 'openai-chat',
|
|
121
|
-
entryEndpoint: ctx.entryEndpoint || '/v1/chat/completions'
|
|
122
|
-
};
|
|
123
|
-
const chatEnvelope = await this.chatMapper.toChat({
|
|
124
|
-
protocol: 'openai-chat',
|
|
125
|
-
direction: 'request',
|
|
126
|
-
payload: openaiPayload
|
|
127
|
-
}, canonicalContext);
|
|
128
|
-
const metadata = chatEnvelope.metadata ?? { context: canonicalContext };
|
|
129
|
-
chatEnvelope.metadata = metadata;
|
|
130
|
-
metadata.context = canonicalContext;
|
|
131
|
-
let semanticsNode;
|
|
132
|
-
const resolveExtraFields = () => {
|
|
133
|
-
if (!isJsonObject(metadata.extraFields)) {
|
|
134
|
-
metadata.extraFields = {};
|
|
135
|
-
}
|
|
136
|
-
return metadata.extraFields;
|
|
137
|
-
};
|
|
138
|
-
const protocolState = ensureProtocolState(metadata, 'anthropic');
|
|
139
|
-
const systemBlocks = cloneAnthropicSystemBlocks(payload.system);
|
|
140
|
-
if (systemBlocks) {
|
|
141
|
-
protocolState.systemBlocks = systemBlocks;
|
|
142
|
-
semanticsNode = semanticsNode ?? ensureAnthropicSemanticsNode(chatEnvelope);
|
|
143
|
-
semanticsNode.systemBlocks = jsonClone(systemBlocks);
|
|
144
|
-
}
|
|
145
|
-
if (payload.tools && Array.isArray(payload.tools) && payload.tools.length === 0) {
|
|
146
|
-
metadata.toolsFieldPresent = true;
|
|
147
|
-
resolveExtraFields().toolsFieldPresent = true;
|
|
148
|
-
markExplicitEmptyTools(chatEnvelope);
|
|
149
|
-
}
|
|
150
|
-
const aliasMap = buildAnthropicToolAliasMap(payload.tools);
|
|
151
|
-
if (aliasMap) {
|
|
152
|
-
const extraFields = resolveExtraFields();
|
|
153
|
-
ctx.anthropicToolNameMap = aliasMap;
|
|
154
|
-
canonicalContext.anthropicToolNameMap = aliasMap;
|
|
155
|
-
metadata.anthropicToolNameMap = aliasMap;
|
|
156
|
-
extraFields.anthropicToolNameMap = aliasMap;
|
|
157
|
-
semanticsNode = semanticsNode ?? ensureAnthropicSemanticsNode(chatEnvelope);
|
|
158
|
-
semanticsNode.toolAliasMap = jsonClone(aliasMap);
|
|
159
|
-
}
|
|
160
|
-
if (Array.isArray(payload.messages) && payload.messages.length) {
|
|
161
|
-
const shapes = payload.messages.map((entry) => {
|
|
162
|
-
if (!entry || typeof entry !== 'object') {
|
|
163
|
-
return 'unknown';
|
|
164
|
-
}
|
|
165
|
-
const rawContent = entry.content;
|
|
166
|
-
if (typeof rawContent === 'string') {
|
|
167
|
-
return 'string';
|
|
168
|
-
}
|
|
169
|
-
if (Array.isArray(rawContent)) {
|
|
170
|
-
return 'array';
|
|
171
|
-
}
|
|
172
|
-
if (rawContent === null || rawContent === undefined) {
|
|
173
|
-
return 'null';
|
|
174
|
-
}
|
|
175
|
-
return typeof rawContent;
|
|
176
|
-
});
|
|
177
|
-
const extraFields = resolveExtraFields();
|
|
178
|
-
const mirrorNode = extraFields.anthropicMirror && typeof extraFields.anthropicMirror === 'object'
|
|
179
|
-
? extraFields.anthropicMirror
|
|
180
|
-
: {};
|
|
181
|
-
mirrorNode.messageContentShape = shapes;
|
|
182
|
-
extraFields.anthropicMirror = mirrorNode;
|
|
183
|
-
semanticsNode = semanticsNode ?? ensureAnthropicSemanticsNode(chatEnvelope);
|
|
184
|
-
semanticsNode.mirror = jsonClone(mirrorNode);
|
|
185
|
-
}
|
|
186
|
-
if (missing.length) {
|
|
187
|
-
metadata.missingFields = Array.isArray(metadata.missingFields)
|
|
188
|
-
? [...metadata.missingFields, ...missing]
|
|
189
|
-
: missing;
|
|
190
|
-
}
|
|
191
|
-
const providerMetadata = passthrough.metadata ??
|
|
192
|
-
(payload.metadata && isJsonObject(payload.metadata) ? jsonClone(payload.metadata) : undefined);
|
|
193
|
-
if (providerMetadata) {
|
|
194
|
-
metadata.providerMetadata = providerMetadata;
|
|
195
|
-
semanticsNode = semanticsNode ?? ensureAnthropicSemanticsNode(chatEnvelope);
|
|
196
|
-
semanticsNode.providerMetadata = jsonClone(providerMetadata);
|
|
197
|
-
}
|
|
198
|
-
const mergedParameters = { ...(chatEnvelope.parameters ?? {}) };
|
|
199
|
-
const mergeParameters = (source) => {
|
|
200
|
-
if (!source) {
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
for (const [key, value] of Object.entries(source)) {
|
|
204
|
-
if (mergedParameters[key] !== undefined) {
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
mergedParameters[key] = jsonClone(value);
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
mergeParameters(collectParameters(payload));
|
|
211
|
-
if (providerMetadata) {
|
|
212
|
-
mergedParameters.metadata = jsonClone(providerMetadata);
|
|
213
|
-
}
|
|
214
|
-
if (passthrough.passthrough) {
|
|
215
|
-
for (const [key, value] of Object.entries(passthrough.passthrough)) {
|
|
216
|
-
mergedParameters[key] = jsonClone(value);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
if (Object.keys(mergedParameters).length) {
|
|
220
|
-
chatEnvelope.parameters = mergedParameters;
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
delete chatEnvelope.parameters;
|
|
224
|
-
}
|
|
225
|
-
try {
|
|
226
|
-
const bridgePolicy = resolveBridgePolicy({ protocol: 'anthropic-messages' });
|
|
227
|
-
const actions = resolvePolicyActions(bridgePolicy, 'request_inbound');
|
|
228
|
-
if (actions?.length) {
|
|
229
|
-
const actionState = createBridgeActionState({
|
|
230
|
-
messages: chatEnvelope.messages,
|
|
231
|
-
rawRequest: payload,
|
|
232
|
-
metadata: metadata
|
|
233
|
-
});
|
|
234
|
-
runBridgeActionPipeline({
|
|
235
|
-
stage: 'request_inbound',
|
|
236
|
-
actions,
|
|
237
|
-
protocol: bridgePolicy?.protocol ?? 'anthropic-messages',
|
|
238
|
-
moduleType: bridgePolicy?.moduleType ?? 'anthropic-messages',
|
|
239
|
-
requestId: ctx.requestId,
|
|
240
|
-
state: actionState
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
catch {
|
|
245
|
-
// best-effort metadata extraction
|
|
246
|
-
}
|
|
247
|
-
return chatEnvelope;
|
|
248
|
-
}
|
|
249
|
-
async fromChat(chat, ctx) {
|
|
250
|
-
// Ensure tool_use / tool_result ordering and per-session history for /v1/messages style entrypoints.
|
|
251
|
-
try {
|
|
252
|
-
const { applyToolSessionCompat } = await import('../tool-session-compat.js');
|
|
253
|
-
await applyToolSessionCompat(chat, ctx);
|
|
254
|
-
}
|
|
255
|
-
catch {
|
|
256
|
-
// best-effort compat; do not block outbound mapping
|
|
257
|
-
}
|
|
258
|
-
const model = chat.parameters?.model;
|
|
259
|
-
if (typeof model !== 'string' || !model.trim()) {
|
|
260
|
-
throw new Error('ChatEnvelope.parameters.model is required for anthropic-messages outbound conversion');
|
|
261
|
-
}
|
|
262
|
-
const baseRequest = {
|
|
263
|
-
model,
|
|
264
|
-
messages: chat.messages,
|
|
265
|
-
tools: chat.tools
|
|
266
|
-
};
|
|
267
|
-
const semanticsNode = readAnthropicSemantics(chat);
|
|
268
|
-
const explicitEmptyTools = (chat.metadata?.toolsFieldPresent === true) || hasExplicitEmptyToolsSemantics(chat);
|
|
269
|
-
const trimmedParameters = chat.parameters && typeof chat.parameters === 'object' ? chat.parameters : undefined;
|
|
270
|
-
if (trimmedParameters) {
|
|
271
|
-
for (const [key, value] of Object.entries(trimmedParameters)) {
|
|
272
|
-
if (ANTHROPIC_TOP_LEVEL_FIELDS.has(key) || key === 'stop') {
|
|
273
|
-
if (key === 'messages' || key === 'tools') {
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
baseRequest[key] = value;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
const passthroughMetadata = encodeMetadataPassthrough(chat.parameters, {
|
|
281
|
-
prefix: PASSTHROUGH_METADATA_PREFIX,
|
|
282
|
-
keys: PASSTHROUGH_PARAMETERS
|
|
283
|
-
});
|
|
284
|
-
if (passthroughMetadata) {
|
|
285
|
-
const rawMetadata = baseRequest.metadata;
|
|
286
|
-
const existingMetadata = isJsonObject(rawMetadata)
|
|
287
|
-
? jsonClone(rawMetadata)
|
|
288
|
-
: {};
|
|
289
|
-
for (const [key, value] of Object.entries(passthroughMetadata)) {
|
|
290
|
-
existingMetadata[key] = value;
|
|
291
|
-
}
|
|
292
|
-
baseRequest.metadata = existingMetadata;
|
|
293
|
-
}
|
|
294
|
-
if (baseRequest.max_output_tokens && !baseRequest.max_tokens) {
|
|
295
|
-
baseRequest.max_tokens = baseRequest.max_output_tokens;
|
|
296
|
-
}
|
|
297
|
-
// 出站阶段不再直接透传其它协议的 providerMetadata,避免跨协议打洞;
|
|
298
|
-
// Anthropic 自身入口的 metadata 已在入站阶段通过 collectParameters/encodeMetadataPassthrough
|
|
299
|
-
// 按白名单收集,这里仅依赖这些显式映射结果。
|
|
300
|
-
if (explicitEmptyTools && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
301
|
-
baseRequest.tools = [];
|
|
302
|
-
}
|
|
303
|
-
const protocolState = getProtocolState(chat.metadata, 'anthropic');
|
|
304
|
-
if (protocolState?.systemBlocks !== undefined) {
|
|
305
|
-
baseRequest.system = jsonClone(protocolState.systemBlocks);
|
|
306
|
-
}
|
|
307
|
-
else if (semanticsNode?.systemBlocks !== undefined) {
|
|
308
|
-
baseRequest.system = jsonClone(semanticsNode.systemBlocks);
|
|
309
|
-
}
|
|
310
|
-
if (chat.metadata &&
|
|
311
|
-
typeof chat.metadata === 'object' &&
|
|
312
|
-
chat.metadata.extraFields &&
|
|
313
|
-
typeof chat.metadata.extraFields === 'object' &&
|
|
314
|
-
chat.metadata.extraFields.anthropicMirror) {
|
|
315
|
-
baseRequest.__anthropicMirror = jsonClone(chat.metadata.extraFields.anthropicMirror ?? {});
|
|
316
|
-
}
|
|
317
|
-
else if (semanticsNode?.mirror && isJsonObject(semanticsNode.mirror)) {
|
|
318
|
-
baseRequest.__anthropicMirror = jsonClone(semanticsNode.mirror);
|
|
319
|
-
}
|
|
320
|
-
const payloadSource = buildAnthropicRequestFromOpenAIChat(baseRequest);
|
|
321
|
-
const payload = sanitizeAnthropicPayload(JSON.parse(JSON.stringify(payloadSource)));
|
|
322
|
-
if (chat.metadata?.toolsFieldPresent && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
323
|
-
payload.tools = [];
|
|
324
|
-
}
|
|
325
|
-
try {
|
|
326
|
-
const bridgePolicy = resolveBridgePolicy({ protocol: 'anthropic-messages' });
|
|
327
|
-
const actions = resolvePolicyActions(bridgePolicy, 'request_outbound');
|
|
328
|
-
if (actions?.length) {
|
|
329
|
-
const capturedToolResults = Array.isArray(chat.toolOutputs)
|
|
330
|
-
? chat.toolOutputs.map((entry) => ({
|
|
331
|
-
tool_call_id: entry.tool_call_id,
|
|
332
|
-
output: entry.content,
|
|
333
|
-
name: entry.name
|
|
334
|
-
}))
|
|
335
|
-
: undefined;
|
|
336
|
-
const actionState = createBridgeActionState({
|
|
337
|
-
messages: Array.isArray(payload.messages) ? payload.messages : undefined,
|
|
338
|
-
rawRequest: payload,
|
|
339
|
-
metadata: chat.metadata,
|
|
340
|
-
capturedToolResults
|
|
341
|
-
});
|
|
342
|
-
runBridgeActionPipeline({
|
|
343
|
-
stage: 'request_outbound',
|
|
344
|
-
actions,
|
|
345
|
-
protocol: bridgePolicy?.protocol ?? 'anthropic-messages',
|
|
346
|
-
moduleType: bridgePolicy?.moduleType ?? 'anthropic-messages',
|
|
347
|
-
requestId: ctx.requestId,
|
|
348
|
-
state: actionState
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
catch {
|
|
353
|
-
// ignore metadata propagation failures
|
|
354
|
-
}
|
|
355
|
-
sanitizeAnthropicPayload(payload);
|
|
356
|
-
return {
|
|
357
|
-
protocol: 'anthropic-messages',
|
|
358
|
-
direction: 'response',
|
|
359
|
-
payload,
|
|
360
|
-
meta: {
|
|
361
|
-
context: ctx
|
|
362
|
-
}
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
}
|
|
1
|
+
export { AnthropicSemanticMapper } from '../operation-table/semantic-mappers/anthropic-mapper.js';
|
|
@@ -1,8 +1 @@
|
|
|
1
|
-
|
|
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
|
-
}
|
|
1
|
+
export { ChatSemanticMapper, maybeAugmentApplyPatchErrorContent } from '../operation-table/semantic-mappers/chat-mapper.js';
|