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,57 @@
1
+ import typing as ta
2
+
3
+ from omlish import lang
4
+
5
+ from ..services.requests import Request
6
+ from ..services.services import Service
7
+ from ..stream.services import StreamOptions
8
+ from ..stream.services import StreamResponse
9
+ from ..types import Output
10
+ from .services import WrappedOptionT
11
+ from .services import WrappedOutputT
12
+ from .services import WrappedRequestV
13
+ from .services import WrappedResponseV
14
+
15
+
16
+ WrappedStreamOutputT = ta.TypeVar('WrappedStreamOutputT', bound=Output)
17
+
18
+
19
+ WrappedStreamOptions: ta.TypeAlias = WrappedOptionT | StreamOptions
20
+
21
+ WrappedStreamRequest: ta.TypeAlias = Request[
22
+ WrappedRequestV,
23
+ WrappedStreamOptions,
24
+ ]
25
+
26
+ WrappedStreamResponse: ta.TypeAlias = StreamResponse[
27
+ WrappedResponseV,
28
+ WrappedOutputT,
29
+ WrappedStreamOutputT,
30
+ ]
31
+
32
+ WrappedStreamService: ta.TypeAlias = Service[
33
+ WrappedStreamRequest,
34
+ WrappedStreamResponse,
35
+ ]
36
+
37
+
38
+ ##
39
+
40
+
41
+ class WrapperStreamService(
42
+ lang.Abstract,
43
+ ta.Generic[
44
+ WrappedRequestV,
45
+ WrappedOptionT,
46
+ WrappedResponseV,
47
+ WrappedOutputT,
48
+ WrappedStreamOutputT,
49
+ ],
50
+ ):
51
+ def __init__(
52
+ self,
53
+ service: WrappedStreamService,
54
+ ) -> None:
55
+ super().__init__()
56
+
57
+ self._service = service
@@ -0,0 +1,9 @@
1
+ # https://github.com/karpathy/nanochat/tree/9467d83cf23dcc9a9b4ca6e35103142f48a55b27
2
+
3
+ ---
4
+
5
+ # rustbpe
6
+
7
+ > The missing tiktoken training code
8
+
9
+ A very lightweight Rust library for training a GPT tokenizer. The issue is that the inference library [tiktoken](https://github.com/openai/tiktoken) is great, but only does inference. Separately, the huggingface [tokenizers](https://github.com/huggingface/tokenizers) library does training, but it is rather bloated and really hard to navigate because it has to support all the different historical baggage of how people dealt with tokenizers over the years. More recently, I also wrote the [minbpe](https://github.com/karpathy/minbpe) library which does both training and inference, but only in inefficient Python. Basically what I really want is a non-fancy, super simple, but still relatively efficient training code for GPT tokenizer (more efficient than minbpe, much cleaner/simpler than tokenizers), and then export the trained vocab for inference with tiktoken. Does that make sense? So here we are. There are more opportunities for optimization here, I just stopped a bit early because unlike minbpe before it, rustbpe is now simple and fast enough, and not a significant bottleneck for nanochat.
@@ -18,7 +18,10 @@ from omlish import lang
18
18
 
19
19
  with lang.auto_proxy_import(globals()):
20
20
  import tiktoken
21
- import tokenizers
21
+ import tokenizers.decoders
22
+ import tokenizers.models
23
+ import tokenizers.pre_tokenizers
24
+ import tokenizers.trainers
22
25
 
23
26
 
24
27
  rustbpe: ta.Any = lang.proxy_import('.rustbpe', __package__)
@@ -27,7 +30,7 @@ rustbpe: ta.Any = lang.proxy_import('.rustbpe', __package__)
27
30
  ##
28
31
 
29
32
 
30
- SPECIAL_TOKENS = [
33
+ SPECIAL_TOKENS: ta.Sequence[str] = [
31
34
  # every document begins with the Beginning of Sequence (BOS) token that delimits documents
32
35
  '<|bos|>',
33
36
  # tokens below are only used during finetuning to render Conversations into token ids
@@ -45,10 +48,18 @@ SPECIAL_TOKENS = [
45
48
  # NOTE: this split pattern deviates from GPT-4 in that we use \p{N}{1,2} instead of \p{N}{1,3}
46
49
  # I did this because I didn't want to "waste" too many tokens on numbers for smaller vocab sizes.
47
50
  # I haven't validated that this is actually a good idea, TODO.
48
- SPLIT_PATTERN = r"""'(?i:[sdmt]|ll|ve|re)|[^\r\n\p{L}\p{N}]?+\p{L}+|\p{N}{1,2}| ?[^\s\p{L}\p{N}]++[\r\n]*|\s*[\r\n]|\s+(?!\S)|\s+""" # noqa
51
+ SPLIT_PATTERN = (
52
+ r"'(?i:[sdmt]|ll|ve|re)|"
53
+ r"[^\r\n\p{L}\p{N}]?+\p{L}+|"
54
+ r"\p{N}{1,2}|"
55
+ r" ?[^\s\p{L}\p{N}]++[\r\n]*|"
56
+ r"\s*[\r\n]|"
57
+ r"\s+(?!\S)|"
58
+ r"\s+"
59
+ )
49
60
 
50
61
 
51
- # -----------------------------------------------------------------------------
62
+ ##
52
63
  # Generic GPT-4-style tokenizer based on HuggingFace Tokenizer
53
64
 
54
65
 
@@ -87,22 +98,28 @@ class HuggingFaceTokenizer:
87
98
  unk_token=None,
88
99
  fuse_unk=False,
89
100
  ))
101
+
90
102
  # Normalizer: None
91
103
  tokenizer.normalizer = None
104
+
92
105
  # Pre-tokenizer: GPT-4 style
93
106
  # the regex pattern used by GPT-4 to split text into groups before BPE
94
107
  # NOTE: The pattern was changed from \p{N}{1,3} to \p{N}{1,2} because I suspect it is harmful to
95
108
  # very small models and smaller vocab sizes, because it is a little bit wasteful in the token space.
96
109
  # (but I haven't validated this! TODO)
97
110
  gpt4_split_regex = tokenizers.Regex(split_pattern) # huggingface demands that you wrap it in Regex!!
111
+
98
112
  tokenizer.pre_tokenizer = tokenizers.pre_tokenizers.Sequence([
99
113
  tokenizers.pre_tokenizers.Split(pattern=gpt4_split_regex, behavior='isolated', invert=False),
100
114
  tokenizers.pre_tokenizers.ByteLevel(add_prefix_space=False, use_regex=False),
101
115
  ])
116
+
102
117
  # Decoder: ByteLevel (it pairs together with the ByteLevel pre-tokenizer)
103
118
  tokenizer.decoder = tokenizers.decoders.ByteLevel()
119
+
104
120
  # Post-processor: None
105
121
  tokenizer.post_processor = None
122
+
106
123
  # Trainer: BPE
107
124
  trainer = tokenizers.trainers.BpeTrainer(
108
125
  vocab_size=vocab_size,
@@ -111,8 +128,10 @@ class HuggingFaceTokenizer:
111
128
  initial_alphabet=tokenizers.pre_tokenizers.ByteLevel.alphabet(),
112
129
  special_tokens=special_tokens,
113
130
  )
131
+
114
132
  # Kick off the training
115
133
  tokenizer.train_from_iterator(text_iterator, trainer)
134
+
116
135
  return cls(tokenizer)
117
136
 
118
137
  def encode_ordinary(self, text):
@@ -174,7 +193,7 @@ class HuggingFaceTokenizer:
174
193
  print(f'Saved tokenizer to {tokenizer_path}')
175
194
 
176
195
 
177
- # -----------------------------------------------------------------------------
196
+ ##
178
197
  # Tokenizer based on rustbpe + tiktoken combo
179
198
 
180
199
 
@@ -255,6 +274,7 @@ class RustBPETokenizer:
255
274
  ids.insert(0, prepend_id) # TODO: slightly inefficient here? :( hmm
256
275
  if append is not None:
257
276
  ids.append(append_id)
277
+
258
278
  elif isinstance(text, list):
259
279
  ids = self.enc.encode_ordinary_batch(text, num_threads=num_threads)
260
280
  if prepend is not None:
@@ -263,6 +283,7 @@ class RustBPETokenizer:
263
283
  if append is not None:
264
284
  for ids_row in ids:
265
285
  ids_row.append(append_id)
286
+
266
287
  else:
267
288
  raise ValueError(f'Invalid input type: {type(text)}') # noqa
268
289
 
@@ -285,6 +306,7 @@ class RustBPETokenizer:
285
306
  def render_conversation(self, conversation, max_tokens=2048):
286
307
  """
287
308
  Tokenize a single Chat conversation (which we call a "doc" or "document" here).
309
+
288
310
  Returns:
289
311
  - ids: list[int] is a list of token ids of this rendered conversation
290
312
  - mask: list[int] of same length, mask = 1 for tokens that the Assistant is expected to train on.
@@ -324,7 +346,10 @@ class RustBPETokenizer:
324
346
  for i, message in enumerate(messages):
325
347
  # some sanity checking here around assumptions, to prevent footguns
326
348
  must_be_from = 'user' if i % 2 == 0 else 'assistant'
327
- check.state(message['role'] == must_be_from, f"Message {i} is from {message['role']} but should be from {must_be_from}") # noqa
349
+ check.state(
350
+ message['role'] == must_be_from,
351
+ f"Message {i} is from {message['role']} but should be from {must_be_from}",
352
+ )
328
353
 
329
354
  # content can be either a simple string or a list of parts (e.g. containing tool calls)
330
355
  content = message['content']
@@ -335,33 +360,42 @@ class RustBPETokenizer:
335
360
  add_tokens(user_start, 0)
336
361
  add_tokens(value_ids, 0)
337
362
  add_tokens(user_end, 0)
363
+
338
364
  elif message['role'] == 'assistant':
339
365
  add_tokens(assistant_start, 0)
366
+
340
367
  if isinstance(content, str):
341
368
  # simple string => simply add the tokens
342
369
  value_ids = self.encode(content)
343
370
  add_tokens(value_ids, 1)
371
+
344
372
  elif isinstance(content, list):
345
373
  for part in content:
346
374
  value_ids = self.encode(part['text'])
375
+
347
376
  if part['type'] == 'text':
348
377
  # string part => simply add the tokens
349
378
  add_tokens(value_ids, 1)
379
+
350
380
  elif part['type'] == 'python':
351
381
  # python tool call => add the tokens inside <|python_start|> and <|python_end|>
352
382
  add_tokens(python_start, 1)
353
383
  add_tokens(value_ids, 1)
354
384
  add_tokens(python_end, 1)
385
+
355
386
  elif part['type'] == 'python_output':
356
387
  # python output => add the tokens inside <|output_start|> and <|output_end|>
357
388
  # none of these tokens are supervised because the tokens come from Python at test time
358
389
  add_tokens(output_start, 0)
359
390
  add_tokens(value_ids, 0)
360
391
  add_tokens(output_end, 0)
392
+
361
393
  else:
362
394
  raise ValueError(f"Unknown part type: {part['type']}")
395
+
363
396
  else:
364
397
  raise ValueError(f'Unknown content type: {type(content)}')
398
+
365
399
  add_tokens(assistant_end, 1)
366
400
 
367
401
  # truncate to max_tokens tokens MAX (helps prevent OOMs)
@@ -0,0 +1,146 @@
1
+ import contextlib
2
+ import subprocess
3
+ import typing as ta
4
+
5
+ import anyio.abc
6
+
7
+ from omlish import check
8
+ from omlish import dataclasses as dc
9
+ from omlish import marshal as msh
10
+ from omlish.asyncs import anyio as aiu
11
+ from omlish.specs import jsonrpc as jr
12
+
13
+ from . import protocol as pt
14
+
15
+
16
+ ##
17
+
18
+
19
+ class McpServerConnection:
20
+ def __init__(
21
+ self,
22
+ tg: anyio.abc.TaskGroup,
23
+ stream: anyio.abc.ByteStream,
24
+ *,
25
+ default_timeout: float | None = 30.,
26
+ ) -> None:
27
+ super().__init__()
28
+
29
+ self._conn = jr.Connection(
30
+ tg,
31
+ stream,
32
+ request_handler=self._handle_client_request,
33
+ notification_handler=self._handle_client_notification,
34
+ default_timeout=default_timeout,
35
+ )
36
+
37
+ #
38
+
39
+ @classmethod
40
+ def from_process(
41
+ cls,
42
+ tg: anyio.abc.TaskGroup,
43
+ proc: anyio.abc.Process,
44
+ **kwargs: ta.Any,
45
+ ) -> 'McpServerConnection':
46
+ return cls(
47
+ tg,
48
+ aiu.StapledByteStream(
49
+ check.not_none(proc.stdin),
50
+ check.not_none(proc.stdout),
51
+ ),
52
+ **kwargs,
53
+ )
54
+
55
+ @classmethod
56
+ def open_process(
57
+ cls,
58
+ tg: anyio.abc.TaskGroup,
59
+ cmd: ta.Sequence[str],
60
+ open_kwargs: ta.Mapping[str, ta.Any] | None = None,
61
+ **kwargs: ta.Any,
62
+ ) -> ta.AsyncContextManager[tuple[anyio.abc.Process, 'McpServerConnection']]:
63
+ @contextlib.asynccontextmanager
64
+ async def inner():
65
+ async with await anyio.open_process(
66
+ cmd,
67
+ stdin=subprocess.PIPE,
68
+ stdout=subprocess.PIPE,
69
+ **open_kwargs or {},
70
+ ) as proc:
71
+ async with cls.from_process(
72
+ tg,
73
+ proc,
74
+ **kwargs,
75
+ ) as client:
76
+ yield (proc, client)
77
+
78
+ return inner()
79
+
80
+ #
81
+
82
+ async def __aenter__(self) -> 'McpServerConnection':
83
+ await self._conn.__aenter__()
84
+ return self
85
+
86
+ async def __aexit__(self, et, e, tb) -> None:
87
+ await self._conn.__aexit__(et, e, tb)
88
+
89
+ #
90
+
91
+ async def _handle_client_request(self, _client: jr.Connection, req: jr.Request) -> None:
92
+ pass
93
+
94
+ async def _handle_client_notification(self, _client: jr.Connection, no: jr.Request) -> None:
95
+ pass
96
+
97
+ #
98
+
99
+ async def request(self, req: pt.ClientRequest[pt.ClientResultT]) -> pt.ClientResultT:
100
+ res_cls = pt.MESSAGE_TYPES_BY_JSON_RPC_METHOD_NAME[pt.ClientResult][req.json_rpc_method_name] # type: ignore[type-abstract] # noqa
101
+ req_mv = msh.marshal(req)
102
+ res_mv = await self._conn.request(req.json_rpc_method_name, req_mv) # type: ignore[arg-type]
103
+ res = msh.unmarshal(res_mv, res_cls)
104
+ return ta.cast(pt.ClientResultT, res)
105
+
106
+ async def notify(self, no: pt.Notification) -> None:
107
+ no_mv = msh.marshal(no)
108
+ await self._conn.notify(no.json_rpc_method_name, no_mv) # type: ignore[arg-type]
109
+
110
+ #
111
+
112
+ async def yield_cursor_request(
113
+ self,
114
+ req: pt.CursorClientRequest[pt.CursorClientResultT],
115
+ ) -> ta.AsyncGenerator[pt.CursorClientResultT]:
116
+ check.none(req.cursor)
117
+
118
+ cursor: str | None = None
119
+ while True:
120
+ res = await self.request(dc.replace(req, cursor=cursor)) # noqa
121
+ yield res
122
+
123
+ if (cursor := res.next_cursor) is None:
124
+ break
125
+
126
+ async def list_cursor_request(
127
+ self,
128
+ req: pt.CursorClientRequest[pt.CursorClientResultT],
129
+ ) -> list[pt.CursorClientResultT]:
130
+ return [res async for res in self.yield_cursor_request(req)]
131
+
132
+ #
133
+
134
+ async def list_tools(self) -> list[pt.Tool]:
135
+ return [
136
+ tool
137
+ async for res in self.yield_cursor_request(pt.ListToolsRequest())
138
+ for tool in res.tools
139
+ ]
140
+
141
+ async def list_prompts(self) -> list[pt.Prompt]:
142
+ return [
143
+ prompt
144
+ async for res in self.yield_cursor_request(pt.ListPromptsRequest())
145
+ for prompt in res.prompts
146
+ ]
@@ -1,6 +1,15 @@
1
1
  """
2
2
  https://modelcontextprotocol.io/specification/2025-06-18
3
3
  https://modelcontextprotocol.io/specification/2025-06-18/schema
4
+
5
+ TODO:
6
+ - https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/cancellation
7
+ - https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress
8
+ - https://modelcontextprotocol.io/specification/2025-06-18/client/sampling
9
+ - https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation
10
+ - https://modelcontextprotocol.io/specification/2025-06-18/server/prompts
11
+ - https://modelcontextprotocol.io/specification/2025-06-18/server/resources
12
+ - https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging
4
13
  """
5
14
  import abc
6
15
  import typing as ta
@@ -11,6 +20,16 @@ from omlish import lang
11
20
  from omlish import marshal as msh
12
21
 
13
22
 
23
+ ClientRequestT = ta.TypeVar('ClientRequestT', bound='ClientRequest')
24
+ ClientResultT = ta.TypeVar('ClientResultT', bound='ClientResult')
25
+
26
+ ServerRequestT = ta.TypeVar('ServerRequestT', bound='ServerRequest')
27
+ ServerResultT = ta.TypeVar('ServerResultT', bound='ServerResult')
28
+
29
+ CursorClientRequestT = ta.TypeVar('CursorClientRequestT', bound='CursorClientRequest')
30
+ CursorClientResultT = ta.TypeVar('CursorClientResultT', bound='CursorClientResult')
31
+
32
+
14
33
  msh.register_global_module_import('._marshal', __package__)
15
34
 
16
35
 
@@ -41,27 +60,36 @@ class Message(lang.Sealed, lang.Abstract):
41
60
  raise NotImplementedError
42
61
 
43
62
 
44
- class ClientRequest(Message, lang.Abstract):
63
+ #
64
+
65
+
66
+ class ClientRequest(Message, lang.Abstract, ta.Generic[ClientResultT]):
45
67
  pass
46
68
 
47
69
 
48
- class ClientResult(Message, lang.Abstract):
70
+ class ClientResult(Message, lang.Abstract, ta.Generic[ClientRequestT]):
49
71
  pass
50
72
 
51
73
 
52
- class ServerRequest(Message, lang.Abstract):
74
+ #
75
+
76
+
77
+ class ServerRequest(Message, lang.Abstract, ta.Generic[ServerResultT]):
53
78
  pass
54
79
 
55
80
 
56
- class ServerResult(Message, lang.Abstract):
81
+ class ServerResult(Message, lang.Abstract, ta.Generic[ServerRequestT]):
57
82
  pass
58
83
 
59
84
 
85
+ #
86
+
87
+
60
88
  class Notification(Message, lang.Abstract):
61
89
  pass
62
90
 
63
91
 
64
- #
92
+ ##
65
93
 
66
94
 
67
95
  @dc.dataclass(frozen=True, kw_only=True)
@@ -84,7 +112,7 @@ DEFAULT_PROTOCOL_VERSION = '2025-06-18'
84
112
 
85
113
  @dc.dataclass(frozen=True, kw_only=True)
86
114
  @_set_class_marshal_options
87
- class ClientCapabilities:
115
+ class ClientCapabilities(lang.Final):
88
116
  elicitation: ta.Any | None = None
89
117
 
90
118
  experimental: ta.Mapping[str, ta.Any] | None = None
@@ -101,7 +129,7 @@ class ClientCapabilities:
101
129
 
102
130
  @dc.dataclass(frozen=True, kw_only=True)
103
131
  @_set_class_marshal_options
104
- class InitializeRequest(ClientRequest):
132
+ class InitializeRequest(ClientRequest['InitializeResult']):
105
133
  json_rpc_method_name: ta.ClassVar[str] = 'initialize'
106
134
 
107
135
  client_info: Implementation
@@ -143,12 +171,13 @@ class ServerCapabilities:
143
171
 
144
172
  @dc.dataclass(frozen=True, kw_only=True)
145
173
  @_set_class_marshal_options
146
- class InitializeResult(ClientResult, WithMeta):
174
+ class InitializeResult(ClientResult[InitializeRequest], WithMeta):
147
175
  json_rpc_method_name: ta.ClassVar[str] = 'initialize'
148
176
 
149
177
  server_info: Implementation
150
178
  protocol_version: str
151
179
  capabilities: ServerCapabilities
180
+ instructions: str | None = None
152
181
 
153
182
 
154
183
  @dc.dataclass(frozen=True, kw_only=True)
@@ -160,6 +189,19 @@ class InitializedNotification(Notification):
160
189
  ##
161
190
 
162
191
 
192
+ @dc.dataclass(frozen=True, kw_only=True)
193
+ class CursorClientRequest(ClientRequest[CursorClientResultT], lang.Abstract):
194
+ cursor: str | None = None
195
+
196
+
197
+ @dc.dataclass(frozen=True, kw_only=True)
198
+ class CursorClientResult(ClientResult[CursorClientRequestT], lang.Abstract):
199
+ next_cursor: str | None = None
200
+
201
+
202
+ ##
203
+
204
+
163
205
  @dc.dataclass(frozen=True, kw_only=True)
164
206
  @_set_class_marshal_options
165
207
  class ToolAnnotations(lang.Final):
@@ -172,7 +214,7 @@ class ToolAnnotations(lang.Final):
172
214
 
173
215
  @dc.dataclass(frozen=True, kw_only=True)
174
216
  @_set_class_marshal_options
175
- class Tool(lang.Final):
217
+ class Tool(WithMeta, lang.Final):
176
218
  name: str
177
219
  title: str | None = None
178
220
 
@@ -186,19 +228,16 @@ class Tool(lang.Final):
186
228
 
187
229
  @dc.dataclass(frozen=True, kw_only=True)
188
230
  @_set_class_marshal_options
189
- class ListToolsRequest(ClientRequest):
231
+ class ListToolsRequest(CursorClientRequest['ListToolsResult']):
190
232
  json_rpc_method_name: ta.ClassVar[str] = 'tools/list'
191
233
 
192
- cursor: str | None = None
193
-
194
234
 
195
235
  @dc.dataclass(frozen=True, kw_only=True)
196
236
  @_set_class_marshal_options
197
- class ListToolsResult(ClientResult, WithMeta):
237
+ class ListToolsResult(CursorClientResult[ListToolsRequest], WithMeta):
198
238
  json_rpc_method_name: ta.ClassVar[str] = 'tools/list'
199
239
 
200
240
  tools: ta.Sequence[Tool]
201
- next_cursor: str | None = None
202
241
 
203
242
 
204
243
  ##
@@ -219,7 +258,7 @@ class TextContentBlock(ContentBlock, lang.Final):
219
258
 
220
259
  @dc.dataclass(frozen=True, kw_only=True)
221
260
  @_set_class_marshal_options
222
- class CallToolRequest(ClientRequest):
261
+ class CallToolRequest(ClientRequest['CallToolResult']):
223
262
  json_rpc_method_name: ta.ClassVar[str] = 'tools/call'
224
263
 
225
264
  name: str
@@ -228,7 +267,7 @@ class CallToolRequest(ClientRequest):
228
267
 
229
268
  @dc.dataclass(frozen=True, kw_only=True)
230
269
  @_set_class_marshal_options
231
- class CallToolResult(ClientResult, WithMeta):
270
+ class CallToolResult(ClientResult[CallToolRequest], WithMeta):
232
271
  json_rpc_method_name: ta.ClassVar[str] = 'tools/call'
233
272
 
234
273
  content: ta.Sequence[ContentBlock]
@@ -241,16 +280,82 @@ class CallToolResult(ClientResult, WithMeta):
241
280
 
242
281
  @dc.dataclass(frozen=True, kw_only=True)
243
282
  @_set_class_marshal_options
244
- class PingRequest(ClientRequest, WithMeta):
283
+ class PingClientRequest(ClientRequest['PingClientResult'], WithMeta):
245
284
  json_rpc_method_name: ta.ClassVar[str] = 'ping'
246
285
 
247
286
 
248
287
  @dc.dataclass(frozen=True, kw_only=True)
249
288
  @_set_class_marshal_options
250
- class PingResult(ClientResult):
289
+ class PingClientResult(ClientResult[PingClientRequest]):
251
290
  json_rpc_method_name: ta.ClassVar[str] = 'ping'
252
291
 
253
292
 
293
+ @dc.dataclass(frozen=True, kw_only=True)
294
+ @_set_class_marshal_options
295
+ class PingServerRequest(ServerRequest['PingServerResult'], WithMeta):
296
+ json_rpc_method_name: ta.ClassVar[str] = 'ping'
297
+
298
+
299
+ @dc.dataclass(frozen=True, kw_only=True)
300
+ @_set_class_marshal_options
301
+ class PingServerResult(ServerResult[PingServerRequest]):
302
+ json_rpc_method_name: ta.ClassVar[str] = 'ping'
303
+
304
+
305
+ ##
306
+
307
+
308
+ @dc.dataclass(frozen=True, kw_only=True)
309
+ @_set_class_marshal_options
310
+ class PromptArgument(lang.Final):
311
+ name: str
312
+ title: str | None = None
313
+
314
+ description: str | None = None
315
+
316
+ required: bool | None = None
317
+
318
+
319
+ @dc.dataclass(frozen=True, kw_only=True)
320
+ @_set_class_marshal_options
321
+ class Prompt(WithMeta, lang.Final):
322
+ name: str
323
+ title: str | None = None
324
+
325
+ description: str | None = None
326
+
327
+ arguments: ta.Sequence[PromptArgument] | None = None
328
+
329
+
330
+ @dc.dataclass(frozen=True, kw_only=True)
331
+ @_set_class_marshal_options
332
+ class ListPromptsRequest(CursorClientRequest['ListPromptsResult']):
333
+ json_rpc_method_name: ta.ClassVar[str] = 'prompts/list'
334
+
335
+
336
+ @dc.dataclass(frozen=True, kw_only=True)
337
+ @_set_class_marshal_options
338
+ class ListPromptsResult(CursorClientResult[ListPromptsRequest], WithMeta):
339
+ json_rpc_method_name: ta.ClassVar[str] = 'prompts/list'
340
+
341
+ prompts: ta.Sequence[Prompt]
342
+
343
+
344
+ ##
345
+
346
+
347
+ LoggingLevel: ta.TypeAlias = ta.Literal[
348
+ 'debug',
349
+ 'info',
350
+ 'notice',
351
+ 'warning',
352
+ 'error',
353
+ 'critical',
354
+ 'alert',
355
+ 'emergency',
356
+ ]
357
+
358
+
254
359
  ##
255
360
 
256
361