agent-folder-workspace 0.1.0__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.
- agent_folder_workspace/__init__.py +42 -0
- agent_folder_workspace/__main__.py +6 -0
- agent_folder_workspace/adapters/__init__.py +5 -0
- agent_folder_workspace/adapters/base.py +93 -0
- agent_folder_workspace/adapters/legacy_office.py +552 -0
- agent_folder_workspace/adapters/manager.py +178 -0
- agent_folder_workspace/adapters/odf.py +636 -0
- agent_folder_workspace/adapters/ooxml.py +645 -0
- agent_folder_workspace/adapters/pdf.py +680 -0
- agent_folder_workspace/adapters/sqlite.py +326 -0
- agent_folder_workspace/adapters/structured.py +280 -0
- agent_folder_workspace/adapters/text.py +145 -0
- agent_folder_workspace/alternative_backends.py +329 -0
- agent_folder_workspace/backend_conversion_worker.py +232 -0
- agent_folder_workspace/backend_probe_worker.py +347 -0
- agent_folder_workspace/backends.py +321 -0
- agent_folder_workspace/cache.py +395 -0
- agent_folder_workspace/cfb.py +425 -0
- agent_folder_workspace/cli.py +139 -0
- agent_folder_workspace/config.py +37 -0
- agent_folder_workspace/cursors.py +46 -0
- agent_folder_workspace/errors.py +27 -0
- agent_folder_workspace/ids.py +42 -0
- agent_folder_workspace/installer.py +180 -0
- agent_folder_workspace/legacy.py +697 -0
- agent_folder_workspace/mcp_server.py +179 -0
- agent_folder_workspace/models.py +158 -0
- agent_folder_workspace/opencode/plugin/agent-folder-workspace.ts.tmpl +41 -0
- agent_folder_workspace/opencode/skill/SKILL.md +24 -0
- agent_folder_workspace/parser_workers.py +690 -0
- agent_folder_workspace/path_safety.py +28 -0
- agent_folder_workspace/probe_fixtures/probe.doc +0 -0
- agent_folder_workspace/probe_fixtures/probe.ppt +0 -0
- agent_folder_workspace/probe_fixtures/probe.xls +0 -0
- agent_folder_workspace/py.typed +1 -0
- agent_folder_workspace/registry.py +394 -0
- agent_folder_workspace/schemas/CapabilityReportV1.schema.json +121 -0
- agent_folder_workspace/schemas/ContentPageV1.schema.json +138 -0
- agent_folder_workspace/schemas/DiagnosticV1.schema.json +49 -0
- agent_folder_workspace/schemas/NodePageV1.schema.json +340 -0
- agent_folder_workspace/schemas/NodeV1.schema.json +289 -0
- agent_folder_workspace/schemas/SearchPageV1.schema.json +95 -0
- agent_folder_workspace/workspace.py +1141 -0
- agent_folder_workspace-0.1.0.dist-info/METADATA +407 -0
- agent_folder_workspace-0.1.0.dist-info/RECORD +48 -0
- agent_folder_workspace-0.1.0.dist-info/WHEEL +4 -0
- agent_folder_workspace-0.1.0.dist-info/entry_points.txt +2 -0
- agent_folder_workspace-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Adapter selection and dispatch."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from agent_folder_workspace.adapters.base import BaseAdapter, ReadResult, VirtualNodeSpec
|
|
9
|
+
from agent_folder_workspace.adapters.text import CsvAdapter, TextAdapter
|
|
10
|
+
from agent_folder_workspace.config import WorkspaceConfig
|
|
11
|
+
from agent_folder_workspace.errors import FolderWorkspaceError, InputValidationError
|
|
12
|
+
from agent_folder_workspace.models import NodeV1
|
|
13
|
+
from agent_folder_workspace.parser_workers import ParserWorkerPool
|
|
14
|
+
|
|
15
|
+
_ISOLATED_ADAPTERS = frozenset(
|
|
16
|
+
{
|
|
17
|
+
"sqlite",
|
|
18
|
+
"structured_text",
|
|
19
|
+
"ooxml_spreadsheet",
|
|
20
|
+
"ooxml_word",
|
|
21
|
+
"ooxml_presentation",
|
|
22
|
+
"odf_text",
|
|
23
|
+
"odf_spreadsheet",
|
|
24
|
+
"odf_presentation",
|
|
25
|
+
"pdf",
|
|
26
|
+
"legacy_doc",
|
|
27
|
+
"legacy_xls",
|
|
28
|
+
"legacy_ppt",
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _create_direct_adapter(name: str) -> BaseAdapter | None:
|
|
34
|
+
"""Lazily import risky parsers only when an in-process binary call needs one."""
|
|
35
|
+
if name == "sqlite":
|
|
36
|
+
from agent_folder_workspace.adapters.sqlite import SqliteAdapter
|
|
37
|
+
|
|
38
|
+
return SqliteAdapter()
|
|
39
|
+
if name == "structured_text":
|
|
40
|
+
from agent_folder_workspace.adapters.structured import StructuredTextAdapter
|
|
41
|
+
|
|
42
|
+
return StructuredTextAdapter()
|
|
43
|
+
if name == "ooxml_spreadsheet":
|
|
44
|
+
from agent_folder_workspace.adapters.ooxml import OoxmlAdapter
|
|
45
|
+
|
|
46
|
+
return OoxmlAdapter("spreadsheet")
|
|
47
|
+
if name == "ooxml_word":
|
|
48
|
+
from agent_folder_workspace.adapters.ooxml import OoxmlAdapter
|
|
49
|
+
|
|
50
|
+
return OoxmlAdapter("word")
|
|
51
|
+
if name == "ooxml_presentation":
|
|
52
|
+
from agent_folder_workspace.adapters.ooxml import OoxmlAdapter
|
|
53
|
+
|
|
54
|
+
return OoxmlAdapter("presentation")
|
|
55
|
+
if name == "odf_text":
|
|
56
|
+
from agent_folder_workspace.adapters.odf import OdfAdapter
|
|
57
|
+
|
|
58
|
+
return OdfAdapter("text")
|
|
59
|
+
if name == "odf_spreadsheet":
|
|
60
|
+
from agent_folder_workspace.adapters.odf import OdfAdapter
|
|
61
|
+
|
|
62
|
+
return OdfAdapter("spreadsheet")
|
|
63
|
+
if name == "odf_presentation":
|
|
64
|
+
from agent_folder_workspace.adapters.odf import OdfAdapter
|
|
65
|
+
|
|
66
|
+
return OdfAdapter("presentation")
|
|
67
|
+
if name == "pdf":
|
|
68
|
+
from agent_folder_workspace.adapters.pdf import PdfAdapter
|
|
69
|
+
|
|
70
|
+
return PdfAdapter()
|
|
71
|
+
if name == "legacy_doc":
|
|
72
|
+
from agent_folder_workspace.adapters.legacy_office import LegacyOfficeAdapter
|
|
73
|
+
|
|
74
|
+
return LegacyOfficeAdapter("doc")
|
|
75
|
+
if name == "legacy_xls":
|
|
76
|
+
from agent_folder_workspace.adapters.legacy_office import LegacyOfficeAdapter
|
|
77
|
+
|
|
78
|
+
return LegacyOfficeAdapter("xls")
|
|
79
|
+
if name == "legacy_ppt":
|
|
80
|
+
from agent_folder_workspace.adapters.legacy_office import LegacyOfficeAdapter
|
|
81
|
+
|
|
82
|
+
return LegacyOfficeAdapter("ppt")
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class AdapterManager:
|
|
87
|
+
def __init__(self, *, isolate_parsers: bool = True) -> None:
|
|
88
|
+
fallback = BaseAdapter()
|
|
89
|
+
self._fallback = fallback
|
|
90
|
+
self._isolate_parsers = isolate_parsers
|
|
91
|
+
self._parser_workers: ParserWorkerPool | None = None
|
|
92
|
+
self._parser_workers_lock = threading.Lock()
|
|
93
|
+
self._adapters_lock = threading.Lock()
|
|
94
|
+
self._closed = False
|
|
95
|
+
self._adapters: dict[str, BaseAdapter] = {
|
|
96
|
+
"binary": fallback,
|
|
97
|
+
"text": TextAdapter(),
|
|
98
|
+
"csv": CsvAdapter(),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
def _adapter(self, name: str) -> BaseAdapter:
|
|
102
|
+
with self._adapters_lock:
|
|
103
|
+
adapter = self._adapters.get(name)
|
|
104
|
+
if adapter is None:
|
|
105
|
+
adapter = _create_direct_adapter(name) or self._fallback
|
|
106
|
+
self._adapters[name] = adapter
|
|
107
|
+
return adapter
|
|
108
|
+
|
|
109
|
+
def _parser_pool(self, config: WorkspaceConfig) -> ParserWorkerPool:
|
|
110
|
+
with self._parser_workers_lock:
|
|
111
|
+
if self._closed:
|
|
112
|
+
raise FolderWorkspaceError("adapter manager is closed")
|
|
113
|
+
if self._parser_workers is None:
|
|
114
|
+
self._parser_workers = ParserWorkerPool(config.parser_max_workers)
|
|
115
|
+
elif self._parser_workers.max_workers != config.parser_max_workers:
|
|
116
|
+
raise InputValidationError(
|
|
117
|
+
"parser_max_workers cannot change while an adapter manager is active"
|
|
118
|
+
)
|
|
119
|
+
return self._parser_workers
|
|
120
|
+
|
|
121
|
+
def _isolated(self, adapter_name: str) -> bool:
|
|
122
|
+
return self._isolate_parsers and adapter_name in _ISOLATED_ADAPTERS
|
|
123
|
+
|
|
124
|
+
def children(
|
|
125
|
+
self, node: NodeV1, source: Path, config: WorkspaceConfig
|
|
126
|
+
) -> list[VirtualNodeSpec]:
|
|
127
|
+
if self._isolated(node.adapter):
|
|
128
|
+
return self._parser_pool(config).children(node.adapter, node, source, config)
|
|
129
|
+
return self._adapter(node.adapter).children(node, source, config)
|
|
130
|
+
|
|
131
|
+
def read(
|
|
132
|
+
self,
|
|
133
|
+
node: NodeV1,
|
|
134
|
+
source: Path,
|
|
135
|
+
offset: int,
|
|
136
|
+
limit: int,
|
|
137
|
+
config: WorkspaceConfig,
|
|
138
|
+
) -> ReadResult:
|
|
139
|
+
if self._isolated(node.adapter):
|
|
140
|
+
return self._parser_pool(config).read(
|
|
141
|
+
node.adapter,
|
|
142
|
+
node,
|
|
143
|
+
source,
|
|
144
|
+
offset,
|
|
145
|
+
limit,
|
|
146
|
+
config,
|
|
147
|
+
)
|
|
148
|
+
return self._adapter(node.adapter).read(node, source, offset, limit, config)
|
|
149
|
+
|
|
150
|
+
def read_binary_part(
|
|
151
|
+
self,
|
|
152
|
+
node: NodeV1,
|
|
153
|
+
source: Path,
|
|
154
|
+
offset: int,
|
|
155
|
+
length: int,
|
|
156
|
+
config: WorkspaceConfig,
|
|
157
|
+
) -> tuple[bytes, int]:
|
|
158
|
+
source_part = node.source_ref.part if node.source_ref is not None else None
|
|
159
|
+
if self._isolated(node.adapter) and source_part not in {None, "original"}:
|
|
160
|
+
return self._parser_pool(config).read_binary_part(
|
|
161
|
+
node.adapter,
|
|
162
|
+
node,
|
|
163
|
+
source,
|
|
164
|
+
offset,
|
|
165
|
+
length,
|
|
166
|
+
config,
|
|
167
|
+
)
|
|
168
|
+
return self._adapter(node.adapter).read_binary_part(node, source, offset, length, config)
|
|
169
|
+
|
|
170
|
+
def close(self) -> None:
|
|
171
|
+
with self._parser_workers_lock:
|
|
172
|
+
if self._closed:
|
|
173
|
+
return
|
|
174
|
+
self._closed = True
|
|
175
|
+
workers = self._parser_workers
|
|
176
|
+
self._parser_workers = None
|
|
177
|
+
if workers is not None:
|
|
178
|
+
workers.close()
|