@x12i/ai-gateway 7.9.1

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 (179) hide show
  1. package/README.md +4259 -0
  2. package/config.defaults.json +31 -0
  3. package/dist/activity-manager.d.ts +206 -0
  4. package/dist/activity-manager.js +1051 -0
  5. package/dist/config/activity-tracking-config.d.ts +11 -0
  6. package/dist/config/activity-tracking-config.js +15 -0
  7. package/dist/config.defaults.json +31 -0
  8. package/dist/content-normalizer/content-normalizer.d.ts +46 -0
  9. package/dist/content-normalizer/content-normalizer.js +393 -0
  10. package/dist/content-normalizer/index.d.ts +7 -0
  11. package/dist/content-normalizer/index.js +6 -0
  12. package/dist/content-normalizer/types.d.ts +33 -0
  13. package/dist/content-normalizer/types.js +4 -0
  14. package/dist/defaults/instructions-blocks.json +61 -0
  15. package/dist/defaults/model-config.json +16 -0
  16. package/dist/defaults/template-rendering.json +6 -0
  17. package/dist/flex-md-loader.d.ts +109 -0
  18. package/dist/flex-md-loader.js +940 -0
  19. package/dist/gateway-config.d.ts +49 -0
  20. package/dist/gateway-config.js +292 -0
  21. package/dist/gateway-conversion.d.ts +29 -0
  22. package/dist/gateway-conversion.js +174 -0
  23. package/dist/gateway-instructions.d.ts +30 -0
  24. package/dist/gateway-instructions.js +62 -0
  25. package/dist/gateway-memory.d.ts +51 -0
  26. package/dist/gateway-memory.js +207 -0
  27. package/dist/gateway-messages.d.ts +23 -0
  28. package/dist/gateway-messages.js +83 -0
  29. package/dist/gateway-meta.d.ts +25 -0
  30. package/dist/gateway-meta.js +87 -0
  31. package/dist/gateway-provider-auto-register.d.ts +17 -0
  32. package/dist/gateway-provider-auto-register.js +159 -0
  33. package/dist/gateway-provider.d.ts +54 -0
  34. package/dist/gateway-provider.js +202 -0
  35. package/dist/gateway-rate-limiter-constants.d.ts +16 -0
  36. package/dist/gateway-rate-limiter-constants.js +16 -0
  37. package/dist/gateway-rate-limiter.d.ts +56 -0
  38. package/dist/gateway-rate-limiter.js +107 -0
  39. package/dist/gateway-retry.d.ts +49 -0
  40. package/dist/gateway-retry.js +204 -0
  41. package/dist/gateway-utils.d.ts +21 -0
  42. package/dist/gateway-utils.js +181 -0
  43. package/dist/gateway-validation.d.ts +13 -0
  44. package/dist/gateway-validation.js +50 -0
  45. package/dist/gateway.d.ts +39 -0
  46. package/dist/gateway.js +430 -0
  47. package/dist/index.d.ts +36 -0
  48. package/dist/index.js +55 -0
  49. package/dist/instruction-errors.d.ts +16 -0
  50. package/dist/instruction-errors.js +29 -0
  51. package/dist/instruction-optimizer.d.ts +113 -0
  52. package/dist/instruction-optimizer.js +293 -0
  53. package/dist/instructions-parser.d.ts +31 -0
  54. package/dist/instructions-parser.js +56 -0
  55. package/dist/logger-factory.d.ts +17 -0
  56. package/dist/logger-factory.js +42 -0
  57. package/dist/message-builder.d.ts +41 -0
  58. package/dist/message-builder.js +522 -0
  59. package/dist/object-types-library-integration.d.ts +22 -0
  60. package/dist/object-types-library-integration.js +27 -0
  61. package/dist/object-types-library.d.ts +351 -0
  62. package/dist/object-types-library.js +210 -0
  63. package/dist/output-auditor.d.ts +44 -0
  64. package/dist/output-auditor.js +49 -0
  65. package/dist/request-report-generator.d.ts +60 -0
  66. package/dist/request-report-generator.js +169 -0
  67. package/dist/response-analyzer/format-type-detector.d.ts +35 -0
  68. package/dist/response-analyzer/format-type-detector.js +115 -0
  69. package/dist/response-analyzer/index.d.ts +9 -0
  70. package/dist/response-analyzer/index.js +8 -0
  71. package/dist/response-analyzer/object-type-detector.d.ts +42 -0
  72. package/dist/response-analyzer/object-type-detector.js +95 -0
  73. package/dist/response-analyzer/response-analyzer.d.ts +38 -0
  74. package/dist/response-analyzer/response-analyzer.js +97 -0
  75. package/dist/response-analyzer/types.d.ts +97 -0
  76. package/dist/response-analyzer/types.js +4 -0
  77. package/dist/response-fallback-fixer.d.ts +11 -0
  78. package/dist/response-fallback-fixer.js +123 -0
  79. package/dist/runtime-objects.d.ts +52 -0
  80. package/dist/runtime-objects.js +46 -0
  81. package/dist/template-parser.d.ts +58 -0
  82. package/dist/template-parser.js +99 -0
  83. package/dist/template-render-merge.d.ts +9 -0
  84. package/dist/template-render-merge.js +40 -0
  85. package/dist/troubleshooting-helper.d.ts +123 -0
  86. package/dist/troubleshooting-helper.js +596 -0
  87. package/dist/types.d.ts +1173 -0
  88. package/dist/types.js +6 -0
  89. package/dist/usage-tracker.d.ts +78 -0
  90. package/dist/usage-tracker.js +79 -0
  91. package/dist-cjs/activity-manager.cjs +1056 -0
  92. package/dist-cjs/activity-manager.d.ts +206 -0
  93. package/dist-cjs/config/activity-tracking-config.cjs +18 -0
  94. package/dist-cjs/config/activity-tracking-config.d.ts +11 -0
  95. package/dist-cjs/config.defaults.json +31 -0
  96. package/dist-cjs/content-normalizer/content-normalizer.cjs +398 -0
  97. package/dist-cjs/content-normalizer/content-normalizer.d.ts +46 -0
  98. package/dist-cjs/content-normalizer/index.cjs +12 -0
  99. package/dist-cjs/content-normalizer/index.d.ts +7 -0
  100. package/dist-cjs/content-normalizer/types.cjs +5 -0
  101. package/dist-cjs/content-normalizer/types.d.ts +33 -0
  102. package/dist-cjs/defaults/instructions-blocks.json +61 -0
  103. package/dist-cjs/defaults/model-config.json +16 -0
  104. package/dist-cjs/defaults/template-rendering.json +6 -0
  105. package/dist-cjs/flex-md-loader.cjs +986 -0
  106. package/dist-cjs/flex-md-loader.d.ts +109 -0
  107. package/dist-cjs/gateway-config.cjs +331 -0
  108. package/dist-cjs/gateway-config.d.ts +49 -0
  109. package/dist-cjs/gateway-conversion.cjs +212 -0
  110. package/dist-cjs/gateway-conversion.d.ts +29 -0
  111. package/dist-cjs/gateway-instructions.cjs +67 -0
  112. package/dist-cjs/gateway-instructions.d.ts +30 -0
  113. package/dist-cjs/gateway-memory.cjs +211 -0
  114. package/dist-cjs/gateway-memory.d.ts +51 -0
  115. package/dist-cjs/gateway-messages.cjs +86 -0
  116. package/dist-cjs/gateway-messages.d.ts +23 -0
  117. package/dist-cjs/gateway-meta.cjs +90 -0
  118. package/dist-cjs/gateway-meta.d.ts +25 -0
  119. package/dist-cjs/gateway-provider-auto-register.cjs +195 -0
  120. package/dist-cjs/gateway-provider-auto-register.d.ts +17 -0
  121. package/dist-cjs/gateway-provider.cjs +214 -0
  122. package/dist-cjs/gateway-provider.d.ts +54 -0
  123. package/dist-cjs/gateway-rate-limiter-constants.cjs +19 -0
  124. package/dist-cjs/gateway-rate-limiter-constants.d.ts +16 -0
  125. package/dist-cjs/gateway-rate-limiter.cjs +111 -0
  126. package/dist-cjs/gateway-rate-limiter.d.ts +56 -0
  127. package/dist-cjs/gateway-retry.cjs +212 -0
  128. package/dist-cjs/gateway-retry.d.ts +49 -0
  129. package/dist-cjs/gateway-utils.cjs +219 -0
  130. package/dist-cjs/gateway-utils.d.ts +21 -0
  131. package/dist-cjs/gateway-validation.cjs +54 -0
  132. package/dist-cjs/gateway-validation.d.ts +13 -0
  133. package/dist-cjs/gateway.cjs +434 -0
  134. package/dist-cjs/gateway.d.ts +39 -0
  135. package/dist-cjs/index.cjs +108 -0
  136. package/dist-cjs/index.d.ts +36 -0
  137. package/dist-cjs/instruction-errors.cjs +34 -0
  138. package/dist-cjs/instruction-errors.d.ts +16 -0
  139. package/dist-cjs/instruction-optimizer.cjs +299 -0
  140. package/dist-cjs/instruction-optimizer.d.ts +113 -0
  141. package/dist-cjs/instructions-parser.cjs +61 -0
  142. package/dist-cjs/instructions-parser.d.ts +31 -0
  143. package/dist-cjs/logger-factory.cjs +45 -0
  144. package/dist-cjs/logger-factory.d.ts +17 -0
  145. package/dist-cjs/message-builder.cjs +558 -0
  146. package/dist-cjs/message-builder.d.ts +41 -0
  147. package/dist-cjs/object-types-library-integration.cjs +32 -0
  148. package/dist-cjs/object-types-library-integration.d.ts +22 -0
  149. package/dist-cjs/object-types-library.cjs +215 -0
  150. package/dist-cjs/object-types-library.d.ts +351 -0
  151. package/dist-cjs/output-auditor.cjs +52 -0
  152. package/dist-cjs/output-auditor.d.ts +44 -0
  153. package/dist-cjs/request-report-generator.cjs +172 -0
  154. package/dist-cjs/request-report-generator.d.ts +60 -0
  155. package/dist-cjs/response-analyzer/format-type-detector.cjs +119 -0
  156. package/dist-cjs/response-analyzer/format-type-detector.d.ts +35 -0
  157. package/dist-cjs/response-analyzer/index.cjs +14 -0
  158. package/dist-cjs/response-analyzer/index.d.ts +9 -0
  159. package/dist-cjs/response-analyzer/object-type-detector.cjs +99 -0
  160. package/dist-cjs/response-analyzer/object-type-detector.d.ts +42 -0
  161. package/dist-cjs/response-analyzer/response-analyzer.cjs +101 -0
  162. package/dist-cjs/response-analyzer/response-analyzer.d.ts +38 -0
  163. package/dist-cjs/response-analyzer/types.cjs +5 -0
  164. package/dist-cjs/response-analyzer/types.d.ts +97 -0
  165. package/dist-cjs/response-fallback-fixer.cjs +126 -0
  166. package/dist-cjs/response-fallback-fixer.d.ts +11 -0
  167. package/dist-cjs/runtime-objects.cjs +52 -0
  168. package/dist-cjs/runtime-objects.d.ts +52 -0
  169. package/dist-cjs/template-parser.cjs +136 -0
  170. package/dist-cjs/template-parser.d.ts +58 -0
  171. package/dist-cjs/template-render-merge.cjs +43 -0
  172. package/dist-cjs/template-render-merge.d.ts +9 -0
  173. package/dist-cjs/troubleshooting-helper.cjs +611 -0
  174. package/dist-cjs/troubleshooting-helper.d.ts +123 -0
  175. package/dist-cjs/types.cjs +7 -0
  176. package/dist-cjs/types.d.ts +1173 -0
  177. package/dist-cjs/usage-tracker.cjs +83 -0
  178. package/dist-cjs/usage-tracker.d.ts +78 -0
  179. package/package.json +91 -0
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Gateway Configuration Module
3
+ * Handles configuration loading and setup
4
+ */
5
+ import type { GatewayConfig } from './types.js';
6
+ import type { Logxer } from '@x12i/logxer';
7
+ import { LLMProviderRouter } from '@x12i/ai-providers-router';
8
+ import { ActivityManager } from './activity-manager.js';
9
+ import { UsageTracker } from './usage-tracker.js';
10
+ import type { MessageBuilderConfig } from './message-builder.js';
11
+ import type { TemplateRenderOptions } from '@x12i/rendrix';
12
+ export interface GatewayConfigContext {
13
+ defaultModelConfig: Record<string, unknown>;
14
+ defaultInstructionsBlocks: Record<string, any>;
15
+ config: GatewayConfig;
16
+ logger: Logxer;
17
+ router: LLMProviderRouter;
18
+ activityManager: ActivityManager;
19
+ usageTracker: UsageTracker;
20
+ messageBuilderConfig: MessageBuilderConfig;
21
+ }
22
+ /**
23
+ * Loads configuration from JSON files (model config and instructionsBlocks)
24
+ * Note: Called before logger is initialized, so no logging here
25
+ */
26
+ export declare function loadConfig(): {
27
+ defaultModelConfig: Record<string, unknown>;
28
+ defaultInstructionsBlocks: Record<string, any>;
29
+ defaultTemplateRendering?: TemplateRenderOptions;
30
+ };
31
+ /**
32
+ * Gets the minimum flex-md compliance level from environment variable
33
+ * Defaults to 'L0' if not set or invalid
34
+ */
35
+ export declare function getFlexMdMinComplianceLevel(): 'L0' | 'L1' | 'L2' | 'L3';
36
+ /**
37
+ * Sets up request interceptor for jobId propagation and config cleanup
38
+ */
39
+ export declare function setupRequestInterceptor(router: LLMProviderRouter, logger: Logxer): void;
40
+ /**
41
+ * Initializes gateway components
42
+ */
43
+ export declare function initializeGatewayComponents(config: GatewayConfig, defaultModelConfig: Record<string, unknown>, defaultInstructionsBlocks: Record<string, any>, defaultTemplateRendering?: TemplateRenderOptions): {
44
+ logger: Logxer;
45
+ router: LLMProviderRouter;
46
+ activityManager: ActivityManager;
47
+ usageTracker: UsageTracker;
48
+ messageBuilderConfig: MessageBuilderConfig;
49
+ };
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Gateway Configuration Module
3
+ * Handles configuration loading and setup
4
+ */
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { fileURLToPath } from 'url';
8
+ /** Resolve current module directory across ESM/CJS builds. */
9
+ function getModuleDir() {
10
+ if (typeof __dirname !== 'undefined') {
11
+ return __dirname;
12
+ }
13
+ try {
14
+ const getMetaUrl = new Function('return import.meta.url');
15
+ const metaUrl = getMetaUrl();
16
+ if (metaUrl && metaUrl.startsWith('file:')) {
17
+ return path.dirname(fileURLToPath(metaUrl));
18
+ }
19
+ }
20
+ catch {
21
+ // Fall through to cwd
22
+ }
23
+ return process.cwd();
24
+ }
25
+ /** Resolve directory containing defaults/ with package-local precedence. */
26
+ function getDefaultsDir() {
27
+ const moduleDir = getModuleDir();
28
+ const cwd = process.cwd();
29
+ const candidates = [
30
+ moduleDir,
31
+ path.resolve(moduleDir, '..'),
32
+ path.resolve(moduleDir, '../dist'),
33
+ path.resolve(moduleDir, '../dist-cjs'),
34
+ path.resolve(moduleDir, '../src'),
35
+ path.join(cwd, 'dist'),
36
+ path.join(cwd, 'dist-cjs'),
37
+ path.join(cwd, 'src'),
38
+ ];
39
+ for (const dir of candidates) {
40
+ const modelConfigPath = path.join(dir, 'defaults', 'model-config.json');
41
+ if (fs.existsSync(modelConfigPath)) {
42
+ return dir;
43
+ }
44
+ }
45
+ // Keep existing behavior as a last fallback.
46
+ return path.join(cwd, 'dist');
47
+ }
48
+ import { LLMProviderRouter } from '@x12i/ai-providers-router';
49
+ import { createGatewayLogger } from './logger-factory.js';
50
+ import { ActivityManager } from './activity-manager.js';
51
+ import { UsageTracker } from './usage-tracker.js';
52
+ import { mergeTemplateRenderOptions } from './template-render-merge.js';
53
+ import { GatewayRateLimiter } from './gateway-rate-limiter.js';
54
+ import { DEFAULT_RATE_LIMIT_MIN_INTERVAL_MS, DEFAULT_RATE_LIMIT_ENABLED } from './gateway-rate-limiter-constants.js';
55
+ /**
56
+ * Loads configuration from JSON files (model config and instructionsBlocks)
57
+ * Note: Called before logger is initialized, so no logging here
58
+ */
59
+ export function loadConfig() {
60
+ const defaultModelConfig = {};
61
+ const defaultInstructionsBlocks = {};
62
+ let defaultTemplateRendering;
63
+ try {
64
+ const defaultsDir = getDefaultsDir();
65
+ const templateRenderingPath = path.join(defaultsDir, 'defaults', 'template-rendering.json');
66
+ if (fs.existsSync(templateRenderingPath)) {
67
+ const trContent = fs.readFileSync(templateRenderingPath, 'utf-8');
68
+ defaultTemplateRendering = JSON.parse(trContent);
69
+ }
70
+ // Load model config (includes rate limiting and retry defaults)
71
+ const modelConfigPath = path.join(defaultsDir, 'defaults', 'model-config.json');
72
+ if (fs.existsSync(modelConfigPath)) {
73
+ const content = fs.readFileSync(modelConfigPath, 'utf-8');
74
+ const parsed = JSON.parse(content);
75
+ Object.assign(defaultModelConfig, parsed);
76
+ }
77
+ // Load instructionsBlocks
78
+ const instructionsBlocksPath = path.join(defaultsDir, 'defaults', 'instructions-blocks.json');
79
+ if (fs.existsSync(instructionsBlocksPath)) {
80
+ const content = fs.readFileSync(instructionsBlocksPath, 'utf-8');
81
+ const parsed = JSON.parse(content);
82
+ // Use Object.assign to merge, preserving nested structure
83
+ Object.assign(defaultInstructionsBlocks, parsed);
84
+ // Debug: Log what was loaded (only in development)
85
+ if (process.env.NODE_ENV !== 'production') {
86
+ console.log('Loaded instructions blocks:', {
87
+ topLevelKeys: Object.keys(defaultInstructionsBlocks),
88
+ hasOutput: 'output' in defaultInstructionsBlocks,
89
+ outputKeys: 'output' in defaultInstructionsBlocks ? Object.keys(defaultInstructionsBlocks.output) : []
90
+ });
91
+ }
92
+ }
93
+ else {
94
+ // Optional file: fallback defaults below still keep the gateway functional.
95
+ if (process.env.NODE_ENV !== 'production') {
96
+ console.debug('Optional instructions blocks file not found at:', instructionsBlocksPath);
97
+ }
98
+ }
99
+ }
100
+ catch (error) {
101
+ // Can't log here as logger isn't initialized yet
102
+ // Error will be logged after logger initialization in constructor
103
+ console.warn('Failed to load defaults from JSON files:', error);
104
+ }
105
+ // Ensure critical blocks exist even if file loading failed
106
+ if (!defaultInstructionsBlocks['outputObjectPrefix']) {
107
+ defaultInstructionsBlocks['outputObjectPrefix'] = "Reply in Markdown. Return your entire answer inside a single ```markdown fenced block and nothing else. The content must conform to the schema provided below. If no items are found, return empty arrays (e.g., emails: []). Never ask for more input. Do not write conversational text. Do not write explanations. Do not ask questions.\n\n";
108
+ }
109
+ if (!defaultInstructionsBlocks['outputObjectTypesPrefix']) {
110
+ defaultInstructionsBlocks['outputObjectTypesPrefix'] = "Reply in Markdown. Return your entire answer inside a single ```markdown fenced block and nothing else. Select ONE of the following object types based on the input. The content must conform to the chosen schema. Do not write conversational text. Do not write explanations.\n\n";
111
+ }
112
+ return { defaultModelConfig, defaultInstructionsBlocks, defaultTemplateRendering };
113
+ }
114
+ /**
115
+ * Gets the minimum flex-md compliance level from environment variable
116
+ * Defaults to 'L0' if not set or invalid
117
+ */
118
+ export function getFlexMdMinComplianceLevel() {
119
+ const envValue = process.env.FLEX_MD_MIN_COMPLIANCE_LEVEL;
120
+ if (envValue === 'L0' || envValue === 'L1' || envValue === 'L2' || envValue === 'L3') {
121
+ return envValue;
122
+ }
123
+ return 'L0'; // Default: allow anything
124
+ }
125
+ /**
126
+ * Sets up request interceptor for jobId propagation and config cleanup
127
+ */
128
+ export function setupRequestInterceptor(router, logger) {
129
+ logger.debug('Setting up request interceptor for jobId propagation and config cleanup');
130
+ router.addRequestInterceptor(async (request, provider) => {
131
+ // Propagate jobId to request metadata
132
+ if (request.jobId) {
133
+ logger.verbose('Propagating jobId to request metadata', {
134
+ jobId: request.jobId,
135
+ provider: provider?.getProviderName?.() || 'unknown'
136
+ });
137
+ if (!request.config) {
138
+ request.config = {};
139
+ }
140
+ if (!request.config.metadata) {
141
+ request.config.metadata = {};
142
+ }
143
+ request.config.metadata.jobId = request.jobId;
144
+ }
145
+ // Remove 'provider' from config - router uses it for routing but providers don't accept it
146
+ // Router reads config.provider to determine which provider to call, but then passes
147
+ // the entire config to the provider, which rejects 'provider' as invalid
148
+ if (request.config && 'provider' in request.config) {
149
+ logger.debug('Removing provider from config before passing to provider', {
150
+ provider: request.config.provider
151
+ });
152
+ const { provider: _, ...cleanConfig } = request.config;
153
+ request.config = cleanConfig;
154
+ }
155
+ return request;
156
+ });
157
+ logger.debug('Request interceptor configured');
158
+ }
159
+ /**
160
+ * Initializes gateway components
161
+ */
162
+ export function initializeGatewayComponents(config, defaultModelConfig, defaultInstructionsBlocks, defaultTemplateRendering) {
163
+ // Initialize logger FIRST (before other components that might need it)
164
+ const logger = createGatewayLogger({
165
+ enableLogging: config.enableLogging ?? true,
166
+ packageName: config.packageName,
167
+ customLogger: config.logger
168
+ });
169
+ // Now that logger is initialized, log the defaults loading
170
+ logger.verbose('Gateway initializing', {
171
+ defaultEngine: config.defaultEngine,
172
+ hasDefaultInstructionsBlocks: Object.keys(defaultInstructionsBlocks).length > 0
173
+ });
174
+ // Activity tracking is handled by Activix internally.
175
+ // Initialize router - this is the ONLY way to access providers
176
+ // RouterConfig properties are inherited from RouterConfig interface
177
+ const routerConfig = {};
178
+ const defaultTarget = config.defaultTarget;
179
+ if (defaultTarget) {
180
+ routerConfig.defaultTarget = defaultTarget;
181
+ }
182
+ else if (config.defaultProvider !== undefined) {
183
+ routerConfig.defaultProvider = config.defaultProvider;
184
+ }
185
+ else if (config.defaultEngine !== undefined) {
186
+ routerConfig.defaultProvider = config.defaultEngine;
187
+ }
188
+ const fallbackChain = config.fallbackChain;
189
+ if (fallbackChain) {
190
+ routerConfig.fallbackChain = fallbackChain;
191
+ }
192
+ else if (config.fallbackChain !== undefined) {
193
+ routerConfig.fallbackChain = config.fallbackChain;
194
+ }
195
+ if (config.autoDiscover !== undefined)
196
+ routerConfig.autoDiscover = config.autoDiscover;
197
+ if (config.usageTracker !== undefined)
198
+ routerConfig.usageTracker = config.usageTracker;
199
+ // OpenRouter: enable when key is set and not explicitly disabled (so consumers get a working default without registering providers).
200
+ // Prefer explicit config from consumer (e.g. ai-skills) to avoid env-loading timing; fall back to process.env.
201
+ const explicitOpenRouterKey = config.openrouter?.apiKey;
202
+ const isExplicitKey = typeof explicitOpenRouterKey === 'string' && !explicitOpenRouterKey.startsWith('ENV.');
203
+ const openRouterKey = isExplicitKey ? explicitOpenRouterKey : (process.env.OPEN_ROUTER_KEY ?? process.env.OPENROUTER_API_KEY);
204
+ const useOpenRouter = config.openRouter?.enabled !== undefined ? config.openRouter?.enabled : process.env.USE_OPENROUTER;
205
+ if (openRouterKey && useOpenRouter !== false && useOpenRouter !== 'false') {
206
+ routerConfig.openRouter = { enabled: true };
207
+ routerConfig.openrouter = { apiKey: openRouterKey };
208
+ routerConfig.defaultMode = 'openrouter';
209
+ }
210
+ const router = new LLMProviderRouter(routerConfig);
211
+ // Set up BETWEEN-CALLS rate limiting as a request interceptor (applies to all provider calls)
212
+ // This ensures rate limiting works even when router is used directly without gateway
213
+ // Hidden in the flow - automatic and transparent
214
+ //
215
+ // NOTE: This is for BETWEEN-CALLS rate limiting (smart, tracks last call time).
216
+ // Retry delays are handled separately in gateway-retry.ts (simple sleep, not smart).
217
+ const rateLimitConfig = config.rateLimit;
218
+ // Get defaults from JSON config, fallback to constants
219
+ const jsonRateLimitConfig = defaultModelConfig.rateLimit || {};
220
+ const rateLimitEnabled = rateLimitConfig?.enabled ?? jsonRateLimitConfig.enabled ?? DEFAULT_RATE_LIMIT_ENABLED;
221
+ if (rateLimitEnabled) {
222
+ // Priority: explicit config > JSON defaults > constants
223
+ const defaultMinIntervalMs = rateLimitConfig?.defaultMinIntervalMs
224
+ ?? jsonRateLimitConfig.defaultMinIntervalMs
225
+ ?? DEFAULT_RATE_LIMIT_MIN_INTERVAL_MS;
226
+ const providerIntervals = rateLimitConfig?.providerIntervals;
227
+ const rateLimiter = new GatewayRateLimiter(defaultMinIntervalMs, providerIntervals, logger);
228
+ // Add request interceptor for BETWEEN-CALLS rate limiting (hidden in the flow)
229
+ router.addRequestInterceptor(async (request, provider) => {
230
+ // Get provider name
231
+ const providerName = typeof provider?.getProviderName === 'function'
232
+ ? provider.getProviderName()
233
+ : 'global';
234
+ // Smart rate limiting: wait only if necessary based on last call time
235
+ // This is for BETWEEN-CALLS, not retries (retries use simple sleep in gateway-retry.ts)
236
+ await rateLimiter.waitIfNeeded(providerName);
237
+ // Return request unchanged (interceptor can modify request, but we just need to wait)
238
+ return request;
239
+ });
240
+ // Add response interceptor to record call completion
241
+ // Note: Type assertion needed due to ResponseInterceptor type definition mismatch
242
+ router.addResponseInterceptor((async (response, request, provider) => {
243
+ // Get provider name
244
+ const providerName = typeof provider?.getProviderName === 'function'
245
+ ? provider.getProviderName()
246
+ : 'global';
247
+ // Record the call time after completion (for smart between-calls rate limiting)
248
+ rateLimiter.recordCall(providerName);
249
+ // Return response unchanged
250
+ return response;
251
+ }));
252
+ logger.debug('Between-calls rate limiting configured as router interceptor', {
253
+ defaultMinIntervalMs,
254
+ providerIntervals: providerIntervals ? Object.keys(providerIntervals).length : 0,
255
+ enabled: true,
256
+ note: 'Smart rate limiting (between-calls only). Retry delays handled separately (simple sleep).'
257
+ });
258
+ }
259
+ else {
260
+ logger.debug('Rate limiting disabled');
261
+ }
262
+ // Initialize usage tracking
263
+ const usageTracker = new UsageTracker({
264
+ enableUsageTracking: config.enableUsageTracking ?? true,
265
+ usageTier: config.usageTier,
266
+ logger
267
+ });
268
+ // Initialize activity tracking
269
+ const activityManager = new ActivityManager({
270
+ enableActivityTracking: config.enableActivityTracking ?? true,
271
+ customTracker: config.activityTracker,
272
+ logger
273
+ });
274
+ const templateRendering = mergeTemplateRenderOptions(defaultTemplateRendering, config.templateRendering);
275
+ const instructionsBlockOverrides = {
276
+ ...(config.instructionsBlocks ?? {})
277
+ };
278
+ // Initialize message builder config - for direct message construction
279
+ const messageBuilderConfig = {
280
+ defaultInstructionsBlocks,
281
+ instructionsBlockOverrides,
282
+ logger,
283
+ templateRendering
284
+ };
285
+ return {
286
+ logger,
287
+ router,
288
+ activityManager,
289
+ usageTracker,
290
+ messageBuilderConfig
291
+ };
292
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Gateway Conversion Module
3
+ * Handles structured text conversion and two-step conversion
4
+ *
5
+ * Text mode now uses flex-md format exclusively.
6
+ * Two-step conversion parses flex-md directly to JSON without additional LLM call.
7
+ */
8
+ import type { AIRequest, EnhancedLLMResponse, GatewayConfig } from './types.js';
9
+ import type { Logxer } from '@x12i/logxer';
10
+ import type { AIGateway } from './gateway.js';
11
+ /**
12
+ * Invoke structured text mode (first step of two-step conversion)
13
+ */
14
+ export declare function invokeStructuredText(request: AIRequest, gateway: AIGateway): Promise<EnhancedLLMResponse<string>>;
15
+ /**
16
+ * Get default conversion instructions for two-step mode
17
+ *
18
+ * NOTE: This function is no longer used in two-step conversion.
19
+ * Two-step mode now parses flex-md directly using the flex-md package.
20
+ * This function is kept for backward compatibility only.
21
+ */
22
+ export declare function getDefaultConversionInstructions(): string;
23
+ /**
24
+ * Convert flex-md structured text to JSON (second step of two-step conversion)
25
+ *
26
+ * This function now parses flex-md directly using the flex-md package,
27
+ * eliminating the need for an additional LLM call.
28
+ */
29
+ export declare function convertStructuredToJson(structuredText: string, originalRequest: AIRequest, config: GatewayConfig, gateway: AIGateway, logger: Logxer): Promise<EnhancedLLMResponse>;
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Gateway Conversion Module
3
+ * Handles structured text conversion and two-step conversion
4
+ *
5
+ * Text mode now uses flex-md format exclusively.
6
+ * Two-step conversion parses flex-md directly to JSON without additional LLM call.
7
+ */
8
+ import { extractJsonFromFlexMd } from './flex-md-loader.js';
9
+ /**
10
+ * Get the current directory - works in both ESM and CJS
11
+ * In CJS: uses __dirname
12
+ * In ESM: uses import.meta.url (accessed via eval to avoid TS errors in CJS build)
13
+ */
14
+ async function getCurrentDir() {
15
+ if (typeof __dirname !== 'undefined') {
16
+ return __dirname;
17
+ }
18
+ // ESM: use import.meta.url
19
+ // Use Function constructor to access import.meta without TypeScript errors
20
+ try {
21
+ const getMetaUrl = new Function('return import.meta.url');
22
+ const metaUrl = getMetaUrl();
23
+ if (metaUrl) {
24
+ const url = await import('url');
25
+ const path = await import('path');
26
+ return path.dirname(url.fileURLToPath(metaUrl));
27
+ }
28
+ }
29
+ catch {
30
+ // Fall through to process.cwd()
31
+ }
32
+ // Fallback
33
+ return process.cwd();
34
+ }
35
+ /**
36
+ * Invoke structured text mode (first step of two-step conversion)
37
+ */
38
+ export async function invokeStructuredText(request, gateway) {
39
+ // Create a new request with structured-text mode
40
+ const structuredRequest = {
41
+ ...request,
42
+ jobId: `${request.jobId}-step1`
43
+ };
44
+ // Make the call (this will recursively call invoke, but with structured-text mode)
45
+ return await gateway.invoke(structuredRequest);
46
+ }
47
+ /**
48
+ * Get default conversion instructions for two-step mode
49
+ *
50
+ * NOTE: This function is no longer used in two-step conversion.
51
+ * Two-step mode now parses flex-md directly using the flex-md package.
52
+ * This function is kept for backward compatibility only.
53
+ */
54
+ export function getDefaultConversionInstructions() {
55
+ return `You are a data converter. Your task is to convert structured text into valid JSON.
56
+
57
+ You will receive structured text output from an AI assistant. Convert it into a valid JSON object that captures all the information from the structured text.
58
+
59
+ Your response must be ONLY a valid JSON object with no other text. The JSON should preserve all information from the structured text in a structured format.
60
+
61
+ CRITICAL RULES:
62
+ - Your ENTIRE response must be parseable JSON
63
+ - Do NOT write conversational text outside the JSON
64
+ - Response must START with { and END with }
65
+ - Preserve all information from the structured text
66
+ - Use appropriate JSON data types (strings, numbers, arrays, objects)
67
+ - If the structured text contains a story or narrative, structure it appropriately in JSON
68
+ - Extract key elements and organize them logically`;
69
+ }
70
+ /**
71
+ * Convert flex-md structured text to JSON (second step of two-step conversion)
72
+ *
73
+ * This function now parses flex-md directly using the flex-md package,
74
+ * eliminating the need for an additional LLM call.
75
+ */
76
+ export async function convertStructuredToJson(structuredText, originalRequest, config, gateway, logger) {
77
+ logger.debug('Two-step conversion: Parsing flex-md text directly using flex-md SDK 3.0.0', {
78
+ aiRequestId: originalRequest.aiRequestId,
79
+ structuredTextLength: structuredText.length,
80
+ usingFlexMd: true
81
+ });
82
+ try {
83
+ // Use flex-md 3.0.0 to extract and parse JSON
84
+ const flexMdResult = await extractJsonFromFlexMd(structuredText);
85
+ if (!flexMdResult || !flexMdResult.json) {
86
+ throw new Error('flex-md SDK could not extract JSON from structured text');
87
+ }
88
+ const parsedJson = flexMdResult.json;
89
+ // Create response structure matching EnhancedLLMResponse
90
+ const conversionResponse = {
91
+ content: structuredText, // Keep original flex-md text
92
+ rawText: structuredText,
93
+ parsedContent: parsedJson, // Parsed JSON from flex-md
94
+ requestId: originalRequest.aiRequestId,
95
+ provider: 'conversion',
96
+ rawResponse: structuredText,
97
+ metadata: {
98
+ aiRequestId: originalRequest.aiRequestId,
99
+ agentId: originalRequest.agentId,
100
+ latencyMs: 0, // No LLM call, so no latency
101
+ tokens: {
102
+ prompt: 0,
103
+ completion: 0,
104
+ total: 0
105
+ },
106
+ model: 'flex-md-parser', // Indicate we used flex-md parser
107
+ provider: 'flex-md',
108
+ isTwoStepConversion: true,
109
+ structuredTextStep: 'second',
110
+ contentType: 'object', // Parsed JSON is an object
111
+ conversionMethod: 'flex-md-direct-parse'
112
+ }
113
+ };
114
+ logger.info('Two-step conversion completed using flex-md parser', {
115
+ aiRequestId: originalRequest.aiRequestId,
116
+ parsedContentType: typeof parsedJson,
117
+ isObject: typeof parsedJson === 'object' && parsedJson !== null
118
+ });
119
+ return conversionResponse;
120
+ }
121
+ catch (parseError) {
122
+ const error = parseError instanceof Error ? parseError : new Error(String(parseError));
123
+ logger.error('Two-step conversion failed: flex-md parse error', {
124
+ jobId: originalRequest.jobId,
125
+ error: error.message,
126
+ errorName: error.name,
127
+ structuredTextPreview: structuredText.substring(0, 200)
128
+ });
129
+ // Create error report for flex-md parsing issue
130
+ let flexMdVersion = 'unknown';
131
+ try {
132
+ // Use type assertion to allow dynamic import of package.json
133
+ const flexMdPkg = await import('flex-md/package.json');
134
+ flexMdVersion = flexMdPkg?.version || flexMdPkg?.default?.version || 'unknown';
135
+ }
136
+ catch {
137
+ // Package.json not accessible
138
+ }
139
+ const report = {
140
+ package: 'flex-md',
141
+ version: flexMdVersion,
142
+ issue: 'parse function threw error',
143
+ error: error.message,
144
+ errorName: error.name,
145
+ stack: error.stack,
146
+ timestamp: new Date().toISOString(),
147
+ request: {
148
+ jobId: originalRequest.jobId,
149
+ agentId: originalRequest.agentId
150
+ },
151
+ inputPreview: structuredText.substring(0, 500),
152
+ inputLength: structuredText.length,
153
+ environment: {
154
+ nodeVersion: process.version,
155
+ platform: process.platform
156
+ }
157
+ };
158
+ // Write report to docs directory
159
+ try {
160
+ const fs = await import('fs');
161
+ const path = await import('path');
162
+ const currentDir = await getCurrentDir();
163
+ const reportPath = path.join(currentDir, '../../docs/FLEX_MD_PACKAGE_ISSUE_REPORT.json');
164
+ fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
165
+ logger.error('flex-md parsing error report written', { reportPath });
166
+ }
167
+ catch (writeError) {
168
+ logger.error('Failed to write flex-md parsing error report', {
169
+ error: writeError instanceof Error ? writeError.message : String(writeError)
170
+ });
171
+ }
172
+ throw new Error(`flex-md parsing failed: ${error.message}`);
173
+ }
174
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Gateway Instructions Module
3
+ * Handles instructions block resolution from inline config only (no content registry).
4
+ */
5
+ import type { Logxer } from '@x12i/logxer';
6
+ export interface InstructionsContext {
7
+ defaultInstructionsBlocks: Record<string, any>;
8
+ /** Flat overrides from gateway `instructionsBlocks` (merged at init). */
9
+ instructionsBlockOverrides: Record<string, string>;
10
+ /** Per-request flat overrides from `request.config.instructionsBlocks`. */
11
+ requestInstructionsBlocks?: Record<string, string>;
12
+ config: {
13
+ instructionsBlocks?: Record<string, any>;
14
+ };
15
+ logger: Logxer;
16
+ }
17
+ /**
18
+ * Resolves nested instructionsBlocks (e.g., "input.inputRecognitionRule")
19
+ * Supports dot notation for nested object access in defaultInstructionsBlocks,
20
+ * then flat keys in merged overrides.
21
+ */
22
+ export declare function resolveNestedInstructionsBlock(blockPath: string, _agentId: string, _taskTypeId: string | undefined, context: InstructionsContext): Promise<string>;
23
+ /**
24
+ * Resolves instructionsBlocks from config overrides, request overrides, nested defaults, or flat defaults.
25
+ */
26
+ export declare function resolveInstructionsBlock(blockName: string, agentId: string, taskTypeId: string | undefined, context: InstructionsContext): Promise<string>;
27
+ /**
28
+ * Pre-parse instructions string for stable hashing (taskTypeId). No external resolution.
29
+ */
30
+ export declare function getPreParsedInstructions(instructions: string | undefined): string;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Gateway Instructions Module
3
+ * Handles instructions block resolution from inline config only (no content registry).
4
+ */
5
+ import { InstructionNotFoundError } from './instruction-errors.js';
6
+ function getNestedString(blocks, dotPath) {
7
+ const parts = dotPath.split('.');
8
+ let cur = blocks;
9
+ for (const p of parts) {
10
+ if (cur == null || typeof cur !== 'object')
11
+ return undefined;
12
+ cur = cur[p];
13
+ }
14
+ return typeof cur === 'string' ? cur : undefined;
15
+ }
16
+ /**
17
+ * Resolves nested instructionsBlocks (e.g., "input.inputRecognitionRule")
18
+ * Supports dot notation for nested object access in defaultInstructionsBlocks,
19
+ * then flat keys in merged overrides.
20
+ */
21
+ export async function resolveNestedInstructionsBlock(blockPath, _agentId, _taskTypeId, context) {
22
+ return resolveInstructionsBlock(blockPath, _agentId, _taskTypeId, context);
23
+ }
24
+ /**
25
+ * Resolves instructionsBlocks from config overrides, request overrides, nested defaults, or flat defaults.
26
+ */
27
+ export async function resolveInstructionsBlock(blockName, agentId, taskTypeId, context) {
28
+ const { config, logger, defaultInstructionsBlocks, instructionsBlockOverrides, requestInstructionsBlocks } = context;
29
+ logger.verbose('Resolving instructionsBlock', {
30
+ blockName,
31
+ agentId,
32
+ taskTypeId,
33
+ hasConfigOverride: !!config.instructionsBlocks?.[blockName]
34
+ });
35
+ const configOverride = config.instructionsBlocks?.[blockName];
36
+ if (typeof configOverride === 'string' && configOverride.trim()) {
37
+ logger.debug('Using per-call config override for instructionsBlock', { blockName, agentId });
38
+ return configOverride;
39
+ }
40
+ const fromRequest = requestInstructionsBlocks?.[blockName];
41
+ if (typeof fromRequest === 'string' && fromRequest.trim()) {
42
+ logger.debug('Using request.config.instructionsBlocks for instructionsBlock', { blockName, agentId });
43
+ return fromRequest;
44
+ }
45
+ const fromGateway = instructionsBlockOverrides[blockName];
46
+ if (typeof fromGateway === 'string' && fromGateway.trim()) {
47
+ logger.debug('Using gateway instructionsBlocks override for instructionsBlock', { blockName, agentId });
48
+ return fromGateway;
49
+ }
50
+ const nested = getNestedString(defaultInstructionsBlocks, blockName);
51
+ if (nested?.trim()) {
52
+ logger.debug('Using nested defaultInstructionsBlocks for instructionsBlock', { blockName, agentId });
53
+ return nested;
54
+ }
55
+ throw new InstructionNotFoundError(blockName, 'instructions-blocks', `InstructionsBlock "${blockName}" not found. Provide it via packaged defaults, gateway instructionsBlocks, or request.config.instructionsBlocks.`, blockName);
56
+ }
57
+ /**
58
+ * Pre-parse instructions string for stable hashing (taskTypeId). No external resolution.
59
+ */
60
+ export function getPreParsedInstructions(instructions) {
61
+ return instructions ?? '';
62
+ }