ommlds 0.0.0.dev490__py3-none-any.whl → 0.0.0.dev492__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.
- ommlds/.omlish-manifests.json +9 -7
- ommlds/README.md +11 -0
- ommlds/__about__.py +1 -1
- ommlds/backends/ollama/_dataclasses.py +53 -23
- ommlds/backends/ollama/protocol.py +3 -0
- ommlds/cli/_dataclasses.py +439 -289
- ommlds/cli/main.py +42 -34
- ommlds/cli/rendering/types.py +6 -0
- ommlds/cli/sessions/chat/configs.py +2 -2
- ommlds/cli/sessions/chat/{agents → drivers}/ai/inject.py +3 -1
- ommlds/cli/sessions/chat/{agents → drivers}/configs.py +1 -1
- ommlds/cli/sessions/chat/{agents/agent.py → drivers/driver.py} +7 -1
- ommlds/cli/sessions/chat/{agents → drivers}/inject.py +13 -6
- ommlds/cli/sessions/chat/{agents → drivers}/tools/configs.py +0 -2
- ommlds/cli/sessions/chat/drivers/tools/confirmation.py +44 -0
- ommlds/cli/sessions/chat/{agents → drivers}/tools/execution.py +2 -3
- ommlds/cli/sessions/chat/{agents → drivers}/tools/inject.py +1 -13
- ommlds/cli/sessions/chat/{agents → drivers}/tools/rendering.py +1 -1
- ommlds/cli/sessions/chat/drivers/types.py +10 -0
- ommlds/cli/sessions/chat/{agents → drivers}/user/inject.py +5 -5
- ommlds/cli/sessions/chat/inject.py +2 -2
- ommlds/cli/sessions/chat/interfaces/bare/inject.py +23 -0
- ommlds/cli/sessions/chat/interfaces/bare/interactive.py +12 -6
- ommlds/cli/sessions/chat/interfaces/bare/oneshot.py +5 -5
- ommlds/cli/sessions/chat/{agents/tools/confirmation.py → interfaces/bare/tools.py} +2 -21
- ommlds/cli/sessions/chat/interfaces/bare/user.py +1 -1
- ommlds/cli/sessions/chat/interfaces/configs.py +8 -0
- ommlds/cli/sessions/chat/interfaces/inject.py +1 -1
- ommlds/cli/sessions/chat/interfaces/textual/app.py +154 -103
- ommlds/cli/sessions/chat/interfaces/textual/inject.py +34 -9
- ommlds/cli/sessions/chat/interfaces/textual/interface.py +85 -0
- ommlds/cli/sessions/chat/interfaces/textual/styles/__init__.py +29 -0
- ommlds/cli/sessions/chat/interfaces/textual/styles/input.tcss +3 -1
- ommlds/cli/sessions/chat/interfaces/textual/styles/markdown.tcss +7 -0
- ommlds/cli/sessions/chat/interfaces/textual/styles/messages.tcss +131 -9
- ommlds/cli/sessions/chat/interfaces/textual/tools.py +37 -0
- ommlds/cli/sessions/chat/interfaces/textual/widgets/__init__.py +0 -0
- ommlds/cli/sessions/chat/interfaces/textual/widgets/input.py +36 -0
- ommlds/cli/sessions/chat/interfaces/textual/widgets/messages.py +164 -0
- ommlds/minichain/backends/impls/ollama/chat.py +50 -56
- ommlds/minichain/backends/impls/ollama/protocol.py +144 -0
- ommlds/minichain/backends/impls/openai/names.py +3 -1
- ommlds/nanochat/rustbpe/README.md +9 -0
- {ommlds-0.0.0.dev490.dist-info → ommlds-0.0.0.dev492.dist-info}/METADATA +6 -6
- {ommlds-0.0.0.dev490.dist-info → ommlds-0.0.0.dev492.dist-info}/RECORD +88 -78
- /ommlds/cli/sessions/chat/{agents → drivers}/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/ai/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/ai/configs.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/ai/events.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/ai/injection.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/ai/rendering.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/ai/services.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/ai/tools.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/ai/types.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/events/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/events/inject.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/events/injection.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/events/manager.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/events/types.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/phases/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/phases/inject.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/phases/injection.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/phases/manager.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/phases/types.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/state/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/state/configs.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/state/inject.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/state/inmemory.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/state/storage.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/state/types.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/tools/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/tools/fs/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/tools/fs/configs.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/tools/fs/inject.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/tools/injection.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/tools/todo/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/tools/todo/configs.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/tools/todo/inject.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/tools/weather/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/tools/weather/configs.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/tools/weather/inject.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/tools/weather/tools.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/user/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{agents → drivers}/user/configs.py +0 -0
- {ommlds-0.0.0.dev490.dist-info → ommlds-0.0.0.dev492.dist-info}/WHEEL +0 -0
- {ommlds-0.0.0.dev490.dist-info → ommlds-0.0.0.dev492.dist-info}/entry_points.txt +0 -0
- {ommlds-0.0.0.dev490.dist-info → ommlds-0.0.0.dev492.dist-info}/licenses/LICENSE +0 -0
- {ommlds-0.0.0.dev490.dist-info → ommlds-0.0.0.dev492.dist-info}/top_level.txt +0 -0
|
@@ -1,135 +1,91 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import dataclasses as dc
|
|
3
|
-
import io
|
|
4
2
|
import typing as ta
|
|
5
3
|
|
|
6
4
|
from omdev.tui import textual as tx
|
|
7
5
|
from omlish import check
|
|
8
6
|
from omlish import lang
|
|
7
|
+
from omlish.logs import all as logs
|
|
9
8
|
|
|
10
9
|
from ...... import minichain as mc
|
|
11
|
-
from ...
|
|
12
|
-
from ...
|
|
10
|
+
from ...drivers.driver import ChatDriver
|
|
11
|
+
from ...drivers.events.types import AiDeltaChatEvent
|
|
12
|
+
from ...drivers.events.types import AiMessagesChatEvent
|
|
13
|
+
from .styles import read_app_css
|
|
14
|
+
from .widgets.input import InputOuter
|
|
15
|
+
from .widgets.input import InputTextArea
|
|
16
|
+
from .widgets.messages import AiMessage
|
|
17
|
+
from .widgets.messages import StaticAiMessage
|
|
18
|
+
from .widgets.messages import StreamAiMessage
|
|
19
|
+
from .widgets.messages import ToolConfirmationMessage
|
|
20
|
+
from .widgets.messages import UserMessage
|
|
21
|
+
from .widgets.messages import WelcomeMessage
|
|
13
22
|
|
|
14
23
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
ChatAgentEventQueue = ta.NewType('ChatAgentEventQueue', asyncio.Queue)
|
|
24
|
+
log, alog = logs.get_module_loggers(globals())
|
|
19
25
|
|
|
20
26
|
|
|
21
27
|
##
|
|
22
28
|
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
def __init__(self, content: str) -> None:
|
|
26
|
-
super().__init__(content)
|
|
27
|
-
|
|
28
|
-
self.add_class('welcome-message')
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class UserMessage(tx.Static):
|
|
32
|
-
def __init__(self, content: str) -> None:
|
|
33
|
-
super().__init__(content)
|
|
34
|
-
|
|
35
|
-
self.add_class('user-message')
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class AiMessage(tx.Static):
|
|
39
|
-
def __init__(self, content: str) -> None:
|
|
40
|
-
super().__init__(content)
|
|
41
|
-
|
|
42
|
-
self.add_class('ai-message')
|
|
30
|
+
ChatDriverEventQueue = ta.NewType('ChatDriverEventQueue', asyncio.Queue)
|
|
43
31
|
|
|
44
32
|
|
|
45
33
|
##
|
|
46
34
|
|
|
47
35
|
|
|
48
|
-
class
|
|
49
|
-
|
|
50
|
-
class Submitted(tx.Message):
|
|
51
|
-
text: str
|
|
52
|
-
|
|
53
|
-
def __init__(self, **kwargs: ta.Any) -> None:
|
|
54
|
-
super().__init__(**kwargs)
|
|
55
|
-
|
|
56
|
-
async def _on_key(self, event: tx.Key) -> None:
|
|
57
|
-
if event.key == 'enter':
|
|
58
|
-
event.prevent_default()
|
|
59
|
-
event.stop()
|
|
60
|
-
|
|
61
|
-
if text := self.text.strip():
|
|
62
|
-
self.post_message(self.Submitted(text))
|
|
63
|
-
|
|
64
|
-
else:
|
|
65
|
-
await super()._on_key(event)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
##
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@lang.cached_function
|
|
72
|
-
def _read_app_css() -> str:
|
|
73
|
-
tcss_rsrcs = [
|
|
74
|
-
rsrc
|
|
75
|
-
for rsrc in lang.get_relative_resources('.styles', globals=globals()).values()
|
|
76
|
-
if rsrc.name.endswith('.tcss')
|
|
77
|
-
]
|
|
78
|
-
|
|
79
|
-
out = io.StringIO()
|
|
80
|
-
|
|
81
|
-
for i, rsrc in enumerate(tcss_rsrcs):
|
|
82
|
-
if i:
|
|
83
|
-
out.write('\n\n')
|
|
84
|
-
|
|
85
|
-
out.write(f'/* {rsrc.name} */\n')
|
|
86
|
-
out.write('\n')
|
|
87
|
-
|
|
88
|
-
out.write(rsrc.read_text().strip())
|
|
89
|
-
out.write('\n')
|
|
90
|
-
|
|
91
|
-
return out.getvalue()
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
#
|
|
36
|
+
class ChatAppGetter(lang.CachedFunc0['ChatApp']):
|
|
37
|
+
pass
|
|
95
38
|
|
|
96
39
|
|
|
97
40
|
class ChatApp(tx.App):
|
|
41
|
+
ENABLE_COMMAND_PALETTE: ta.ClassVar[bool] = False
|
|
42
|
+
|
|
98
43
|
def __init__(
|
|
99
44
|
self,
|
|
100
45
|
*,
|
|
101
|
-
|
|
102
|
-
|
|
46
|
+
chat_driver: ChatDriver,
|
|
47
|
+
chat_driver_event_queue: ChatDriverEventQueue,
|
|
103
48
|
) -> None:
|
|
104
49
|
super().__init__()
|
|
105
50
|
|
|
106
|
-
self
|
|
107
|
-
self._event_queue = event_queue
|
|
51
|
+
tx.setup_app_devtools(self, port=41932)
|
|
108
52
|
|
|
109
|
-
|
|
53
|
+
self._chat_driver = chat_driver
|
|
54
|
+
self._chat_driver_event_queue = chat_driver_event_queue
|
|
110
55
|
|
|
111
|
-
|
|
56
|
+
self._chat_driver_action_queue: asyncio.Queue[ta.Any] = asyncio.Queue()
|
|
57
|
+
|
|
58
|
+
def get_driver_class(self) -> type[tx.Driver]:
|
|
59
|
+
return tx.get_pending_writes_driver_class(super().get_driver_class())
|
|
60
|
+
|
|
61
|
+
CSS: ta.ClassVar[str] = read_app_css()
|
|
112
62
|
|
|
113
63
|
#
|
|
114
64
|
|
|
115
65
|
def compose(self) -> tx.ComposeResult:
|
|
116
|
-
|
|
117
|
-
yield tx.Static(id='messages-container')
|
|
66
|
+
yield tx.VerticalScroll(id='messages-container')
|
|
118
67
|
|
|
119
|
-
|
|
120
|
-
with tx.Vertical(id='input-vertical'):
|
|
121
|
-
with tx.Vertical(id='input-vertical2'):
|
|
122
|
-
with tx.Horizontal(id='input-horizontal'):
|
|
123
|
-
yield tx.Static('>', id='input-glyph')
|
|
124
|
-
yield InputTextArea(placeholder='...', id='input')
|
|
68
|
+
yield InputOuter(id='input-outer')
|
|
125
69
|
|
|
126
70
|
#
|
|
127
71
|
|
|
128
72
|
def _get_input_text_area(self) -> InputTextArea:
|
|
129
73
|
return self.query_one('#input', InputTextArea)
|
|
130
74
|
|
|
131
|
-
def _get_messages_container(self) -> tx.
|
|
132
|
-
return self.query_one('#messages-container', tx.
|
|
75
|
+
def _get_messages_container(self) -> tx.VerticalScroll:
|
|
76
|
+
return self.query_one('#messages-container', tx.VerticalScroll)
|
|
77
|
+
|
|
78
|
+
#
|
|
79
|
+
|
|
80
|
+
def _is_messages_at_bottom(self, threshold: int = 3) -> bool:
|
|
81
|
+
return (ms := self._get_messages_container()).scroll_y >= (ms.max_scroll_y - threshold)
|
|
82
|
+
|
|
83
|
+
def _scroll_messages_to_bottom(self) -> None:
|
|
84
|
+
self._get_messages_container().scroll_end(animate=False)
|
|
85
|
+
|
|
86
|
+
def _anchor_messages(self) -> None:
|
|
87
|
+
if (ms := self._get_messages_container()).max_scroll_y:
|
|
88
|
+
ms.anchor()
|
|
133
89
|
|
|
134
90
|
#
|
|
135
91
|
|
|
@@ -141,34 +97,73 @@ class ChatApp(tx.App):
|
|
|
141
97
|
|
|
142
98
|
lst.extend(messages)
|
|
143
99
|
|
|
100
|
+
_stream_ai_message: StreamAiMessage | None = None
|
|
101
|
+
|
|
102
|
+
async def _finalize_stream_ai_message(self) -> None:
|
|
103
|
+
if self._stream_ai_message is None:
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
await self._stream_ai_message.stop_stream()
|
|
107
|
+
self._stream_ai_message = None
|
|
108
|
+
|
|
109
|
+
async def _append_stream_ai_message_content(self, content: str) -> None:
|
|
110
|
+
if (sam := self._stream_ai_message) is not None:
|
|
111
|
+
was_at_bottom = self._is_messages_at_bottom()
|
|
112
|
+
|
|
113
|
+
await sam.append_content(content)
|
|
114
|
+
|
|
115
|
+
self.call_after_refresh(self._scroll_messages_to_bottom)
|
|
116
|
+
|
|
117
|
+
if was_at_bottom:
|
|
118
|
+
self.call_after_refresh(self._anchor_messages)
|
|
119
|
+
|
|
120
|
+
else:
|
|
121
|
+
await self._mount_messages(StreamAiMessage(content))
|
|
122
|
+
|
|
144
123
|
async def _mount_messages(self, *messages: tx.Widget) -> None:
|
|
124
|
+
was_at_bottom = self._is_messages_at_bottom()
|
|
125
|
+
|
|
145
126
|
msg_ctr = self._get_messages_container()
|
|
146
127
|
|
|
147
128
|
for msg in [*(self._pending_mount_messages or []), *messages]:
|
|
129
|
+
if isinstance(msg, (AiMessage, ToolConfirmationMessage)):
|
|
130
|
+
await self._finalize_stream_ai_message()
|
|
131
|
+
|
|
148
132
|
await msg_ctr.mount(msg)
|
|
149
133
|
|
|
134
|
+
if isinstance(msg, StreamAiMessage):
|
|
135
|
+
self._stream_ai_message = check.replacing_none(self._stream_ai_message, msg)
|
|
136
|
+
await msg.write_initial_content()
|
|
137
|
+
|
|
150
138
|
self._pending_mount_messages = None
|
|
151
139
|
|
|
152
|
-
self.call_after_refresh(
|
|
140
|
+
self.call_after_refresh(self._scroll_messages_to_bottom)
|
|
141
|
+
|
|
142
|
+
if was_at_bottom:
|
|
143
|
+
self.call_after_refresh(self._anchor_messages)
|
|
153
144
|
|
|
154
145
|
#
|
|
155
146
|
|
|
156
|
-
|
|
147
|
+
_chat_driver_event_task: asyncio.Task[None] | None = None
|
|
157
148
|
|
|
158
|
-
|
|
149
|
+
@logs.async_exception_logging(alog)
|
|
150
|
+
async def _chat_driver_event_task_main(self) -> None:
|
|
159
151
|
while True:
|
|
160
|
-
ev = await self.
|
|
152
|
+
ev = await self._chat_driver_event_queue.get()
|
|
161
153
|
if ev is None:
|
|
162
154
|
break
|
|
163
155
|
|
|
156
|
+
await alog.debug(lambda: f'Got chat driver event: {ev!r}')
|
|
157
|
+
|
|
164
158
|
if isinstance(ev, AiMessagesChatEvent):
|
|
165
159
|
wx: list[tx.Widget] = []
|
|
166
160
|
|
|
167
161
|
for ai_msg in ev.chat:
|
|
168
162
|
if isinstance(ai_msg, mc.AiMessage):
|
|
169
163
|
wx.append(
|
|
170
|
-
|
|
164
|
+
StaticAiMessage(
|
|
171
165
|
check.isinstance(ai_msg.c, str),
|
|
166
|
+
markdown=True,
|
|
172
167
|
),
|
|
173
168
|
)
|
|
174
169
|
|
|
@@ -176,13 +171,46 @@ class ChatApp(tx.App):
|
|
|
176
171
|
await self._enqueue_mount_messages(*wx)
|
|
177
172
|
self.call_later(self._mount_messages)
|
|
178
173
|
|
|
174
|
+
elif isinstance(ev, AiDeltaChatEvent):
|
|
175
|
+
if isinstance(ev.delta, mc.ContentAiDelta):
|
|
176
|
+
cc = check.isinstance(ev.delta.c, str)
|
|
177
|
+
self.call_later(self._append_stream_ai_message_content, cc)
|
|
178
|
+
|
|
179
|
+
elif isinstance(ev.delta, mc.ToolUseAiDelta):
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
#
|
|
183
|
+
|
|
184
|
+
_chat_driver_action_task: asyncio.Task[None] | None = None
|
|
185
|
+
|
|
186
|
+
@logs.async_exception_logging(alog)
|
|
187
|
+
async def _chat_driver_action_task_main(self) -> None:
|
|
188
|
+
while True:
|
|
189
|
+
ac = await self._chat_driver_action_queue.get()
|
|
190
|
+
if ac is None:
|
|
191
|
+
break
|
|
192
|
+
|
|
193
|
+
await alog.debug(lambda: f'Got chat driver action: {ac!r}')
|
|
194
|
+
|
|
195
|
+
if isinstance(ac, mc.UserMessage):
|
|
196
|
+
try:
|
|
197
|
+
await self._chat_driver.send_user_messages([ac])
|
|
198
|
+
except Exception as e: # noqa
|
|
199
|
+
raise
|
|
200
|
+
|
|
201
|
+
else:
|
|
202
|
+
raise TypeError(ac) # noqa
|
|
203
|
+
|
|
179
204
|
#
|
|
180
205
|
|
|
181
206
|
async def on_mount(self) -> None:
|
|
182
|
-
check.state(self.
|
|
183
|
-
self.
|
|
207
|
+
check.state(self._chat_driver_event_task is None)
|
|
208
|
+
self._chat_driver_event_task = asyncio.create_task(self._chat_driver_event_task_main())
|
|
184
209
|
|
|
185
|
-
await self.
|
|
210
|
+
await self._chat_driver.start()
|
|
211
|
+
|
|
212
|
+
check.state(self._chat_driver_action_task is None)
|
|
213
|
+
self._chat_driver_action_task = asyncio.create_task(self._chat_driver_action_task_main())
|
|
186
214
|
|
|
187
215
|
self._get_input_text_area().focus()
|
|
188
216
|
|
|
@@ -193,19 +221,42 @@ class ChatApp(tx.App):
|
|
|
193
221
|
)
|
|
194
222
|
|
|
195
223
|
async def on_unmount(self) -> None:
|
|
196
|
-
|
|
224
|
+
if (cdt := self._chat_driver_event_task) is not None:
|
|
225
|
+
await self._chat_driver_event_queue.put(None)
|
|
226
|
+
await cdt
|
|
227
|
+
|
|
228
|
+
await self._chat_driver.stop()
|
|
197
229
|
|
|
198
|
-
if (
|
|
199
|
-
await self.
|
|
200
|
-
await
|
|
230
|
+
if (cet := self._chat_driver_event_task) is not None:
|
|
231
|
+
await self._chat_driver_event_queue.put(None)
|
|
232
|
+
await cet
|
|
201
233
|
|
|
234
|
+
@tx.on(InputTextArea.Submitted)
|
|
202
235
|
async def on_input_text_area_submitted(self, event: InputTextArea.Submitted) -> None:
|
|
203
236
|
self._get_input_text_area().clear()
|
|
204
237
|
|
|
238
|
+
await self._finalize_stream_ai_message()
|
|
239
|
+
|
|
205
240
|
await self._mount_messages(
|
|
206
241
|
UserMessage(
|
|
207
242
|
event.text,
|
|
208
243
|
),
|
|
209
244
|
)
|
|
210
245
|
|
|
211
|
-
await self.
|
|
246
|
+
await self._chat_driver_action_queue.put(mc.UserMessage(event.text))
|
|
247
|
+
|
|
248
|
+
#
|
|
249
|
+
|
|
250
|
+
async def confirm_tool_use(self, message: str) -> bool:
|
|
251
|
+
fut: asyncio.Future[bool] = asyncio.get_running_loop().create_future()
|
|
252
|
+
|
|
253
|
+
tcm = ToolConfirmationMessage(message, fut)
|
|
254
|
+
|
|
255
|
+
async def inner() -> None:
|
|
256
|
+
await self._mount_messages(tcm)
|
|
257
|
+
|
|
258
|
+
self.call_later(inner)
|
|
259
|
+
|
|
260
|
+
ret = await fut
|
|
261
|
+
|
|
262
|
+
return ret
|
|
@@ -5,39 +5,64 @@ FIXME:
|
|
|
5
5
|
import asyncio
|
|
6
6
|
|
|
7
7
|
from omlish import inject as inj
|
|
8
|
+
from omlish import lang
|
|
8
9
|
|
|
9
|
-
from ...
|
|
10
|
+
from ...drivers.events.injection import event_callbacks
|
|
10
11
|
from ..base import ChatInterface
|
|
11
|
-
from
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
from ..configs import InterfaceConfig
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
with lang.auto_proxy_import(globals()):
|
|
16
|
+
from ...drivers.tools import confirmation as _tools_confirmation
|
|
17
|
+
from . import app as _app
|
|
18
|
+
from . import interface as _interface
|
|
19
|
+
from . import tools as _tools
|
|
14
20
|
|
|
15
21
|
|
|
16
22
|
##
|
|
17
23
|
|
|
18
24
|
|
|
19
|
-
def bind_textual() -> inj.Elements:
|
|
25
|
+
def bind_textual(cfg: InterfaceConfig = InterfaceConfig()) -> inj.Elements:
|
|
20
26
|
els: list[inj.Elemental] = [
|
|
21
|
-
inj.bind(ChatInterface, to_ctor=TextualChatInterface, singleton=True),
|
|
27
|
+
inj.bind(ChatInterface, to_ctor=_interface.TextualChatInterface, singleton=True),
|
|
22
28
|
]
|
|
23
29
|
|
|
24
30
|
#
|
|
25
31
|
|
|
26
32
|
els.extend([
|
|
27
|
-
inj.bind(ChatApp, singleton=True),
|
|
33
|
+
inj.bind(_app.ChatApp, singleton=True),
|
|
34
|
+
|
|
35
|
+
inj.bind_late(_app.ChatApp, _app.ChatAppGetter),
|
|
28
36
|
])
|
|
29
37
|
|
|
30
38
|
#
|
|
31
39
|
|
|
32
40
|
els.extend([
|
|
33
|
-
inj.bind(
|
|
41
|
+
inj.bind(_app.ChatDriverEventQueue, to_const=asyncio.Queue()),
|
|
34
42
|
|
|
35
43
|
event_callbacks().bind_item(to_fn=inj.KwargsTarget.of(
|
|
36
44
|
lambda eq: lambda ev: eq.put(ev),
|
|
37
|
-
eq=
|
|
45
|
+
eq=_app.ChatDriverEventQueue,
|
|
38
46
|
)),
|
|
39
47
|
])
|
|
40
48
|
|
|
41
49
|
#
|
|
42
50
|
|
|
51
|
+
if cfg.enable_tools:
|
|
52
|
+
if cfg.dangerous_no_tool_confirmation:
|
|
53
|
+
els.append(inj.bind(
|
|
54
|
+
_tools_confirmation.ToolExecutionConfirmation,
|
|
55
|
+
to_ctor=_tools_confirmation.UnsafeAlwaysAllowToolExecutionConfirmation,
|
|
56
|
+
singleton=True,
|
|
57
|
+
))
|
|
58
|
+
|
|
59
|
+
else:
|
|
60
|
+
els.append(inj.bind(
|
|
61
|
+
_tools_confirmation.ToolExecutionConfirmation,
|
|
62
|
+
to_ctor=_tools.ChatAppToolExecutionConfirmation,
|
|
63
|
+
singleton=True,
|
|
64
|
+
))
|
|
65
|
+
|
|
66
|
+
#
|
|
67
|
+
|
|
43
68
|
return inj.as_elements(*els)
|
|
@@ -1,7 +1,90 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
from omdev.tui import textual as tx
|
|
6
|
+
|
|
1
7
|
from ..base import ChatInterface
|
|
2
8
|
from .app import ChatApp
|
|
3
9
|
|
|
4
10
|
|
|
11
|
+
if ta.TYPE_CHECKING:
|
|
12
|
+
from textual_dev.client import DevtoolsClient
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _translate_log_level(level: int) -> tuple['tx.LogGroup', 'tx.LogVerbosity']:
|
|
19
|
+
if level >= logging.ERROR:
|
|
20
|
+
return (tx.LogGroup.ERROR, tx.LogVerbosity.HIGH)
|
|
21
|
+
elif level >= logging.WARNING:
|
|
22
|
+
return (tx.LogGroup.ERROR, tx.LogVerbosity.HIGH)
|
|
23
|
+
elif level >= logging.INFO:
|
|
24
|
+
return (tx.LogGroup.INFO, tx.LogVerbosity.NORMAL)
|
|
25
|
+
elif level >= logging.DEBUG:
|
|
26
|
+
return (tx.LogGroup.DEBUG, tx.LogVerbosity.NORMAL)
|
|
27
|
+
else:
|
|
28
|
+
return (tx.LogGroup.UNDEFINED, tx.LogVerbosity.NORMAL)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class _HackLoggingHandler(logging.Handler):
|
|
32
|
+
"""
|
|
33
|
+
TODO:
|
|
34
|
+
- reify caller from LogContextInfos
|
|
35
|
+
- queue worker, this blocks the asyncio thread lol
|
|
36
|
+
- move to omdev.tui.textual obviously
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, devtools: ta.Optional['DevtoolsClient']) -> None:
|
|
40
|
+
super().__init__()
|
|
41
|
+
|
|
42
|
+
self._devtools = devtools
|
|
43
|
+
|
|
44
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
45
|
+
if (devtools := self._devtools) is not None and devtools.is_connected:
|
|
46
|
+
from textual_dev.client import DevtoolsLog
|
|
47
|
+
|
|
48
|
+
msg = self.format(record)
|
|
49
|
+
|
|
50
|
+
caller = inspect.Traceback(
|
|
51
|
+
filename=record.filename,
|
|
52
|
+
lineno=record.lineno,
|
|
53
|
+
function=record.funcName,
|
|
54
|
+
code_context=None,
|
|
55
|
+
index=None,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
group, verbosity = _translate_log_level(record.levelno)
|
|
59
|
+
|
|
60
|
+
devtools.log(
|
|
61
|
+
DevtoolsLog(
|
|
62
|
+
msg,
|
|
63
|
+
caller=caller,
|
|
64
|
+
),
|
|
65
|
+
group=group,
|
|
66
|
+
verbosity=verbosity,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _hack_loggers(devtools: ta.Optional['DevtoolsClient']) -> None:
|
|
71
|
+
from omlish.logs.std.standard import _locking_logging_module_lock # noqa
|
|
72
|
+
from omlish.logs.std.standard import StandardConfiguredLoggingHandler
|
|
73
|
+
|
|
74
|
+
with _locking_logging_module_lock():
|
|
75
|
+
std_handler = next((h for h in logging.root.handlers if isinstance(h, StandardConfiguredLoggingHandler)), None)
|
|
76
|
+
|
|
77
|
+
hack_handler = _HackLoggingHandler(devtools)
|
|
78
|
+
|
|
79
|
+
if std_handler is not None:
|
|
80
|
+
hack_handler.setFormatter(std_handler.formatter)
|
|
81
|
+
|
|
82
|
+
for std_filter in std_handler.filters:
|
|
83
|
+
hack_handler.addFilter(std_filter)
|
|
84
|
+
|
|
85
|
+
logging.root.handlers = [hack_handler]
|
|
86
|
+
|
|
87
|
+
|
|
5
88
|
##
|
|
6
89
|
|
|
7
90
|
|
|
@@ -16,4 +99,6 @@ class TextualChatInterface(ChatInterface):
|
|
|
16
99
|
self._app = app
|
|
17
100
|
|
|
18
101
|
async def run(self) -> None:
|
|
102
|
+
_hack_loggers(self._app.devtools)
|
|
103
|
+
|
|
19
104
|
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()
|