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.
Files changed (48) hide show
  1. agent_folder_workspace/__init__.py +42 -0
  2. agent_folder_workspace/__main__.py +6 -0
  3. agent_folder_workspace/adapters/__init__.py +5 -0
  4. agent_folder_workspace/adapters/base.py +93 -0
  5. agent_folder_workspace/adapters/legacy_office.py +552 -0
  6. agent_folder_workspace/adapters/manager.py +178 -0
  7. agent_folder_workspace/adapters/odf.py +636 -0
  8. agent_folder_workspace/adapters/ooxml.py +645 -0
  9. agent_folder_workspace/adapters/pdf.py +680 -0
  10. agent_folder_workspace/adapters/sqlite.py +326 -0
  11. agent_folder_workspace/adapters/structured.py +280 -0
  12. agent_folder_workspace/adapters/text.py +145 -0
  13. agent_folder_workspace/alternative_backends.py +329 -0
  14. agent_folder_workspace/backend_conversion_worker.py +232 -0
  15. agent_folder_workspace/backend_probe_worker.py +347 -0
  16. agent_folder_workspace/backends.py +321 -0
  17. agent_folder_workspace/cache.py +395 -0
  18. agent_folder_workspace/cfb.py +425 -0
  19. agent_folder_workspace/cli.py +139 -0
  20. agent_folder_workspace/config.py +37 -0
  21. agent_folder_workspace/cursors.py +46 -0
  22. agent_folder_workspace/errors.py +27 -0
  23. agent_folder_workspace/ids.py +42 -0
  24. agent_folder_workspace/installer.py +180 -0
  25. agent_folder_workspace/legacy.py +697 -0
  26. agent_folder_workspace/mcp_server.py +179 -0
  27. agent_folder_workspace/models.py +158 -0
  28. agent_folder_workspace/opencode/plugin/agent-folder-workspace.ts.tmpl +41 -0
  29. agent_folder_workspace/opencode/skill/SKILL.md +24 -0
  30. agent_folder_workspace/parser_workers.py +690 -0
  31. agent_folder_workspace/path_safety.py +28 -0
  32. agent_folder_workspace/probe_fixtures/probe.doc +0 -0
  33. agent_folder_workspace/probe_fixtures/probe.ppt +0 -0
  34. agent_folder_workspace/probe_fixtures/probe.xls +0 -0
  35. agent_folder_workspace/py.typed +1 -0
  36. agent_folder_workspace/registry.py +394 -0
  37. agent_folder_workspace/schemas/CapabilityReportV1.schema.json +121 -0
  38. agent_folder_workspace/schemas/ContentPageV1.schema.json +138 -0
  39. agent_folder_workspace/schemas/DiagnosticV1.schema.json +49 -0
  40. agent_folder_workspace/schemas/NodePageV1.schema.json +340 -0
  41. agent_folder_workspace/schemas/NodeV1.schema.json +289 -0
  42. agent_folder_workspace/schemas/SearchPageV1.schema.json +95 -0
  43. agent_folder_workspace/workspace.py +1141 -0
  44. agent_folder_workspace-0.1.0.dist-info/METADATA +407 -0
  45. agent_folder_workspace-0.1.0.dist-info/RECORD +48 -0
  46. agent_folder_workspace-0.1.0.dist-info/WHEEL +4 -0
  47. agent_folder_workspace-0.1.0.dist-info/entry_points.txt +2 -0
  48. 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()