@jsonstudio/llms 0.6.2979 → 0.6.3238

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 (246) hide show
  1. package/dist/conversion/args-mapping.js +8 -0
  2. package/dist/conversion/{shared/bridge-actions.js → bridge-actions.js} +2 -1
  3. package/dist/conversion/{shared/bridge-id-utils.js → bridge-id-utils.js} +1 -1
  4. package/dist/conversion/{shared/bridge-instructions.js → bridge-instructions.js} +1 -1
  5. package/dist/conversion/{shared/bridge-message-utils.d.ts → bridge-message-utils.d.ts} +1 -1
  6. package/dist/conversion/{shared/bridge-message-utils.js → bridge-message-utils.js} +5 -149
  7. package/dist/conversion/{shared/bridge-metadata.js → bridge-metadata.js} +1 -1
  8. package/dist/conversion/{shared/bridge-policies.js → bridge-policies.js} +1 -1
  9. package/dist/conversion/codecs/gemini-openai-codec.js +27 -8
  10. package/dist/conversion/codecs/responses-openai-codec.js +1 -1
  11. package/dist/conversion/{shared/compaction-detect.d.ts → compaction-detect.d.ts} +1 -1
  12. package/dist/conversion/compaction-detect.js +4 -0
  13. package/dist/conversion/compat/actions/apply-patch-fixer.js +2 -2
  14. package/dist/conversion/compat/actions/deepseek-web-response.d.ts +0 -1
  15. package/dist/conversion/compat/actions/deepseek-web-response.js +15 -405
  16. package/dist/conversion/compat/actions/harvest-tool-calls-from-text.js +1 -1
  17. package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.js +1 -1
  18. package/dist/conversion/compat/actions/qwen-transform.js +74 -2
  19. package/dist/conversion/compat/actions/snapshot.js +1 -1
  20. package/dist/conversion/compat/antigravity-session-signature.js +36 -0
  21. package/dist/conversion/compat/profiles/chat-deepseek-web.json +0 -22
  22. package/dist/conversion/compat/profiles/chat-glm.json +251 -72
  23. package/dist/conversion/compat/profiles/chat-iflow.json +174 -39
  24. package/dist/conversion/compat/profiles/chat-lmstudio.json +43 -14
  25. package/dist/conversion/hub/operation-table/operation-table-runner.js +2 -2
  26. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +1 -1
  27. package/dist/conversion/hub/operation-table/semantic-mappers/archive/chat-mapper.archive.d.ts +8 -0
  28. package/dist/conversion/hub/operation-table/semantic-mappers/archive/chat-mapper.archive.js +404 -0
  29. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +5 -381
  30. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +2 -2
  31. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +2 -8
  32. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +1 -0
  33. package/dist/conversion/hub/pipeline/hub-pipeline.js +50 -3
  34. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +1 -1
  35. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +62 -0
  36. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +3 -1
  37. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +1 -1
  38. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/chat-process-semantics-bridge.d.ts +1 -1
  39. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +42 -29
  40. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.js +12 -0
  41. package/dist/conversion/hub/policy/protocol-spec.js +1 -1
  42. package/dist/conversion/hub/process/chat-process-clock-reminders.js +1 -1
  43. package/dist/conversion/hub/process/chat-process-clock-tools.js +1 -1
  44. package/dist/conversion/hub/process/chat-process-continue-execution.js +1 -1
  45. package/dist/conversion/hub/process/chat-process-servertool-orchestration.js +1 -1
  46. package/dist/conversion/hub/process/chat-process-web-search.js +1 -1
  47. package/dist/conversion/hub/response/provider-response.js +14 -5
  48. package/dist/conversion/hub/response/response-mappers.js +23 -1
  49. package/dist/conversion/hub/response/response-runtime.js +28 -5
  50. package/dist/conversion/hub/snapshot-recorder.js +3 -92
  51. package/dist/conversion/hub/tool-governance/engine.d.ts +8 -0
  52. package/dist/conversion/hub/tool-governance/engine.js +40 -193
  53. package/dist/conversion/hub/tool-governance/rules.js +73 -69
  54. package/dist/conversion/hub/tool-surface/tool-surface-engine.js +1 -1
  55. package/dist/conversion/index.d.ts +1 -2
  56. package/dist/conversion/index.js +1 -2
  57. package/dist/conversion/{shared/jsonish.js → jsonish.js} +1 -1
  58. package/dist/conversion/{shared/mcp-injection.js → mcp-injection.js} +1 -1
  59. package/dist/conversion/media.js +4 -0
  60. package/dist/conversion/{shared/metadata-passthrough.d.ts → metadata-passthrough.d.ts} +1 -1
  61. package/dist/conversion/{shared/metadata-passthrough.js → metadata-passthrough.js} +2 -2
  62. package/dist/conversion/payload-budget.js +47 -0
  63. package/dist/conversion/protocol-field-allowlists.d.ts +7 -0
  64. package/dist/conversion/protocol-field-allowlists.js +9 -0
  65. package/dist/conversion/{shared/protocol-state.d.ts → protocol-state.d.ts} +2 -2
  66. package/dist/conversion/{shared/protocol-state.js → protocol-state.js} +2 -2
  67. package/dist/conversion/{shared/errors.d.ts → provider-protocol-error.d.ts} +0 -3
  68. package/dist/conversion/provider-protocol-error.js +25 -0
  69. package/dist/conversion/responses/responses-openai-bridge/response-payload.js +8 -5
  70. package/dist/conversion/responses/responses-openai-bridge/types.d.ts +1 -1
  71. package/dist/conversion/responses/responses-openai-bridge.d.ts +1 -1
  72. package/dist/conversion/responses/responses-openai-bridge.js +43 -10
  73. package/dist/conversion/{shared/runtime-metadata.d.ts → runtime-metadata.d.ts} +1 -1
  74. package/dist/conversion/{shared/runtime-metadata.js → runtime-metadata.js} +2 -2
  75. package/dist/conversion/shared/anthropic-message-utils.js +19 -8
  76. package/dist/conversion/shared/chat-request-filters.d.ts +3 -4
  77. package/dist/conversion/shared/chat-request-filters.js +22 -78
  78. package/dist/conversion/shared/gemini-tool-utils.d.ts +1 -1
  79. package/dist/conversion/shared/openai-finalizer.js +1 -0
  80. package/dist/conversion/shared/openai-message-normalize.js +2 -2
  81. package/dist/conversion/shared/reasoning-normalizer.js +6 -0
  82. package/dist/conversion/shared/reasoning-utils.js +5 -2
  83. package/dist/conversion/shared/responses-conversation-store.js +1 -1
  84. package/dist/conversion/shared/responses-output-builder.js +55 -11
  85. package/dist/conversion/shared/responses-reasoning-registry.d.ts +14 -2
  86. package/dist/conversion/shared/responses-reasoning-registry.js +34 -6
  87. package/dist/conversion/shared/responses-response-utils.js +99 -9
  88. package/dist/conversion/shared/responses-tool-utils.js +1 -1
  89. package/dist/conversion/shared/text-markup-normalizer/normalize.d.ts +1 -1
  90. package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -2
  91. package/dist/conversion/shared/text-markup-normalizer.js +1 -1
  92. package/dist/conversion/shared/tool-filter-pipeline.js +1 -1
  93. package/dist/conversion/shared/tool-governor.js +3 -3
  94. package/dist/conversion/shared/tool-mapping.d.ts +1 -1
  95. package/dist/conversion/{shared/snapshot-utils.d.ts → snapshot-utils.d.ts} +11 -0
  96. package/dist/conversion/{shared/snapshot-utils.js → snapshot-utils.js} +14 -23
  97. package/dist/conversion/types/text-markup-normalizer.d.ts +13 -0
  98. package/dist/conversion/types/text-markup-normalizer.js +1 -0
  99. package/dist/filters/special/request-tools-normalize.js +1 -1
  100. package/dist/filters/special/response-tool-text-canonicalize.js +2 -2
  101. package/dist/native/router_hotpath_napi.node +0 -0
  102. package/dist/quota/quota-manager.js +31 -59
  103. package/dist/quota/quota-state.js +14 -7
  104. package/dist/router/virtual-router/bootstrap/profile-builder.d.ts +1 -0
  105. package/dist/router/virtual-router/bootstrap/profile-builder.js +13 -0
  106. package/dist/router/virtual-router/bootstrap/provider-normalization.d.ts +2 -0
  107. package/dist/router/virtual-router/bootstrap/provider-normalization.js +4 -1
  108. package/dist/router/virtual-router/bootstrap/streaming-helpers.d.ts +7 -0
  109. package/dist/router/virtual-router/bootstrap/streaming-helpers.js +44 -0
  110. package/dist/router/virtual-router/bootstrap.js +2 -0
  111. package/dist/router/virtual-router/engine/routing-state/store.d.ts +1 -2
  112. package/dist/router/virtual-router/engine/routing-state/store.js +2 -2
  113. package/dist/router/virtual-router/engine-legacy/config.d.ts +11 -0
  114. package/dist/router/virtual-router/engine-legacy/config.js +108 -0
  115. package/dist/router/virtual-router/engine-legacy/direct-model.d.ts +10 -0
  116. package/dist/router/virtual-router/engine-legacy/direct-model.js +38 -0
  117. package/dist/router/virtual-router/engine-legacy/health.d.ts +13 -0
  118. package/dist/router/virtual-router/engine-legacy/health.js +104 -0
  119. package/dist/router/virtual-router/engine-legacy/helpers.d.ts +16 -0
  120. package/dist/router/virtual-router/engine-legacy/helpers.js +226 -0
  121. package/dist/router/virtual-router/engine-legacy/route-finalize.d.ts +9 -0
  122. package/dist/router/virtual-router/engine-legacy/route-finalize.js +84 -0
  123. package/dist/router/virtual-router/engine-legacy/route-selection.d.ts +17 -0
  124. package/dist/router/virtual-router/engine-legacy/route-selection.js +205 -0
  125. package/dist/router/virtual-router/engine-legacy/route-state-allowlist.d.ts +3 -0
  126. package/dist/router/virtual-router/engine-legacy/route-state-allowlist.js +36 -0
  127. package/dist/router/virtual-router/engine-legacy/route-state.d.ts +12 -0
  128. package/dist/router/virtual-router/engine-legacy/route-state.js +386 -0
  129. package/dist/router/virtual-router/engine-legacy/route-utils.d.ts +19 -0
  130. package/dist/router/virtual-router/engine-legacy/route-utils.js +212 -0
  131. package/dist/router/virtual-router/engine-legacy/routing.d.ts +8 -0
  132. package/dist/router/virtual-router/engine-legacy/routing.js +8 -0
  133. package/dist/router/virtual-router/engine-legacy/selection-core.d.ts +28 -0
  134. package/dist/router/virtual-router/engine-legacy/selection-core.js +112 -0
  135. package/dist/router/virtual-router/engine-legacy/selection-state.d.ts +16 -0
  136. package/dist/router/virtual-router/engine-legacy/selection-state.js +187 -0
  137. package/dist/router/virtual-router/engine-legacy/state-accessors.d.ts +21 -0
  138. package/dist/router/virtual-router/engine-legacy/state-accessors.js +118 -0
  139. package/dist/router/virtual-router/engine-legacy.d.ts +123 -0
  140. package/dist/router/virtual-router/engine-legacy.js +194 -0
  141. package/dist/router/virtual-router/engine-logging.d.ts +2 -0
  142. package/dist/router/virtual-router/engine-logging.js +7 -2
  143. package/dist/router/virtual-router/engine-selection/key-parsing.js +0 -3
  144. package/dist/router/virtual-router/engine-selection/native-chat-request-filter-semantics.d.ts +1 -0
  145. package/dist/router/virtual-router/engine-selection/native-chat-request-filter-semantics.js +54 -0
  146. package/dist/router/virtual-router/engine-selection/native-hub-bridge-policy-semantics.d.ts +10 -0
  147. package/dist/router/virtual-router/engine-selection/native-hub-bridge-policy-semantics.js +67 -0
  148. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-governance-semantics.d.ts +30 -0
  149. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-governance-semantics.js +202 -0
  150. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-semantic-mappers.d.ts +2 -0
  151. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-semantic-mappers.js +83 -0
  152. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +43 -2
  153. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +75 -0
  154. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +205 -0
  155. package/dist/router/virtual-router/engine-selection/native-snapshot-hooks.d.ts +3 -0
  156. package/dist/router/virtual-router/engine-selection/native-snapshot-hooks.js +109 -0
  157. package/dist/router/virtual-router/engine-selection/native-virtual-router-engine-proxy.d.ts +16 -0
  158. package/dist/router/virtual-router/engine-selection/native-virtual-router-engine-proxy.js +14 -0
  159. package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.d.ts +2 -0
  160. package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.js +86 -0
  161. package/dist/router/virtual-router/engine-selection/tier-selection-quota-integration.js +100 -0
  162. package/dist/router/virtual-router/engine-selection/tier-selection-select.js +99 -0
  163. package/dist/router/virtual-router/engine.d.ts +22 -105
  164. package/dist/router/virtual-router/engine.js +274 -1641
  165. package/dist/router/virtual-router/load-balancer.d.ts +8 -0
  166. package/dist/router/virtual-router/load-balancer.js +65 -2
  167. package/dist/router/virtual-router/provider-registry.js +2 -0
  168. package/dist/router/virtual-router/routing-instructions/clean.d.ts +3 -0
  169. package/dist/router/virtual-router/routing-instructions/clean.js +34 -0
  170. package/dist/router/virtual-router/routing-instructions/parse.d.ts +18 -0
  171. package/dist/router/virtual-router/routing-instructions/parse.js +377 -0
  172. package/dist/router/virtual-router/routing-instructions/state.d.ts +4 -0
  173. package/dist/router/virtual-router/routing-instructions/state.js +245 -0
  174. package/dist/router/virtual-router/routing-instructions/types.d.ts +70 -0
  175. package/dist/router/virtual-router/routing-instructions/types.js +2 -0
  176. package/dist/router/virtual-router/routing-instructions.d.ts +5 -89
  177. package/dist/router/virtual-router/routing-instructions.js +4 -655
  178. package/dist/router/virtual-router/sticky-session-store.d.ts +4 -0
  179. package/dist/router/virtual-router/sticky-session-store.js +19 -81
  180. package/dist/router/virtual-router/tool-signals.js +21 -3
  181. package/dist/router/virtual-router/types.d.ts +4 -0
  182. package/dist/servertool/clock/session-scope.js +32 -1
  183. package/dist/servertool/engine.js +79 -8
  184. package/dist/servertool/handlers/antigravity-thought-signature-bootstrap.js +1 -1
  185. package/dist/servertool/handlers/clock-auto.js +1 -1
  186. package/dist/servertool/handlers/clock.js +1 -1
  187. package/dist/servertool/handlers/compaction-detect.d.ts +1 -1
  188. package/dist/servertool/handlers/compaction-detect.js +1 -1
  189. package/dist/servertool/handlers/gemini-empty-reply-continue.js +1 -1
  190. package/dist/servertool/handlers/iflow-model-error-retry.js +1 -1
  191. package/dist/servertool/handlers/recursive-detection-guard.js +1 -1
  192. package/dist/servertool/handlers/review.js +1 -1
  193. package/dist/servertool/handlers/stop-message-auto/iflow-followup.js +1 -1
  194. package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +1 -1
  195. package/dist/servertool/handlers/stop-message-auto.js +1 -1
  196. package/dist/servertool/handlers/vision.js +1 -1
  197. package/dist/servertool/handlers/web-search.js +1 -1
  198. package/dist/servertool/reenter-backend.js +1 -1
  199. package/dist/servertool/server-side-tools.js +2 -2
  200. package/dist/servertool/stop-gateway-context.js +1 -1
  201. package/dist/servertool/stop-message-compare-context.js +1 -1
  202. package/dist/sse/json-to-sse/event-generators/responses.d.ts +4 -0
  203. package/dist/sse/json-to-sse/event-generators/responses.js +95 -1
  204. package/dist/sse/json-to-sse/sequencers/responses-sequencer.js +6 -4
  205. package/dist/sse/sse-to-json/builders/response-builder.d.ts +8 -0
  206. package/dist/sse/sse-to-json/builders/response-builder.js +162 -4
  207. package/dist/sse/sse-to-json/responses-sse-to-json-converter.js +2 -0
  208. package/dist/sse/types/responses-types.d.ts +6 -2
  209. package/dist/tools/apply-patch/structured/coercion.js +5 -0
  210. package/dist/tools/args-json.js +29 -0
  211. package/package.json +8 -5
  212. package/dist/conversion/shared/args-mapping.js +0 -77
  213. package/dist/conversion/shared/compaction-detect.js +0 -4
  214. package/dist/conversion/shared/errors.js +0 -31
  215. package/dist/conversion/shared/media.js +0 -4
  216. package/dist/conversion/shared/payload-budget.js +0 -165
  217. package/dist/conversion/shared/protocol-field-allowlists.d.ts +0 -7
  218. package/dist/conversion/shared/protocol-field-allowlists.js +0 -149
  219. package/dist/conversion/shared/snapshot-hooks.d.ts +0 -11
  220. package/dist/conversion/shared/snapshot-hooks.js +0 -503
  221. package/dist/conversion/shared/text-markup-normalizer/extractors-apply-patch.d.ts +0 -2
  222. package/dist/conversion/shared/text-markup-normalizer/extractors-apply-patch.js +0 -129
  223. package/dist/conversion/shared/text-markup-normalizer/extractors-json.d.ts +0 -4
  224. package/dist/conversion/shared/text-markup-normalizer/extractors-json.js +0 -637
  225. package/dist/conversion/shared/text-markup-normalizer/extractors-shared.d.ts +0 -21
  226. package/dist/conversion/shared/text-markup-normalizer/extractors-shared.js +0 -177
  227. package/dist/conversion/shared/text-markup-normalizer/extractors-transcript.d.ts +0 -5
  228. package/dist/conversion/shared/text-markup-normalizer/extractors-transcript.js +0 -385
  229. package/dist/conversion/shared/text-markup-normalizer/extractors-xml.d.ts +0 -10
  230. package/dist/conversion/shared/text-markup-normalizer/extractors-xml.js +0 -602
  231. package/dist/conversion/shared/text-markup-normalizer/extractors.d.ts +0 -5
  232. package/dist/conversion/shared/text-markup-normalizer/extractors.js +0 -4
  233. package/dist/conversion/shared/tool-canonicalizer.d.ts +0 -2
  234. package/dist/conversion/shared/tool-canonicalizer.js +0 -38
  235. /package/dist/conversion/{shared/args-mapping.d.ts → args-mapping.d.ts} +0 -0
  236. /package/dist/conversion/{shared/bridge-actions.d.ts → bridge-actions.d.ts} +0 -0
  237. /package/dist/conversion/{shared/bridge-id-utils.d.ts → bridge-id-utils.d.ts} +0 -0
  238. /package/dist/conversion/{shared/bridge-instructions.d.ts → bridge-instructions.d.ts} +0 -0
  239. /package/dist/conversion/{shared/bridge-metadata.d.ts → bridge-metadata.d.ts} +0 -0
  240. /package/dist/conversion/{shared/bridge-policies.d.ts → bridge-policies.d.ts} +0 -0
  241. /package/dist/conversion/{shared/jsonish.d.ts → jsonish.d.ts} +0 -0
  242. /package/dist/conversion/{shared/mcp-injection.d.ts → mcp-injection.d.ts} +0 -0
  243. /package/dist/conversion/{shared/media.d.ts → media.d.ts} +0 -0
  244. /package/dist/conversion/{shared/payload-budget.d.ts → payload-budget.d.ts} +0 -0
  245. /package/dist/conversion/{shared → types}/bridge-message-types.d.ts +0 -0
  246. /package/dist/conversion/{shared → types}/bridge-message-types.js +0 -0
@@ -6,6 +6,13 @@ export interface LoadBalancingOptions {
6
6
  weights?: Record<string, number>;
7
7
  availabilityCheck: (providerKey: string) => boolean;
8
8
  }
9
+ export interface GroupedLoadBalancingOptions {
10
+ routeName: string;
11
+ groups: Map<string, string[]>;
12
+ stickyKey?: string;
13
+ weights?: Record<string, number>;
14
+ availabilityCheck: (providerKey: string) => boolean;
15
+ }
9
16
  export declare class RouteLoadBalancer {
10
17
  private policy;
11
18
  private readonly states;
@@ -13,6 +20,7 @@ export declare class RouteLoadBalancer {
13
20
  updatePolicy(policy?: LoadBalancingPolicy): void;
14
21
  getPolicy(): LoadBalancingPolicy;
15
22
  select(options: LoadBalancingOptions, strategyOverride?: LoadBalancingPolicy['strategy']): string | null;
23
+ selectGrouped(options: GroupedLoadBalancingOptions, strategyOverride?: LoadBalancingPolicy['strategy']): string | null;
16
24
  private selectRoundRobin;
17
25
  private selectWeighted;
18
26
  private selectSticky;
@@ -25,7 +25,7 @@ export class RouteLoadBalancer {
25
25
  return this.selectWeighted(options.routeName, available, options.weights ?? this.policy.weights);
26
26
  default:
27
27
  if (options.weights) {
28
- const distinct = new Set(available.map((candidate) => Math.max(1, options.weights?.[candidate] ?? 1)));
28
+ const distinct = new Set(available.map((candidate) => normalizeWeight(options.weights?.[candidate])));
29
29
  if (distinct.size > 1) {
30
30
  return this.selectWeighted(options.routeName, available, options.weights);
31
31
  }
@@ -33,6 +33,38 @@ export class RouteLoadBalancer {
33
33
  return this.selectRoundRobin(options.routeName, available);
34
34
  }
35
35
  }
36
+ selectGrouped(options, strategyOverride) {
37
+ const groupIds = Array.from(options.groups.keys()).filter((groupId) => {
38
+ const members = options.groups.get(groupId) ?? [];
39
+ return members.some((candidate) => options.availabilityCheck(candidate));
40
+ });
41
+ if (groupIds.length === 0) {
42
+ return null;
43
+ }
44
+ const normalizedWeights = normalizeGroupWeights(groupIds, options.weights);
45
+ const strategy = strategyOverride ?? this.policy.strategy;
46
+ const groupRoute = `${options.routeName}:group`;
47
+ let selectedGroup = null;
48
+ switch (strategy) {
49
+ case 'sticky':
50
+ selectedGroup = this.selectSticky(groupRoute, groupIds, options.stickyKey, normalizedWeights);
51
+ break;
52
+ case 'weighted':
53
+ selectedGroup = this.selectWeighted(groupRoute, groupIds, normalizedWeights);
54
+ break;
55
+ default:
56
+ selectedGroup = this.selectRoundRobin(groupRoute, groupIds);
57
+ break;
58
+ }
59
+ if (!selectedGroup) {
60
+ return null;
61
+ }
62
+ const groupCandidates = (options.groups.get(selectedGroup) ?? []).filter((candidate) => options.availabilityCheck(candidate));
63
+ if (groupCandidates.length === 0) {
64
+ return null;
65
+ }
66
+ return this.selectRoundRobin(`${groupRoute}:${selectedGroup}`, groupCandidates);
67
+ }
36
68
  selectRoundRobin(routeName, candidates) {
37
69
  const state = this.getState(routeName);
38
70
  const choice = candidates[state.pointer % candidates.length];
@@ -55,8 +87,11 @@ export class RouteLoadBalancer {
55
87
  current.set(key, 0);
56
88
  }
57
89
  }
58
- const candidateWeights = candidates.map((candidate) => Math.max(1, weights?.[candidate] ?? 1));
90
+ const candidateWeights = candidates.map((candidate) => normalizeWeight(weights?.[candidate]));
59
91
  const totalWeight = candidateWeights.reduce((sum, w) => sum + w, 0);
92
+ if (!Number.isFinite(totalWeight) || totalWeight <= 0) {
93
+ return this.selectRoundRobin(routeName, candidates);
94
+ }
60
95
  let bestIndex = 0;
61
96
  let bestScore = Number.NEGATIVE_INFINITY;
62
97
  for (let i = 0; i < candidates.length; i += 1) {
@@ -95,3 +130,31 @@ export class RouteLoadBalancer {
95
130
  return this.states.get(routeName);
96
131
  }
97
132
  }
133
+ function normalizeWeight(value) {
134
+ if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
135
+ return value;
136
+ }
137
+ return 1;
138
+ }
139
+ function normalizeGroupWeights(groupIds, weights) {
140
+ if (!weights || groupIds.length === 0) {
141
+ return undefined;
142
+ }
143
+ const normalized = {};
144
+ let total = 0;
145
+ for (const id of groupIds) {
146
+ const raw = weights[id];
147
+ const value = typeof raw === 'number' && Number.isFinite(raw) && raw > 0 ? raw : 0;
148
+ if (value > 0) {
149
+ normalized[id] = value;
150
+ total += value;
151
+ }
152
+ }
153
+ if (!Number.isFinite(total) || total <= 0) {
154
+ return undefined;
155
+ }
156
+ for (const id of Object.keys(normalized)) {
157
+ normalized[id] = normalized[id] / total;
158
+ }
159
+ return normalized;
160
+ }
@@ -84,6 +84,7 @@ export class ProviderRegistry {
84
84
  processMode: profile.processMode || 'chat',
85
85
  responsesConfig: profile.responsesConfig,
86
86
  streaming: profile.streaming,
87
+ maxOutputTokens: profile.maxOutputTokens,
87
88
  maxContextTokens: profile.maxContextTokens,
88
89
  ...(profile.deepseek ? { deepseek: profile.deepseek } : {})
89
90
  };
@@ -103,6 +104,7 @@ export class ProviderRegistry {
103
104
  processMode: profile.processMode || 'chat',
104
105
  responsesConfig: profile.responsesConfig,
105
106
  streaming: profile.streaming,
107
+ maxOutputTokens: profile.maxOutputTokens,
106
108
  maxContextTokens: profile.maxContextTokens,
107
109
  ...(profile.deepseek ? { deepseek: profile.deepseek } : {}),
108
110
  ...(profile.serverToolsDisabled ? { serverToolsDisabled: true } : {})
@@ -0,0 +1,3 @@
1
+ import type { StandardizedMessage } from '../../../conversion/hub/types/standardized.js';
2
+ export declare function stripCodeSegments(text: string): string;
3
+ export declare function cleanMessagesFromRoutingInstructions(messages: StandardizedMessage[]): StandardizedMessage[];
@@ -0,0 +1,34 @@
1
+ import { ROUTING_INSTRUCTION_MARKER_GLOBAL_PATTERN } from './types.js';
2
+ export function stripCodeSegments(text) {
3
+ if (!text) {
4
+ return '';
5
+ }
6
+ // Remove fenced code blocks ```...``` or ~~~...~~~
7
+ let sanitized = text.replace(/```[\s\S]*?```/g, ' ');
8
+ sanitized = sanitized.replace(/~~~[\s\S]*?~~~/g, ' ');
9
+ // Remove inline code `...`
10
+ sanitized = sanitized.replace(/`[^`]*`/g, ' ');
11
+ return sanitized;
12
+ }
13
+ export function cleanMessagesFromRoutingInstructions(messages) {
14
+ return messages
15
+ .map((message) => {
16
+ if (message.role !== 'user' || typeof message.content !== 'string') {
17
+ return message;
18
+ }
19
+ const cleanedContent = message.content.replace(ROUTING_INSTRUCTION_MARKER_GLOBAL_PATTERN, '').trim();
20
+ return {
21
+ ...message,
22
+ content: cleanedContent
23
+ };
24
+ })
25
+ .filter((message) => {
26
+ if (message.role !== 'user') {
27
+ return true;
28
+ }
29
+ if (typeof message.content !== 'string') {
30
+ return true;
31
+ }
32
+ return message.content.trim().length > 0;
33
+ });
34
+ }
@@ -0,0 +1,18 @@
1
+ import type { StandardizedMessage } from '../../../conversion/hub/types/standardized.js';
2
+ import type { RoutingInstruction } from './types.js';
3
+ export declare function parseRoutingInstructions(messages: StandardizedMessage[]): RoutingInstruction[];
4
+ /**
5
+ * 解析并预处理路由指令,优先处理 clear 指令,确保新指令能够覆盖旧状态。
6
+ * 返回清理后的指令列表,移除冗余的 stopMessageSet 指令。
7
+ */
8
+ export declare function parseAndPreprocessRoutingInstructions(messages: StandardizedMessage[]): RoutingInstruction[];
9
+ /**
10
+ * 提取 clear 指令(如果存在)。用于在路由选择前优先执行清理操作。
11
+ * @returns 是否存在 clear 指令
12
+ */
13
+ export declare function extractClearInstruction(messages: StandardizedMessage[]): boolean;
14
+ /**
15
+ * 提取 stopMessageClear 指令(如果存在)。
16
+ * @returns 是否存在 stopMessageClear 指令
17
+ */
18
+ export declare function extractStopMessageClearInstruction(messages: StandardizedMessage[]): boolean;
@@ -0,0 +1,377 @@
1
+ import { extractMessageText } from '../message-utils.js';
2
+ import { parseStopMessageInstruction } from '../routing-stop-message-parser.js';
3
+ import { parsePreCommandInstruction } from '../routing-pre-command-parser.js';
4
+ import { stripCodeSegments } from './clean.js';
5
+ import { ROUTING_INSTRUCTION_MARKER_PATTERN } from './types.js';
6
+ export function parseRoutingInstructions(messages) {
7
+ const instructions = [];
8
+ // 只解析“当前最新一条消息”中的 marker,避免历史 user marker 在后续轮次被重复回放。
9
+ const latestMessage = messages.length > 0 ? messages[messages.length - 1] : undefined;
10
+ if (!latestMessage || latestMessage.role !== 'user') {
11
+ return instructions;
12
+ }
13
+ const content = extractMessageText(latestMessage);
14
+ if (!content) {
15
+ return instructions;
16
+ }
17
+ const sanitized = stripCodeSegments(content);
18
+ if (!sanitized || !ROUTING_INSTRUCTION_MARKER_PATTERN.test(sanitized)) {
19
+ return instructions;
20
+ }
21
+ const regex = /<\*\*([\s\S]*?)\*\*>/g;
22
+ let match;
23
+ while ((match = regex.exec(sanitized)) !== null) {
24
+ const instruction = match[1].trim();
25
+ if (!instruction) {
26
+ continue;
27
+ }
28
+ const segments = expandInstructionSegments(instruction);
29
+ for (const segment of segments) {
30
+ const parsed = parseSingleInstruction(segment);
31
+ if (parsed) {
32
+ instructions.push(parsed);
33
+ }
34
+ }
35
+ }
36
+ return normalizeStopMessageInstructionPrecedence(instructions);
37
+ }
38
+ /**
39
+ * 解析并预处理路由指令,优先处理 clear 指令,确保新指令能够覆盖旧状态。
40
+ * 返回清理后的指令列表,移除冗余的 stopMessageSet 指令。
41
+ */
42
+ export function parseAndPreprocessRoutingInstructions(messages) {
43
+ const rawInstructions = parseRoutingInstructions(messages);
44
+ if (rawInstructions.length === 0) {
45
+ return [];
46
+ }
47
+ // 检查是否有 clear 指令
48
+ const hasClear = rawInstructions.some((inst) => inst.type === 'clear');
49
+ if (!hasClear) {
50
+ return rawInstructions;
51
+ }
52
+ // 如果有 clear 指令,clear 之后的指令才有效,clear 之前的指令被清除
53
+ const clearIndex = rawInstructions.findIndex((inst) => inst.type === 'clear');
54
+ const effectiveInstructions = rawInstructions.slice(clearIndex + 1);
55
+ // 移除 clear 后冗余的 stopMessageSet 指令(如果与原指令相同)
56
+ // 这里的逻辑会在 applyRoutingInstructions 中处理,
57
+ // 所以我们只需要返回 clear 之后的指令即可
58
+ return effectiveInstructions;
59
+ }
60
+ /**
61
+ * 提取 clear 指令(如果存在)。用于在路由选择前优先执行清理操作。
62
+ * @returns 是否存在 clear 指令
63
+ */
64
+ export function extractClearInstruction(messages) {
65
+ const instructions = parseRoutingInstructions(messages);
66
+ return instructions.some((inst) => inst.type === 'clear');
67
+ }
68
+ /**
69
+ * 提取 stopMessageClear 指令(如果存在)。
70
+ * @returns 是否存在 stopMessageClear 指令
71
+ */
72
+ export function extractStopMessageClearInstruction(messages) {
73
+ const instructions = parseRoutingInstructions(messages);
74
+ return instructions.some((inst) => inst.type === 'stopMessageClear');
75
+ }
76
+ function expandInstructionSegments(instruction) {
77
+ const trimmed = instruction.trim();
78
+ if (!trimmed) {
79
+ return [];
80
+ }
81
+ const normalizedLeading = normalizeInstructionLeading(trimmed);
82
+ // stopMessage 指令需要整体解析,不能按逗号拆分,否则类似
83
+ // "<**stopMessage:\"继续\",3**>" 会被错误拆成 ["stopMessage:\"继续\"", "3"]。
84
+ if (/^(?:"|')?stopMessage(?:"|')?\s*[:,]/i.test(normalizedLeading)) {
85
+ return [normalizeStopMessageCommandPrefix(normalizedLeading)];
86
+ }
87
+ if (/^precommand(?:\s*:|$)/i.test(normalizedLeading)) {
88
+ return [normalizedLeading];
89
+ }
90
+ const prefix = trimmed[0];
91
+ if (prefix === '!' || prefix === '#' || prefix === '@') {
92
+ const tokens = splitInstructionTargets(trimmed.substring(1));
93
+ return tokens
94
+ .map((token) => token.replace(/^[!#@]+/, '').trim())
95
+ .filter((token) => token.length > 0)
96
+ .map((token) => `${prefix}${token}`);
97
+ }
98
+ const splitTokens = splitInstructionTargets(trimmed);
99
+ const recoveredStopMessage = recoverSplitStopMessageInstruction(splitTokens);
100
+ if (recoveredStopMessage) {
101
+ return [recoveredStopMessage];
102
+ }
103
+ return splitTokens;
104
+ }
105
+ function splitInstructionTargets(content) {
106
+ return content
107
+ .split(',')
108
+ .map((segment) => segment.trim())
109
+ .filter((segment) => segment.length > 0);
110
+ }
111
+ function normalizeInstructionLeading(content) {
112
+ // Remove common zero-width prefixes that may be injected by client/editor copies.
113
+ return content.replace(/^[\u200B-\u200D\u2060\uFEFF]+/, '').trimStart();
114
+ }
115
+ function normalizeStopMessageCommandPrefix(content) {
116
+ const normalized = normalizeInstructionLeading(content);
117
+ return normalized.replace(/^(?:"|')?stopMessage(?:"|')?\s*([:,])/i, 'stopMessage$1');
118
+ }
119
+ function normalizeSplitStopMessageHeadToken(token) {
120
+ return normalizeInstructionLeading(token)
121
+ .replace(/^["']+|["']+$/g, '')
122
+ .trim();
123
+ }
124
+ function recoverSplitStopMessageInstruction(tokens) {
125
+ if (!Array.isArray(tokens) || tokens.length < 2) {
126
+ return null;
127
+ }
128
+ const head = normalizeSplitStopMessageHeadToken(tokens[0]);
129
+ if (!/^stopmessage$/i.test(head)) {
130
+ return null;
131
+ }
132
+ const tail = tokens.slice(1).join(',').trim();
133
+ if (!tail) {
134
+ return null;
135
+ }
136
+ return `stopMessage:${tail}`;
137
+ }
138
+ function splitTargetAndProcessMode(rawTarget) {
139
+ const trimmed = typeof rawTarget === 'string' ? rawTarget.trim() : '';
140
+ if (!trimmed) {
141
+ return { target: '' };
142
+ }
143
+ const separatorIndex = trimmed.lastIndexOf(':');
144
+ if (separatorIndex <= 0 || separatorIndex === trimmed.length - 1) {
145
+ return { target: trimmed };
146
+ }
147
+ const target = trimmed.slice(0, separatorIndex).trim();
148
+ const modeToken = trimmed.slice(separatorIndex + 1).trim().toLowerCase();
149
+ if (!target) {
150
+ return { target: trimmed };
151
+ }
152
+ if (modeToken === 'passthrough') {
153
+ return { target, processMode: 'passthrough' };
154
+ }
155
+ if (modeToken === 'chat') {
156
+ return { target, processMode: 'chat' };
157
+ }
158
+ return { target };
159
+ }
160
+ function parseNamedTargetInstruction(instruction, prefix) {
161
+ const re = new RegExp('^' + prefix + '\\s*:', 'i');
162
+ if (!re.test(instruction)) {
163
+ return null;
164
+ }
165
+ const body = instruction.slice(instruction.indexOf(':') + 1).trim();
166
+ if (!body) {
167
+ return null;
168
+ }
169
+ const { target, processMode } = splitTargetAndProcessMode(body);
170
+ if (!target) {
171
+ return null;
172
+ }
173
+ const parsed = parseTarget(target);
174
+ if (!parsed) {
175
+ return null;
176
+ }
177
+ const normalized = normalizeStickyOrForceTarget(parsed);
178
+ return { type: prefix, ...normalized, ...(processMode ? { processMode } : {}) };
179
+ }
180
+ function parseSingleInstruction(instruction) {
181
+ if (/^clear$/i.test(instruction)) {
182
+ return { type: 'clear' };
183
+ }
184
+ const preCommandInstruction = parsePreCommandInstruction(instruction);
185
+ if (preCommandInstruction) {
186
+ return preCommandInstruction;
187
+ }
188
+ const stopMessageInstruction = parseStopMessageInstruction(instruction);
189
+ if (stopMessageInstruction) {
190
+ return stopMessageInstruction;
191
+ }
192
+ const stickyInstruction = parseNamedTargetInstruction(instruction, 'sticky');
193
+ if (stickyInstruction) {
194
+ return stickyInstruction;
195
+ }
196
+ const forceInstruction = parseNamedTargetInstruction(instruction, 'force');
197
+ if (forceInstruction) {
198
+ return forceInstruction;
199
+ }
200
+ const preferInstruction = parseNamedTargetInstruction(instruction, 'prefer');
201
+ if (preferInstruction) {
202
+ return preferInstruction;
203
+ }
204
+ if (instruction.startsWith('!')) {
205
+ const rawTarget = instruction.substring(1).trim();
206
+ const { target, processMode } = splitTargetAndProcessMode(rawTarget);
207
+ if (!target) {
208
+ return null;
209
+ }
210
+ const parsed = parseTarget(target);
211
+ if (!parsed) {
212
+ return null;
213
+ }
214
+ // 约定:
215
+ // - "!providerA,providerB":允许列表(whitelist),用于快速限制可用 provider 集合;
216
+ // - "!provider.model" / "!provider[alias].model" / "!provider.2":prefer 语义;
217
+ // model 可用时只命中该 model(忽略路由),不可用则自动清除并回退到正常路由命中。
218
+ //
219
+ // 这样可以在不破坏既有 "!glm,openai" 语义的前提下,引入基于模型的优先命中行为。
220
+ if (!target.includes('.')) {
221
+ if (parsed.provider) {
222
+ return { type: 'allow', provider: parsed.provider, pathLength: parsed.pathLength };
223
+ }
224
+ return null;
225
+ }
226
+ const normalized = normalizeStickyOrForceTarget(parsed);
227
+ return { type: 'prefer', ...normalized, ...(processMode ? { processMode } : {}) };
228
+ }
229
+ if (instruction.startsWith('#')) {
230
+ const target = instruction.substring(1).trim();
231
+ const parsed = parseTarget(target);
232
+ if (parsed) {
233
+ return { type: 'disable', ...parsed };
234
+ }
235
+ }
236
+ else if (instruction.startsWith('@')) {
237
+ const target = instruction.substring(1).trim();
238
+ const parsed = parseTarget(target);
239
+ if (parsed) {
240
+ return { type: 'enable', ...parsed };
241
+ }
242
+ }
243
+ else if (isValidProviderModel(instruction)) {
244
+ const parsed = parseTarget(instruction);
245
+ if (parsed) {
246
+ const normalized = normalizeStickyOrForceTarget(parsed);
247
+ return { type: 'force', ...normalized };
248
+ }
249
+ }
250
+ else if (isValidIdentifier(instruction)) {
251
+ // 仅 provider 标识(无 .)时,视为 provider 级白名单,等价于 "<**!provider**>"。
252
+ // 这样可以用 "<**antigravity**>" 快速激活当前 routing 中所有 antigravity 相关池子,
253
+ // 并保证路由仅命中该 provider 的所有模型/key。
254
+ return {
255
+ type: 'allow',
256
+ provider: instruction,
257
+ pathLength: 1
258
+ };
259
+ }
260
+ return null;
261
+ }
262
+ function normalizeStopMessageInstructionPrecedence(instructions) {
263
+ if (!Array.isArray(instructions) || instructions.length <= 1) {
264
+ return instructions;
265
+ }
266
+ const isStopDirective = (inst) => inst.type === 'stopMessageSet' || inst.type === 'stopMessageMode' || inst.type === 'stopMessageClear';
267
+ const hasGlobalClear = instructions.some((inst) => inst.type === 'clear');
268
+ const hasStopClear = instructions.some((inst) => inst.type === 'stopMessageClear');
269
+ if (hasGlobalClear) {
270
+ const lastGlobalClearIndex = instructions.map((inst) => inst.type).lastIndexOf('clear');
271
+ return lastGlobalClearIndex >= 0 ? [instructions[lastGlobalClearIndex]] : instructions;
272
+ }
273
+ if (hasStopClear) {
274
+ const lastStopClearIndex = instructions.map((inst) => inst.type).lastIndexOf('stopMessageClear');
275
+ return lastStopClearIndex >= 0 ? [instructions[lastStopClearIndex]] : instructions;
276
+ }
277
+ let lastStopIndex = -1;
278
+ for (let idx = instructions.length - 1; idx >= 0; idx -= 1) {
279
+ if (isStopDirective(instructions[idx])) {
280
+ lastStopIndex = idx;
281
+ break;
282
+ }
283
+ }
284
+ if (lastStopIndex < 0) {
285
+ return instructions;
286
+ }
287
+ return instructions.filter((inst, idx) => !isStopDirective(inst) || idx === lastStopIndex);
288
+ }
289
+ function parseTarget(target) {
290
+ if (!target) {
291
+ return null;
292
+ }
293
+ // Accept "provider[alias].model" (as printed in virtual-router-hit logs) to avoid users
294
+ // needing to translate bracket notation back to dot notation manually.
295
+ // With the alias disambiguated, allow dots in model ids (e.g. gpt-5.2) without ambiguity.
296
+ const bracketMatch = target.match(/^([a-zA-Z0-9_-]+)\[([a-zA-Z0-9_-]*)\](?:\.(.+))?$/);
297
+ if (bracketMatch) {
298
+ const provider = bracketMatch[1];
299
+ const keyAliasRaw = bracketMatch[2];
300
+ const keyAlias = typeof keyAliasRaw === 'string' ? keyAliasRaw.trim() : '';
301
+ const model = typeof bracketMatch[3] === 'string' ? bracketMatch[3].trim() : '';
302
+ if (!provider || !isValidIdentifier(provider)) {
303
+ return null;
304
+ }
305
+ // Allow omitting the alias: "provider[].model" means "provider.model across all aliases".
306
+ // This also enables disambiguating model ids that contain dots, without requiring the user
307
+ // to specify the alias.
308
+ if (!keyAlias) {
309
+ if (!model) {
310
+ return { provider, pathLength: 1 };
311
+ }
312
+ if (!/^[a-zA-Z0-9_.-]+$/.test(model)) {
313
+ return null;
314
+ }
315
+ return { provider, model, pathLength: 2 };
316
+ }
317
+ if (!isValidIdentifier(keyAlias)) {
318
+ return null;
319
+ }
320
+ if (!model) {
321
+ // Treat as explicit alias selection. Use pathLength=3 so engine resolves keyAlias directly.
322
+ return { provider, keyAlias, pathLength: 3 };
323
+ }
324
+ // Model ids may contain dots (e.g. gpt-5.2); allow them here because alias is already explicit.
325
+ if (!/^[a-zA-Z0-9_.-]+$/.test(model)) {
326
+ return null;
327
+ }
328
+ return { provider, keyAlias, model, pathLength: 3 };
329
+ }
330
+ // Dot syntax: align with config parsing used by routing entries.
331
+ // - "provider.modelId" -> modelId may contain dots; it always means model across all aliases.
332
+ // - Key/alias selection must use bracket syntax: "provider[alias]" or "provider[alias].modelId".
333
+ const firstDot = target.indexOf('.');
334
+ if (firstDot < 0) {
335
+ const provider = target.trim();
336
+ if (!provider || !isValidIdentifier(provider)) {
337
+ return null;
338
+ }
339
+ return { provider, pathLength: 1 };
340
+ }
341
+ const provider = target.slice(0, firstDot).trim();
342
+ const remainder = target.slice(firstDot + 1).trim();
343
+ if (!provider || !isValidIdentifier(provider) || !remainder) {
344
+ return null;
345
+ }
346
+ // Support "provider.2" key-index notation (only when remainder is a plain integer).
347
+ if (/^\d+$/.test(remainder)) {
348
+ const keyIndex = Number.parseInt(remainder, 10);
349
+ if (Number.isFinite(keyIndex) && keyIndex > 0) {
350
+ return { provider, keyIndex, pathLength: 2 };
351
+ }
352
+ }
353
+ // Treat everything after the first dot as the model id, allowing dots.
354
+ if (!/^[a-zA-Z0-9_.-]+$/.test(remainder)) {
355
+ return null;
356
+ }
357
+ return { provider, model: remainder, pathLength: 2 };
358
+ }
359
+ function normalizeStickyOrForceTarget(target) {
360
+ if (target &&
361
+ target.pathLength === 2 &&
362
+ typeof target.model === 'string' &&
363
+ typeof target.keyAlias === 'string' &&
364
+ target.model === target.keyAlias) {
365
+ const clone = { ...target };
366
+ delete clone.keyAlias;
367
+ return clone;
368
+ }
369
+ return target;
370
+ }
371
+ function isValidIdentifier(id) {
372
+ return /^[a-zA-Z0-9_-]+$/.test(id);
373
+ }
374
+ function isValidProviderModel(providerModel) {
375
+ const pattern = /^[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)+$/;
376
+ return pattern.test(providerModel);
377
+ }
@@ -0,0 +1,4 @@
1
+ import type { RoutingInstruction, RoutingInstructionState } from './types.js';
2
+ export declare function applyRoutingInstructions(instructions: RoutingInstruction[], currentState: RoutingInstructionState): RoutingInstructionState;
3
+ export declare function serializeRoutingInstructionState(state: RoutingInstructionState): Record<string, unknown>;
4
+ export declare function deserializeRoutingInstructionState(data: Record<string, unknown>): RoutingInstructionState;