crabcode 0.1.6__py3-none-win_amd64.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.
- crabcode-0.1.6.dist-info/METADATA +63 -0
- crabcode-0.1.6.dist-info/RECORD +205 -0
- crabcode-0.1.6.dist-info/WHEEL +4 -0
- crabcode-0.1.6.dist-info/entry_points.txt +2 -0
- crabcode_cli/__init__.py +7 -0
- crabcode_cli/__main__.py +4 -0
- crabcode_cli/_native/crabcode-computer-use-host.exe +0 -0
- crabcode_cli/_native/runtime.json +6 -0
- crabcode_cli/app.py +776 -0
- crabcode_cli/banner.py +39 -0
- crabcode_cli/computer_use.py +286 -0
- crabcode_cli/model_manager.py +649 -0
- crabcode_cli/model_picker.py +168 -0
- crabcode_cli/pipe.py +148 -0
- crabcode_cli/render/__init__.py +0 -0
- crabcode_cli/render/messages.py +19 -0
- crabcode_cli/repl.py +4841 -0
- crabcode_cli/session_picker.py +239 -0
- crabcode_core/__init__.py +7 -0
- crabcode_core/_version.py +3 -0
- crabcode_core/_windows_process.py +68 -0
- crabcode_core/agent_manager.py +1731 -0
- crabcode_core/api/__init__.py +20 -0
- crabcode_core/api/anthropic_adapter.py +648 -0
- crabcode_core/api/azure_adapter.py +69 -0
- crabcode_core/api/base.py +233 -0
- crabcode_core/api/codex_adapter.py +1327 -0
- crabcode_core/api/error_logging.py +132 -0
- crabcode_core/api/gemini_adapter.py +26 -0
- crabcode_core/api/model_info.py +257 -0
- crabcode_core/api/network.py +62 -0
- crabcode_core/api/ollama_adapter.py +27 -0
- crabcode_core/api/openai_adapter.py +323 -0
- crabcode_core/api/registry.py +58 -0
- crabcode_core/command_line.py +18 -0
- crabcode_core/compact/__init__.py +17 -0
- crabcode_core/compact/compact.py +514 -0
- crabcode_core/compact/context_tokens.py +165 -0
- crabcode_core/computer_use_mode.py +38 -0
- crabcode_core/computer_use_observation.py +144 -0
- crabcode_core/config/__init__.py +3 -0
- crabcode_core/config/claudemd.py +61 -0
- crabcode_core/config/manager.py +237 -0
- crabcode_core/config/model_catalog.py +256 -0
- crabcode_core/events.py +4896 -0
- crabcode_core/file_lock.py +81 -0
- crabcode_core/filesystem.py +22 -0
- crabcode_core/goal.py +149 -0
- crabcode_core/hooks/__init__.py +5 -0
- crabcode_core/hooks/manager.py +290 -0
- crabcode_core/image_io.py +111 -0
- crabcode_core/io_worker.py +211 -0
- crabcode_core/logging_utils.py +168 -0
- crabcode_core/lsp/__init__.py +6 -0
- crabcode_core/lsp/client.py +786 -0
- crabcode_core/lsp/diagnostics.py +150 -0
- crabcode_core/lsp/manager.py +583 -0
- crabcode_core/mcp/__init__.py +4 -0
- crabcode_core/mcp/client.py +169 -0
- crabcode_core/mcp/config.py +57 -0
- crabcode_core/path_validation.py +34 -0
- crabcode_core/paths.py +24 -0
- crabcode_core/peer/__init__.py +10 -0
- crabcode_core/peer/runtime.py +518 -0
- crabcode_core/permissions/__init__.py +7 -0
- crabcode_core/permissions/ai_reviewer.py +184 -0
- crabcode_core/permissions/manager.py +187 -0
- crabcode_core/plan/__init__.py +5 -0
- crabcode_core/plan/executor.py +270 -0
- crabcode_core/plan/types.py +169 -0
- crabcode_core/prompts/__init__.py +0 -0
- crabcode_core/prompts/blocks.py +10 -0
- crabcode_core/prompts/context.py +229 -0
- crabcode_core/prompts/library.py +92 -0
- crabcode_core/prompts/profile.py +79 -0
- crabcode_core/prompts/system.py +356 -0
- crabcode_core/prompts/templates.py +104 -0
- crabcode_core/query/__init__.py +0 -0
- crabcode_core/query/computer_use_history.py +83 -0
- crabcode_core/query/loop.py +2446 -0
- crabcode_core/query/retry.py +116 -0
- crabcode_core/schedule/__init__.py +13 -0
- crabcode_core/schedule/manager.py +631 -0
- crabcode_core/schedule/models.py +204 -0
- crabcode_core/schedule/store.py +558 -0
- crabcode_core/schedule/timing.py +287 -0
- crabcode_core/session/__init__.py +12 -0
- crabcode_core/session/export.py +141 -0
- crabcode_core/session/meta_db.py +684 -0
- crabcode_core/session/storage.py +2032 -0
- crabcode_core/session/title_gen.py +52 -0
- crabcode_core/skills/__init__.py +6 -0
- crabcode_core/skills/context.py +23 -0
- crabcode_core/skills/loader.py +214 -0
- crabcode_core/skills/matcher.py +203 -0
- crabcode_core/snapshot/__init__.py +21 -0
- crabcode_core/snapshot/snapshot.py +436 -0
- crabcode_core/snapshot/tracker.py +359 -0
- crabcode_core/subprocess_utils.py +260 -0
- crabcode_core/team/__init__.py +27 -0
- crabcode_core/team/bridge.py +66 -0
- crabcode_core/team/inbox.py +237 -0
- crabcode_core/team/manager.py +1660 -0
- crabcode_core/team/message_bus.py +821 -0
- crabcode_core/team/models.py +191 -0
- crabcode_core/team/recovery.py +192 -0
- crabcode_core/text_io.py +52 -0
- crabcode_core/tools/__init__.py +112 -0
- crabcode_core/tools/_input_helpers.py +39 -0
- crabcode_core/tools/_input_schema.py +130 -0
- crabcode_core/tools/agent.py +413 -0
- crabcode_core/tools/apply_patch.py +587 -0
- crabcode_core/tools/ask_user.py +122 -0
- crabcode_core/tools/bash.py +154 -0
- crabcode_core/tools/browser.py +678 -0
- crabcode_core/tools/checklist.py +241 -0
- crabcode_core/tools/checkpoint.py +78 -0
- crabcode_core/tools/computer_use.py +599 -0
- crabcode_core/tools/diff_utils.py +123 -0
- crabcode_core/tools/file_edit.py +240 -0
- crabcode_core/tools/file_read.py +187 -0
- crabcode_core/tools/file_write.py +154 -0
- crabcode_core/tools/glob.py +123 -0
- crabcode_core/tools/goal.py +134 -0
- crabcode_core/tools/grep.py +247 -0
- crabcode_core/tools/image.py +173 -0
- crabcode_core/tools/image_generate.py +115 -0
- crabcode_core/tools/lint.py +388 -0
- crabcode_core/tools/loading.py +187 -0
- crabcode_core/tools/memory.py +456 -0
- crabcode_core/tools/monitor.py +677 -0
- crabcode_core/tools/peer.py +103 -0
- crabcode_core/tools/revert.py +90 -0
- crabcode_core/tools/schedule.py +683 -0
- crabcode_core/tools/skill.py +108 -0
- crabcode_core/tools/switch_mode.py +213 -0
- crabcode_core/tools/team.py +329 -0
- crabcode_core/tools/tool_search.py +64 -0
- crabcode_core/tools/web_search.py +444 -0
- crabcode_core/turn_details.py +64 -0
- crabcode_core/types/__init__.py +95 -0
- crabcode_core/types/config.py +434 -0
- crabcode_core/types/event.py +334 -0
- crabcode_core/types/message.py +263 -0
- crabcode_core/types/tool.py +208 -0
- crabcode_core/usage.py +289 -0
- crabcode_core/utf8_sanitize.py +29 -0
- crabcode_debugger/__init__.py +5 -0
- crabcode_debugger/adapters.py +332 -0
- crabcode_debugger/dap.py +304 -0
- crabcode_debugger/memory.py +1806 -0
- crabcode_debugger/process.py +707 -0
- crabcode_debugger/runtime.py +58 -0
- crabcode_debugger/sessions.py +387 -0
- crabcode_debugger/tool.py +731 -0
- crabcode_gateway/__init__.py +18 -0
- crabcode_gateway/acp/__init__.py +13 -0
- crabcode_gateway/acp/agent.py +1581 -0
- crabcode_gateway/acp/session.py +114 -0
- crabcode_gateway/acp/transport.py +39 -0
- crabcode_gateway/acp/types.py +90 -0
- crabcode_gateway/adapter.py +30 -0
- crabcode_gateway/auth.py +139 -0
- crabcode_gateway/computer_use.py +364 -0
- crabcode_gateway/document_engine.py +637 -0
- crabcode_gateway/document_worker.py +301 -0
- crabcode_gateway/event_bus.py +655 -0
- crabcode_gateway/grpc/__init__.py +0 -0
- crabcode_gateway/grpc/crabcode_pb2.py +70 -0
- crabcode_gateway/grpc/crabcode_pb2_grpc.py +474 -0
- crabcode_gateway/grpc/proto/crabcode.proto +160 -0
- crabcode_gateway/grpc/server.py +765 -0
- crabcode_gateway/middleware.py +344 -0
- crabcode_gateway/protocol.py +17 -0
- crabcode_gateway/routes/__init__.py +0 -0
- crabcode_gateway/routes/agent.py +392 -0
- crabcode_gateway/routes/auth.py +150 -0
- crabcode_gateway/routes/computer_use.py +101 -0
- crabcode_gateway/routes/config.py +1698 -0
- crabcode_gateway/routes/document.py +1810 -0
- crabcode_gateway/routes/event.py +3801 -0
- crabcode_gateway/routes/git_review.py +232 -0
- crabcode_gateway/routes/health.py +14 -0
- crabcode_gateway/routes/peer.py +78 -0
- crabcode_gateway/routes/permission.py +121 -0
- crabcode_gateway/routes/schedule.py +300 -0
- crabcode_gateway/routes/session.py +1496 -0
- crabcode_gateway/routes/snapshot.py +211 -0
- crabcode_gateway/routes/tasks.py +318 -0
- crabcode_gateway/routes/team.py +392 -0
- crabcode_gateway/routes/usage.py +49 -0
- crabcode_gateway/routes/workspace.py +299 -0
- crabcode_gateway/safe_file.py +63 -0
- crabcode_gateway/schemas.py +1820 -0
- crabcode_gateway/server.py +514 -0
- crabcode_gateway/session_registry.py +36 -0
- crabcode_gateway/task_registry.py +708 -0
- crabcode_gateway/turn_details.py +60 -0
- crabcode_search/__init__.py +11 -0
- crabcode_search/background.py +368 -0
- crabcode_search/chunker.py +517 -0
- crabcode_search/embedder.py +497 -0
- crabcode_search/indexer.py +215 -0
- crabcode_search/store.py +222 -0
- crabcode_search/tool.py +268 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"""Isolated BabelDOC worker using an NDJSON translation bridge.
|
|
2
|
+
|
|
3
|
+
This module runs inside the optional engine virtual environment. It receives
|
|
4
|
+
only document paths and translated strings; provider credentials remain in the
|
|
5
|
+
Gateway process.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import shutil
|
|
15
|
+
import socket
|
|
16
|
+
import sys
|
|
17
|
+
import threading
|
|
18
|
+
import uuid
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
_WRITE_LOCK = threading.Lock()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _emit(payload: dict[str, Any]) -> None:
|
|
26
|
+
with _WRITE_LOCK:
|
|
27
|
+
sys.stdout.write(json.dumps(payload, ensure_ascii=False, separators=(",", ":")) + "\n")
|
|
28
|
+
sys.stdout.flush()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class _Bridge:
|
|
32
|
+
def __init__(self) -> None:
|
|
33
|
+
self.cancelled = threading.Event()
|
|
34
|
+
self._lock = threading.Lock()
|
|
35
|
+
self._responses: dict[str, tuple[threading.Event, dict[str, Any] | None]] = {}
|
|
36
|
+
|
|
37
|
+
def read_forever(self) -> None:
|
|
38
|
+
for raw in sys.stdin:
|
|
39
|
+
try:
|
|
40
|
+
message = json.loads(raw)
|
|
41
|
+
except json.JSONDecodeError:
|
|
42
|
+
continue
|
|
43
|
+
if not isinstance(message, dict):
|
|
44
|
+
continue
|
|
45
|
+
if message.get("type") == "cancel":
|
|
46
|
+
self.cancelled.set()
|
|
47
|
+
with self._lock:
|
|
48
|
+
pending = list(self._responses.values())
|
|
49
|
+
for event, _ in pending:
|
|
50
|
+
event.set()
|
|
51
|
+
continue
|
|
52
|
+
if message.get("type") != "translation_response":
|
|
53
|
+
continue
|
|
54
|
+
request_id = message.get("request_id")
|
|
55
|
+
if not isinstance(request_id, str):
|
|
56
|
+
continue
|
|
57
|
+
with self._lock:
|
|
58
|
+
pending = self._responses.get(request_id)
|
|
59
|
+
if pending is not None:
|
|
60
|
+
self._responses[request_id] = (pending[0], message)
|
|
61
|
+
pending[0].set()
|
|
62
|
+
|
|
63
|
+
def translate(self, text: str) -> str:
|
|
64
|
+
if self.cancelled.is_set():
|
|
65
|
+
raise asyncio.CancelledError
|
|
66
|
+
request_id = uuid.uuid4().hex
|
|
67
|
+
event = threading.Event()
|
|
68
|
+
with self._lock:
|
|
69
|
+
self._responses[request_id] = (event, None)
|
|
70
|
+
_emit({"type": "translate_request", "request_id": request_id, "text": text})
|
|
71
|
+
while not event.wait(0.25):
|
|
72
|
+
if self.cancelled.is_set():
|
|
73
|
+
raise asyncio.CancelledError
|
|
74
|
+
with self._lock:
|
|
75
|
+
_, response = self._responses.pop(request_id, (event, None))
|
|
76
|
+
if self.cancelled.is_set():
|
|
77
|
+
raise asyncio.CancelledError
|
|
78
|
+
if not response or response.get("error"):
|
|
79
|
+
raise RuntimeError(str((response or {}).get("error") or "translation bridge closed"))
|
|
80
|
+
translated = response.get("translated_text")
|
|
81
|
+
if not isinstance(translated, str) or not translated.strip():
|
|
82
|
+
raise RuntimeError("translation bridge returned empty text")
|
|
83
|
+
return translated
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _configure_asset_cache(engine_root: Path) -> None:
|
|
87
|
+
assets = engine_root / "assets"
|
|
88
|
+
if not (assets / "asset-manifest.json").is_file():
|
|
89
|
+
raise RuntimeError("document engine assets are missing")
|
|
90
|
+
# BabelDOC 0.6.4 fixes its cache below Path.home(). Repoint the module
|
|
91
|
+
# globals before importing any asset consumers so no runtime download is
|
|
92
|
+
# attempted and all managed files stay inside the engine directory.
|
|
93
|
+
import babeldoc.const as babel_const
|
|
94
|
+
|
|
95
|
+
babel_const.CACHE_FOLDER = assets
|
|
96
|
+
babel_const.TIKTOKEN_CACHE_FOLDER = assets / "tiktoken"
|
|
97
|
+
babel_const.TIKTOKEN_CACHE_FOLDER.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
os.environ["TIKTOKEN_CACHE_DIR"] = str(babel_const.TIKTOKEN_CACHE_FOLDER)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _disable_network() -> None:
|
|
102
|
+
"""Make accidental BabelDOC asset/provider HTTP calls fail immediately."""
|
|
103
|
+
original_connect = socket.socket.connect
|
|
104
|
+
|
|
105
|
+
def local_only_connect(sock: socket.socket, address: Any) -> Any:
|
|
106
|
+
if sock.family in {socket.AF_INET, socket.AF_INET6}:
|
|
107
|
+
raise OSError("network access is disabled in the document worker")
|
|
108
|
+
return original_connect(sock, address)
|
|
109
|
+
|
|
110
|
+
def no_network_connection(*_args: Any, **_kwargs: Any) -> Any:
|
|
111
|
+
raise OSError("network access is disabled in the document worker")
|
|
112
|
+
|
|
113
|
+
socket.socket.connect = local_only_connect # type: ignore[method-assign]
|
|
114
|
+
socket.create_connection = no_network_connection # type: ignore[assignment]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _write_content_json(pdf_path: Path, output: Path) -> int:
|
|
118
|
+
import pymupdf
|
|
119
|
+
|
|
120
|
+
pages: list[dict[str, Any]] = []
|
|
121
|
+
with pymupdf.open(pdf_path) as document:
|
|
122
|
+
for index, page in enumerate(document, start=1):
|
|
123
|
+
paragraphs = [
|
|
124
|
+
str(block[4]).strip()
|
|
125
|
+
for block in page.get_text("blocks", sort=True)
|
|
126
|
+
if len(block) > 4 and str(block[4]).strip()
|
|
127
|
+
]
|
|
128
|
+
pages.append({"page": index, "paragraphs": paragraphs})
|
|
129
|
+
output.write_text(
|
|
130
|
+
json.dumps(
|
|
131
|
+
{"schema_version": 1, "engine": "precise", "pages": pages},
|
|
132
|
+
ensure_ascii=False,
|
|
133
|
+
indent=2,
|
|
134
|
+
) + "\n",
|
|
135
|
+
encoding="utf-8",
|
|
136
|
+
)
|
|
137
|
+
return len(pages)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _translate_with_progress(
|
|
141
|
+
config: Any,
|
|
142
|
+
bridge: _Bridge,
|
|
143
|
+
*,
|
|
144
|
+
translate: Any,
|
|
145
|
+
get_stages: Any,
|
|
146
|
+
progress_monitor_type: Any,
|
|
147
|
+
) -> Any:
|
|
148
|
+
"""Run BabelDOC synchronously while forwarding its progress callbacks.
|
|
149
|
+
|
|
150
|
+
BabelDOC 0.6.4's async wrapper waits for a separate finish event after the
|
|
151
|
+
executor has returned. That event can be lost, leaving a completed PDF at
|
|
152
|
+
99% forever. The synchronous API already returns the authoritative result,
|
|
153
|
+
so run it in our worker thread and use callbacks only for progress updates.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
def progress_changed(**event: Any) -> None:
|
|
157
|
+
if event.get("type") not in {"progress_start", "progress_update", "progress_end"}:
|
|
158
|
+
return
|
|
159
|
+
_emit({
|
|
160
|
+
"type": "progress",
|
|
161
|
+
"stage": str(event.get("stage") or "processing"),
|
|
162
|
+
"overall_progress": float(event.get("overall_progress") or 0),
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
# ProgressMonitor.on_finish invokes its finish callback whenever a cancel
|
|
166
|
+
# event is supplied. Translation errors and the returned result are
|
|
167
|
+
# propagated directly by do_translate, so this callback is intentionally a
|
|
168
|
+
# no-op rather than another completion channel.
|
|
169
|
+
def finished(**_event: Any) -> None:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
with progress_monitor_type(
|
|
173
|
+
get_stages(config),
|
|
174
|
+
progress_change_callback=progress_changed,
|
|
175
|
+
finish_callback=finished,
|
|
176
|
+
cancel_event=bridge.cancelled,
|
|
177
|
+
report_interval=config.report_interval,
|
|
178
|
+
) as progress_monitor:
|
|
179
|
+
return translate(progress_monitor, config)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
async def _run(start: dict[str, Any], bridge: _Bridge) -> None:
|
|
183
|
+
engine_root = Path(str(start["engine_root"])).resolve(strict=True)
|
|
184
|
+
input_path = Path(str(start["input_path"])).resolve(strict=True)
|
|
185
|
+
job_dir = Path(str(start["job_dir"])).resolve(strict=True)
|
|
186
|
+
if not input_path.is_file() or not job_dir.is_dir():
|
|
187
|
+
raise RuntimeError("document worker input paths are invalid")
|
|
188
|
+
engine_version = str(start.get("engine_version") or "")
|
|
189
|
+
if not engine_version:
|
|
190
|
+
raise RuntimeError("document worker engine version is missing")
|
|
191
|
+
_configure_asset_cache(engine_root)
|
|
192
|
+
_disable_network()
|
|
193
|
+
|
|
194
|
+
from babeldoc.docvision.base_doclayout import DocLayoutModel
|
|
195
|
+
from babeldoc.format.pdf.high_level import do_translate
|
|
196
|
+
from babeldoc.format.pdf.high_level import get_translation_stage
|
|
197
|
+
from babeldoc.format.pdf.translation_config import TranslationConfig
|
|
198
|
+
from babeldoc.format.pdf.translation_config import WatermarkOutputMode
|
|
199
|
+
from babeldoc.progress_monitor import ProgressMonitor
|
|
200
|
+
from babeldoc.translator.translator import BaseTranslator
|
|
201
|
+
|
|
202
|
+
class CrabCodeTranslator(BaseTranslator):
|
|
203
|
+
name = "crabcode"
|
|
204
|
+
|
|
205
|
+
def __init__(self) -> None:
|
|
206
|
+
super().__init__("auto", str(start["locale"]), True)
|
|
207
|
+
self.model = "gateway"
|
|
208
|
+
|
|
209
|
+
def do_translate(self, text, rate_limit_params=None): # noqa: ANN001, ARG002
|
|
210
|
+
if not isinstance(text, str):
|
|
211
|
+
raise ValueError("translation input must be text")
|
|
212
|
+
return bridge.translate(text)
|
|
213
|
+
|
|
214
|
+
def do_llm_translate(self, text, rate_limit_params=None): # noqa: ANN001, ARG002
|
|
215
|
+
raise NotImplementedError
|
|
216
|
+
|
|
217
|
+
output_dir = job_dir / "precise-output"
|
|
218
|
+
working_dir = job_dir / "precise-working"
|
|
219
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
220
|
+
working_dir.mkdir(parents=True, exist_ok=True)
|
|
221
|
+
translator = CrabCodeTranslator()
|
|
222
|
+
model = DocLayoutModel.load_onnx()
|
|
223
|
+
config = TranslationConfig(
|
|
224
|
+
translator=translator,
|
|
225
|
+
input_file=input_path,
|
|
226
|
+
lang_in="auto",
|
|
227
|
+
lang_out=str(start["locale"]),
|
|
228
|
+
doc_layout_model=model,
|
|
229
|
+
output_dir=output_dir,
|
|
230
|
+
working_dir=working_dir,
|
|
231
|
+
no_dual=True,
|
|
232
|
+
no_mono=False,
|
|
233
|
+
qps=max(1, int(start.get("concurrency") or 1)),
|
|
234
|
+
pool_max_workers=max(1, int(start.get("concurrency") or 1)),
|
|
235
|
+
watermark_output_mode=WatermarkOutputMode.NoWatermark,
|
|
236
|
+
skip_scanned_detection=False,
|
|
237
|
+
ocr_workaround=False,
|
|
238
|
+
auto_enable_ocr_workaround=False,
|
|
239
|
+
auto_extract_glossary=False,
|
|
240
|
+
save_auto_extracted_glossary=False,
|
|
241
|
+
report_interval=0.1,
|
|
242
|
+
)
|
|
243
|
+
result = await asyncio.to_thread(
|
|
244
|
+
_translate_with_progress,
|
|
245
|
+
config,
|
|
246
|
+
bridge,
|
|
247
|
+
translate=do_translate,
|
|
248
|
+
get_stages=get_translation_stage,
|
|
249
|
+
progress_monitor_type=ProgressMonitor,
|
|
250
|
+
)
|
|
251
|
+
if result is None or result.mono_pdf_path is None:
|
|
252
|
+
raise RuntimeError("BabelDOC did not produce a monolingual PDF")
|
|
253
|
+
|
|
254
|
+
translated_pdf = job_dir / "translated.pdf"
|
|
255
|
+
shutil.copy2(Path(result.mono_pdf_path), translated_pdf)
|
|
256
|
+
page_count = _write_content_json(translated_pdf, job_dir / "content.json")
|
|
257
|
+
diagnostics = {
|
|
258
|
+
"schema_version": 1,
|
|
259
|
+
"engine": "precise",
|
|
260
|
+
"engine_version": engine_version,
|
|
261
|
+
"source_sha256": str(start.get("source_sha256") or ""),
|
|
262
|
+
"page_count": page_count,
|
|
263
|
+
"warnings": [],
|
|
264
|
+
}
|
|
265
|
+
(job_dir / "diagnostics.json").write_text(
|
|
266
|
+
json.dumps(diagnostics, ensure_ascii=False, indent=2) + "\n",
|
|
267
|
+
encoding="utf-8",
|
|
268
|
+
)
|
|
269
|
+
_emit({
|
|
270
|
+
"type": "completed",
|
|
271
|
+
"page_count": page_count,
|
|
272
|
+
"warnings": [],
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def main() -> None:
|
|
277
|
+
raw = sys.stdin.readline()
|
|
278
|
+
try:
|
|
279
|
+
start = json.loads(raw)
|
|
280
|
+
except json.JSONDecodeError:
|
|
281
|
+
_emit({"type": "error", "code": "invalid_request", "message": "invalid worker start message"})
|
|
282
|
+
raise SystemExit(2)
|
|
283
|
+
if not isinstance(start, dict) or start.get("type") != "start":
|
|
284
|
+
_emit({"type": "error", "code": "invalid_request", "message": "missing worker start message"})
|
|
285
|
+
raise SystemExit(2)
|
|
286
|
+
bridge = _Bridge()
|
|
287
|
+
reader = threading.Thread(target=bridge.read_forever, name="document-worker-stdin", daemon=True)
|
|
288
|
+
reader.start()
|
|
289
|
+
try:
|
|
290
|
+
asyncio.run(_run(start, bridge))
|
|
291
|
+
except asyncio.CancelledError:
|
|
292
|
+
_emit({"type": "error", "code": "cancelled", "message": "document translation cancelled"})
|
|
293
|
+
raise SystemExit(130)
|
|
294
|
+
except Exception as exc:
|
|
295
|
+
code = "scanned_pdf_unsupported" if re.search(r"scanned pdf", str(exc), re.IGNORECASE) else "precise_failed"
|
|
296
|
+
_emit({"type": "error", "code": code, "message": str(exc)})
|
|
297
|
+
raise SystemExit(1) from exc
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
if __name__ == "__main__":
|
|
301
|
+
main()
|