$npx -y skills add vercel-labs/json-render --skill yamlYAML wire format for json-render with streaming parser, prompt generation, and AI SDK transform. Use when working with @json-render/yaml, YAML-based spec streaming, yaml-spec/yaml-edit fences, or YAML prompt generation.
| 1 | # @json-render/yaml |
| 2 | |
| 3 | YAML wire format for `@json-render/core`. Progressive rendering and surgical edits via streaming YAML. |
| 4 | |
| 5 | ## Key Concepts |
| 6 | |
| 7 | - **YAML wire format**: Alternative to JSONL that uses code fences (`yaml-spec`, `yaml-edit`, `yaml-patch`, `diff`) |
| 8 | - **Streaming parser**: Incrementally parses YAML, emits JSON Patch operations via diffing |
| 9 | - **Edit modes**: Patch (RFC 6902), merge (RFC 7396), and unified diff |
| 10 | - **AI SDK transform**: `TransformStream` that converts YAML fences into json-render patches |
| 11 | |
| 12 | ## Generating YAML Prompts |
| 13 | |
| 14 | ```typescript |
| 15 | import { yamlPrompt } from "@json-render/yaml"; |
| 16 | import { catalog } from "./catalog"; |
| 17 | |
| 18 | // Standalone mode (LLM outputs only YAML) |
| 19 | const systemPrompt = yamlPrompt(catalog, { |
| 20 | mode: "standalone", |
| 21 | editModes: ["merge"], |
| 22 | customRules: ["Always use dark theme"], |
| 23 | }); |
| 24 | |
| 25 | // Inline mode (LLM responds conversationally, wraps YAML in fences) |
| 26 | const chatPrompt = yamlPrompt(catalog, { mode: "inline" }); |
| 27 | ``` |
| 28 | |
| 29 | Options: |
| 30 | |
| 31 | - `system` (string) — Custom system message intro |
| 32 | - `mode` ("standalone" | "inline") — Output mode, default "standalone" |
| 33 | - `customRules` (string[]) — Additional rules appended to prompt |
| 34 | - `editModes` (EditMode[]) — Edit modes to document, default ["merge"] |
| 35 | |
| 36 | ## AI SDK Transform |
| 37 | |
| 38 | Use `pipeYamlRender` as a drop-in replacement for `pipeJsonRender`: |
| 39 | |
| 40 | ```typescript |
| 41 | import { pipeYamlRender } from "@json-render/yaml"; |
| 42 | import { createUIMessageStream, createUIMessageStreamResponse } from "ai"; |
| 43 | |
| 44 | const stream = createUIMessageStream({ |
| 45 | execute: async ({ writer }) => { |
| 46 | writer.merge(pipeYamlRender(result.toUIMessageStream())); |
| 47 | }, |
| 48 | }); |
| 49 | return createUIMessageStreamResponse({ stream }); |
| 50 | ``` |
| 51 | |
| 52 | For multi-turn edits, pass the previous spec: |
| 53 | |
| 54 | ```typescript |
| 55 | pipeYamlRender(result.toUIMessageStream(), { |
| 56 | previousSpec: currentSpec, |
| 57 | }); |
| 58 | ``` |
| 59 | |
| 60 | The transform recognizes four fence types: |
| 61 | |
| 62 | - `yaml-spec` — Full spec, parsed progressively line-by-line |
| 63 | - `yaml-edit` — Partial YAML deep-merged with current spec (RFC 7396) |
| 64 | - `yaml-patch` — RFC 6902 JSON Patch lines |
| 65 | - `diff` — Unified diff applied to serialized spec |
| 66 | |
| 67 | ## Streaming Parser (Low-Level) |
| 68 | |
| 69 | ```typescript |
| 70 | import { createYamlStreamCompiler } from "@json-render/yaml"; |
| 71 | |
| 72 | const compiler = createYamlStreamCompiler<Spec>(); |
| 73 | |
| 74 | // Feed chunks as they arrive from any source |
| 75 | const { result, newPatches } = compiler.push("root: main\n"); |
| 76 | compiler.push("elements:\n main:\n type: Card\n"); |
| 77 | |
| 78 | // Flush remaining data at end of stream |
| 79 | const { result: final } = compiler.flush(); |
| 80 | |
| 81 | // Reset for next stream (optionally with initial state) |
| 82 | compiler.reset({ root: "main", elements: {} }); |
| 83 | ``` |
| 84 | |
| 85 | Methods: `push(chunk)`, `flush()`, `getResult()`, `getPatches()`, `reset(initial?)` |
| 86 | |
| 87 | ## Edit Modes (from @json-render/core) |
| 88 | |
| 89 | The YAML package uses the universal edit mode system from core: |
| 90 | |
| 91 | ```typescript |
| 92 | import { buildEditInstructions, buildEditUserPrompt } from "@json-render/core"; |
| 93 | import type { EditMode } from "@json-render/core"; |
| 94 | |
| 95 | // Generate edit instructions for YAML format |
| 96 | const instructions = buildEditInstructions({ modes: ["merge", "patch"] }, "yaml"); |
| 97 | |
| 98 | // Build user prompt with current spec context |
| 99 | const userPrompt = buildEditUserPrompt({ |
| 100 | prompt: "Change the title to Dashboard", |
| 101 | currentSpec: spec, |
| 102 | config: { modes: ["merge"] }, |
| 103 | format: "yaml", |
| 104 | serializer: (s) => yamlStringify(s, { indent: 2 }).trimEnd(), |
| 105 | }); |
| 106 | ``` |
| 107 | |
| 108 | ## Fence Constants |
| 109 | |
| 110 | For custom parsing, use the exported constants: |
| 111 | |
| 112 | ```typescript |
| 113 | import { |
| 114 | YAML_SPEC_FENCE, // "```yaml-spec" |
| 115 | YAML_EDIT_FENCE, // "```yaml-edit" |
| 116 | YAML_PATCH_FENCE, // "```yaml-patch" |
| 117 | DIFF_FENCE, // "```diff" |
| 118 | FENCE_CLOSE, // "```" |
| 119 | } from "@json-render/yaml"; |
| 120 | ``` |
| 121 | |
| 122 | ## Key Exports |
| 123 | |
| 124 | | Export | Description | |
| 125 | |--------|-------------| |
| 126 | | `yamlPrompt` | Generate YAML system prompt from catalog | |
| 127 | | `createYamlTransform` | AI SDK TransformStream for YAML fences | |
| 128 | | `pipeYamlRender` | Convenience pipe wrapper (replaces `pipeJsonRender`) | |
| 129 | | `createYamlStreamCompiler` | Streaming YAML parser with patch emission | |
| 130 | | `YAML_SPEC_FENCE` | Fence constant for yaml-spec | |
| 131 | | `YAML_EDIT_FENCE` | Fence constant for yaml-edit | |
| 132 | | `YAML_PATCH_FENCE` | Fence constant for yaml-patch | |
| 133 | | `DIFF_FENCE` | Fence constant for diff | |
| 134 | | `FENCE_CLOSE` | Fence close constant | |
| 135 | | `diffToPatches` | Re-export: object diff to JSON Patch | |
| 136 | | `deepMergeSpec` | Re-export: RFC 7396 deep merge | |