ommlds 0.0.0.dev426__py3-none-any.whl → 0.0.0.dev485__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 (295) hide show
  1. ommlds/.omlish-manifests.json +336 -39
  2. ommlds/__about__.py +16 -10
  3. ommlds/_hacks/__init__.py +4 -0
  4. ommlds/_hacks/funcs.py +110 -0
  5. ommlds/_hacks/names.py +158 -0
  6. ommlds/_hacks/params.py +73 -0
  7. ommlds/_hacks/patches.py +0 -3
  8. ommlds/backends/anthropic/protocol/__init__.py +13 -1
  9. ommlds/backends/anthropic/protocol/_dataclasses.py +1625 -0
  10. ommlds/backends/anthropic/protocol/_marshal.py +2 -2
  11. ommlds/backends/anthropic/protocol/sse/_marshal.py +1 -1
  12. ommlds/backends/anthropic/protocol/sse/assemble.py +23 -7
  13. ommlds/backends/anthropic/protocol/sse/events.py +13 -0
  14. ommlds/backends/anthropic/protocol/types.py +40 -8
  15. ommlds/backends/google/protocol/__init__.py +16 -0
  16. ommlds/backends/google/protocol/_dataclasses.py +5997 -0
  17. ommlds/backends/google/protocol/_marshal.py +16 -0
  18. ommlds/backends/google/protocol/types.py +626 -0
  19. ommlds/backends/groq/__init__.py +7 -0
  20. ommlds/backends/groq/_dataclasses.py +3901 -0
  21. ommlds/backends/groq/_marshal.py +23 -0
  22. ommlds/backends/groq/protocol.py +249 -0
  23. ommlds/backends/llamacpp/logging.py +4 -1
  24. ommlds/backends/mlx/caching.py +7 -3
  25. ommlds/backends/mlx/cli.py +10 -7
  26. ommlds/backends/mlx/generation.py +19 -17
  27. ommlds/backends/mlx/limits.py +10 -6
  28. ommlds/backends/mlx/loading.py +65 -5
  29. ommlds/backends/ollama/__init__.py +7 -0
  30. ommlds/backends/ollama/_dataclasses.py +3458 -0
  31. ommlds/backends/ollama/protocol.py +170 -0
  32. ommlds/backends/openai/protocol/__init__.py +24 -29
  33. ommlds/backends/openai/protocol/_common.py +18 -0
  34. ommlds/backends/openai/protocol/_dataclasses.py +7708 -0
  35. ommlds/backends/openai/protocol/_marshal.py +27 -0
  36. ommlds/backends/openai/protocol/chatcompletion/chunk.py +58 -31
  37. ommlds/backends/openai/protocol/chatcompletion/contentpart.py +49 -44
  38. ommlds/backends/openai/protocol/chatcompletion/message.py +55 -43
  39. ommlds/backends/openai/protocol/chatcompletion/request.py +114 -66
  40. ommlds/backends/openai/protocol/chatcompletion/response.py +71 -45
  41. ommlds/backends/openai/protocol/chatcompletion/responseformat.py +27 -20
  42. ommlds/backends/openai/protocol/chatcompletion/tokenlogprob.py +16 -7
  43. ommlds/backends/openai/protocol/completionusage.py +24 -15
  44. ommlds/backends/tavily/__init__.py +7 -0
  45. ommlds/backends/tavily/_dataclasses.py +1734 -0
  46. ommlds/backends/tavily/protocol.py +301 -0
  47. ommlds/backends/tinygrad/models/llama3/__init__.py +22 -14
  48. ommlds/backends/transformers/__init__.py +14 -0
  49. ommlds/backends/transformers/filecache.py +109 -0
  50. ommlds/backends/transformers/streamers.py +73 -0
  51. ommlds/cli/__init__.py +7 -0
  52. ommlds/cli/_dataclasses.py +2562 -0
  53. ommlds/cli/asyncs.py +30 -0
  54. ommlds/cli/backends/catalog.py +93 -0
  55. ommlds/cli/backends/configs.py +9 -0
  56. ommlds/cli/backends/inject.py +31 -36
  57. ommlds/cli/backends/injection.py +16 -0
  58. ommlds/cli/backends/types.py +46 -0
  59. ommlds/cli/content/messages.py +34 -0
  60. ommlds/cli/content/strings.py +42 -0
  61. ommlds/cli/inject.py +17 -32
  62. ommlds/cli/inputs/__init__.py +0 -0
  63. ommlds/cli/inputs/asyncs.py +32 -0
  64. ommlds/cli/inputs/sync.py +75 -0
  65. ommlds/cli/main.py +270 -110
  66. ommlds/cli/rendering/__init__.py +0 -0
  67. ommlds/cli/rendering/configs.py +9 -0
  68. ommlds/cli/rendering/inject.py +31 -0
  69. ommlds/cli/rendering/markdown.py +52 -0
  70. ommlds/cli/rendering/raw.py +73 -0
  71. ommlds/cli/rendering/types.py +21 -0
  72. ommlds/cli/secrets.py +21 -0
  73. ommlds/cli/sessions/base.py +1 -1
  74. ommlds/cli/sessions/chat/chat/__init__.py +0 -0
  75. ommlds/cli/sessions/chat/chat/ai/__init__.py +0 -0
  76. ommlds/cli/sessions/chat/chat/ai/configs.py +11 -0
  77. ommlds/cli/sessions/chat/chat/ai/inject.py +74 -0
  78. ommlds/cli/sessions/chat/chat/ai/injection.py +14 -0
  79. ommlds/cli/sessions/chat/chat/ai/rendering.py +70 -0
  80. ommlds/cli/sessions/chat/chat/ai/services.py +79 -0
  81. ommlds/cli/sessions/chat/chat/ai/tools.py +44 -0
  82. ommlds/cli/sessions/chat/chat/ai/types.py +28 -0
  83. ommlds/cli/sessions/chat/chat/state/__init__.py +0 -0
  84. ommlds/cli/sessions/chat/chat/state/configs.py +11 -0
  85. ommlds/cli/sessions/chat/chat/state/inject.py +36 -0
  86. ommlds/cli/sessions/chat/chat/state/inmemory.py +33 -0
  87. ommlds/cli/sessions/chat/chat/state/storage.py +52 -0
  88. ommlds/cli/sessions/chat/chat/state/types.py +38 -0
  89. ommlds/cli/sessions/chat/chat/user/__init__.py +0 -0
  90. ommlds/cli/sessions/chat/chat/user/configs.py +17 -0
  91. ommlds/cli/sessions/chat/chat/user/inject.py +62 -0
  92. ommlds/cli/sessions/chat/chat/user/interactive.py +31 -0
  93. ommlds/cli/sessions/chat/chat/user/oneshot.py +25 -0
  94. ommlds/cli/sessions/chat/chat/user/types.py +15 -0
  95. ommlds/cli/sessions/chat/configs.py +27 -0
  96. ommlds/cli/sessions/chat/driver.py +43 -0
  97. ommlds/cli/sessions/chat/inject.py +33 -65
  98. ommlds/cli/sessions/chat/phases/__init__.py +0 -0
  99. ommlds/cli/sessions/chat/phases/inject.py +27 -0
  100. ommlds/cli/sessions/chat/phases/injection.py +14 -0
  101. ommlds/cli/sessions/chat/phases/manager.py +29 -0
  102. ommlds/cli/sessions/chat/phases/types.py +29 -0
  103. ommlds/cli/sessions/chat/session.py +27 -0
  104. ommlds/cli/sessions/chat/tools/__init__.py +0 -0
  105. ommlds/cli/sessions/chat/tools/configs.py +22 -0
  106. ommlds/cli/sessions/chat/tools/confirmation.py +46 -0
  107. ommlds/cli/sessions/chat/tools/execution.py +66 -0
  108. ommlds/cli/sessions/chat/tools/fs/__init__.py +0 -0
  109. ommlds/cli/sessions/chat/tools/fs/configs.py +12 -0
  110. ommlds/cli/sessions/chat/tools/fs/inject.py +35 -0
  111. ommlds/cli/sessions/chat/tools/inject.py +88 -0
  112. ommlds/cli/sessions/chat/tools/injection.py +44 -0
  113. ommlds/cli/sessions/chat/tools/rendering.py +58 -0
  114. ommlds/cli/sessions/chat/tools/todo/__init__.py +0 -0
  115. ommlds/cli/sessions/chat/tools/todo/configs.py +12 -0
  116. ommlds/cli/sessions/chat/tools/todo/inject.py +31 -0
  117. ommlds/cli/sessions/chat/tools/weather/__init__.py +0 -0
  118. ommlds/cli/sessions/chat/tools/weather/configs.py +12 -0
  119. ommlds/cli/sessions/chat/tools/weather/inject.py +22 -0
  120. ommlds/cli/{tools/weather.py → sessions/chat/tools/weather/tools.py} +1 -1
  121. ommlds/cli/sessions/completion/configs.py +21 -0
  122. ommlds/cli/sessions/completion/inject.py +42 -0
  123. ommlds/cli/sessions/completion/session.py +35 -0
  124. ommlds/cli/sessions/embedding/configs.py +21 -0
  125. ommlds/cli/sessions/embedding/inject.py +42 -0
  126. ommlds/cli/sessions/embedding/session.py +33 -0
  127. ommlds/cli/sessions/inject.py +28 -11
  128. ommlds/cli/state/__init__.py +0 -0
  129. ommlds/cli/state/inject.py +28 -0
  130. ommlds/cli/{state.py → state/storage.py} +41 -24
  131. ommlds/minichain/__init__.py +84 -24
  132. ommlds/minichain/_dataclasses.py +15401 -0
  133. ommlds/minichain/_marshal.py +49 -9
  134. ommlds/minichain/_typedvalues.py +2 -4
  135. ommlds/minichain/backends/catalogs/base.py +20 -1
  136. ommlds/minichain/backends/catalogs/simple.py +2 -2
  137. ommlds/minichain/backends/catalogs/strings.py +10 -8
  138. ommlds/minichain/backends/impls/anthropic/chat.py +65 -27
  139. ommlds/minichain/backends/impls/anthropic/names.py +10 -8
  140. ommlds/minichain/backends/impls/anthropic/protocol.py +109 -0
  141. ommlds/minichain/backends/impls/anthropic/stream.py +111 -43
  142. ommlds/minichain/backends/impls/duckduckgo/search.py +6 -2
  143. ommlds/minichain/backends/impls/dummy/__init__.py +0 -0
  144. ommlds/minichain/backends/impls/dummy/chat.py +69 -0
  145. ommlds/minichain/backends/impls/google/chat.py +114 -22
  146. ommlds/minichain/backends/impls/google/search.py +7 -2
  147. ommlds/minichain/backends/impls/google/stream.py +219 -0
  148. ommlds/minichain/backends/impls/google/tools.py +149 -0
  149. ommlds/minichain/backends/impls/groq/__init__.py +0 -0
  150. ommlds/minichain/backends/impls/groq/chat.py +75 -0
  151. ommlds/minichain/backends/impls/groq/names.py +48 -0
  152. ommlds/minichain/backends/impls/groq/protocol.py +143 -0
  153. ommlds/minichain/backends/impls/groq/stream.py +125 -0
  154. ommlds/minichain/backends/impls/huggingface/repos.py +1 -5
  155. ommlds/minichain/backends/impls/llamacpp/chat.py +40 -22
  156. ommlds/minichain/backends/impls/llamacpp/completion.py +9 -5
  157. ommlds/minichain/backends/impls/llamacpp/format.py +4 -2
  158. ommlds/minichain/backends/impls/llamacpp/stream.py +43 -23
  159. ommlds/minichain/backends/impls/mistral.py +20 -5
  160. ommlds/minichain/backends/impls/mlx/chat.py +101 -24
  161. ommlds/minichain/backends/impls/ollama/__init__.py +0 -0
  162. ommlds/minichain/backends/impls/ollama/chat.py +199 -0
  163. ommlds/minichain/backends/impls/openai/chat.py +18 -8
  164. ommlds/minichain/backends/impls/openai/completion.py +10 -3
  165. ommlds/minichain/backends/impls/openai/embedding.py +10 -3
  166. ommlds/minichain/backends/impls/openai/format.py +131 -106
  167. ommlds/minichain/backends/impls/openai/names.py +31 -5
  168. ommlds/minichain/backends/impls/openai/stream.py +43 -25
  169. ommlds/minichain/backends/impls/sentencepiece/tokens.py +9 -6
  170. ommlds/minichain/backends/impls/tavily.py +66 -0
  171. ommlds/minichain/backends/impls/tinygrad/chat.py +30 -20
  172. ommlds/minichain/backends/impls/tokenizers/tokens.py +9 -6
  173. ommlds/minichain/backends/impls/transformers/sentence.py +6 -3
  174. ommlds/minichain/backends/impls/transformers/tokens.py +10 -7
  175. ommlds/minichain/backends/impls/transformers/transformers.py +160 -37
  176. ommlds/minichain/backends/strings/parsing.py +1 -1
  177. ommlds/minichain/backends/strings/resolving.py +4 -1
  178. ommlds/minichain/chat/_marshal.py +16 -9
  179. ommlds/minichain/chat/choices/adapters.py +4 -4
  180. ommlds/minichain/chat/choices/services.py +1 -1
  181. ommlds/minichain/chat/choices/stream/__init__.py +0 -0
  182. ommlds/minichain/chat/choices/stream/adapters.py +35 -0
  183. ommlds/minichain/chat/choices/stream/joining.py +31 -0
  184. ommlds/minichain/chat/choices/stream/services.py +45 -0
  185. ommlds/minichain/chat/choices/stream/types.py +43 -0
  186. ommlds/minichain/chat/choices/types.py +2 -2
  187. ommlds/minichain/chat/history.py +3 -3
  188. ommlds/minichain/chat/messages.py +55 -19
  189. ommlds/minichain/chat/services.py +3 -3
  190. ommlds/minichain/chat/stream/_marshal.py +16 -0
  191. ommlds/minichain/chat/stream/joining.py +85 -0
  192. ommlds/minichain/chat/stream/services.py +15 -21
  193. ommlds/minichain/chat/stream/types.py +32 -19
  194. ommlds/minichain/chat/tools/execution.py +8 -7
  195. ommlds/minichain/chat/tools/ids.py +9 -15
  196. ommlds/minichain/chat/tools/parsing.py +17 -26
  197. ommlds/minichain/chat/transforms/base.py +29 -38
  198. ommlds/minichain/chat/transforms/metadata.py +30 -4
  199. ommlds/minichain/chat/transforms/services.py +9 -11
  200. ommlds/minichain/content/_marshal.py +44 -20
  201. ommlds/minichain/content/json.py +13 -0
  202. ommlds/minichain/content/materialize.py +14 -21
  203. ommlds/minichain/content/prepare.py +4 -0
  204. ommlds/minichain/content/transforms/interleave.py +1 -1
  205. ommlds/minichain/content/transforms/squeeze.py +1 -1
  206. ommlds/minichain/content/transforms/stringify.py +1 -1
  207. ommlds/minichain/json.py +20 -0
  208. ommlds/minichain/lib/code/__init__.py +0 -0
  209. ommlds/minichain/lib/code/prompts.py +6 -0
  210. ommlds/minichain/lib/fs/binfiles.py +108 -0
  211. ommlds/minichain/lib/fs/context.py +126 -0
  212. ommlds/minichain/lib/fs/errors.py +101 -0
  213. ommlds/minichain/lib/fs/suggestions.py +36 -0
  214. ommlds/minichain/lib/fs/tools/__init__.py +0 -0
  215. ommlds/minichain/lib/fs/tools/edit.py +104 -0
  216. ommlds/minichain/lib/fs/tools/ls.py +38 -0
  217. ommlds/minichain/lib/fs/tools/read.py +115 -0
  218. ommlds/minichain/lib/fs/tools/recursivels/__init__.py +0 -0
  219. ommlds/minichain/lib/fs/tools/recursivels/execution.py +40 -0
  220. ommlds/minichain/lib/todo/__init__.py +0 -0
  221. ommlds/minichain/lib/todo/context.py +54 -0
  222. ommlds/minichain/lib/todo/tools/__init__.py +0 -0
  223. ommlds/minichain/lib/todo/tools/read.py +44 -0
  224. ommlds/minichain/lib/todo/tools/write.py +335 -0
  225. ommlds/minichain/lib/todo/types.py +60 -0
  226. ommlds/minichain/llms/_marshal.py +25 -17
  227. ommlds/minichain/llms/types.py +4 -0
  228. ommlds/minichain/registries/globals.py +18 -4
  229. ommlds/minichain/resources.py +68 -45
  230. ommlds/minichain/search.py +1 -1
  231. ommlds/minichain/services/_marshal.py +46 -39
  232. ommlds/minichain/services/facades.py +3 -3
  233. ommlds/minichain/services/services.py +1 -1
  234. ommlds/minichain/standard.py +8 -0
  235. ommlds/minichain/stream/services.py +152 -38
  236. ommlds/minichain/stream/wrap.py +22 -24
  237. ommlds/minichain/text/toolparsing/llamacpp/hermes2.py +3 -2
  238. ommlds/minichain/text/toolparsing/llamacpp/llama31.py +3 -2
  239. ommlds/minichain/text/toolparsing/llamacpp/utils.py +3 -2
  240. ommlds/minichain/tools/_marshal.py +1 -1
  241. ommlds/minichain/tools/execution/catalog.py +2 -1
  242. ommlds/minichain/tools/execution/context.py +34 -14
  243. ommlds/minichain/tools/execution/errors.py +15 -0
  244. ommlds/minichain/tools/execution/executors.py +8 -3
  245. ommlds/minichain/tools/execution/reflect.py +40 -5
  246. ommlds/minichain/tools/fns.py +46 -9
  247. ommlds/minichain/tools/jsonschema.py +14 -5
  248. ommlds/minichain/tools/reflect.py +54 -18
  249. ommlds/minichain/tools/types.py +33 -1
  250. ommlds/minichain/utils.py +27 -0
  251. ommlds/minichain/vectors/_marshal.py +11 -10
  252. ommlds/minichain/vectors/types.py +1 -1
  253. ommlds/nanochat/LICENSE +21 -0
  254. ommlds/nanochat/__init__.py +0 -0
  255. ommlds/nanochat/rustbpe/LICENSE +21 -0
  256. ommlds/nanochat/tokenizers.py +406 -0
  257. ommlds/server/cli.py +1 -2
  258. ommlds/server/server.py +5 -5
  259. ommlds/server/service.py +1 -1
  260. ommlds/specs/__init__.py +0 -0
  261. ommlds/specs/mcp/__init__.py +0 -0
  262. ommlds/specs/mcp/_marshal.py +23 -0
  263. ommlds/specs/mcp/clients.py +146 -0
  264. ommlds/specs/mcp/protocol.py +371 -0
  265. ommlds/tools/git.py +35 -12
  266. ommlds/tools/ocr.py +8 -9
  267. ommlds/wiki/analyze.py +6 -7
  268. ommlds/wiki/text/mfh.py +1 -5
  269. ommlds/wiki/text/wtp.py +1 -3
  270. ommlds/wiki/utils/xml.py +5 -5
  271. {ommlds-0.0.0.dev426.dist-info → ommlds-0.0.0.dev485.dist-info}/METADATA +24 -21
  272. ommlds-0.0.0.dev485.dist-info/RECORD +436 -0
  273. ommlds/cli/backends/standard.py +0 -20
  274. ommlds/cli/sessions/chat/base.py +0 -42
  275. ommlds/cli/sessions/chat/interactive.py +0 -73
  276. ommlds/cli/sessions/chat/printing.py +0 -96
  277. ommlds/cli/sessions/chat/prompt.py +0 -143
  278. ommlds/cli/sessions/chat/state.py +0 -109
  279. ommlds/cli/sessions/chat/tools.py +0 -91
  280. ommlds/cli/sessions/completion/completion.py +0 -44
  281. ommlds/cli/sessions/embedding/embedding.py +0 -42
  282. ommlds/cli/tools/config.py +0 -13
  283. ommlds/cli/tools/inject.py +0 -64
  284. ommlds/minichain/chat/stream/adapters.py +0 -69
  285. ommlds/minichain/lib/fs/ls/execution.py +0 -32
  286. ommlds-0.0.0.dev426.dist-info/RECORD +0 -303
  287. /ommlds/{cli/tools → backends/google}/__init__.py +0 -0
  288. /ommlds/{huggingface.py → backends/huggingface.py} +0 -0
  289. /ommlds/{minichain/lib/fs/ls → cli/content}/__init__.py +0 -0
  290. /ommlds/minichain/lib/fs/{ls → tools/recursivels}/rendering.py +0 -0
  291. /ommlds/minichain/lib/fs/{ls → tools/recursivels}/running.py +0 -0
  292. {ommlds-0.0.0.dev426.dist-info → ommlds-0.0.0.dev485.dist-info}/WHEEL +0 -0
  293. {ommlds-0.0.0.dev426.dist-info → ommlds-0.0.0.dev485.dist-info}/entry_points.txt +0 -0
  294. {ommlds-0.0.0.dev426.dist-info → ommlds-0.0.0.dev485.dist-info}/licenses/LICENSE +0 -0
  295. {ommlds-0.0.0.dev426.dist-info → ommlds-0.0.0.dev485.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,126 @@
1
+ import os.path
2
+ import stat
3
+
4
+ from omlish import check
5
+
6
+ from ...tools.execution.context import tool_context
7
+ from .binfiles import has_binary_file_extension
8
+ from .binfiles import is_binary_file
9
+ from .errors import RequestedPathDoesNotExistError
10
+ from .errors import RequestedPathOutsideRootDirError
11
+ from .errors import RequestedPathWriteNotPermittedError
12
+ from .errors import RequestedPathWrongTypeError
13
+ from .suggestions import get_path_suggestions
14
+
15
+
16
+ ##
17
+
18
+
19
+ class FsContext:
20
+ def __init__(
21
+ self,
22
+ *,
23
+ root_dir: str | None = None,
24
+ writes_permitted: bool = False,
25
+ ) -> None:
26
+ super().__init__()
27
+
28
+ self._root_dir = root_dir
29
+ self._writes_permitted = writes_permitted
30
+
31
+ self._abs_root_dir = os.path.abspath(root_dir) if root_dir is not None else None
32
+
33
+ #
34
+
35
+ @property
36
+ def writes_permitted(self) -> bool:
37
+ return self._writes_permitted
38
+
39
+ #
40
+
41
+ def check_requested_path(self, req_path: str) -> None:
42
+ abs_req_path = os.path.abspath(req_path)
43
+
44
+ if (
45
+ self._abs_root_dir is None or
46
+ not (
47
+ abs_req_path == self._abs_root_dir or
48
+ abs_req_path.startswith(self._abs_root_dir + os.path.sep)
49
+ )
50
+ ):
51
+ raise RequestedPathOutsideRootDirError(
52
+ req_path,
53
+ root_dir=check.not_none(self._root_dir),
54
+ )
55
+
56
+ #
57
+
58
+ def check_stat_dir(
59
+ self,
60
+ req_path: str,
61
+ ) -> os.stat_result:
62
+ self.check_requested_path(req_path)
63
+
64
+ try:
65
+ st = os.stat(req_path)
66
+ except FileNotFoundError:
67
+ raise RequestedPathDoesNotExistError(
68
+ req_path,
69
+ suggested_paths=get_path_suggestions(
70
+ req_path,
71
+ filter=lambda e: e.is_dir(),
72
+ ),
73
+ ) from None
74
+
75
+ if not stat.S_ISDIR(st.st_mode):
76
+ raise RequestedPathWrongTypeError(
77
+ req_path,
78
+ expected_type='dir',
79
+ **(dict(actual_type='file') if stat.S_ISREG(st.st_mode) else {}),
80
+ )
81
+
82
+ return st
83
+
84
+ def check_stat_file(
85
+ self,
86
+ req_path: str,
87
+ *,
88
+ text: bool = False,
89
+ write: bool = False,
90
+ ) -> os.stat_result:
91
+ self.check_requested_path(req_path)
92
+
93
+ try:
94
+ st = os.stat(req_path)
95
+ except FileNotFoundError:
96
+ raise RequestedPathDoesNotExistError(
97
+ req_path,
98
+ suggested_paths=get_path_suggestions(
99
+ req_path,
100
+ filter=lambda e: (e.is_file() and not (text and has_binary_file_extension(e.name))),
101
+ ),
102
+ ) from None
103
+
104
+ if not stat.S_ISREG(st.st_mode):
105
+ is_dir = stat.S_ISDIR(st.st_mode)
106
+ raise RequestedPathWrongTypeError(
107
+ req_path,
108
+ expected_type='file',
109
+ **(dict(actual_type='dir') if is_dir else {}),
110
+ )
111
+
112
+ if text and is_binary_file(req_path, st=st):
113
+ raise RequestedPathWrongTypeError(
114
+ req_path,
115
+ expected_type='text file',
116
+ actual_type='binary file',
117
+ )
118
+
119
+ if write and not self._writes_permitted:
120
+ raise RequestedPathWriteNotPermittedError(req_path)
121
+
122
+ return st
123
+
124
+
125
+ def tool_fs_context() -> FsContext:
126
+ return tool_context()[FsContext]
@@ -0,0 +1,101 @@
1
+ import typing as ta
2
+
3
+ from omlish import lang
4
+
5
+ from ...tools.execution.errors import ToolExecutionError
6
+
7
+
8
+ ##
9
+
10
+
11
+ class FsToolExecutionError(ToolExecutionError, lang.Abstract):
12
+ pass
13
+
14
+
15
+ ##
16
+
17
+
18
+ class RequestedPathError(FsToolExecutionError, lang.Abstract):
19
+ def __init__(self, requested_path: str, *args: ta.Any) -> None:
20
+ super().__init__(requested_path, *args)
21
+
22
+ self.requested_path = requested_path
23
+
24
+
25
+ class RequestedPathMustBeAbsoluteError(RequestedPathError):
26
+ @property
27
+ def content(self) -> str:
28
+ return f'Requested path {self.requested_path!r} must be absolute.'
29
+
30
+
31
+ class RequestedPathOutsideRootDirError(RequestedPathError):
32
+ def __init__(
33
+ self,
34
+ requested_path: str,
35
+ *,
36
+ root_dir: str,
37
+ ) -> None:
38
+ super().__init__(
39
+ requested_path,
40
+ root_dir,
41
+ )
42
+
43
+ self.root_dir = root_dir
44
+
45
+ @property
46
+ def content(self) -> str:
47
+ return f'Requested path {self.requested_path!r} was outside of permitted root directory {self.root_dir!r}.'
48
+
49
+
50
+ class RequestedPathWrongTypeError(RequestedPathError):
51
+ def __init__(
52
+ self,
53
+ requested_path: str,
54
+ *,
55
+ expected_type: str,
56
+ actual_type: str | None = None,
57
+ ) -> None:
58
+ super().__init__(
59
+ requested_path,
60
+ expected_type,
61
+ actual_type,
62
+ )
63
+
64
+ self.expected_type = expected_type
65
+ self.actual_type = actual_type
66
+
67
+ @property
68
+ def content(self) -> str:
69
+ return ''.join([
70
+ f'Requested path {self.requested_path!r} must be of type {self.expected_type!r}',
71
+ *([f', but it is actually of type {self.actual_type!r}'] if self.actual_type is not None else []),
72
+ '.',
73
+ ])
74
+
75
+
76
+ class RequestedPathDoesNotExistError(RequestedPathError):
77
+ def __init__(
78
+ self,
79
+ requested_path: str,
80
+ *,
81
+ suggested_paths: ta.Sequence[str] | None = None,
82
+ ) -> None:
83
+ super().__init__(
84
+ requested_path,
85
+ suggested_paths,
86
+ )
87
+
88
+ self.suggested_paths = suggested_paths
89
+
90
+ @property
91
+ def content(self) -> str:
92
+ return ''.join([
93
+ f'Requested path {self.requested_path!r} does not exist.',
94
+ *([f' Did you mean one of these valid paths: {self.suggested_paths!r}?'] if self.suggested_paths else []),
95
+ ])
96
+
97
+
98
+ class RequestedPathWriteNotPermittedError(RequestedPathError):
99
+ @property
100
+ def content(self) -> str:
101
+ return f'Writes are not permitted to requested path {self.requested_path!r}.'
@@ -0,0 +1,36 @@
1
+ import difflib
2
+ import os.path
3
+ import typing as ta
4
+
5
+
6
+ ##
7
+
8
+
9
+ def get_path_suggestions(
10
+ bad_path: str,
11
+ n: int = 3,
12
+ *,
13
+ filter: ta.Callable[[os.DirEntry], bool] | None = None, # noqa
14
+ cutoff: float = .6,
15
+ ) -> ta.Sequence[str] | None:
16
+ dn = os.path.dirname(bad_path)
17
+ try:
18
+ sdi = os.scandir(dn)
19
+ except FileNotFoundError:
20
+ return None
21
+
22
+ fl = [
23
+ e.name
24
+ for e in sdi
25
+ if (filter is None or filter(e))
26
+ ]
27
+
28
+ return [
29
+ os.path.join(dn, sn)
30
+ for sn in difflib.get_close_matches(
31
+ os.path.basename(bad_path),
32
+ fl,
33
+ n,
34
+ cutoff=cutoff,
35
+ )
36
+ ]
File without changes
@@ -0,0 +1,104 @@
1
+ """
2
+ TODO:
3
+ - must read file before editing
4
+ - must re-read file if file has been modified
5
+ - loosened replacer helpers
6
+ - accept diff format impl
7
+ - injectable confirmation, diff format
8
+ """
9
+ from omlish import lang
10
+
11
+ from ....tools.execution.catalog import ToolCatalogEntry
12
+ from ....tools.execution.reflect import reflect_tool_catalog_entry
13
+ from ..context import tool_fs_context
14
+ from ..errors import RequestedPathError
15
+
16
+
17
+ ##
18
+
19
+
20
+ class EditToolError(RequestedPathError, lang.Abstract):
21
+ pass
22
+
23
+
24
+ class EmptyNewStringError(EditToolError):
25
+ @property
26
+ def content(self) -> str:
27
+ return f'The requested edit to {self.requested_path!r} was given an empty "old_string" parameter.'
28
+
29
+
30
+ class OldStringNotPresentError(EditToolError):
31
+ @property
32
+ def content(self) -> str:
33
+ return f'The requested edit to {self.requested_path!r} did not contain the given "old_string" parameter.'
34
+
35
+
36
+ class OldStringPresentMultipleTimesError(EditToolError):
37
+ @property
38
+ def content(self) -> str:
39
+ return f'The requested edit to {self.requested_path!r} contained the given "old_string" parameter multiple times.' # noqa
40
+
41
+
42
+ ##
43
+
44
+
45
+ def execute_edit_tool(
46
+ *,
47
+ file_path: str,
48
+ old_string: str,
49
+ new_string: str,
50
+ replace_all: bool = False,
51
+ ) -> str:
52
+ """
53
+ Edits the given file by replacing the string given by the 'old_string' parameter with the string given by the
54
+ 'new_string' parameter.
55
+
56
+ The file must exist, must be a valid text file, and must be given as an absolute path.
57
+
58
+ If the 'replace_all' parameter is false (the default) then 'new_string' must be present exactly once in the file,
59
+ otherwise the operation will fail. If 'replace_all' is true then all instances of 'old_string' will be replaced by
60
+ 'new_string', but the operation will fail if there are no instances of 'old_string'
61
+ present in the file.
62
+
63
+ For the operation to succeed, both 'old_string' and 'new_string' must be EXACT, including all exact indentation and
64
+ other whitespace. This *includes* trailing newlines - this operates on the file as a single string, not a list of
65
+ lines.
66
+
67
+ Args:
68
+ file_path: The path of the file to edit. Must be an absolute path.
69
+ old_string: The old string to be replaced. May not be empty, and must be exact, including all exact whitespace.
70
+ new_string: The new string to replace the old string with.
71
+ replace_all: If false (the default) then exactly one instance of 'old_string' must be present in the file to be
72
+ replaced. If true then all instances of 'old_string' will be replaced by 'new_string', but at least one
73
+ instance of 'old_string' must be present in the file.
74
+ """
75
+
76
+ if not old_string:
77
+ raise EmptyNewStringError(file_path)
78
+
79
+ ctx = tool_fs_context()
80
+ ctx.check_stat_file(file_path, text=True, write=True)
81
+
82
+ with open(file_path) as f:
83
+ old_file = f.read()
84
+
85
+ n = old_file.count(old_string)
86
+ if not n:
87
+ raise OldStringNotPresentError(file_path)
88
+
89
+ if not replace_all and n != 1:
90
+ raise OldStringPresentMultipleTimesError(file_path)
91
+
92
+ new_file = old_file.replace(old_string, new_string)
93
+
94
+ # FIXME: confirm lol
95
+
96
+ with open(file_path, 'w') as f:
97
+ f.write(new_file)
98
+
99
+ return 'The file has been edited successfully.'
100
+
101
+
102
+ @lang.cached_function
103
+ def edit_tool() -> ToolCatalogEntry:
104
+ return reflect_tool_catalog_entry(execute_edit_tool)
@@ -0,0 +1,38 @@
1
+ import io
2
+ import os
3
+
4
+ from omlish import lang
5
+
6
+ from ....tools.execution.catalog import ToolCatalogEntry
7
+ from ....tools.execution.reflect import reflect_tool_catalog_entry
8
+ from ..context import tool_fs_context
9
+
10
+
11
+ ##
12
+
13
+
14
+ def execute_ls_tool(
15
+ dir_path: str,
16
+ ) -> str:
17
+ """
18
+ Lists the contents of the specified dir.
19
+
20
+ Args:
21
+ dir_path: The dir to list the contents of. Must be an absolute path.
22
+ """
23
+
24
+ ctx = tool_fs_context()
25
+ ctx.check_stat_dir(dir_path)
26
+
27
+ out = io.StringIO()
28
+ out.write('<dir>\n')
29
+ for e in sorted(os.scandir(dir_path), key=lambda e: e.name): # noqa
30
+ out.write(f'{e.name}{"/" if e.is_dir() else ""}\n')
31
+ out.write('</dir>\n')
32
+
33
+ return out.getvalue()
34
+
35
+
36
+ @lang.cached_function
37
+ def ls_tool() -> ToolCatalogEntry:
38
+ return reflect_tool_catalog_entry(execute_ls_tool)
@@ -0,0 +1,115 @@
1
+ """
2
+ TODO:
3
+ - better bad unicode handling
4
+ - read whole file if < some size, report filesize / num lines / mtime inline
5
+ - fs cache
6
+ - track changes
7
+ """
8
+ import io
9
+ import itertools
10
+
11
+ from omlish import lang
12
+
13
+ from ....tools.execution.catalog import ToolCatalogEntry
14
+ from ....tools.execution.reflect import reflect_tool_catalog_entry
15
+ from ....tools.reflect import tool_spec_override
16
+ from ....tools.types import ToolParam
17
+ from ..context import tool_fs_context
18
+
19
+
20
+ ##
21
+
22
+
23
+ DEFAULT_MAX_NUM_LINES = 2_000
24
+
25
+ MAX_LINE_LENGTH = 2_000
26
+
27
+
28
+ @tool_spec_override(
29
+ desc=f"""
30
+ Reads a file from the local filesystem. You can access any file directly by using this tool.
31
+
32
+ Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that
33
+ path is valid. It is okay to read a file that does not exist; an error will be returned.
34
+
35
+ Usage:
36
+ - The file_path parameter must be an absolute path, not a relative path.
37
+ - By default, it reads up to {DEFAULT_MAX_NUM_LINES} lines starting from the beginning of the file.
38
+ - You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to
39
+ read the whole file by not providing these parameters.
40
+ - Any lines longer than {MAX_LINE_LENGTH} characters will be truncated with "...".
41
+ - Invalid unicode characters will be replaced with the unicode replacement character "\\ufffd".
42
+ - Results are returned using cat -n format, with line numbers starting at 1 and suffixed with a pipe character
43
+ "|".
44
+ - This tool cannot read binary files, including images.
45
+ """,
46
+ params=[
47
+ ToolParam(
48
+ 'file_path',
49
+ desc='The absolute path to the file to read.',
50
+ ),
51
+ ToolParam(
52
+ 'line_offset',
53
+ desc='The line number to start reading from (0-based).',
54
+ ),
55
+ ToolParam(
56
+ 'num_lines',
57
+ desc=f'The number of lines to read (defaults to {DEFAULT_MAX_NUM_LINES}).',
58
+ ),
59
+ ],
60
+ )
61
+ def execute_read_tool(
62
+ file_path: str,
63
+ *,
64
+ line_offset: int = 0,
65
+ num_lines: int = DEFAULT_MAX_NUM_LINES,
66
+ ) -> str:
67
+ ctx = tool_fs_context()
68
+ ctx.check_stat_file(file_path, text=True)
69
+
70
+ out = io.StringIO()
71
+ out.write('<file>\n')
72
+
73
+ zp = len(str(line_offset + num_lines))
74
+ n = line_offset
75
+ has_trunc = False # noqa
76
+ with open(file_path, errors='replace') as f:
77
+ fi = iter(f)
78
+
79
+ for line in itertools.islice(fi, line_offset, line_offset + num_lines):
80
+ out.write(f'{str(n + 1).zfill(zp):}|')
81
+ line = line.removesuffix('\n')
82
+ if len(line) > MAX_LINE_LENGTH:
83
+ has_trunc = True # noqa
84
+ out.write(line[:MAX_LINE_LENGTH])
85
+ out.write('...')
86
+ else:
87
+ out.write(line)
88
+ out.write('\n')
89
+ n += 1
90
+
91
+ # tl = n
92
+ # if (ml := lang.ilen(fi)):
93
+ # check.state(n == num_lines)
94
+ # tl += ml
95
+
96
+ try:
97
+ next(fi)
98
+ except StopIteration:
99
+ has_more = False
100
+ else:
101
+ has_more = True
102
+
103
+ out.write(f'</file>\n')
104
+
105
+ if has_more:
106
+ out.write(
107
+ f'\n(File has more lines. Use "line_offset" parameter to read beyond line {line_offset + num_lines}.)\n',
108
+ )
109
+
110
+ return out.getvalue()
111
+
112
+
113
+ @lang.cached_function
114
+ def read_tool() -> ToolCatalogEntry:
115
+ return reflect_tool_catalog_entry(execute_read_tool)
File without changes
@@ -0,0 +1,40 @@
1
+ from omlish import lang
2
+
3
+ from .....tools.execution.catalog import ToolCatalogEntry
4
+ from .....tools.execution.reflect import reflect_tool_catalog_entry
5
+ from ...context import tool_fs_context
6
+ from .rendering import LsLinesRenderer
7
+ from .running import LsRunner
8
+
9
+
10
+ ##
11
+
12
+
13
+ def execute_recursive_ls_tool(
14
+ base_path: str,
15
+ ) -> str:
16
+ """
17
+ Recursively lists the directory contents of the given base path.
18
+
19
+ Args:
20
+ base_path: The path of the directory to list the contents of. Must be absolute.
21
+
22
+ Returns:
23
+ A formatted string of the recursive directory contents.
24
+ """
25
+
26
+ ft_ctx = tool_fs_context()
27
+ ft_ctx.check_requested_path(base_path)
28
+
29
+ root = LsRunner().run(base_path)
30
+ lines = LsLinesRenderer().render(root)
31
+ return '\n'.join([
32
+ '<dir>',
33
+ *lines.lines,
34
+ '</dir>',
35
+ ])
36
+
37
+
38
+ @lang.cached_function
39
+ def recursive_ls_tool() -> ToolCatalogEntry:
40
+ return reflect_tool_catalog_entry(execute_recursive_ls_tool)
File without changes
@@ -0,0 +1,54 @@
1
+ import typing as ta
2
+
3
+ from omlish import dataclasses as dc
4
+
5
+ from ...tools.execution.context import tool_context
6
+ from .types import TodoItem
7
+
8
+
9
+ ##
10
+
11
+
12
+ def _try_int(s: str | None) -> int | None:
13
+ if s is None:
14
+ return None
15
+ try:
16
+ return int(s)
17
+ except ValueError:
18
+ return None
19
+
20
+
21
+ class TodoContext:
22
+ def __init__(
23
+ self,
24
+ items: ta.Sequence[TodoItem] | None = None,
25
+ ) -> None:
26
+ super().__init__()
27
+
28
+ self._items = items
29
+ self._next_id: int = 1
30
+
31
+ def get_items(self) -> ta.Sequence[TodoItem] | None:
32
+ return self._items
33
+
34
+ def set_items(self, items: ta.Sequence[TodoItem] | None) -> ta.Sequence[TodoItem] | None:
35
+ if items and any(item.id is None for item in items):
36
+ max_id = max([
37
+ *[ii for item in items if (ii := _try_int(item.id)) is not None],
38
+ self._next_id - 1,
39
+ ])
40
+ new_items: list[TodoItem] = []
41
+ for item in items:
42
+ if item.id is None:
43
+ item = dc.replace(item, id=str(max_id + 1))
44
+ max_id += 1
45
+ new_items.append(item)
46
+ items = new_items
47
+ self._next_id = max_id + 1
48
+
49
+ self._items = list(items) if items is not None else None
50
+ return items
51
+
52
+
53
+ def tool_todo_context() -> TodoContext:
54
+ return tool_context()[TodoContext]
File without changes
@@ -0,0 +1,44 @@
1
+ import typing as ta
2
+
3
+ from omlish import lang
4
+
5
+ from ....tools.execution.catalog import ToolCatalogEntry
6
+ from ....tools.execution.reflect import reflect_tool_catalog_entry
7
+ from ....tools.reflect import tool_spec_override
8
+ from ..context import tool_todo_context
9
+ from ..types import TodoItem
10
+
11
+
12
+ ##
13
+
14
+
15
+ @tool_spec_override(
16
+ desc="""
17
+ Use this tool to read the current todo list for your current session. This tool should be used proactively and
18
+ frequently to ensure that you are aware of the status of the current subtask list.
19
+
20
+ You should make use of this tool often, especially in the following situations:
21
+ - At the beginning of conversations to see what's pending.
22
+ - Before starting new tasks to prioritize work.
23
+ - When the user asks about previous tasks or plans.
24
+ - Whenever you're uncertain about what to do next.
25
+ - After completing tasks to update your understanding of remaining work.
26
+ - After every few messages to ensure you're on track.
27
+
28
+ Usage:
29
+ - Returns a list of todo items in json format with their id, status, priority, and content.
30
+ - Use this information to track progress and plan next steps.
31
+ """,
32
+ )
33
+ def execute_todo_read_tool() -> ta.Sequence[TodoItem]:
34
+ ctx = tool_todo_context()
35
+
36
+ return ctx.get_items() or []
37
+
38
+
39
+ @lang.cached_function
40
+ def todo_read_tool() -> ToolCatalogEntry:
41
+ return reflect_tool_catalog_entry(
42
+ execute_todo_read_tool,
43
+ marshal_output=True,
44
+ )