@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,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
+ }