@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,940 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @x12i/flex-md Loader
|
|
3
|
+
*
|
|
4
|
+
* Handles ES Module loading and provides unified API for flex-md operations.
|
|
5
|
+
* @x12i/flex-md is an ES Module, so we use dynamic import() instead of require().
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - extractJsonFromFlexMd: Extract and parse JSON from flex-md content
|
|
9
|
+
* - buildDynamicInstructions: Build output format instructions from schema objects (3.1+)
|
|
10
|
+
* - Embedded nx-md-parser: flex-md 4.x includes nx-md-parser functionality (no separate package needed)
|
|
11
|
+
* - Utilizes latest flex-md 4.x APIs: extractFromMarkdown, transformWithOfs, processResponseMarkdown
|
|
12
|
+
* - Memory/caching: flex-md 4.x functions may utilize internal memory/caching for performance
|
|
13
|
+
* - Native output format support: Extracts and uses flex-md native output formats from instructions
|
|
14
|
+
* based on compliance level when no explicit format is provided
|
|
15
|
+
*/
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import { pathToFileURL } from 'url';
|
|
18
|
+
let flexMdModule = null;
|
|
19
|
+
let flexMdLoadError = null;
|
|
20
|
+
let flexMdLoading = null;
|
|
21
|
+
let hasLoggedFlexMdLoadWarning = false;
|
|
22
|
+
// Preserve native dynamic import in both ESM and transpiled CJS output.
|
|
23
|
+
// TypeScript can rewrite direct `import()` to `require()` in CJS builds.
|
|
24
|
+
const importModule = new Function('specifier', 'return import(specifier)');
|
|
25
|
+
async function importFlexMdModule() {
|
|
26
|
+
try {
|
|
27
|
+
return await importModule('@x12i/flex-md');
|
|
28
|
+
}
|
|
29
|
+
catch (esmImportError) {
|
|
30
|
+
// If package import condition is broken, import explicit CJS entry via file URL.
|
|
31
|
+
const { createRequire } = await importModule('module');
|
|
32
|
+
const requireFn = typeof require === 'function' ? require : createRequire(path.join(process.cwd(), 'noop.cjs'));
|
|
33
|
+
const cjsEntryPath = requireFn.resolve('@x12i/flex-md');
|
|
34
|
+
const cjsEntryUrl = pathToFileURL(cjsEntryPath).href;
|
|
35
|
+
try {
|
|
36
|
+
return await importModule(cjsEntryUrl);
|
|
37
|
+
}
|
|
38
|
+
catch (cjsImportError) {
|
|
39
|
+
const details = [
|
|
40
|
+
`import condition failed: ${esmImportError instanceof Error ? esmImportError.message : String(esmImportError)}`,
|
|
41
|
+
`cjs entry fallback failed: ${cjsImportError instanceof Error ? cjsImportError.message : String(cjsImportError)}`
|
|
42
|
+
].join('\n');
|
|
43
|
+
throw new Error(details);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// nx-md-parser functionality is now embedded in flex-md, no separate loading needed
|
|
48
|
+
/**
|
|
49
|
+
* Load flex-md module (ES Module) asynchronously
|
|
50
|
+
* Caches the result to avoid multiple imports
|
|
51
|
+
*/
|
|
52
|
+
export async function loadFlexMd() {
|
|
53
|
+
// Return cached module if available
|
|
54
|
+
if (flexMdModule) {
|
|
55
|
+
return flexMdModule;
|
|
56
|
+
}
|
|
57
|
+
// Return cached error if loading previously failed
|
|
58
|
+
if (flexMdLoadError) {
|
|
59
|
+
throw flexMdLoadError;
|
|
60
|
+
}
|
|
61
|
+
// If already loading, wait for that promise
|
|
62
|
+
if (flexMdLoading) {
|
|
63
|
+
return await flexMdLoading;
|
|
64
|
+
}
|
|
65
|
+
// Start loading
|
|
66
|
+
flexMdLoading = (async () => {
|
|
67
|
+
try {
|
|
68
|
+
// Always load through preserved dynamic import to avoid CJS require() rewrites.
|
|
69
|
+
const imported = await importFlexMdModule();
|
|
70
|
+
flexMdModule = imported.default || imported;
|
|
71
|
+
return flexMdModule;
|
|
72
|
+
}
|
|
73
|
+
catch (esImportError) {
|
|
74
|
+
const errorMsg = [
|
|
75
|
+
'@x12i/flex-md failed to load via dynamic import',
|
|
76
|
+
`reason: ${esImportError instanceof Error ? esImportError.message : String(esImportError)}`
|
|
77
|
+
].join('\n');
|
|
78
|
+
flexMdLoadError = new Error(errorMsg);
|
|
79
|
+
throw flexMdLoadError;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
flexMdLoading = null;
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
85
|
+
return await flexMdLoading;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Transform markdown to JSON using flex-md's embedded nx-md-parser functionality
|
|
89
|
+
* flex-md 4.x includes nx-md-parser functionality, so we use flex-md's APIs directly
|
|
90
|
+
*/
|
|
91
|
+
async function transformWithFlexMdNxParser(content, schema, flexMd) {
|
|
92
|
+
try {
|
|
93
|
+
// flex-md 4.x includes nx-md-parser functionality
|
|
94
|
+
// Check if flex-md has JSONTransformer and Schema (from embedded nx-md-parser)
|
|
95
|
+
if (flexMd.JSONTransformer && flexMd.Schema) {
|
|
96
|
+
const { JSONTransformer, Schema } = flexMd;
|
|
97
|
+
// Convert schema to nx-md-parser Schema format
|
|
98
|
+
let nxSchema;
|
|
99
|
+
// If schema is already a Schema object (from Schema.object()), use it directly
|
|
100
|
+
if (schema && typeof schema === 'object' && 'toJSON' in schema) {
|
|
101
|
+
nxSchema = schema;
|
|
102
|
+
}
|
|
103
|
+
else if (schema && typeof schema === 'object' && schema.type === 'object' && schema.properties) {
|
|
104
|
+
// Convert JSON Schema to Schema builder format
|
|
105
|
+
nxSchema = convertJsonSchemaToSchemaBuilder(schema, Schema);
|
|
106
|
+
}
|
|
107
|
+
else if (schema && typeof schema === 'object') {
|
|
108
|
+
// Plain object - try to convert to Schema builder format
|
|
109
|
+
nxSchema = convertPlainObjectToSchemaBuilder(schema, Schema);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
return null; // Invalid schema format
|
|
113
|
+
}
|
|
114
|
+
const transformer = new JSONTransformer(nxSchema);
|
|
115
|
+
const result = transformer.transformMarkdown(content);
|
|
116
|
+
if (result && result.status !== 'failed' && result.result) {
|
|
117
|
+
return {
|
|
118
|
+
json: result.result,
|
|
119
|
+
method: `flex-md-nx-parser-${result.status || 'validated'}`
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
// Transformation failed, return null to fall back to other methods
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Convert JSON Schema to nx-md-parser Schema format
|
|
132
|
+
* Supports both JSON Schema format and nx-md-parser Schema builder format
|
|
133
|
+
*/
|
|
134
|
+
function convertJsonSchemaToNxMdSchema(schema) {
|
|
135
|
+
// If it's already a nx-md-parser Schema object (has methods like toJSON), return as-is
|
|
136
|
+
if (schema && typeof schema === 'object' && 'toJSON' in schema) {
|
|
137
|
+
return schema;
|
|
138
|
+
}
|
|
139
|
+
// If it's a JSON Schema-like object, convert it
|
|
140
|
+
if (schema && typeof schema === 'object' && schema.type === 'object' && schema.properties) {
|
|
141
|
+
// We'll need to convert JSON Schema to nx-md-parser Schema format
|
|
142
|
+
// For now, return the schema as-is and let nx-md-parser handle it
|
|
143
|
+
// nx-md-parser supports JSON Schema format via createTransformerFromSchemaFile
|
|
144
|
+
return schema;
|
|
145
|
+
}
|
|
146
|
+
// If it's a plain object (simple format), try to infer structure
|
|
147
|
+
if (schema && typeof schema === 'object' && !Array.isArray(schema)) {
|
|
148
|
+
return schema;
|
|
149
|
+
}
|
|
150
|
+
return schema;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Convert JSON Schema properties to nx-md-parser Schema builder format
|
|
154
|
+
*/
|
|
155
|
+
function convertJsonSchemaToSchemaBuilder(jsonSchema, Schema) {
|
|
156
|
+
if (!jsonSchema.properties || typeof jsonSchema.properties !== 'object') {
|
|
157
|
+
throw new Error('Invalid JSON Schema: missing properties');
|
|
158
|
+
}
|
|
159
|
+
const schemaProps = {};
|
|
160
|
+
for (const [key, prop] of Object.entries(jsonSchema.properties)) {
|
|
161
|
+
const propObj = prop;
|
|
162
|
+
const propType = propObj.type;
|
|
163
|
+
if (propType === 'string') {
|
|
164
|
+
schemaProps[key] = Schema.string();
|
|
165
|
+
}
|
|
166
|
+
else if (propType === 'number' || propType === 'integer') {
|
|
167
|
+
schemaProps[key] = Schema.number();
|
|
168
|
+
}
|
|
169
|
+
else if (propType === 'boolean') {
|
|
170
|
+
schemaProps[key] = Schema.boolean();
|
|
171
|
+
}
|
|
172
|
+
else if (propType === 'array') {
|
|
173
|
+
const items = propObj.items;
|
|
174
|
+
if (items && items.type === 'string') {
|
|
175
|
+
schemaProps[key] = Schema.array(Schema.string());
|
|
176
|
+
}
|
|
177
|
+
else if (items && items.type === 'number') {
|
|
178
|
+
schemaProps[key] = Schema.array(Schema.number());
|
|
179
|
+
}
|
|
180
|
+
else if (items && items.type === 'boolean') {
|
|
181
|
+
schemaProps[key] = Schema.array(Schema.boolean());
|
|
182
|
+
}
|
|
183
|
+
else if (items && items.type === 'object') {
|
|
184
|
+
schemaProps[key] = Schema.array(convertJsonSchemaToSchemaBuilder(items, Schema));
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
schemaProps[key] = Schema.array(Schema.string()); // Default to string array
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else if (propType === 'object') {
|
|
191
|
+
schemaProps[key] = convertJsonSchemaToSchemaBuilder(propObj, Schema);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
// Default to string for unknown types
|
|
195
|
+
schemaProps[key] = Schema.string();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return Schema.object(schemaProps);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Convert plain object to nx-md-parser Schema builder format
|
|
202
|
+
* Infers types from example values
|
|
203
|
+
*/
|
|
204
|
+
function convertPlainObjectToSchemaBuilder(obj, Schema) {
|
|
205
|
+
const schemaProps = {};
|
|
206
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
207
|
+
if (typeof value === 'string') {
|
|
208
|
+
schemaProps[key] = Schema.string();
|
|
209
|
+
}
|
|
210
|
+
else if (typeof value === 'number') {
|
|
211
|
+
schemaProps[key] = Schema.number();
|
|
212
|
+
}
|
|
213
|
+
else if (typeof value === 'boolean') {
|
|
214
|
+
schemaProps[key] = Schema.boolean();
|
|
215
|
+
}
|
|
216
|
+
else if (Array.isArray(value)) {
|
|
217
|
+
if (value.length > 0) {
|
|
218
|
+
const firstItem = value[0];
|
|
219
|
+
if (typeof firstItem === 'string') {
|
|
220
|
+
schemaProps[key] = Schema.array(Schema.string());
|
|
221
|
+
}
|
|
222
|
+
else if (typeof firstItem === 'number') {
|
|
223
|
+
schemaProps[key] = Schema.array(Schema.number());
|
|
224
|
+
}
|
|
225
|
+
else if (typeof firstItem === 'boolean') {
|
|
226
|
+
schemaProps[key] = Schema.array(Schema.boolean());
|
|
227
|
+
}
|
|
228
|
+
else if (typeof firstItem === 'object') {
|
|
229
|
+
schemaProps[key] = Schema.array(convertPlainObjectToSchemaBuilder(firstItem, Schema));
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
schemaProps[key] = Schema.array(Schema.string());
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
schemaProps[key] = Schema.array(Schema.string()); // Empty array defaults to string array
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
else if (value && typeof value === 'object') {
|
|
240
|
+
schemaProps[key] = convertPlainObjectToSchemaBuilder(value, Schema);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
schemaProps[key] = Schema.string(); // Default to string
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return Schema.object(schemaProps);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Extract flex-md native output format from instructions text
|
|
250
|
+
* Uses flex-md's parseOutputFormatSpec to parse format specifications
|
|
251
|
+
*/
|
|
252
|
+
async function extractFlexMdOutputFormatFromInstructions(instructions, complianceLevel) {
|
|
253
|
+
try {
|
|
254
|
+
const flexMd = await loadFlexMd();
|
|
255
|
+
// Use flex-md's parseOutputFormatSpec if available
|
|
256
|
+
if (flexMd.parseOutputFormatSpec && typeof flexMd.parseOutputFormatSpec === 'function') {
|
|
257
|
+
try {
|
|
258
|
+
const parsedSpec = flexMd.parseOutputFormatSpec(instructions);
|
|
259
|
+
if (parsedSpec && typeof parsedSpec === 'object' && parsedSpec !== null) {
|
|
260
|
+
// Add compliance level if provided
|
|
261
|
+
if (complianceLevel) {
|
|
262
|
+
parsedSpec.strictness = complianceLevel;
|
|
263
|
+
}
|
|
264
|
+
return parsedSpec;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch (e) {
|
|
268
|
+
// parseOutputFormatSpec failed, try manual extraction
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Fallback: Try to extract OUTPUT FORMAT section manually
|
|
272
|
+
const formatPatterns = [
|
|
273
|
+
/OUTPUT\s+FORMAT[:\s]*(?:\([^)]*\))?[:\s]*\n([\s\S]*?)$/im,
|
|
274
|
+
/OUTPUT\s+FORMAT[:\s]*(?:\([^)]*\))?[:\s]*\n([\s\S]*?)(?=\n\n(?:##|#|OUTPUT|$|\n[A-Z]{2,})|$)/i,
|
|
275
|
+
/^##+\s+Output\s+Format[:\s]*\n([\s\S]*?)(?=^##+\s+|$)/im,
|
|
276
|
+
];
|
|
277
|
+
for (const pattern of formatPatterns) {
|
|
278
|
+
const match = instructions.match(pattern);
|
|
279
|
+
if (match && match[1]) {
|
|
280
|
+
const formatSpec = match[1].trim();
|
|
281
|
+
if (formatSpec.length >= 30) {
|
|
282
|
+
// Try to parse with flex-md if available
|
|
283
|
+
if (flexMd.parseOutputFormatSpec) {
|
|
284
|
+
try {
|
|
285
|
+
const parsedSpec = flexMd.parseOutputFormatSpec(formatSpec);
|
|
286
|
+
if (parsedSpec && typeof parsedSpec === 'object' && parsedSpec !== null) {
|
|
287
|
+
if (complianceLevel) {
|
|
288
|
+
parsedSpec.strictness = complianceLevel;
|
|
289
|
+
}
|
|
290
|
+
return parsedSpec;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch (e) {
|
|
294
|
+
// Continue to next pattern
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Return raw format spec as fallback
|
|
298
|
+
return {
|
|
299
|
+
format: formatSpec,
|
|
300
|
+
strictness: complianceLevel || 'L0'
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Extract and parse JSON from flex-md content (unstructured parsing only)
|
|
313
|
+
* Uses flex-md 4.2.0 APIs with latest features for unstructured markdown conversion
|
|
314
|
+
*
|
|
315
|
+
* ARCHITECTURE: This function only handles unstructured parsing at ai-gateway layer
|
|
316
|
+
* Schema-driven parsing happens at skills layer with nx-md-parser
|
|
317
|
+
*
|
|
318
|
+
* Supports flex-md native output formats from instructions:
|
|
319
|
+
* - Extracts flex-md output format from instructions when provided
|
|
320
|
+
* - Uses the extracted format based on compliance level for parsing
|
|
321
|
+
* - Falls back to simple markdown parsing when flex-md is unavailable
|
|
322
|
+
*
|
|
323
|
+
* @param content - The markdown content to parse (unstructured)
|
|
324
|
+
* @param options - Optional flex-md options:
|
|
325
|
+
* - instructions: Instruction text to guide unstructured parsing
|
|
326
|
+
* - complianceLevel: Compliance level ('L0' | 'L1' | 'L2' | 'L3') for format extraction
|
|
327
|
+
* - spec: Pre-parsed flex-md output format spec
|
|
328
|
+
* - Other flex-md options
|
|
329
|
+
* @returns Parsed JSON object and method used, or null if parsing fails
|
|
330
|
+
*/
|
|
331
|
+
export async function extractJsonFromFlexMd(content) {
|
|
332
|
+
// ARCHITECTURE: ai-gateway layer only handles unstructured parsing
|
|
333
|
+
// Schema-driven parsing is handled at skills layer with nx-md-parser
|
|
334
|
+
// No schema parameter - this layer doesn't know about schemas
|
|
335
|
+
try {
|
|
336
|
+
const flexMd = await loadFlexMd();
|
|
337
|
+
// Primary approach: Use markdownToJson() for simple markdown → JSON conversion
|
|
338
|
+
if (flexMd.markdownToJson && typeof flexMd.markdownToJson === 'function') {
|
|
339
|
+
try {
|
|
340
|
+
const json = flexMd.markdownToJson(content);
|
|
341
|
+
if (json && typeof json === 'object' && Object.keys(json).length > 0) {
|
|
342
|
+
return {
|
|
343
|
+
json,
|
|
344
|
+
method: 'markdownToJson'
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
// markdownToJson failed, continue to other methods
|
|
350
|
+
console.warn('markdownToJson failed:', error instanceof Error ? error.message : String(error));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Secondary approach: Use extractFromMarkdown for low-level section extraction
|
|
354
|
+
if (flexMd.extractFromMarkdown && typeof flexMd.extractFromMarkdown === 'function') {
|
|
355
|
+
try {
|
|
356
|
+
const result = flexMd.extractFromMarkdown(content);
|
|
357
|
+
if (result && typeof result === 'object' && result !== null) {
|
|
358
|
+
const json = result.json || result.data || result.result || result;
|
|
359
|
+
if (json && typeof json === 'object' && json !== null) {
|
|
360
|
+
console.debug('extractJsonFromFlexMd: using extractFromMarkdown');
|
|
361
|
+
console.debug('extractJsonFromFlexMd: json:', json);
|
|
362
|
+
return { json, method: 'extractFromMarkdown' };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
// extractFromMarkdown failed, continue
|
|
368
|
+
console.warn('extractFromMarkdown failed:', error instanceof Error ? error.message : String(error));
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
// flex-md loading failed, log once and continue to fallback.
|
|
374
|
+
if (!hasLoggedFlexMdLoadWarning) {
|
|
375
|
+
hasLoggedFlexMdLoadWarning = true;
|
|
376
|
+
console.warn('extractJsonFromFlexMd: flex-md library failed to load, using markdown parser fallback:', error instanceof Error ? error.message : String(error));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
console.debug('extractJsonFromFlexMd: using markdown parser fallback');
|
|
380
|
+
console.debug('extractJsonFromFlexMd: content:', content);
|
|
381
|
+
// Final fallback: Parse markdown sections
|
|
382
|
+
return fallbackMarkdownParser(content);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Helper function to detect and parse markdown lists into arrays
|
|
386
|
+
*/
|
|
387
|
+
function parseMarkdownList(content) {
|
|
388
|
+
if (content.length === 0)
|
|
389
|
+
return '';
|
|
390
|
+
// Check if content contains actual list markers
|
|
391
|
+
const hasBulletPoints = content.some(line => line.trim().startsWith('- '));
|
|
392
|
+
const hasNumberedItems = content.some(line => /^\d+\.\s/.test(line.trim()));
|
|
393
|
+
// If no list markers found, return as joined string
|
|
394
|
+
if (!hasBulletPoints && !hasNumberedItems) {
|
|
395
|
+
return content.join('\n').trim();
|
|
396
|
+
}
|
|
397
|
+
// Parse list items
|
|
398
|
+
const listItems = [];
|
|
399
|
+
let currentItem = '';
|
|
400
|
+
for (const line of content) {
|
|
401
|
+
const trimmed = line.trim();
|
|
402
|
+
// Check for bullet points (- item)
|
|
403
|
+
if (trimmed.startsWith('- ')) {
|
|
404
|
+
// Save previous item if exists
|
|
405
|
+
if (currentItem) {
|
|
406
|
+
listItems.push(currentItem.trim());
|
|
407
|
+
}
|
|
408
|
+
// Start new item
|
|
409
|
+
currentItem = trimmed.substring(2).trim();
|
|
410
|
+
}
|
|
411
|
+
// Check for numbered items (1. item, 2. item, etc.)
|
|
412
|
+
else if (/^\d+\.\s/.test(trimmed)) {
|
|
413
|
+
// Save previous item if exists
|
|
414
|
+
if (currentItem) {
|
|
415
|
+
listItems.push(currentItem.trim());
|
|
416
|
+
}
|
|
417
|
+
// Start new item
|
|
418
|
+
const match = trimmed.match(/^\d+\.\s(.+)$/);
|
|
419
|
+
currentItem = match ? match[1].trim() : trimmed;
|
|
420
|
+
}
|
|
421
|
+
// Continuation of current item
|
|
422
|
+
else if (currentItem && trimmed) {
|
|
423
|
+
currentItem += ' ' + trimmed;
|
|
424
|
+
}
|
|
425
|
+
// If we haven't started an item yet and this is content
|
|
426
|
+
else if (!currentItem && trimmed) {
|
|
427
|
+
currentItem = trimmed;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// Add final item
|
|
431
|
+
if (currentItem) {
|
|
432
|
+
listItems.push(currentItem.trim());
|
|
433
|
+
}
|
|
434
|
+
// Return array if we have multiple items or explicit list markers, otherwise string
|
|
435
|
+
return listItems.length > 1 || hasBulletPoints || hasNumberedItems ? listItems : content.join('\n').trim();
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Simple markdown parser for unstructured content (fallback when flex-md unavailable)
|
|
439
|
+
*/
|
|
440
|
+
function fallbackMarkdownParser(content) {
|
|
441
|
+
const result = {};
|
|
442
|
+
const detectedHeaders = [];
|
|
443
|
+
// Simple section extraction based on markdown headers
|
|
444
|
+
const lines = content.split('\n');
|
|
445
|
+
let currentSection = '';
|
|
446
|
+
let currentContent = [];
|
|
447
|
+
for (const line of lines) {
|
|
448
|
+
// Check for markdown headers (# ## ###) - improved regex
|
|
449
|
+
const headerMatch = line.match(/^(#{1,6})\s*(.+?)\s*$/);
|
|
450
|
+
if (headerMatch) {
|
|
451
|
+
// Save previous section if exists
|
|
452
|
+
if (currentSection && currentContent.length > 0) {
|
|
453
|
+
// Convert header to camelCase key
|
|
454
|
+
const key = currentSection
|
|
455
|
+
.toLowerCase()
|
|
456
|
+
.replace(/[^a-zA-Z0-9\s]/g, '') // Remove special chars
|
|
457
|
+
.replace(/\s+/g, ' ') // Normalize spaces
|
|
458
|
+
.trim()
|
|
459
|
+
.replace(/\s+(\w)/g, (_, letter) => letter.toUpperCase()); // camelCase
|
|
460
|
+
result[key] = parseMarkdownList(currentContent);
|
|
461
|
+
detectedHeaders.push(key);
|
|
462
|
+
}
|
|
463
|
+
// Start new section
|
|
464
|
+
currentSection = headerMatch[2].trim();
|
|
465
|
+
currentContent = [];
|
|
466
|
+
}
|
|
467
|
+
else if (currentSection) {
|
|
468
|
+
// Add line to current section
|
|
469
|
+
currentContent.push(line);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// Save last section
|
|
473
|
+
if (currentSection && currentContent.length > 0) {
|
|
474
|
+
const key = currentSection
|
|
475
|
+
.toLowerCase()
|
|
476
|
+
.replace(/[^a-zA-Z0-9\s]/g, '')
|
|
477
|
+
.replace(/\s+/g, ' ')
|
|
478
|
+
.trim()
|
|
479
|
+
.replace(/\s+(\w)/g, (_, letter) => letter.toUpperCase());
|
|
480
|
+
result[key] = parseMarkdownList(currentContent);
|
|
481
|
+
detectedHeaders.push(key);
|
|
482
|
+
}
|
|
483
|
+
// Debug logging for incomplete parsing
|
|
484
|
+
console.log('Fallback parser detected headers:', detectedHeaders);
|
|
485
|
+
const expectedHeaders = ['shortAnswer', 'fullAnswer', 'assumptions', 'unknowns', 'evidence'];
|
|
486
|
+
const missingHeaders = expectedHeaders.filter(h => !detectedHeaders.includes(h));
|
|
487
|
+
if (missingHeaders.length > 0) {
|
|
488
|
+
console.log('Fallback parser missing expected headers:', missingHeaders);
|
|
489
|
+
}
|
|
490
|
+
// If we extracted some structure, return it
|
|
491
|
+
if (Object.keys(result).length > 0) {
|
|
492
|
+
return {
|
|
493
|
+
json: result,
|
|
494
|
+
method: 'fallback-markdown-parser'
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
// No structure found, return raw text
|
|
498
|
+
return {
|
|
499
|
+
json: { rawText: content },
|
|
500
|
+
method: 'fallback-raw-text'
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Fallback JSON extraction when flex-md is not available
|
|
505
|
+
*/
|
|
506
|
+
function extractJsonFallback(content) {
|
|
507
|
+
// Look for fenced JSON blocks
|
|
508
|
+
const jsonBlockRegex = /```(?:json)?\s*\n?([\s\S]*?)\n?```/g;
|
|
509
|
+
let match;
|
|
510
|
+
while ((match = jsonBlockRegex.exec(content)) !== null) {
|
|
511
|
+
try {
|
|
512
|
+
const jsonContent = match[1].trim();
|
|
513
|
+
const parsed = JSON.parse(jsonContent);
|
|
514
|
+
if (parsed !== null && typeof parsed === 'object') {
|
|
515
|
+
return {
|
|
516
|
+
json: parsed,
|
|
517
|
+
method: 'fallback-json-block'
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
catch (e) {
|
|
522
|
+
// Continue to next block
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// No structured data found, return raw text wrapper
|
|
526
|
+
return {
|
|
527
|
+
json: { rawText: content },
|
|
528
|
+
method: 'fallback-raw-text'
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Check if flex-md module is available
|
|
533
|
+
*/
|
|
534
|
+
export function isFlexMdAvailable() {
|
|
535
|
+
return flexMdModule !== null;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Get maxTokens for a model from flex-md if available
|
|
539
|
+
* Tries various flex-md APIs to get model information and maxTokens
|
|
540
|
+
*
|
|
541
|
+
* @param provider - Provider name (e.g., 'openai', 'anthropic')
|
|
542
|
+
* @param model - Model name (e.g., 'gpt-4', 'claude-3-opus')
|
|
543
|
+
* @returns maxTokens if found, null otherwise
|
|
544
|
+
*/
|
|
545
|
+
export async function getModelMaxTokensFromFlexMd(provider, model) {
|
|
546
|
+
try {
|
|
547
|
+
const flexMd = await loadFlexMd();
|
|
548
|
+
if (!flexMd || typeof flexMd !== 'object') {
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
// Try various possible function names that flex-md might export
|
|
552
|
+
const possibleFunctions = [
|
|
553
|
+
'getModel',
|
|
554
|
+
'getModelInfo',
|
|
555
|
+
'getModelContext',
|
|
556
|
+
'getMaxTokens',
|
|
557
|
+
'modelRegistry',
|
|
558
|
+
'getModelMaxTokens'
|
|
559
|
+
];
|
|
560
|
+
for (const funcName of possibleFunctions) {
|
|
561
|
+
if (flexMd[funcName] && typeof flexMd[funcName] === 'function') {
|
|
562
|
+
try {
|
|
563
|
+
const result = flexMd[funcName](provider, model);
|
|
564
|
+
// Handle different possible return formats
|
|
565
|
+
if (result) {
|
|
566
|
+
// Case 1: Direct number
|
|
567
|
+
if (typeof result === 'number') {
|
|
568
|
+
return result;
|
|
569
|
+
}
|
|
570
|
+
// Case 2: Object with maxTokens or maxOutputTokens property
|
|
571
|
+
if (typeof result === 'object') {
|
|
572
|
+
if (typeof result.maxTokens === 'number') {
|
|
573
|
+
return result.maxTokens;
|
|
574
|
+
}
|
|
575
|
+
if (typeof result.maxOutputTokens === 'number') {
|
|
576
|
+
return result.maxOutputTokens;
|
|
577
|
+
}
|
|
578
|
+
// Case 3: Nested context object
|
|
579
|
+
if (result.context) {
|
|
580
|
+
if (typeof result.context.maxTokens === 'number') {
|
|
581
|
+
return result.context.maxTokens;
|
|
582
|
+
}
|
|
583
|
+
if (typeof result.context.maxOutputTokens === 'number') {
|
|
584
|
+
return result.context.maxOutputTokens;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
catch (e) {
|
|
591
|
+
// Function exists but call failed, continue to next
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
// Try registry pattern if available
|
|
597
|
+
if (flexMd.modelRegistry && typeof flexMd.modelRegistry === 'object') {
|
|
598
|
+
const registry = flexMd.modelRegistry;
|
|
599
|
+
const key = `${provider}/${model}`;
|
|
600
|
+
const altKey = `${provider}:${model}`;
|
|
601
|
+
const modelInfo = registry[key] || registry[altKey] || registry[model];
|
|
602
|
+
if (modelInfo) {
|
|
603
|
+
if (typeof modelInfo.maxTokens === 'number') {
|
|
604
|
+
return modelInfo.maxTokens;
|
|
605
|
+
}
|
|
606
|
+
if (typeof modelInfo.maxOutputTokens === 'number') {
|
|
607
|
+
return modelInfo.maxOutputTokens;
|
|
608
|
+
}
|
|
609
|
+
if (modelInfo.context && typeof modelInfo.context.maxOutputTokens === 'number') {
|
|
610
|
+
return modelInfo.context.maxOutputTokens;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
catch (error) {
|
|
617
|
+
// flex-md not available or doesn't have model info functionality
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Get flex-md module directly (must call loadFlexMd() first)
|
|
623
|
+
*/
|
|
624
|
+
export function getFlexMdModule() {
|
|
625
|
+
if (!flexMdModule) {
|
|
626
|
+
throw new Error('flex-md module not loaded. Call loadFlexMd() first.');
|
|
627
|
+
}
|
|
628
|
+
return flexMdModule;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Build dynamic instructions from an object/schema (flex-md 3.1+ feature)
|
|
632
|
+
* Takes an object (like a JSON schema) and generates flex-md format instructions
|
|
633
|
+
*
|
|
634
|
+
* @param schemaObject - The object/schema to generate instructions from
|
|
635
|
+
* @param options - Optional configuration for instruction generation
|
|
636
|
+
* @returns Generated flex-md format instructions as string
|
|
637
|
+
*/
|
|
638
|
+
/**
|
|
639
|
+
* Manually generates OUTPUT FORMAT instructions from JSON Schema
|
|
640
|
+
* This is a fallback when flex-md's buildDynamicInstructions doesn't exist
|
|
641
|
+
*/
|
|
642
|
+
function generateFormatInstructionsFromSchema(schema, options) {
|
|
643
|
+
const parts = [];
|
|
644
|
+
if (options?.title || options?.description) {
|
|
645
|
+
parts.push(`Output Format: ${options.title || 'Structured Data'}`);
|
|
646
|
+
if (options.description) {
|
|
647
|
+
parts.push(options.description);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
// Extract properties from JSON Schema
|
|
651
|
+
if (schema.type === 'object' && schema.properties) {
|
|
652
|
+
parts.push('\nReturn a JSON object with the following structure:');
|
|
653
|
+
parts.push('\n```json');
|
|
654
|
+
parts.push(JSON.stringify(schema, null, 2));
|
|
655
|
+
parts.push('```');
|
|
656
|
+
// Add property descriptions if available
|
|
657
|
+
const propertyDescriptions = [];
|
|
658
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
659
|
+
if (typeof prop === 'object' && prop !== null) {
|
|
660
|
+
const propObj = prop;
|
|
661
|
+
const desc = propObj.description || '';
|
|
662
|
+
const type = propObj.type || 'unknown';
|
|
663
|
+
const enumVals = propObj.enum ? ` (one of: ${propObj.enum.join(', ')})` : '';
|
|
664
|
+
if (desc || enumVals) {
|
|
665
|
+
propertyDescriptions.push(`- \`${key}\` (${type})${enumVals}: ${desc || 'No description'}`);
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
propertyDescriptions.push(`- \`${key}\` (${type})`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (propertyDescriptions.length > 0) {
|
|
673
|
+
parts.push('\nProperties:');
|
|
674
|
+
parts.push(...propertyDescriptions);
|
|
675
|
+
}
|
|
676
|
+
// Add required fields note
|
|
677
|
+
if (schema.required && Array.isArray(schema.required) && schema.required.length > 0) {
|
|
678
|
+
parts.push(`\nRequired fields: ${schema.required.join(', ')}`);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
// Not a standard JSON Schema object, just show the schema
|
|
683
|
+
parts.push('\nReturn a JSON object matching this schema:');
|
|
684
|
+
parts.push('\n```json');
|
|
685
|
+
parts.push(JSON.stringify(schema, null, 2));
|
|
686
|
+
parts.push('```');
|
|
687
|
+
}
|
|
688
|
+
parts.push('\n\nIMPORTANT: Return your answer as JSON inside the markdown fenced block. The JSON must match the schema above exactly.');
|
|
689
|
+
return parts.join('\n');
|
|
690
|
+
}
|
|
691
|
+
export async function buildDynamicInstructions(schemaObject, options) {
|
|
692
|
+
try {
|
|
693
|
+
const flexMd = await loadFlexMd();
|
|
694
|
+
// Check if dynamic instruction feature is available (flex-md 3.1+)
|
|
695
|
+
if (flexMd.buildDynamicInstructions && typeof flexMd.buildDynamicInstructions === 'function') {
|
|
696
|
+
const result = flexMd.buildDynamicInstructions(schemaObject, options);
|
|
697
|
+
const finalResult = typeof result === 'string' ? result : (result?.instructions || result?.format || null);
|
|
698
|
+
return finalResult;
|
|
699
|
+
}
|
|
700
|
+
// Fallback: Generate format instructions manually from JSON Schema
|
|
701
|
+
const manualInstructions = generateFormatInstructionsFromSchema(schemaObject, options);
|
|
702
|
+
return manualInstructions;
|
|
703
|
+
}
|
|
704
|
+
catch (error) {
|
|
705
|
+
// On error, try manual fallback
|
|
706
|
+
try {
|
|
707
|
+
const manualInstructions = generateFormatInstructionsFromSchema(schemaObject, options);
|
|
708
|
+
return manualInstructions;
|
|
709
|
+
}
|
|
710
|
+
catch (fallbackError) {
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Enrich instructions with flex-md compliance guidance (flex-md 3.0+ feature)
|
|
717
|
+
* Adds appropriate flex-md format instructions based on strictness level
|
|
718
|
+
*
|
|
719
|
+
* @param instructions - Base instructions text
|
|
720
|
+
* @param strictnessLevel - Compliance level ('L0' | 'L1' | 'L2' | 'L3')
|
|
721
|
+
* @returns Enriched instructions with flex-md guidance
|
|
722
|
+
*/
|
|
723
|
+
export async function enrichInstructionsWithFlexMd(instructions, strictnessLevel = 'L0') {
|
|
724
|
+
try {
|
|
725
|
+
const flexMd = await loadFlexMd();
|
|
726
|
+
// Use native flex-md function if available (flex-md 3.2+)
|
|
727
|
+
if (flexMd.enrichInstructionsWithFlexMd && typeof flexMd.enrichInstructionsWithFlexMd === 'function') {
|
|
728
|
+
const result = flexMd.enrichInstructionsWithFlexMd(instructions, strictnessLevel);
|
|
729
|
+
return typeof result === 'string' ? result : instructions;
|
|
730
|
+
}
|
|
731
|
+
// Fallback to enrichInstructions if native function not available
|
|
732
|
+
if (flexMd.enrichInstructions && typeof flexMd.enrichInstructions === 'function') {
|
|
733
|
+
const result = flexMd.enrichInstructions(instructions, { strictness: strictnessLevel });
|
|
734
|
+
return typeof result === 'string' ? result : instructions;
|
|
735
|
+
}
|
|
736
|
+
// Fallback: return original instructions if function not available
|
|
737
|
+
return instructions;
|
|
738
|
+
}
|
|
739
|
+
catch (error) {
|
|
740
|
+
// On error, return original instructions
|
|
741
|
+
return instructions;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Get strictness defaults for a compliance level (flex-md 3.0+ feature)
|
|
746
|
+
* Returns configuration object for the specified compliance level
|
|
747
|
+
*
|
|
748
|
+
* @param strictnessLevel - Compliance level ('L0' | 'L1' | 'L2' | 'L3')
|
|
749
|
+
* @returns Configuration object with level-specific defaults
|
|
750
|
+
*/
|
|
751
|
+
export async function getStrictnessDefaults(strictnessLevel = 'L0') {
|
|
752
|
+
try {
|
|
753
|
+
const flexMd = await loadFlexMd();
|
|
754
|
+
if (flexMd.strictnessDefaults && typeof flexMd.strictnessDefaults === 'function') {
|
|
755
|
+
return flexMd.strictnessDefaults(strictnessLevel);
|
|
756
|
+
}
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
catch (error) {
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Enforce flex-md format in instructions (flex-md 3.0+ feature)
|
|
765
|
+
* Adds enforcement rules based on output format spec and strictness level
|
|
766
|
+
*
|
|
767
|
+
* @param instructions - Base instructions text
|
|
768
|
+
* @param outputFormatSpec - Output format specification (optional)
|
|
769
|
+
* @param strictnessLevel - Compliance level ('L0' | 'L1' | 'L2' | 'L3')
|
|
770
|
+
* @returns Instructions with flex-md enforcement rules
|
|
771
|
+
*/
|
|
772
|
+
export async function enforceFlexMdFormat(instructions, outputFormatSpec, strictnessLevel = 'L0') {
|
|
773
|
+
try {
|
|
774
|
+
const flexMd = await loadFlexMd();
|
|
775
|
+
if (flexMd.enforceFlexMd && typeof flexMd.enforceFlexMd === 'function') {
|
|
776
|
+
const spec = outputFormatSpec || { strictness: strictnessLevel };
|
|
777
|
+
if (!spec.strictness) {
|
|
778
|
+
spec.strictness = strictnessLevel;
|
|
779
|
+
}
|
|
780
|
+
const result = flexMd.enforceFlexMd(instructions, spec);
|
|
781
|
+
return typeof result === 'string' ? result : instructions;
|
|
782
|
+
}
|
|
783
|
+
// Fallback: return original instructions if function not available
|
|
784
|
+
return instructions;
|
|
785
|
+
}
|
|
786
|
+
catch (error) {
|
|
787
|
+
// On error, return original instructions
|
|
788
|
+
return instructions;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Check if instructions meet a specific flex-md compliance level
|
|
793
|
+
*
|
|
794
|
+
* @param instructions - Instructions text to check
|
|
795
|
+
* @param complianceLevel - Required compliance level
|
|
796
|
+
* @returns true if instructions meet the compliance level
|
|
797
|
+
*/
|
|
798
|
+
export async function hasFlexMdContract(instructions, complianceLevel = 'L0') {
|
|
799
|
+
if (!instructions || instructions.trim() === '') {
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
try {
|
|
803
|
+
const flexMd = await loadFlexMd();
|
|
804
|
+
// Use native flex-md function if available (flex-md 3.2+)
|
|
805
|
+
if (flexMd.hasFlexMdContract && typeof flexMd.hasFlexMdContract === 'function') {
|
|
806
|
+
try {
|
|
807
|
+
const result = flexMd.hasFlexMdContract(instructions, complianceLevel);
|
|
808
|
+
return typeof result === 'boolean' ? result : false;
|
|
809
|
+
}
|
|
810
|
+
catch (e) {
|
|
811
|
+
// Native function might have different signature, fall through
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
// Fallback: Check if validateMarkdownAgainstOfs is available
|
|
815
|
+
if (flexMd.validateMarkdownAgainstOfs && typeof flexMd.validateMarkdownAgainstOfs === 'function') {
|
|
816
|
+
try {
|
|
817
|
+
// Try to validate - this might require a spec, so we catch errors
|
|
818
|
+
const result = flexMd.validateMarkdownAgainstOfs(instructions, { strictness: complianceLevel });
|
|
819
|
+
// If validation passes, instructions meet compliance
|
|
820
|
+
if (result && result.valid !== false) {
|
|
821
|
+
return true;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
catch (e) {
|
|
825
|
+
// Validation function might need more parameters, fall through to pattern matching
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
catch (error) {
|
|
830
|
+
// flex-md SDK not available or error - fall through to pattern matching
|
|
831
|
+
}
|
|
832
|
+
// Fallback: Pattern-based checking
|
|
833
|
+
const text = instructions.toLowerCase();
|
|
834
|
+
// Check for key flex-md enforcement phrases
|
|
835
|
+
const flexMdIndicators = [
|
|
836
|
+
'markdown',
|
|
837
|
+
'fenced block',
|
|
838
|
+
'```markdown',
|
|
839
|
+
'```json',
|
|
840
|
+
'reply in markdown',
|
|
841
|
+
'return your entire answer'
|
|
842
|
+
];
|
|
843
|
+
// Check for enforcement language based on compliance level
|
|
844
|
+
const enforcementIndicators = [];
|
|
845
|
+
if (complianceLevel === 'L2' || complianceLevel === 'L3') {
|
|
846
|
+
// L2/L3 require fenced block container
|
|
847
|
+
enforcementIndicators.push('fenced block', '```markdown', 'single ```markdown', 'entire answer inside', 'nothing else');
|
|
848
|
+
}
|
|
849
|
+
if (complianceLevel === 'L1' || complianceLevel === 'L2' || complianceLevel === 'L3') {
|
|
850
|
+
// L1+ requires section headings
|
|
851
|
+
enforcementIndicators.push('section', 'heading', 'include these');
|
|
852
|
+
}
|
|
853
|
+
// Must have at least one flex-md indicator AND appropriate enforcement indicators
|
|
854
|
+
const hasFlexMd = flexMdIndicators.some(indicator => text.includes(indicator));
|
|
855
|
+
const hasEnforcement = enforcementIndicators.length === 0 ||
|
|
856
|
+
enforcementIndicators.some(indicator => text.includes(indicator));
|
|
857
|
+
return hasFlexMd && hasEnforcement;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Detect the actual compliance level that instructions meet
|
|
861
|
+
* Checks from highest (L3) to lowest (L0) to find the highest level met
|
|
862
|
+
*
|
|
863
|
+
* @param instructions - Instructions text to check
|
|
864
|
+
* @returns The highest compliance level met ('L0' | 'L1' | 'L2' | 'L3' | null if none)
|
|
865
|
+
*/
|
|
866
|
+
export async function detectComplianceLevel(instructions) {
|
|
867
|
+
if (!instructions || instructions.trim() === '') {
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
try {
|
|
871
|
+
const flexMd = await loadFlexMd();
|
|
872
|
+
// Check from highest to lowest level using native hasFlexMdContract
|
|
873
|
+
const levels = ['L3', 'L2', 'L1', 'L0'];
|
|
874
|
+
// Use native hasFlexMdContract if available (flex-md 3.2+)
|
|
875
|
+
if (flexMd.hasFlexMdContract && typeof flexMd.hasFlexMdContract === 'function') {
|
|
876
|
+
for (const level of levels) {
|
|
877
|
+
try {
|
|
878
|
+
const meetsLevel = flexMd.hasFlexMdContract(instructions, level);
|
|
879
|
+
if (meetsLevel === true) {
|
|
880
|
+
return level;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
catch (e) {
|
|
884
|
+
// Continue to next level
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
889
|
+
// Fallback: Use our wrapper function
|
|
890
|
+
for (const level of levels) {
|
|
891
|
+
const meetsLevel = await hasFlexMdContract(instructions, level);
|
|
892
|
+
if (meetsLevel) {
|
|
893
|
+
return level;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
// If flex-md SDK is available, try using validateMarkdownAgainstOfs directly
|
|
898
|
+
if (flexMd.validateMarkdownAgainstOfs && typeof flexMd.validateMarkdownAgainstOfs === 'function') {
|
|
899
|
+
// Try L3 first (highest)
|
|
900
|
+
for (const level of levels) {
|
|
901
|
+
try {
|
|
902
|
+
const result = flexMd.validateMarkdownAgainstOfs(instructions, { strictness: level });
|
|
903
|
+
if (result && result.valid !== false) {
|
|
904
|
+
return level;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
catch (e) {
|
|
908
|
+
// Continue to next level
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
// Fallback: Pattern-based detection
|
|
913
|
+
const text = instructions.toLowerCase();
|
|
914
|
+
// Check for L3 indicators (strictest)
|
|
915
|
+
const hasL3Indicators = text.includes('fenced block') &&
|
|
916
|
+
text.includes('section') &&
|
|
917
|
+
(text.includes('required') || text.includes('must'));
|
|
918
|
+
// Check for L2 indicators (container + sections)
|
|
919
|
+
const hasL2Indicators = (text.includes('```markdown') || text.includes('fenced block')) &&
|
|
920
|
+
(text.includes('section') || text.includes('heading'));
|
|
921
|
+
// Check for L1 indicators (sections only)
|
|
922
|
+
const hasL1Indicators = (text.includes('section') || text.includes('heading')) &&
|
|
923
|
+
text.includes('markdown');
|
|
924
|
+
// Check for L0 indicators (basic markdown)
|
|
925
|
+
const hasL0Indicators = text.includes('markdown');
|
|
926
|
+
if (hasL3Indicators)
|
|
927
|
+
return 'L3';
|
|
928
|
+
if (hasL2Indicators)
|
|
929
|
+
return 'L2';
|
|
930
|
+
if (hasL1Indicators)
|
|
931
|
+
return 'L1';
|
|
932
|
+
if (hasL0Indicators)
|
|
933
|
+
return 'L0';
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
catch (error) {
|
|
937
|
+
// On error, return null (unknown)
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
940
|
+
}
|