markdown-flow 0.2.2__tar.gz → 1.0.0__tar.gz
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.
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/PKG-INFO +5 -4
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/README.md +4 -3
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/markdown_flow/constants.py +7 -5
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/markdown_flow/core.py +1 -1
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/markdown_flow/enums.py +1 -1
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/markdown_flow/utils.py +91 -94
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/markdown_flow.egg-info/PKG-INFO +5 -4
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/pyproject.toml +1 -1
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/LICENSE +0 -0
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/markdown_flow/__init__.py +0 -0
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/markdown_flow/exceptions.py +0 -0
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/markdown_flow/llm.py +0 -0
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/markdown_flow/models.py +0 -0
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/markdown_flow.egg-info/SOURCES.txt +0 -0
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/markdown_flow.egg-info/dependency_links.txt +0 -0
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/markdown_flow.egg-info/top_level.txt +0 -0
- {markdown_flow-0.2.2 → markdown_flow-1.0.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: markdown-flow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: An agent library designed to parse and process MarkdownFlow documents
|
|
5
5
|
Project-URL: Homepage, https://github.com/ai-shifu/markdown-flow-agent-py
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/ai-shifu/markdown-flow-agent-py/issues
|
|
@@ -241,7 +241,7 @@ Enumeration of different block types in MarkdownFlow documents.
|
|
|
241
241
|
class BlockType(Enum):
|
|
242
242
|
CONTENT = "content" # Regular markdown content
|
|
243
243
|
INTERACTION = "interaction" # User interaction blocks (?[...])
|
|
244
|
-
PRESERVED_CONTENT = "preserved_content" # Content wrapped in
|
|
244
|
+
PRESERVED_CONTENT = "preserved_content" # Content wrapped in !=== markers (inline or multiline)
|
|
245
245
|
```
|
|
246
246
|
|
|
247
247
|
**Block Structure:**
|
|
@@ -259,10 +259,11 @@ Hello {{name}}! Welcome to our platform.
|
|
|
259
259
|
|
|
260
260
|
# Preserved content - output as-is
|
|
261
261
|
"""
|
|
262
|
-
|
|
262
|
+
# Multiline fence with leading '!'
|
|
263
|
+
!===
|
|
263
264
|
This content is preserved exactly as written.
|
|
264
265
|
No LLM processing or variable replacement.
|
|
265
|
-
|
|
266
|
+
!===
|
|
266
267
|
"""
|
|
267
268
|
```
|
|
268
269
|
|
|
@@ -223,7 +223,7 @@ Enumeration of different block types in MarkdownFlow documents.
|
|
|
223
223
|
class BlockType(Enum):
|
|
224
224
|
CONTENT = "content" # Regular markdown content
|
|
225
225
|
INTERACTION = "interaction" # User interaction blocks (?[...])
|
|
226
|
-
PRESERVED_CONTENT = "preserved_content" # Content wrapped in
|
|
226
|
+
PRESERVED_CONTENT = "preserved_content" # Content wrapped in !=== markers (inline or multiline)
|
|
227
227
|
```
|
|
228
228
|
|
|
229
229
|
**Block Structure:**
|
|
@@ -241,10 +241,11 @@ Hello {{name}}! Welcome to our platform.
|
|
|
241
241
|
|
|
242
242
|
# Preserved content - output as-is
|
|
243
243
|
"""
|
|
244
|
-
|
|
244
|
+
# Multiline fence with leading '!'
|
|
245
|
+
!===
|
|
245
246
|
This content is preserved exactly as written.
|
|
246
247
|
No LLM processing or variable replacement.
|
|
247
|
-
|
|
248
|
+
!===
|
|
248
249
|
"""
|
|
249
250
|
```
|
|
250
251
|
|
|
@@ -21,23 +21,25 @@ INTERACTION_PATTERN_SPLIT = r"((?<!\\)\?\[[^\]]*\](?!\())" # Pattern for re.spl
|
|
|
21
21
|
COMPILED_INTERACTION_REGEX = re.compile(INTERACTION_PATTERN) # Main interaction pattern matcher
|
|
22
22
|
COMPILED_LAYER1_INTERACTION_REGEX = COMPILED_INTERACTION_REGEX # Layer 1: Basic format validation (alias)
|
|
23
23
|
COMPILED_LAYER2_VARIABLE_REGEX = re.compile(r"^%\{\{([^}]+)\}\}(.*)$") # Layer 2: Variable detection
|
|
24
|
-
COMPILED_LAYER3_ELLIPSIS_REGEX = re.compile(r"^(
|
|
25
|
-
COMPILED_LAYER3_BUTTON_VALUE_REGEX = re.compile(r"^(
|
|
24
|
+
COMPILED_LAYER3_ELLIPSIS_REGEX = re.compile(r"^(.*)\.\.\.(.*)") # Layer 3: Split content around ellipsis
|
|
25
|
+
COMPILED_LAYER3_BUTTON_VALUE_REGEX = re.compile(r"^(.+)//(.+)$") # Layer 3: Parse Button//value format
|
|
26
26
|
COMPILED_BRACE_VARIABLE_REGEX = re.compile(
|
|
27
27
|
r"(?<!%)\{\{([^}]+)\}\}" # Match {{variable}} format for replaceable variables
|
|
28
28
|
)
|
|
29
29
|
COMPILED_INTERACTION_CONTENT_RECONSTRUCT_REGEX = re.compile(
|
|
30
|
-
r"(\?\[
|
|
30
|
+
r"(\?\[[^]]*\.\.\.)([^]]*\])" # Reconstruct interaction content: prefix + question + suffix
|
|
31
31
|
)
|
|
32
32
|
COMPILED_BRACKETS_CLEANUP_REGEX = re.compile(r"[\[\]()]")
|
|
33
|
-
COMPILED_VARIABLE_REFERENCE_CLEANUP_REGEX = re.compile(r"%\{\{
|
|
33
|
+
COMPILED_VARIABLE_REFERENCE_CLEANUP_REGEX = re.compile(r"%\{\{[^}]*\}\}")
|
|
34
34
|
COMPILED_WHITESPACE_CLEANUP_REGEX = re.compile(r"\s+")
|
|
35
35
|
|
|
36
36
|
# Document parsing constants (using shared INTERACTION_PATTERN defined above)
|
|
37
37
|
|
|
38
38
|
# Separators
|
|
39
39
|
BLOCK_SEPARATOR = r"\n\s*---\s*\n"
|
|
40
|
-
|
|
40
|
+
# Multiline preserved block fence: starts with '!' followed by 3 or more '='
|
|
41
|
+
PRESERVE_FENCE_PATTERN = r"^!={3,}\s*$"
|
|
42
|
+
COMPILED_PRESERVE_FENCE_REGEX = re.compile(PRESERVE_FENCE_PATTERN)
|
|
41
43
|
|
|
42
44
|
# Output instruction markers
|
|
43
45
|
OUTPUT_INSTRUCTION_PREFIX = "[输出]"
|
|
@@ -257,7 +257,7 @@ class MarkdownFlow:
|
|
|
257
257
|
"""Process preserved content block, output as-is without LLM call."""
|
|
258
258
|
block = self.get_block(block_index)
|
|
259
259
|
|
|
260
|
-
# Extract preserved content (remove
|
|
260
|
+
# Extract preserved content (remove !=== markers)
|
|
261
261
|
content = extract_preserved_content(block.content)
|
|
262
262
|
|
|
263
263
|
# Replace variables
|
|
@@ -27,4 +27,4 @@ class BlockType(Enum):
|
|
|
27
27
|
|
|
28
28
|
CONTENT = "content" # Regular document content blocks
|
|
29
29
|
INTERACTION = "interaction" # Interactive blocks requiring user input
|
|
30
|
-
PRESERVED_CONTENT = "preserved_content" # Special
|
|
30
|
+
PRESERVED_CONTENT = "preserved_content" # Special blocks: inline !===content!=== or multiline !===...!===
|
|
@@ -17,6 +17,7 @@ from .constants import (
|
|
|
17
17
|
COMPILED_LAYER3_BUTTON_VALUE_REGEX,
|
|
18
18
|
COMPILED_LAYER3_ELLIPSIS_REGEX,
|
|
19
19
|
COMPILED_PERCENT_VARIABLE_REGEX,
|
|
20
|
+
COMPILED_PRESERVE_FENCE_REGEX,
|
|
20
21
|
CONTEXT_CONVERSATION_TEMPLATE,
|
|
21
22
|
CONTEXT_QUESTION_MARKER,
|
|
22
23
|
CONTEXT_QUESTION_TEMPLATE,
|
|
@@ -25,7 +26,6 @@ from .constants import (
|
|
|
25
26
|
OUTPUT_INSTRUCTION_PREFIX,
|
|
26
27
|
OUTPUT_INSTRUCTION_SUFFIX,
|
|
27
28
|
SMART_VALIDATION_TEMPLATE,
|
|
28
|
-
TRIPLE_EQUALS_DELIMITER,
|
|
29
29
|
VALIDATION_ILLEGAL_DEFAULT_REASON,
|
|
30
30
|
VALIDATION_RESPONSE_ILLEGAL,
|
|
31
31
|
VALIDATION_RESPONSE_OK,
|
|
@@ -66,14 +66,14 @@ def is_preserved_content_block(content: str) -> bool:
|
|
|
66
66
|
"""
|
|
67
67
|
Check if content is completely preserved content block.
|
|
68
68
|
|
|
69
|
-
Preserved blocks are entirely wrapped by
|
|
70
|
-
Supports inline (
|
|
69
|
+
Preserved blocks are entirely wrapped by markers with no external content.
|
|
70
|
+
Supports inline (!===content!===) and multiline (!=== ... !===) formats.
|
|
71
71
|
|
|
72
72
|
Args:
|
|
73
73
|
content: Content to check
|
|
74
74
|
|
|
75
75
|
Returns:
|
|
76
|
-
True if content is fully wrapped by
|
|
76
|
+
True if content is fully wrapped by preserved markers
|
|
77
77
|
"""
|
|
78
78
|
content = content.strip()
|
|
79
79
|
if not content:
|
|
@@ -81,7 +81,7 @@ def is_preserved_content_block(content: str) -> bool:
|
|
|
81
81
|
|
|
82
82
|
lines = content.split("\n")
|
|
83
83
|
|
|
84
|
-
# Check if all non-empty lines are inline format
|
|
84
|
+
# Check if all non-empty lines are inline format (!===content!===)
|
|
85
85
|
all_inline_format = True
|
|
86
86
|
has_any_content = False
|
|
87
87
|
|
|
@@ -89,22 +89,22 @@ def is_preserved_content_block(content: str) -> bool:
|
|
|
89
89
|
stripped_line = line.strip()
|
|
90
90
|
if stripped_line: # Non-empty line
|
|
91
91
|
has_any_content = True
|
|
92
|
-
# Check if inline format
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if not inner_content or "=" in inner_content:
|
|
92
|
+
# Check if inline format: !===content!===
|
|
93
|
+
match = re.match(r"^!===(.+)!=== *$", stripped_line)
|
|
94
|
+
if match:
|
|
95
|
+
# Ensure inner content exists and contains no !===
|
|
96
|
+
inner_content = match.group(1).strip()
|
|
97
|
+
if not inner_content or "!==" in inner_content:
|
|
98
|
+
all_inline_format = False
|
|
99
|
+
break
|
|
100
|
+
else:
|
|
102
101
|
all_inline_format = False # type: ignore[unreachable]
|
|
103
102
|
break
|
|
104
103
|
|
|
105
104
|
# If all lines are inline format, return directly
|
|
106
105
|
if has_any_content and all_inline_format:
|
|
107
106
|
return True
|
|
107
|
+
|
|
108
108
|
# Check multiline format using state machine
|
|
109
109
|
state = "OUTSIDE" # States: OUTSIDE, INSIDE
|
|
110
110
|
has_content_outside = False # Has external content
|
|
@@ -113,7 +113,7 @@ def is_preserved_content_block(content: str) -> bool:
|
|
|
113
113
|
for line in lines:
|
|
114
114
|
stripped_line = line.strip()
|
|
115
115
|
|
|
116
|
-
if stripped_line
|
|
116
|
+
if COMPILED_PRESERVE_FENCE_REGEX.match(stripped_line):
|
|
117
117
|
if state == "OUTSIDE":
|
|
118
118
|
# Enter preserve block
|
|
119
119
|
state = "INSIDE"
|
|
@@ -121,13 +121,14 @@ def is_preserved_content_block(content: str) -> bool:
|
|
|
121
121
|
elif state == "INSIDE":
|
|
122
122
|
# Exit preserve block
|
|
123
123
|
state = "OUTSIDE"
|
|
124
|
-
#
|
|
124
|
+
# !=== lines don't count as external content
|
|
125
125
|
else:
|
|
126
|
-
# Non
|
|
127
|
-
if stripped_line: # Non-empty line
|
|
126
|
+
# Non-!=== lines
|
|
127
|
+
if stripped_line: # type: ignore[unreachable] # Non-empty line
|
|
128
128
|
if state == "OUTSIDE":
|
|
129
129
|
# External content found
|
|
130
130
|
has_content_outside = True
|
|
131
|
+
break
|
|
131
132
|
# Internal content doesn't affect judgment
|
|
132
133
|
|
|
133
134
|
# Judgment conditions:
|
|
@@ -204,6 +205,7 @@ class InteractionParser:
|
|
|
204
205
|
|
|
205
206
|
# Layer 3: Specific content parsing
|
|
206
207
|
if has_variable:
|
|
208
|
+
assert variable_name is not None, "variable_name should not be None when has_variable is True"
|
|
207
209
|
return self._layer3_parse_variable_interaction(variable_name, remaining_content)
|
|
208
210
|
return self._layer3_parse_display_buttons(inner_content)
|
|
209
211
|
|
|
@@ -485,15 +487,15 @@ def parse_json_response(response_text: str) -> dict[str, Any]:
|
|
|
485
487
|
|
|
486
488
|
def process_output_instructions(content: str) -> str:
|
|
487
489
|
"""
|
|
488
|
-
Process output instruction markers, converting
|
|
490
|
+
Process output instruction markers, converting !=== format to [output] format.
|
|
489
491
|
|
|
490
|
-
Uses unified state machine to handle inline (
|
|
492
|
+
Uses unified state machine to handle inline (!===content!===) and multiline (!===...!===) formats.
|
|
491
493
|
|
|
492
494
|
Args:
|
|
493
495
|
content: Raw content containing output instructions
|
|
494
496
|
|
|
495
497
|
Returns:
|
|
496
|
-
Processed content with
|
|
498
|
+
Processed content with !=== markers converted to [output] format
|
|
497
499
|
"""
|
|
498
500
|
lines = content.split("\n")
|
|
499
501
|
result_lines = []
|
|
@@ -503,72 +505,67 @@ def process_output_instructions(content: str) -> str:
|
|
|
503
505
|
while i < len(lines):
|
|
504
506
|
line = lines[i]
|
|
505
507
|
|
|
506
|
-
# Check if contains
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
i += 1
|
|
508
|
+
# Check if contains preserved markers (inline !===...!=== or multiline !===...)
|
|
509
|
+
# Check inline format first: !===content!===
|
|
510
|
+
inline_match = re.search(r"!===\s*([^!]+)\s*!===", line)
|
|
511
|
+
if inline_match and line.count("!===") >= 2:
|
|
512
|
+
# Process inline format
|
|
513
|
+
full_match = inline_match.group(0)
|
|
514
|
+
inner_content = inline_match.group(1).strip()
|
|
515
|
+
|
|
516
|
+
# Build output instruction - keep inline format on same line
|
|
517
|
+
output_instruction = f"{OUTPUT_INSTRUCTION_PREFIX}{inner_content}{OUTPUT_INSTRUCTION_SUFFIX}"
|
|
518
|
+
|
|
519
|
+
# Replace !===...!=== part in original line
|
|
520
|
+
processed_line = line.replace(full_match, output_instruction)
|
|
521
|
+
result_lines.append(processed_line)
|
|
522
|
+
has_output_instruction = True
|
|
523
|
+
i += 1
|
|
523
524
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
i += 1
|
|
557
|
-
break
|
|
558
|
-
# Continue collecting content
|
|
559
|
-
output_content_lines.append(current_line)
|
|
525
|
+
elif COMPILED_PRESERVE_FENCE_REGEX.match(line.strip()):
|
|
526
|
+
# Multiline format start
|
|
527
|
+
i += 1
|
|
528
|
+
output_content_lines: list[str] = []
|
|
529
|
+
|
|
530
|
+
# Collect multiline content
|
|
531
|
+
while i < len(lines):
|
|
532
|
+
current_line = lines[i]
|
|
533
|
+
if COMPILED_PRESERVE_FENCE_REGEX.match(current_line.strip()):
|
|
534
|
+
# Found end marker, process collected content
|
|
535
|
+
output_content = "\n".join(output_content_lines).strip()
|
|
536
|
+
|
|
537
|
+
# Special handling for title format (maintain original logic)
|
|
538
|
+
hash_prefix = ""
|
|
539
|
+
if output_content.startswith("#"):
|
|
540
|
+
first_space = output_content.find(" ")
|
|
541
|
+
first_newline = output_content.find("\n")
|
|
542
|
+
|
|
543
|
+
if first_space != -1 and (first_newline == -1 or first_space < first_newline):
|
|
544
|
+
hash_prefix = output_content[: first_space + 1]
|
|
545
|
+
output_content = output_content[first_space + 1 :].strip()
|
|
546
|
+
elif first_newline != -1:
|
|
547
|
+
hash_prefix = output_content[: first_newline + 1]
|
|
548
|
+
output_content = output_content[first_newline + 1 :].strip()
|
|
549
|
+
|
|
550
|
+
# Build output instruction
|
|
551
|
+
if hash_prefix:
|
|
552
|
+
result_lines.append(f"{OUTPUT_INSTRUCTION_PREFIX}{hash_prefix}{output_content}{OUTPUT_INSTRUCTION_SUFFIX}")
|
|
553
|
+
else:
|
|
554
|
+
result_lines.append(f"{OUTPUT_INSTRUCTION_PREFIX}{output_content}{OUTPUT_INSTRUCTION_SUFFIX}")
|
|
555
|
+
|
|
556
|
+
has_output_instruction = True
|
|
560
557
|
i += 1
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
result_lines.extend(output_content_lines)
|
|
565
|
-
else:
|
|
566
|
-
# Contains === but not valid format, treat as normal line
|
|
567
|
-
result_lines.append(line)
|
|
558
|
+
break
|
|
559
|
+
# Continue collecting content
|
|
560
|
+
output_content_lines.append(current_line) # type: ignore[unreachable]
|
|
568
561
|
i += 1
|
|
562
|
+
else:
|
|
563
|
+
# No end marker found, rollback processing
|
|
564
|
+
result_lines.append(lines[i - len(output_content_lines) - 1])
|
|
565
|
+
result_lines.extend(output_content_lines)
|
|
569
566
|
else:
|
|
570
567
|
# Normal line
|
|
571
|
-
result_lines.append(line)
|
|
568
|
+
result_lines.append(line) # type: ignore[unreachable]
|
|
572
569
|
i += 1
|
|
573
570
|
|
|
574
571
|
# Assemble final content
|
|
@@ -583,15 +580,15 @@ def process_output_instructions(content: str) -> str:
|
|
|
583
580
|
|
|
584
581
|
def extract_preserved_content(content: str) -> str:
|
|
585
582
|
"""
|
|
586
|
-
Extract actual content from preserved content blocks, removing
|
|
583
|
+
Extract actual content from preserved content blocks, removing markers.
|
|
587
584
|
|
|
588
|
-
Handles inline (
|
|
585
|
+
Handles inline (!===content!===) and multiline (!===...!===) formats.
|
|
589
586
|
|
|
590
587
|
Args:
|
|
591
|
-
content: Preserved content containing
|
|
588
|
+
content: Preserved content containing preserved markers
|
|
592
589
|
|
|
593
590
|
Returns:
|
|
594
|
-
Actual content with
|
|
591
|
+
Actual content with !=== markers removed
|
|
595
592
|
"""
|
|
596
593
|
content = content.strip()
|
|
597
594
|
if not content:
|
|
@@ -603,14 +600,14 @@ def extract_preserved_content(content: str) -> str:
|
|
|
603
600
|
for line in lines:
|
|
604
601
|
stripped_line = line.strip()
|
|
605
602
|
|
|
606
|
-
# Check inline format
|
|
607
|
-
|
|
608
|
-
if
|
|
603
|
+
# Check inline format: !===content!===
|
|
604
|
+
inline_match = re.match(r"^!===(.+)!=== *$", stripped_line)
|
|
605
|
+
if inline_match:
|
|
609
606
|
# Inline format, extract middle content
|
|
610
|
-
inner_content =
|
|
611
|
-
if inner_content and "
|
|
607
|
+
inner_content = inline_match.group(1).strip()
|
|
608
|
+
if inner_content and "!==" not in inner_content:
|
|
612
609
|
result_lines.append(inner_content)
|
|
613
|
-
elif stripped_line
|
|
610
|
+
elif COMPILED_PRESERVE_FENCE_REGEX.match(stripped_line): # type: ignore[unreachable]
|
|
614
611
|
# Multiline format delimiter, skip
|
|
615
612
|
continue
|
|
616
613
|
else:
|
|
@@ -694,7 +691,7 @@ def replace_variables_in_text(text: str, variables: dict[str, str]) -> str:
|
|
|
694
691
|
variables = {}
|
|
695
692
|
|
|
696
693
|
# Find all {{variable}} format variable references
|
|
697
|
-
variable_pattern = r"\{\{([^{}]
|
|
694
|
+
variable_pattern = r"\{\{([^{}]+)\}\}"
|
|
698
695
|
matches = re.findall(variable_pattern, text)
|
|
699
696
|
|
|
700
697
|
# Assign "UNKNOWN" to undefined variables
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: markdown-flow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: An agent library designed to parse and process MarkdownFlow documents
|
|
5
5
|
Project-URL: Homepage, https://github.com/ai-shifu/markdown-flow-agent-py
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/ai-shifu/markdown-flow-agent-py/issues
|
|
@@ -241,7 +241,7 @@ Enumeration of different block types in MarkdownFlow documents.
|
|
|
241
241
|
class BlockType(Enum):
|
|
242
242
|
CONTENT = "content" # Regular markdown content
|
|
243
243
|
INTERACTION = "interaction" # User interaction blocks (?[...])
|
|
244
|
-
PRESERVED_CONTENT = "preserved_content" # Content wrapped in
|
|
244
|
+
PRESERVED_CONTENT = "preserved_content" # Content wrapped in !=== markers (inline or multiline)
|
|
245
245
|
```
|
|
246
246
|
|
|
247
247
|
**Block Structure:**
|
|
@@ -259,10 +259,11 @@ Hello {{name}}! Welcome to our platform.
|
|
|
259
259
|
|
|
260
260
|
# Preserved content - output as-is
|
|
261
261
|
"""
|
|
262
|
-
|
|
262
|
+
# Multiline fence with leading '!'
|
|
263
|
+
!===
|
|
263
264
|
This content is preserved exactly as written.
|
|
264
265
|
No LLM processing or variable replacement.
|
|
265
|
-
|
|
266
|
+
!===
|
|
266
267
|
"""
|
|
267
268
|
```
|
|
268
269
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|