python-hwpx-automation 6.0.3__py3-none-any.whl
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.
- hwpx_automation/__init__.py +61 -0
- hwpx_automation/__init__.pyi +27 -0
- hwpx_automation/__main__.py +8 -0
- hwpx_automation/agent_document.py +392 -0
- hwpx_automation/api.py +136 -0
- hwpx_automation/blind_eval.py +407 -0
- hwpx_automation/capabilities.py +110 -0
- hwpx_automation/compat.py +48 -0
- hwpx_automation/configuration.py +60 -0
- hwpx_automation/core/__init__.py +2 -0
- hwpx_automation/core/content.py +762 -0
- hwpx_automation/core/context.py +111 -0
- hwpx_automation/core/diff.py +53 -0
- hwpx_automation/core/document.py +37 -0
- hwpx_automation/core/formatting.py +513 -0
- hwpx_automation/core/handles.py +24 -0
- hwpx_automation/core/locations.py +205 -0
- hwpx_automation/core/locator.py +162 -0
- hwpx_automation/core/plan.py +680 -0
- hwpx_automation/core/resources.py +42 -0
- hwpx_automation/core/search.py +296 -0
- hwpx_automation/core/transactions.py +434 -0
- hwpx_automation/core/txn.py +48 -0
- hwpx_automation/document_state.py +95 -0
- hwpx_automation/errors.py +174 -0
- hwpx_automation/execution_lock.py +15 -0
- hwpx_automation/fastmcp_adapter.py +672 -0
- hwpx_automation/form_fill.py +1177 -0
- hwpx_automation/form_output_models.py +223 -0
- hwpx_automation/handlers/__init__.py +2 -0
- hwpx_automation/handlers/_shared.py +377 -0
- hwpx_automation/handlers/agent_document.py +257 -0
- hwpx_automation/handlers/authoring.py +750 -0
- hwpx_automation/handlers/content_edit.py +1078 -0
- hwpx_automation/handlers/form_fill.py +607 -0
- hwpx_automation/handlers/layout_style.py +660 -0
- hwpx_automation/handlers/quality_render.py +566 -0
- hwpx_automation/handlers/read_export.py +1295 -0
- hwpx_automation/handlers/specialized.py +624 -0
- hwpx_automation/handlers/tracked_changes.py +589 -0
- hwpx_automation/handlers/workflow.py +105 -0
- hwpx_automation/hwp_converter.py +227 -0
- hwpx_automation/hwp_support.py +94 -0
- hwpx_automation/hwpx_ops.py +1439 -0
- hwpx_automation/identity.json +263 -0
- hwpx_automation/identity.py +18 -0
- hwpx_automation/ingest_adapters.py +85 -0
- hwpx_automation/markdown_plan.py +216 -0
- hwpx_automation/mcp_cli.py +29 -0
- hwpx_automation/metadata/tools_meta.py +40 -0
- hwpx_automation/mixed_form.py +3007 -0
- hwpx_automation/mutation_models.py +401 -0
- hwpx_automation/network_policy.py +232 -0
- hwpx_automation/office/__init__.py +14 -0
- hwpx_automation/office/agent/__init__.py +125 -0
- hwpx_automation/office/agent/_batch_verification.py +383 -0
- hwpx_automation/office/agent/blueprint/__init__.py +58 -0
- hwpx_automation/office/agent/blueprint/bundle.py +282 -0
- hwpx_automation/office/agent/blueprint/catalog.py +136 -0
- hwpx_automation/office/agent/blueprint/dump.py +520 -0
- hwpx_automation/office/agent/blueprint/mapping.py +312 -0
- hwpx_automation/office/agent/blueprint/model.py +722 -0
- hwpx_automation/office/agent/blueprint/native.py +621 -0
- hwpx_automation/office/agent/blueprint/replay.py +622 -0
- hwpx_automation/office/agent/catalog.py +252 -0
- hwpx_automation/office/agent/cli.py +647 -0
- hwpx_automation/office/agent/commands.py +1383 -0
- hwpx_automation/office/agent/document.py +801 -0
- hwpx_automation/office/agent/form_plan.py +1760 -0
- hwpx_automation/office/agent/model.py +808 -0
- hwpx_automation/office/agent/path.py +155 -0
- hwpx_automation/office/agent/query.py +230 -0
- hwpx_automation/office/agent/story.py +207 -0
- hwpx_automation/office/authoring/__init__.py +3542 -0
- hwpx_automation/office/authoring/advanced_generators.py +154 -0
- hwpx_automation/office/authoring/builder/__init__.py +52 -0
- hwpx_automation/office/authoring/builder/core.py +996 -0
- hwpx_automation/office/authoring/builder/report.py +195 -0
- hwpx_automation/office/authoring/design/__init__.py +30 -0
- hwpx_automation/office/authoring/design/_support.py +144 -0
- hwpx_automation/office/authoring/design/composer.py +282 -0
- hwpx_automation/office/authoring/design/harvest.py +305 -0
- hwpx_automation/office/authoring/design/plan.py +69 -0
- hwpx_automation/office/authoring/design/profile.py +88 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/application_form/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/profile.json +24 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/report/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/validator.py +107 -0
- hwpx_automation/office/authoring/presets/__init__.py +22 -0
- hwpx_automation/office/authoring/presets/proposal.py +538 -0
- hwpx_automation/office/authoring/report_parser.py +141 -0
- hwpx_automation/office/authoring/style_profile.py +437 -0
- hwpx_automation/office/authoring/template_analyzer.py +657 -0
- hwpx_automation/office/compliance/__init__.py +38 -0
- hwpx_automation/office/compliance/official_lint.py +478 -0
- hwpx_automation/office/compliance/pii.py +388 -0
- hwpx_automation/office/document_ops/__init__.py +13 -0
- hwpx_automation/office/document_ops/comparison.py +62 -0
- hwpx_automation/office/document_ops/mail_merge.py +73 -0
- hwpx_automation/office/document_ops/redline.py +35 -0
- hwpx_automation/office/evalplan/__init__.py +36 -0
- hwpx_automation/office/evalplan/runtime.py +2762 -0
- hwpx_automation/office/exam/__init__.py +44 -0
- hwpx_automation/office/exam/compose.py +282 -0
- hwpx_automation/office/exam/ir.py +44 -0
- hwpx_automation/office/exam/measure.py +163 -0
- hwpx_automation/office/exam/parser.py +150 -0
- hwpx_automation/office/exam/profile.py +123 -0
- hwpx_automation/office/form_fill/__init__.py +66 -0
- hwpx_automation/office/form_fill/classification.py +108 -0
- hwpx_automation/office/form_fill/fill_residue.py +242 -0
- hwpx_automation/office/form_fill/fit/__init__.py +36 -0
- hwpx_automation/office/form_fill/fit/apply.py +24 -0
- hwpx_automation/office/form_fill/fit/engine.py +24 -0
- hwpx_automation/office/form_fill/fit/measure.py +50 -0
- hwpx_automation/office/form_fill/fit/policy.py +28 -0
- hwpx_automation/office/form_fill/fit/report.py +28 -0
- hwpx_automation/office/form_fill/fit/seal.py +457 -0
- hwpx_automation/office/form_fill/fit/wordbox.py +1343 -0
- hwpx_automation/office/form_fill/guidance.py +704 -0
- hwpx_automation/office/form_fill/quality.py +961 -0
- hwpx_automation/office/form_fill/split_run.py +333 -0
- hwpx_automation/office/form_fill/template_formfit.py +656 -0
- hwpx_automation/office/house_style/__init__.py +196 -0
- hwpx_automation/office/house_style/composition.py +68 -0
- hwpx_automation/office/house_style/data/bank.json +625 -0
- hwpx_automation/office/house_style/data/genres.json +43 -0
- hwpx_automation/office/quality/__init__.py +14 -0
- hwpx_automation/office/quality/page_guard.py +277 -0
- hwpx_automation/office/rendering/__init__.py +145 -0
- hwpx_automation/office/rendering/_hancom_open_rate.ps1 +374 -0
- hwpx_automation/office/rendering/_refresh_hwpx_mac.applescript +162 -0
- hwpx_automation/office/rendering/_render_hwpx.ps1 +72 -0
- hwpx_automation/office/rendering/_render_hwpx_mac.applescript +249 -0
- hwpx_automation/office/rendering/block_splits.py +76 -0
- hwpx_automation/office/rendering/detectors.py +151 -0
- hwpx_automation/office/rendering/diff.py +153 -0
- hwpx_automation/office/rendering/fixture_corpus.py +215 -0
- hwpx_automation/office/rendering/oracle.py +909 -0
- hwpx_automation/office/rendering/page_qa.py +245 -0
- hwpx_automation/office/rendering/qa_contracts.py +293 -0
- hwpx_automation/office/rendering/qa_metrics.py +241 -0
- hwpx_automation/office/rendering/worker.py +290 -0
- hwpx_automation/office/utilities/__init__.py +12 -0
- hwpx_automation/office/utilities/table_compute.py +477 -0
- hwpx_automation/ops_services/__init__.py +1 -0
- hwpx_automation/ops_services/_border_fill.py +283 -0
- hwpx_automation/ops_services/composition.py +55 -0
- hwpx_automation/ops_services/content_layout.py +322 -0
- hwpx_automation/ops_services/context.py +213 -0
- hwpx_automation/ops_services/form_fields.py +557 -0
- hwpx_automation/ops_services/media.py +178 -0
- hwpx_automation/ops_services/memo_style.py +477 -0
- hwpx_automation/ops_services/package_validation.py +166 -0
- hwpx_automation/ops_services/planning.py +201 -0
- hwpx_automation/ops_services/preview_export.py +585 -0
- hwpx_automation/ops_services/read_query.py +601 -0
- hwpx_automation/ops_services/save_policy.py +604 -0
- hwpx_automation/ops_services/tables.py +539 -0
- hwpx_automation/ops_services/transactions.py +616 -0
- hwpx_automation/preview_output_models.py +69 -0
- hwpx_automation/public-modules.json +206 -0
- hwpx_automation/py.typed +1 -0
- hwpx_automation/quality.py +351 -0
- hwpx_automation/quality_generation.py +725 -0
- hwpx_automation/runtime.py +321 -0
- hwpx_automation/runtime_services.py +100 -0
- hwpx_automation/server.py +259 -0
- hwpx_automation/storage.py +747 -0
- hwpx_automation/tool_bindings.py +170 -0
- hwpx_automation/tool_contract.py +982 -0
- hwpx_automation/upstream.py +755 -0
- hwpx_automation/utils/__init__.py +2 -0
- hwpx_automation/utils/helpers.py +29 -0
- hwpx_automation/visual_qa.py +667 -0
- hwpx_automation/workflow/__init__.py +55 -0
- hwpx_automation/workflow/adapters.py +482 -0
- hwpx_automation/workflow/dispatcher.py +213 -0
- hwpx_automation/workflow/models.py +243 -0
- hwpx_automation/workflow/policy.py +198 -0
- hwpx_automation/workflow/render_contracts.py +173 -0
- hwpx_automation/workflow/render_metrics.py +196 -0
- hwpx_automation/workflow/render_queue.py +482 -0
- hwpx_automation/workflow/render_security.py +172 -0
- hwpx_automation/workflow/render_transport.py +369 -0
- hwpx_automation/workflow/rendering.py +206 -0
- hwpx_automation/workflow/service.py +758 -0
- hwpx_automation/workflow/state_machine.py +65 -0
- hwpx_automation/workflow/store.py +747 -0
- hwpx_automation/workspace.py +1694 -0
- python_hwpx_automation-6.0.3.dist-info/METADATA +279 -0
- python_hwpx_automation-6.0.3.dist-info/RECORD +217 -0
- python_hwpx_automation-6.0.3.dist-info/WHEEL +5 -0
- python_hwpx_automation-6.0.3.dist-info/entry_points.txt +3 -0
- python_hwpx_automation-6.0.3.dist-info/licenses/LICENSE +178 -0
- python_hwpx_automation-6.0.3.dist-info/licenses/NOTICE +14 -0
- python_hwpx_automation-6.0.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import tempfile
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Dict, List, Optional, Sequence, cast
|
|
10
|
+
|
|
11
|
+
from ..core.content import (
|
|
12
|
+
add_heading_to_doc,
|
|
13
|
+
add_page_break_to_doc,
|
|
14
|
+
add_paragraph_to_doc,
|
|
15
|
+
add_table_to_doc,
|
|
16
|
+
delete_paragraph_from_doc,
|
|
17
|
+
insert_paragraph_to_doc,
|
|
18
|
+
set_cell_text,
|
|
19
|
+
)
|
|
20
|
+
from ..storage import LocalDocumentStorage, build_hwpx_verification_report
|
|
21
|
+
from ..workspace import (
|
|
22
|
+
WorkspaceOutputGuard,
|
|
23
|
+
)
|
|
24
|
+
from ..core.search import batch_replace_in_doc, replace_in_doc
|
|
25
|
+
from ..core.transactions import (
|
|
26
|
+
backup_path_for,
|
|
27
|
+
rotate_and_backup,
|
|
28
|
+
save_dry_run,
|
|
29
|
+
semantic_diff,
|
|
30
|
+
undo_last_backup,
|
|
31
|
+
)
|
|
32
|
+
from ..upstream import (
|
|
33
|
+
HwpxDocument,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
from .context import DocumentContext
|
|
37
|
+
from .save_policy import SavePolicy
|
|
38
|
+
from .save_policy import _ExactBackupResult, _ExactRecoveryPublication
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger("hwpx_automation.hwpx_ops")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TransactionService:
|
|
44
|
+
def __init__(self, context: DocumentContext, save: SavePolicy) -> None:
|
|
45
|
+
self._context = context
|
|
46
|
+
self._save = save
|
|
47
|
+
|
|
48
|
+
def _with_transaction_verification(
|
|
49
|
+
self,
|
|
50
|
+
result: Dict[str, Any],
|
|
51
|
+
document: HwpxDocument,
|
|
52
|
+
target: Path,
|
|
53
|
+
*,
|
|
54
|
+
dry_run: bool,
|
|
55
|
+
quality: Any = None,
|
|
56
|
+
) -> Dict[str, Any]:
|
|
57
|
+
payload = dict(result)
|
|
58
|
+
payload.setdefault("dryRun", dry_run)
|
|
59
|
+
if dry_run:
|
|
60
|
+
payload.update(save_dry_run(document, target, quality=quality))
|
|
61
|
+
return payload
|
|
62
|
+
|
|
63
|
+
verification = self._save._save_transaction_document(
|
|
64
|
+
document, target, quality=quality
|
|
65
|
+
)
|
|
66
|
+
payload["verificationReport"] = verification
|
|
67
|
+
payload["openSafety"] = verification.get("openSafety")
|
|
68
|
+
if "visualComplete" in verification:
|
|
69
|
+
payload["visualComplete"] = verification["visualComplete"]
|
|
70
|
+
if "backup" in verification:
|
|
71
|
+
payload["backup"] = verification["backup"]
|
|
72
|
+
if "semanticDiff" in verification:
|
|
73
|
+
payload["semanticDiff"] = verification["semanticDiff"]
|
|
74
|
+
return payload
|
|
75
|
+
|
|
76
|
+
def _operation_value(
|
|
77
|
+
self, operation: Dict[str, Any], *names: str, default: Any = None
|
|
78
|
+
) -> Any:
|
|
79
|
+
for name in names:
|
|
80
|
+
if name in operation:
|
|
81
|
+
return operation[name]
|
|
82
|
+
return default
|
|
83
|
+
|
|
84
|
+
def _apply_transaction_operation(
|
|
85
|
+
self,
|
|
86
|
+
document: HwpxDocument,
|
|
87
|
+
operation: Dict[str, Any],
|
|
88
|
+
index: int,
|
|
89
|
+
) -> Dict[str, Any]:
|
|
90
|
+
if not isinstance(operation, dict):
|
|
91
|
+
raise TypeError(f"operation {index} must be an object")
|
|
92
|
+
raw_type = self._operation_value(operation, "type", "op", "operation")
|
|
93
|
+
if not isinstance(raw_type, str) or not raw_type.strip():
|
|
94
|
+
raise ValueError(f"operation {index} must include a type")
|
|
95
|
+
op_type = raw_type.strip().replace("-", "_")
|
|
96
|
+
|
|
97
|
+
if op_type == "replace_text":
|
|
98
|
+
find = self._operation_value(operation, "findText", "find_text", "find")
|
|
99
|
+
replace = self._operation_value(
|
|
100
|
+
operation, "replaceText", "replace_text", "replace", default=""
|
|
101
|
+
)
|
|
102
|
+
if find is None:
|
|
103
|
+
raise ValueError("replace_text requires findText")
|
|
104
|
+
count = replace_in_doc(
|
|
105
|
+
document, find_text=str(find), replace_text=str(replace)
|
|
106
|
+
)
|
|
107
|
+
return {"type": op_type, "replaced_count": count}
|
|
108
|
+
|
|
109
|
+
if op_type == "batch_replace":
|
|
110
|
+
replacements = self._operation_value(operation, "replacements")
|
|
111
|
+
if not isinstance(replacements, list):
|
|
112
|
+
raise ValueError("batch_replace requires a replacements list")
|
|
113
|
+
result = batch_replace_in_doc(document, replacements)
|
|
114
|
+
return {"type": op_type, **result}
|
|
115
|
+
|
|
116
|
+
if op_type == "add_heading":
|
|
117
|
+
text = self._operation_value(operation, "text", default="")
|
|
118
|
+
level = int(self._operation_value(operation, "level", default=1))
|
|
119
|
+
paragraph_index = add_heading_to_doc(document, str(text), level)
|
|
120
|
+
return {"type": op_type, "paragraph_index": paragraph_index}
|
|
121
|
+
|
|
122
|
+
if op_type == "add_paragraph":
|
|
123
|
+
text = self._operation_value(operation, "text", default="")
|
|
124
|
+
style = self._operation_value(operation, "style")
|
|
125
|
+
paragraph_index = add_paragraph_to_doc(document, str(text), style)
|
|
126
|
+
return {"type": op_type, "paragraph_index": paragraph_index}
|
|
127
|
+
|
|
128
|
+
if op_type == "insert_paragraph":
|
|
129
|
+
paragraph_index = self._operation_value(
|
|
130
|
+
operation, "paragraphIndex", "paragraph_index"
|
|
131
|
+
)
|
|
132
|
+
if paragraph_index is None:
|
|
133
|
+
raise ValueError("insert_paragraph requires paragraphIndex")
|
|
134
|
+
text = self._operation_value(operation, "text", default="")
|
|
135
|
+
style = self._operation_value(operation, "style")
|
|
136
|
+
inserted = insert_paragraph_to_doc(
|
|
137
|
+
document, int(paragraph_index), str(text), style
|
|
138
|
+
)
|
|
139
|
+
return {"type": op_type, "inserted_index": inserted}
|
|
140
|
+
|
|
141
|
+
if op_type == "delete_paragraph":
|
|
142
|
+
paragraph_index = self._operation_value(
|
|
143
|
+
operation, "paragraphIndex", "paragraph_index"
|
|
144
|
+
)
|
|
145
|
+
if paragraph_index is None:
|
|
146
|
+
raise ValueError("delete_paragraph requires paragraphIndex")
|
|
147
|
+
remaining = delete_paragraph_from_doc(document, int(paragraph_index))
|
|
148
|
+
return {
|
|
149
|
+
"type": op_type,
|
|
150
|
+
"deleted_index": int(paragraph_index),
|
|
151
|
+
"remaining_paragraphs": remaining,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if op_type == "add_table":
|
|
155
|
+
rows = self._operation_value(operation, "rows")
|
|
156
|
+
cols = self._operation_value(operation, "cols", "columns")
|
|
157
|
+
if rows is None or cols is None:
|
|
158
|
+
raise ValueError("add_table requires rows and cols")
|
|
159
|
+
data = self._operation_value(operation, "data")
|
|
160
|
+
table_index = add_table_to_doc(document, int(rows), int(cols), data)
|
|
161
|
+
return {"type": op_type, "table_index": table_index}
|
|
162
|
+
|
|
163
|
+
if op_type == "set_table_cell_text":
|
|
164
|
+
table_index = self._operation_value(
|
|
165
|
+
operation, "tableIndex", "table_index", default=0
|
|
166
|
+
)
|
|
167
|
+
row = self._operation_value(operation, "row")
|
|
168
|
+
col = self._operation_value(operation, "col", "column")
|
|
169
|
+
text = self._operation_value(operation, "text", default="")
|
|
170
|
+
if row is None or col is None:
|
|
171
|
+
raise ValueError("set_table_cell_text requires row and col")
|
|
172
|
+
preserve_format = bool(
|
|
173
|
+
self._operation_value(
|
|
174
|
+
operation, "preserveFormat", "preserve_format", default=True
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
split_paragraphs = bool(
|
|
178
|
+
self._operation_value(
|
|
179
|
+
operation, "splitParagraphs", "split_paragraphs", default=False
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
set_cell_text(
|
|
183
|
+
document,
|
|
184
|
+
int(table_index),
|
|
185
|
+
int(row),
|
|
186
|
+
int(col),
|
|
187
|
+
str(text),
|
|
188
|
+
preserve_format=preserve_format,
|
|
189
|
+
split_paragraphs=split_paragraphs,
|
|
190
|
+
)
|
|
191
|
+
return {
|
|
192
|
+
"type": op_type,
|
|
193
|
+
"table_index": int(table_index),
|
|
194
|
+
"row": int(row),
|
|
195
|
+
"col": int(col),
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if op_type == "fill_by_path":
|
|
199
|
+
mappings = self._operation_value(operation, "mappings")
|
|
200
|
+
if not isinstance(mappings, dict):
|
|
201
|
+
raise ValueError("fill_by_path requires mappings")
|
|
202
|
+
result = document.fill_by_path(mappings)
|
|
203
|
+
return {"type": op_type, **result}
|
|
204
|
+
|
|
205
|
+
if op_type == "add_page_break":
|
|
206
|
+
add_page_break_to_doc(document)
|
|
207
|
+
return {"type": op_type, "success": True}
|
|
208
|
+
|
|
209
|
+
raise ValueError(f"unsupported operation type: {raw_type}")
|
|
210
|
+
|
|
211
|
+
def apply_edits(
|
|
212
|
+
self,
|
|
213
|
+
path: str,
|
|
214
|
+
operations: Sequence[Dict[str, Any]],
|
|
215
|
+
*,
|
|
216
|
+
dry_run: bool = False,
|
|
217
|
+
quality: Any = None,
|
|
218
|
+
) -> Dict[str, Any]:
|
|
219
|
+
document, resolved = self._context._open_document(path)
|
|
220
|
+
operation_results: List[Dict[str, Any]] = []
|
|
221
|
+
try:
|
|
222
|
+
for index, operation in enumerate(operations):
|
|
223
|
+
operation_result = self._apply_transaction_operation(document, operation, index)
|
|
224
|
+
operation_result["operationIndex"] = index
|
|
225
|
+
operation_results.append(operation_result)
|
|
226
|
+
except Exception as exc:
|
|
227
|
+
return {
|
|
228
|
+
"ok": False,
|
|
229
|
+
"rolledBack": True,
|
|
230
|
+
"dryRun": dry_run,
|
|
231
|
+
"filename": path,
|
|
232
|
+
"failedOperationIndex": len(operation_results),
|
|
233
|
+
"error": str(exc),
|
|
234
|
+
"operationsApplied": 0,
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
result: Dict[str, Any] = {
|
|
238
|
+
"ok": True,
|
|
239
|
+
"rolledBack": False,
|
|
240
|
+
"dryRun": dry_run,
|
|
241
|
+
"filename": path,
|
|
242
|
+
"operationsApplied": len(operation_results),
|
|
243
|
+
"operationResults": operation_results,
|
|
244
|
+
}
|
|
245
|
+
if dry_run:
|
|
246
|
+
result.update(save_dry_run(document, resolved, quality=quality))
|
|
247
|
+
return result
|
|
248
|
+
verification = self._save._save_transaction_document(
|
|
249
|
+
document, resolved, quality=quality
|
|
250
|
+
)
|
|
251
|
+
result["verificationReport"] = verification
|
|
252
|
+
result["openSafety"] = verification.get("openSafety")
|
|
253
|
+
if "visualComplete" in verification:
|
|
254
|
+
result["visualComplete"] = verification["visualComplete"]
|
|
255
|
+
if "backup" in verification:
|
|
256
|
+
result["backup"] = verification["backup"]
|
|
257
|
+
if "semanticDiff" in verification:
|
|
258
|
+
result["semanticDiff"] = verification["semanticDiff"]
|
|
259
|
+
return result
|
|
260
|
+
|
|
261
|
+
def undo_last_edit(self, path: str) -> Dict[str, Any]:
|
|
262
|
+
resolved = self._context._resolve_path(path)
|
|
263
|
+
if not isinstance(self._context.storage, LocalDocumentStorage):
|
|
264
|
+
return undo_last_backup(resolved)
|
|
265
|
+
|
|
266
|
+
backup_path = backup_path_for(resolved)
|
|
267
|
+
target_guard = self._context.storage.capture_output_guard(resolved)
|
|
268
|
+
if not target_guard.target_existed:
|
|
269
|
+
raise FileNotFoundError(f"target document does not exist: {resolved}")
|
|
270
|
+
backup_guard = self._save._capture_exact_sidecar_guard(backup_path)
|
|
271
|
+
if not backup_guard.target_existed:
|
|
272
|
+
raise FileNotFoundError(f"backup document does not exist: {backup_path}")
|
|
273
|
+
target_before = self._context.storage.read_guarded_bytes(target_guard)
|
|
274
|
+
backup_before = self._context.storage.read_guarded_bytes(backup_guard)
|
|
275
|
+
target_report = self._save._report_for_bytes(target_before, file_path=resolved)
|
|
276
|
+
backup_report = self._save._report_for_bytes(
|
|
277
|
+
backup_before,
|
|
278
|
+
file_path=backup_path,
|
|
279
|
+
)
|
|
280
|
+
if not target_report["openSafety"]["ok"]:
|
|
281
|
+
raise self._context._new_error(
|
|
282
|
+
"UNDO_TARGET_OPEN_SAFETY_FAILED",
|
|
283
|
+
"current document failed open-safety verification",
|
|
284
|
+
)
|
|
285
|
+
if not backup_report["openSafety"]["ok"]:
|
|
286
|
+
raise self._context._new_error(
|
|
287
|
+
"UNDO_BACKUP_OPEN_SAFETY_FAILED",
|
|
288
|
+
"backup document failed open-safety verification",
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
recoveries = self._save._preserve_exact_preimages(
|
|
292
|
+
[
|
|
293
|
+
(
|
|
294
|
+
resolved,
|
|
295
|
+
target_before,
|
|
296
|
+
target_guard.target_mode,
|
|
297
|
+
),
|
|
298
|
+
(
|
|
299
|
+
backup_path,
|
|
300
|
+
backup_before,
|
|
301
|
+
backup_guard.target_mode,
|
|
302
|
+
),
|
|
303
|
+
],
|
|
304
|
+
marker="undo-recovery",
|
|
305
|
+
)
|
|
306
|
+
if recoveries is None:
|
|
307
|
+
raise RuntimeError("undo preimages could not be preserved")
|
|
308
|
+
target_publication: WorkspaceOutputGuard | None = None
|
|
309
|
+
backup_publication: WorkspaceOutputGuard | None = None
|
|
310
|
+
rollback_recoveries_ready = True
|
|
311
|
+
try:
|
|
312
|
+
target_publication = self._context.storage.atomic_publish_bytes(
|
|
313
|
+
target_guard,
|
|
314
|
+
backup_before,
|
|
315
|
+
mode=backup_guard.target_mode,
|
|
316
|
+
)
|
|
317
|
+
backup_publication = self._context.storage.atomic_publish_bytes(
|
|
318
|
+
backup_guard,
|
|
319
|
+
target_before,
|
|
320
|
+
mode=target_guard.target_mode,
|
|
321
|
+
)
|
|
322
|
+
self._context.storage.read_guarded_bytes(target_publication)
|
|
323
|
+
self._context.storage.read_guarded_bytes(backup_publication)
|
|
324
|
+
verification = self._save._report_for_bytes(
|
|
325
|
+
backup_before,
|
|
326
|
+
file_path=resolved,
|
|
327
|
+
)
|
|
328
|
+
if not verification["openSafety"]["ok"]:
|
|
329
|
+
raise RuntimeError("undo HWPX failed open-safety verification")
|
|
330
|
+
diff = self._save._semantic_diff_bytes(target_before, backup_before)
|
|
331
|
+
# Evidence was computed from immutable bytes; bind the success
|
|
332
|
+
# response to the exact two publications immediately before return.
|
|
333
|
+
self._context.storage.read_guarded_bytes(target_publication)
|
|
334
|
+
self._context.storage.read_guarded_bytes(backup_publication)
|
|
335
|
+
payload = {
|
|
336
|
+
"restored": True,
|
|
337
|
+
"filename": str(resolved),
|
|
338
|
+
"backupPath": str(backup_path),
|
|
339
|
+
"verificationReport": verification,
|
|
340
|
+
"openSafety": verification.get("openSafety"),
|
|
341
|
+
"semanticDiff": diff,
|
|
342
|
+
}
|
|
343
|
+
cleaned, rollback_recoveries_ready = self._save._cleanup_exact_recoveries(
|
|
344
|
+
recoveries
|
|
345
|
+
)
|
|
346
|
+
if not cleaned:
|
|
347
|
+
raise RuntimeError("undo recovery cleanup lost its exact claim")
|
|
348
|
+
try:
|
|
349
|
+
self._context.storage.read_guarded_bytes(target_publication)
|
|
350
|
+
self._context.storage.read_guarded_bytes(backup_publication)
|
|
351
|
+
except (FileNotFoundError, OSError, RuntimeError):
|
|
352
|
+
rollback_recoveries_ready = self._save._republish_exact_recoveries(
|
|
353
|
+
recoveries
|
|
354
|
+
)
|
|
355
|
+
raise
|
|
356
|
+
return payload
|
|
357
|
+
except BaseException:
|
|
358
|
+
if not rollback_recoveries_ready:
|
|
359
|
+
raise
|
|
360
|
+
if target_publication is not None and backup_publication is None:
|
|
361
|
+
try:
|
|
362
|
+
self._context.storage.read_guarded_bytes(target_publication)
|
|
363
|
+
self._context.storage.atomic_publish_bytes(
|
|
364
|
+
target_publication,
|
|
365
|
+
target_before,
|
|
366
|
+
mode=target_guard.target_mode,
|
|
367
|
+
)
|
|
368
|
+
except (FileNotFoundError, OSError, RuntimeError):
|
|
369
|
+
pass
|
|
370
|
+
raise
|
|
371
|
+
target_owned = False
|
|
372
|
+
backup_owned = False
|
|
373
|
+
if target_publication is not None:
|
|
374
|
+
try:
|
|
375
|
+
self._context.storage.read_guarded_bytes(target_publication)
|
|
376
|
+
target_owned = True
|
|
377
|
+
except (FileNotFoundError, OSError, RuntimeError):
|
|
378
|
+
pass
|
|
379
|
+
if backup_publication is not None:
|
|
380
|
+
try:
|
|
381
|
+
self._context.storage.read_guarded_bytes(backup_publication)
|
|
382
|
+
backup_owned = True
|
|
383
|
+
except (FileNotFoundError, OSError, RuntimeError):
|
|
384
|
+
pass
|
|
385
|
+
# Roll back only while both swap candidates are still ours. The
|
|
386
|
+
# A/B recovery sidecars above remain available regardless of how
|
|
387
|
+
# either publication changes during the following writes.
|
|
388
|
+
# target_owned/backup_owned are only ever set once the matching
|
|
389
|
+
# publication is non-None, so the explicit checks below never alter
|
|
390
|
+
# the branch — they re-express that invariant for the type checker.
|
|
391
|
+
if (
|
|
392
|
+
target_owned
|
|
393
|
+
and backup_owned
|
|
394
|
+
and target_publication is not None
|
|
395
|
+
and backup_publication is not None
|
|
396
|
+
):
|
|
397
|
+
try:
|
|
398
|
+
self._context.storage.atomic_publish_bytes(
|
|
399
|
+
target_publication,
|
|
400
|
+
target_before,
|
|
401
|
+
mode=target_guard.target_mode,
|
|
402
|
+
)
|
|
403
|
+
self._context.storage.atomic_publish_bytes(
|
|
404
|
+
backup_publication,
|
|
405
|
+
backup_before,
|
|
406
|
+
mode=backup_guard.target_mode,
|
|
407
|
+
)
|
|
408
|
+
except (FileNotFoundError, OSError, RuntimeError):
|
|
409
|
+
# The exact A/B recovery sidecars are intentionally retained
|
|
410
|
+
# on every failure path, including cross-step CAS loss.
|
|
411
|
+
pass
|
|
412
|
+
raise
|
|
413
|
+
|
|
414
|
+
def byte_preserving_patch(
|
|
415
|
+
self,
|
|
416
|
+
path: str,
|
|
417
|
+
patches: Sequence[Dict[str, Any]],
|
|
418
|
+
*,
|
|
419
|
+
output: Optional[str] = None,
|
|
420
|
+
) -> Dict[str, Any]:
|
|
421
|
+
try:
|
|
422
|
+
from hwpx.patch import paragraph_patch
|
|
423
|
+
except Exception as exc: # pragma: no cover - dependency compatibility
|
|
424
|
+
raise self._context._new_error(
|
|
425
|
+
"BYTE_PATCH_UNAVAILABLE",
|
|
426
|
+
"installed python-hwpx does not provide hwpx.patch.paragraph_patch",
|
|
427
|
+
) from exc
|
|
428
|
+
|
|
429
|
+
source_path = self._context._resolve_path(path)
|
|
430
|
+
output_precondition = None
|
|
431
|
+
if isinstance(self._context.storage, LocalDocumentStorage):
|
|
432
|
+
output_precondition = self._context.storage.capture_output_precondition(
|
|
433
|
+
output if output else source_path
|
|
434
|
+
)
|
|
435
|
+
target_path = output_precondition.path
|
|
436
|
+
local_guard = (
|
|
437
|
+
output_precondition
|
|
438
|
+
if isinstance(output_precondition, WorkspaceOutputGuard)
|
|
439
|
+
else None
|
|
440
|
+
)
|
|
441
|
+
else:
|
|
442
|
+
target_path = (
|
|
443
|
+
self._context._resolve_output_path(output) if output else source_path
|
|
444
|
+
)
|
|
445
|
+
local_guard = None
|
|
446
|
+
target_before = (
|
|
447
|
+
self._context._local_storage().read_guarded_bytes(local_guard)
|
|
448
|
+
if local_guard is not None and local_guard.target_existed
|
|
449
|
+
else None
|
|
450
|
+
)
|
|
451
|
+
result = paragraph_patch(source_path, patches)
|
|
452
|
+
payload = result.to_dict()
|
|
453
|
+
payload["outputPath"] = str(target_path)
|
|
454
|
+
verification_report = {
|
|
455
|
+
"ok": bool(payload["openSafety"]["ok"]) and not payload["skipped"],
|
|
456
|
+
"filePath": str(target_path),
|
|
457
|
+
"openSafety": payload["openSafety"],
|
|
458
|
+
"byteIdentical": payload["byteIdentical"],
|
|
459
|
+
"changedParts": payload["changedParts"],
|
|
460
|
+
"skipped": payload["skipped"],
|
|
461
|
+
}
|
|
462
|
+
if payload["skipped"]:
|
|
463
|
+
payload["verificationReport"] = verification_report
|
|
464
|
+
return payload
|
|
465
|
+
|
|
466
|
+
candidate_bytes = bytes(result.data)
|
|
467
|
+
verification_report = build_hwpx_verification_report(
|
|
468
|
+
candidate_bytes,
|
|
469
|
+
file_path=target_path,
|
|
470
|
+
)
|
|
471
|
+
if not verification_report["openSafety"]["ok"]:
|
|
472
|
+
raise self._context._new_error(
|
|
473
|
+
"BYTE_PATCH_OPEN_SAFETY_FAILED",
|
|
474
|
+
"patched HWPX failed open-safety verification: "
|
|
475
|
+
+ verification_report["openSafety"]["summary"],
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
target_recoveries: tuple[_ExactRecoveryPublication, ...] = ()
|
|
479
|
+
if target_before is not None:
|
|
480
|
+
preserved_target = self._save._preserve_exact_preimages(
|
|
481
|
+
[
|
|
482
|
+
(
|
|
483
|
+
target_path,
|
|
484
|
+
target_before,
|
|
485
|
+
(local_guard.target_mode if local_guard is not None else None),
|
|
486
|
+
)
|
|
487
|
+
],
|
|
488
|
+
marker="rollback-recovery",
|
|
489
|
+
)
|
|
490
|
+
if preserved_target is None:
|
|
491
|
+
raise RuntimeError("byte patch target preimage could not be preserved")
|
|
492
|
+
target_recoveries = preserved_target
|
|
493
|
+
publication: WorkspaceOutputGuard | None = None
|
|
494
|
+
exact_backup: _ExactBackupResult | None = None
|
|
495
|
+
materialized_guard: WorkspaceOutputGuard | None = None
|
|
496
|
+
rollback_recoveries_ready = True
|
|
497
|
+
try:
|
|
498
|
+
if output_precondition is not None:
|
|
499
|
+
local_storage = self._context._local_storage()
|
|
500
|
+
local_guard = local_storage.materialize_output_guard(
|
|
501
|
+
output_precondition
|
|
502
|
+
)
|
|
503
|
+
materialized_guard = local_guard
|
|
504
|
+
# Preserve the retained public backup contract, but bind the
|
|
505
|
+
# sidecar to the exact preimage captured with the output guard.
|
|
506
|
+
exact_backup = self._save._rotate_and_backup_exact(
|
|
507
|
+
target_path,
|
|
508
|
+
target_guard=local_guard,
|
|
509
|
+
target_bytes=target_before,
|
|
510
|
+
)
|
|
511
|
+
backup = exact_backup.report
|
|
512
|
+
publication = local_storage.atomic_publish_bytes(
|
|
513
|
+
local_guard,
|
|
514
|
+
candidate_bytes,
|
|
515
|
+
)
|
|
516
|
+
else:
|
|
517
|
+
backup = rotate_and_backup(target_path)
|
|
518
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
519
|
+
fd, tmp_name = tempfile.mkstemp(
|
|
520
|
+
prefix=f".{target_path.stem}.",
|
|
521
|
+
suffix=target_path.suffix or ".hwpx",
|
|
522
|
+
dir=str(target_path.parent),
|
|
523
|
+
)
|
|
524
|
+
tmp_path = Path(tmp_name)
|
|
525
|
+
try:
|
|
526
|
+
os.close(fd)
|
|
527
|
+
tmp_path.write_bytes(candidate_bytes)
|
|
528
|
+
os.replace(tmp_path, target_path)
|
|
529
|
+
finally:
|
|
530
|
+
tmp_path.unlink(missing_ok=True)
|
|
531
|
+
verification_report["backup"] = backup.to_dict()
|
|
532
|
+
if backup.backup_path is not None:
|
|
533
|
+
try:
|
|
534
|
+
verification_report["semanticDiff"] = (
|
|
535
|
+
self._save._semantic_diff_bytes(
|
|
536
|
+
target_before or b"",
|
|
537
|
+
candidate_bytes,
|
|
538
|
+
)
|
|
539
|
+
if local_guard is not None
|
|
540
|
+
else semantic_diff(backup.backup_path, target_path)
|
|
541
|
+
)
|
|
542
|
+
except Exception as exc: # pragma: no cover - diagnostic fallback
|
|
543
|
+
verification_report["semanticDiff"] = {
|
|
544
|
+
"schemaVersion": "hwpx.semantic-diff.v1",
|
|
545
|
+
"changed": True,
|
|
546
|
+
"summary": f"Semantic diff unavailable: {exc}",
|
|
547
|
+
"items": [],
|
|
548
|
+
"error": str(exc),
|
|
549
|
+
}
|
|
550
|
+
if publication is not None:
|
|
551
|
+
# Receipt evidence is valid only while the exact published
|
|
552
|
+
# candidate and reported backup sidecars still occupy their
|
|
553
|
+
# authorized names with the exact published identities.
|
|
554
|
+
for mutation in cast(_ExactBackupResult, exact_backup).mutations:
|
|
555
|
+
self._save._assert_exact_sidecar_publication(mutation.publication)
|
|
556
|
+
self._context._local_storage().read_guarded_bytes(publication)
|
|
557
|
+
all_recoveries = target_recoveries + (
|
|
558
|
+
exact_backup.recoveries if exact_backup is not None else ()
|
|
559
|
+
)
|
|
560
|
+
cleaned, rollback_recoveries_ready = self._save._cleanup_exact_recoveries(
|
|
561
|
+
all_recoveries
|
|
562
|
+
)
|
|
563
|
+
if not cleaned:
|
|
564
|
+
raise RuntimeError("byte patch recovery cleanup lost its exact claim")
|
|
565
|
+
try:
|
|
566
|
+
if publication is not None:
|
|
567
|
+
for mutation in cast(_ExactBackupResult, exact_backup).mutations:
|
|
568
|
+
self._save._assert_exact_sidecar_publication(
|
|
569
|
+
mutation.publication
|
|
570
|
+
)
|
|
571
|
+
self._context._local_storage().read_guarded_bytes(publication)
|
|
572
|
+
except (FileNotFoundError, OSError, RuntimeError):
|
|
573
|
+
rollback_recoveries_ready = self._save._republish_exact_recoveries(
|
|
574
|
+
all_recoveries
|
|
575
|
+
)
|
|
576
|
+
raise
|
|
577
|
+
except BaseException:
|
|
578
|
+
target_preimage_preserved = (
|
|
579
|
+
target_before is None or rollback_recoveries_ready
|
|
580
|
+
)
|
|
581
|
+
if target_preimage_preserved and publication is not None:
|
|
582
|
+
local_storage = self._context._local_storage()
|
|
583
|
+
try:
|
|
584
|
+
if local_guard is not None and local_guard.target_existed:
|
|
585
|
+
if target_before is not None:
|
|
586
|
+
restored_publication = (
|
|
587
|
+
local_storage.atomic_publish_bytes(
|
|
588
|
+
publication,
|
|
589
|
+
target_before,
|
|
590
|
+
mode=local_guard.target_mode,
|
|
591
|
+
)
|
|
592
|
+
)
|
|
593
|
+
local_storage.read_guarded_bytes(
|
|
594
|
+
restored_publication
|
|
595
|
+
)
|
|
596
|
+
else:
|
|
597
|
+
local_storage.remove_guarded_output(publication)
|
|
598
|
+
except (OSError, RuntimeError):
|
|
599
|
+
pass
|
|
600
|
+
if exact_backup is not None and target_preimage_preserved:
|
|
601
|
+
self._save._rollback_exact_backup_mutations(
|
|
602
|
+
exact_backup.mutations,
|
|
603
|
+
preimages_preserved=rollback_recoveries_ready,
|
|
604
|
+
)
|
|
605
|
+
if materialized_guard is not None:
|
|
606
|
+
self._context._local_storage().cleanup_owned_parent_directories(
|
|
607
|
+
materialized_guard
|
|
608
|
+
)
|
|
609
|
+
raise
|
|
610
|
+
verification_report["filePath"] = str(target_path)
|
|
611
|
+
verification_report["byteIdentical"] = payload["byteIdentical"]
|
|
612
|
+
verification_report["changedParts"] = payload["changedParts"]
|
|
613
|
+
verification_report["skipped"] = payload["skipped"]
|
|
614
|
+
payload["verificationReport"] = verification_report
|
|
615
|
+
payload["openSafety"] = verification_report["openSafety"]
|
|
616
|
+
return payload
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Typed structured output for the layout preview tool."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _StrictOutput(BaseModel):
|
|
12
|
+
model_config = ConfigDict(extra="forbid", populate_by_name=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RenderPreviewScreenshotEngine(_StrictOutput):
|
|
16
|
+
requested: bool
|
|
17
|
+
available: bool
|
|
18
|
+
backend: str | None
|
|
19
|
+
executable: str | None = None
|
|
20
|
+
message: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RenderPreviewViewer(_StrictOutput):
|
|
24
|
+
"""Self-contained scrollable document viewer, emitted only when ``viewer=True``.
|
|
25
|
+
|
|
26
|
+
The viewer HTML is always written to ``viewerPath`` inside the workspace; the
|
|
27
|
+
inline ``html`` is a convenience copy that is dropped (``htmlOmitted``) when it
|
|
28
|
+
exceeds the inline byte cap so the response can never balloon.
|
|
29
|
+
``equationRendering`` reports the honest fidelity tier as MathML/latex/script
|
|
30
|
+
fallback counts (Constitution IX).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
viewer_path: str = Field(alias="viewerPath")
|
|
34
|
+
byte_size: int = Field(alias="byteSize", ge=0)
|
|
35
|
+
page_count: int = Field(alias="pageCount", ge=0)
|
|
36
|
+
warnings: list[str]
|
|
37
|
+
fidelity_tier: str = Field(alias="fidelityTier")
|
|
38
|
+
equation_library: Literal["latex2mathml", "absent"] = Field(alias="equationLibrary")
|
|
39
|
+
equation_rendering: dict[str, int] = Field(alias="equationRendering")
|
|
40
|
+
html: str | None = None
|
|
41
|
+
html_omitted: str | None = Field(alias="htmlOmitted", default=None)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class RenderPreviewOutput(_StrictOutput):
|
|
45
|
+
"""Manifest carried in ``CallToolResult.structuredContent``."""
|
|
46
|
+
|
|
47
|
+
schema_version: Literal["hwpx.render-preview.v1"] = Field(alias="schemaVersion")
|
|
48
|
+
status: Literal["ok", "partial", "blocked", "html_only"]
|
|
49
|
+
generated_at: str = Field(alias="generatedAt")
|
|
50
|
+
source_path: str = Field(alias="sourcePath")
|
|
51
|
+
output_dir: str = Field(alias="outputDir")
|
|
52
|
+
html_path: str = Field(alias="htmlPath")
|
|
53
|
+
manifest_path: str = Field(alias="manifestPath")
|
|
54
|
+
visual_review_path: str = Field(alias="visualReviewPath")
|
|
55
|
+
mode: Literal["pages", "long"]
|
|
56
|
+
page_count: int = Field(alias="pageCount", ge=0)
|
|
57
|
+
pages: list[dict[str, Any]]
|
|
58
|
+
screenshots: list[dict[str, Any]]
|
|
59
|
+
screenshot_engine: RenderPreviewScreenshotEngine = Field(alias="screenshotEngine")
|
|
60
|
+
warnings: list[str]
|
|
61
|
+
suggestion: str | None
|
|
62
|
+
viewer: RenderPreviewViewer | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
__all__ = [
|
|
66
|
+
"RenderPreviewOutput",
|
|
67
|
+
"RenderPreviewScreenshotEngine",
|
|
68
|
+
"RenderPreviewViewer",
|
|
69
|
+
]
|