cognitive-modules-cli 2.2.10 → 2.2.12

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.
@@ -0,0 +1,7 @@
1
+ export type JsonExtractStrategy = 'as_is' | 'code_fence' | 'balanced_first' | 'balanced_last';
2
+ export interface JsonExtractResult {
3
+ json: string;
4
+ strategy: JsonExtractStrategy;
5
+ }
6
+ export declare function extractJsonCandidates(text: string): JsonExtractResult[];
7
+ export declare function extractJsonCandidate(text: string): JsonExtractResult | null;
@@ -0,0 +1,132 @@
1
+ export function extractJsonCandidates(text) {
2
+ const raw = String(text ?? '');
3
+ if (!raw.trim())
4
+ return [];
5
+ const out = [];
6
+ const trimmed = raw.trim();
7
+ const trimmedLooksJson = looksLikeJson(trimmed);
8
+ // Candidate 1: treat the entire output as JSON only if it already looks like JSON.
9
+ if (trimmedLooksJson)
10
+ out.push({ json: trimmed, strategy: 'as_is' });
11
+ // Candidate 2: markdown code fences
12
+ const fence = extractFromCodeFence(raw);
13
+ if (fence)
14
+ out.push(fence);
15
+ // Candidate 3/4: balanced JSON values from the text
16
+ const first = extractBalancedJson(raw, 'first');
17
+ if (first)
18
+ out.push({ json: first, strategy: 'balanced_first' });
19
+ const last = extractBalancedJson(raw, 'last');
20
+ if (last)
21
+ out.push({ json: last, strategy: 'balanced_last' });
22
+ // Candidate last: as-is, even if it doesn't look like JSON.
23
+ // This is useful for providers that return a single JSON value with leading/trailing whitespace.
24
+ if (!trimmedLooksJson)
25
+ out.push({ json: trimmed, strategy: 'as_is' });
26
+ // Deduplicate by json string to keep parse attempts small.
27
+ const seen = new Set();
28
+ return out.filter((c) => {
29
+ if (!c.json)
30
+ return false;
31
+ if (seen.has(c.json))
32
+ return false;
33
+ seen.add(c.json);
34
+ return true;
35
+ });
36
+ }
37
+ export function extractJsonCandidate(text) {
38
+ const raw = String(text ?? '');
39
+ if (!raw.trim())
40
+ return null;
41
+ // 1) If it's already JSON, prefer that.
42
+ const trimmed = raw.trim();
43
+ if (looksLikeJson(trimmed))
44
+ return { json: trimmed, strategy: 'as_is' };
45
+ // 2) Markdown code fence ```json ... ```
46
+ const fence = extractFromCodeFence(raw);
47
+ if (fence)
48
+ return fence;
49
+ // 3) Best-effort: find a balanced JSON value in the text.
50
+ const first = extractBalancedJson(raw, 'first');
51
+ if (first)
52
+ return { json: first, strategy: 'balanced_first' };
53
+ const last = extractBalancedJson(raw, 'last');
54
+ if (last)
55
+ return { json: last, strategy: 'balanced_last' };
56
+ return null;
57
+ }
58
+ function looksLikeJson(s) {
59
+ const t = s.trim();
60
+ return (t.startsWith('{') && t.endsWith('}')) || (t.startsWith('[') && t.endsWith(']'));
61
+ }
62
+ function extractFromCodeFence(raw) {
63
+ // Non-greedy capture; allow "```json" or plain "```".
64
+ const m = raw.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
65
+ if (!m)
66
+ return null;
67
+ const json = (m[1] ?? '').trim();
68
+ if (!json)
69
+ return null;
70
+ return { json, strategy: 'code_fence' };
71
+ }
72
+ function extractBalancedJson(raw, mode) {
73
+ // Cap scanning to avoid pathological memory usage on giant outputs.
74
+ const maxScan = 256_000;
75
+ const s = raw.length > maxScan ? raw.slice(0, maxScan) : raw;
76
+ const starts = [];
77
+ for (let i = 0; i < s.length; i++) {
78
+ const ch = s[i];
79
+ if (ch === '{' || ch === '[')
80
+ starts.push(i);
81
+ }
82
+ if (!starts.length)
83
+ return null;
84
+ const order = mode === 'first' ? starts : [...starts].reverse();
85
+ for (const start of order) {
86
+ const end = findBalancedEnd(s, start);
87
+ if (end === -1)
88
+ continue;
89
+ const candidate = s.slice(start, end + 1).trim();
90
+ if (candidate)
91
+ return candidate;
92
+ }
93
+ return null;
94
+ }
95
+ function findBalancedEnd(s, start) {
96
+ const open = s[start];
97
+ const close = open === '{' ? '}' : open === '[' ? ']' : '';
98
+ if (!close)
99
+ return -1;
100
+ let depth = 0;
101
+ let inString = false;
102
+ let escape = false;
103
+ for (let i = start; i < s.length; i++) {
104
+ const ch = s[i];
105
+ if (inString) {
106
+ if (escape) {
107
+ escape = false;
108
+ continue;
109
+ }
110
+ if (ch === '\\\\') {
111
+ escape = true;
112
+ continue;
113
+ }
114
+ if (ch === '"') {
115
+ inString = false;
116
+ }
117
+ continue;
118
+ }
119
+ if (ch === '"') {
120
+ inString = true;
121
+ continue;
122
+ }
123
+ if (ch === open)
124
+ depth++;
125
+ if (ch === close) {
126
+ depth--;
127
+ if (depth === 0)
128
+ return i;
129
+ }
130
+ }
131
+ return -1;
132
+ }
@@ -3,7 +3,7 @@
3
3
  * v2.2: Envelope format with meta/data separation, risk_rule, repair pass
4
4
  * v2.2.1: Version field, enhanced error taxonomy, observability hooks, streaming
5
5
  */
6
- import type { Provider, CognitiveModule, ModuleResult, ModuleInput, EnvelopeResponseV22, EnvelopeMeta, RiskLevel, ExecutionPolicy } from '../types.js';
6
+ import type { Provider, StructuredOutputPreference, CognitiveModule, ModuleResult, ModuleInput, EnvelopeResponseV22, EnvelopeMeta, RiskLevel, ExecutionPolicy } from '../types.js';
7
7
  /**
8
8
  * Validate data against JSON schema. Returns list of errors.
9
9
  */
@@ -367,6 +367,11 @@ export interface RunOptions {
367
367
  traceId?: string;
368
368
  model?: string;
369
369
  policy?: ExecutionPolicy;
370
+ /**
371
+ * Structured output preference override (CLI/tests).
372
+ * If omitted, uses policy.structured (or auto).
373
+ */
374
+ structured?: StructuredOutputPreference;
370
375
  }
371
376
  export declare function runModule(module: CognitiveModule, provider: Provider, options?: RunOptions): Promise<ModuleResult>;
372
377
  /** Event types emitted during streaming execution */
@@ -396,6 +401,11 @@ export interface StreamOptions {
396
401
  traceId?: string;
397
402
  model?: string;
398
403
  policy?: ExecutionPolicy;
404
+ /**
405
+ * Structured output preference override (CLI/tests).
406
+ * If omitted, uses policy.structured (or auto).
407
+ */
408
+ structured?: StructuredOutputPreference;
399
409
  }
400
410
  /**
401
411
  * Run a cognitive module with streaming output.