auwgent-yaml-lite 0.1.0

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 ADDED
@@ -0,0 +1,1144 @@
1
+ # @auwgent/yaml-lite
2
+
3
+ A **streaming YAML-Lite parser** designed for AI agents, LLM applications, and generative UI.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@auwgent/yaml-lite.svg)](https://www.npmjs.com/package/@auwgent/yaml-lite)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+
8
+ ---
9
+
10
+ ## Table of Contents
11
+
12
+ - [Why YAML-Lite?](#why-yaml-lite)
13
+ - [Installation](#installation)
14
+ - [Quick Start](#quick-start)
15
+ - [Core Concepts](#core-concepts)
16
+ - [API Reference](#api-reference)
17
+ - [parseToJSON](#parsetojson)
18
+ - [createStreamingParser](#createstreamingparser)
19
+ - [parseWithDiagnostics](#parsewithdiagnostics)
20
+ - [validate](#validate)
21
+ - [Low-Level APIs](#low-level-apis)
22
+ - [Types Reference](#types-reference)
23
+ - [The Reference System (id/ref)](#the-reference-system-idref)
24
+ - [Use Cases](#use-cases)
25
+ - [YAML-Lite Syntax](#yaml-lite-syntax)
26
+ - [Error Handling](#error-handling)
27
+ - [Best Practices](#best-practices)
28
+
29
+ ---
30
+
31
+ ## Why YAML-Lite?
32
+
33
+ **Problem:** LLMs generate structured output that needs to be parsed. JSON has issues:
34
+ - Syntax errors (missing commas, quotes) are common during generation
35
+ - No partial parsing — you wait for the entire response
36
+ - Verbose, uses more tokens
37
+
38
+ **Solution:** YAML-Lite is a restricted YAML subset designed for LLM output:
39
+
40
+ | Feature | YAML-Lite | JSON | Full YAML |
41
+ |---------|-----------|------|-----------|
42
+ | **Streaming parsing** | ✅ Yes | ❌ No | ❌ Fragile |
43
+ | **Partial documents** | ✅ Valid JSON anytime | ❌ No | ❌ No |
44
+ | **Token efficiency** | ✅ ~30% fewer tokens | ❌ Verbose | ✅ Good |
45
+ | **LLM reliability** | ✅ High | ❌ Syntax errors | ⚠️ Too complex |
46
+ | **Generative UI refs** | ✅ Built-in | ❌ No | ❌ Complex |
47
+ | **Zero dependencies** | ✅ Yes | — | ❌ No |
48
+
49
+ ---
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ # npm
55
+ npm install @auwgent/yaml-lite
56
+
57
+ # bun
58
+ bun add @auwgent/yaml-lite
59
+
60
+ # pnpm
61
+ pnpm add @auwgent/yaml-lite
62
+
63
+ # yarn
64
+ yarn add @auwgent/yaml-lite
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Quick Start
70
+
71
+ ### Basic Parsing
72
+
73
+ ```typescript
74
+ import { parseToJSON } from '@auwgent/yaml-lite';
75
+
76
+ const yaml = `
77
+ user:
78
+ name: John Doe
79
+ email: john@example.com
80
+ roles:
81
+ - admin
82
+ - editor
83
+ `;
84
+
85
+ const json = parseToJSON(yaml);
86
+ console.log(json);
87
+ // {
88
+ // user: {
89
+ // name: 'John Doe',
90
+ // email: 'john@example.com',
91
+ // roles: ['admin', 'editor']
92
+ // }
93
+ // }
94
+ ```
95
+
96
+ ### Streaming from LLM
97
+
98
+ ```typescript
99
+ import { createStreamingParser } from '@auwgent/yaml-lite';
100
+
101
+ const parser = createStreamingParser();
102
+
103
+ // Simulate LLM streaming chunks
104
+ const chunks = [
105
+ 'intent:\n',
106
+ ' type: tool_call\n',
107
+ ' name: search\n',
108
+ ' args:\n',
109
+ ' query: "weather"\n'
110
+ ];
111
+
112
+ for (const chunk of chunks) {
113
+ parser.write(chunk);
114
+
115
+ // Get valid JSON at any point!
116
+ const current = parser.peek();
117
+ console.log('Current state:', JSON.stringify(current));
118
+ }
119
+
120
+ // Output after each chunk:
121
+ // Current state: {"intent":{}}
122
+ // Current state: {"intent":{"type":"tool_call"}}
123
+ // Current state: {"intent":{"type":"tool_call","name":"search"}}
124
+ // Current state: {"intent":{"type":"tool_call","name":"search","args":{}}}
125
+ // Current state: {"intent":{"type":"tool_call","name":"search","args":{"query":"weather"}}}
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Core Concepts
131
+
132
+ ### 1. Streaming-First Design
133
+
134
+ Unlike traditional parsers that require complete input, YAML-Lite produces valid JSON at **every moment** during parsing. This enables:
135
+
136
+ - **Progressive UI rendering** — Show UI as it generates
137
+ - **Early action execution** — Start tool calls before response completes
138
+ - **Lower latency** — Don't wait for full LLM response
139
+
140
+ ### 2. Automatic Type Coercion
141
+
142
+ String values are automatically converted to appropriate types:
143
+
144
+ ```yaml
145
+ count: 42 # → number: 42
146
+ price: 19.99 # → number: 19.99
147
+ enabled: true # → boolean: true
148
+ disabled: false # → boolean: false
149
+ empty: null # → null
150
+ name: John # → string: "John"
151
+ quoted: "123" # → string: "123" (quoted = keep as string)
152
+ ```
153
+
154
+ ### 3. Empty Block Initialization
155
+
156
+ Empty blocks automatically become objects:
157
+
158
+ ```yaml
159
+ config:
160
+ database:
161
+ cache:
162
+ ```
163
+
164
+ Produces:
165
+ ```json
166
+ { "config": { "database": {}, "cache": {} } }
167
+ ```
168
+
169
+ ### 4. The Reference System
170
+
171
+ Objects with `id` are registered. Objects with `ref` are replaced:
172
+
173
+ ```yaml
174
+ components:
175
+ - id: submit_btn
176
+ type: Button
177
+ label: Submit
178
+
179
+ form:
180
+ children:
181
+ - ref: submit_btn # ← Replaced with the Button object
182
+ ```
183
+
184
+ ---
185
+
186
+ ## API Reference
187
+
188
+ ### `parseToJSON`
189
+
190
+ Parse a complete YAML-Lite string to JSON.
191
+
192
+ ```typescript
193
+ function parseToJSON(input: string, options?: ParserOptions): IRValue
194
+ ```
195
+
196
+ **Parameters:**
197
+ - `input` — The YAML-Lite string to parse
198
+ - `options` — Optional parser configuration
199
+
200
+ **Returns:** The parsed JSON value (object, array, or primitive)
201
+
202
+ **Example:**
203
+
204
+ ```typescript
205
+ import { parseToJSON } from '@auwgent/yaml-lite';
206
+
207
+ // Simple object
208
+ const obj = parseToJSON(`
209
+ name: Alice
210
+ age: 30
211
+ `);
212
+ // { name: 'Alice', age: 30 }
213
+
214
+ // Array
215
+ const arr = parseToJSON(`
216
+ items:
217
+ - apple
218
+ - banana
219
+ - cherry
220
+ `);
221
+ // { items: ['apple', 'banana', 'cherry'] }
222
+
223
+ // Nested structure
224
+ const nested = parseToJSON(`
225
+ user:
226
+ profile:
227
+ name: Bob
228
+ settings:
229
+ theme: dark
230
+ notifications: true
231
+ `);
232
+ // { user: { profile: { name: 'Bob', settings: { theme: 'dark', notifications: true } } } }
233
+ ```
234
+
235
+ **When to use:**
236
+ - Parsing complete LLM responses (non-streaming)
237
+ - Processing saved YAML files
238
+ - Converting YAML config to JSON
239
+
240
+ ---
241
+
242
+ ### `createStreamingParser`
243
+
244
+ Create a parser for incremental/streaming parsing.
245
+
246
+ ```typescript
247
+ function createStreamingParser(options?: ParserOptions): {
248
+ write: (chunk: string) => void;
249
+ peek: () => IRValue;
250
+ end: () => IRValue;
251
+ onIntentReady: (handler: (intentType: string) => void) => void;
252
+ }
253
+ ```
254
+
255
+ **Returns an object with:**
256
+
257
+ #### `write(chunk: string): void`
258
+
259
+ Feed a chunk of input to the parser. Call this as you receive data from the LLM.
260
+
261
+ ```typescript
262
+ const parser = createStreamingParser();
263
+ parser.write('name: ');
264
+ parser.write('Alice');
265
+ parser.write('\nage: 30');
266
+ ```
267
+
268
+ #### `peek(): IRValue`
269
+
270
+ Get the current parsed state **without** finalizing. Safe to call multiple times. Returns valid JSON even if the document is incomplete.
271
+
272
+ ```typescript
273
+ parser.write('name: Alice\n');
274
+ const state1 = parser.peek(); // { name: 'Alice' }
275
+
276
+ parser.write('age: 30\n');
277
+ const state2 = parser.peek(); // { name: 'Alice', age: 30 }
278
+ ```
279
+
280
+ #### `end(): IRValue`
281
+
282
+ Finalize parsing and return the complete result. Call this when the LLM response is complete.
283
+
284
+ ```typescript
285
+ parser.write('name: Alice\nage: 30');
286
+ const final = parser.end();
287
+ // { name: 'Alice', age: 30 }
288
+ ```
289
+
290
+ #### `onIntentReady(handler: (intentType: string) => void): void`
291
+
292
+ Register a callback that fires when an `intent` block's `type` field is parsed. Enables early action routing.
293
+
294
+ ```typescript
295
+ const parser = createStreamingParser();
296
+
297
+ parser.onIntentReady((type) => {
298
+ console.log('Intent detected:', type);
299
+ // "tool_call" — start preparing tool execution
300
+ });
301
+
302
+ parser.write('intent:\n');
303
+ parser.write(' type: tool_call\n'); // ← Callback fires here!
304
+ parser.write(' name: search\n');
305
+ ```
306
+
307
+ **Full streaming example:**
308
+
309
+ ```typescript
310
+ import { createStreamingParser } from '@auwgent/yaml-lite';
311
+
312
+ async function processLLMStream(stream: AsyncIterable<string>) {
313
+ const parser = createStreamingParser();
314
+
315
+ // React early to intent type
316
+ parser.onIntentReady((type) => {
317
+ if (type === 'tool_call') {
318
+ console.log('Tool call detected — preparing execution');
319
+ }
320
+ });
321
+
322
+ // Process stream
323
+ for await (const chunk of stream) {
324
+ parser.write(chunk);
325
+
326
+ // Progressive state
327
+ const current = parser.peek();
328
+ updateUI(current); // Update UI progressively
329
+ }
330
+
331
+ // Get final result
332
+ return parser.end();
333
+ }
334
+ ```
335
+
336
+ ---
337
+
338
+ ### `parseWithDiagnostics`
339
+
340
+ Parse with full diagnostics including AST, errors, and unresolved references.
341
+
342
+ ```typescript
343
+ function parseWithDiagnostics(input: string, options?: ParserOptions): {
344
+ parse: ParseResult;
345
+ ir: IRResult;
346
+ }
347
+ ```
348
+
349
+ **Returns:**
350
+ - `parse.ast` — The abstract syntax tree
351
+ - `parse.errors` — Parse-time errors
352
+ - `parse.complete` — Whether document was complete
353
+ - `ir.value` — The JSON output
354
+ - `ir.registry` — Map of all registered `id` objects
355
+ - `ir.unresolvedRefs` — List of unresolved reference names
356
+ - `ir.errors` — IR building errors
357
+
358
+ **Example:**
359
+
360
+ ```typescript
361
+ import { parseWithDiagnostics } from '@auwgent/yaml-lite';
362
+
363
+ const result = parseWithDiagnostics(`
364
+ button:
365
+ id: my_btn
366
+ label: Click me
367
+
368
+ panel:
369
+ child:
370
+ ref: my_btn
371
+ ref: unknown_ref
372
+ `);
373
+
374
+ console.log('JSON:', result.ir.value);
375
+ console.log('Registry:', [...result.ir.registry.keys()]); // ['my_btn']
376
+ console.log('Unresolved:', result.ir.unresolvedRefs); // ['unknown_ref']
377
+ ```
378
+
379
+ **When to use:**
380
+ - Debugging parse issues
381
+ - Validating LLM output quality
382
+ - Checking for unresolved references
383
+ - Building development tools
384
+
385
+ ---
386
+
387
+ ### `validate`
388
+
389
+ Validate YAML-Lite input without full parsing. Faster for validation-only use cases.
390
+
391
+ ```typescript
392
+ function validate(input: string, options?: ParserOptions): true | ParseError[]
393
+ ```
394
+
395
+ **Returns:**
396
+ - `true` if valid
397
+ - Array of `ParseError` objects if invalid
398
+
399
+ **Example:**
400
+
401
+ ```typescript
402
+ import { validate } from '@auwgent/yaml-lite';
403
+
404
+ const valid = validate(`
405
+ name: Alice
406
+ age: 30
407
+ `);
408
+ console.log(valid); // true
409
+
410
+ const invalid = validate(`
411
+ bad indentation
412
+ name: Alice
413
+ `);
414
+ console.log(invalid);
415
+ // [{ message: 'Unexpected indent...', line: 1, column: 3, severity: 'error' }]
416
+ ```
417
+
418
+ ---
419
+
420
+ ### Low-Level APIs
421
+
422
+ For advanced use cases, you can access the internal components:
423
+
424
+ #### `Tokenizer` / `tokenize`
425
+
426
+ Convert YAML-Lite string into tokens.
427
+
428
+ ```typescript
429
+ import { tokenize } from '@auwgent/yaml-lite';
430
+
431
+ const tokens = tokenize('name: Alice\nage: 30');
432
+ // [
433
+ // { type: 'KEY', value: 'name', line: 1, column: 1, indent: 0 },
434
+ // { type: 'COLON', value: ':', line: 1, column: 5, indent: 0 },
435
+ // { type: 'SCALAR', value: 'Alice', line: 1, column: 7, indent: 0 },
436
+ // { type: 'NEWLINE', value: '\n', line: 1, column: 12, indent: 0 },
437
+ // ...
438
+ // ]
439
+ ```
440
+
441
+ #### `Parser` / `parse`
442
+
443
+ Convert tokens into an AST (Abstract Syntax Tree).
444
+
445
+ ```typescript
446
+ import { parse } from '@auwgent/yaml-lite';
447
+
448
+ const result = parse('name: Alice');
449
+ console.log(result.ast);
450
+ // {
451
+ // kind: 'mapping',
452
+ // entries: [{ key: 'name', value: { kind: 'scalar', value: 'Alice', quoted: false } }]
453
+ // }
454
+ ```
455
+
456
+ #### `IRBuilder` / `buildIR`
457
+
458
+ Convert AST to JSON IR (Intermediate Representation).
459
+
460
+ ```typescript
461
+ import { parse, buildIR } from '@auwgent/yaml-lite';
462
+
463
+ const parseResult = parse('count: 42');
464
+ const ir = buildIR(parseResult.ast);
465
+ console.log(ir.value);
466
+ // { count: 42 } ← Note: '42' string became number 42
467
+ ```
468
+
469
+ ---
470
+
471
+ ## Types Reference
472
+
473
+ ### Core Output Types
474
+
475
+ ```typescript
476
+ // JSON-compatible value (what you get from parsing)
477
+ type IRValue = string | number | boolean | null | IRObject | IRArray | IRRef;
478
+
479
+ // Object output
480
+ interface IRObject {
481
+ [key: string]: IRValue;
482
+ }
483
+
484
+ // Array output
485
+ type IRArray = IRValue[];
486
+
487
+ // Unresolved reference (kept as-is if target not found)
488
+ interface IRRef {
489
+ $ref: string;
490
+ }
491
+ ```
492
+
493
+ ### Parse Results
494
+
495
+ ```typescript
496
+ interface ParseResult {
497
+ ast: ASTNode | null; // The syntax tree
498
+ errors: ParseError[]; // Any parse errors
499
+ complete: boolean; // Was document complete?
500
+ }
501
+
502
+ interface IRResult {
503
+ value: IRValue; // The JSON output
504
+ registry: Map<string, IRValue>; // id → object mapping
505
+ unresolvedRefs: string[]; // Refs that couldn't resolve
506
+ errors: IRError[]; // IR building errors
507
+ }
508
+ ```
509
+
510
+ ### Errors
511
+
512
+ ```typescript
513
+ interface ParseError {
514
+ message: string;
515
+ severity: 'error' | 'warning' | 'info';
516
+ line: number;
517
+ column: number;
518
+ context?: string; // The problematic line
519
+ }
520
+
521
+ interface IRError {
522
+ message: string;
523
+ severity: 'error' | 'warning' | 'info';
524
+ path: string[]; // Path to the error in the document
525
+ }
526
+ ```
527
+
528
+ ### Parser Options
529
+
530
+ ```typescript
531
+ interface ParserOptions {
532
+ indentSize?: number; // Spaces per indent (default: 2)
533
+ allowTabs?: boolean; // Allow tab characters (default: false)
534
+ preserveComments?: boolean; // Keep comments in output (default: false)
535
+ strict?: boolean; // Fail on any warning (default: false)
536
+ intentSchema?: IntentSchema; // Schema for intent validation
537
+ middleware?: ParserMiddleware[]; // Event hooks
538
+ }
539
+
540
+ interface IntentSchema {
541
+ requiredKeys: string[]; // Keys required for intent to be valid
542
+ knownTypes?: string[]; // List of valid intent types
543
+ }
544
+ ```
545
+
546
+ ### AST Node Types
547
+
548
+ ```typescript
549
+ // Scalar value (string, but may become number/boolean after coercion)
550
+ interface ScalarNode {
551
+ kind: 'scalar';
552
+ value: string;
553
+ quoted: boolean; // Was it "quoted" or 'quoted'?
554
+ line: number;
555
+ column: number;
556
+ }
557
+
558
+ // Mapping (object)
559
+ interface MappingNode {
560
+ kind: 'mapping';
561
+ entries: MappingEntry[];
562
+ line: number;
563
+ column: number;
564
+ }
565
+
566
+ interface MappingEntry {
567
+ key: string;
568
+ value: ASTNode;
569
+ line: number;
570
+ column: number;
571
+ }
572
+
573
+ // Sequence (array)
574
+ interface SequenceNode {
575
+ kind: 'sequence';
576
+ items: ASTNode[];
577
+ line: number;
578
+ column: number;
579
+ }
580
+
581
+ // Reference
582
+ interface RefNode {
583
+ kind: 'ref';
584
+ target: string; // The id being referenced
585
+ line: number;
586
+ column: number;
587
+ }
588
+
589
+ // Empty block
590
+ interface EmptyNode {
591
+ kind: 'empty';
592
+ hint?: 'mapping' | 'sequence';
593
+ line: number;
594
+ column: number;
595
+ }
596
+ ```
597
+
598
+ ### Tokens
599
+
600
+ ```typescript
601
+ type TokenType =
602
+ | 'KEY' // name
603
+ | 'COLON' // :
604
+ | 'DASH' // -
605
+ | 'SCALAR' // unquoted value
606
+ | 'QUOTED' // "quoted" or 'quoted'
607
+ | 'INDENT' // indentation increase
608
+ | 'DEDENT' // indentation decrease
609
+ | 'NEWLINE' // line terminator
610
+ | 'COMMENT' // # comment
611
+ | 'EOF'; // end of input
612
+
613
+ interface Token {
614
+ type: TokenType;
615
+ value: string;
616
+ line: number;
617
+ column: number;
618
+ indent: number; // Indent level (0, 1, 2, ...)
619
+ }
620
+ ```
621
+
622
+ ---
623
+
624
+ ## The Reference System (id/ref)
625
+
626
+ The reference system enables reusable, composable structures — essential for generative UI.
627
+
628
+ ### How It Works
629
+
630
+ 1. **Registration:** Any object with an `id` key is registered globally
631
+ 2. **Resolution:** Any object with only a `ref` key is replaced with the registered object
632
+ 3. **Timing:** References are resolved **after** parsing (supports forward refs)
633
+
634
+ ### Example: Reusable UI Components
635
+
636
+ ```yaml
637
+ # Define reusable components
638
+ components:
639
+ - id: primary_button
640
+ type: Button
641
+ variant: primary
642
+ size: large
643
+
644
+ - id: input_field
645
+ type: Input
646
+ style: outlined
647
+
648
+ # Use them in a form
649
+ login_form:
650
+ type: Form
651
+ action: /login
652
+ children:
653
+ - ref: input_field # Email input
654
+ - ref: input_field # Password input (same style!)
655
+ - ref: primary_button # Submit button
656
+ ```
657
+
658
+ Output:
659
+ ```json
660
+ {
661
+ "components": [
662
+ { "id": "primary_button", "type": "Button", "variant": "primary", "size": "large" },
663
+ { "id": "input_field", "type": "Input", "style": "outlined" }
664
+ ],
665
+ "login_form": {
666
+ "type": "Form",
667
+ "action": "/login",
668
+ "children": [
669
+ { "id": "input_field", "type": "Input", "style": "outlined" },
670
+ { "id": "input_field", "type": "Input", "style": "outlined" },
671
+ { "id": "primary_button", "type": "Button", "variant": "primary", "size": "large" }
672
+ ]
673
+ }
674
+ }
675
+ ```
676
+
677
+ ### Forward References
678
+
679
+ References can point to objects defined later:
680
+
681
+ ```yaml
682
+ page:
683
+ header:
684
+ ref: navbar # ← Forward reference (navbar defined below)
685
+
686
+ navbar:
687
+ id: navbar
688
+ type: Navigation
689
+ items:
690
+ - Home
691
+ - About
692
+ ```
693
+
694
+ ### Checking Unresolved References
695
+
696
+ ```typescript
697
+ const { ir } = parseWithDiagnostics(yaml);
698
+
699
+ if (ir.unresolvedRefs.length > 0) {
700
+ console.warn('Missing components:', ir.unresolvedRefs);
701
+ // Handle gracefully or show error to user
702
+ }
703
+ ```
704
+
705
+ ---
706
+
707
+ ## Use Cases
708
+
709
+ ### 1. AI Agent Tool Calling
710
+
711
+ Execute tools as soon as the intent is clear — don't wait for full response.
712
+
713
+ ```typescript
714
+ import { createStreamingParser } from '@auwgent/yaml-lite';
715
+
716
+ async function handleAgentStream(llmStream: AsyncIterable<string>) {
717
+ const parser = createStreamingParser();
718
+ let toolPromise: Promise<any> | null = null;
719
+
720
+ // Start tool execution early
721
+ parser.onIntentReady((type) => {
722
+ if (type === 'tool_call') {
723
+ // Peek at current state to get tool name
724
+ const state = parser.peek() as any;
725
+ if (state.intent?.name) {
726
+ console.log(`Starting ${state.intent.name} tool...`);
727
+ toolPromise = prepareTool(state.intent.name);
728
+ }
729
+ }
730
+ });
731
+
732
+ for await (const chunk of llmStream) {
733
+ parser.write(chunk);
734
+ }
735
+
736
+ const result = parser.end();
737
+
738
+ // Execute with full arguments
739
+ if (result.intent?.type === 'tool_call') {
740
+ await toolPromise; // Tool was already preparing!
741
+ return await executeTool(result.intent.name, result.intent.args);
742
+ }
743
+ }
744
+ ```
745
+
746
+ **Why this matters:** If the LLM takes 2 seconds to generate the full response, but the intent type appears in 200ms, you save 1.8 seconds of latency.
747
+
748
+ ### 2. Generative UI with Streaming Preview
749
+
750
+ Show UI components as they're generated.
751
+
752
+ ```typescript
753
+ import { createStreamingParser } from '@auwgent/yaml-lite';
754
+
755
+ async function streamGenerativeUI(llmStream: AsyncIterable<string>) {
756
+ const parser = createStreamingParser();
757
+
758
+ for await (const chunk of llmStream) {
759
+ parser.write(chunk);
760
+
761
+ // Render current UI state
762
+ const ui = parser.peek();
763
+ renderToDOM(ui); // Your renderer
764
+ }
765
+
766
+ // Final render
767
+ const finalUI = parser.end();
768
+ renderToDOM(finalUI);
769
+ }
770
+
771
+ function renderToDOM(ui: any) {
772
+ // Map YAML structure to React/Vue/Svelte components
773
+ if (ui.type === 'Card') {
774
+ return <Card title={ui.title}>{renderChildren(ui.children)}</Card>;
775
+ }
776
+ // ... etc
777
+ }
778
+ ```
779
+
780
+ ### 3. Structured Data Extraction
781
+
782
+ Extract structured data from LLM responses reliably.
783
+
784
+ ```typescript
785
+ import { parseToJSON } from '@auwgent/yaml-lite';
786
+
787
+ const prompt = `
788
+ Extract the following information from this text:
789
+ "John Smith, age 32, works as a software engineer at Google."
790
+
791
+ Respond in this format:
792
+ person:
793
+ name: <full name>
794
+ age: <number>
795
+ job:
796
+ title: <job title>
797
+ company: <company name>
798
+ `;
799
+
800
+ const llmResponse = `
801
+ person:
802
+ name: John Smith
803
+ age: 32
804
+ job:
805
+ title: software engineer
806
+ company: Google
807
+ `;
808
+
809
+ const data = parseToJSON(llmResponse);
810
+ // { person: { name: 'John Smith', age: 32, job: { title: 'software engineer', company: 'Google' } } }
811
+
812
+ // Type-safe access
813
+ console.log(data.person.name); // 'John Smith'
814
+ console.log(data.person.age); // 32 (number, not string!)
815
+ ```
816
+
817
+ ### 4. Multi-Step Agent Workflows
818
+
819
+ Parse complex agent workflows with multiple intents.
820
+
821
+ ```typescript
822
+ import { parseToJSON } from '@auwgent/yaml-lite';
823
+
824
+ const workflow = parseToJSON(`
825
+ workflow:
826
+ name: Research Task
827
+ steps:
828
+ - intent:
829
+ type: tool_call
830
+ name: web_search
831
+ args:
832
+ query: "latest AI news"
833
+ - intent:
834
+ type: tool_call
835
+ name: summarize
836
+ args:
837
+ max_length: 200
838
+ - intent:
839
+ type: respond
840
+ message: "Here's what I found..."
841
+ `);
842
+
843
+ // Execute steps sequentially
844
+ for (const step of workflow.steps) {
845
+ if (step.intent.type === 'tool_call') {
846
+ const result = await executeTool(step.intent.name, step.intent.args);
847
+ context.addResult(step.intent.name, result);
848
+ } else if (step.intent.type === 'respond') {
849
+ return step.intent.message;
850
+ }
851
+ }
852
+ ```
853
+
854
+ ### 5. Configuration Files
855
+
856
+ Use YAML-Lite for type-safe configuration.
857
+
858
+ ```typescript
859
+ import { parseToJSON } from '@auwgent/yaml-lite';
860
+ import { readFileSync } from 'fs';
861
+
862
+ interface AppConfig {
863
+ server: {
864
+ port: number;
865
+ host: string;
866
+ };
867
+ database: {
868
+ url: string;
869
+ poolSize: number;
870
+ };
871
+ features: string[];
872
+ }
873
+
874
+ const configYaml = readFileSync('config.yaml', 'utf-8');
875
+ const config = parseToJSON(configYaml) as AppConfig;
876
+
877
+ // Types are preservCastException
878
+ console.log(config.server.port); // number
879
+ console.log(config.features[0]); // string
880
+ ```
881
+
882
+ ---
883
+
884
+ ## YAML-Lite Syntax
885
+
886
+ ### Supported Features
887
+
888
+ ```yaml
889
+ # Comments (ignored in output)
890
+
891
+ # Mappings (objects)
892
+ object:
893
+ key1: value1
894
+ key2: value2
895
+
896
+ # Sequences (arrays)
897
+ list:
898
+ - item1
899
+ - item2
900
+ - item3
901
+
902
+ # Nested structures
903
+ parent:
904
+ child:
905
+ grandchild: value
906
+
907
+ # Scalars with type coercion
908
+ string: hello world
909
+ number: 42
910
+ float: 3.14
911
+ boolean_true: true
912
+ boolean_false: false
913
+ null_value: null
914
+ quoted_string: "keeps as string"
915
+ quoted_number: "123"
916
+
917
+ # Inline JSON arrays
918
+ tags: ["red", "green", "blue"]
919
+ numbers: [1, 2, 3, 4, 5]
920
+
921
+ # Empty blocks (become {})
922
+ config:
923
+ section:
924
+
925
+ # References
926
+ templates:
927
+ - id: my_template
928
+ content: Hello
929
+
930
+ usage:
931
+ item:
932
+ ref: my_template
933
+ ```
934
+
935
+ ### Not Supported (By Design)
936
+
937
+ These YAML features are **intentionally not supported** to ensure reliability:
938
+
939
+ ```yaml
940
+ # ❌ Anchors and aliases
941
+ defaults: &defaults
942
+ timeout: 30
943
+ production:
944
+ <<: *defaults
945
+
946
+ # ❌ Flow style
947
+ object: { key: value }
948
+ array: [1, 2, 3] # ← Actually this IS supported for inline arrays
949
+
950
+ # ❌ Tags
951
+ date: !!timestamp 2024-01-01
952
+
953
+ # ❌ Multiple documents
954
+ ---
955
+ doc1: true
956
+ ---
957
+ doc2: true
958
+ ```
959
+
960
+ **Why?** LLMs produce more reliable output with simpler syntax. The unsupported features add complexity without benefit for LLM use cases.
961
+
962
+ ---
963
+
964
+ ## Error Handling
965
+
966
+ ### Parse Errors
967
+
968
+ ```typescript
969
+ import { parseWithDiagnostics, validate } from '@auwgent/yaml-lite';
970
+
971
+ const input = `
972
+ invalid: indentation
973
+ name: value
974
+ `;
975
+
976
+ // Option 1: validate only
977
+ const validation = validate(input);
978
+ if (validation !== true) {
979
+ console.error('Validation failed:', validation);
980
+ // [{ message: 'Unexpected indent at start...', line: 2, severity: 'error' }]
981
+ }
982
+
983
+ // Option 2: parse with diagnostics
984
+ const { parse, ir } = parseWithDiagnostics(input);
985
+ if (parse.errors.length > 0) {
986
+ console.error('Parse errors:', parse.errors);
987
+ }
988
+ ```
989
+
990
+ ### Handling Incomplete Documents
991
+
992
+ Streaming documents may be incomplete. The parser handles this gracefully:
993
+
994
+ ```typescript
995
+ const parser = createStreamingParser();
996
+ parser.write('partial:\n incomplete:');
997
+
998
+ const state = parser.peek();
999
+ console.log(state); // { partial: { incomplete: {} } }
1000
+
1001
+ // Document is usable even when incomplete!
1002
+ ```
1003
+
1004
+ ### Unresolved References
1005
+
1006
+ ```typescript
1007
+ const { ir } = parseWithDiagnostics(`
1008
+ panel:
1009
+ child:
1010
+ ref: nonexistent
1011
+ `);
1012
+
1013
+ if (ir.unresolvedRefs.length > 0) {
1014
+ console.warn('Unresolved:', ir.unresolvedRefs);
1015
+ // ['nonexistent']
1016
+ }
1017
+
1018
+ // The ref is kept as { $ref: 'nonexistent' } in output
1019
+ ```
1020
+
1021
+ ### Strict Mode
1022
+
1023
+ Enable strict mode to fail on any warning:
1024
+
1025
+ ```typescript
1026
+ import { parseToJSON } from '@auwgent/yaml-lite';
1027
+
1028
+ try {
1029
+ parseToJSON(input, { strict: true });
1030
+ } catch (error) {
1031
+ console.error('Strict validation failed:', error);
1032
+ }
1033
+ ```
1034
+
1035
+ ---
1036
+
1037
+ ## Best Practices
1038
+
1039
+ ### 1. Use Streaming for LLM Output
1040
+
1041
+ Always use `createStreamingParser()` when parsing LLM output:
1042
+
1043
+ ```typescript
1044
+ // ✅ Good
1045
+ const parser = createStreamingParser();
1046
+ for await (const chunk of llmStream) {
1047
+ parser.write(chunk);
1048
+ updateUI(parser.peek());
1049
+ }
1050
+
1051
+ // ❌ Avoid
1052
+ let fullText = '';
1053
+ for await (const chunk of llmStream) {
1054
+ fullText += chunk;
1055
+ }
1056
+ parseToJSON(fullText); // No progressive updates!
1057
+ ```
1058
+
1059
+ ### 2. Handle LLM Noise
1060
+
1061
+ LLMs often add conversational text or code fences. Use `extractYAML` to clean it:
1062
+
1063
+ ```typescript
1064
+ import { extractYAML, parseToJSON } from '@auwgent/yaml-lite';
1065
+
1066
+ const llmOutput = `Sure! Here's the YAML:
1067
+ \`\`\`yaml
1068
+ text: Hello
1069
+ tool_call:
1070
+ name: search
1071
+ \`\`\`
1072
+ Let me know if you need changes!`;
1073
+
1074
+ const cleanYaml = extractYAML(llmOutput);
1075
+ const json = parseToJSON(cleanYaml);
1076
+ ```
1077
+
1078
+ ### 3. Handle Partial State
1079
+
1080
+ During streaming, state may be incomplete:
1081
+
1082
+ ```typescript
1083
+ const state = parser.peek();
1084
+
1085
+ // Safe access
1086
+ if (state?.intent?.name) {
1087
+ executeTool(state.intent.name, state.intent.args || {});
1088
+ }
1089
+ ```
1090
+
1091
+ ### 4. Use TypeScript
1092
+
1093
+ Define types for your expected structures:
1094
+
1095
+ ```typescript
1096
+ interface ToolCallIntent {
1097
+ type: 'tool_call';
1098
+ name: string;
1099
+ args: Record<string, unknown>;
1100
+ }
1101
+
1102
+ interface RespondIntent {
1103
+ type: 'respond';
1104
+ message: string;
1105
+ }
1106
+
1107
+ type Intent = ToolCallIntent | RespondIntent;
1108
+
1109
+ interface AgentOutput {
1110
+ intent: Intent;
1111
+ }
1112
+
1113
+ const output = parser.end() as AgentOutput;
1114
+ ```
1115
+
1116
+ ### 5. Quote Strings That Look Like Other Types
1117
+
1118
+ If a string should stay a string, quote it:
1119
+
1120
+ ```yaml
1121
+ # ❌ Will become boolean
1122
+ flag: true
1123
+
1124
+ # ✅ Stays string
1125
+ flag: "true"
1126
+
1127
+ # ❌ Will become number
1128
+ code: 12345
1129
+
1130
+ # ✅ Stays string
1131
+ code: "12345"
1132
+ ```
1133
+
1134
+ ---
1135
+
1136
+ ## License
1137
+
1138
+ MIT License - see [LICENSE](LICENSE) for details.
1139
+
1140
+ ---
1141
+
1142
+ ## Contributing
1143
+
1144
+ Contributions welcome! Please read our contributing guidelines before submitting PRs.