ommlds 0.0.0.dev466__py3-none-any.whl → 0.0.0.dev512__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 (367) hide show
  1. ommlds/.omlish-manifests.json +404 -31
  2. ommlds/README.md +11 -0
  3. ommlds/__about__.py +21 -12
  4. ommlds/_hacks/__init__.py +4 -0
  5. ommlds/_hacks/funcs.py +110 -0
  6. ommlds/_hacks/names.py +158 -0
  7. ommlds/_hacks/params.py +73 -0
  8. ommlds/_hacks/patches.py +0 -3
  9. ommlds/backends/anthropic/protocol/__init__.py +13 -1
  10. ommlds/backends/anthropic/protocol/_dataclasses.py +1625 -0
  11. ommlds/backends/anthropic/protocol/sse/events.py +2 -0
  12. ommlds/backends/anthropic/protocol/types.py +5 -7
  13. ommlds/backends/cerebras/__init__.py +7 -0
  14. ommlds/backends/cerebras/_dataclasses.py +4254 -0
  15. ommlds/backends/cerebras/_marshal.py +24 -0
  16. ommlds/backends/cerebras/clients.py +9 -0
  17. ommlds/backends/cerebras/protocol.py +310 -0
  18. ommlds/backends/google/protocol/__init__.py +13 -0
  19. ommlds/backends/google/protocol/_dataclasses.py +5997 -0
  20. ommlds/backends/google/protocol/types.py +6 -8
  21. ommlds/backends/groq/__init__.py +7 -0
  22. ommlds/backends/groq/_dataclasses.py +3901 -0
  23. ommlds/backends/groq/_marshal.py +23 -0
  24. ommlds/backends/groq/clients.py +9 -0
  25. ommlds/backends/groq/protocol.py +247 -0
  26. ommlds/{huggingface.py → backends/huggingface/cache.py} +1 -6
  27. ommlds/backends/huggingface/cli.py +208 -0
  28. ommlds/backends/llamacpp/logging.py +4 -1
  29. ommlds/backends/mlx/caching.py +7 -3
  30. ommlds/backends/mlx/cli.py +10 -7
  31. ommlds/backends/mlx/generation.py +18 -16
  32. ommlds/backends/mlx/limits.py +10 -6
  33. ommlds/backends/mlx/loading.py +7 -4
  34. ommlds/backends/ollama/__init__.py +7 -0
  35. ommlds/backends/ollama/_dataclasses.py +3940 -0
  36. ommlds/backends/ollama/cli.py +36 -0
  37. ommlds/backends/ollama/protocol.py +201 -0
  38. ommlds/backends/openai/protocol/__init__.py +15 -1
  39. ommlds/backends/openai/protocol/_common.py +3 -5
  40. ommlds/backends/openai/protocol/_dataclasses.py +7708 -0
  41. ommlds/backends/tavily/__init__.py +7 -0
  42. ommlds/backends/tavily/_dataclasses.py +1734 -0
  43. ommlds/backends/tavily/protocol.py +299 -0
  44. ommlds/backends/tinygrad/models/llama3/__init__.py +22 -14
  45. ommlds/backends/torch/backends.py +1 -1
  46. ommlds/backends/transformers/__init__.py +14 -0
  47. ommlds/backends/transformers/filecache.py +109 -0
  48. ommlds/backends/transformers/streamers.py +73 -0
  49. ommlds/cli/__init__.py +7 -0
  50. ommlds/cli/_dataclasses.py +3835 -0
  51. ommlds/cli/asyncs.py +30 -0
  52. ommlds/cli/backends/catalog.py +88 -0
  53. ommlds/cli/backends/configs.py +9 -0
  54. ommlds/cli/backends/inject.py +100 -42
  55. ommlds/cli/{sessions/chat/backends → backends}/injection.py +1 -1
  56. ommlds/cli/backends/meta.py +82 -0
  57. ommlds/cli/{sessions/chat/backends → backends}/types.py +11 -1
  58. ommlds/cli/{sessions/chat/content → content}/messages.py +2 -2
  59. ommlds/cli/{sessions/chat/content → content}/strings.py +1 -1
  60. ommlds/cli/inject.py +17 -8
  61. ommlds/cli/inputs/asyncs.py +32 -0
  62. ommlds/cli/inputs/sync.py +75 -0
  63. ommlds/cli/main.py +346 -114
  64. ommlds/cli/rendering/configs.py +9 -0
  65. ommlds/cli/{sessions/chat/rendering → rendering}/inject.py +4 -5
  66. ommlds/cli/{sessions/chat/rendering → rendering}/markdown.py +1 -1
  67. ommlds/cli/{sessions/chat/rendering → rendering}/raw.py +1 -1
  68. ommlds/cli/{sessions/chat/rendering → rendering}/types.py +7 -1
  69. ommlds/cli/secrets.py +22 -0
  70. ommlds/cli/sessions/base.py +1 -10
  71. ommlds/cli/sessions/chat/configs.py +13 -30
  72. ommlds/cli/sessions/chat/drivers/ai/configs.py +13 -0
  73. ommlds/cli/sessions/chat/drivers/ai/events.py +57 -0
  74. ommlds/cli/sessions/chat/{chat → drivers}/ai/inject.py +15 -12
  75. ommlds/cli/sessions/chat/{chat → drivers}/ai/rendering.py +8 -8
  76. ommlds/cli/sessions/chat/{chat → drivers}/ai/services.py +5 -5
  77. ommlds/cli/sessions/chat/{chat → drivers}/ai/tools.py +4 -8
  78. ommlds/cli/sessions/chat/{chat → drivers}/ai/types.py +10 -1
  79. ommlds/cli/sessions/chat/drivers/configs.py +25 -0
  80. ommlds/cli/sessions/chat/drivers/events/inject.py +27 -0
  81. ommlds/cli/sessions/chat/drivers/events/injection.py +14 -0
  82. ommlds/cli/sessions/chat/drivers/events/manager.py +16 -0
  83. ommlds/cli/sessions/chat/drivers/events/types.py +44 -0
  84. ommlds/cli/sessions/chat/drivers/impl.py +50 -0
  85. ommlds/cli/sessions/chat/drivers/inject.py +70 -0
  86. ommlds/cli/sessions/chat/drivers/state/configs.py +13 -0
  87. ommlds/cli/sessions/chat/drivers/state/ids.py +25 -0
  88. ommlds/cli/sessions/chat/drivers/state/inject.py +83 -0
  89. ommlds/cli/sessions/chat/{chat → drivers}/state/inmemory.py +1 -6
  90. ommlds/cli/sessions/chat/{chat → drivers}/state/storage.py +18 -12
  91. ommlds/cli/sessions/chat/{chat → drivers}/state/types.py +11 -6
  92. ommlds/cli/sessions/chat/drivers/tools/configs.py +22 -0
  93. ommlds/cli/sessions/chat/drivers/tools/confirmation.py +44 -0
  94. ommlds/cli/sessions/chat/drivers/tools/errorhandling.py +39 -0
  95. ommlds/cli/sessions/chat/{tools → drivers/tools}/execution.py +3 -4
  96. ommlds/cli/sessions/chat/drivers/tools/fs/__init__.py +0 -0
  97. ommlds/cli/sessions/chat/drivers/tools/fs/configs.py +12 -0
  98. ommlds/cli/sessions/chat/drivers/tools/fs/inject.py +35 -0
  99. ommlds/cli/sessions/chat/drivers/tools/inject.py +83 -0
  100. ommlds/cli/sessions/chat/{tools → drivers/tools}/injection.py +20 -5
  101. ommlds/cli/sessions/chat/{tools → drivers/tools}/rendering.py +3 -3
  102. ommlds/cli/sessions/chat/drivers/tools/todo/__init__.py +0 -0
  103. ommlds/cli/sessions/chat/drivers/tools/todo/configs.py +12 -0
  104. ommlds/cli/sessions/chat/drivers/tools/todo/inject.py +31 -0
  105. ommlds/cli/sessions/chat/drivers/tools/weather/__init__.py +0 -0
  106. ommlds/cli/sessions/chat/drivers/tools/weather/configs.py +12 -0
  107. ommlds/cli/sessions/chat/drivers/tools/weather/inject.py +22 -0
  108. ommlds/cli/sessions/chat/{tools/weather.py → drivers/tools/weather/tools.py} +1 -1
  109. ommlds/cli/sessions/chat/drivers/types.py +31 -0
  110. ommlds/cli/sessions/chat/drivers/user/__init__.py +0 -0
  111. ommlds/cli/sessions/chat/drivers/user/configs.py +14 -0
  112. ommlds/cli/sessions/chat/drivers/user/inject.py +41 -0
  113. ommlds/cli/sessions/chat/facades/__init__.py +0 -0
  114. ommlds/cli/sessions/chat/facades/commands/__init__.py +0 -0
  115. ommlds/cli/sessions/chat/facades/commands/base.py +83 -0
  116. ommlds/cli/sessions/chat/facades/commands/configs.py +9 -0
  117. ommlds/cli/sessions/chat/facades/commands/inject.py +41 -0
  118. ommlds/cli/sessions/chat/facades/commands/injection.py +15 -0
  119. ommlds/cli/sessions/chat/facades/commands/manager.py +59 -0
  120. ommlds/cli/sessions/chat/facades/commands/simple.py +34 -0
  121. ommlds/cli/sessions/chat/facades/commands/types.py +13 -0
  122. ommlds/cli/sessions/chat/facades/configs.py +11 -0
  123. ommlds/cli/sessions/chat/facades/facade.py +26 -0
  124. ommlds/cli/sessions/chat/facades/inject.py +35 -0
  125. ommlds/cli/sessions/chat/facades/ui.py +34 -0
  126. ommlds/cli/sessions/chat/inject.py +10 -49
  127. ommlds/cli/sessions/chat/interfaces/__init__.py +0 -0
  128. ommlds/cli/sessions/chat/interfaces/bare/__init__.py +0 -0
  129. ommlds/cli/sessions/chat/interfaces/bare/configs.py +15 -0
  130. ommlds/cli/sessions/chat/interfaces/bare/inject.py +69 -0
  131. ommlds/cli/sessions/chat/interfaces/bare/interactive.py +49 -0
  132. ommlds/cli/sessions/chat/interfaces/bare/oneshot.py +21 -0
  133. ommlds/cli/sessions/chat/{tools/confirmation.py → interfaces/bare/tools.py} +3 -22
  134. ommlds/cli/sessions/chat/interfaces/base.py +13 -0
  135. ommlds/cli/sessions/chat/interfaces/configs.py +11 -0
  136. ommlds/cli/sessions/chat/interfaces/inject.py +29 -0
  137. ommlds/cli/sessions/chat/interfaces/textual/__init__.py +0 -0
  138. ommlds/cli/sessions/chat/interfaces/textual/app.py +429 -0
  139. ommlds/cli/sessions/chat/interfaces/textual/configs.py +11 -0
  140. ommlds/cli/sessions/chat/interfaces/textual/facades.py +19 -0
  141. ommlds/cli/sessions/chat/interfaces/textual/inject.py +111 -0
  142. ommlds/cli/sessions/chat/interfaces/textual/inputhistory.py +174 -0
  143. ommlds/cli/sessions/chat/interfaces/textual/interface.py +24 -0
  144. ommlds/cli/sessions/chat/interfaces/textual/styles/__init__.py +29 -0
  145. ommlds/cli/sessions/chat/interfaces/textual/styles/input.tcss +53 -0
  146. ommlds/cli/sessions/chat/interfaces/textual/styles/markdown.tcss +7 -0
  147. ommlds/cli/sessions/chat/interfaces/textual/styles/messages.tcss +167 -0
  148. ommlds/cli/sessions/chat/interfaces/textual/tools.py +38 -0
  149. ommlds/cli/sessions/chat/interfaces/textual/widgets/__init__.py +0 -0
  150. ommlds/cli/sessions/chat/interfaces/textual/widgets/input.py +70 -0
  151. ommlds/cli/sessions/chat/interfaces/textual/widgets/messages.py +207 -0
  152. ommlds/cli/sessions/chat/session.py +8 -13
  153. ommlds/cli/sessions/completion/configs.py +5 -6
  154. ommlds/cli/sessions/completion/inject.py +15 -2
  155. ommlds/cli/sessions/completion/session.py +10 -18
  156. ommlds/cli/sessions/configs.py +10 -0
  157. ommlds/cli/sessions/embedding/configs.py +5 -6
  158. ommlds/cli/sessions/embedding/inject.py +15 -2
  159. ommlds/cli/sessions/embedding/session.py +10 -18
  160. ommlds/cli/sessions/inject.py +15 -15
  161. ommlds/cli/state/storage.py +8 -2
  162. ommlds/minichain/__init__.py +217 -60
  163. ommlds/minichain/_dataclasses.py +20640 -0
  164. ommlds/minichain/_typedvalues.py +15 -8
  165. ommlds/minichain/backends/catalogs/base.py +20 -1
  166. ommlds/minichain/backends/catalogs/simple.py +2 -2
  167. ommlds/minichain/backends/catalogs/strings.py +13 -10
  168. ommlds/minichain/backends/impls/anthropic/chat.py +28 -5
  169. ommlds/minichain/backends/impls/anthropic/names.py +3 -3
  170. ommlds/minichain/backends/impls/anthropic/protocol.py +2 -2
  171. ommlds/minichain/backends/impls/anthropic/stream.py +23 -18
  172. ommlds/minichain/backends/impls/cerebras/__init__.py +0 -0
  173. ommlds/minichain/backends/impls/cerebras/chat.py +82 -0
  174. ommlds/minichain/backends/impls/cerebras/names.py +45 -0
  175. ommlds/minichain/backends/impls/cerebras/protocol.py +143 -0
  176. ommlds/minichain/backends/impls/cerebras/stream.py +114 -0
  177. ommlds/minichain/backends/impls/duckduckgo/search.py +5 -1
  178. ommlds/minichain/backends/impls/dummy/__init__.py +0 -0
  179. ommlds/minichain/backends/impls/dummy/chat.py +69 -0
  180. ommlds/minichain/backends/impls/google/chat.py +20 -84
  181. ommlds/minichain/backends/impls/google/names.py +6 -0
  182. ommlds/minichain/backends/impls/google/protocol.py +105 -0
  183. ommlds/minichain/backends/impls/google/search.py +10 -5
  184. ommlds/minichain/backends/impls/google/stream.py +64 -142
  185. ommlds/minichain/backends/impls/google/tools.py +2 -2
  186. ommlds/minichain/backends/impls/groq/__init__.py +0 -0
  187. ommlds/minichain/backends/impls/groq/chat.py +77 -0
  188. ommlds/minichain/backends/impls/groq/names.py +48 -0
  189. ommlds/minichain/backends/impls/groq/protocol.py +143 -0
  190. ommlds/minichain/backends/impls/groq/stream.py +114 -0
  191. ommlds/minichain/backends/impls/huggingface/repos.py +1 -5
  192. ommlds/minichain/backends/impls/llamacpp/chat.py +15 -3
  193. ommlds/minichain/backends/impls/llamacpp/completion.py +7 -3
  194. ommlds/minichain/backends/impls/llamacpp/stream.py +38 -19
  195. ommlds/minichain/backends/impls/mistral.py +9 -2
  196. ommlds/minichain/backends/impls/mlx/chat.py +100 -23
  197. ommlds/minichain/backends/impls/ollama/__init__.py +0 -0
  198. ommlds/minichain/backends/impls/ollama/chat.py +193 -0
  199. ommlds/minichain/backends/impls/ollama/protocol.py +144 -0
  200. ommlds/minichain/backends/impls/openai/chat.py +14 -7
  201. ommlds/minichain/backends/impls/openai/completion.py +9 -2
  202. ommlds/minichain/backends/impls/openai/embedding.py +9 -2
  203. ommlds/minichain/backends/impls/openai/format.py +117 -115
  204. ommlds/minichain/backends/impls/openai/names.py +33 -5
  205. ommlds/minichain/backends/impls/openai/stream.py +61 -70
  206. ommlds/minichain/backends/impls/sentencepiece/tokens.py +9 -6
  207. ommlds/minichain/backends/impls/tavily.py +66 -0
  208. ommlds/minichain/backends/impls/tinygrad/chat.py +17 -14
  209. ommlds/minichain/backends/impls/tokenizers/tokens.py +9 -6
  210. ommlds/minichain/backends/impls/transformers/sentence.py +5 -2
  211. ommlds/minichain/backends/impls/transformers/tokens.py +9 -6
  212. ommlds/minichain/backends/impls/transformers/transformers.py +139 -20
  213. ommlds/minichain/backends/strings/parsing.py +2 -2
  214. ommlds/minichain/backends/strings/resolving.py +7 -2
  215. ommlds/minichain/chat/choices/stream/__init__.py +0 -0
  216. ommlds/minichain/chat/{stream → choices/stream}/adapters.py +7 -7
  217. ommlds/minichain/chat/choices/stream/joining.py +31 -0
  218. ommlds/minichain/chat/choices/stream/services.py +45 -0
  219. ommlds/minichain/chat/choices/stream/types.py +43 -0
  220. ommlds/minichain/chat/content.py +42 -0
  221. ommlds/minichain/chat/messages.py +46 -42
  222. ommlds/minichain/chat/stream/_marshal.py +4 -4
  223. ommlds/minichain/chat/stream/joining.py +56 -43
  224. ommlds/minichain/chat/stream/services.py +15 -15
  225. ommlds/minichain/chat/stream/types.py +17 -24
  226. ommlds/minichain/chat/templating.py +3 -3
  227. ommlds/minichain/content/__init__.py +20 -3
  228. ommlds/minichain/content/_marshal.py +181 -55
  229. ommlds/minichain/content/code.py +26 -0
  230. ommlds/minichain/content/composite.py +28 -0
  231. ommlds/minichain/content/content.py +27 -0
  232. ommlds/minichain/content/dynamic.py +12 -0
  233. ommlds/minichain/content/emphasis.py +27 -0
  234. ommlds/minichain/content/images.py +2 -2
  235. ommlds/minichain/content/json.py +2 -2
  236. ommlds/minichain/content/link.py +13 -0
  237. ommlds/minichain/content/markdown.py +12 -0
  238. ommlds/minichain/content/metadata.py +10 -0
  239. ommlds/minichain/content/namespaces.py +8 -0
  240. ommlds/minichain/content/placeholders.py +10 -9
  241. ommlds/minichain/content/quote.py +26 -0
  242. ommlds/minichain/content/raw.py +49 -0
  243. ommlds/minichain/content/recursive.py +12 -0
  244. ommlds/minichain/content/resources.py +22 -0
  245. ommlds/minichain/content/section.py +26 -0
  246. ommlds/minichain/content/sequence.py +17 -3
  247. ommlds/minichain/content/standard.py +32 -0
  248. ommlds/minichain/content/tag.py +28 -0
  249. ommlds/minichain/content/templates.py +13 -0
  250. ommlds/minichain/content/text.py +2 -2
  251. ommlds/minichain/content/transform/__init__.py +0 -0
  252. ommlds/minichain/content/transform/json.py +55 -0
  253. ommlds/minichain/content/transform/markdown.py +8 -0
  254. ommlds/minichain/content/transform/materialize.py +59 -0
  255. ommlds/minichain/content/transform/metadata.py +16 -0
  256. ommlds/minichain/content/transform/namespaces.py +20 -0
  257. ommlds/minichain/content/transform/placeholders.py +60 -0
  258. ommlds/minichain/content/{prepare.py → transform/prepare.py} +10 -15
  259. ommlds/minichain/content/transform/recursive.py +54 -0
  260. ommlds/minichain/content/transform/resources.py +58 -0
  261. ommlds/minichain/content/transform/standard.py +43 -0
  262. ommlds/minichain/content/{transforms → transform}/stringify.py +1 -7
  263. ommlds/minichain/content/transform/strings.py +33 -0
  264. ommlds/minichain/content/transform/templates.py +25 -0
  265. ommlds/minichain/content/transform/types.py +18 -0
  266. ommlds/minichain/content/transform/visitors.py +38 -0
  267. ommlds/minichain/content/visitors.py +218 -0
  268. ommlds/minichain/http/__init__.py +0 -0
  269. ommlds/minichain/http/stream.py +195 -0
  270. ommlds/minichain/lib/fs/tools/read.py +1 -1
  271. ommlds/minichain/lib/fs/tools/recursivels/rendering.py +1 -1
  272. ommlds/minichain/lib/fs/tools/recursivels/running.py +1 -1
  273. ommlds/minichain/lib/todo/tools/write.py +2 -1
  274. ommlds/minichain/lib/todo/types.py +1 -1
  275. ommlds/minichain/llms/types.py +4 -0
  276. ommlds/minichain/metadata.py +56 -2
  277. ommlds/minichain/models/configs.py +2 -2
  278. ommlds/minichain/models/names.py +2 -0
  279. ommlds/minichain/registries/globals.py +18 -4
  280. ommlds/minichain/resources.py +49 -3
  281. ommlds/minichain/search.py +1 -1
  282. ommlds/minichain/services/README.md +154 -0
  283. ommlds/minichain/services/__init__.py +6 -2
  284. ommlds/minichain/services/_marshal.py +46 -10
  285. ommlds/minichain/services/_origclasses.py +11 -0
  286. ommlds/minichain/services/_typedvalues.py +8 -3
  287. ommlds/minichain/services/requests.py +73 -3
  288. ommlds/minichain/services/responses.py +73 -3
  289. ommlds/minichain/services/services.py +9 -0
  290. ommlds/minichain/standard.py +8 -0
  291. ommlds/minichain/stream/services.py +43 -17
  292. ommlds/minichain/text/applypatch.py +2 -1
  293. ommlds/minichain/text/toolparsing/llamacpp/types.py +1 -1
  294. ommlds/minichain/tokens/specials.py +1 -1
  295. ommlds/minichain/tools/execution/catalog.py +2 -2
  296. ommlds/minichain/tools/execution/errorhandling.py +36 -0
  297. ommlds/minichain/tools/execution/errors.py +2 -2
  298. ommlds/minichain/tools/execution/executors.py +1 -1
  299. ommlds/minichain/tools/fns.py +1 -1
  300. ommlds/minichain/tools/jsonschema.py +2 -2
  301. ommlds/minichain/tools/reflect.py +11 -7
  302. ommlds/minichain/tools/types.py +16 -19
  303. ommlds/minichain/vectors/_marshal.py +1 -1
  304. ommlds/minichain/vectors/embeddings.py +1 -1
  305. ommlds/minichain/wrappers/__init__.py +7 -0
  306. ommlds/minichain/wrappers/firstinwins.py +144 -0
  307. ommlds/minichain/wrappers/instrument.py +146 -0
  308. ommlds/minichain/wrappers/retry.py +168 -0
  309. ommlds/minichain/wrappers/services.py +98 -0
  310. ommlds/minichain/wrappers/stream.py +57 -0
  311. ommlds/nanochat/LICENSE +21 -0
  312. ommlds/nanochat/__init__.py +0 -0
  313. ommlds/nanochat/rustbpe/LICENSE +21 -0
  314. ommlds/nanochat/rustbpe/README.md +10 -0
  315. ommlds/nanochat/tokenizers.py +440 -0
  316. ommlds/specs/__init__.py +0 -0
  317. ommlds/specs/mcp/__init__.py +0 -0
  318. ommlds/specs/mcp/_marshal.py +23 -0
  319. ommlds/specs/mcp/clients.py +146 -0
  320. ommlds/specs/mcp/protocol.py +369 -0
  321. ommlds/tools/git.py +84 -64
  322. ommlds/tools/ocr.py +1 -1
  323. ommlds/wiki/analyze.py +2 -2
  324. ommlds/wiki/models.py +4 -4
  325. ommlds/wiki/text/mfh.py +9 -9
  326. ommlds/wiki/utils/xml.py +5 -5
  327. {ommlds-0.0.0.dev466.dist-info → ommlds-0.0.0.dev512.dist-info}/METADATA +28 -21
  328. ommlds-0.0.0.dev512.dist-info/RECORD +534 -0
  329. {ommlds-0.0.0.dev466.dist-info → ommlds-0.0.0.dev512.dist-info}/WHEEL +1 -1
  330. ommlds/cli/backends/standard.py +0 -20
  331. ommlds/cli/sessions/chat/backends/catalog.py +0 -56
  332. ommlds/cli/sessions/chat/backends/inject.py +0 -37
  333. ommlds/cli/sessions/chat/chat/state/inject.py +0 -40
  334. ommlds/cli/sessions/chat/chat/user/inject.py +0 -61
  335. ommlds/cli/sessions/chat/chat/user/interactive.py +0 -29
  336. ommlds/cli/sessions/chat/chat/user/oneshot.py +0 -25
  337. ommlds/cli/sessions/chat/chat/user/types.py +0 -15
  338. ommlds/cli/sessions/chat/driver.py +0 -43
  339. ommlds/cli/sessions/chat/tools/inject.py +0 -145
  340. ommlds/minichain/backends/impls/openai/format2.py +0 -210
  341. ommlds/minichain/content/materialize.py +0 -196
  342. ommlds/minichain/content/simple.py +0 -47
  343. ommlds/minichain/content/transforms/base.py +0 -46
  344. ommlds/minichain/content/transforms/interleave.py +0 -70
  345. ommlds/minichain/content/transforms/squeeze.py +0 -72
  346. ommlds/minichain/content/transforms/strings.py +0 -24
  347. ommlds/minichain/content/types.py +0 -43
  348. ommlds/minichain/stream/wrap.py +0 -62
  349. ommlds-0.0.0.dev466.dist-info/RECORD +0 -376
  350. /ommlds/{cli/sessions/chat/backends → backends/huggingface}/__init__.py +0 -0
  351. /ommlds/cli/{sessions/chat/chat → content}/__init__.py +0 -0
  352. /ommlds/cli/{sessions/chat/chat/ai → inputs}/__init__.py +0 -0
  353. /ommlds/cli/{sessions/chat/chat/state → rendering}/__init__.py +0 -0
  354. /ommlds/cli/sessions/chat/{chat/user → drivers}/__init__.py +0 -0
  355. /ommlds/cli/sessions/chat/{content → drivers/ai}/__init__.py +0 -0
  356. /ommlds/cli/sessions/chat/{chat → drivers}/ai/injection.py +0 -0
  357. /ommlds/cli/sessions/chat/{phases → drivers/events}/__init__.py +0 -0
  358. /ommlds/cli/sessions/chat/{rendering → drivers/phases}/__init__.py +0 -0
  359. /ommlds/cli/sessions/chat/{phases → drivers/phases}/inject.py +0 -0
  360. /ommlds/cli/sessions/chat/{phases → drivers/phases}/injection.py +0 -0
  361. /ommlds/cli/sessions/chat/{phases → drivers/phases}/manager.py +0 -0
  362. /ommlds/cli/sessions/chat/{phases → drivers/phases}/types.py +0 -0
  363. /ommlds/cli/sessions/chat/{tools → drivers/state}/__init__.py +0 -0
  364. /ommlds/{minichain/content/transforms → cli/sessions/chat/drivers/tools}/__init__.py +0 -0
  365. {ommlds-0.0.0.dev466.dist-info → ommlds-0.0.0.dev512.dist-info}/entry_points.txt +0 -0
  366. {ommlds-0.0.0.dev466.dist-info → ommlds-0.0.0.dev512.dist-info}/licenses/LICENSE +0 -0
  367. {ommlds-0.0.0.dev466.dist-info → ommlds-0.0.0.dev512.dist-info}/top_level.txt +0 -0
@@ -37,5 +37,5 @@ class ModelRepo(ModelSpecifier):
37
37
 
38
38
  _: dc.KW_ONLY
39
39
 
40
- tag: str | None = dc.xfield(None, repr_fn=dc.opt_repr)
41
- path: str | None = dc.xfield(None, repr_fn=dc.opt_repr)
40
+ tag: str | None = dc.xfield(None, repr_fn=lang.opt_repr)
41
+ path: str | None = dc.xfield(None, repr_fn=lang.opt_repr)
@@ -6,6 +6,7 @@ import typing as ta
6
6
 
7
7
  from omlish import cached
8
8
  from omlish import dataclasses as dc
9
+ from omlish import lang
9
10
  from omlish.algorithm.toposort import mut_toposort
10
11
 
11
12
 
@@ -13,6 +14,7 @@ from omlish.algorithm.toposort import mut_toposort
13
14
 
14
15
 
15
16
  @dc.dataclass(frozen=True, kw_only=True)
17
+ @dc.extra_class_params(default_repr_fn=lang.opt_repr)
16
18
  class ModelNameCollection:
17
19
  default: str | None = None
18
20
 
@@ -98,20 +98,34 @@ def register_type(
98
98
 
99
99
 
100
100
  @ta.overload
101
- def registry_new(cls: type[T], name: str, *args: ta.Any, **kwargs: ta.Any) -> T:
101
+ def get_registry_cls(cls: type[T], name: str) -> type[T]:
102
102
  ...
103
103
 
104
104
 
105
105
  @ta.overload
106
- def registry_new(cls: ta.Any, name: str, *args: ta.Any, **kwargs: ta.Any) -> ta.Any:
106
+ def get_registry_cls(cls: ta.Any, name: str) -> ta.Any:
107
107
  ...
108
108
 
109
109
 
110
- def registry_new(cls, name, *args, **kwargs):
110
+ def get_registry_cls(cls, name, *args, **kwargs):
111
111
  be_cls = _GlobalRegistry.instance().get_registry_cls(cls, name)
112
112
  if isinstance(cls, type):
113
113
  be_cls = check.issubclass(be_cls, cls) # noqa
114
- return be_cls(*args, **kwargs)
114
+ return be_cls
115
+
116
+
117
+ @ta.overload
118
+ def registry_new(cls: type[T], name: str, *args: ta.Any, **kwargs: ta.Any) -> T:
119
+ ...
120
+
121
+
122
+ @ta.overload
123
+ def registry_new(cls: ta.Any, name: str, *args: ta.Any, **kwargs: ta.Any) -> ta.Any:
124
+ ...
125
+
126
+
127
+ def registry_new(cls, name, *args, **kwargs):
128
+ return get_registry_cls(cls, name)(*args, **kwargs)
115
129
 
116
130
 
117
131
  #
@@ -31,7 +31,12 @@ class ResourcesRefNotRegisteredError(Exception):
31
31
  pass
32
32
 
33
33
 
34
+ @ta.final
34
35
  class Resources(lang.Final, lang.NotPicklable):
36
+ """
37
+ Essentially a reference-tracked AsyncContextManager.
38
+ """
39
+
35
40
  def __init__(
36
41
  self,
37
42
  *,
@@ -79,10 +84,14 @@ class Resources(lang.Final, lang.NotPicklable):
79
84
  @contextlib.asynccontextmanager
80
85
  async def inner():
81
86
  init_ref = Resources._InitRef()
87
+
82
88
  res = Resources(init_ref=init_ref, **kwargs)
89
+
83
90
  await res.init()
91
+
84
92
  try:
85
93
  yield res
94
+
86
95
  finally:
87
96
  await res.remove_ref(init_ref)
88
97
 
@@ -93,6 +102,7 @@ class Resources(lang.Final, lang.NotPicklable):
93
102
  def add_ref(self, ref: ResourcesRef) -> None:
94
103
  check.isinstance(ref, ResourcesRef)
95
104
  check.state(not self._closed)
105
+
96
106
  self._refs.add(ref)
97
107
 
98
108
  def has_ref(self, ref: ResourcesRef) -> bool:
@@ -100,10 +110,13 @@ class Resources(lang.Final, lang.NotPicklable):
100
110
 
101
111
  async def remove_ref(self, ref: ResourcesRef) -> None:
102
112
  check.isinstance(ref, ResourcesRef)
113
+
103
114
  try:
104
115
  self._refs.remove(ref)
116
+
105
117
  except KeyError:
106
118
  raise ResourcesRefNotRegisteredError(ref) from None
119
+
107
120
  if not self._no_autoclose and not self._refs:
108
121
  await self.aclose()
109
122
 
@@ -111,10 +124,12 @@ class Resources(lang.Final, lang.NotPicklable):
111
124
 
112
125
  def enter_context(self, cm: ta.ContextManager[T]) -> T:
113
126
  check.state(not self._closed)
127
+
114
128
  return self._aes.enter_context(cm)
115
129
 
116
130
  async def enter_async_context(self, cm: ta.AsyncContextManager[T]) -> T:
117
131
  check.state(not self._closed)
132
+
118
133
  return await self._aes.enter_async_context(cm)
119
134
 
120
135
  #
@@ -145,24 +160,54 @@ class Resources(lang.Final, lang.NotPicklable):
145
160
  ##
146
161
 
147
162
 
163
+ @ta.final
148
164
  class ResourceManaged(ResourcesRef, lang.Final, lang.NotPicklable, ta.Generic[T]):
165
+ """
166
+ A class to 'handoff' a ref to a `Resources`, allowing the `Resources` to temporarily survive being passed from
167
+ instantiation within a callee.
168
+
169
+ This class wraps an arbitrary value, likely an object referencing resources managed by the `Resources`, which is
170
+ accessed by `__aenter__`'ing. However, as the point of this class is handoff of a `Resources`, not necessarily some
171
+ arbitrary value, the value needn't necessarily be related to the `Resources`, or may even be `None`.
172
+
173
+ The ref to the `Resources` is allocated in the ctor, so the contract is that an instance of this must be immediately
174
+ `__aenter__`'d before doing anything else with the return value of the call. Failure to do so leaks the `Resources`.
175
+ """
176
+
149
177
  def __init__(self, v: T, resources: Resources) -> None:
150
178
  super().__init__()
151
179
 
152
- self._v = v
180
+ self.__v = v
153
181
  self.__resources = resources
154
182
 
155
183
  resources.add_ref(self)
156
184
 
185
+ __state: ta.Literal['new', 'entered', 'exited'] = 'new'
186
+
157
187
  def __repr__(self) -> str:
158
- return f'{self.__class__.__name__}<{self._v!r}>'
188
+ return f'{self.__class__.__name__}<{self.__v!r}, {self.__state}>'
159
189
 
160
190
  async def __aenter__(self) -> T:
161
- return self._v
191
+ check.state(self.__state == 'new')
192
+ self.__state = 'entered'
193
+
194
+ return self.__v
162
195
 
163
196
  async def __aexit__(self, exc_type, exc_val, exc_tb):
197
+ check.state(self.__state == 'entered')
198
+ self.__state = 'exited'
199
+
164
200
  await self.__resources.remove_ref(self)
165
201
 
202
+ def __del__(self) -> None:
203
+ if self.__state != 'exited':
204
+ log.error(
205
+ f'{__package__}.{self.__class__.__name__}.__del__: ' # noqa
206
+ f'%r deleted without being entered and exited! '
207
+ f'resources: %s',
208
+ repr(self),
209
+ repr(self.__resources),
210
+ )
166
211
 
167
212
  ##
168
213
 
@@ -178,6 +223,7 @@ class UseResources(tv.UniqueScalarTypedValue[Resources], ResourcesOption, lang.F
178
223
  if (ur := tv.as_collection(options).get(UseResources)) is not None:
179
224
  async with ResourceManaged(ur.v, ur.v) as rs:
180
225
  yield rs
226
+
181
227
  else:
182
228
  async with Resources.new() as rs:
183
229
  yield rs
@@ -19,7 +19,7 @@ class SearchHit(lang.Final):
19
19
  title: str | None
20
20
  url: str | None
21
21
  description: str | None = None
22
- snippets: lang.SequenceNotStr[str] | None
22
+ snippets: lang.SequenceNotStr[str] | None = None
23
23
 
24
24
 
25
25
  @dc.dataclass(frozen=True, kw_only=True)
@@ -0,0 +1,154 @@
1
+ The core service abstraction. In general, Services are intended to encapsulate non-trivial, resourceful, effectful
2
+ operations, which are likely to have various implementations, each having their own specific capabilities in addition
3
+ to their common interface, and where wrapping, adapting, and transforming them in a uniform manner is desirable.
4
+
5
+ For example:
6
+ - A ChatService is passed a Request with a Chat value and returns a Response with an AiChat value.
7
+ - A ChatChoicesService is passed a Request with a Chat and returns a Response with a list of AiChoices.
8
+ - A ChatChoicesServiceChatService is a simple adapter taking a ChatChoicesService which it will invoke with its given
9
+ Request, expect a single AiChoice value in that Response, and return that single AiChat as its Response - thus acting
10
+ as a ChatService.
11
+ - Thus, all chat backends that return choices can be adapted to code expecting a single chat as output.
12
+ - A ChatStreamService is passed a Request with a Chat value and returns a Response with a value from which AiDeltas
13
+ may be streamed.
14
+ - A ChatChoicesStreamService is passed a Request with a Chat value and returns a Response with a value from which
15
+ AiChoicesDeltas may be streamed.
16
+ - A ChatChoicesStreamServiceChatChoicesService is an adapter taking a ChatChoicesStreamService and aggregating the
17
+ AiChoicesDeltas into joined, non-delta AiChoices.
18
+ - This may then be wrapped in a ChatChoicesServiceChatService to act as a ChatService.
19
+ - In practice however there are usually dedicated streaming and non-streaming implementations if possible as
20
+ non-streaming will usually have less overhead.
21
+ - An OpenaiChatChoicesService can act as a ChatChoicesService, and will accept all generic ChatOptions, in addition to
22
+ any OpenaiChatOptions inapplicable to any other backend. It may also produce all generic ChatOutputs, in addition to
23
+ OpenaiChatOutputs that will not be produced by other backends.
24
+ - Beyond chat, a VectorSearchService is passed a Request with a VectorSearch value and returns a Response with a
25
+ VectorHits value.
26
+ - A RetryService wraps any other Service and will attempt to re-invoke it on failure.
27
+ - A FirstInWinsService wraps any number of other Services and will return the first non-error Response it receives.
28
+
29
+ The service abstraction consists of 3 interrelated generic types:
30
+ - Request, an immutable final generic class containing a single value and any number of options.
31
+ - Response, an immutable final generic class containing a single value and any number of outputs.
32
+ - Service, a generic protocol consisting of a single async method `invoke`, taking a request and returning a response.
33
+
34
+ There are 2 related abstract base classes in the parent package:
35
+ - Option, a non-generic abstract class representing a service option.
36
+ - Output, a non-generic abstract class representing a service output.
37
+
38
+ The purpose of this arrangement is to provide the following:
39
+ - There is only one method - `Service.invoke` - to deal with.
40
+ - There is no base `Service` class - service types are distinguished only by the requests they accept and responses
41
+ they return.
42
+ - It facilitates a clear, introspectable, generally type-safe means for handling 'less-specific' and 'more-specific'
43
+ service types.
44
+ - It facilitates generic wrapper and transformation machinery.
45
+
46
+ The variance of the type parameters of the 3 classes is central:
47
+ - `Request[V_co, OptionT_co]`
48
+ - `Response[V_co, OutputT_contra]`
49
+ - `Service[RequestT_contra, ResponseT_co]`
50
+
51
+ And to understand this, it's important to understand how Option and Output subtypes are intended to be arranged:
52
+ - These types are *not* intended to form a deep type hierarchy:
53
+ - A RemoteChatOption is *not* intended to inherit from a ChatOption: a ChatOption (be it a base class or union alias)
54
+ represents an option that *any* ChatService can accept, whereas a RemoteChatOption represents an option that *only*
55
+ applies to a RemoteChatService.
56
+ - If RemoteChatOption inherited from a base ChatOption, then it would have to apply to *all* ChatService
57
+ implementations.
58
+ - For example: were ApiKey to inherit from ChatOption, then it would have to apply to all ChatServices, including
59
+ LocalChatService, which has no concept of an api key.
60
+ - Similarly, a RemoteChatOutput is *not* intended to inherit from a ChatOutput: a ChatOutput represents an output that
61
+ *any* ChatService can produce, whereas a RemoteChatOutput represents an output that *only* applies to a
62
+ RemoteChatService.
63
+ - These 2 types are intended to form flat, disjoint, unrelated families of subtypes, and Request and Response are
64
+ intended to be parameterized by the unions of all such families they may contain.
65
+ - Because of this, one's visual intuition regarding types and subtypes may be reversed: `int` is effectively a subtype
66
+ of `int | str` despite `int` being a visually shorter, less complex type.
67
+ - `int` is a *MORE SPECIFIC* / *STRICT SUBSET* subtype of `int | str`, the *LESS SPECIFIC* / *STRICT SUPERSET*
68
+ supertype.
69
+
70
+ Regarding type variance:
71
+ - Service has the classic setup of contravariant input and covariant output:
72
+ - A RemoteChatService *is a* ChatService.
73
+ - A RemoteChatService may accept less specific requests than a ChatService.
74
+ - A RemoteChatService may return more specific responses than a ChatService.
75
+ - Request is covariant on its options:
76
+ - Recall, a RemoteChatOption *is not a* ChatOption.
77
+ - A ChatRequest *is a* RemoteChatRequest as it will not contain options RemoteChatService cannot accept.
78
+ - Response is contravariant on its outputs:
79
+ - Recall, a RemoteChatOutput *is not a* ChatOutput.
80
+ - A RemoteChatResponse *is a* ChatResponse even though it may contain additional output variants not produced by
81
+ every ChatService.
82
+ - Code that calls a ChatService and is given a ChatResponse must be prepared to handle (usually by simply ignoring)
83
+ outputs not necessarily produced by a base ChatService.
84
+
85
+ Finally, in addition to a value and either options or outputs, a Request and Response each also contain a collection of
86
+ metadata. Very much unlike the Options and Outputs, the elements of these collections are simply of the types
87
+ `RequestMetadata | CommonMetadata` and `ResponseMetadata | CommonMetadata`, and are not otherwise parameterized. These
88
+ are intended for looser inputs and outputs: a generic unique id, timestamps, metrics, etc., and in general should
89
+ neither affect the behavior of services nor be depended upon by callers.
90
+
91
+ Below is a representative illustration of these types and their relationships. Note how:
92
+ - There is no subclassing of Request, Response, or Service - just type aliasing.
93
+ - There is no deep, shared subclassing of Option or Output.
94
+ - The type args passed to Request and Response are unions of all the Option and Output subtypes they may contain.
95
+ - These unions are kept in pluralized type aliases for convenience.
96
+ - There is no base ChatOption or ChatOutput class - were there, it would not be included in the base classes of any
97
+ local or remote only option or output.
98
+ - The local and remote sections take different but equivalent approaches:
99
+ - There are no base LocalChatOption or LocalChatOutput classes, but there *are* base RemoteChatOption and
100
+ RemoteChatOutput classes.
101
+ - Without any common base classes (besides the lowest level Output and Option classes), the local section treats them
102
+ as each distinct and bespoke, and the pluralized LocalChatOptions and LocalChatOutputs type aliases aggregate them
103
+ by explicitly listing them.
104
+ - With the common RemoteChatOption and RemoteChatOutput base classes, the remote section treats them as a related
105
+ family that any 'RemoteChat'-like service should accept and produce.
106
+
107
+ ```python
108
+ # Common chat
109
+
110
+ class MaxTokens(Option, tv.UniqueScalarTypedValue[int]): pass
111
+ class Temperature(Option, tv.UniqueScalarTypedValue[float]): pass
112
+
113
+ ChatOptions: ta.TypeAlias = MaxTokens | Temperature
114
+ ChatRequest: ta.TypeAlias = Request[Chat, ChatOptions]
115
+
116
+ class TokenUsage(Output, tv.UniqueScalarTypedValue[int]): pass
117
+ class ElapsedTime(Output, tv.UniqueScalarTypedValue[float]): pass
118
+
119
+ ChatOutputs: ta.TypeAlias = TokenUsage | ElapsedTime
120
+ ChatResponse: ta.TypeAlias = Response[Message, ChatOutputs]
121
+
122
+ ChatService: ta.TypeAlias = Service[ChatRequest, ChatResponse]
123
+
124
+ # Local chat
125
+
126
+ class ModelPath(Option, tv.ScalarTypedValue[str]): pass
127
+
128
+ LocalChatOptions: ta.TypeAlias = ChatOptions | ModelPath
129
+ LocalChatRequest: ta.TypeAlias = Request[Chat, LocalChatOptions]
130
+
131
+ class LogPath(Output, tv.ScalarTypedValue[str]): pass
132
+
133
+ LocalChatOutputs: ta.TypeAlias = ChatOutputs | LogPath
134
+ LocalChatResponse: ta.TypeAlias = Response[Message, LocalChatOutputs]
135
+
136
+ LocalChatService: ta.TypeAlias = Service[LocalChatRequest, LocalChatResponse]
137
+
138
+ # Remote chat
139
+
140
+ class RemoteChatOption(Option, lang.Abstract): pass
141
+ class ApiKey(RemoteChatOption, tv.ScalarTypedValue[str]): pass
142
+
143
+ RemoteChatOptions: ta.TypeAlias = ChatOptions | RemoteChatOption
144
+ RemoteChatRequest: ta.TypeAlias = Request[Chat, RemoteChatOptions]
145
+
146
+ class RemoteChatOutput(Output, lang.Abstract): pass
147
+ class BilledCostInUsd(RemoteChatOutput, tv.UniqueScalarTypedValue[float]): pass
148
+
149
+ RemoteChatOutputs: ta.TypeAlias = ChatOutputs | RemoteChatOutput
150
+ RemoteChatResponse: ta.TypeAlias = Response[Message, RemoteChatOutputs]
151
+
152
+ RemoteChatService: ta.TypeAlias = Service[RemoteChatRequest, RemoteChatResponse]
153
+ ```
154
+
@@ -1,6 +1,4 @@
1
1
  # ruff: noqa: I001
2
-
3
-
4
2
  from .facades import ( # noqa
5
3
  ServiceFacade,
6
4
 
@@ -8,10 +6,16 @@ from .facades import ( # noqa
8
6
  )
9
7
 
10
8
  from .requests import ( # noqa
9
+ RequestMetadata,
10
+ RequestMetadatas,
11
+
11
12
  Request,
12
13
  )
13
14
 
14
15
  from .responses import ( # noqa
16
+ ResponseMetadata,
17
+ ResponseMetadatas,
18
+
15
19
  Response,
16
20
  )
17
21
 
@@ -1,3 +1,8 @@
1
+ """
2
+ FIXME:
3
+ - everything lol
4
+ - can this just do what metadata does
5
+ """
1
6
  import typing as ta
2
7
 
3
8
  from omlish import check
@@ -22,10 +27,21 @@ def _is_rr_rty(rty: rfl.Type) -> bool:
22
27
  )
23
28
 
24
29
 
25
- def _get_tv_fld(rty: rfl.Type) -> dc.Field:
30
+ class _RrFlds(ta.NamedTuple):
31
+ v: dc.Field
32
+ tv: dc.Field
33
+ md: dc.Field
34
+
35
+
36
+ def _get_rr_flds(rty: rfl.Type) -> _RrFlds:
26
37
  flds = col.make_map_by(lambda f: f.name, dc.fields(check.not_none(rfl.get_concrete_type(rty))), strict=True)
27
- flds.pop('v')
28
- return check.single(flds.values())
38
+ v_fld = flds.pop('v')
39
+ md_fld = flds.pop('_metadata')
40
+ return _RrFlds(
41
+ v=v_fld,
42
+ tv=check.single(flds.values()),
43
+ md=md_fld,
44
+ )
29
45
 
30
46
 
31
47
  ##
@@ -34,7 +50,7 @@ def _get_tv_fld(rty: rfl.Type) -> dc.Field:
34
50
  @dc.dataclass(frozen=True)
35
51
  class _RequestResponseMarshaler(msh.Marshaler):
36
52
  rty: rfl.Type
37
- tv_fld: dc.Field
53
+ rr_flds: _RrFlds
38
54
  v_m: msh.Marshaler | None
39
55
 
40
56
  def marshal(self, ctx: msh.MarshalContext, o: ta.Any) -> msh.Value:
@@ -51,9 +67,14 @@ class _RequestResponseMarshaler(msh.Marshaler):
51
67
  else:
52
68
  v_v = self.v_m.marshal(ctx, o.v)
53
69
 
70
+ md_fmd = self.rr_flds.md.metadata[msh.FieldOptions]
71
+ md_m = md_fmd.marshaler_factory.make_marshaler(ctx.marshal_factory_context, self.rr_flds.md.type)() # FIXME
72
+ md_v = md_m.marshal(ctx, o._metadata) # noqa
73
+
54
74
  return {
55
75
  'v': v_v,
56
- **({lang.strip_prefix(self.tv_fld.name, '_'): tv_v} if tv_v else {}),
76
+ **({lang.strip_prefix(self.rr_flds.tv.name, '_'): tv_v} if tv_v else {}),
77
+ **({'metadata': md_v} if md_v else {}),
57
78
  }
58
79
 
59
80
 
@@ -76,7 +97,7 @@ class _RequestResponseMarshalerFactory(msh.MarshalerFactory):
76
97
  v_m = ctx.make_marshaler(v_rty)
77
98
  return _RequestResponseMarshaler(
78
99
  rty,
79
- _get_tv_fld(rty),
100
+ _get_rr_flds(rty),
80
101
  v_m,
81
102
  )
82
103
 
@@ -89,9 +110,10 @@ class _RequestResponseMarshalerFactory(msh.MarshalerFactory):
89
110
  @dc.dataclass(frozen=True)
90
111
  class _RequestResponseUnmarshaler(msh.Unmarshaler):
91
112
  rty: rfl.Type
92
- tv_fld: dc.Field
113
+ rr_flds: _RrFlds
93
114
  v_u: msh.Unmarshaler
94
115
  tv_u: msh.Unmarshaler
116
+ md_u: msh.Unmarshaler
95
117
 
96
118
  def unmarshal(self, ctx: msh.UnmarshalContext, v: msh.Value) -> ta.Any:
97
119
  dct = dict(check.isinstance(v, ta.Mapping))
@@ -99,9 +121,14 @@ class _RequestResponseUnmarshaler(msh.Unmarshaler):
99
121
  v_v = dct.pop('v')
100
122
  v = self.v_u.unmarshal(ctx, v_v)
101
123
 
124
+ if md_v := dct.pop('metadata', None):
125
+ md = self.md_u.unmarshal(ctx, md_v)
126
+ else:
127
+ md = []
128
+
102
129
  tvs: ta.Any
103
130
  if dct:
104
- tv_vs = dct.pop(lang.strip_prefix(self.tv_fld.name, '_'))
131
+ tv_vs = dct.pop(lang.strip_prefix(self.rr_flds.tv.name, '_'))
105
132
  tvs = self.tv_u.unmarshal(ctx, tv_vs)
106
133
  else:
107
134
  tvs = []
@@ -109,7 +136,7 @@ class _RequestResponseUnmarshaler(msh.Unmarshaler):
109
136
  check.empty(dct)
110
137
 
111
138
  cty = rfl.get_concrete_type(self.rty)
112
- return cty(v, tvs) # type: ignore
139
+ return cty(v, tvs, _metadata=md) # type: ignore
113
140
 
114
141
 
115
142
  class _RequestResponseUnmarshalerFactory(msh.UnmarshalerFactory):
@@ -123,15 +150,24 @@ class _RequestResponseUnmarshalerFactory(msh.UnmarshalerFactory):
123
150
  else:
124
151
  # FIXME: ...
125
152
  raise TypeError(rty)
153
+
154
+ rr_flds = _get_rr_flds(rty)
155
+
126
156
  tv_types_set = check.isinstance(tv_rty, rfl.Union).args
127
157
  tv_ta = tv.TypedValues[ta.Union[*tv_types_set]] # type: ignore
128
158
  tv_u = ctx.make_unmarshaler(tv_ta)
159
+
129
160
  v_u = ctx.make_unmarshaler(v_rty)
161
+
162
+ md_fmd = rr_flds.md.metadata[msh.FieldOptions]
163
+ md_u = md_fmd.unmarshaler_factory.make_unmarshaler(ctx, rr_flds.md.type)() # FIXME
164
+
130
165
  return _RequestResponseUnmarshaler(
131
166
  rty,
132
- _get_tv_fld(rty),
167
+ _get_rr_flds(rty),
133
168
  v_u,
134
169
  tv_u,
170
+ md_u,
135
171
  )
136
172
 
137
173
  return inner
@@ -43,3 +43,14 @@ class _OrigClassCapture:
43
43
  """Enforces that __orig_class__ has only been set once."""
44
44
 
45
45
  return check.single(object.__getattribute__(self, '__captured_orig_classes__'))
46
+
47
+
48
+ def confer_orig_class(src, dst):
49
+ if src is not dst:
50
+ try:
51
+ oc = src.__orig_class__
52
+ except AttributeError:
53
+ pass
54
+ else:
55
+ dst.__orig_class__ = oc
56
+ return dst
@@ -45,14 +45,19 @@ class _TypedValues(
45
45
  lang.Abstract,
46
46
  ta.Generic[TypedValueT],
47
47
  ):
48
- __typed_values_class__: ta.ClassVar[type[tv.TypedValue]]
48
+ """
49
+ The reason this is so complicated compared to any other TypedValues field (like metadata) is that the real set of
50
+ TypedValue types it accepts is known only via __orig_class__.
51
+ """
52
+
53
+ __typed_values_base__: ta.ClassVar[type[tv.TypedValue]]
49
54
 
50
55
  def __init_subclass__(cls, **kwargs: ta.Any) -> None:
51
56
  super().__init_subclass__(**kwargs)
52
57
 
53
58
  tvt = _get_typed_values_type_arg(cls)
54
59
  tvct = rfl.get_concrete_type(tvt, use_type_var_bound=True)
55
- cls.__typed_values_class__ = check.issubclass(check.isinstance(tvct, type), tv.TypedValue)
60
+ cls.__typed_values_base__ = check.issubclass(check.isinstance(tvct, type), tv.TypedValue)
56
61
 
57
62
  #
58
63
 
@@ -77,7 +82,7 @@ class _TypedValues(
77
82
 
78
83
  tv_types_set = frozenset(tv.reflect_typed_values_impls(tvt))
79
84
  tv_types = tuple(sorted(
80
- [check.issubclass(c, self.__typed_values_class__) for c in tv_types_set],
85
+ [check.issubclass(c, self.__typed_values_base__) for c in tv_types_set],
81
86
  key=lambda c: c.__qualname__,
82
87
  ))
83
88
 
@@ -6,8 +6,12 @@ from omlish import lang
6
6
  from omlish import typedvalues as tv
7
7
 
8
8
  from .._typedvalues import _tv_field_metadata
9
+ from ..metadata import CommonMetadata
10
+ from ..metadata import Metadata
11
+ from ..metadata import MetadataContainerDataclass
9
12
  from ..types import Option
10
13
  from ..types import OptionT_co
14
+ from ._origclasses import confer_orig_class
11
15
  from ._typedvalues import _TypedValues
12
16
 
13
17
 
@@ -19,6 +23,17 @@ OptionU = ta.TypeVar('OptionU', bound=Option)
19
23
  ##
20
24
 
21
25
 
26
+ class RequestMetadata(Metadata, lang.Abstract):
27
+ pass
28
+
29
+
30
+ RequestMetadatas: ta.TypeAlias = RequestMetadata | CommonMetadata
31
+
32
+
33
+ ##
34
+
35
+
36
+ @ta.final
22
37
  @dc.dataclass(frozen=True)
23
38
  @dc.extra_class_params(
24
39
  allow_dynamic_dunder_attrs=True,
@@ -26,11 +41,32 @@ OptionU = ta.TypeVar('OptionU', bound=Option)
26
41
  )
27
42
  class Request( # type: ignore[type-var] # FIXME: _TypedValues param is invariant
28
43
  _TypedValues[OptionT_co],
44
+ MetadataContainerDataclass[RequestMetadatas],
29
45
  lang.Final,
30
46
  ta.Generic[V_co, OptionT_co],
31
47
  ):
48
+ """
49
+ Universal service request, comprised of:
50
+ - a value of type `V_co`
51
+ - a sequence of options of type `OptionT_co`
52
+ - metadata of type `RequestMetadatas`
53
+
54
+ Refer to the package README.md for an explanation of its type var variance.
55
+
56
+ This class is final, but each instance's `__orig_class__` (if present) is significant. It is encouraged to construct
57
+ these through a pre-parameterized type alias, and the provided `with_` methods should be used rather than
58
+ `dc.replace` (as they will propagate `__orig_class__`).
59
+ """
60
+
61
+ #
62
+
32
63
  v: V_co # type: ignore[misc] # FIXME: Cannot use a covariant type variable as a parameter
33
64
 
65
+ def with_v(self, v: V_co) -> ta.Self: # type: ignore[misc]
66
+ return confer_orig_class(self, dc.replace(self, v=v))
67
+
68
+ #
69
+
34
70
  _options: ta.Sequence[OptionT_co] = dc.field(
35
71
  default=(),
36
72
  metadata=_tv_field_metadata(
@@ -43,13 +79,47 @@ class Request( # type: ignore[type-var] # FIXME: _TypedValues param is invaria
43
79
  def options(self) -> tv.TypedValues[OptionT_co]:
44
80
  return check.isinstance(self._options, tv.TypedValues)
45
81
 
46
- def with_options(self, *options: OptionU, override: bool = False) -> 'Request[V_co, OptionT_co | OptionU]':
47
- return dc.replace(self, _options=self.options.update(*options, override=override))
48
-
49
82
  @property
50
83
  def _typed_values(self) -> tv.TypedValues[OptionT_co]:
51
84
  return check.isinstance(self._options, tv.TypedValues)
52
85
 
86
+ def with_options(
87
+ self,
88
+ *add: OptionU,
89
+ discard: ta.Iterable[type] | None = None,
90
+ override: bool = False,
91
+ ) -> 'Request[V_co, OptionT_co | OptionU]':
92
+ new = (old := self.options).update(
93
+ *add,
94
+ discard=discard,
95
+ override=override,
96
+ )
97
+
98
+ if new is old:
99
+ return self
100
+
101
+ return confer_orig_class(self, dc.replace(self, _options=new))
102
+
103
+ #
104
+
105
+ _metadata: ta.Sequence[RequestMetadatas] = dc.field(
106
+ default=(),
107
+ kw_only=True,
108
+ repr=False,
109
+ )
110
+
111
+ MetadataContainerDataclass._configure_metadata_field(_metadata, RequestMetadatas) # noqa
112
+
113
+ def with_metadata(
114
+ self,
115
+ *add: RequestMetadatas,
116
+ discard: ta.Iterable[type] | None = None,
117
+ override: bool = False,
118
+ ) -> ta.Self:
119
+ return confer_orig_class(self, super().with_metadata(*add, discard=discard, override=override))
120
+
121
+ #
122
+
53
123
  def validate(self) -> ta.Self:
54
124
  self._check_typed_values()
55
125
  return self