@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.
- package/README.md +4259 -0
- package/config.defaults.json +31 -0
- package/dist/activity-manager.d.ts +206 -0
- package/dist/activity-manager.js +1051 -0
- package/dist/config/activity-tracking-config.d.ts +11 -0
- package/dist/config/activity-tracking-config.js +15 -0
- package/dist/config.defaults.json +31 -0
- package/dist/content-normalizer/content-normalizer.d.ts +46 -0
- package/dist/content-normalizer/content-normalizer.js +393 -0
- package/dist/content-normalizer/index.d.ts +7 -0
- package/dist/content-normalizer/index.js +6 -0
- package/dist/content-normalizer/types.d.ts +33 -0
- package/dist/content-normalizer/types.js +4 -0
- package/dist/defaults/instructions-blocks.json +61 -0
- package/dist/defaults/model-config.json +16 -0
- package/dist/defaults/template-rendering.json +6 -0
- package/dist/flex-md-loader.d.ts +109 -0
- package/dist/flex-md-loader.js +940 -0
- package/dist/gateway-config.d.ts +49 -0
- package/dist/gateway-config.js +292 -0
- package/dist/gateway-conversion.d.ts +29 -0
- package/dist/gateway-conversion.js +174 -0
- package/dist/gateway-instructions.d.ts +30 -0
- package/dist/gateway-instructions.js +62 -0
- package/dist/gateway-memory.d.ts +51 -0
- package/dist/gateway-memory.js +207 -0
- package/dist/gateway-messages.d.ts +23 -0
- package/dist/gateway-messages.js +83 -0
- package/dist/gateway-meta.d.ts +25 -0
- package/dist/gateway-meta.js +87 -0
- package/dist/gateway-provider-auto-register.d.ts +17 -0
- package/dist/gateway-provider-auto-register.js +159 -0
- package/dist/gateway-provider.d.ts +54 -0
- package/dist/gateway-provider.js +202 -0
- package/dist/gateway-rate-limiter-constants.d.ts +16 -0
- package/dist/gateway-rate-limiter-constants.js +16 -0
- package/dist/gateway-rate-limiter.d.ts +56 -0
- package/dist/gateway-rate-limiter.js +107 -0
- package/dist/gateway-retry.d.ts +49 -0
- package/dist/gateway-retry.js +204 -0
- package/dist/gateway-utils.d.ts +21 -0
- package/dist/gateway-utils.js +181 -0
- package/dist/gateway-validation.d.ts +13 -0
- package/dist/gateway-validation.js +50 -0
- package/dist/gateway.d.ts +39 -0
- package/dist/gateway.js +430 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +55 -0
- package/dist/instruction-errors.d.ts +16 -0
- package/dist/instruction-errors.js +29 -0
- package/dist/instruction-optimizer.d.ts +113 -0
- package/dist/instruction-optimizer.js +293 -0
- package/dist/instructions-parser.d.ts +31 -0
- package/dist/instructions-parser.js +56 -0
- package/dist/logger-factory.d.ts +17 -0
- package/dist/logger-factory.js +42 -0
- package/dist/message-builder.d.ts +41 -0
- package/dist/message-builder.js +522 -0
- package/dist/object-types-library-integration.d.ts +22 -0
- package/dist/object-types-library-integration.js +27 -0
- package/dist/object-types-library.d.ts +351 -0
- package/dist/object-types-library.js +210 -0
- package/dist/output-auditor.d.ts +44 -0
- package/dist/output-auditor.js +49 -0
- package/dist/request-report-generator.d.ts +60 -0
- package/dist/request-report-generator.js +169 -0
- package/dist/response-analyzer/format-type-detector.d.ts +35 -0
- package/dist/response-analyzer/format-type-detector.js +115 -0
- package/dist/response-analyzer/index.d.ts +9 -0
- package/dist/response-analyzer/index.js +8 -0
- package/dist/response-analyzer/object-type-detector.d.ts +42 -0
- package/dist/response-analyzer/object-type-detector.js +95 -0
- package/dist/response-analyzer/response-analyzer.d.ts +38 -0
- package/dist/response-analyzer/response-analyzer.js +97 -0
- package/dist/response-analyzer/types.d.ts +97 -0
- package/dist/response-analyzer/types.js +4 -0
- package/dist/response-fallback-fixer.d.ts +11 -0
- package/dist/response-fallback-fixer.js +123 -0
- package/dist/runtime-objects.d.ts +52 -0
- package/dist/runtime-objects.js +46 -0
- package/dist/template-parser.d.ts +58 -0
- package/dist/template-parser.js +99 -0
- package/dist/template-render-merge.d.ts +9 -0
- package/dist/template-render-merge.js +40 -0
- package/dist/troubleshooting-helper.d.ts +123 -0
- package/dist/troubleshooting-helper.js +596 -0
- package/dist/types.d.ts +1173 -0
- package/dist/types.js +6 -0
- package/dist/usage-tracker.d.ts +78 -0
- package/dist/usage-tracker.js +79 -0
- package/dist-cjs/activity-manager.cjs +1056 -0
- package/dist-cjs/activity-manager.d.ts +206 -0
- package/dist-cjs/config/activity-tracking-config.cjs +18 -0
- package/dist-cjs/config/activity-tracking-config.d.ts +11 -0
- package/dist-cjs/config.defaults.json +31 -0
- package/dist-cjs/content-normalizer/content-normalizer.cjs +398 -0
- package/dist-cjs/content-normalizer/content-normalizer.d.ts +46 -0
- package/dist-cjs/content-normalizer/index.cjs +12 -0
- package/dist-cjs/content-normalizer/index.d.ts +7 -0
- package/dist-cjs/content-normalizer/types.cjs +5 -0
- package/dist-cjs/content-normalizer/types.d.ts +33 -0
- package/dist-cjs/defaults/instructions-blocks.json +61 -0
- package/dist-cjs/defaults/model-config.json +16 -0
- package/dist-cjs/defaults/template-rendering.json +6 -0
- package/dist-cjs/flex-md-loader.cjs +986 -0
- package/dist-cjs/flex-md-loader.d.ts +109 -0
- package/dist-cjs/gateway-config.cjs +331 -0
- package/dist-cjs/gateway-config.d.ts +49 -0
- package/dist-cjs/gateway-conversion.cjs +212 -0
- package/dist-cjs/gateway-conversion.d.ts +29 -0
- package/dist-cjs/gateway-instructions.cjs +67 -0
- package/dist-cjs/gateway-instructions.d.ts +30 -0
- package/dist-cjs/gateway-memory.cjs +211 -0
- package/dist-cjs/gateway-memory.d.ts +51 -0
- package/dist-cjs/gateway-messages.cjs +86 -0
- package/dist-cjs/gateway-messages.d.ts +23 -0
- package/dist-cjs/gateway-meta.cjs +90 -0
- package/dist-cjs/gateway-meta.d.ts +25 -0
- package/dist-cjs/gateway-provider-auto-register.cjs +195 -0
- package/dist-cjs/gateway-provider-auto-register.d.ts +17 -0
- package/dist-cjs/gateway-provider.cjs +214 -0
- package/dist-cjs/gateway-provider.d.ts +54 -0
- package/dist-cjs/gateway-rate-limiter-constants.cjs +19 -0
- package/dist-cjs/gateway-rate-limiter-constants.d.ts +16 -0
- package/dist-cjs/gateway-rate-limiter.cjs +111 -0
- package/dist-cjs/gateway-rate-limiter.d.ts +56 -0
- package/dist-cjs/gateway-retry.cjs +212 -0
- package/dist-cjs/gateway-retry.d.ts +49 -0
- package/dist-cjs/gateway-utils.cjs +219 -0
- package/dist-cjs/gateway-utils.d.ts +21 -0
- package/dist-cjs/gateway-validation.cjs +54 -0
- package/dist-cjs/gateway-validation.d.ts +13 -0
- package/dist-cjs/gateway.cjs +434 -0
- package/dist-cjs/gateway.d.ts +39 -0
- package/dist-cjs/index.cjs +108 -0
- package/dist-cjs/index.d.ts +36 -0
- package/dist-cjs/instruction-errors.cjs +34 -0
- package/dist-cjs/instruction-errors.d.ts +16 -0
- package/dist-cjs/instruction-optimizer.cjs +299 -0
- package/dist-cjs/instruction-optimizer.d.ts +113 -0
- package/dist-cjs/instructions-parser.cjs +61 -0
- package/dist-cjs/instructions-parser.d.ts +31 -0
- package/dist-cjs/logger-factory.cjs +45 -0
- package/dist-cjs/logger-factory.d.ts +17 -0
- package/dist-cjs/message-builder.cjs +558 -0
- package/dist-cjs/message-builder.d.ts +41 -0
- package/dist-cjs/object-types-library-integration.cjs +32 -0
- package/dist-cjs/object-types-library-integration.d.ts +22 -0
- package/dist-cjs/object-types-library.cjs +215 -0
- package/dist-cjs/object-types-library.d.ts +351 -0
- package/dist-cjs/output-auditor.cjs +52 -0
- package/dist-cjs/output-auditor.d.ts +44 -0
- package/dist-cjs/request-report-generator.cjs +172 -0
- package/dist-cjs/request-report-generator.d.ts +60 -0
- package/dist-cjs/response-analyzer/format-type-detector.cjs +119 -0
- package/dist-cjs/response-analyzer/format-type-detector.d.ts +35 -0
- package/dist-cjs/response-analyzer/index.cjs +14 -0
- package/dist-cjs/response-analyzer/index.d.ts +9 -0
- package/dist-cjs/response-analyzer/object-type-detector.cjs +99 -0
- package/dist-cjs/response-analyzer/object-type-detector.d.ts +42 -0
- package/dist-cjs/response-analyzer/response-analyzer.cjs +101 -0
- package/dist-cjs/response-analyzer/response-analyzer.d.ts +38 -0
- package/dist-cjs/response-analyzer/types.cjs +5 -0
- package/dist-cjs/response-analyzer/types.d.ts +97 -0
- package/dist-cjs/response-fallback-fixer.cjs +126 -0
- package/dist-cjs/response-fallback-fixer.d.ts +11 -0
- package/dist-cjs/runtime-objects.cjs +52 -0
- package/dist-cjs/runtime-objects.d.ts +52 -0
- package/dist-cjs/template-parser.cjs +136 -0
- package/dist-cjs/template-parser.d.ts +58 -0
- package/dist-cjs/template-render-merge.cjs +43 -0
- package/dist-cjs/template-render-merge.d.ts +9 -0
- package/dist-cjs/troubleshooting-helper.cjs +611 -0
- package/dist-cjs/troubleshooting-helper.d.ts +123 -0
- package/dist-cjs/types.cjs +7 -0
- package/dist-cjs/types.d.ts +1173 -0
- package/dist-cjs/usage-tracker.cjs +83 -0
- package/dist-cjs/usage-tracker.d.ts +78 -0
- 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,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,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
|
+
|