@jsonstudio/llms 0.6.954 → 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.
Files changed (130) hide show
  1. package/dist/conversion/hub/operation-table/operation-table-runner.d.ts +18 -0
  2. package/dist/conversion/hub/operation-table/operation-table-runner.js +158 -0
  3. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.d.ts +8 -0
  4. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +303 -0
  5. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.d.ts +8 -0
  6. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +413 -0
  7. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.d.ts +7 -0
  8. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +841 -0
  9. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.d.ts +21 -0
  10. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +535 -0
  11. package/dist/conversion/hub/ops/operations.d.ts +19 -0
  12. package/dist/conversion/hub/ops/operations.js +126 -0
  13. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +9 -0
  14. package/dist/conversion/hub/pipeline/hub-pipeline.js +489 -19
  15. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +6 -0
  16. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +11 -0
  17. package/dist/conversion/hub/policy/policy-engine.js +41 -9
  18. package/dist/conversion/hub/policy/protocol-spec.d.ts +25 -0
  19. package/dist/conversion/hub/policy/protocol-spec.js +73 -23
  20. package/dist/conversion/hub/process/chat-process.js +252 -41
  21. package/dist/conversion/hub/response/provider-response.js +175 -2
  22. package/dist/conversion/hub/response/response-runtime.js +1 -1
  23. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +1 -8
  24. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +1 -365
  25. package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +1 -8
  26. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +1 -467
  27. package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +1 -7
  28. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +1 -903
  29. package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +1 -21
  30. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +1 -593
  31. package/dist/conversion/hub/tool-surface/tool-surface-engine.d.ts +18 -0
  32. package/dist/conversion/hub/tool-surface/tool-surface-engine.js +571 -0
  33. package/dist/conversion/responses/responses-openai-bridge.js +14 -2
  34. package/dist/conversion/shared/bridge-message-utils.js +2 -8
  35. package/dist/conversion/shared/bridge-policies.js +5 -105
  36. package/dist/conversion/shared/gemini-tool-utils.js +89 -15
  37. package/dist/conversion/shared/protocol-field-allowlists.d.ts +7 -0
  38. package/dist/conversion/shared/protocol-field-allowlists.js +145 -0
  39. package/dist/conversion/shared/reasoning-tool-normalizer.js +4 -2
  40. package/dist/conversion/shared/snapshot-hooks.js +166 -3
  41. package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
  42. package/dist/conversion/shared/text-markup-normalizer.js +345 -9
  43. package/dist/conversion/shared/thought-signature-validator.d.ts +52 -0
  44. package/dist/conversion/shared/thought-signature-validator.js +170 -0
  45. package/dist/conversion/shared/tool-argument-repairer.d.ts +39 -0
  46. package/dist/conversion/shared/tool-argument-repairer.js +56 -0
  47. package/dist/conversion/shared/tool-call-id-manager.d.ts +113 -0
  48. package/dist/conversion/shared/tool-call-id-manager.js +231 -0
  49. package/dist/conversion/shared/tool-canonicalizer.js +2 -11
  50. package/dist/router/virtual-router/bootstrap.js +54 -5
  51. package/dist/router/virtual-router/engine-selection.js +132 -42
  52. package/dist/router/virtual-router/engine.d.ts +3 -0
  53. package/dist/router/virtual-router/engine.js +142 -33
  54. package/dist/router/virtual-router/health-weighted.d.ts +25 -0
  55. package/dist/router/virtual-router/health-weighted.js +63 -0
  56. package/dist/router/virtual-router/load-balancer.d.ts +2 -0
  57. package/dist/router/virtual-router/load-balancer.js +45 -16
  58. package/dist/router/virtual-router/routing-instructions.js +17 -1
  59. package/dist/router/virtual-router/sticky-session-store.js +136 -24
  60. package/dist/router/virtual-router/stop-message-file-resolver.d.ts +1 -0
  61. package/dist/router/virtual-router/stop-message-file-resolver.js +74 -0
  62. package/dist/router/virtual-router/stop-message-state-sync.d.ts +15 -0
  63. package/dist/router/virtual-router/stop-message-state-sync.js +57 -0
  64. package/dist/router/virtual-router/types.d.ts +70 -0
  65. package/dist/servertool/clock/config.d.ts +7 -0
  66. package/dist/servertool/clock/config.js +27 -0
  67. package/dist/servertool/clock/daemon.d.ts +3 -0
  68. package/dist/servertool/clock/daemon.js +79 -0
  69. package/dist/servertool/clock/io.d.ts +2 -0
  70. package/dist/servertool/clock/io.js +13 -0
  71. package/dist/servertool/clock/paths.d.ts +4 -0
  72. package/dist/servertool/clock/paths.js +25 -0
  73. package/dist/servertool/clock/session-store.d.ts +3 -0
  74. package/dist/servertool/clock/session-store.js +56 -0
  75. package/dist/servertool/clock/state.d.ts +5 -0
  76. package/dist/servertool/clock/state.js +62 -0
  77. package/dist/servertool/clock/task-store.d.ts +5 -0
  78. package/dist/servertool/clock/task-store.js +4 -0
  79. package/dist/servertool/clock/tasks.d.ts +17 -0
  80. package/dist/servertool/clock/tasks.js +221 -0
  81. package/dist/servertool/clock/types.d.ts +36 -0
  82. package/dist/servertool/clock/types.js +1 -0
  83. package/dist/servertool/engine.d.ts +2 -0
  84. package/dist/servertool/engine.js +161 -7
  85. package/dist/servertool/followup-shadow.d.ts +16 -0
  86. package/dist/servertool/followup-shadow.js +145 -0
  87. package/dist/servertool/handlers/apply-patch-guard.js +1 -265
  88. package/dist/servertool/handlers/clock-auto.d.ts +1 -0
  89. package/dist/servertool/handlers/clock-auto.js +160 -0
  90. package/dist/servertool/handlers/clock.d.ts +1 -0
  91. package/dist/servertool/handlers/clock.js +197 -0
  92. package/dist/servertool/handlers/exec-command-guard.js +7 -555
  93. package/dist/servertool/handlers/followup-request-builder.d.ts +15 -7
  94. package/dist/servertool/handlers/followup-request-builder.js +248 -28
  95. package/dist/servertool/handlers/gemini-empty-reply-continue.js +62 -169
  96. package/dist/servertool/handlers/iflow-model-error-retry.js +18 -28
  97. package/dist/servertool/handlers/recursive-detection-guard.d.ts +1 -0
  98. package/dist/servertool/handlers/recursive-detection-guard.js +333 -0
  99. package/dist/servertool/handlers/stop-message-auto.js +47 -175
  100. package/dist/servertool/handlers/vision.d.ts +7 -1
  101. package/dist/servertool/handlers/vision.js +61 -117
  102. package/dist/servertool/handlers/web-search.d.ts +7 -1
  103. package/dist/servertool/handlers/web-search.js +122 -105
  104. package/dist/servertool/reenter-backend.d.ts +23 -0
  105. package/dist/servertool/reenter-backend.js +18 -0
  106. package/dist/servertool/server-side-tools.d.ts +3 -2
  107. package/dist/servertool/server-side-tools.js +64 -10
  108. package/dist/servertool/types.d.ts +92 -3
  109. package/dist/sse/json-to-sse/event-generators/responses.js +3 -21
  110. package/dist/sse/shared/serializers/responses-event-serializer.d.ts +8 -0
  111. package/dist/sse/shared/serializers/responses-event-serializer.js +19 -0
  112. package/dist/sse/shared/writer.js +24 -7
  113. package/dist/tools/apply-patch/execution-capturer.js +3 -1
  114. package/dist/tools/apply-patch/json/parse-loose.d.ts +3 -0
  115. package/dist/tools/apply-patch/json/parse-loose.js +139 -0
  116. package/dist/tools/apply-patch/patch-text/context-diff.d.ts +1 -0
  117. package/dist/tools/apply-patch/patch-text/context-diff.js +173 -0
  118. package/dist/tools/apply-patch/patch-text/git-diff.d.ts +1 -0
  119. package/dist/tools/apply-patch/patch-text/git-diff.js +138 -0
  120. package/dist/tools/apply-patch/patch-text/looks-like-patch.d.ts +1 -0
  121. package/dist/tools/apply-patch/patch-text/looks-like-patch.js +13 -0
  122. package/dist/tools/apply-patch/patch-text/normalize.d.ts +3 -0
  123. package/dist/tools/apply-patch/patch-text/normalize.js +262 -0
  124. package/dist/tools/apply-patch/structured/coercion.d.ts +3 -0
  125. package/dist/tools/apply-patch/structured/coercion.js +82 -0
  126. package/dist/tools/apply-patch/validation/shared.d.ts +3 -0
  127. package/dist/tools/apply-patch/validation/shared.js +6 -0
  128. package/dist/tools/apply-patch/validator.d.ts +2 -2
  129. package/dist/tools/apply-patch/validator.js +6 -556
  130. 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: options.context.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: options.context.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 'content_filter';
192
+ case 'stop_sequence': return 'stop';
193
193
  default: return 'stop';
194
194
  }
195
195
  };
@@ -1,8 +1 @@
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
- }
1
+ export { AnthropicSemanticMapper } from '../operation-table/semantic-mappers/anthropic-mapper.js';
@@ -1,365 +1 @@
1
- import { isJsonObject, jsonClone } from '../types/json.js';
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
- 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
- }
1
+ export { ChatSemanticMapper, maybeAugmentApplyPatchErrorContent } from '../operation-table/semantic-mappers/chat-mapper.js';