@jsonstudio/llms 0.6.1164 → 0.6.1354

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 (164) hide show
  1. package/dist/conversion/codecs/gemini-openai-codec.d.ts +3 -1
  2. package/dist/conversion/codecs/gemini-openai-codec.js +10 -4
  3. package/dist/conversion/compat/actions/gemini-web-search.d.ts +1 -1
  4. package/dist/conversion/compat/actions/gemini-web-search.js +5 -2
  5. package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +12 -0
  6. package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +199 -0
  7. package/dist/conversion/compat/actions/iflow-web-search.d.ts +1 -1
  8. package/dist/conversion/compat/actions/iflow-web-search.js +5 -2
  9. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +47 -56
  10. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +1 -13
  11. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +523 -50
  12. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +18 -38
  13. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +6 -0
  14. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +3 -0
  15. package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.d.ts +10 -0
  16. package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.js +134 -0
  17. package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.d.ts +6 -0
  18. package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.js +79 -0
  19. package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.d.ts +3 -0
  20. package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.js +46 -0
  21. package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.d.ts +8 -0
  22. package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.js +366 -0
  23. package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.d.ts +9 -0
  24. package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.js +384 -0
  25. package/dist/conversion/hub/pipeline/hub-pipeline/node-results.d.ts +3 -0
  26. package/dist/conversion/hub/pipeline/hub-pipeline/node-results.js +14 -0
  27. package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.d.ts +2 -0
  28. package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.js +144 -0
  29. package/dist/conversion/hub/pipeline/hub-pipeline/policy.d.ts +4 -0
  30. package/dist/conversion/hub/pipeline/hub-pipeline/policy.js +32 -0
  31. package/dist/conversion/hub/pipeline/hub-pipeline/protocol.d.ts +8 -0
  32. package/dist/conversion/hub/pipeline/hub-pipeline/protocol.js +63 -0
  33. package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.d.ts +2 -0
  34. package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.js +43 -0
  35. package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.d.ts +1 -0
  36. package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.js +29 -0
  37. package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.d.ts +2 -0
  38. package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.js +16 -0
  39. package/dist/conversion/hub/pipeline/hub-pipeline/types.d.ts +116 -0
  40. package/dist/conversion/hub/pipeline/hub-pipeline/types.js +1 -0
  41. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +3 -95
  42. package/dist/conversion/hub/pipeline/hub-pipeline.js +19 -1281
  43. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +1 -1
  44. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +7 -0
  45. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +65 -1
  46. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +25 -22
  47. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +1 -1
  48. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.d.ts +1 -1
  49. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.js +2 -2
  50. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +2 -2
  51. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +1 -1
  52. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +1 -1
  53. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +11 -11
  54. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.js +1 -1
  55. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.d.ts +1 -0
  56. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.js +4 -2
  57. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.d.ts +1 -0
  58. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +17 -9
  59. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js +2 -2
  60. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +40 -2
  61. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.js +1 -1
  62. package/dist/conversion/hub/pipeline/target-utils.js +9 -5
  63. package/dist/conversion/hub/process/chat-process.js +256 -16
  64. package/dist/conversion/hub/response/provider-response.d.ts +8 -0
  65. package/dist/conversion/hub/response/provider-response.js +85 -27
  66. package/dist/conversion/hub/response/response-mappers.d.ts +10 -3
  67. package/dist/conversion/hub/response/response-mappers.js +30 -6
  68. package/dist/conversion/hub/response/response-runtime.js +4 -38
  69. package/dist/conversion/hub/snapshot-recorder.js +5 -1
  70. package/dist/conversion/hub/standardized-bridge.js +23 -15
  71. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +36 -5
  72. package/dist/conversion/responses/responses-openai-bridge.js +20 -4
  73. package/dist/conversion/shared/gemini-tool-utils.d.ts +8 -1
  74. package/dist/conversion/shared/gemini-tool-utils.js +580 -108
  75. package/dist/conversion/shared/jsonish.js +1 -1
  76. package/dist/conversion/shared/mcp-injection.js +67 -33
  77. package/dist/conversion/shared/openai-finalizer.js +2 -1
  78. package/dist/conversion/shared/openai-message-normalize.js +76 -21
  79. package/dist/conversion/shared/responses-output-builder.js +6 -0
  80. package/dist/conversion/shared/runtime-metadata.d.ts +7 -0
  81. package/dist/conversion/shared/runtime-metadata.js +23 -0
  82. package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
  83. package/dist/conversion/shared/text-markup-normalizer.js +284 -4
  84. package/dist/conversion/shared/tool-canonicalizer.js +2 -1
  85. package/dist/conversion/shared/tool-governor.js +3 -3
  86. package/dist/filters/engine.js +5 -5
  87. package/dist/filters/special/request-tool-list-filter.js +194 -60
  88. package/dist/filters/special/request-tools-normalize.js +1 -1
  89. package/dist/filters/special/response-tool-text-canonicalize.d.ts +4 -7
  90. package/dist/filters/special/response-tool-text-canonicalize.js +7 -35
  91. package/dist/filters/special/tool-filter-hooks.js +58 -62
  92. package/dist/guidance/index.js +5 -1
  93. package/dist/http/sse-response.js +6 -6
  94. package/dist/router/virtual-router/bootstrap.js +65 -5
  95. package/dist/router/virtual-router/context-advisor.d.ts +4 -0
  96. package/dist/router/virtual-router/context-advisor.js +3 -0
  97. package/dist/router/virtual-router/context-weighted.d.ts +31 -0
  98. package/dist/router/virtual-router/context-weighted.js +54 -0
  99. package/dist/router/virtual-router/engine-health.d.ts +1 -1
  100. package/dist/router/virtual-router/engine-health.js +11 -110
  101. package/dist/router/virtual-router/engine-selection/alias-selection.d.ts +15 -0
  102. package/dist/router/virtual-router/engine-selection/alias-selection.js +156 -0
  103. package/dist/router/virtual-router/engine-selection/context-weight-multipliers.d.ts +11 -0
  104. package/dist/router/virtual-router/engine-selection/context-weight-multipliers.js +23 -0
  105. package/dist/router/virtual-router/engine-selection/direct-provider-model.d.ts +9 -0
  106. package/dist/router/virtual-router/engine-selection/direct-provider-model.js +49 -0
  107. package/dist/router/virtual-router/engine-selection/instruction-target.d.ts +6 -0
  108. package/dist/router/virtual-router/engine-selection/instruction-target.js +54 -0
  109. package/dist/router/virtual-router/engine-selection/key-parsing.d.ts +8 -0
  110. package/dist/router/virtual-router/engine-selection/key-parsing.js +64 -0
  111. package/dist/router/virtual-router/engine-selection/route-utils.d.ts +12 -0
  112. package/dist/router/virtual-router/engine-selection/route-utils.js +150 -0
  113. package/dist/router/virtual-router/engine-selection/routing-state-filter.d.ts +4 -0
  114. package/dist/router/virtual-router/engine-selection/routing-state-filter.js +50 -0
  115. package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +39 -0
  116. package/dist/router/virtual-router/engine-selection/selection-deps.js +1 -0
  117. package/dist/router/virtual-router/engine-selection/sticky-pool.d.ts +11 -0
  118. package/dist/router/virtual-router/engine-selection/sticky-pool.js +109 -0
  119. package/dist/router/virtual-router/engine-selection/tier-priority.d.ts +12 -0
  120. package/dist/router/virtual-router/engine-selection/tier-priority.js +55 -0
  121. package/dist/router/virtual-router/engine-selection/tier-selection-select.d.ts +22 -0
  122. package/dist/router/virtual-router/engine-selection/tier-selection-select.js +400 -0
  123. package/dist/router/virtual-router/engine-selection/tier-selection.d.ts +3 -0
  124. package/dist/router/virtual-router/engine-selection/tier-selection.js +225 -0
  125. package/dist/router/virtual-router/engine-selection.d.ts +4 -30
  126. package/dist/router/virtual-router/engine-selection.js +10 -815
  127. package/dist/router/virtual-router/engine.d.ts +1 -0
  128. package/dist/router/virtual-router/engine.js +55 -10
  129. package/dist/router/virtual-router/routing-instructions.js +6 -1
  130. package/dist/router/virtual-router/stop-message-state-sync.d.ts +5 -0
  131. package/dist/router/virtual-router/stop-message-state-sync.js +6 -14
  132. package/dist/router/virtual-router/types.d.ts +53 -1
  133. package/dist/servertool/clock/config.d.ts +8 -0
  134. package/dist/servertool/clock/config.js +22 -0
  135. package/dist/servertool/clock/log.d.ts +3 -0
  136. package/dist/servertool/clock/log.js +13 -0
  137. package/dist/servertool/clock/task-store.d.ts +1 -1
  138. package/dist/servertool/clock/task-store.js +1 -1
  139. package/dist/servertool/clock/tasks.js +1 -1
  140. package/dist/servertool/engine.js +146 -21
  141. package/dist/servertool/handlers/clock-auto.js +11 -6
  142. package/dist/servertool/handlers/clock.js +36 -10
  143. package/dist/servertool/handlers/followup-request-builder.js +8 -2
  144. package/dist/servertool/handlers/gemini-empty-reply-continue.js +15 -9
  145. package/dist/servertool/handlers/iflow-model-error-retry.js +6 -4
  146. package/dist/servertool/handlers/recursive-detection-guard.js +4 -2
  147. package/dist/servertool/handlers/stop-message-auto.js +100 -10
  148. package/dist/servertool/handlers/vision.js +4 -1
  149. package/dist/servertool/handlers/web-search.js +3 -1
  150. package/dist/servertool/pending-session.d.ts +19 -0
  151. package/dist/servertool/pending-session.js +97 -0
  152. package/dist/servertool/reenter-backend.js +5 -3
  153. package/dist/servertool/server-side-tools.js +235 -6
  154. package/dist/servertool/types.d.ts +13 -0
  155. package/dist/sse/json-to-sse/event-generators/responses.js +1 -1
  156. package/dist/sse/shared/chat-serializer.js +2 -2
  157. package/dist/sse/shared/constants.js +1 -1
  158. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +7 -1
  159. package/dist/sse/sse-to-json/builders/response-builder.js +16 -0
  160. package/dist/sse/sse-to-json/responses-sse-to-json-converter.d.ts +1 -1
  161. package/dist/tools/apply-patch/execution-capturer.js +1 -1
  162. package/dist/tools/exec-command/normalize.js +4 -0
  163. package/dist/tools/exec-command/regression-capturer.js +1 -1
  164. package/package.json +10 -5
@@ -0,0 +1,54 @@
1
+ import { getProviderModelId } from './key-parsing.js';
2
+ export function resolveInstructionTarget(target, providerRegistry) {
3
+ if (!target || !target.provider) {
4
+ return null;
5
+ }
6
+ const providerId = target.provider;
7
+ const providerKeys = providerRegistry.listProviderKeys(providerId);
8
+ if (providerKeys.length === 0) {
9
+ return null;
10
+ }
11
+ const alias = typeof target.keyAlias === 'string' ? target.keyAlias.trim() : '';
12
+ const aliasExplicit = alias.length > 0 && target.pathLength === 3;
13
+ if (aliasExplicit) {
14
+ const prefix = `${providerId}.${alias}.`;
15
+ const aliasKeys = providerKeys.filter((key) => key.startsWith(prefix));
16
+ if (aliasKeys.length > 0) {
17
+ if (target.model && target.model.trim()) {
18
+ const normalizedModel = target.model.trim();
19
+ const matching = aliasKeys.filter((key) => getProviderModelId(key, providerRegistry) === normalizedModel);
20
+ if (matching.length > 0) {
21
+ // Prefer exact to keep sticky pool deterministic when only one key matches.
22
+ if (matching.length === 1) {
23
+ return { mode: 'exact', keys: [matching[0]] };
24
+ }
25
+ return { mode: 'filter', keys: matching };
26
+ }
27
+ }
28
+ return { mode: 'filter', keys: aliasKeys };
29
+ }
30
+ }
31
+ if (typeof target.keyIndex === 'number' && target.keyIndex > 0) {
32
+ const runtimeKey = providerRegistry.resolveRuntimeKeyByIndex(providerId, target.keyIndex);
33
+ if (runtimeKey) {
34
+ return { mode: 'exact', keys: [runtimeKey] };
35
+ }
36
+ }
37
+ if (target.model && target.model.trim()) {
38
+ const normalizedModel = target.model.trim();
39
+ const matchingKeys = providerKeys.filter((key) => {
40
+ const modelId = getProviderModelId(key, providerRegistry);
41
+ return modelId === normalizedModel;
42
+ });
43
+ if (matchingKeys.length > 0) {
44
+ return { mode: 'filter', keys: matchingKeys };
45
+ }
46
+ }
47
+ if (alias && !aliasExplicit) {
48
+ const legacyKey = providerRegistry.resolveRuntimeKeyByAlias(providerId, alias);
49
+ if (legacyKey) {
50
+ return { mode: 'exact', keys: [legacyKey] };
51
+ }
52
+ }
53
+ return { mode: 'filter', keys: providerKeys };
54
+ }
@@ -0,0 +1,8 @@
1
+ import type { ProviderRegistry } from '../provider-registry.js';
2
+ import type { RouterMetadataInput } from '../types.js';
3
+ export declare function extractProviderId(providerKey: string): string | null;
4
+ export declare function extractKeyAlias(providerKey: string): string | null;
5
+ export declare function normalizeAliasDescriptor(alias: string): string;
6
+ export declare function extractKeyIndex(providerKey: string): number | undefined;
7
+ export declare function getProviderModelId(providerKey: string, providerRegistry: ProviderRegistry): string | null;
8
+ export declare function extractExcludedProviderKeySet(metadata: RouterMetadataInput | undefined): Set<string>;
@@ -0,0 +1,64 @@
1
+ export function extractProviderId(providerKey) {
2
+ const firstDot = providerKey.indexOf('.');
3
+ if (firstDot <= 0)
4
+ return null;
5
+ return providerKey.substring(0, firstDot);
6
+ }
7
+ export function extractKeyAlias(providerKey) {
8
+ // Provider keys are logically: <providerId>.<alias>.<modelId>
9
+ //
10
+ // NOTE: modelId can contain dots (e.g. "gpt-5.2"), so we must not assume `split('.')` length === 3.
11
+ const firstDot = providerKey.indexOf('.');
12
+ if (firstDot <= 0)
13
+ return null;
14
+ const secondDot = providerKey.indexOf('.', firstDot + 1);
15
+ if (secondDot <= firstDot + 1)
16
+ return null;
17
+ const alias = providerKey.substring(firstDot + 1, secondDot);
18
+ if (!alias)
19
+ return null;
20
+ return normalizeAliasDescriptor(alias);
21
+ }
22
+ export function normalizeAliasDescriptor(alias) {
23
+ if (/^\d+-/.test(alias)) {
24
+ return alias.replace(/^\d+-/, '');
25
+ }
26
+ return alias;
27
+ }
28
+ export function extractKeyIndex(providerKey) {
29
+ const parts = providerKey.split('.');
30
+ if (parts.length === 2) {
31
+ const index = parseInt(parts[1], 10);
32
+ if (!isNaN(index) && index > 0) {
33
+ return index;
34
+ }
35
+ }
36
+ return undefined;
37
+ }
38
+ export function getProviderModelId(providerKey, providerRegistry) {
39
+ const profile = providerRegistry.get(providerKey);
40
+ if (profile.modelId) {
41
+ return profile.modelId;
42
+ }
43
+ const parts = providerKey.split('.');
44
+ if (parts.length === 2) {
45
+ return parts[1] || null;
46
+ }
47
+ if (parts.length === 3) {
48
+ return parts[2] || null;
49
+ }
50
+ return null;
51
+ }
52
+ export function extractExcludedProviderKeySet(metadata) {
53
+ if (!metadata) {
54
+ return new Set();
55
+ }
56
+ const raw = metadata.excludedProviderKeys;
57
+ if (!Array.isArray(raw) || raw.length === 0) {
58
+ return new Set();
59
+ }
60
+ const normalized = raw
61
+ .map((value) => (typeof value === 'string' ? value.trim() : ''))
62
+ .filter((value) => Boolean(value));
63
+ return new Set(normalized);
64
+ }
@@ -0,0 +1,12 @@
1
+ import { type RoutePoolTier, type RoutingFeatures } from '../types.js';
2
+ import type { ProviderRegistry } from '../provider-registry.js';
3
+ export declare function sortByPriority(routeNames: string[]): string[];
4
+ export declare function initializeRouteQueue(candidates: string[]): string[];
5
+ export declare function normalizeRouteAlias(routeName: string | undefined): string;
6
+ export declare function routeHasForceFlag(routeName: string, routing: Record<string, RoutePoolTier[]>): boolean;
7
+ export declare function routeHasTargets(pools?: RoutePoolTier[]): boolean;
8
+ export declare function sortRoutePools(pools?: RoutePoolTier[]): RoutePoolTier[];
9
+ export declare function buildRouteCandidates(requestedRoute: string, classificationCandidates: string[] | undefined, features: RoutingFeatures, routing: Record<string, RoutePoolTier[]>, providerRegistry: ProviderRegistry): string[];
10
+ export declare function extendRouteCandidatesForState(candidates: string[], state: {
11
+ allowedProviders?: Set<string>;
12
+ }, routing: Record<string, RoutePoolTier[]>): string[];
@@ -0,0 +1,150 @@
1
+ import { DEFAULT_ROUTE, ROUTE_PRIORITY } from '../types.js';
2
+ export function sortByPriority(routeNames) {
3
+ return [...routeNames].sort((a, b) => routeWeight(a) - routeWeight(b));
4
+ }
5
+ export function initializeRouteQueue(candidates) {
6
+ return Array.from(new Set(candidates));
7
+ }
8
+ export function normalizeRouteAlias(routeName) {
9
+ const base = routeName && routeName.trim() ? routeName.trim() : DEFAULT_ROUTE;
10
+ return base;
11
+ }
12
+ export function routeHasForceFlag(routeName, routing) {
13
+ const pools = routing[routeName];
14
+ if (!Array.isArray(pools)) {
15
+ return false;
16
+ }
17
+ return pools.some((pool) => pool.force);
18
+ }
19
+ export function routeHasTargets(pools) {
20
+ if (!Array.isArray(pools)) {
21
+ return false;
22
+ }
23
+ return pools.some((pool) => Array.isArray(pool.targets) && pool.targets.length > 0);
24
+ }
25
+ export function sortRoutePools(pools) {
26
+ if (!Array.isArray(pools)) {
27
+ return [];
28
+ }
29
+ return pools
30
+ .filter((pool) => Array.isArray(pool.targets) && pool.targets.length > 0)
31
+ .sort((a, b) => {
32
+ if (a.backup && !b.backup)
33
+ return 1;
34
+ if (!a.backup && b.backup)
35
+ return -1;
36
+ if (a.priority !== b.priority) {
37
+ return b.priority - a.priority;
38
+ }
39
+ return a.id.localeCompare(b.id);
40
+ });
41
+ }
42
+ export function buildRouteCandidates(requestedRoute, classificationCandidates, features, routing, providerRegistry) {
43
+ const forceVision = routeHasForceFlag('vision', routing);
44
+ const normalized = normalizeRouteAlias(requestedRoute || DEFAULT_ROUTE);
45
+ const baseList = [];
46
+ if (classificationCandidates && classificationCandidates.length) {
47
+ for (const candidate of classificationCandidates) {
48
+ baseList.push(normalizeRouteAlias(candidate));
49
+ }
50
+ }
51
+ else if (normalized) {
52
+ baseList.push(normalized);
53
+ }
54
+ if (features.hasImageAttachment && !forceVision) {
55
+ const visionAwareRoutes = [DEFAULT_ROUTE, 'thinking'];
56
+ for (const routeName of visionAwareRoutes) {
57
+ if (routeHasTargets(routing[routeName])) {
58
+ if (!baseList.includes(routeName)) {
59
+ baseList.push(routeName);
60
+ }
61
+ }
62
+ }
63
+ }
64
+ let ordered = sortByPriority(baseList);
65
+ if (features.hasImageAttachment && !forceVision) {
66
+ ordered = reorderForInlineVision(ordered, routing, providerRegistry);
67
+ }
68
+ const deduped = [];
69
+ for (const routeName of ordered) {
70
+ if (routeName && !deduped.includes(routeName)) {
71
+ deduped.push(routeName);
72
+ }
73
+ }
74
+ if (!deduped.includes(DEFAULT_ROUTE)) {
75
+ deduped.push(DEFAULT_ROUTE);
76
+ }
77
+ const filtered = deduped.filter((routeName) => routeHasTargets(routing[routeName]));
78
+ if (!filtered.includes(DEFAULT_ROUTE) && routeHasTargets(routing[DEFAULT_ROUTE])) {
79
+ filtered.push(DEFAULT_ROUTE);
80
+ }
81
+ return filtered.length ? filtered : [DEFAULT_ROUTE];
82
+ }
83
+ export function extendRouteCandidatesForState(candidates, state, routing) {
84
+ // When provider allowlists are active (e.g. "<**!glm**>"), routing should not be bounded by
85
+ // classifier candidates only. Otherwise, a perfectly valid provider that exists in config
86
+ // (e.g. in a backup/default pool) can become unreachable and cause PROVIDER_NOT_AVAILABLE.
87
+ //
88
+ // We keep original ordering, then append all known routes (by priority) as a fallback search space.
89
+ if (!state.allowedProviders || state.allowedProviders.size === 0) {
90
+ return candidates;
91
+ }
92
+ const allRoutes = sortByPriority(Object.keys(routing).filter((routeName) => routeName && routeHasTargets(routing[routeName])));
93
+ const expanded = Array.isArray(candidates) ? [...candidates] : [];
94
+ for (const routeName of allRoutes) {
95
+ if (!expanded.includes(routeName)) {
96
+ expanded.push(routeName);
97
+ }
98
+ }
99
+ return expanded;
100
+ }
101
+ function routeWeight(routeName) {
102
+ const idx = ROUTE_PRIORITY.indexOf(routeName);
103
+ return idx >= 0 ? idx : ROUTE_PRIORITY.length;
104
+ }
105
+ function reorderForInlineVision(routeNames, routing, providerRegistry) {
106
+ const unique = Array.from(new Set(routeNames.filter(Boolean)));
107
+ if (!unique.length) {
108
+ return unique;
109
+ }
110
+ const inlinePreferred = [];
111
+ const inlineRoutes = [DEFAULT_ROUTE, 'thinking'];
112
+ for (const routeName of inlineRoutes) {
113
+ if (routeSupportsInlineVision(routeName, routing, providerRegistry) && !inlinePreferred.includes(routeName)) {
114
+ inlinePreferred.push(routeName);
115
+ }
116
+ }
117
+ if (!inlinePreferred.length) {
118
+ return unique;
119
+ }
120
+ const remaining = [];
121
+ for (const routeName of unique) {
122
+ if (!inlinePreferred.includes(routeName)) {
123
+ remaining.push(routeName);
124
+ }
125
+ }
126
+ return [...inlinePreferred, ...remaining];
127
+ }
128
+ function routeSupportsInlineVision(routeName, routing, providerRegistry) {
129
+ const pools = routing[routeName];
130
+ if (!Array.isArray(pools)) {
131
+ return false;
132
+ }
133
+ for (const pool of pools) {
134
+ if (!Array.isArray(pool.targets)) {
135
+ continue;
136
+ }
137
+ for (const providerKey of pool.targets) {
138
+ try {
139
+ const profile = providerRegistry.get(providerKey);
140
+ if (profile.providerType === 'responses' || profile.providerType === 'gemini') {
141
+ return true;
142
+ }
143
+ }
144
+ catch {
145
+ // ignore unknown providers when probing capabilities
146
+ }
147
+ }
148
+ }
149
+ return false;
150
+ }
@@ -0,0 +1,4 @@
1
+ import type { RoutePoolTier } from '../types.js';
2
+ import type { RoutingInstructionState } from '../routing-instructions.js';
3
+ import type { ProviderRegistry } from '../provider-registry.js';
4
+ export declare function filterCandidatesByRoutingState(routes: string[], state: RoutingInstructionState, routing: Record<string, RoutePoolTier[]>, providerRegistry: ProviderRegistry): string[];
@@ -0,0 +1,50 @@
1
+ import { extractKeyAlias, extractKeyIndex, extractProviderId, getProviderModelId } from './key-parsing.js';
2
+ export function filterCandidatesByRoutingState(routes, state, routing, providerRegistry) {
3
+ if (state.allowedProviders.size === 0 &&
4
+ state.disabledProviders.size === 0 &&
5
+ state.disabledKeys.size === 0 &&
6
+ state.disabledModels.size === 0) {
7
+ return routes;
8
+ }
9
+ return routes.filter((routeName) => {
10
+ const pools = routing[routeName];
11
+ if (!pools)
12
+ return false;
13
+ for (const pool of pools) {
14
+ if (!Array.isArray(pool.targets) || pool.targets.length === 0) {
15
+ continue;
16
+ }
17
+ for (const providerKey of pool.targets) {
18
+ const providerId = extractProviderId(providerKey);
19
+ if (!providerId)
20
+ continue;
21
+ if (state.allowedProviders.size > 0 && !state.allowedProviders.has(providerId)) {
22
+ continue;
23
+ }
24
+ if (state.disabledProviders.has(providerId)) {
25
+ continue;
26
+ }
27
+ const disabledKeys = state.disabledKeys.get(providerId);
28
+ if (disabledKeys && disabledKeys.size > 0) {
29
+ const keyAlias = extractKeyAlias(providerKey);
30
+ const keyIndex = extractKeyIndex(providerKey);
31
+ if (keyAlias && disabledKeys.has(keyAlias)) {
32
+ continue;
33
+ }
34
+ if (keyIndex !== undefined && disabledKeys.has(keyIndex + 1)) {
35
+ continue;
36
+ }
37
+ }
38
+ const disabledModels = state.disabledModels.get(providerId);
39
+ if (disabledModels && disabledModels.size > 0) {
40
+ const modelId = getProviderModelId(providerKey, providerRegistry);
41
+ if (modelId && disabledModels.has(modelId)) {
42
+ continue;
43
+ }
44
+ }
45
+ return true;
46
+ }
47
+ }
48
+ return false;
49
+ });
50
+ }
@@ -0,0 +1,39 @@
1
+ import type { RoutePoolTier, RouterMetadataInput, RoutingFeatures, ProviderQuotaView } from '../types.js';
2
+ import type { ContextAdvisor } from '../context-advisor.js';
3
+ import type { RouteLoadBalancer } from '../load-balancer.js';
4
+ import type { ProviderHealthManager } from '../health-manager.js';
5
+ import type { ProviderRegistry } from '../provider-registry.js';
6
+ export type SelectionDeps = {
7
+ routing: Record<string, RoutePoolTier[]>;
8
+ providerRegistry: ProviderRegistry;
9
+ healthManager: ProviderHealthManager;
10
+ contextAdvisor: ContextAdvisor;
11
+ loadBalancer: RouteLoadBalancer;
12
+ isProviderCoolingDown: (providerKey: string) => boolean;
13
+ resolveStickyKey: (metadata: RouterMetadataInput) => string | undefined;
14
+ quotaView?: ProviderQuotaView;
15
+ aliasQueueStore?: Map<string, string[]>;
16
+ };
17
+ export type TrySelectFromTierOptions = {
18
+ disabledProviders?: Set<string>;
19
+ disabledKeysMap?: Map<string, Set<string | number>>;
20
+ allowedProviders?: Set<string>;
21
+ disabledModels?: Map<string, Set<string>>;
22
+ requiredProviderKeys?: Set<string>;
23
+ allowAliasRotation?: boolean;
24
+ };
25
+ export type SelectProviderInput = {
26
+ routeName: string;
27
+ tier: RoutePoolTier;
28
+ stickyKey: string | undefined;
29
+ estimatedTokens: number;
30
+ features: RoutingFeatures;
31
+ deps: SelectionDeps;
32
+ options: TrySelectFromTierOptions;
33
+ };
34
+ export type SelectionResult = {
35
+ providerKey: string | null;
36
+ poolTargets: string[];
37
+ tierId?: string;
38
+ failureHint?: string;
39
+ };
@@ -0,0 +1,11 @@
1
+ import type { RouterMetadataInput, RoutingFeatures } from '../types.js';
2
+ import type { RoutingInstructionState } from '../routing-instructions.js';
3
+ import type { SelectionDeps } from './selection-deps.js';
4
+ export declare function selectFromStickyPool(stickyKeySet: Set<string>, metadata: RouterMetadataInput, features: RoutingFeatures, state: RoutingInstructionState, deps: SelectionDeps, options: {
5
+ allowAliasRotation?: boolean;
6
+ }): {
7
+ providerKey: string;
8
+ routeUsed: string;
9
+ pool: string[];
10
+ poolId?: string;
11
+ } | null;
@@ -0,0 +1,109 @@
1
+ import { trySelectFromTier } from './tier-selection.js';
2
+ import { extractKeyAlias, extractKeyIndex, extractProviderId, getProviderModelId } from './key-parsing.js';
3
+ export function selectFromStickyPool(stickyKeySet, metadata, features, state, deps, options) {
4
+ if (!stickyKeySet || stickyKeySet.size === 0) {
5
+ return null;
6
+ }
7
+ const allowedProviders = new Set(state.allowedProviders);
8
+ const disabledProviders = new Set(state.disabledProviders);
9
+ const disabledKeysMap = new Map(Array.from(state.disabledKeys.entries()).map(([provider, keys]) => [
10
+ provider,
11
+ new Set(Array.from(keys).map((k) => (typeof k === 'string' ? k : k + 1)))
12
+ ]));
13
+ const disabledModels = new Map(Array.from(state.disabledModels.entries()).map(([provider, models]) => [provider, new Set(models)]));
14
+ let candidates = Array.from(stickyKeySet).filter((key) => !deps.isProviderCoolingDown(key));
15
+ if (!candidates.length && stickyKeySet.size === 1) {
16
+ candidates = Array.from(stickyKeySet);
17
+ }
18
+ const quotaView = deps.quotaView;
19
+ const now = quotaView ? Date.now() : 0;
20
+ if (quotaView) {
21
+ const filtered = candidates.filter((key) => {
22
+ const entry = quotaView(key);
23
+ if (!entry) {
24
+ return true;
25
+ }
26
+ if (!entry.inPool) {
27
+ return false;
28
+ }
29
+ if (entry.cooldownUntil && entry.cooldownUntil > now) {
30
+ return false;
31
+ }
32
+ if (entry.blacklistUntil && entry.blacklistUntil > now) {
33
+ return false;
34
+ }
35
+ return true;
36
+ });
37
+ if (filtered.length > 0 || candidates.length !== 1) {
38
+ candidates = filtered;
39
+ }
40
+ }
41
+ if (allowedProviders.size > 0) {
42
+ candidates = candidates.filter((key) => {
43
+ const providerId = extractProviderId(key);
44
+ return providerId && allowedProviders.has(providerId);
45
+ });
46
+ }
47
+ if (disabledProviders.size > 0) {
48
+ candidates = candidates.filter((key) => {
49
+ const providerId = extractProviderId(key);
50
+ return providerId && !disabledProviders.has(providerId);
51
+ });
52
+ }
53
+ if (disabledKeysMap.size > 0 || disabledModels.size > 0) {
54
+ candidates = candidates.filter((key) => {
55
+ const providerId = extractProviderId(key);
56
+ if (!providerId) {
57
+ return true;
58
+ }
59
+ const disabledKeys = disabledKeysMap.get(providerId);
60
+ if (disabledKeys && disabledKeys.size > 0) {
61
+ const keyAlias = extractKeyAlias(key);
62
+ const keyIndex = extractKeyIndex(key);
63
+ if (keyAlias && disabledKeys.has(keyAlias)) {
64
+ return false;
65
+ }
66
+ if (keyIndex !== undefined && disabledKeys.has(keyIndex + 1)) {
67
+ return false;
68
+ }
69
+ }
70
+ const disabledModelSet = disabledModels.get(providerId);
71
+ if (disabledModelSet && disabledModelSet.size > 0) {
72
+ const modelId = getProviderModelId(key, deps.providerRegistry);
73
+ if (modelId && disabledModelSet.has(modelId)) {
74
+ return false;
75
+ }
76
+ }
77
+ return true;
78
+ });
79
+ }
80
+ if (!candidates.length) {
81
+ return null;
82
+ }
83
+ const stickyKey = options.allowAliasRotation ? undefined : deps.resolveStickyKey(metadata);
84
+ const estimatedTokens = typeof features.estimatedTokens === 'number' && Number.isFinite(features.estimatedTokens)
85
+ ? Math.max(0, features.estimatedTokens)
86
+ : 0;
87
+ const tier = {
88
+ id: 'sticky-primary',
89
+ targets: candidates,
90
+ priority: 0
91
+ };
92
+ const { providerKey, poolTargets, tierId } = trySelectFromTier('sticky', tier, stickyKey, estimatedTokens, features, deps, {
93
+ disabledProviders,
94
+ disabledKeysMap,
95
+ allowedProviders,
96
+ disabledModels,
97
+ requiredProviderKeys: stickyKeySet,
98
+ allowAliasRotation: options.allowAliasRotation
99
+ });
100
+ if (!providerKey) {
101
+ return null;
102
+ }
103
+ return {
104
+ providerKey,
105
+ routeUsed: 'sticky',
106
+ pool: poolTargets,
107
+ poolId: tierId
108
+ };
109
+ }
@@ -0,0 +1,12 @@
1
+ import type { ProviderHealthManager } from '../health-manager.js';
2
+ import type { ProviderRegistry } from '../provider-registry.js';
3
+ export declare function pickPriorityGroup(opts: {
4
+ candidates: string[];
5
+ orderedTargets: string[];
6
+ providerRegistry: ProviderRegistry;
7
+ healthManager: ProviderHealthManager;
8
+ penalties?: Record<string, number>;
9
+ }): {
10
+ groupId: string;
11
+ groupCandidates: string[];
12
+ } | null;
@@ -0,0 +1,55 @@
1
+ import { extractProviderId, getProviderModelId } from './key-parsing.js';
2
+ function resolvePriorityMeta(orderedTargets, providerRegistry) {
3
+ // Priority mode semantics (strict group priority + alias-level balancing):
4
+ // - Targets are interpreted as ordered (providerId, modelId) groups.
5
+ // - Group base priorities: 100, 90, 80, ... (step=10) by appearance order.
6
+ // - Within a group (different auth aliases), base scores: 100, 99, 98, ... (step=1).
7
+ //
8
+ // Group selection is strict: always use the best group until it is unavailable.
9
+ // Alias selection is balanced within the chosen group (RR / health-weighted / context-weighted).
10
+ const meta = new Map();
11
+ if (!Array.isArray(orderedTargets) || orderedTargets.length === 0) {
12
+ return meta;
13
+ }
14
+ let groupIndex = -1;
15
+ let aliasOffset = 0;
16
+ let lastGroupKey = '';
17
+ for (const key of orderedTargets) {
18
+ const providerId = extractProviderId(key) ?? '';
19
+ const modelId = getProviderModelId(key, providerRegistry) ?? '';
20
+ const groupKey = `${providerId}::${modelId}`;
21
+ if (groupKey !== lastGroupKey) {
22
+ groupIndex += 1;
23
+ aliasOffset = 0;
24
+ lastGroupKey = groupKey;
25
+ }
26
+ const groupBase = 100 - groupIndex * 10;
27
+ const base = groupBase - aliasOffset;
28
+ meta.set(key, { groupId: `${providerId}.${modelId}`, groupBase, base });
29
+ aliasOffset += 1;
30
+ }
31
+ return meta;
32
+ }
33
+ export function pickPriorityGroup(opts) {
34
+ const { candidates, orderedTargets, providerRegistry, healthManager, penalties } = opts;
35
+ const meta = resolvePriorityMeta(orderedTargets, providerRegistry);
36
+ let bestGroupId = null;
37
+ let bestScore = Number.NEGATIVE_INFINITY;
38
+ for (const key of candidates) {
39
+ if (!healthManager.isAvailable(key))
40
+ continue;
41
+ const m = meta.get(key);
42
+ if (!m)
43
+ continue;
44
+ const penalty = penalties ? Math.max(0, Math.floor(penalties[key] ?? 0)) : 0;
45
+ const score = m.base - penalty;
46
+ if (score > bestScore) {
47
+ bestScore = score;
48
+ bestGroupId = m.groupId;
49
+ }
50
+ }
51
+ if (!bestGroupId)
52
+ return null;
53
+ const groupCandidates = candidates.filter((key) => meta.get(key)?.groupId === bestGroupId);
54
+ return groupCandidates.length ? { groupId: bestGroupId, groupCandidates } : null;
55
+ }
@@ -0,0 +1,22 @@
1
+ import type { ContextAdvisorResult } from '../context-advisor.js';
2
+ import { type ResolvedHealthWeightedConfig } from '../health-weighted.js';
3
+ import type { RoutePoolTier } from '../types.js';
4
+ import type { ResolvedContextWeightedConfig } from '../context-weighted.js';
5
+ import type { SelectionDeps, TrySelectFromTierOptions } from './selection-deps.js';
6
+ export declare function selectProviderKeyFromCandidatePool(opts: {
7
+ routeName: string;
8
+ tier: RoutePoolTier;
9
+ stickyKey: string | undefined;
10
+ candidates: string[];
11
+ isSafePool: boolean;
12
+ deps: SelectionDeps;
13
+ options: TrySelectFromTierOptions;
14
+ contextResult: ContextAdvisorResult;
15
+ warnRatio: number;
16
+ excludedKeys: Set<string>;
17
+ isRecoveryAttempt: boolean;
18
+ now: number;
19
+ nowForWeights: number;
20
+ healthWeightedCfg: ResolvedHealthWeightedConfig;
21
+ contextWeightedCfg: ResolvedContextWeightedConfig;
22
+ }): string | null;