ommlds 0.0.0.dev503__py3-none-any.whl → 0.0.0.dev505__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 (30) hide show
  1. ommlds/.omlish-manifests.json +5 -5
  2. ommlds/__about__.py +1 -1
  3. ommlds/backends/anthropic/protocol/_dataclasses.py +16 -16
  4. ommlds/backends/cerebras/_dataclasses.py +42 -42
  5. ommlds/backends/google/protocol/_dataclasses.py +64 -64
  6. ommlds/backends/groq/_dataclasses.py +36 -36
  7. ommlds/backends/ollama/_dataclasses.py +28 -28
  8. ommlds/backends/openai/protocol/_dataclasses.py +88 -88
  9. ommlds/backends/tavily/_dataclasses.py +16 -16
  10. ommlds/cli/_dataclasses.py +212 -43
  11. ommlds/cli/sessions/chat/interfaces/textual/app.py +34 -0
  12. ommlds/cli/sessions/chat/interfaces/textual/configs.py +1 -1
  13. ommlds/cli/sessions/chat/interfaces/textual/inject.py +14 -0
  14. ommlds/cli/sessions/chat/interfaces/textual/inputhistory.py +174 -0
  15. ommlds/cli/sessions/chat/interfaces/textual/widgets/input.py +42 -8
  16. ommlds/minichain/_dataclasses.py +361 -343
  17. ommlds/minichain/backends/impls/cerebras/stream.py +39 -52
  18. ommlds/minichain/backends/impls/google/chat.py +11 -82
  19. ommlds/minichain/backends/impls/google/protocol.py +105 -0
  20. ommlds/minichain/backends/impls/google/stream.py +49 -132
  21. ommlds/minichain/backends/impls/groq/stream.py +40 -53
  22. ommlds/minichain/backends/impls/openai/stream.py +40 -87
  23. ommlds/minichain/http/__init__.py +0 -0
  24. ommlds/minichain/http/stream.py +195 -0
  25. {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/METADATA +6 -6
  26. {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/RECORD +30 -26
  27. {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/WHEEL +0 -0
  28. {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/entry_points.txt +0 -0
  29. {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/licenses/LICENSE +0 -0
  30. {ommlds-0.0.0.dev503.dist-info → ommlds-0.0.0.dev505.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,174 @@
1
+ import abc
2
+ import json
3
+ import os
4
+ import typing as ta
5
+
6
+ from omlish import lang
7
+
8
+
9
+ ##
10
+
11
+
12
+ class InputHistoryStorage(lang.Abstract):
13
+ @abc.abstractmethod
14
+ def load(self) -> list[str]:
15
+ raise NotImplementedError
16
+
17
+ @abc.abstractmethod
18
+ def save(self, entries: ta.Sequence[str]) -> None:
19
+ raise NotImplementedError
20
+
21
+
22
+ class InMemoryInputHistoryStorage(InputHistoryStorage):
23
+ def __init__(self) -> None:
24
+ super().__init__()
25
+
26
+ self._entries: list[str] = []
27
+
28
+ def load(self) -> list[str]:
29
+ return list(self._entries)
30
+
31
+ def save(self, entries: ta.Sequence[str]) -> None:
32
+ self._entries = list(entries)
33
+
34
+
35
+ class FileInputHistoryStorage(InputHistoryStorage):
36
+ def __init__(self, *, path: str) -> None:
37
+ super().__init__()
38
+
39
+ self._path = path
40
+
41
+ def load(self) -> list[str]:
42
+ if not os.path.exists(self._path):
43
+ return []
44
+
45
+ try:
46
+ with open(self._path) as f:
47
+ data = json.load(f)
48
+ if isinstance(data, list) and all(isinstance(e, str) for e in data):
49
+ return data
50
+ return []
51
+ except (json.JSONDecodeError, OSError):
52
+ return []
53
+
54
+ def save(self, entries: ta.Sequence[str]) -> None:
55
+ try:
56
+ dir_path = os.path.dirname(self._path)
57
+ if dir_path:
58
+ os.makedirs(dir_path, exist_ok=True)
59
+ with open(self._path, 'w') as f:
60
+ json.dump(list(entries), f, indent=2)
61
+ except OSError:
62
+ pass
63
+
64
+
65
+ ##
66
+
67
+
68
+ class InputHistoryManager:
69
+ """
70
+ Manages input history with readline-style navigation.
71
+
72
+ History position semantics:
73
+ - Position starts at len(history) (one past the end)
74
+ - Moving 'previous' decrements position
75
+ - Moving 'next' increments position
76
+ - Position is clamped to [0, len(history)]
77
+ - Position len(history) represents 'no history item selected'
78
+ """
79
+
80
+ def __init__(
81
+ self,
82
+ *,
83
+ storage: InputHistoryStorage,
84
+ max_entries: int = 1000,
85
+ ) -> None:
86
+ super().__init__()
87
+
88
+ self._storage = storage
89
+ self._max_entries = max_entries
90
+
91
+ self._entries: list[str] = self._storage.load()
92
+ self._position: int = len(self._entries)
93
+ self._current_draft: str = ''
94
+
95
+ def add(self, text: str) -> None:
96
+ """Add a new history entry and reset position."""
97
+
98
+ if not text.strip():
99
+ return
100
+
101
+ # Don't add duplicate consecutive entries
102
+ if self._entries and self._entries[-1] == text:
103
+ self.reset_position()
104
+ return
105
+
106
+ self._entries.append(text)
107
+
108
+ # Trim to max size
109
+ if len(self._entries) > self._max_entries:
110
+ self._entries = self._entries[-self._max_entries:]
111
+
112
+ self._storage.save(self._entries)
113
+ self.reset_position()
114
+
115
+ def get_previous(self, text: str | None = None) -> str | None:
116
+ """
117
+ Navigate to previous (older) history entry.
118
+
119
+ Args:
120
+ text: Current text in the input field (saved as draft when at end of history)
121
+
122
+ Returns:
123
+ The previous history entry, or None if at the beginning
124
+ """
125
+
126
+ if not self._entries:
127
+ return None
128
+
129
+ # Save current draft if we're at the end
130
+ if self._position == len(self._entries) and text is not None:
131
+ self._current_draft = text
132
+
133
+ # Move to previous entry
134
+ if self._position > 0:
135
+ self._position -= 1
136
+ return self._entries[self._position]
137
+
138
+ # Already at oldest entry
139
+ return self._entries[0] if self._entries else None
140
+
141
+ def get_next(self, text: str | None = None) -> str | None:
142
+ """
143
+ Navigate to next (newer) history entry.
144
+
145
+ Args:
146
+ text: Current text in the input field (unused for now)
147
+
148
+ Returns:
149
+ The next history entry, the saved draft if moving past the end, or None
150
+ """
151
+
152
+ if not self._entries:
153
+ return None
154
+
155
+ # Move to next entry
156
+ if self._position < len(self._entries):
157
+ self._position += 1
158
+
159
+ # If we moved past the end, return the draft
160
+ if self._position == len(self._entries):
161
+ draft = self._current_draft
162
+ self._current_draft = ''
163
+ return draft
164
+
165
+ return self._entries[self._position]
166
+
167
+ # Already at newest position
168
+ return None
169
+
170
+ def reset_position(self) -> None:
171
+ """Reset history position to the end (no history item selected)."""
172
+
173
+ self._position = len(self._entries)
174
+ self._current_draft = ''
@@ -12,19 +12,53 @@ class InputTextArea(tx.TextArea):
12
12
  class Submitted(tx.Message):
13
13
  text: str
14
14
 
15
+ @dc.dataclass()
16
+ class HistoryPrevious(tx.Message):
17
+ text: str
18
+
19
+ @dc.dataclass()
20
+ class HistoryNext(tx.Message):
21
+ text: str
22
+
23
+ @dc.dataclass()
24
+ class HistoryReset(tx.Message):
25
+ pass
26
+
15
27
  def __init__(self, **kwargs: ta.Any) -> None:
16
28
  super().__init__(**kwargs)
17
29
 
18
- async def _on_key(self, event: tx.Key) -> None:
19
- if event.key == 'enter':
20
- event.prevent_default()
21
- event.stop()
30
+ BINDINGS: ta.ClassVar[ta.Sequence[tx.Binding]] = [ # type: ignore[assignment]
31
+ tx.Binding(
32
+ 'enter',
33
+ 'submit',
34
+ priority=True,
35
+ ),
36
+ tx.Binding(
37
+ 'ctrl+p',
38
+ 'history_previous',
39
+ ),
40
+ tx.Binding(
41
+ 'ctrl+n',
42
+ 'history_next',
43
+ ),
44
+ ]
45
+
46
+ def action_submit(self) -> None:
47
+ if text := self.text.strip():
48
+ self.post_message(self.Submitted(text))
49
+
50
+ def action_cursor_up(self, select: bool = False) -> None:
51
+ # FIXME: if empty -> history_previous
52
+ super().action_cursor_up(select=select)
53
+
54
+ def action_cursor_down(self, select: bool = False) -> None:
55
+ super().action_cursor_down(select=select)
22
56
 
23
- if text := self.text.strip():
24
- self.post_message(self.Submitted(text))
57
+ def action_history_previous(self) -> None:
58
+ self.post_message(self.HistoryPrevious(self.text))
25
59
 
26
- else:
27
- await super()._on_key(event)
60
+ def action_history_next(self) -> None:
61
+ self.post_message(self.HistoryNext(self.text))
28
62
 
29
63
 
30
64
  class InputOuter(tx.Static):