ommlds 0.0.0.dev436__py3-none-any.whl → 0.0.0.dev480__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 (271) hide show
  1. ommlds/.omlish-manifests.json +332 -35
  2. ommlds/__about__.py +15 -9
  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/_marshal.py +2 -2
  9. ommlds/backends/anthropic/protocol/sse/_marshal.py +1 -1
  10. ommlds/backends/anthropic/protocol/sse/assemble.py +23 -7
  11. ommlds/backends/anthropic/protocol/sse/events.py +13 -0
  12. ommlds/backends/anthropic/protocol/types.py +30 -9
  13. ommlds/backends/google/protocol/__init__.py +3 -0
  14. ommlds/backends/google/protocol/_marshal.py +16 -0
  15. ommlds/backends/google/protocol/types.py +626 -0
  16. ommlds/backends/groq/_marshal.py +23 -0
  17. ommlds/backends/groq/protocol.py +249 -0
  18. ommlds/backends/mlx/generation.py +1 -1
  19. ommlds/backends/mlx/loading.py +58 -1
  20. ommlds/backends/ollama/__init__.py +0 -0
  21. ommlds/backends/ollama/protocol.py +170 -0
  22. ommlds/backends/openai/protocol/__init__.py +9 -28
  23. ommlds/backends/openai/protocol/_common.py +18 -0
  24. ommlds/backends/openai/protocol/_marshal.py +27 -0
  25. ommlds/backends/openai/protocol/chatcompletion/chunk.py +58 -31
  26. ommlds/backends/openai/protocol/chatcompletion/contentpart.py +49 -44
  27. ommlds/backends/openai/protocol/chatcompletion/message.py +55 -43
  28. ommlds/backends/openai/protocol/chatcompletion/request.py +114 -66
  29. ommlds/backends/openai/protocol/chatcompletion/response.py +71 -45
  30. ommlds/backends/openai/protocol/chatcompletion/responseformat.py +27 -20
  31. ommlds/backends/openai/protocol/chatcompletion/tokenlogprob.py +16 -7
  32. ommlds/backends/openai/protocol/completionusage.py +24 -15
  33. ommlds/backends/tavily/__init__.py +0 -0
  34. ommlds/backends/tavily/protocol.py +301 -0
  35. ommlds/backends/tinygrad/models/llama3/__init__.py +22 -14
  36. ommlds/backends/transformers/__init__.py +0 -0
  37. ommlds/backends/transformers/filecache.py +109 -0
  38. ommlds/backends/transformers/streamers.py +73 -0
  39. ommlds/cli/asyncs.py +30 -0
  40. ommlds/cli/backends/catalog.py +93 -0
  41. ommlds/cli/backends/configs.py +9 -0
  42. ommlds/cli/backends/inject.py +31 -36
  43. ommlds/cli/backends/injection.py +16 -0
  44. ommlds/cli/backends/types.py +46 -0
  45. ommlds/cli/content/__init__.py +0 -0
  46. ommlds/cli/content/messages.py +34 -0
  47. ommlds/cli/content/strings.py +42 -0
  48. ommlds/cli/inject.py +15 -32
  49. ommlds/cli/inputs/__init__.py +0 -0
  50. ommlds/cli/inputs/asyncs.py +32 -0
  51. ommlds/cli/inputs/sync.py +75 -0
  52. ommlds/cli/main.py +270 -110
  53. ommlds/cli/rendering/__init__.py +0 -0
  54. ommlds/cli/rendering/configs.py +9 -0
  55. ommlds/cli/rendering/inject.py +31 -0
  56. ommlds/cli/rendering/markdown.py +52 -0
  57. ommlds/cli/rendering/raw.py +73 -0
  58. ommlds/cli/rendering/types.py +21 -0
  59. ommlds/cli/secrets.py +21 -0
  60. ommlds/cli/sessions/base.py +1 -1
  61. ommlds/cli/sessions/chat/chat/__init__.py +0 -0
  62. ommlds/cli/sessions/chat/chat/ai/__init__.py +0 -0
  63. ommlds/cli/sessions/chat/chat/ai/configs.py +11 -0
  64. ommlds/cli/sessions/chat/chat/ai/inject.py +74 -0
  65. ommlds/cli/sessions/chat/chat/ai/injection.py +14 -0
  66. ommlds/cli/sessions/chat/chat/ai/rendering.py +70 -0
  67. ommlds/cli/sessions/chat/chat/ai/services.py +79 -0
  68. ommlds/cli/sessions/chat/chat/ai/tools.py +44 -0
  69. ommlds/cli/sessions/chat/chat/ai/types.py +28 -0
  70. ommlds/cli/sessions/chat/chat/state/__init__.py +0 -0
  71. ommlds/cli/sessions/chat/chat/state/configs.py +11 -0
  72. ommlds/cli/sessions/chat/chat/state/inject.py +36 -0
  73. ommlds/cli/sessions/chat/chat/state/inmemory.py +33 -0
  74. ommlds/cli/sessions/chat/chat/state/storage.py +52 -0
  75. ommlds/cli/sessions/chat/chat/state/types.py +38 -0
  76. ommlds/cli/sessions/chat/chat/user/__init__.py +0 -0
  77. ommlds/cli/sessions/chat/chat/user/configs.py +17 -0
  78. ommlds/cli/sessions/chat/chat/user/inject.py +62 -0
  79. ommlds/cli/sessions/chat/chat/user/interactive.py +31 -0
  80. ommlds/cli/sessions/chat/chat/user/oneshot.py +25 -0
  81. ommlds/cli/sessions/chat/chat/user/types.py +15 -0
  82. ommlds/cli/sessions/chat/configs.py +27 -0
  83. ommlds/cli/sessions/chat/driver.py +43 -0
  84. ommlds/cli/sessions/chat/inject.py +33 -65
  85. ommlds/cli/sessions/chat/phases/__init__.py +0 -0
  86. ommlds/cli/sessions/chat/phases/inject.py +27 -0
  87. ommlds/cli/sessions/chat/phases/injection.py +14 -0
  88. ommlds/cli/sessions/chat/phases/manager.py +29 -0
  89. ommlds/cli/sessions/chat/phases/types.py +29 -0
  90. ommlds/cli/sessions/chat/session.py +27 -0
  91. ommlds/cli/sessions/chat/tools/__init__.py +0 -0
  92. ommlds/cli/sessions/chat/tools/configs.py +22 -0
  93. ommlds/cli/sessions/chat/tools/confirmation.py +46 -0
  94. ommlds/cli/sessions/chat/tools/execution.py +66 -0
  95. ommlds/cli/sessions/chat/tools/fs/__init__.py +0 -0
  96. ommlds/cli/sessions/chat/tools/fs/configs.py +12 -0
  97. ommlds/cli/sessions/chat/tools/fs/inject.py +35 -0
  98. ommlds/cli/sessions/chat/tools/inject.py +88 -0
  99. ommlds/cli/sessions/chat/tools/injection.py +44 -0
  100. ommlds/cli/sessions/chat/tools/rendering.py +58 -0
  101. ommlds/cli/sessions/chat/tools/todo/__init__.py +0 -0
  102. ommlds/cli/sessions/chat/tools/todo/configs.py +12 -0
  103. ommlds/cli/sessions/chat/tools/todo/inject.py +31 -0
  104. ommlds/cli/sessions/chat/tools/weather/__init__.py +0 -0
  105. ommlds/cli/sessions/chat/tools/weather/configs.py +12 -0
  106. ommlds/cli/sessions/chat/tools/weather/inject.py +22 -0
  107. ommlds/cli/{tools/weather.py → sessions/chat/tools/weather/tools.py} +1 -1
  108. ommlds/cli/sessions/completion/configs.py +21 -0
  109. ommlds/cli/sessions/completion/inject.py +42 -0
  110. ommlds/cli/sessions/completion/session.py +35 -0
  111. ommlds/cli/sessions/embedding/configs.py +21 -0
  112. ommlds/cli/sessions/embedding/inject.py +42 -0
  113. ommlds/cli/sessions/embedding/session.py +33 -0
  114. ommlds/cli/sessions/inject.py +28 -11
  115. ommlds/cli/state/__init__.py +0 -0
  116. ommlds/cli/state/inject.py +28 -0
  117. ommlds/cli/{state.py → state/storage.py} +41 -24
  118. ommlds/minichain/__init__.py +84 -24
  119. ommlds/minichain/_marshal.py +49 -9
  120. ommlds/minichain/_typedvalues.py +2 -4
  121. ommlds/minichain/backends/catalogs/base.py +20 -1
  122. ommlds/minichain/backends/catalogs/simple.py +2 -2
  123. ommlds/minichain/backends/catalogs/strings.py +10 -8
  124. ommlds/minichain/backends/impls/anthropic/chat.py +65 -27
  125. ommlds/minichain/backends/impls/anthropic/names.py +10 -8
  126. ommlds/minichain/backends/impls/anthropic/protocol.py +109 -0
  127. ommlds/minichain/backends/impls/anthropic/stream.py +111 -43
  128. ommlds/minichain/backends/impls/duckduckgo/search.py +1 -1
  129. ommlds/minichain/backends/impls/dummy/__init__.py +0 -0
  130. ommlds/minichain/backends/impls/dummy/chat.py +69 -0
  131. ommlds/minichain/backends/impls/google/chat.py +114 -22
  132. ommlds/minichain/backends/impls/google/search.py +7 -2
  133. ommlds/minichain/backends/impls/google/stream.py +219 -0
  134. ommlds/minichain/backends/impls/google/tools.py +149 -0
  135. ommlds/minichain/backends/impls/groq/__init__.py +0 -0
  136. ommlds/minichain/backends/impls/groq/chat.py +75 -0
  137. ommlds/minichain/backends/impls/groq/names.py +48 -0
  138. ommlds/minichain/backends/impls/groq/protocol.py +143 -0
  139. ommlds/minichain/backends/impls/groq/stream.py +125 -0
  140. ommlds/minichain/backends/impls/llamacpp/chat.py +33 -18
  141. ommlds/minichain/backends/impls/llamacpp/completion.py +1 -1
  142. ommlds/minichain/backends/impls/llamacpp/format.py +4 -2
  143. ommlds/minichain/backends/impls/llamacpp/stream.py +37 -20
  144. ommlds/minichain/backends/impls/mistral.py +20 -5
  145. ommlds/minichain/backends/impls/mlx/chat.py +96 -22
  146. ommlds/minichain/backends/impls/ollama/__init__.py +0 -0
  147. ommlds/minichain/backends/impls/ollama/chat.py +199 -0
  148. ommlds/minichain/backends/impls/openai/chat.py +18 -8
  149. ommlds/minichain/backends/impls/openai/completion.py +10 -3
  150. ommlds/minichain/backends/impls/openai/embedding.py +10 -3
  151. ommlds/minichain/backends/impls/openai/format.py +131 -106
  152. ommlds/minichain/backends/impls/openai/names.py +31 -5
  153. ommlds/minichain/backends/impls/openai/stream.py +43 -25
  154. ommlds/minichain/backends/impls/tavily.py +66 -0
  155. ommlds/minichain/backends/impls/tinygrad/chat.py +23 -16
  156. ommlds/minichain/backends/impls/transformers/sentence.py +1 -1
  157. ommlds/minichain/backends/impls/transformers/tokens.py +1 -1
  158. ommlds/minichain/backends/impls/transformers/transformers.py +155 -34
  159. ommlds/minichain/backends/strings/parsing.py +1 -1
  160. ommlds/minichain/backends/strings/resolving.py +4 -1
  161. ommlds/minichain/chat/_marshal.py +16 -9
  162. ommlds/minichain/chat/choices/adapters.py +4 -4
  163. ommlds/minichain/chat/choices/services.py +1 -1
  164. ommlds/minichain/chat/choices/stream/__init__.py +0 -0
  165. ommlds/minichain/chat/choices/stream/adapters.py +35 -0
  166. ommlds/minichain/chat/choices/stream/joining.py +31 -0
  167. ommlds/minichain/chat/choices/stream/services.py +45 -0
  168. ommlds/minichain/chat/choices/stream/types.py +43 -0
  169. ommlds/minichain/chat/choices/types.py +2 -2
  170. ommlds/minichain/chat/history.py +3 -3
  171. ommlds/minichain/chat/messages.py +55 -19
  172. ommlds/minichain/chat/services.py +3 -3
  173. ommlds/minichain/chat/stream/_marshal.py +16 -0
  174. ommlds/minichain/chat/stream/joining.py +85 -0
  175. ommlds/minichain/chat/stream/services.py +15 -21
  176. ommlds/minichain/chat/stream/types.py +32 -19
  177. ommlds/minichain/chat/tools/execution.py +8 -7
  178. ommlds/minichain/chat/tools/ids.py +9 -15
  179. ommlds/minichain/chat/tools/parsing.py +17 -26
  180. ommlds/minichain/chat/transforms/base.py +29 -38
  181. ommlds/minichain/chat/transforms/metadata.py +30 -4
  182. ommlds/minichain/chat/transforms/services.py +9 -11
  183. ommlds/minichain/content/_marshal.py +44 -20
  184. ommlds/minichain/content/json.py +13 -0
  185. ommlds/minichain/content/materialize.py +14 -21
  186. ommlds/minichain/content/prepare.py +4 -0
  187. ommlds/minichain/content/transforms/interleave.py +1 -1
  188. ommlds/minichain/content/transforms/squeeze.py +1 -1
  189. ommlds/minichain/content/transforms/stringify.py +1 -1
  190. ommlds/minichain/json.py +20 -0
  191. ommlds/minichain/lib/code/__init__.py +0 -0
  192. ommlds/minichain/lib/code/prompts.py +6 -0
  193. ommlds/minichain/lib/fs/binfiles.py +108 -0
  194. ommlds/minichain/lib/fs/context.py +126 -0
  195. ommlds/minichain/lib/fs/errors.py +101 -0
  196. ommlds/minichain/lib/fs/suggestions.py +36 -0
  197. ommlds/minichain/lib/fs/tools/__init__.py +0 -0
  198. ommlds/minichain/lib/fs/tools/edit.py +104 -0
  199. ommlds/minichain/lib/fs/tools/ls.py +38 -0
  200. ommlds/minichain/lib/fs/tools/read.py +115 -0
  201. ommlds/minichain/lib/fs/tools/recursivels/__init__.py +0 -0
  202. ommlds/minichain/lib/fs/tools/recursivels/execution.py +40 -0
  203. ommlds/minichain/lib/todo/__init__.py +0 -0
  204. ommlds/minichain/lib/todo/context.py +54 -0
  205. ommlds/minichain/lib/todo/tools/__init__.py +0 -0
  206. ommlds/minichain/lib/todo/tools/read.py +44 -0
  207. ommlds/minichain/lib/todo/tools/write.py +335 -0
  208. ommlds/minichain/lib/todo/types.py +60 -0
  209. ommlds/minichain/llms/_marshal.py +25 -17
  210. ommlds/minichain/llms/types.py +4 -0
  211. ommlds/minichain/registries/globals.py +18 -4
  212. ommlds/minichain/resources.py +66 -43
  213. ommlds/minichain/search.py +1 -1
  214. ommlds/minichain/services/_marshal.py +46 -39
  215. ommlds/minichain/services/facades.py +3 -3
  216. ommlds/minichain/services/services.py +1 -1
  217. ommlds/minichain/standard.py +8 -0
  218. ommlds/minichain/stream/services.py +152 -38
  219. ommlds/minichain/stream/wrap.py +22 -24
  220. ommlds/minichain/tools/_marshal.py +1 -1
  221. ommlds/minichain/tools/execution/catalog.py +2 -1
  222. ommlds/minichain/tools/execution/context.py +34 -14
  223. ommlds/minichain/tools/execution/errors.py +15 -0
  224. ommlds/minichain/tools/execution/executors.py +8 -3
  225. ommlds/minichain/tools/execution/reflect.py +40 -5
  226. ommlds/minichain/tools/fns.py +46 -9
  227. ommlds/minichain/tools/jsonschema.py +14 -5
  228. ommlds/minichain/tools/reflect.py +54 -18
  229. ommlds/minichain/tools/types.py +33 -1
  230. ommlds/minichain/utils.py +27 -0
  231. ommlds/minichain/vectors/_marshal.py +11 -10
  232. ommlds/nanochat/LICENSE +21 -0
  233. ommlds/nanochat/__init__.py +0 -0
  234. ommlds/nanochat/rustbpe/LICENSE +21 -0
  235. ommlds/nanochat/tokenizers.py +406 -0
  236. ommlds/server/server.py +3 -3
  237. ommlds/specs/__init__.py +0 -0
  238. ommlds/specs/mcp/__init__.py +0 -0
  239. ommlds/specs/mcp/_marshal.py +23 -0
  240. ommlds/specs/mcp/protocol.py +266 -0
  241. ommlds/tools/git.py +27 -10
  242. ommlds/tools/ocr.py +8 -9
  243. ommlds/wiki/analyze.py +2 -2
  244. ommlds/wiki/text/mfh.py +1 -5
  245. ommlds/wiki/text/wtp.py +1 -3
  246. ommlds/wiki/utils/xml.py +5 -5
  247. {ommlds-0.0.0.dev436.dist-info → ommlds-0.0.0.dev480.dist-info}/METADATA +24 -21
  248. ommlds-0.0.0.dev480.dist-info/RECORD +427 -0
  249. ommlds/cli/backends/standard.py +0 -20
  250. ommlds/cli/sessions/chat/base.py +0 -42
  251. ommlds/cli/sessions/chat/interactive.py +0 -73
  252. ommlds/cli/sessions/chat/printing.py +0 -96
  253. ommlds/cli/sessions/chat/prompt.py +0 -143
  254. ommlds/cli/sessions/chat/state.py +0 -109
  255. ommlds/cli/sessions/chat/tools.py +0 -91
  256. ommlds/cli/sessions/completion/completion.py +0 -44
  257. ommlds/cli/sessions/embedding/embedding.py +0 -42
  258. ommlds/cli/tools/config.py +0 -13
  259. ommlds/cli/tools/inject.py +0 -64
  260. ommlds/minichain/chat/stream/adapters.py +0 -69
  261. ommlds/minichain/lib/fs/ls/execution.py +0 -32
  262. ommlds-0.0.0.dev436.dist-info/RECORD +0 -303
  263. /ommlds/{cli/tools → backends/google}/__init__.py +0 -0
  264. /ommlds/{minichain/lib/fs/ls → backends/groq}/__init__.py +0 -0
  265. /ommlds/{huggingface.py → backends/huggingface.py} +0 -0
  266. /ommlds/minichain/lib/fs/{ls → tools/recursivels}/rendering.py +0 -0
  267. /ommlds/minichain/lib/fs/{ls → tools/recursivels}/running.py +0 -0
  268. {ommlds-0.0.0.dev436.dist-info → ommlds-0.0.0.dev480.dist-info}/WHEEL +0 -0
  269. {ommlds-0.0.0.dev436.dist-info → ommlds-0.0.0.dev480.dist-info}/entry_points.txt +0 -0
  270. {ommlds-0.0.0.dev436.dist-info → ommlds-0.0.0.dev480.dist-info}/licenses/LICENSE +0 -0
  271. {ommlds-0.0.0.dev436.dist-info → ommlds-0.0.0.dev480.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,75 @@
1
+ import sys
2
+ import typing as ta
3
+
4
+ from omlish import check
5
+ from omlish import lang
6
+
7
+
8
+ with lang.auto_proxy_import(globals()):
9
+ from omlish.subprocesses import editor
10
+ from omlish.subprocesses import sync as sync_subprocesses
11
+
12
+
13
+ ##
14
+
15
+
16
+ class SyncStringInput(ta.Protocol):
17
+ def __call__(self) -> str: ...
18
+
19
+
20
+ class InputSyncStringInput:
21
+ DEFAULT_PROMPT: ta.ClassVar[str] = '> '
22
+
23
+ def __init__(
24
+ self,
25
+ prompt: str | None = None,
26
+ *,
27
+ use_readline: bool | ta.Literal['auto'] = False,
28
+ ) -> None:
29
+ super().__init__()
30
+
31
+ if prompt is None:
32
+ prompt = self.DEFAULT_PROMPT
33
+ self._prompt = prompt
34
+ self._use_readline = use_readline
35
+
36
+ self._handled_readline = False
37
+
38
+ def _handle_readline(self) -> None:
39
+ if self._handled_readline:
40
+ return
41
+ self._handled_readline = True
42
+
43
+ if not self._use_readline:
44
+ return
45
+
46
+ if self._use_readline == 'auto':
47
+ if not sys.stdin.isatty():
48
+ return
49
+
50
+ try:
51
+ import readline # noqa
52
+ except ImportError:
53
+ pass
54
+
55
+ def __call__(self) -> str:
56
+ self._handle_readline()
57
+ return input(self._prompt)
58
+
59
+
60
+ class FileSyncStringInput(InputSyncStringInput):
61
+ def __init__(self, path: str) -> None:
62
+ super().__init__()
63
+
64
+ self._path = check.non_empty_str(path)
65
+
66
+ def __call__(self) -> str:
67
+ with open(self._path) as f:
68
+ return f.read()
69
+
70
+
71
+ class UserEditorSyncStringInput(InputSyncStringInput):
72
+ def __call__(self) -> str:
73
+ if (ec := editor.edit_text_with_user_editor('', sync_subprocesses.subprocesses)) is None:
74
+ raise EOFError
75
+ return ec
ommlds/cli/main.py CHANGED
@@ -1,170 +1,331 @@
1
1
  """
2
- See:
3
- - https://github.com/simonw/llm
4
- - https://github.com/TheR1D/shell_gpt
5
- - https://github.com/paul-gauthier/aider
2
+ TODO:
3
+ - bootstrap lol
6
4
  """
7
- import argparse
5
+ import abc
8
6
  import functools
9
- import os.path
10
- import sys
11
7
  import typing as ta
12
8
 
13
9
  import anyio
14
10
 
15
- from omdev.home.secrets import load_secrets
16
11
  from omlish import check
12
+ from omlish import dataclasses as dc
17
13
  from omlish import inject as inj
18
14
  from omlish import lang
19
- from omlish.diag import pycharm
15
+ from omlish.argparse import all as ap
20
16
  from omlish.logs import all as logs
21
- from omlish.subprocesses.editor import edit_text_with_user_editor
22
- from omlish.subprocesses.sync import subprocesses
23
17
 
24
- from .. import minichain as mc
25
18
  from .inject import bind_main
19
+ from .secrets import install_secrets
26
20
  from .sessions.base import Session
27
- from .sessions.chat.interactive import InteractiveChatSession
28
- from .sessions.chat.prompt import PromptChatSession
29
- from .sessions.completion.completion import CompletionSession
30
- from .sessions.embedding.embedding import EmbeddingSession
31
- from .tools.config import ToolsConfig
21
+ from .sessions.chat.configs import ChatConfig
22
+ from .sessions.completion.configs import CompletionConfig
23
+ from .sessions.embedding.configs import EmbeddingConfig
32
24
 
33
25
 
34
- if ta.TYPE_CHECKING:
35
- import PIL.Image as pimg # noqa
36
- else:
37
- pimg = lang.proxy_import('PIL.Image')
26
+ ##
38
27
 
39
28
 
40
- ##
29
+ MAIN_EXTRA_ARGS: ta.Sequence[ap.Arg] = [
30
+ ap.arg('-v', '--verbose', action='store_true'),
31
+ ]
41
32
 
42
33
 
43
- async def _a_main(args: ta.Any = None) -> None:
44
- logs.configure_standard_logging('INFO')
34
+ def _process_main_extra_args(args: ap.Namespace) -> None:
35
+ if args.verbose:
36
+ logs.configure_standard_logging('DEBUG')
37
+ else:
38
+ logs.configure_standard_logging('INFO')
39
+ logs.silence_noisy_loggers()
45
40
 
46
- #
47
41
 
48
- parser = argparse.ArgumentParser()
49
- parser.add_argument('prompt', nargs='*')
42
+ ##
50
43
 
51
- parser.add_argument('-b', '--backend', default='openai')
52
44
 
53
- parser.add_argument('-m', '--model-name')
45
+ class Profile(lang.Abstract):
46
+ @abc.abstractmethod
47
+ def run(self, argv: ta.Sequence[str]) -> ta.Awaitable[None]:
48
+ raise NotImplementedError
54
49
 
55
- parser.add_argument('-C', '--completion', action='store_true')
56
50
 
57
- parser.add_argument('-n', '--new', action='store_true')
51
+ ##
58
52
 
59
- parser.add_argument('-e', '--editor', action='store_true')
60
- parser.add_argument('-i', '--interactive', action='store_true')
61
- parser.add_argument('-s', '--stream', action='store_true')
62
- parser.add_argument('-M', '--markdown', action='store_true')
63
53
 
64
- parser.add_argument('-E', '--embed', action='store_true')
65
- parser.add_argument('-j', '--image', action='store_true')
54
+ # class ChatAspect(lang.Abstract):
55
+ # def get_parser_args(self) -> ta.Sequence[ap.Arg]: ...
56
+ # def set_args(self, args: ap.Namespace) -> None: ...
57
+ # def configure(self, cfg: ChatConfig) -> ChatConfig: ...
66
58
 
67
- parser.add_argument('--enable-fs-tools', action='store_true')
68
- parser.add_argument('--enable-unsafe-bash-tool', action='store_true')
69
- parser.add_argument('--enable-test-weather-tool', action='store_true')
70
- parser.add_argument('--dangerous-no-tool-confirmation', action='store_true')
71
59
 
72
- args = parser.parse_args(args)
60
+ class ChatProfile(Profile):
61
+ _args: ap.Namespace
73
62
 
74
63
  #
75
64
 
76
- content: mc.Content
77
-
78
- if args.image:
79
- content = mc.ImageContent(pimg.open(check.non_empty_str(check.single(args.prompt))))
65
+ BACKEND_ARGS: ta.ClassVar[ta.Sequence[ap.Arg]] = [
66
+ ap.arg('-b', '--backend', group='backend'),
67
+ ]
68
+
69
+ def configure_backend(self, cfg: ChatConfig) -> ChatConfig:
70
+ return dc.replace(
71
+ cfg,
72
+ backend=dc.replace(
73
+ cfg.backend,
74
+ backend=self._args.backend,
75
+ ),
76
+ )
80
77
 
81
- elif args.editor:
82
- check.arg(not args.prompt)
83
- if (ec := edit_text_with_user_editor('', subprocesses)) is None:
84
- return
85
- content = ec
78
+ #
86
79
 
87
- elif args.interactive:
88
- if args.prompt:
89
- raise ValueError('Must not provide prompt')
80
+ INPUT_ARGS: ta.ClassVar[ta.Sequence[ap.Arg]] = [
81
+ ap.arg('message', nargs='*', group='input'),
82
+ ap.arg('-i', '--interactive', action='store_true', group='input'),
83
+ ap.arg('-e', '--editor', action='store_true', group='input'),
84
+ ]
85
+
86
+ def configure_input(self, cfg: ChatConfig) -> ChatConfig:
87
+ if self._args.editor:
88
+ check.arg(not self._args.interactive)
89
+ check.arg(not self._args.message)
90
+ raise NotImplementedError
91
+
92
+ elif self._args.interactive:
93
+ check.arg(not self._args.message)
94
+ return dc.replace(
95
+ cfg,
96
+ user=dc.replace(
97
+ cfg.user,
98
+ interactive=True,
99
+ ),
100
+ )
101
+
102
+ elif self._args.message:
103
+ # TODO: '-' -> stdin
104
+ return dc.replace(
105
+ cfg,
106
+ user=dc.replace(
107
+ cfg.user,
108
+ initial_user_content=' '.join(self._args.message),
109
+ ),
110
+ )
111
+
112
+ else:
113
+ raise ValueError('Must specify input')
90
114
 
91
- elif not args.prompt:
92
- raise ValueError('Must provide prompt')
115
+ #
93
116
 
94
- else:
95
- prompt = ' '.join(args.prompt)
117
+ STATE_ARGS: ta.ClassVar[ta.Sequence[ap.Arg]] = [
118
+ ap.arg('-n', '--new', action='store_true', group='state'),
119
+ ap.arg('--ephemeral', action='store_true', group='state'),
120
+ ]
121
+
122
+ def configure_state(self, cfg: ChatConfig) -> ChatConfig:
123
+ return dc.replace(
124
+ cfg,
125
+ state=dc.replace(
126
+ cfg.state,
127
+ state='ephemeral' if self._args.ephemeral else 'new' if self._args.new else 'continue',
128
+ ),
129
+ )
96
130
 
97
- if not sys.stdin.isatty() and not pycharm.is_pycharm_hosted():
98
- stdin_data = sys.stdin.read()
99
- prompt = '\n'.join([prompt, stdin_data])
131
+ #
100
132
 
101
- content = prompt
133
+ OUTPUT_ARGS: ta.ClassVar[ta.Sequence[ap.Arg]] = [
134
+ ap.arg('-s', '--stream', action='store_true', group='output'),
135
+ ap.arg('-M', '--markdown', action='store_true', group='output'),
136
+ ]
137
+
138
+ def configure_output(self, cfg: ChatConfig) -> ChatConfig:
139
+ return dc.replace(
140
+ cfg,
141
+ ai=dc.replace(
142
+ cfg.ai,
143
+ stream=bool(self._args.stream),
144
+ ),
145
+ rendering=dc.replace(
146
+ cfg.rendering,
147
+ markdown=bool(self._args.markdown),
148
+ ),
149
+ )
102
150
 
103
151
  #
104
152
 
105
- # FIXME: lol garbage
106
- for key in [
107
- 'OPENAI_API_KEY',
108
- 'HUGGINGFACE_TOKEN',
109
- 'TAVILY_API_KEY',
110
- 'ANTHROPIC_API_KEY',
111
- 'MISTRAL_API_KEY',
112
- 'GEMINI_API_KEY',
113
- ]:
114
- if (sec := load_secrets().try_get(key.lower())) is not None:
115
- os.environ[key] = sec.reveal()
153
+ TOOLS_ARGS: ta.ClassVar[ta.Sequence[ap.Arg]] = [
154
+ ap.arg('--enable-fs-tools', action='store_true', group='tools'),
155
+ ap.arg('--enable-todo-tools', action='store_true', group='tools'),
156
+ # ap.arg('--enable-unsafe-tools-do-not-use-lol', action='store_true', group='tools'),
157
+ ap.arg('--enable-test-weather-tool', action='store_true', group='tools'),
158
+ ]
159
+
160
+ def configure_tools(self, cfg: ChatConfig) -> ChatConfig:
161
+ return dc.replace(
162
+ cfg,
163
+ ai=dc.replace(
164
+ cfg.ai,
165
+ enable_tools=(
166
+ self._args.enable_fs_tools or
167
+ self._args.enable_todo_tools or
168
+ # self._args.enable_unsafe_tools_do_not_use_lol or
169
+ self._args.enable_test_weather_tool or
170
+ self._args.code
171
+ ),
172
+ ),
173
+ tools=dc.replace(
174
+ cfg.tools,
175
+ enabled_tools={ # noqa
176
+ *(cfg.tools.enabled_tools or []),
177
+ *(['fs'] if self._args.enable_fs_tools else []),
178
+ *(['todo'] if self._args.enable_todo_tools else []),
179
+ *(['weather'] if self._args.enable_test_weather_tool else []),
180
+ },
181
+ ),
182
+ )
116
183
 
117
184
  #
118
185
 
119
- session_cfg: Session.Config
186
+ CODE_CONFIG: ta.ClassVar[ta.Sequence[ap.Arg]] = [
187
+ ap.arg('-c', '--code', action='store_true', group='code'),
188
+ ]
120
189
 
121
- if args.interactive:
122
- session_cfg = InteractiveChatSession.Config(
123
- backend=args.backend,
124
- model_name=args.model_name,
125
- new=bool(args.new),
126
- dangerous_no_tool_confirmation=bool(args.dangerous_no_tool_confirmation),
127
- )
190
+ def configure_code(self, cfg: ChatConfig) -> ChatConfig:
191
+ if not self._args.code:
192
+ return cfg
128
193
 
129
- elif args.embed:
130
- session_cfg = EmbeddingSession.Config(
131
- content, # noqa
132
- backend=args.backend,
194
+ cfg = dc.replace(
195
+ cfg,
196
+ ai=dc.replace(
197
+ cfg.ai,
198
+ enable_tools=True,
199
+ ),
133
200
  )
134
201
 
135
- elif args.completion:
136
- session_cfg = CompletionSession.Config(
137
- content, # noqa
202
+ if self._args.new or self._args.ephemeral:
203
+ from ..minichain.lib.code.prompts import CODE_AGENT_SYSTEM_PROMPT
204
+ system_content = CODE_AGENT_SYSTEM_PROMPT
205
+
206
+ cfg = dc.replace(
207
+ cfg,
208
+ user=dc.replace(
209
+ cfg.user,
210
+ initial_system_content=system_content,
211
+ ),
212
+ )
213
+
214
+ return cfg
215
+
216
+ #
217
+
218
+ async def run(self, argv: ta.Sequence[str]) -> None:
219
+ parser = ap.ArgumentParser()
220
+
221
+ for grp_name, grp_args in [
222
+ ('backend', self.BACKEND_ARGS),
223
+ ('input', self.INPUT_ARGS),
224
+ ('state', self.STATE_ARGS),
225
+ ('output', self.OUTPUT_ARGS),
226
+ ('tools', self.TOOLS_ARGS),
227
+ ('code', self.CODE_CONFIG),
228
+ ]:
229
+ grp = parser.add_argument_group(grp_name)
230
+ for a in grp_args:
231
+ grp.add_argument(*a.args, **a.kwargs)
232
+
233
+ self._args = parser.parse_args(argv)
234
+
235
+ cfg = ChatConfig()
236
+ cfg = self.configure_backend(cfg)
237
+ cfg = self.configure_input(cfg)
238
+ cfg = self.configure_state(cfg)
239
+ cfg = self.configure_output(cfg)
240
+ cfg = self.configure_tools(cfg)
241
+ cfg = self.configure_code(cfg)
242
+
243
+ with inj.create_managed_injector(bind_main(
244
+ session_cfg=cfg,
245
+ )) as injector:
246
+ await injector[Session].run()
247
+
248
+
249
+ ##
250
+
251
+
252
+ class CompletionProfile(Profile):
253
+ async def run(self, argv: ta.Sequence[str]) -> None:
254
+ parser = ap.ArgumentParser()
255
+ parser.add_argument('prompt', nargs='*')
256
+ parser.add_argument('-b', '--backend', default='openai')
257
+ args = parser.parse_args(argv)
258
+
259
+ content = ' '.join(args.prompt)
260
+
261
+ cfg = CompletionConfig(
262
+ check.non_empty_str(content),
138
263
  backend=args.backend,
139
264
  )
140
265
 
141
- else:
142
- session_cfg = PromptChatSession.Config(
143
- content, # noqa
266
+ with inj.create_managed_injector(bind_main(
267
+ session_cfg=cfg,
268
+ )) as injector:
269
+ await injector[Session].run()
270
+
271
+
272
+ ##
273
+
274
+
275
+ class EmbedProfile(Profile):
276
+ async def run(self, argv: ta.Sequence[str]) -> None:
277
+ parser = ap.ArgumentParser()
278
+ parser.add_argument('prompt', nargs='*')
279
+ parser.add_argument('-b', '--backend', default='openai')
280
+ args = parser.parse_args(argv)
281
+
282
+ content = ' '.join(args.prompt)
283
+
284
+ cfg = EmbeddingConfig(
285
+ check.non_empty_str(content),
144
286
  backend=args.backend,
145
- model_name=args.model_name,
146
- new=bool(args.new),
147
- stream=bool(args.stream),
148
- markdown=bool(args.markdown),
149
- dangerous_no_tool_confirmation=bool(args.dangerous_no_tool_confirmation),
150
287
  )
151
288
 
152
- #
289
+ with inj.create_managed_injector(bind_main(
290
+ session_cfg=cfg,
291
+ )) as injector:
292
+ await injector[Session].run()
153
293
 
154
- tools_config = ToolsConfig(
155
- enable_fs_tools=args.enable_fs_tools,
156
- enable_unsafe_bash_tool=args.enable_unsafe_bash_tool,
157
- enable_test_weather_tool=args.enable_test_weather_tool,
158
- )
159
294
 
160
- #
295
+ ##
296
+
297
+
298
+ PROFILE_TYPES: ta.Mapping[str, type[Profile]] = {
299
+ 'chat': ChatProfile,
300
+ 'complete': CompletionProfile,
301
+ 'embed': EmbedProfile,
302
+ }
303
+
304
+
305
+ ##
306
+
307
+
308
+ MAIN_PROFILE_ARGS: ta.Sequence[ap.Arg] = [
309
+ ap.arg('-p', '--profile', default='chat'),
310
+ ap.arg('args', nargs=ap.REMAINDER),
311
+ ]
312
+
313
+
314
+ async def _a_main(argv: ta.Any = None) -> None:
315
+ parser = ap.ArgumentParser()
316
+
317
+ for a in [*MAIN_PROFILE_ARGS, *MAIN_EXTRA_ARGS]:
318
+ parser.add_argument(*a.args, **a.kwargs)
319
+
320
+ args, unk_args = parser.parse_known_args(argv)
321
+
322
+ _process_main_extra_args(args)
323
+
324
+ install_secrets()
161
325
 
162
- with inj.create_managed_injector(bind_main(
163
- session_cfg=session_cfg,
164
- tools_config=tools_config,
165
- enable_backend_strings=isinstance(session_cfg, PromptChatSession.Config),
166
- )) as injector:
167
- await injector[Session].run()
326
+ profile_cls = PROFILE_TYPES[args.profile]
327
+ profile = profile_cls()
328
+ await profile.run([*unk_args, *args.args])
168
329
 
169
330
 
170
331
  def _main(args: ta.Any = None) -> None:
@@ -173,7 +334,6 @@ def _main(args: ta.Any = None) -> None:
173
334
  _a_main,
174
335
  args,
175
336
  ),
176
- backend='asyncio',
177
337
  ) # noqa
178
338
 
179
339
 
File without changes
@@ -0,0 +1,9 @@
1
+ from omlish import dataclasses as dc
2
+
3
+
4
+ ##
5
+
6
+
7
+ @dc.dataclass(frozen=True, kw_only=True)
8
+ class RenderingConfig:
9
+ markdown: bool = False
@@ -0,0 +1,31 @@
1
+ from omlish import inject as inj
2
+ from omlish import lang
3
+
4
+ from .configs import RenderingConfig
5
+
6
+
7
+ with lang.auto_proxy_import(globals()):
8
+ from . import markdown as _markdown
9
+ from . import raw as _raw
10
+ from . import types as _types
11
+
12
+
13
+ ##
14
+
15
+
16
+ def bind_rendering(cfg: RenderingConfig = RenderingConfig()) -> inj.Elements:
17
+ els: list[inj.Elemental] = []
18
+
19
+ if cfg.markdown:
20
+ els.extend([
21
+ inj.bind(_types.ContentRendering, to_ctor=_markdown.MarkdownContentRendering, singleton=True),
22
+ inj.bind(_types.StreamContentRendering, to_ctor=_markdown.MarkdownStreamContentRendering, singleton=True),
23
+ ])
24
+
25
+ else:
26
+ els.extend([
27
+ inj.bind(_types.ContentRendering, to_ctor=_raw.RawContentRendering, singleton=True),
28
+ inj.bind(_types.StreamContentRendering, to_ctor=_raw.RawStreamContentRendering, singleton=True),
29
+ ])
30
+
31
+ return inj.as_elements(*els)
@@ -0,0 +1,52 @@
1
+ import typing as ta
2
+
3
+ from omdev.tui import rich
4
+ from omlish import lang
5
+
6
+ from ... import minichain as mc
7
+ from ..content.strings import ContentStringifier
8
+ from ..content.strings import HasContentStringifier
9
+ from .types import ContentRendering
10
+ from .types import StreamContentRendering
11
+
12
+
13
+ ##
14
+
15
+
16
+ class MarkdownContentRendering(ContentRendering, HasContentStringifier):
17
+ def __init__(
18
+ self,
19
+ *,
20
+ content_stringifier: ContentStringifier | None = None,
21
+ ) -> None:
22
+ super().__init__(content_stringifier=content_stringifier)
23
+
24
+ async def render_content(self, content: 'mc.Content') -> None:
25
+ if (s := self._content_stringifier.stringify_content(content)) is not None and (s := s.strip()):
26
+ rich.Console().print(rich.Markdown(s))
27
+
28
+
29
+ class MarkdownStreamContentRendering(StreamContentRendering, HasContentStringifier):
30
+ def __init__(
31
+ self,
32
+ *,
33
+ content_stringifier: ContentStringifier | None = None,
34
+ ) -> None:
35
+ super().__init__(content_stringifier=content_stringifier)
36
+
37
+ @ta.final
38
+ class _ContextInstance(ContentRendering, lang.AsyncExitStacked):
39
+ def __init__(self, owner: 'MarkdownStreamContentRendering') -> None:
40
+ self._owner = owner
41
+
42
+ _ir: rich.MarkdownLiveStream
43
+
44
+ async def _async_enter_contexts(self) -> None:
45
+ self._ir = self._enter_context(rich.IncrementalMarkdownLiveStream())
46
+
47
+ async def render_content(self, content: 'mc.Content') -> None:
48
+ if (s := self._owner._content_stringifier.stringify_content(content)) is not None: # noqa: SLF001
49
+ self._ir.feed(s)
50
+
51
+ def create_context(self) -> ta.AsyncContextManager[ContentRendering]:
52
+ return MarkdownStreamContentRendering._ContextInstance(self)
@@ -0,0 +1,73 @@
1
+ import typing as ta
2
+
3
+ from omlish import lang
4
+
5
+ from ... import minichain as mc
6
+ from ..content.strings import ContentStringifier
7
+ from ..content.strings import HasContentStringifier
8
+ from .types import ContentRendering
9
+ from .types import StreamContentRendering
10
+
11
+
12
+ ##
13
+
14
+
15
+ class RawContentRendering(ContentRendering, HasContentStringifier):
16
+ def __init__(
17
+ self,
18
+ *,
19
+ printer: ta.Callable[[str], ta.Awaitable[None]] | None = None,
20
+ content_stringifier: ContentStringifier | None = None,
21
+ ) -> None:
22
+ super().__init__(content_stringifier=content_stringifier)
23
+
24
+ if printer is None:
25
+ printer = lang.as_async(print)
26
+ self._printer = printer
27
+
28
+ async def render_content(self, content: 'mc.Content') -> None:
29
+ if (s := self._content_stringifier.stringify_content(content)) is not None:
30
+ await self._printer(s)
31
+
32
+
33
+ class RawStreamContentRendering(StreamContentRendering, HasContentStringifier):
34
+ class Output(ta.Protocol):
35
+ def write(self, s: str) -> ta.Awaitable[None]: ...
36
+ def flush(self) -> ta.Awaitable[None]: ...
37
+
38
+ class PrintOutput:
39
+ async def write(self, s: str) -> None:
40
+ print(s, end='', flush=True)
41
+
42
+ async def flush(self) -> None:
43
+ print(flush=True)
44
+
45
+ def __init__(
46
+ self,
47
+ *,
48
+ output: Output | None = None,
49
+ content_stringifier: ContentStringifier | None = None,
50
+ ) -> None:
51
+ super().__init__(content_stringifier=content_stringifier)
52
+
53
+ if output is None:
54
+ output = RawStreamContentRendering.PrintOutput()
55
+ self._output = output
56
+
57
+ @ta.final
58
+ class _ContextInstance(ContentRendering, ta.AsyncContextManager):
59
+ def __init__(self, owner: 'RawStreamContentRendering') -> None:
60
+ self._owner = owner
61
+
62
+ async def __aenter__(self) -> ta.Self:
63
+ return self
64
+
65
+ async def __aexit__(self, *exc_info) -> None:
66
+ await self._owner._output.flush() # noqa: SLF001
67
+
68
+ async def render_content(self, content: 'mc.Content') -> None:
69
+ if (s := self._owner._content_stringifier.stringify_content(content)) is not None: # noqa: SLF001
70
+ await self._owner._output.write(s) # noqa: SLF001
71
+
72
+ def create_context(self) -> ta.AsyncContextManager[ContentRendering]:
73
+ return RawStreamContentRendering._ContextInstance(self)