@jsonstudio/llms 0.6.141 → 0.6.187

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 (61) hide show
  1. package/dist/conversion/codecs/gemini-openai-codec.js +15 -1
  2. package/dist/conversion/compat/actions/auto-thinking.d.ts +6 -0
  3. package/dist/conversion/compat/actions/auto-thinking.js +25 -0
  4. package/dist/conversion/compat/actions/field-mapping.d.ts +14 -0
  5. package/dist/conversion/compat/actions/field-mapping.js +155 -0
  6. package/dist/conversion/compat/actions/qwen-transform.d.ts +3 -0
  7. package/dist/conversion/compat/actions/qwen-transform.js +209 -0
  8. package/dist/conversion/compat/actions/request-rules.d.ts +24 -0
  9. package/dist/conversion/compat/actions/request-rules.js +63 -0
  10. package/dist/conversion/compat/actions/response-blacklist.d.ts +14 -0
  11. package/dist/conversion/compat/actions/response-blacklist.js +85 -0
  12. package/dist/conversion/compat/actions/response-normalize.d.ts +5 -0
  13. package/dist/conversion/compat/actions/response-normalize.js +121 -0
  14. package/dist/conversion/compat/actions/response-validate.d.ts +5 -0
  15. package/dist/conversion/compat/actions/response-validate.js +76 -0
  16. package/dist/conversion/compat/actions/snapshot.d.ts +8 -0
  17. package/dist/conversion/compat/actions/snapshot.js +21 -0
  18. package/dist/conversion/compat/actions/tool-schema.d.ts +6 -0
  19. package/dist/conversion/compat/actions/tool-schema.js +91 -0
  20. package/dist/conversion/compat/actions/universal-shape-filter.d.ts +74 -0
  21. package/dist/conversion/compat/actions/universal-shape-filter.js +382 -0
  22. package/dist/conversion/compat/profiles/chat-glm.json +187 -13
  23. package/dist/conversion/compat/profiles/chat-iflow.json +177 -9
  24. package/dist/conversion/compat/profiles/chat-lmstudio.json +10 -2
  25. package/dist/conversion/compat/profiles/chat-qwen.json +14 -10
  26. package/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +7 -2
  27. package/dist/conversion/hub/pipeline/compat/compat-engine.js +409 -5
  28. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +47 -0
  29. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +2 -0
  30. package/dist/conversion/hub/pipeline/hub-pipeline.js +35 -1
  31. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +2 -2
  32. package/dist/conversion/hub/pipeline/target-utils.js +3 -0
  33. package/dist/conversion/hub/response/response-runtime.js +19 -2
  34. package/dist/conversion/responses/responses-host-policy.d.ts +6 -0
  35. package/dist/conversion/responses/responses-host-policy.js +14 -0
  36. package/dist/conversion/responses/responses-openai-bridge.js +51 -2
  37. package/dist/conversion/shared/anthropic-message-utils.js +6 -0
  38. package/dist/conversion/shared/responses-conversation-store.js +3 -26
  39. package/dist/conversion/shared/responses-reasoning-registry.d.ts +4 -0
  40. package/dist/conversion/shared/responses-reasoning-registry.js +62 -1
  41. package/dist/conversion/shared/responses-response-utils.js +23 -1
  42. package/dist/conversion/shared/tool-canonicalizer.d.ts +2 -0
  43. package/dist/conversion/shared/tool-filter-pipeline.js +11 -0
  44. package/dist/router/virtual-router/bootstrap.js +218 -39
  45. package/dist/router/virtual-router/classifier.js +19 -52
  46. package/dist/router/virtual-router/context-advisor.d.ts +21 -0
  47. package/dist/router/virtual-router/context-advisor.js +76 -0
  48. package/dist/router/virtual-router/engine.d.ts +11 -26
  49. package/dist/router/virtual-router/engine.js +191 -386
  50. package/dist/router/virtual-router/features.js +24 -621
  51. package/dist/router/virtual-router/health-manager.js +2 -7
  52. package/dist/router/virtual-router/message-utils.d.ts +7 -0
  53. package/dist/router/virtual-router/message-utils.js +66 -0
  54. package/dist/router/virtual-router/provider-registry.js +6 -2
  55. package/dist/router/virtual-router/token-estimator.d.ts +2 -0
  56. package/dist/router/virtual-router/token-estimator.js +16 -0
  57. package/dist/router/virtual-router/tool-signals.d.ts +13 -0
  58. package/dist/router/virtual-router/tool-signals.js +403 -0
  59. package/dist/router/virtual-router/types.d.ts +21 -7
  60. package/dist/router/virtual-router/types.js +1 -0
  61. package/package.json +2 -2
@@ -1,4 +1,5 @@
1
1
  import { ensureBridgeInstructions } from '../shared/bridge-instructions.js';
2
+ import { evaluateResponsesHostPolicy } from './responses-host-policy.js';
2
3
  import { convertMessagesToBridgeInput, convertBridgeInputToChatMessages } from '../shared/bridge-message-utils.js';
3
4
  import { createToolCallIdTransformer, enforceToolCallIdStyle, resolveToolCallIdStyle, stripInternalToolingMetadata, sanitizeResponsesFunctionName } from '../shared/responses-tool-utils.js';
4
5
  import { mapBridgeToolsToChat, mapChatToolsToBridge } from '../shared/tool-mapping.js';
@@ -8,6 +9,7 @@ import { normalizeMessageReasoningTools } from '../shared/reasoning-tool-normali
8
9
  import { createBridgeActionState, runBridgeActionPipeline } from '../shared/bridge-actions.js';
9
10
  import { resolveBridgePolicy, resolvePolicyActions } from '../shared/bridge-policies.js';
10
11
  import { buildResponsesOutputFromChat } from '../shared/responses-output-builder.js';
12
+ import { consumeResponsesPayloadSnapshot, consumeResponsesPassthrough } from '../shared/responses-reasoning-registry.js';
11
13
  function isObject(v) {
12
14
  return !!v && typeof v === 'object' && !Array.isArray(v);
13
15
  }
@@ -257,12 +259,20 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
257
259
  else if (metadataExtraFields?.include !== undefined && out.include === undefined) {
258
260
  out.include = metadataExtraFields.include;
259
261
  }
260
- if (ctx?.store !== undefined && out.store === undefined) {
262
+ const stripHostFields = shouldStripHostManagedFields(ctx);
263
+ if (stripHostFields) {
264
+ delete out.store;
265
+ }
266
+ else if (ctx?.store !== undefined && out.store === undefined) {
261
267
  out.store = ctx.store;
262
268
  }
263
269
  else if (metadataExtraFields?.store !== undefined && out.store === undefined) {
264
270
  out.store = metadataExtraFields.store;
265
271
  }
272
+ else if (out.store === undefined) {
273
+ // Chat 入口无 store 概念,但 Responses provider 仍要求显式声明。
274
+ out.store = false;
275
+ }
266
276
  if (ctx?.toolChoice !== undefined && out.tool_choice === undefined) {
267
277
  out.tool_choice = ctx.toolChoice;
268
278
  }
@@ -433,6 +443,17 @@ export function buildResponsesPayloadFromChat(payload, context) {
433
443
  const response = unwrapData(payload);
434
444
  if (!response || typeof response !== 'object')
435
445
  return payload;
446
+ const snapshotKey = resolveSnapshotLookupKey(response, context);
447
+ if (snapshotKey) {
448
+ const passthrough = consumeResponsesPassthrough(snapshotKey);
449
+ if (passthrough) {
450
+ return passthrough;
451
+ }
452
+ const snapshot = consumeResponsesPayloadSnapshot(snapshotKey);
453
+ if (snapshot) {
454
+ return snapshot;
455
+ }
456
+ }
436
457
  if (response.object === 'response' && Array.isArray(response.output)) {
437
458
  return response;
438
459
  }
@@ -499,10 +520,22 @@ export function buildResponsesPayloadFromChat(payload, context) {
499
520
  out.required_action = outputBuild.requiredAction;
500
521
  // Do not inject captured tool results here; keep Chat back-half behavior standard.
501
522
  if (context) {
502
- for (const k of ['metadata', 'parallel_tool_calls', 'tool_choice', 'include', 'store']) {
523
+ for (const k of ['metadata', 'parallel_tool_calls', 'tool_choice', 'include']) {
503
524
  if (context[k] !== undefined)
504
525
  out[k] = context[k];
505
526
  }
527
+ if (!shouldStripHostManagedFields(context) && context.store !== undefined) {
528
+ out.store = context.store;
529
+ }
530
+ }
531
+ if (typeof response.request_id === 'string') {
532
+ out.request_id = response.request_id;
533
+ }
534
+ else if (typeof response.id === 'string') {
535
+ out.request_id = response.id;
536
+ }
537
+ else if (typeof context?.requestId === 'string') {
538
+ out.request_id = context.requestId;
506
539
  }
507
540
  if (out.metadata) {
508
541
  stripInternalToolingMetadata(out.metadata);
@@ -524,6 +557,22 @@ function unwrapData(value) {
524
557
  }
525
558
  return current;
526
559
  }
560
+ function resolveSnapshotLookupKey(response, context) {
561
+ if (typeof response?.request_id === 'string') {
562
+ return response.request_id;
563
+ }
564
+ if (typeof context?.requestId === 'string') {
565
+ return context.requestId;
566
+ }
567
+ if (typeof response?.id === 'string') {
568
+ return response.id;
569
+ }
570
+ return undefined;
571
+ }
572
+ function shouldStripHostManagedFields(context) {
573
+ const result = evaluateResponsesHostPolicy(context, typeof context?.targetProtocol === 'string' ? context?.targetProtocol : undefined);
574
+ return result.shouldStripHostManagedFields;
575
+ }
527
576
  export function extractRequestIdFromResponse(response) {
528
577
  if (response && typeof response === 'object' && 'metadata' in response && response.metadata && typeof response.metadata === 'object') {
529
578
  const meta = response.metadata;
@@ -336,6 +336,12 @@ export function buildOpenAIChatFromAnthropic(payload) {
336
336
  request.top_p = body.top_p;
337
337
  if (typeof body.stream === 'boolean')
338
338
  request.stream = body.stream;
339
+ if (typeof body.id === 'string') {
340
+ request.request_id = body.id;
341
+ }
342
+ else if (typeof body.request_id === 'string') {
343
+ request.request_id = body.request_id;
344
+ }
339
345
  if ('tool_choice' in body)
340
346
  request.tool_choice = body.tool_choice;
341
347
  const normalizedTools = mapAnthropicToolsToChat(body.tools);
@@ -205,29 +205,16 @@ class ResponsesConversationStore {
205
205
  }
206
206
  resumeConversation(responseId, submitPayload, options) {
207
207
  if (typeof responseId !== 'string' || !responseId.trim()) {
208
- raiseResumeError('Responses conversation requires valid response_id', {
209
- code: 'RESPONSES_RESUME_MISSING_ID',
210
- status: 422,
211
- origin: 'client'
212
- });
208
+ throw new Error('Responses conversation requires valid response_id');
213
209
  }
214
210
  this.prune();
215
211
  const entry = this.responseIndex.get(responseId);
216
212
  if (!entry) {
217
- raiseResumeError('Responses conversation expired or not found', {
218
- code: 'RESPONSES_RESUME_NOT_FOUND',
219
- status: 500,
220
- origin: 'server',
221
- details: { responseId }
222
- });
213
+ throw new Error('Responses conversation expired or not found');
223
214
  }
224
215
  const toolOutputs = Array.isArray(submitPayload.tool_outputs) ? submitPayload.tool_outputs : [];
225
216
  if (!toolOutputs.length) {
226
- raiseResumeError('tool_outputs array is required when submitting Responses tool results', {
227
- code: 'RESPONSES_RESUME_MISSING_OUTPUTS',
228
- status: 422,
229
- origin: 'client'
230
- });
217
+ throw new Error('tool_outputs array is required when submitting Responses tool results');
231
218
  }
232
219
  const mergedInput = coerceInputArray(entry.input);
233
220
  const normalizedOutputs = normalizeSubmittedToolOutputs(toolOutputs);
@@ -294,16 +281,6 @@ class ResponsesConversationStore {
294
281
  }
295
282
  const store = new ResponsesConversationStore();
296
283
  const RESPONSES_DEBUG = (process.env.ROUTECODEX_RESPONSES_DEBUG || '').trim() === '1';
297
- function raiseResumeError(message, options) {
298
- const err = new Error(message);
299
- err.code = options?.code ?? 'RESPONSES_RESUME_ERROR';
300
- err.status = options?.status;
301
- err.origin = options?.origin;
302
- if (options?.details) {
303
- err.details = options.details;
304
- }
305
- throw err;
306
- }
307
284
  export function captureResponsesRequestContext(args) {
308
285
  try {
309
286
  if (RESPONSES_DEBUG) {
@@ -6,3 +6,7 @@ export declare function registerResponsesReasoning(id: unknown, segments: string
6
6
  export declare function consumeResponsesReasoning(id: unknown): string[] | undefined;
7
7
  export declare function registerResponsesOutputTextMeta(id: unknown, meta: ResponsesOutputTextMeta | undefined): void;
8
8
  export declare function consumeResponsesOutputTextMeta(id: unknown): ResponsesOutputTextMeta | undefined;
9
+ export declare function registerResponsesPayloadSnapshot(id: unknown, snapshot: Record<string, unknown> | undefined): void;
10
+ export declare function consumeResponsesPayloadSnapshot(id: unknown): Record<string, unknown> | undefined;
11
+ export declare function registerResponsesPassthrough(id: unknown, payload: Record<string, unknown> | undefined): void;
12
+ export declare function consumeResponsesPassthrough(id: unknown): Record<string, unknown> | undefined;
@@ -11,10 +11,27 @@ function pruneEntry(id) {
11
11
  const entry = registry.get(id);
12
12
  if (!entry)
13
13
  return;
14
- if (!entry.reasoning && !entry.outputText) {
14
+ if (!entry.reasoning && !entry.outputText && !entry.payloadSnapshot && !entry.passthroughPayload) {
15
15
  registry.delete(id);
16
16
  }
17
17
  }
18
+ function cloneSnapshot(snapshot) {
19
+ try {
20
+ const structuredCloneImpl = globalThis.structuredClone;
21
+ if (typeof structuredCloneImpl === 'function') {
22
+ return structuredCloneImpl(snapshot);
23
+ }
24
+ }
25
+ catch {
26
+ /* ignore structuredClone failures */
27
+ }
28
+ try {
29
+ return JSON.parse(JSON.stringify(snapshot));
30
+ }
31
+ catch {
32
+ return undefined;
33
+ }
34
+ }
18
35
  export function registerResponsesReasoning(id, segments) {
19
36
  if (typeof id !== 'string')
20
37
  return;
@@ -59,3 +76,47 @@ export function consumeResponsesOutputTextMeta(id) {
59
76
  pruneEntry(id);
60
77
  return value;
61
78
  }
79
+ export function registerResponsesPayloadSnapshot(id, snapshot) {
80
+ if (typeof id !== 'string')
81
+ return;
82
+ if (!snapshot || typeof snapshot !== 'object')
83
+ return;
84
+ const clone = cloneSnapshot(snapshot);
85
+ if (!clone)
86
+ return;
87
+ const entry = ensureEntry(id);
88
+ entry.payloadSnapshot = clone;
89
+ }
90
+ export function consumeResponsesPayloadSnapshot(id) {
91
+ if (typeof id !== 'string')
92
+ return undefined;
93
+ const entry = registry.get(id);
94
+ if (!entry?.payloadSnapshot)
95
+ return undefined;
96
+ const clone = cloneSnapshot(entry.payloadSnapshot) ?? entry.payloadSnapshot;
97
+ entry.payloadSnapshot = undefined;
98
+ pruneEntry(id);
99
+ return clone;
100
+ }
101
+ export function registerResponsesPassthrough(id, payload) {
102
+ if (typeof id !== 'string')
103
+ return;
104
+ if (!payload || typeof payload !== 'object')
105
+ return;
106
+ const clone = cloneSnapshot(payload);
107
+ if (!clone)
108
+ return;
109
+ const entry = ensureEntry(id);
110
+ entry.passthroughPayload = clone;
111
+ }
112
+ export function consumeResponsesPassthrough(id) {
113
+ if (typeof id !== 'string')
114
+ return undefined;
115
+ const entry = registry.get(id);
116
+ if (!entry?.passthroughPayload)
117
+ return undefined;
118
+ const clone = cloneSnapshot(entry.passthroughPayload) ?? entry.passthroughPayload;
119
+ entry.passthroughPayload = undefined;
120
+ pruneEntry(id);
121
+ return clone;
122
+ }
@@ -4,6 +4,7 @@ import { extractOutputSegments } from './output-content-normalizer.js';
4
4
  import { sanitizeReasoningTaggedText } from './reasoning-utils.js';
5
5
  import { createBridgeActionState, runBridgeActionPipeline } from './bridge-actions.js';
6
6
  import { resolveBridgePolicy, resolvePolicyActions } from './bridge-policies.js';
7
+ import { registerResponsesPayloadSnapshot, registerResponsesPassthrough } from './responses-reasoning-registry.js';
7
8
  function selectCallId(entry) {
8
9
  const candidates = [
9
10
  entry?.call_id,
@@ -153,13 +154,27 @@ function collectRawReasoningSegments(response) {
153
154
  }
154
155
  return segments;
155
156
  }
157
+ function registerPassthroughSnapshot(payload) {
158
+ const ids = new Set();
159
+ const requestId = typeof payload?.request_id === 'string' ? payload.request_id.trim() : '';
160
+ const id = typeof payload?.id === 'string' ? payload.id.trim() : '';
161
+ if (requestId.length)
162
+ ids.add(requestId);
163
+ if (id.length)
164
+ ids.add(id);
165
+ for (const candidate of ids) {
166
+ registerResponsesPassthrough(candidate, payload);
167
+ }
168
+ }
156
169
  export function buildChatResponseFromResponses(payload) {
157
170
  if (!payload || typeof payload !== 'object')
158
171
  return payload;
159
172
  const response = unwrapResponsesResponse(payload);
160
173
  if (!response) {
161
- if (Array.isArray(payload.choices))
174
+ if (Array.isArray(payload.choices)) {
175
+ registerPassthroughSnapshot(payload);
162
176
  return payload;
177
+ }
163
178
  return payload;
164
179
  }
165
180
  const id = typeof response.id === 'string' ? response.id : `resp_${Date.now()}`;
@@ -235,5 +250,12 @@ export function buildChatResponseFromResponses(payload) {
235
250
  if (usage !== undefined) {
236
251
  chat.usage = usage;
237
252
  }
253
+ const requestId = typeof response.request_id === 'string'
254
+ ? response.request_id
255
+ : (typeof response.id === 'string' ? response.id : undefined);
256
+ if (requestId) {
257
+ chat.request_id = requestId;
258
+ }
259
+ registerResponsesPayloadSnapshot(id, response);
238
260
  return chat;
239
261
  }
@@ -0,0 +1,2 @@
1
+ export declare function canonicalizeChatResponseTools(payload: unknown): unknown;
2
+ export default canonicalizeChatResponseTools;
@@ -1,5 +1,6 @@
1
1
  import { FilterEngine } from '../../filters/index.js';
2
2
  import { loadFieldMapConfig } from '../../filters/utils/fieldmap-loader.js';
3
+ import { normalizeChatResponseReasoningTools } from './reasoning-tool-normalizer.js';
3
4
  import { createSnapshotWriter } from './snapshot-utils.js';
4
5
  const REQUEST_FILTER_STAGES = [
5
6
  'request_pre',
@@ -191,6 +192,16 @@ export async function runChatResponseToolFilters(chatJson, options = {}) {
191
192
  snapshot(stage, payload);
192
193
  };
193
194
  recordStage('resp_process_tool_filters_input', chatJson);
195
+ try {
196
+ if (chatJson && typeof chatJson === 'object') {
197
+ normalizeChatResponseReasoningTools(chatJson, {
198
+ idPrefixBase: 'reasoning_choice'
199
+ });
200
+ }
201
+ }
202
+ catch {
203
+ // best-effort; do not block response flow on reasoning parsing issues
204
+ }
194
205
  const engine = new FilterEngine();
195
206
  const registeredStages = new Set();
196
207
  const register = (filter) => {