ommlds 0.0.0.dev456__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 (212) hide show
  1. ommlds/.omlish-manifests.json +314 -33
  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/__init__.py +13 -1
  9. ommlds/backends/anthropic/protocol/_dataclasses.py +1625 -0
  10. ommlds/backends/anthropic/protocol/sse/assemble.py +22 -6
  11. ommlds/backends/anthropic/protocol/sse/events.py +13 -0
  12. ommlds/backends/google/protocol/__init__.py +13 -0
  13. ommlds/backends/google/protocol/_dataclasses.py +5997 -0
  14. ommlds/backends/google/protocol/types.py +5 -1
  15. ommlds/backends/groq/__init__.py +7 -0
  16. ommlds/backends/groq/_dataclasses.py +3901 -0
  17. ommlds/backends/groq/_marshal.py +23 -0
  18. ommlds/backends/groq/protocol.py +249 -0
  19. ommlds/backends/llamacpp/logging.py +4 -1
  20. ommlds/backends/mlx/caching.py +7 -3
  21. ommlds/backends/mlx/cli.py +10 -7
  22. ommlds/backends/mlx/generation.py +18 -16
  23. ommlds/backends/mlx/limits.py +10 -6
  24. ommlds/backends/mlx/loading.py +65 -5
  25. ommlds/backends/ollama/__init__.py +7 -0
  26. ommlds/backends/ollama/_dataclasses.py +3458 -0
  27. ommlds/backends/ollama/protocol.py +170 -0
  28. ommlds/backends/openai/protocol/__init__.py +15 -1
  29. ommlds/backends/openai/protocol/_dataclasses.py +7708 -0
  30. ommlds/backends/tavily/__init__.py +7 -0
  31. ommlds/backends/tavily/_dataclasses.py +1734 -0
  32. ommlds/backends/tavily/protocol.py +301 -0
  33. ommlds/backends/tinygrad/models/llama3/__init__.py +22 -14
  34. ommlds/backends/transformers/__init__.py +14 -0
  35. ommlds/backends/transformers/filecache.py +109 -0
  36. ommlds/backends/transformers/streamers.py +73 -0
  37. ommlds/cli/__init__.py +7 -0
  38. ommlds/cli/_dataclasses.py +2562 -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/messages.py +34 -0
  46. ommlds/cli/content/strings.py +42 -0
  47. ommlds/cli/inject.py +15 -32
  48. ommlds/cli/inputs/__init__.py +0 -0
  49. ommlds/cli/inputs/asyncs.py +32 -0
  50. ommlds/cli/inputs/sync.py +75 -0
  51. ommlds/cli/main.py +267 -128
  52. ommlds/cli/rendering/__init__.py +0 -0
  53. ommlds/cli/rendering/configs.py +9 -0
  54. ommlds/cli/rendering/inject.py +31 -0
  55. ommlds/cli/rendering/markdown.py +52 -0
  56. ommlds/cli/rendering/raw.py +73 -0
  57. ommlds/cli/rendering/types.py +21 -0
  58. ommlds/cli/secrets.py +21 -0
  59. ommlds/cli/sessions/base.py +1 -1
  60. ommlds/cli/sessions/chat/chat/__init__.py +0 -0
  61. ommlds/cli/sessions/chat/chat/ai/__init__.py +0 -0
  62. ommlds/cli/sessions/chat/chat/ai/configs.py +11 -0
  63. ommlds/cli/sessions/chat/chat/ai/inject.py +74 -0
  64. ommlds/cli/sessions/chat/chat/ai/injection.py +14 -0
  65. ommlds/cli/sessions/chat/chat/ai/rendering.py +70 -0
  66. ommlds/cli/sessions/chat/chat/ai/services.py +79 -0
  67. ommlds/cli/sessions/chat/chat/ai/tools.py +44 -0
  68. ommlds/cli/sessions/chat/chat/ai/types.py +28 -0
  69. ommlds/cli/sessions/chat/chat/state/__init__.py +0 -0
  70. ommlds/cli/sessions/chat/chat/state/configs.py +11 -0
  71. ommlds/cli/sessions/chat/chat/state/inject.py +36 -0
  72. ommlds/cli/sessions/chat/chat/state/inmemory.py +33 -0
  73. ommlds/cli/sessions/chat/chat/state/storage.py +52 -0
  74. ommlds/cli/sessions/chat/chat/state/types.py +38 -0
  75. ommlds/cli/sessions/chat/chat/user/__init__.py +0 -0
  76. ommlds/cli/sessions/chat/chat/user/configs.py +17 -0
  77. ommlds/cli/sessions/chat/chat/user/inject.py +62 -0
  78. ommlds/cli/sessions/chat/chat/user/interactive.py +31 -0
  79. ommlds/cli/sessions/chat/chat/user/oneshot.py +25 -0
  80. ommlds/cli/sessions/chat/chat/user/types.py +15 -0
  81. ommlds/cli/sessions/chat/configs.py +27 -0
  82. ommlds/cli/sessions/chat/driver.py +43 -0
  83. ommlds/cli/sessions/chat/inject.py +33 -65
  84. ommlds/cli/sessions/chat/phases/__init__.py +0 -0
  85. ommlds/cli/sessions/chat/phases/inject.py +27 -0
  86. ommlds/cli/sessions/chat/phases/injection.py +14 -0
  87. ommlds/cli/sessions/chat/phases/manager.py +29 -0
  88. ommlds/cli/sessions/chat/phases/types.py +29 -0
  89. ommlds/cli/sessions/chat/session.py +27 -0
  90. ommlds/cli/sessions/chat/tools/__init__.py +0 -0
  91. ommlds/cli/sessions/chat/tools/configs.py +22 -0
  92. ommlds/cli/sessions/chat/tools/confirmation.py +46 -0
  93. ommlds/cli/sessions/chat/tools/execution.py +66 -0
  94. ommlds/cli/sessions/chat/tools/fs/__init__.py +0 -0
  95. ommlds/cli/sessions/chat/tools/fs/configs.py +12 -0
  96. ommlds/cli/sessions/chat/tools/fs/inject.py +35 -0
  97. ommlds/cli/sessions/chat/tools/inject.py +88 -0
  98. ommlds/cli/sessions/chat/tools/injection.py +44 -0
  99. ommlds/cli/sessions/chat/tools/rendering.py +58 -0
  100. ommlds/cli/sessions/chat/tools/todo/__init__.py +0 -0
  101. ommlds/cli/sessions/chat/tools/todo/configs.py +12 -0
  102. ommlds/cli/sessions/chat/tools/todo/inject.py +31 -0
  103. ommlds/cli/sessions/chat/tools/weather/__init__.py +0 -0
  104. ommlds/cli/sessions/chat/tools/weather/configs.py +12 -0
  105. ommlds/cli/sessions/chat/tools/weather/inject.py +22 -0
  106. ommlds/cli/{tools/weather.py → sessions/chat/tools/weather/tools.py} +1 -1
  107. ommlds/cli/sessions/completion/configs.py +21 -0
  108. ommlds/cli/sessions/completion/inject.py +42 -0
  109. ommlds/cli/sessions/completion/session.py +35 -0
  110. ommlds/cli/sessions/embedding/configs.py +21 -0
  111. ommlds/cli/sessions/embedding/inject.py +42 -0
  112. ommlds/cli/sessions/embedding/session.py +33 -0
  113. ommlds/cli/sessions/inject.py +28 -11
  114. ommlds/cli/state/__init__.py +0 -0
  115. ommlds/cli/state/inject.py +28 -0
  116. ommlds/cli/{state.py → state/storage.py} +41 -24
  117. ommlds/minichain/__init__.py +46 -17
  118. ommlds/minichain/_dataclasses.py +15401 -0
  119. ommlds/minichain/backends/catalogs/base.py +20 -1
  120. ommlds/minichain/backends/catalogs/simple.py +2 -2
  121. ommlds/minichain/backends/catalogs/strings.py +10 -8
  122. ommlds/minichain/backends/impls/anthropic/chat.py +31 -65
  123. ommlds/minichain/backends/impls/anthropic/names.py +3 -4
  124. ommlds/minichain/backends/impls/anthropic/protocol.py +109 -0
  125. ommlds/minichain/backends/impls/anthropic/stream.py +53 -31
  126. ommlds/minichain/backends/impls/duckduckgo/search.py +5 -1
  127. ommlds/minichain/backends/impls/dummy/__init__.py +0 -0
  128. ommlds/minichain/backends/impls/dummy/chat.py +69 -0
  129. ommlds/minichain/backends/impls/google/chat.py +9 -2
  130. ommlds/minichain/backends/impls/google/search.py +6 -1
  131. ommlds/minichain/backends/impls/google/stream.py +122 -32
  132. ommlds/minichain/backends/impls/groq/__init__.py +0 -0
  133. ommlds/minichain/backends/impls/groq/chat.py +75 -0
  134. ommlds/minichain/backends/impls/groq/names.py +48 -0
  135. ommlds/minichain/backends/impls/groq/protocol.py +143 -0
  136. ommlds/minichain/backends/impls/groq/stream.py +125 -0
  137. ommlds/minichain/backends/impls/huggingface/repos.py +1 -5
  138. ommlds/minichain/backends/impls/llamacpp/chat.py +15 -3
  139. ommlds/minichain/backends/impls/llamacpp/completion.py +7 -3
  140. ommlds/minichain/backends/impls/llamacpp/stream.py +38 -19
  141. ommlds/minichain/backends/impls/mistral.py +9 -2
  142. ommlds/minichain/backends/impls/mlx/chat.py +100 -23
  143. ommlds/minichain/backends/impls/ollama/__init__.py +0 -0
  144. ommlds/minichain/backends/impls/ollama/chat.py +199 -0
  145. ommlds/minichain/backends/impls/openai/chat.py +14 -7
  146. ommlds/minichain/backends/impls/openai/completion.py +9 -2
  147. ommlds/minichain/backends/impls/openai/embedding.py +9 -2
  148. ommlds/minichain/backends/impls/openai/format.py +115 -109
  149. ommlds/minichain/backends/impls/openai/names.py +31 -5
  150. ommlds/minichain/backends/impls/openai/stream.py +33 -27
  151. ommlds/minichain/backends/impls/sentencepiece/tokens.py +9 -6
  152. ommlds/minichain/backends/impls/tavily.py +66 -0
  153. ommlds/minichain/backends/impls/tinygrad/chat.py +17 -14
  154. ommlds/minichain/backends/impls/tokenizers/tokens.py +9 -6
  155. ommlds/minichain/backends/impls/transformers/sentence.py +5 -2
  156. ommlds/minichain/backends/impls/transformers/tokens.py +10 -7
  157. ommlds/minichain/backends/impls/transformers/transformers.py +139 -20
  158. ommlds/minichain/backends/strings/parsing.py +1 -1
  159. ommlds/minichain/backends/strings/resolving.py +4 -1
  160. ommlds/minichain/chat/choices/stream/__init__.py +0 -0
  161. ommlds/minichain/chat/choices/stream/adapters.py +35 -0
  162. ommlds/minichain/chat/choices/stream/joining.py +31 -0
  163. ommlds/minichain/chat/choices/stream/services.py +45 -0
  164. ommlds/minichain/chat/choices/stream/types.py +43 -0
  165. ommlds/minichain/chat/stream/_marshal.py +4 -4
  166. ommlds/minichain/chat/stream/joining.py +85 -0
  167. ommlds/minichain/chat/stream/services.py +15 -15
  168. ommlds/minichain/chat/stream/types.py +24 -18
  169. ommlds/minichain/llms/types.py +4 -0
  170. ommlds/minichain/registries/globals.py +18 -4
  171. ommlds/minichain/resources.py +28 -3
  172. ommlds/minichain/search.py +1 -1
  173. ommlds/minichain/standard.py +8 -0
  174. ommlds/minichain/stream/services.py +19 -16
  175. ommlds/minichain/tools/reflect.py +5 -1
  176. ommlds/nanochat/LICENSE +21 -0
  177. ommlds/nanochat/__init__.py +0 -0
  178. ommlds/nanochat/rustbpe/LICENSE +21 -0
  179. ommlds/nanochat/tokenizers.py +406 -0
  180. ommlds/specs/__init__.py +0 -0
  181. ommlds/specs/mcp/__init__.py +0 -0
  182. ommlds/specs/mcp/_marshal.py +23 -0
  183. ommlds/specs/mcp/clients.py +146 -0
  184. ommlds/specs/mcp/protocol.py +371 -0
  185. ommlds/tools/git.py +13 -6
  186. ommlds/tools/ocr.py +1 -8
  187. ommlds/wiki/analyze.py +2 -2
  188. ommlds/wiki/text/mfh.py +1 -5
  189. ommlds/wiki/text/wtp.py +1 -3
  190. ommlds/wiki/utils/xml.py +5 -5
  191. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/METADATA +22 -19
  192. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/RECORD +198 -95
  193. ommlds/cli/backends/standard.py +0 -20
  194. ommlds/cli/sessions/chat/base.py +0 -42
  195. ommlds/cli/sessions/chat/code.py +0 -129
  196. ommlds/cli/sessions/chat/interactive.py +0 -71
  197. ommlds/cli/sessions/chat/printing.py +0 -97
  198. ommlds/cli/sessions/chat/prompt.py +0 -151
  199. ommlds/cli/sessions/chat/state.py +0 -110
  200. ommlds/cli/sessions/chat/tools.py +0 -100
  201. ommlds/cli/sessions/completion/completion.py +0 -44
  202. ommlds/cli/sessions/embedding/embedding.py +0 -42
  203. ommlds/cli/tools/config.py +0 -14
  204. ommlds/cli/tools/inject.py +0 -75
  205. ommlds/minichain/backends/impls/openai/format2.py +0 -210
  206. ommlds/minichain/chat/stream/adapters.py +0 -80
  207. /ommlds/{huggingface.py → backends/huggingface.py} +0 -0
  208. /ommlds/cli/{tools → content}/__init__.py +0 -0
  209. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/WHEEL +0 -0
  210. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/entry_points.txt +0 -0
  211. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/licenses/LICENSE +0 -0
  212. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/top_level.txt +0 -0
ommlds/cli/main.py CHANGED
@@ -1,191 +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.code import CodeChatSession
28
- from .sessions.chat.interactive import InteractiveChatSession
29
- from .sessions.chat.prompt import PromptChatSession
30
- from .sessions.completion.completion import CompletionSession
31
- from .sessions.embedding.embedding import EmbeddingSession
32
- 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
33
24
 
34
25
 
35
- if ta.TYPE_CHECKING:
36
- import PIL.Image as pimg # noqa
37
- else:
38
- pimg = lang.proxy_import('PIL.Image')
26
+ ##
39
27
 
40
28
 
41
- ##
29
+ MAIN_EXTRA_ARGS: ta.Sequence[ap.Arg] = [
30
+ ap.arg('-v', '--verbose', action='store_true'),
31
+ ]
42
32
 
43
33
 
44
- async def _a_main(args: ta.Any = None) -> None:
45
- 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()
46
40
 
47
- #
48
41
 
49
- parser = argparse.ArgumentParser()
50
- parser.add_argument('prompt', nargs='*')
42
+ ##
51
43
 
52
- parser.add_argument('-b', '--backend', default='openai')
53
44
 
54
- 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
55
49
 
56
- parser.add_argument('-C', '--completion', action='store_true')
57
50
 
58
- parser.add_argument('-n', '--new', action='store_true')
51
+ ##
59
52
 
60
- parser.add_argument('-e', '--editor', action='store_true')
61
- parser.add_argument('-i', '--interactive', action='store_true')
62
- parser.add_argument('-c', '--code', action='store_true')
63
- parser.add_argument('-s', '--stream', action='store_true')
64
- parser.add_argument('-M', '--markdown', action='store_true')
65
53
 
66
- parser.add_argument('-E', '--embed', action='store_true')
67
- 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: ...
68
58
 
69
- parser.add_argument('--enable-fs-tools', action='store_true')
70
- parser.add_argument('--enable-todo-tools', action='store_true')
71
- parser.add_argument('--enable-unsafe-tools-do-not-use-lol', action='store_true')
72
- parser.add_argument('--enable-test-weather-tool', action='store_true')
73
- parser.add_argument('--dangerous-no-tool-confirmation', action='store_true')
74
59
 
75
- args = parser.parse_args(args)
60
+ class ChatProfile(Profile):
61
+ _args: ap.Namespace
76
62
 
77
63
  #
78
64
 
79
- content: mc.Content | None
80
-
81
- if args.image:
82
- 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
+ )
83
77
 
84
- elif args.editor:
85
- check.arg(not args.prompt)
86
- if (ec := edit_text_with_user_editor('', subprocesses)) is None:
87
- return
88
- content = ec
78
+ #
89
79
 
90
- elif args.interactive:
91
- if args.prompt:
92
- 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
+ )
93
111
 
94
- elif args.code:
95
- if args.prompt:
96
- content = ' '.join(args.prompt)
97
112
  else:
98
- content = None
113
+ raise ValueError('Must specify input')
99
114
 
100
- elif not args.prompt:
101
- raise ValueError('Must provide prompt')
115
+ #
102
116
 
103
- else:
104
- 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
+ )
105
130
 
106
- # FIXME: ptk / maysync
107
- if not sys.stdin.isatty() and not pycharm.is_pycharm_hosted():
108
- stdin_data = sys.stdin.read()
109
- prompt = '\n'.join([prompt, stdin_data])
131
+ #
110
132
 
111
- 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
+ )
112
150
 
113
151
  #
114
152
 
115
- # FIXME: lol garbage
116
- for key in [
117
- 'OPENAI_API_KEY',
118
- 'HUGGINGFACE_TOKEN',
119
- 'TAVILY_API_KEY',
120
- 'ANTHROPIC_API_KEY',
121
- 'MISTRAL_API_KEY',
122
- 'GEMINI_API_KEY',
123
- ]:
124
- if (sec := load_secrets().try_get(key.lower())) is not None:
125
- 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
+ )
126
183
 
127
184
  #
128
185
 
129
- session_cfg: Session.Config
186
+ CODE_CONFIG: ta.ClassVar[ta.Sequence[ap.Arg]] = [
187
+ ap.arg('-c', '--code', action='store_true', group='code'),
188
+ ]
130
189
 
131
- if args.interactive:
132
- session_cfg = InteractiveChatSession.Config(
133
- backend=args.backend,
134
- model_name=args.model_name,
135
- new=bool(args.new),
136
- dangerous_no_tool_confirmation=bool(args.dangerous_no_tool_confirmation),
137
- )
190
+ def configure_code(self, cfg: ChatConfig) -> ChatConfig:
191
+ if not self._args.code:
192
+ return cfg
138
193
 
139
- elif args.code:
140
- session_cfg = CodeChatSession.Config(
141
- backend=args.backend,
142
- model_name=args.model_name,
143
- new=bool(args.new),
144
- dangerous_no_tool_confirmation=bool(args.dangerous_no_tool_confirmation),
145
- initial_message=content, # noqa
146
- markdown=bool(args.markdown),
194
+ cfg = dc.replace(
195
+ cfg,
196
+ ai=dc.replace(
197
+ cfg.ai,
198
+ enable_tools=True,
199
+ ),
147
200
  )
148
201
 
149
- elif args.embed:
150
- session_cfg = EmbeddingSession.Config(
151
- check.not_none(content), # noqa
152
- backend=args.backend,
153
- )
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
+ ##
154
250
 
155
- elif args.completion:
156
- session_cfg = CompletionSession.Config(
157
- check.not_none(content), # noqa
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),
158
263
  backend=args.backend,
159
264
  )
160
265
 
161
- else:
162
- session_cfg = PromptChatSession.Config(
163
- check.not_none(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),
164
286
  backend=args.backend,
165
- model_name=args.model_name,
166
- new=bool(args.new),
167
- stream=bool(args.stream),
168
- markdown=bool(args.markdown),
169
- dangerous_no_tool_confirmation=bool(args.dangerous_no_tool_confirmation),
170
287
  )
171
288
 
172
- #
289
+ with inj.create_managed_injector(bind_main(
290
+ session_cfg=cfg,
291
+ )) as injector:
292
+ await injector[Session].run()
173
293
 
174
- tools_config = ToolsConfig(
175
- enable_fs_tools=args.enable_fs_tools or args.code,
176
- enable_todo_tools=args.enable_todo_tools or args.code,
177
- enable_unsafe_tools_do_not_use_lol=args.enable_unsafe_tools_do_not_use_lol,
178
- enable_test_weather_tool=args.enable_test_weather_tool,
179
- )
180
294
 
181
- #
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()
182
325
 
183
- with inj.create_managed_injector(bind_main(
184
- session_cfg=session_cfg,
185
- tools_config=tools_config,
186
- enable_backend_strings=isinstance(session_cfg, (PromptChatSession.Config, CodeChatSession.Config)),
187
- )) as injector:
188
- await injector[Session].run()
326
+ profile_cls = PROFILE_TYPES[args.profile]
327
+ profile = profile_cls()
328
+ await profile.run([*unk_args, *args.args])
189
329
 
190
330
 
191
331
  def _main(args: ta.Any = None) -> None:
@@ -194,7 +334,6 @@ def _main(args: ta.Any = None) -> None:
194
334
  _a_main,
195
335
  args,
196
336
  ),
197
- backend='asyncio',
198
337
  ) # noqa
199
338
 
200
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)
@@ -0,0 +1,21 @@
1
+ import abc
2
+ import typing as ta
3
+
4
+ from omlish import lang
5
+
6
+ from ... import minichain as mc
7
+
8
+
9
+ ##
10
+
11
+
12
+ class ContentRendering(lang.Abstract):
13
+ @abc.abstractmethod
14
+ def render_content(self, content: 'mc.Content') -> ta.Awaitable[None]:
15
+ raise NotImplementedError
16
+
17
+
18
+ class StreamContentRendering(lang.Abstract):
19
+ @abc.abstractmethod
20
+ def create_context(self) -> ta.AsyncContextManager[ContentRendering]:
21
+ raise NotImplementedError
ommlds/cli/secrets.py ADDED
@@ -0,0 +1,21 @@
1
+ import os
2
+
3
+ from omdev.home.secrets import load_secrets
4
+
5
+
6
+ ##
7
+
8
+
9
+ def install_secrets() -> None:
10
+ # FIXME: lol garbage
11
+ for key in [
12
+ 'ANTHROPIC_API_KEY',
13
+ 'GEMINI_API_KEY',
14
+ 'GROQ_API_KEY',
15
+ 'HUGGINGFACE_TOKEN',
16
+ 'MISTRAL_API_KEY',
17
+ 'OPENAI_API_KEY',
18
+ 'TAVILY_API_KEY',
19
+ ]:
20
+ if (sec := load_secrets().try_get(key.lower())) is not None:
21
+ os.environ[key] = sec.reveal()