ommlds 0.0.0.dev480__py3-none-any.whl → 0.0.0.dev503__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 (277) hide show
  1. ommlds/.omlish-manifests.json +100 -33
  2. ommlds/README.md +11 -0
  3. ommlds/__about__.py +9 -6
  4. ommlds/backends/anthropic/protocol/__init__.py +13 -1
  5. ommlds/backends/anthropic/protocol/_dataclasses.py +1625 -0
  6. ommlds/backends/anthropic/protocol/sse/events.py +2 -0
  7. ommlds/backends/cerebras/__init__.py +7 -0
  8. ommlds/backends/cerebras/_dataclasses.py +4254 -0
  9. ommlds/backends/cerebras/_marshal.py +24 -0
  10. ommlds/backends/cerebras/protocol.py +312 -0
  11. ommlds/backends/google/protocol/__init__.py +13 -0
  12. ommlds/backends/google/protocol/_dataclasses.py +5997 -0
  13. ommlds/backends/groq/__init__.py +7 -0
  14. ommlds/backends/groq/_dataclasses.py +3901 -0
  15. ommlds/backends/groq/clients.py +9 -0
  16. ommlds/backends/llamacpp/logging.py +4 -1
  17. ommlds/backends/mlx/caching.py +7 -3
  18. ommlds/backends/mlx/cli.py +10 -7
  19. ommlds/backends/mlx/generation.py +18 -16
  20. ommlds/backends/mlx/limits.py +10 -6
  21. ommlds/backends/mlx/loading.py +7 -4
  22. ommlds/backends/ollama/__init__.py +7 -0
  23. ommlds/backends/ollama/_dataclasses.py +3488 -0
  24. ommlds/backends/ollama/protocol.py +3 -0
  25. ommlds/backends/openai/protocol/__init__.py +15 -1
  26. ommlds/backends/openai/protocol/_dataclasses.py +7708 -0
  27. ommlds/backends/tavily/__init__.py +7 -0
  28. ommlds/backends/tavily/_dataclasses.py +1734 -0
  29. ommlds/backends/transformers/__init__.py +14 -0
  30. ommlds/cli/__init__.py +7 -0
  31. ommlds/cli/_dataclasses.py +3515 -0
  32. ommlds/cli/backends/catalog.py +0 -5
  33. ommlds/cli/backends/inject.py +70 -7
  34. ommlds/cli/backends/meta.py +82 -0
  35. ommlds/cli/content/messages.py +1 -1
  36. ommlds/cli/inject.py +11 -3
  37. ommlds/cli/main.py +137 -68
  38. ommlds/cli/rendering/types.py +6 -0
  39. ommlds/cli/secrets.py +2 -1
  40. ommlds/cli/sessions/base.py +1 -10
  41. ommlds/cli/sessions/chat/configs.py +9 -17
  42. ommlds/cli/sessions/chat/{chat → drivers}/ai/configs.py +3 -1
  43. ommlds/cli/sessions/chat/drivers/ai/events.py +57 -0
  44. ommlds/cli/sessions/chat/{chat → drivers}/ai/inject.py +10 -3
  45. ommlds/cli/sessions/chat/{chat → drivers}/ai/rendering.py +1 -1
  46. ommlds/cli/sessions/chat/{chat → drivers}/ai/services.py +1 -1
  47. ommlds/cli/sessions/chat/{chat → drivers}/ai/tools.py +4 -8
  48. ommlds/cli/sessions/chat/{chat → drivers}/ai/types.py +9 -0
  49. ommlds/cli/sessions/chat/drivers/configs.py +25 -0
  50. ommlds/cli/sessions/chat/drivers/events/inject.py +27 -0
  51. ommlds/cli/sessions/chat/drivers/events/injection.py +14 -0
  52. ommlds/cli/sessions/chat/drivers/events/manager.py +16 -0
  53. ommlds/cli/sessions/chat/drivers/events/types.py +38 -0
  54. ommlds/cli/sessions/chat/drivers/impl.py +50 -0
  55. ommlds/cli/sessions/chat/drivers/inject.py +70 -0
  56. ommlds/cli/sessions/chat/{chat → drivers}/state/configs.py +2 -0
  57. ommlds/cli/sessions/chat/drivers/state/ids.py +25 -0
  58. ommlds/cli/sessions/chat/drivers/state/inject.py +83 -0
  59. ommlds/cli/sessions/chat/{chat → drivers}/state/inmemory.py +0 -4
  60. ommlds/cli/sessions/chat/{chat → drivers}/state/storage.py +17 -10
  61. ommlds/cli/sessions/chat/{chat → drivers}/state/types.py +10 -5
  62. ommlds/cli/sessions/chat/{tools → drivers/tools}/configs.py +2 -2
  63. ommlds/cli/sessions/chat/drivers/tools/confirmation.py +44 -0
  64. ommlds/cli/sessions/chat/drivers/tools/errorhandling.py +39 -0
  65. ommlds/cli/sessions/chat/{tools → drivers/tools}/execution.py +3 -4
  66. ommlds/cli/sessions/chat/{tools → drivers/tools}/fs/inject.py +3 -3
  67. ommlds/cli/sessions/chat/{tools → drivers/tools}/inject.py +7 -12
  68. ommlds/cli/sessions/chat/{tools → drivers/tools}/injection.py +5 -5
  69. ommlds/cli/sessions/chat/{tools → drivers/tools}/rendering.py +3 -3
  70. ommlds/cli/sessions/chat/{tools → drivers/tools}/todo/inject.py +3 -3
  71. ommlds/cli/sessions/chat/{tools → drivers/tools}/weather/tools.py +1 -1
  72. ommlds/cli/sessions/chat/drivers/types.py +31 -0
  73. ommlds/cli/sessions/chat/{chat → drivers}/user/configs.py +0 -3
  74. ommlds/cli/sessions/chat/drivers/user/inject.py +41 -0
  75. ommlds/cli/sessions/chat/facades/__init__.py +0 -0
  76. ommlds/cli/sessions/chat/facades/commands/__init__.py +0 -0
  77. ommlds/cli/sessions/chat/facades/commands/base.py +83 -0
  78. ommlds/cli/sessions/chat/facades/commands/configs.py +9 -0
  79. ommlds/cli/sessions/chat/facades/commands/inject.py +41 -0
  80. ommlds/cli/sessions/chat/facades/commands/injection.py +15 -0
  81. ommlds/cli/sessions/chat/facades/commands/manager.py +59 -0
  82. ommlds/cli/sessions/chat/facades/commands/simple.py +34 -0
  83. ommlds/cli/sessions/chat/facades/commands/types.py +13 -0
  84. ommlds/cli/sessions/chat/facades/configs.py +11 -0
  85. ommlds/cli/sessions/chat/facades/facade.py +26 -0
  86. ommlds/cli/sessions/chat/facades/inject.py +35 -0
  87. ommlds/cli/sessions/chat/facades/ui.py +34 -0
  88. ommlds/cli/sessions/chat/inject.py +8 -31
  89. ommlds/cli/sessions/chat/interfaces/__init__.py +0 -0
  90. ommlds/cli/sessions/chat/interfaces/bare/__init__.py +0 -0
  91. ommlds/cli/sessions/chat/interfaces/bare/configs.py +15 -0
  92. ommlds/cli/sessions/chat/interfaces/bare/inject.py +69 -0
  93. ommlds/cli/sessions/chat/interfaces/bare/interactive.py +49 -0
  94. ommlds/cli/sessions/chat/interfaces/bare/oneshot.py +21 -0
  95. ommlds/cli/sessions/chat/{tools/confirmation.py → interfaces/bare/tools.py} +3 -22
  96. ommlds/cli/sessions/chat/interfaces/base.py +13 -0
  97. ommlds/cli/sessions/chat/interfaces/configs.py +11 -0
  98. ommlds/cli/sessions/chat/interfaces/inject.py +29 -0
  99. ommlds/cli/sessions/chat/interfaces/textual/__init__.py +0 -0
  100. ommlds/cli/sessions/chat/interfaces/textual/app.py +310 -0
  101. ommlds/cli/sessions/chat/interfaces/textual/configs.py +11 -0
  102. ommlds/cli/sessions/chat/interfaces/textual/facades.py +19 -0
  103. ommlds/cli/sessions/chat/interfaces/textual/inject.py +97 -0
  104. ommlds/cli/sessions/chat/interfaces/textual/interface.py +24 -0
  105. ommlds/cli/sessions/chat/interfaces/textual/styles/__init__.py +29 -0
  106. ommlds/cli/sessions/chat/interfaces/textual/styles/input.tcss +53 -0
  107. ommlds/cli/sessions/chat/interfaces/textual/styles/markdown.tcss +7 -0
  108. ommlds/cli/sessions/chat/interfaces/textual/styles/messages.tcss +157 -0
  109. ommlds/cli/sessions/chat/interfaces/textual/tools.py +38 -0
  110. ommlds/cli/sessions/chat/interfaces/textual/widgets/__init__.py +0 -0
  111. ommlds/cli/sessions/chat/interfaces/textual/widgets/input.py +36 -0
  112. ommlds/cli/sessions/chat/interfaces/textual/widgets/messages.py +197 -0
  113. ommlds/cli/sessions/chat/session.py +8 -13
  114. ommlds/cli/sessions/completion/configs.py +3 -4
  115. ommlds/cli/sessions/completion/inject.py +1 -2
  116. ommlds/cli/sessions/completion/session.py +4 -8
  117. ommlds/cli/sessions/configs.py +10 -0
  118. ommlds/cli/sessions/embedding/configs.py +3 -4
  119. ommlds/cli/sessions/embedding/inject.py +1 -2
  120. ommlds/cli/sessions/embedding/session.py +4 -8
  121. ommlds/cli/sessions/inject.py +15 -15
  122. ommlds/cli/state/storage.py +7 -1
  123. ommlds/minichain/__init__.py +161 -38
  124. ommlds/minichain/_dataclasses.py +20452 -0
  125. ommlds/minichain/_typedvalues.py +11 -4
  126. ommlds/minichain/backends/impls/anthropic/names.py +3 -3
  127. ommlds/minichain/backends/impls/anthropic/protocol.py +2 -2
  128. ommlds/minichain/backends/impls/anthropic/stream.py +1 -1
  129. ommlds/minichain/backends/impls/cerebras/__init__.py +0 -0
  130. ommlds/minichain/backends/impls/cerebras/chat.py +80 -0
  131. ommlds/minichain/backends/impls/cerebras/names.py +45 -0
  132. ommlds/minichain/backends/impls/cerebras/protocol.py +143 -0
  133. ommlds/minichain/backends/impls/cerebras/stream.py +125 -0
  134. ommlds/minichain/backends/impls/duckduckgo/search.py +5 -1
  135. ommlds/minichain/backends/impls/google/names.py +6 -0
  136. ommlds/minichain/backends/impls/google/stream.py +1 -1
  137. ommlds/minichain/backends/impls/google/tools.py +2 -2
  138. ommlds/minichain/backends/impls/groq/chat.py +2 -0
  139. ommlds/minichain/backends/impls/groq/protocol.py +2 -2
  140. ommlds/minichain/backends/impls/groq/stream.py +3 -1
  141. ommlds/minichain/backends/impls/huggingface/repos.py +1 -5
  142. ommlds/minichain/backends/impls/llamacpp/chat.py +6 -3
  143. ommlds/minichain/backends/impls/llamacpp/completion.py +7 -3
  144. ommlds/minichain/backends/impls/llamacpp/stream.py +6 -3
  145. ommlds/minichain/backends/impls/mlx/chat.py +6 -3
  146. ommlds/minichain/backends/impls/ollama/chat.py +51 -57
  147. ommlds/minichain/backends/impls/ollama/protocol.py +144 -0
  148. ommlds/minichain/backends/impls/openai/format.py +4 -3
  149. ommlds/minichain/backends/impls/openai/names.py +3 -1
  150. ommlds/minichain/backends/impls/openai/stream.py +33 -1
  151. ommlds/minichain/backends/impls/sentencepiece/tokens.py +9 -6
  152. ommlds/minichain/backends/impls/tinygrad/chat.py +7 -4
  153. ommlds/minichain/backends/impls/tokenizers/tokens.py +9 -6
  154. ommlds/minichain/backends/impls/transformers/sentence.py +5 -2
  155. ommlds/minichain/backends/impls/transformers/tokens.py +9 -6
  156. ommlds/minichain/backends/impls/transformers/transformers.py +10 -8
  157. ommlds/minichain/backends/strings/resolving.py +1 -1
  158. ommlds/minichain/chat/content.py +42 -0
  159. ommlds/minichain/chat/messages.py +43 -39
  160. ommlds/minichain/chat/stream/joining.py +36 -12
  161. ommlds/minichain/chat/stream/types.py +1 -1
  162. ommlds/minichain/chat/templating.py +3 -3
  163. ommlds/minichain/content/__init__.py +19 -3
  164. ommlds/minichain/content/_marshal.py +181 -55
  165. ommlds/minichain/content/code.py +26 -0
  166. ommlds/minichain/content/composite.py +28 -0
  167. ommlds/minichain/content/content.py +27 -0
  168. ommlds/minichain/content/dynamic.py +12 -0
  169. ommlds/minichain/content/emphasis.py +27 -0
  170. ommlds/minichain/content/images.py +2 -2
  171. ommlds/minichain/content/json.py +2 -2
  172. ommlds/minichain/content/link.py +13 -0
  173. ommlds/minichain/content/markdown.py +12 -0
  174. ommlds/minichain/content/metadata.py +10 -0
  175. ommlds/minichain/content/namespaces.py +8 -0
  176. ommlds/minichain/content/placeholders.py +10 -9
  177. ommlds/minichain/content/quote.py +26 -0
  178. ommlds/minichain/content/raw.py +49 -0
  179. ommlds/minichain/content/recursive.py +12 -0
  180. ommlds/minichain/content/section.py +26 -0
  181. ommlds/minichain/content/sequence.py +17 -3
  182. ommlds/minichain/content/standard.py +32 -0
  183. ommlds/minichain/content/tag.py +28 -0
  184. ommlds/minichain/content/templates.py +13 -0
  185. ommlds/minichain/content/text.py +2 -2
  186. ommlds/minichain/content/transform/__init__.py +0 -0
  187. ommlds/minichain/content/transform/json.py +55 -0
  188. ommlds/minichain/content/transform/markdown.py +8 -0
  189. ommlds/minichain/content/transform/materialize.py +51 -0
  190. ommlds/minichain/content/transform/metadata.py +16 -0
  191. ommlds/minichain/content/{prepare.py → transform/prepare.py} +10 -15
  192. ommlds/minichain/content/transform/recursive.py +97 -0
  193. ommlds/minichain/content/transform/standard.py +43 -0
  194. ommlds/minichain/content/{transforms → transform}/stringify.py +1 -7
  195. ommlds/minichain/content/transform/strings.py +33 -0
  196. ommlds/minichain/content/transform/templates.py +25 -0
  197. ommlds/minichain/content/visitors.py +231 -0
  198. ommlds/minichain/lib/fs/tools/read.py +1 -1
  199. ommlds/minichain/lib/fs/tools/recursivels/rendering.py +1 -1
  200. ommlds/minichain/lib/fs/tools/recursivels/running.py +1 -1
  201. ommlds/minichain/lib/todo/tools/write.py +2 -1
  202. ommlds/minichain/lib/todo/types.py +1 -1
  203. ommlds/minichain/metadata.py +56 -2
  204. ommlds/minichain/resources.py +22 -1
  205. ommlds/minichain/services/README.md +154 -0
  206. ommlds/minichain/services/__init__.py +6 -2
  207. ommlds/minichain/services/_marshal.py +46 -10
  208. ommlds/minichain/services/_origclasses.py +11 -0
  209. ommlds/minichain/services/_typedvalues.py +8 -3
  210. ommlds/minichain/services/requests.py +73 -3
  211. ommlds/minichain/services/responses.py +73 -3
  212. ommlds/minichain/services/services.py +9 -0
  213. ommlds/minichain/stream/services.py +24 -1
  214. ommlds/minichain/text/applypatch.py +2 -1
  215. ommlds/minichain/text/toolparsing/llamacpp/types.py +1 -1
  216. ommlds/minichain/tokens/specials.py +1 -1
  217. ommlds/minichain/tools/execution/catalog.py +1 -1
  218. ommlds/minichain/tools/execution/errorhandling.py +36 -0
  219. ommlds/minichain/tools/execution/errors.py +2 -2
  220. ommlds/minichain/tools/execution/executors.py +1 -1
  221. ommlds/minichain/tools/fns.py +1 -1
  222. ommlds/minichain/tools/jsonschema.py +2 -2
  223. ommlds/minichain/tools/reflect.py +6 -6
  224. ommlds/minichain/tools/types.py +12 -15
  225. ommlds/minichain/vectors/_marshal.py +1 -1
  226. ommlds/minichain/vectors/embeddings.py +1 -1
  227. ommlds/minichain/wrappers/__init__.py +7 -0
  228. ommlds/minichain/wrappers/firstinwins.py +144 -0
  229. ommlds/minichain/wrappers/instrument.py +146 -0
  230. ommlds/minichain/wrappers/retry.py +168 -0
  231. ommlds/minichain/wrappers/services.py +98 -0
  232. ommlds/minichain/wrappers/stream.py +57 -0
  233. ommlds/nanochat/rustbpe/README.md +9 -0
  234. ommlds/nanochat/tokenizers.py +40 -6
  235. ommlds/specs/mcp/clients.py +146 -0
  236. ommlds/specs/mcp/protocol.py +123 -18
  237. ommlds/tools/git.py +82 -65
  238. {ommlds-0.0.0.dev480.dist-info → ommlds-0.0.0.dev503.dist-info}/METADATA +13 -11
  239. ommlds-0.0.0.dev503.dist-info/RECORD +520 -0
  240. ommlds/cli/sessions/chat/chat/state/inject.py +0 -36
  241. ommlds/cli/sessions/chat/chat/user/inject.py +0 -62
  242. ommlds/cli/sessions/chat/chat/user/interactive.py +0 -31
  243. ommlds/cli/sessions/chat/chat/user/oneshot.py +0 -25
  244. ommlds/cli/sessions/chat/chat/user/types.py +0 -15
  245. ommlds/cli/sessions/chat/driver.py +0 -43
  246. ommlds/minichain/content/materialize.py +0 -196
  247. ommlds/minichain/content/simple.py +0 -47
  248. ommlds/minichain/content/transforms/base.py +0 -46
  249. ommlds/minichain/content/transforms/interleave.py +0 -70
  250. ommlds/minichain/content/transforms/squeeze.py +0 -72
  251. ommlds/minichain/content/transforms/strings.py +0 -24
  252. ommlds/minichain/content/types.py +0 -43
  253. ommlds/minichain/stream/wrap.py +0 -62
  254. ommlds-0.0.0.dev480.dist-info/RECORD +0 -427
  255. /ommlds/cli/sessions/chat/{chat → drivers}/__init__.py +0 -0
  256. /ommlds/cli/sessions/chat/{chat → drivers}/ai/__init__.py +0 -0
  257. /ommlds/cli/sessions/chat/{chat → drivers}/ai/injection.py +0 -0
  258. /ommlds/cli/sessions/chat/{chat/state → drivers/events}/__init__.py +0 -0
  259. /ommlds/cli/sessions/chat/{chat/user → drivers/phases}/__init__.py +0 -0
  260. /ommlds/cli/sessions/chat/{phases → drivers/phases}/inject.py +0 -0
  261. /ommlds/cli/sessions/chat/{phases → drivers/phases}/injection.py +0 -0
  262. /ommlds/cli/sessions/chat/{phases → drivers/phases}/manager.py +0 -0
  263. /ommlds/cli/sessions/chat/{phases → drivers/phases}/types.py +0 -0
  264. /ommlds/cli/sessions/chat/{phases → drivers/state}/__init__.py +0 -0
  265. /ommlds/cli/sessions/chat/{tools → drivers/tools}/__init__.py +0 -0
  266. /ommlds/cli/sessions/chat/{tools → drivers/tools}/fs/__init__.py +0 -0
  267. /ommlds/cli/sessions/chat/{tools → drivers/tools}/fs/configs.py +0 -0
  268. /ommlds/cli/sessions/chat/{tools → drivers/tools}/todo/__init__.py +0 -0
  269. /ommlds/cli/sessions/chat/{tools → drivers/tools}/todo/configs.py +0 -0
  270. /ommlds/cli/sessions/chat/{tools → drivers/tools}/weather/__init__.py +0 -0
  271. /ommlds/cli/sessions/chat/{tools → drivers/tools}/weather/configs.py +0 -0
  272. /ommlds/cli/sessions/chat/{tools → drivers/tools}/weather/inject.py +0 -0
  273. /ommlds/{minichain/content/transforms → cli/sessions/chat/drivers/user}/__init__.py +0 -0
  274. {ommlds-0.0.0.dev480.dist-info → ommlds-0.0.0.dev503.dist-info}/WHEEL +0 -0
  275. {ommlds-0.0.0.dev480.dist-info → ommlds-0.0.0.dev503.dist-info}/entry_points.txt +0 -0
  276. {ommlds-0.0.0.dev480.dist-info → ommlds-0.0.0.dev503.dist-info}/licenses/LICENSE +0 -0
  277. {ommlds-0.0.0.dev480.dist-info → ommlds-0.0.0.dev503.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,310 @@
1
+ import asyncio
2
+ import os
3
+ import typing as ta
4
+ import weakref
5
+
6
+ from omdev.tui import textual as tx
7
+ from omlish import check
8
+ from omlish import dataclasses as dc
9
+ from omlish import lang
10
+ from omlish.logs import all as logs
11
+
12
+ from ...... import minichain as mc
13
+ from .....backends.types import BackendName
14
+ from ...drivers.events.types import AiDeltaChatEvent
15
+ from ...drivers.events.types import AiMessagesChatEvent
16
+ from ...drivers.types import ChatDriver
17
+ from ...facades.facade import ChatFacade
18
+ from .styles import read_app_css
19
+ from .widgets.input import InputOuter
20
+ from .widgets.input import InputTextArea
21
+ from .widgets.messages import AiMessage
22
+ from .widgets.messages import MessagesContainer
23
+ from .widgets.messages import StaticAiMessage
24
+ from .widgets.messages import StreamAiMessage
25
+ from .widgets.messages import ToolConfirmationMessage
26
+ from .widgets.messages import UiMessage
27
+ from .widgets.messages import UserMessage
28
+ from .widgets.messages import WelcomeMessage
29
+
30
+
31
+ log, alog = logs.get_module_loggers(globals())
32
+
33
+
34
+ ##
35
+
36
+
37
+ ChatEventQueue = ta.NewType('ChatEventQueue', asyncio.Queue)
38
+
39
+
40
+ ##
41
+
42
+
43
+ class ChatAppGetter(lang.AsyncCachedFunc0['ChatApp']):
44
+ pass
45
+
46
+
47
+ class ChatApp(tx.App):
48
+ ENABLE_COMMAND_PALETTE: ta.ClassVar[bool] = False
49
+
50
+ def __init__(
51
+ self,
52
+ *,
53
+ chat_facade: ChatFacade,
54
+ chat_driver: ChatDriver,
55
+ chat_event_queue: ChatEventQueue,
56
+ backend_name: BackendName | None = None,
57
+ devtools_setup: tx.DevtoolsSetup | None = None,
58
+ ) -> None:
59
+ super().__init__()
60
+
61
+ if devtools_setup is not None:
62
+ devtools_setup(self)
63
+
64
+ self._chat_facade = chat_facade
65
+ self._chat_driver = chat_driver
66
+ self._chat_event_queue = chat_event_queue
67
+ self._backend_name = backend_name
68
+
69
+ self._chat_action_queue: asyncio.Queue[ta.Any] = asyncio.Queue()
70
+
71
+ self._input_focused_key_events: weakref.WeakSet[tx.Key] = weakref.WeakSet()
72
+
73
+ def get_driver_class(self) -> type[tx.Driver]:
74
+ return tx.get_pending_writes_driver_class(super().get_driver_class())
75
+
76
+ CSS: ta.ClassVar[str] = read_app_css()
77
+
78
+ #
79
+
80
+ def compose(self) -> tx.ComposeResult:
81
+ yield MessagesContainer(id='messages-container')
82
+
83
+ yield InputOuter(id='input-outer')
84
+
85
+ #
86
+
87
+ def _get_input_text_area(self) -> InputTextArea:
88
+ return self.query_one('#input', InputTextArea)
89
+
90
+ def _get_messages_container(self) -> tx.VerticalScroll:
91
+ return self.query_one('#messages-container', MessagesContainer)
92
+
93
+ #
94
+
95
+ def _is_messages_at_bottom(self, threshold: int = 3) -> bool:
96
+ return (ms := self._get_messages_container()).scroll_y >= (ms.max_scroll_y - threshold)
97
+
98
+ def _scroll_messages_to_bottom(self) -> None:
99
+ self._get_messages_container().scroll_end(animate=False)
100
+
101
+ def _anchor_messages(self) -> None:
102
+ if (ms := self._get_messages_container()).max_scroll_y:
103
+ ms.anchor()
104
+
105
+ #
106
+
107
+ _pending_mount_messages: list[tx.Widget] | None = None
108
+
109
+ async def _enqueue_mount_messages(self, *messages: tx.Widget) -> None:
110
+ if (lst := self._pending_mount_messages) is None:
111
+ lst = self._pending_mount_messages = []
112
+
113
+ lst.extend(messages)
114
+
115
+ _stream_ai_message: StreamAiMessage | None = None
116
+
117
+ async def _finalize_stream_ai_message(self) -> None:
118
+ if self._stream_ai_message is None:
119
+ return
120
+
121
+ await self._stream_ai_message.stop_stream()
122
+ self._stream_ai_message = None
123
+
124
+ async def _append_stream_ai_message_content(self, content: str) -> None:
125
+ if (sam := self._stream_ai_message) is not None:
126
+ was_at_bottom = self._is_messages_at_bottom()
127
+
128
+ await sam.append_content(content)
129
+
130
+ self.call_after_refresh(self._scroll_messages_to_bottom)
131
+
132
+ if was_at_bottom:
133
+ self.call_after_refresh(self._anchor_messages)
134
+
135
+ else:
136
+ await self._mount_messages(StreamAiMessage(content))
137
+
138
+ async def _mount_messages(self, *messages: tx.Widget) -> None:
139
+ was_at_bottom = self._is_messages_at_bottom()
140
+
141
+ msg_ctr = self._get_messages_container()
142
+
143
+ for msg in [*(self._pending_mount_messages or []), *messages]:
144
+ if isinstance(msg, (AiMessage, ToolConfirmationMessage)):
145
+ await self._finalize_stream_ai_message()
146
+
147
+ await msg_ctr.mount(msg)
148
+
149
+ if isinstance(msg, StreamAiMessage):
150
+ self._stream_ai_message = check.replacing_none(self._stream_ai_message, msg)
151
+ await msg.write_initial_content()
152
+
153
+ self._pending_mount_messages = None
154
+
155
+ self.call_after_refresh(self._scroll_messages_to_bottom)
156
+
157
+ if was_at_bottom:
158
+ self.call_after_refresh(self._anchor_messages)
159
+
160
+ #
161
+
162
+ _chat_event_task: asyncio.Task[None] | None = None
163
+
164
+ @logs.async_exception_logging(alog)
165
+ async def _chat_event_task_main(self) -> None:
166
+ while True:
167
+ ev = await self._chat_event_queue.get()
168
+ if ev is None:
169
+ break
170
+
171
+ await alog.debug(lambda: f'Got chat event: {ev!r}')
172
+
173
+ if isinstance(ev, AiMessagesChatEvent):
174
+ wx: list[tx.Widget] = []
175
+
176
+ for ai_msg in ev.chat:
177
+ if isinstance(ai_msg, mc.AiMessage):
178
+ wx.append(
179
+ StaticAiMessage(
180
+ check.isinstance(ai_msg.c, str),
181
+ markdown=True,
182
+ ),
183
+ )
184
+
185
+ if wx:
186
+ await self._enqueue_mount_messages(*wx)
187
+ self.call_later(self._mount_messages)
188
+
189
+ elif isinstance(ev, AiDeltaChatEvent):
190
+ if isinstance(ev.delta, mc.ContentAiDelta):
191
+ cc = check.isinstance(ev.delta.c, str)
192
+ self.call_later(self._append_stream_ai_message_content, cc)
193
+
194
+ elif isinstance(ev.delta, mc.ToolUseAiDelta):
195
+ pass
196
+
197
+ #
198
+
199
+ @dc.dataclass(frozen=True)
200
+ class UserInput:
201
+ text: str
202
+
203
+ _chat_action_task: asyncio.Task[None] | None = None
204
+
205
+ @logs.async_exception_logging(alog)
206
+ async def _chat_action_task_main(self) -> None:
207
+ while True:
208
+ ac = await self._chat_action_queue.get()
209
+ if ac is None:
210
+ break
211
+
212
+ await alog.debug(lambda: f'Got chat action: {ac!r}')
213
+
214
+ if isinstance(ac, ChatApp.UserInput):
215
+ try:
216
+ await self._chat_facade.handle_user_input(ac.text)
217
+ except Exception as e: # noqa
218
+ raise
219
+
220
+ else:
221
+ raise TypeError(ac) # noqa
222
+
223
+ #
224
+
225
+ async def on_mount(self) -> None:
226
+ check.state(self._chat_event_task is None)
227
+ self._chat_event_task = asyncio.create_task(self._chat_event_task_main())
228
+
229
+ await self._chat_driver.start()
230
+
231
+ check.state(self._chat_action_task is None)
232
+ self._chat_action_task = asyncio.create_task(self._chat_action_task_main())
233
+
234
+ self._get_input_text_area().focus()
235
+
236
+ await self._mount_messages(
237
+ WelcomeMessage('\n'.join([
238
+ f'Backend: {self._backend_name or "?"}',
239
+ f'Dir: {os.getcwd()}',
240
+ ])),
241
+ )
242
+
243
+ async def on_unmount(self) -> None:
244
+ if (cdt := self._chat_event_task) is not None:
245
+ await self._chat_event_queue.put(None)
246
+ await cdt
247
+
248
+ await self._chat_driver.stop()
249
+
250
+ if (cet := self._chat_event_task) is not None:
251
+ await self._chat_event_queue.put(None)
252
+ await cet
253
+
254
+ @tx.on(InputTextArea.Submitted)
255
+ async def on_input_text_area_submitted(self, event: InputTextArea.Submitted) -> None:
256
+ self._get_input_text_area().clear()
257
+
258
+ await self._finalize_stream_ai_message()
259
+
260
+ await self._mount_messages(
261
+ UserMessage(
262
+ event.text,
263
+ ),
264
+ )
265
+
266
+ await self._chat_action_queue.put(ChatApp.UserInput(event.text))
267
+
268
+ @tx.on(tx.Key)
269
+ async def on_key(self, event: tx.Key) -> None:
270
+ if event in self._input_focused_key_events:
271
+ return
272
+
273
+ chat_input = self._get_input_text_area()
274
+
275
+ if not chat_input.has_focus:
276
+ self._input_focused_key_events.add(event)
277
+
278
+ chat_input.focus()
279
+
280
+ self.screen.post_message(tx.Key(event.key, event.character))
281
+
282
+ #
283
+
284
+ async def confirm_tool_use(
285
+ self,
286
+ outer_message: str,
287
+ inner_message: str,
288
+ ) -> bool:
289
+ fut: asyncio.Future[bool] = asyncio.get_running_loop().create_future()
290
+
291
+ tcm = ToolConfirmationMessage(
292
+ outer_message,
293
+ inner_message,
294
+ fut,
295
+ )
296
+
297
+ async def inner() -> None:
298
+ await self._mount_messages(tcm)
299
+
300
+ self.call_later(inner)
301
+
302
+ ret = await fut
303
+
304
+ return ret
305
+
306
+ async def display_ui_message(
307
+ self,
308
+ content: str,
309
+ ) -> None:
310
+ await self._mount_messages(UiMessage(content))
@@ -0,0 +1,11 @@
1
+ from omlish import dataclasses as dc
2
+
3
+ from ..configs import InterfaceConfig
4
+
5
+
6
+ ##
7
+
8
+
9
+ @dc.dataclass(frozen=True, kw_only=True)
10
+ class TextualInterfaceConfig(InterfaceConfig):
11
+ pass
@@ -0,0 +1,19 @@
1
+ from ...facades.ui import UiMessageDisplayer
2
+ from .app import ChatAppGetter
3
+
4
+
5
+ ##
6
+
7
+
8
+ class ChatAppUiMessageDisplayer(UiMessageDisplayer):
9
+ def __init__(
10
+ self,
11
+ *,
12
+ app: ChatAppGetter,
13
+ ) -> None:
14
+ super().__init__()
15
+
16
+ self._app = app
17
+
18
+ async def display_ui_message(self, content: str) -> None:
19
+ await (await self._app()).display_ui_message(content)
@@ -0,0 +1,97 @@
1
+ """
2
+ FIXME:
3
+ - too lazy to lazy import guts like every other proper inject module lol >_<
4
+ """
5
+ import asyncio
6
+ import contextlib
7
+
8
+ from omlish import inject as inj
9
+ from omlish import lang
10
+
11
+ from ...drivers.events.injection import event_callbacks
12
+ from ..base import ChatInterface
13
+ from .configs import TextualInterfaceConfig
14
+
15
+
16
+ with lang.auto_proxy_import(globals()):
17
+ from omdev.tui import textual as tx
18
+
19
+ from ...drivers.tools import confirmation as _tools_confirmation
20
+ from ...facades import ui as _facades_ui
21
+ from . import app as _app
22
+ from . import facades as _facades
23
+ from . import interface as _interface
24
+ from . import tools as _tools
25
+
26
+
27
+ ##
28
+
29
+
30
+ def bind_textual(cfg: TextualInterfaceConfig = TextualInterfaceConfig()) -> inj.Elements:
31
+ els: list[inj.Elemental] = [
32
+ inj.bind(ChatInterface, to_ctor=_interface.TextualChatInterface, singleton=True),
33
+ ]
34
+
35
+ #
36
+
37
+ els.extend([
38
+ inj.bind(_app.ChatApp, singleton=True),
39
+ inj.bind_async_late(_app.ChatApp, _app.ChatAppGetter),
40
+ ])
41
+
42
+ #
43
+
44
+ els.extend([
45
+ inj.bind(_app.ChatEventQueue, to_const=asyncio.Queue()),
46
+
47
+ event_callbacks().bind_item(to_fn=inj.target(eq=_app.ChatEventQueue)(lambda eq: lambda ev: eq.put(ev))),
48
+ ])
49
+
50
+ #
51
+
52
+ if cfg.enable_tools:
53
+ if cfg.dangerous_no_tool_confirmation:
54
+ els.append(inj.bind(
55
+ _tools_confirmation.ToolExecutionConfirmation,
56
+ to_ctor=_tools_confirmation.UnsafeAlwaysAllowToolExecutionConfirmation,
57
+ singleton=True,
58
+ ))
59
+
60
+ else:
61
+ els.append(inj.bind(
62
+ _tools_confirmation.ToolExecutionConfirmation,
63
+ to_ctor=_tools.ChatAppToolExecutionConfirmation,
64
+ singleton=True,
65
+ ))
66
+
67
+ #
68
+
69
+ els.extend([
70
+ inj.bind(tx.DevtoolsConfig(port=41932)), # FIXME: lol
71
+
72
+ inj.bind(
73
+ tx.DevtoolsManager,
74
+ singleton=True,
75
+ to_async_fn=inj.make_async_managed_provider(
76
+ tx.DevtoolsManager,
77
+ contextlib.aclosing,
78
+ ),
79
+ ),
80
+
81
+ inj.bind(
82
+ tx.DevtoolsSetup,
83
+ to_async_fn=inj.target(mgr=tx.DevtoolsManager)(lambda mgr: mgr.get_setup()),
84
+ singleton=True,
85
+ ),
86
+ ])
87
+
88
+ #
89
+
90
+ els.extend([
91
+ inj.bind(_facades.ChatAppUiMessageDisplayer, singleton=True),
92
+ inj.bind(_facades_ui.UiMessageDisplayer, to_key=_facades.ChatAppUiMessageDisplayer),
93
+ ])
94
+
95
+ #
96
+
97
+ return inj.as_elements(*els)
@@ -0,0 +1,24 @@
1
+ from omdev.tui import textual as tx
2
+
3
+ from ..base import ChatInterface
4
+ from .app import ChatApp
5
+
6
+
7
+ ##
8
+
9
+
10
+ class TextualChatInterface(ChatInterface):
11
+ def __init__(
12
+ self,
13
+ *,
14
+ app: ChatApp,
15
+ ) -> None:
16
+ super().__init__()
17
+
18
+ self._app = app
19
+
20
+ async def run(self) -> None:
21
+ # FIXME: move lol
22
+ tx.set_root_logger_to_devtools(self._app.devtools)
23
+
24
+ await self._app.run_async()
@@ -0,0 +1,29 @@
1
+ import io as _io
2
+
3
+ from omlish import lang as _lang
4
+
5
+
6
+ ##
7
+
8
+
9
+ @_lang.cached_function
10
+ def read_app_css() -> str:
11
+ tcss_rsrcs = [
12
+ rsrc
13
+ for rsrc in _lang.get_relative_resources(globals=globals()).values()
14
+ if rsrc.name.endswith('.tcss')
15
+ ]
16
+
17
+ out = _io.StringIO()
18
+
19
+ for i, rsrc in enumerate(tcss_rsrcs):
20
+ if i:
21
+ out.write('\n\n')
22
+
23
+ out.write(f'/*** {rsrc.name} ***/\n')
24
+ out.write('\n')
25
+
26
+ out.write(rsrc.read_text().strip())
27
+ out.write('\n')
28
+
29
+ return out.getvalue()
@@ -0,0 +1,53 @@
1
+ #input-outer {
2
+ width: 100%;
3
+ height: auto;
4
+
5
+ background: $background-darken-3;
6
+ }
7
+
8
+ #input-vertical {
9
+ width: 100%;
10
+ height: auto;
11
+
12
+ margin: 0 1 1 1;
13
+
14
+ padding: 0;
15
+ }
16
+
17
+ #input-vertical2 {
18
+ width: 100%;
19
+ height: auto;
20
+
21
+ border: round $foreground-muted;
22
+
23
+ padding: 0 1;
24
+ }
25
+
26
+ #input-horizontal {
27
+ width: 100%;
28
+ height: auto;
29
+ }
30
+
31
+ #input-glyph {
32
+ width: auto;
33
+
34
+ padding: 0 1 0 0;
35
+
36
+ background: transparent;
37
+ color: $primary;
38
+
39
+ text-style: bold;
40
+ }
41
+
42
+ #input {
43
+ width: 1fr;
44
+ height: auto;
45
+ max-height: 16;
46
+
47
+ border: none;
48
+
49
+ padding: 0;
50
+
51
+ background: transparent;
52
+ color: $text;
53
+ }
@@ -0,0 +1,7 @@
1
+ Markdown MarkdownFence {
2
+ max-width: 95%;
3
+
4
+ overflow-x: auto;
5
+
6
+ scrollbar-size-horizontal: 1;
7
+ }
@@ -0,0 +1,157 @@
1
+ /* Container */
2
+
3
+ #messages-container {
4
+ width: 100%;
5
+ height: 1fr;
6
+
7
+ scrollbar-gutter: stable;
8
+
9
+ background: $background-darken-3;
10
+
11
+ text-align: left;
12
+
13
+ scrollbar-size: 1 1;
14
+ }
15
+
16
+
17
+ /* Base */
18
+
19
+ .message {
20
+ width: 1fr;
21
+ height: auto;
22
+
23
+ margin: 1 0 0 0;
24
+
25
+ padding-right: 1;
26
+
27
+ layout: stream;
28
+ }
29
+
30
+ .message-glyph {
31
+ width: auto;
32
+ height: auto;
33
+
34
+ background: transparent;
35
+ color: $primary;
36
+
37
+ text-style: bold;
38
+ }
39
+
40
+ .message-outer {
41
+ width: 1fr;
42
+ height: auto;
43
+
44
+ align: left top;
45
+ }
46
+
47
+ .message-inner {
48
+ width: 1fr;
49
+ height: auto;
50
+ }
51
+
52
+
53
+ /* Welcome */
54
+
55
+ .welcome-message {
56
+ margin: 0;
57
+
58
+ padding: 1 2 1 1;
59
+
60
+ border: round;
61
+ }
62
+
63
+ .welcome-message-outer {
64
+ }
65
+
66
+ .welcome-message-content {
67
+ }
68
+
69
+
70
+ /* User */
71
+
72
+ .user-message {
73
+ }
74
+
75
+ .user-message-outer {
76
+ }
77
+
78
+ .user-message-glyph {
79
+ }
80
+
81
+ .user-message-inner {
82
+ }
83
+
84
+
85
+ /* Ai */
86
+
87
+ .ai-message {
88
+ }
89
+
90
+ .ai-message-outer {
91
+ }
92
+
93
+ .ai-message-glyph {
94
+ }
95
+
96
+ .ai-message-inner {
97
+ padding: 0;
98
+
99
+ Markdown {
100
+ width: 100%;
101
+ height: auto;
102
+
103
+ margin: 0;
104
+ padding: 0;
105
+ }
106
+ }
107
+
108
+
109
+ /* Tool Confirmation */
110
+
111
+ .tool-confirmation-message {
112
+ }
113
+
114
+ .tool-confirmation-message-outer {
115
+ }
116
+
117
+ .tool-confirmation-message-glyph {
118
+ }
119
+
120
+ .tool-confirmation-message-inner {
121
+ }
122
+
123
+ .tool-confirmation-message-inner-open {
124
+ background: $warning-lighten-3 15%;
125
+ }
126
+
127
+ .tool-confirmation-message-inner-closed {
128
+ }
129
+
130
+ .tool-confirmation-message-outer-content {
131
+ }
132
+
133
+ .tool-confirmation-message-inner-content {
134
+ background: $background;
135
+
136
+ margin: 1;
137
+ padding: 1;
138
+ }
139
+
140
+ .tool-confirmation-message-controls {
141
+ margin: 1;
142
+ }
143
+
144
+
145
+ /* Ui */
146
+
147
+ .ui-message {
148
+ }
149
+
150
+ .ui-message-glyph {
151
+ }
152
+
153
+ .ui-message-outer {
154
+ }
155
+
156
+ .ui-message-inner {
157
+ }