@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,11 @@
1
+ /**
2
+ * Centralized activity tracking configuration.
3
+ * Single source of truth for package-level collection names.
4
+ */
5
+ export interface ActivityTrackingConfig {
6
+ mongoUri: string;
7
+ databaseName: string;
8
+ collectionName: string;
9
+ badRequestsCollectionName: string;
10
+ }
11
+ export declare function resolveActivityTrackingConfig(): ActivityTrackingConfig;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Centralized activity tracking configuration.
3
+ * Single source of truth for package-level collection names.
4
+ */
5
+ const ACTIVITY_COLLECTION_NAME = 'ai-activities';
6
+ const BAD_REQUESTS_COLLECTION_NAME = 'bad-requests';
7
+ export function resolveActivityTrackingConfig() {
8
+ // Collection names are intentionally hardcoded at package level.
9
+ return {
10
+ mongoUri: '',
11
+ databaseName: '',
12
+ collectionName: ACTIVITY_COLLECTION_NAME,
13
+ badRequestsCollectionName: BAD_REQUESTS_COLLECTION_NAME
14
+ };
15
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "contentRegistry": {
3
+ "mode": "dev",
4
+ "localRoot": ".metadata",
5
+ "github": {
6
+ "token": null,
7
+ "repo": null
8
+ },
9
+ "s3": {
10
+ "bucket": null,
11
+ "region": null
12
+ },
13
+ "redis": {
14
+ "host": null,
15
+ "port": null
16
+ },
17
+ "cache": {
18
+ "ttl": 3600000
19
+ }
20
+ },
21
+ "logging": {
22
+ "level": "info",
23
+ "enabled": true
24
+ },
25
+ "templateRendering": {
26
+ "subPathSearch": {
27
+ "enabled": false,
28
+ "roots": ["execution", "input", "inputs"]
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Content Normalizer
3
+ *
4
+ * Extracts and normalizes content from LLM provider responses.
5
+ * Fixes the "[object Object]" bug by properly handling objects.
6
+ * Preserves both rawText and parsedContent for downstream consumers.
7
+ */
8
+ import type { LLMResponse } from '../types.js';
9
+ import type { NormalizedContent } from './types.js';
10
+ /**
11
+ * Normalizes content from router response
12
+ *
13
+ * This function:
14
+ * 1. Extracts content from multiple possible locations
15
+ * 2. Preserves rawText if available
16
+ * 3. Attempts to parse flex-md content to JSON
17
+ * 4. Properly stringifies objects (fixes "[object Object]" bug)
18
+ * 5. Determines content type
19
+ *
20
+ * @param response - Router response object
21
+ * @returns Normalized content with metadata
22
+ */
23
+ export declare function normalizeContent(response: LLMResponse): NormalizedContent;
24
+ /**
25
+ * Checks if content is empty
26
+ *
27
+ * @param normalized - Normalized content result
28
+ * @returns True if content is empty
29
+ */
30
+ export declare function isEmptyContent(normalized: NormalizedContent): boolean;
31
+ /**
32
+ * Gets diagnostic information about response structure
33
+ * Useful for debugging when content extraction fails
34
+ *
35
+ * @param response - Router response object
36
+ * @returns Diagnostic information
37
+ */
38
+ export declare function getResponseDiagnostics(response: any): {
39
+ responseKeys: string[];
40
+ hasMessage: boolean;
41
+ hasChoices: boolean;
42
+ hasOutput: boolean;
43
+ hasRawText: boolean;
44
+ originalContent: any;
45
+ originalContentType: string;
46
+ };
@@ -0,0 +1,393 @@
1
+ /**
2
+ * Content Normalizer
3
+ *
4
+ * Extracts and normalizes content from LLM provider responses.
5
+ * Fixes the "[object Object]" bug by properly handling objects.
6
+ * Preserves both rawText and parsedContent for downstream consumers.
7
+ */
8
+ /**
9
+ * Extracts content from router response, checking multiple possible locations
10
+ *
11
+ * @param response - Router response object
12
+ * @returns Extracted content value (any type) or null
13
+ */
14
+ function extractContentValue(response) {
15
+ // Check multiple possible locations
16
+ // CRITICAL: Check outputText FIRST (router's standard field) before other fields
17
+ return response.outputText ?? // Router's standard output field (check first)
18
+ response.content ??
19
+ response.rawText ??
20
+ response.output ??
21
+ response.choices?.[0]?.message?.content ??
22
+ response.choices?.[0]?.text ??
23
+ response.message?.content ??
24
+ response.text ??
25
+ response.result ??
26
+ null;
27
+ }
28
+ /**
29
+ * Extracts rawText from router response
30
+ *
31
+ * @param response - Router response object
32
+ * @param contentValue - The extracted content value (to avoid re-extraction)
33
+ * @returns Raw text string or undefined
34
+ */
35
+ function extractRawText(response, contentValue) {
36
+ // Try to get rawText from router response (preferred - original from provider)
37
+ // Check multiple possible locations where router might store rawText
38
+ const rawText = response.rawText || // Direct rawText field (preferred)
39
+ response.outputText || // Router's standard output field (if rawText not available)
40
+ response.raw || // Some providers use 'raw'
41
+ response.rawContent || // Some providers use 'rawContent'
42
+ response.originalText || // Some providers use 'originalText'
43
+ response.originalContent || // Some providers use 'originalContent'
44
+ response.choices?.[0]?.message?.rawText || // OpenAI-style nested rawText
45
+ response.choices?.[0]?.rawText || // OpenAI-style direct rawText
46
+ response.message?.rawText || // Message-level rawText
47
+ response.output?.rawText; // Output-level rawText
48
+ if (typeof rawText === 'string' && rawText.trim().length > 0) {
49
+ return rawText;
50
+ }
51
+ // If no rawText found, but content is a string, use it as rawText
52
+ // This ensures we always have something to work with
53
+ // If content is already a string, check if it's a JSON string with wrapped prose
54
+ if (typeof contentValue === 'string' && contentValue.trim().length > 0) {
55
+ // Check if it's a JSON string that might contain wrapped prose
56
+ const trimmed = contentValue.trim();
57
+ if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
58
+ (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
59
+ try {
60
+ const parsed = JSON.parse(trimmed);
61
+ // If it's a wrapped prose object (has _wrapped and text fields), extract the text
62
+ if (parsed && typeof parsed === 'object' && parsed._wrapped === true && typeof parsed.text === 'string') {
63
+ return parsed.text;
64
+ }
65
+ // Otherwise, return the original string (it's a valid JSON string)
66
+ return contentValue;
67
+ }
68
+ catch {
69
+ // Not valid JSON, return as-is
70
+ return contentValue;
71
+ }
72
+ }
73
+ // Not JSON, return as-is
74
+ return contentValue;
75
+ }
76
+ // If content is an object/array, check for wrapped prose first
77
+ if (contentValue != null && typeof contentValue === 'object') {
78
+ // Check if it's a wrapped prose object (has _wrapped and text fields)
79
+ if (contentValue._wrapped === true && typeof contentValue.text === 'string') {
80
+ return contentValue.text;
81
+ }
82
+ // Otherwise, stringify it to get the JSON string
83
+ // This handles the case where router doesn't preserve rawText but returns parsed object
84
+ try {
85
+ return JSON.stringify(contentValue);
86
+ }
87
+ catch {
88
+ // If stringify fails, return undefined
89
+ return undefined;
90
+ }
91
+ }
92
+ return undefined;
93
+ }
94
+ /**
95
+ * Attempts to parse content as JSON
96
+ * Always returns a structure - forces JSON structure even if parsing fails
97
+ *
98
+ * @param value - Value to parse
99
+ * @param forceStructure - If true, always return an object/array structure (default: true)
100
+ * @returns Parsed object/array or forced structure
101
+ */
102
+ function tryParseJSON(value, forceStructure = true) {
103
+ if (value == null) {
104
+ // Force structure: return empty object instead of undefined
105
+ return forceStructure ? {} : undefined;
106
+ }
107
+ // If already an object or array, return it
108
+ if (typeof value === 'object' && !Array.isArray(value)) {
109
+ return value;
110
+ }
111
+ if (Array.isArray(value)) {
112
+ return value;
113
+ }
114
+ // If it's a string, try to parse it
115
+ if (typeof value === 'string') {
116
+ const trimmed = value.trim();
117
+ // Try to parse as JSON
118
+ if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
119
+ (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
120
+ try {
121
+ return JSON.parse(trimmed);
122
+ }
123
+ catch {
124
+ // Not valid JSON - force structure if requested
125
+ if (forceStructure) {
126
+ // Wrap in object with the text as a value
127
+ return trimmed.startsWith('[') ? [] : { value: trimmed, _parseError: true };
128
+ }
129
+ return undefined;
130
+ }
131
+ }
132
+ // If forceStructure and it's not JSON-like, wrap it
133
+ if (forceStructure && trimmed.length > 0) {
134
+ return { text: trimmed, _wrapped: true };
135
+ }
136
+ }
137
+ // For other types (number, boolean, etc.), wrap in object if forceStructure
138
+ if (forceStructure && value != null) {
139
+ return { value, _wrapped: true };
140
+ }
141
+ return undefined;
142
+ }
143
+ /**
144
+ * Determines content type
145
+ *
146
+ * @param value - Content value
147
+ * @param parsedContent - Parsed content (if available)
148
+ * @returns Content type classification
149
+ */
150
+ function determineContentType(value, parsedContent) {
151
+ if (value == null || value === '') {
152
+ return 'null';
153
+ }
154
+ // Use parsedContent if available (more accurate)
155
+ if (parsedContent != null) {
156
+ if (Array.isArray(parsedContent)) {
157
+ return 'array';
158
+ }
159
+ if (typeof parsedContent === 'object') {
160
+ return 'object';
161
+ }
162
+ }
163
+ // Fall back to checking value directly
164
+ if (Array.isArray(value)) {
165
+ return 'array';
166
+ }
167
+ if (typeof value === 'object') {
168
+ return 'object';
169
+ }
170
+ return 'string';
171
+ }
172
+ /**
173
+ * Normalizes content from router response
174
+ *
175
+ * This function:
176
+ * 1. Extracts content from multiple possible locations
177
+ * 2. Preserves rawText if available
178
+ * 3. Attempts to parse flex-md content to JSON
179
+ * 4. Properly stringifies objects (fixes "[object Object]" bug)
180
+ * 5. Determines content type
181
+ *
182
+ * @param response - Router response object
183
+ * @returns Normalized content with metadata
184
+ */
185
+ export function normalizeContent(response) {
186
+ // Extract content value from response
187
+ const contentValue = extractContentValue(response);
188
+ // CRITICAL: Check if router already converted object to "[object Object]" string
189
+ // This happens when router uses String() on objects before returning
190
+ if (typeof contentValue === 'string' && contentValue === '[object Object]') {
191
+ // Router already broke it - try to recover from other fields
192
+ console.error('[content-normalizer] Router returned "[object Object]" string - attempting recovery', {
193
+ responseKeys: Object.keys(response),
194
+ hasOutput: !!response.output,
195
+ hasRawText: !!response.rawText,
196
+ hasChoices: !!response.choices
197
+ });
198
+ // Try to get the original object from other fields
199
+ const output = response.output;
200
+ const choicesContent = response.choices?.[0]?.message?.content;
201
+ const messageContent = response.message?.content;
202
+ // Prefer output (parsed object from provider)
203
+ if (output && typeof output === 'object' && output !== null) {
204
+ const recovered = JSON.stringify(output);
205
+ return {
206
+ content: recovered,
207
+ rawText: recovered,
208
+ parsedContent: output,
209
+ contentType: Array.isArray(output) ? 'array' : 'object'
210
+ };
211
+ }
212
+ // Try choices content (might be object)
213
+ if (choicesContent && typeof choicesContent === 'object' && choicesContent !== null) {
214
+ const recovered = JSON.stringify(choicesContent);
215
+ return {
216
+ content: recovered,
217
+ rawText: recovered,
218
+ parsedContent: choicesContent,
219
+ contentType: Array.isArray(choicesContent) ? 'array' : 'object'
220
+ };
221
+ }
222
+ // Try message content (might be object)
223
+ if (messageContent && typeof messageContent === 'object' && messageContent !== null) {
224
+ const recovered = JSON.stringify(messageContent);
225
+ return {
226
+ content: recovered,
227
+ rawText: recovered,
228
+ parsedContent: messageContent,
229
+ contentType: Array.isArray(messageContent) ? 'array' : 'object'
230
+ };
231
+ }
232
+ // If we can't recover, log error and return empty
233
+ console.error('[content-normalizer] Cannot recover from "[object Object]" - no valid object found in response');
234
+ return {
235
+ content: '',
236
+ rawText: undefined,
237
+ parsedContent: undefined,
238
+ contentType: 'null'
239
+ };
240
+ }
241
+ // Extract rawText (original text from provider)
242
+ // Pass contentValue to avoid re-extraction and handle object case
243
+ const rawText = extractRawText(response, contentValue);
244
+ // Always parse flex-md to JSON
245
+ let parsedContent;
246
+ let contentType;
247
+ // Try to parse flex-md content to JSON
248
+ parsedContent = tryParseJSON(contentValue, true);
249
+ contentType = determineContentType(contentValue, parsedContent);
250
+ // Ensure parsedContent matches contentType - force structure consistency
251
+ // JSON is always JSON (object/array), text is always text (string), etc.
252
+ let finalParsedContent = parsedContent;
253
+ if (contentType === 'object') {
254
+ // Force object structure - always return an object
255
+ if (!finalParsedContent || typeof finalParsedContent !== 'object' || Array.isArray(finalParsedContent)) {
256
+ if (typeof contentValue === 'string' && contentValue.trim().length > 0) {
257
+ // Wrap text in object
258
+ finalParsedContent = { text: contentValue, _wrapped: true };
259
+ }
260
+ else if (Array.isArray(contentValue)) {
261
+ // Wrap array in object
262
+ finalParsedContent = { array: contentValue, _wrapped: true };
263
+ }
264
+ else {
265
+ // Empty object
266
+ finalParsedContent = {};
267
+ }
268
+ }
269
+ }
270
+ else if (contentType === 'array') {
271
+ // Force array structure - always return an array
272
+ if (!Array.isArray(finalParsedContent)) {
273
+ if (finalParsedContent && typeof finalParsedContent === 'object') {
274
+ // Wrap object in array
275
+ finalParsedContent = [finalParsedContent];
276
+ }
277
+ else if (typeof contentValue === 'string' && contentValue.trim().length > 0) {
278
+ // Wrap text in array
279
+ finalParsedContent = [contentValue];
280
+ }
281
+ else {
282
+ // Empty array
283
+ finalParsedContent = [];
284
+ }
285
+ }
286
+ }
287
+ else if (contentType === 'string') {
288
+ // For string type, parsedContent can be undefined (it's just text)
289
+ // But if we have an object, we should keep it for structure preservation
290
+ // Don't force anything - string content doesn't need parsedContent
291
+ finalParsedContent = parsedContent; // Keep as-is (may be undefined, which is fine for strings)
292
+ }
293
+ else {
294
+ // null type - no parsedContent
295
+ finalParsedContent = undefined;
296
+ }
297
+ // Normalize content to string (for backward compatibility)
298
+ // CRITICAL: This must NEVER return "[object Object]" - always stringify objects properly
299
+ let normalizedString;
300
+ if (contentValue == null || contentValue === '') {
301
+ normalizedString = '';
302
+ }
303
+ else if (typeof contentValue === 'string') {
304
+ normalizedString = contentValue;
305
+ }
306
+ else if (typeof contentValue === 'object') {
307
+ // Fix "[object Object]" bug: properly stringify objects
308
+ // This is the critical fix - never use String() on objects
309
+ try {
310
+ normalizedString = JSON.stringify(contentValue);
311
+ }
312
+ catch (error) {
313
+ // If JSON.stringify fails (circular reference, etc.), try to recover
314
+ // Use rawText if available, otherwise fallback (but log error)
315
+ if (rawText && typeof rawText === 'string') {
316
+ normalizedString = rawText;
317
+ }
318
+ else {
319
+ // Last resort: use String() but this should never happen
320
+ normalizedString = String(contentValue);
321
+ // This will be "[object Object]" but we've exhausted all options
322
+ console.error('[content-normalizer] Failed to stringify object and no rawText available', {
323
+ error,
324
+ contentValueType: typeof contentValue,
325
+ isArray: Array.isArray(contentValue)
326
+ });
327
+ }
328
+ }
329
+ }
330
+ else {
331
+ normalizedString = String(contentValue);
332
+ }
333
+ // Final safety check: if normalizedString is "[object Object]", try to recover
334
+ if (normalizedString === '[object Object]') {
335
+ // This should never happen, but if it does, try to recover
336
+ if (rawText && typeof rawText === 'string' && rawText !== '[object Object]') {
337
+ normalizedString = rawText;
338
+ }
339
+ else if (parsedContent) {
340
+ try {
341
+ normalizedString = JSON.stringify(parsedContent);
342
+ }
343
+ catch {
344
+ // If this also fails, we're stuck with "[object Object]"
345
+ console.error('[content-normalizer] Cannot recover from "[object Object]" - all recovery attempts failed');
346
+ }
347
+ }
348
+ }
349
+ // CRITICAL: Ensure rawText is always set
350
+ // Priority: extracted rawText > normalizedString > contentValue (if string) > empty string
351
+ let finalRawText = rawText;
352
+ if (!finalRawText || typeof finalRawText !== 'string' || finalRawText.trim().length === 0) {
353
+ // Fallback to normalizedString (which is always a string)
354
+ finalRawText = normalizedString;
355
+ }
356
+ // If we still don't have rawText and contentValue is a string, use it
357
+ if ((!finalRawText || finalRawText.trim().length === 0) && typeof contentValue === 'string' && contentValue.trim().length > 0) {
358
+ finalRawText = contentValue;
359
+ }
360
+ return {
361
+ content: normalizedString,
362
+ rawText: finalRawText, // Always set (never undefined)
363
+ parsedContent: finalParsedContent, // Use final parsed content with forced structure
364
+ contentType
365
+ };
366
+ }
367
+ /**
368
+ * Checks if content is empty
369
+ *
370
+ * @param normalized - Normalized content result
371
+ * @returns True if content is empty
372
+ */
373
+ export function isEmptyContent(normalized) {
374
+ return !normalized.content || normalized.content.trim().length === 0;
375
+ }
376
+ /**
377
+ * Gets diagnostic information about response structure
378
+ * Useful for debugging when content extraction fails
379
+ *
380
+ * @param response - Router response object
381
+ * @returns Diagnostic information
382
+ */
383
+ export function getResponseDiagnostics(response) {
384
+ return {
385
+ responseKeys: Object.keys(response),
386
+ hasMessage: !!(response?.message),
387
+ hasChoices: !!(response?.choices),
388
+ hasOutput: !!(response?.output),
389
+ hasRawText: !!(response?.rawText),
390
+ originalContent: response?.content,
391
+ originalContentType: typeof response?.content
392
+ };
393
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Content Normalizer Module
3
+ *
4
+ * Exports all public APIs for content normalization
5
+ */
6
+ export { normalizeContent, isEmptyContent, getResponseDiagnostics } from './content-normalizer.js';
7
+ export type { NormalizedContent, ContentType } from './types.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Content Normalizer Module
3
+ *
4
+ * Exports all public APIs for content normalization
5
+ */
6
+ export { normalizeContent, isEmptyContent, getResponseDiagnostics } from './content-normalizer.js';
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Type definitions for Content Normalizer
3
+ */
4
+ /**
5
+ * Content type classification
6
+ */
7
+ export type ContentType = 'string' | 'object' | 'array' | 'null';
8
+ /**
9
+ * Normalized content result
10
+ */
11
+ export interface NormalizedContent {
12
+ /**
13
+ * Normalized content string (always present, for backward compatibility)
14
+ * - If original was string: returns the string
15
+ * - If original was object/array: returns JSON.stringify()
16
+ * - If original was null: returns empty string
17
+ */
18
+ content: string;
19
+ /**
20
+ * Original raw text from provider (before any parsing)
21
+ * Available if router preserved rawText
22
+ */
23
+ rawText?: string;
24
+ /**
25
+ * Parsed content (object or array) if content was JSON
26
+ * Available if content was successfully parsed
27
+ */
28
+ parsedContent?: any;
29
+ /**
30
+ * Content type classification
31
+ */
32
+ contentType: ContentType;
33
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Type definitions for Content Normalizer
3
+ */
4
+ export {};
@@ -0,0 +1,61 @@
1
+ {
2
+ "system-context": "You are performing the following task: {{taskDescription}}. Follow the instructions provided and respond according to the specified output format. Never ask follow-up questions.",
3
+
4
+ "input": {
5
+ "inputLabel": "INPUT:",
6
+ "inputPrefix": "INPUT DATA (process this now):",
7
+ "defaultPromptTemplate": "{{input}}",
8
+ "inputRecognitionRule": "The user message below is the complete input to process. It has already been provided. Do not ask for more input.",
9
+ "emptyInputHandling": "If input appears empty or minimal (e.g., 'No emails here', 'none', 'empty'), this IS valid input. Process it and return the success schema with empty arrays/values where appropriate.",
10
+ "testInputHandling": "Even if input contains words like 'test', 'invalid', 'placeholder', or 'example', treat it as real input. Reply in Markdown. Return your entire answer inside a single ```markdown fenced block.",
11
+ "inputLocationClarifier": "The user's message below is your input data. Please process it now. Reply in Markdown. Return your entire answer inside a single ```markdown fenced block."
12
+ },
13
+
14
+ "errorSchemas": {
15
+ "instructions-error": {
16
+ "type": "object",
17
+ "additionalProperties": false,
18
+ "properties": {
19
+ "error": {
20
+ "type": "string",
21
+ "description": "What is wrong with the instructions that prevents completion."
22
+ },
23
+ "errorType": {
24
+ "type": "string",
25
+ "enum": ["bad-instructions", "missing-context", "bad-context"]
26
+ },
27
+ "suggestion": {
28
+ "type": "string",
29
+ "description": "How to fix the instruction issue."
30
+ }
31
+ },
32
+ "required": ["error", "errorType"]
33
+ },
34
+ "prompt-error": {
35
+ "type": "object",
36
+ "additionalProperties": false,
37
+ "properties": {
38
+ "error": {
39
+ "type": "string",
40
+ "description": "What is wrong with the input that prevents completion."
41
+ },
42
+ "errorType": {
43
+ "type": "string",
44
+ "enum": ["missing-input", "bad-input"]
45
+ },
46
+ "suggestion": {
47
+ "type": "string",
48
+ "description": "How to fix the input issue."
49
+ }
50
+ },
51
+ "required": ["error", "errorType"]
52
+ }
53
+ },
54
+
55
+ "reinforcement": {
56
+ "emptyIsSuccess": "Empty results (e.g., items: []) are SUCCESS cases, not errors.",
57
+ "inputAlreadyProvided": "The input has been provided in the user message. Process it immediately.",
58
+ "noConversation": "You are not having a conversation. Reply in Markdown. Return your entire answer inside a single ```markdown fenced block.",
59
+ "failureIndicators": "If you write prose text outside the fenced block, you have FAILED. If you ask a question, you have FAILED. If you explain anything outside the ```markdown fenced block, you have FAILED."
60
+ }
61
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "defaultEngine": "openai",
3
+ "defaultModel": "gpt-5-nano",
4
+ "temperature": 0.7,
5
+ "topP": 1.0,
6
+ "frequencyPenalty": 0.0,
7
+ "presencePenalty": 0.0,
8
+ "rateLimit": {
9
+ "defaultMinIntervalMs": 500,
10
+ "enabled": true
11
+ },
12
+ "retry": {
13
+ "throttlingDelay": 5000
14
+ }
15
+ }
16
+
@@ -0,0 +1,6 @@
1
+ {
2
+ "subPathSearch": {
3
+ "enabled": false,
4
+ "roots": ["execution", "input", "inputs"]
5
+ }
6
+ }