ommlds 0.0.0.dev456__py3-none-any.whl → 0.0.0.dev485__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 (212) hide show
  1. ommlds/.omlish-manifests.json +314 -33
  2. ommlds/__about__.py +15 -9
  3. ommlds/_hacks/__init__.py +4 -0
  4. ommlds/_hacks/funcs.py +110 -0
  5. ommlds/_hacks/names.py +158 -0
  6. ommlds/_hacks/params.py +73 -0
  7. ommlds/_hacks/patches.py +0 -3
  8. ommlds/backends/anthropic/protocol/__init__.py +13 -1
  9. ommlds/backends/anthropic/protocol/_dataclasses.py +1625 -0
  10. ommlds/backends/anthropic/protocol/sse/assemble.py +22 -6
  11. ommlds/backends/anthropic/protocol/sse/events.py +13 -0
  12. ommlds/backends/google/protocol/__init__.py +13 -0
  13. ommlds/backends/google/protocol/_dataclasses.py +5997 -0
  14. ommlds/backends/google/protocol/types.py +5 -1
  15. ommlds/backends/groq/__init__.py +7 -0
  16. ommlds/backends/groq/_dataclasses.py +3901 -0
  17. ommlds/backends/groq/_marshal.py +23 -0
  18. ommlds/backends/groq/protocol.py +249 -0
  19. ommlds/backends/llamacpp/logging.py +4 -1
  20. ommlds/backends/mlx/caching.py +7 -3
  21. ommlds/backends/mlx/cli.py +10 -7
  22. ommlds/backends/mlx/generation.py +18 -16
  23. ommlds/backends/mlx/limits.py +10 -6
  24. ommlds/backends/mlx/loading.py +65 -5
  25. ommlds/backends/ollama/__init__.py +7 -0
  26. ommlds/backends/ollama/_dataclasses.py +3458 -0
  27. ommlds/backends/ollama/protocol.py +170 -0
  28. ommlds/backends/openai/protocol/__init__.py +15 -1
  29. ommlds/backends/openai/protocol/_dataclasses.py +7708 -0
  30. ommlds/backends/tavily/__init__.py +7 -0
  31. ommlds/backends/tavily/_dataclasses.py +1734 -0
  32. ommlds/backends/tavily/protocol.py +301 -0
  33. ommlds/backends/tinygrad/models/llama3/__init__.py +22 -14
  34. ommlds/backends/transformers/__init__.py +14 -0
  35. ommlds/backends/transformers/filecache.py +109 -0
  36. ommlds/backends/transformers/streamers.py +73 -0
  37. ommlds/cli/__init__.py +7 -0
  38. ommlds/cli/_dataclasses.py +2562 -0
  39. ommlds/cli/asyncs.py +30 -0
  40. ommlds/cli/backends/catalog.py +93 -0
  41. ommlds/cli/backends/configs.py +9 -0
  42. ommlds/cli/backends/inject.py +31 -36
  43. ommlds/cli/backends/injection.py +16 -0
  44. ommlds/cli/backends/types.py +46 -0
  45. ommlds/cli/content/messages.py +34 -0
  46. ommlds/cli/content/strings.py +42 -0
  47. ommlds/cli/inject.py +15 -32
  48. ommlds/cli/inputs/__init__.py +0 -0
  49. ommlds/cli/inputs/asyncs.py +32 -0
  50. ommlds/cli/inputs/sync.py +75 -0
  51. ommlds/cli/main.py +267 -128
  52. ommlds/cli/rendering/__init__.py +0 -0
  53. ommlds/cli/rendering/configs.py +9 -0
  54. ommlds/cli/rendering/inject.py +31 -0
  55. ommlds/cli/rendering/markdown.py +52 -0
  56. ommlds/cli/rendering/raw.py +73 -0
  57. ommlds/cli/rendering/types.py +21 -0
  58. ommlds/cli/secrets.py +21 -0
  59. ommlds/cli/sessions/base.py +1 -1
  60. ommlds/cli/sessions/chat/chat/__init__.py +0 -0
  61. ommlds/cli/sessions/chat/chat/ai/__init__.py +0 -0
  62. ommlds/cli/sessions/chat/chat/ai/configs.py +11 -0
  63. ommlds/cli/sessions/chat/chat/ai/inject.py +74 -0
  64. ommlds/cli/sessions/chat/chat/ai/injection.py +14 -0
  65. ommlds/cli/sessions/chat/chat/ai/rendering.py +70 -0
  66. ommlds/cli/sessions/chat/chat/ai/services.py +79 -0
  67. ommlds/cli/sessions/chat/chat/ai/tools.py +44 -0
  68. ommlds/cli/sessions/chat/chat/ai/types.py +28 -0
  69. ommlds/cli/sessions/chat/chat/state/__init__.py +0 -0
  70. ommlds/cli/sessions/chat/chat/state/configs.py +11 -0
  71. ommlds/cli/sessions/chat/chat/state/inject.py +36 -0
  72. ommlds/cli/sessions/chat/chat/state/inmemory.py +33 -0
  73. ommlds/cli/sessions/chat/chat/state/storage.py +52 -0
  74. ommlds/cli/sessions/chat/chat/state/types.py +38 -0
  75. ommlds/cli/sessions/chat/chat/user/__init__.py +0 -0
  76. ommlds/cli/sessions/chat/chat/user/configs.py +17 -0
  77. ommlds/cli/sessions/chat/chat/user/inject.py +62 -0
  78. ommlds/cli/sessions/chat/chat/user/interactive.py +31 -0
  79. ommlds/cli/sessions/chat/chat/user/oneshot.py +25 -0
  80. ommlds/cli/sessions/chat/chat/user/types.py +15 -0
  81. ommlds/cli/sessions/chat/configs.py +27 -0
  82. ommlds/cli/sessions/chat/driver.py +43 -0
  83. ommlds/cli/sessions/chat/inject.py +33 -65
  84. ommlds/cli/sessions/chat/phases/__init__.py +0 -0
  85. ommlds/cli/sessions/chat/phases/inject.py +27 -0
  86. ommlds/cli/sessions/chat/phases/injection.py +14 -0
  87. ommlds/cli/sessions/chat/phases/manager.py +29 -0
  88. ommlds/cli/sessions/chat/phases/types.py +29 -0
  89. ommlds/cli/sessions/chat/session.py +27 -0
  90. ommlds/cli/sessions/chat/tools/__init__.py +0 -0
  91. ommlds/cli/sessions/chat/tools/configs.py +22 -0
  92. ommlds/cli/sessions/chat/tools/confirmation.py +46 -0
  93. ommlds/cli/sessions/chat/tools/execution.py +66 -0
  94. ommlds/cli/sessions/chat/tools/fs/__init__.py +0 -0
  95. ommlds/cli/sessions/chat/tools/fs/configs.py +12 -0
  96. ommlds/cli/sessions/chat/tools/fs/inject.py +35 -0
  97. ommlds/cli/sessions/chat/tools/inject.py +88 -0
  98. ommlds/cli/sessions/chat/tools/injection.py +44 -0
  99. ommlds/cli/sessions/chat/tools/rendering.py +58 -0
  100. ommlds/cli/sessions/chat/tools/todo/__init__.py +0 -0
  101. ommlds/cli/sessions/chat/tools/todo/configs.py +12 -0
  102. ommlds/cli/sessions/chat/tools/todo/inject.py +31 -0
  103. ommlds/cli/sessions/chat/tools/weather/__init__.py +0 -0
  104. ommlds/cli/sessions/chat/tools/weather/configs.py +12 -0
  105. ommlds/cli/sessions/chat/tools/weather/inject.py +22 -0
  106. ommlds/cli/{tools/weather.py → sessions/chat/tools/weather/tools.py} +1 -1
  107. ommlds/cli/sessions/completion/configs.py +21 -0
  108. ommlds/cli/sessions/completion/inject.py +42 -0
  109. ommlds/cli/sessions/completion/session.py +35 -0
  110. ommlds/cli/sessions/embedding/configs.py +21 -0
  111. ommlds/cli/sessions/embedding/inject.py +42 -0
  112. ommlds/cli/sessions/embedding/session.py +33 -0
  113. ommlds/cli/sessions/inject.py +28 -11
  114. ommlds/cli/state/__init__.py +0 -0
  115. ommlds/cli/state/inject.py +28 -0
  116. ommlds/cli/{state.py → state/storage.py} +41 -24
  117. ommlds/minichain/__init__.py +46 -17
  118. ommlds/minichain/_dataclasses.py +15401 -0
  119. ommlds/minichain/backends/catalogs/base.py +20 -1
  120. ommlds/minichain/backends/catalogs/simple.py +2 -2
  121. ommlds/minichain/backends/catalogs/strings.py +10 -8
  122. ommlds/minichain/backends/impls/anthropic/chat.py +31 -65
  123. ommlds/minichain/backends/impls/anthropic/names.py +3 -4
  124. ommlds/minichain/backends/impls/anthropic/protocol.py +109 -0
  125. ommlds/minichain/backends/impls/anthropic/stream.py +53 -31
  126. ommlds/minichain/backends/impls/duckduckgo/search.py +5 -1
  127. ommlds/minichain/backends/impls/dummy/__init__.py +0 -0
  128. ommlds/minichain/backends/impls/dummy/chat.py +69 -0
  129. ommlds/minichain/backends/impls/google/chat.py +9 -2
  130. ommlds/minichain/backends/impls/google/search.py +6 -1
  131. ommlds/minichain/backends/impls/google/stream.py +122 -32
  132. ommlds/minichain/backends/impls/groq/__init__.py +0 -0
  133. ommlds/minichain/backends/impls/groq/chat.py +75 -0
  134. ommlds/minichain/backends/impls/groq/names.py +48 -0
  135. ommlds/minichain/backends/impls/groq/protocol.py +143 -0
  136. ommlds/minichain/backends/impls/groq/stream.py +125 -0
  137. ommlds/minichain/backends/impls/huggingface/repos.py +1 -5
  138. ommlds/minichain/backends/impls/llamacpp/chat.py +15 -3
  139. ommlds/minichain/backends/impls/llamacpp/completion.py +7 -3
  140. ommlds/minichain/backends/impls/llamacpp/stream.py +38 -19
  141. ommlds/minichain/backends/impls/mistral.py +9 -2
  142. ommlds/minichain/backends/impls/mlx/chat.py +100 -23
  143. ommlds/minichain/backends/impls/ollama/__init__.py +0 -0
  144. ommlds/minichain/backends/impls/ollama/chat.py +199 -0
  145. ommlds/minichain/backends/impls/openai/chat.py +14 -7
  146. ommlds/minichain/backends/impls/openai/completion.py +9 -2
  147. ommlds/minichain/backends/impls/openai/embedding.py +9 -2
  148. ommlds/minichain/backends/impls/openai/format.py +115 -109
  149. ommlds/minichain/backends/impls/openai/names.py +31 -5
  150. ommlds/minichain/backends/impls/openai/stream.py +33 -27
  151. ommlds/minichain/backends/impls/sentencepiece/tokens.py +9 -6
  152. ommlds/minichain/backends/impls/tavily.py +66 -0
  153. ommlds/minichain/backends/impls/tinygrad/chat.py +17 -14
  154. ommlds/minichain/backends/impls/tokenizers/tokens.py +9 -6
  155. ommlds/minichain/backends/impls/transformers/sentence.py +5 -2
  156. ommlds/minichain/backends/impls/transformers/tokens.py +10 -7
  157. ommlds/minichain/backends/impls/transformers/transformers.py +139 -20
  158. ommlds/minichain/backends/strings/parsing.py +1 -1
  159. ommlds/minichain/backends/strings/resolving.py +4 -1
  160. ommlds/minichain/chat/choices/stream/__init__.py +0 -0
  161. ommlds/minichain/chat/choices/stream/adapters.py +35 -0
  162. ommlds/minichain/chat/choices/stream/joining.py +31 -0
  163. ommlds/minichain/chat/choices/stream/services.py +45 -0
  164. ommlds/minichain/chat/choices/stream/types.py +43 -0
  165. ommlds/minichain/chat/stream/_marshal.py +4 -4
  166. ommlds/minichain/chat/stream/joining.py +85 -0
  167. ommlds/minichain/chat/stream/services.py +15 -15
  168. ommlds/minichain/chat/stream/types.py +24 -18
  169. ommlds/minichain/llms/types.py +4 -0
  170. ommlds/minichain/registries/globals.py +18 -4
  171. ommlds/minichain/resources.py +28 -3
  172. ommlds/minichain/search.py +1 -1
  173. ommlds/minichain/standard.py +8 -0
  174. ommlds/minichain/stream/services.py +19 -16
  175. ommlds/minichain/tools/reflect.py +5 -1
  176. ommlds/nanochat/LICENSE +21 -0
  177. ommlds/nanochat/__init__.py +0 -0
  178. ommlds/nanochat/rustbpe/LICENSE +21 -0
  179. ommlds/nanochat/tokenizers.py +406 -0
  180. ommlds/specs/__init__.py +0 -0
  181. ommlds/specs/mcp/__init__.py +0 -0
  182. ommlds/specs/mcp/_marshal.py +23 -0
  183. ommlds/specs/mcp/clients.py +146 -0
  184. ommlds/specs/mcp/protocol.py +371 -0
  185. ommlds/tools/git.py +13 -6
  186. ommlds/tools/ocr.py +1 -8
  187. ommlds/wiki/analyze.py +2 -2
  188. ommlds/wiki/text/mfh.py +1 -5
  189. ommlds/wiki/text/wtp.py +1 -3
  190. ommlds/wiki/utils/xml.py +5 -5
  191. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/METADATA +22 -19
  192. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/RECORD +198 -95
  193. ommlds/cli/backends/standard.py +0 -20
  194. ommlds/cli/sessions/chat/base.py +0 -42
  195. ommlds/cli/sessions/chat/code.py +0 -129
  196. ommlds/cli/sessions/chat/interactive.py +0 -71
  197. ommlds/cli/sessions/chat/printing.py +0 -97
  198. ommlds/cli/sessions/chat/prompt.py +0 -151
  199. ommlds/cli/sessions/chat/state.py +0 -110
  200. ommlds/cli/sessions/chat/tools.py +0 -100
  201. ommlds/cli/sessions/completion/completion.py +0 -44
  202. ommlds/cli/sessions/embedding/embedding.py +0 -42
  203. ommlds/cli/tools/config.py +0 -14
  204. ommlds/cli/tools/inject.py +0 -75
  205. ommlds/minichain/backends/impls/openai/format2.py +0 -210
  206. ommlds/minichain/chat/stream/adapters.py +0 -80
  207. /ommlds/{huggingface.py → backends/huggingface.py} +0 -0
  208. /ommlds/cli/{tools → content}/__init__.py +0 -0
  209. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/WHEEL +0 -0
  210. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/entry_points.txt +0 -0
  211. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/licenses/LICENSE +0 -0
  212. {ommlds-0.0.0.dev456.dist-info → ommlds-0.0.0.dev485.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,85 @@
1
+ import typing as ta
2
+
3
+ from omlish import check
4
+ from omlish.formats import json
5
+
6
+ from ...tools.types import ToolUse
7
+ from ..messages import AiChat
8
+ from ..messages import AiMessage
9
+ from ..messages import AnyAiMessage
10
+ from ..messages import ToolUseMessage
11
+ from .types import AiDelta
12
+ from .types import AiDeltas
13
+ from .types import ContentAiDelta
14
+ from .types import PartialToolUseAiDelta
15
+ from .types import ToolUseAiDelta
16
+
17
+
18
+ ##
19
+
20
+
21
+ class AiDeltaJoiner:
22
+ def __init__(self) -> None:
23
+ super().__init__()
24
+
25
+ self._deltas: list[AiDelta] = []
26
+ self._messages: list[AnyAiMessage] = []
27
+
28
+ def _build_joined(self, deltas: ta.Sequence[AiDelta]) -> AnyAiMessage:
29
+ dty = check.single(set(map(type, check.not_empty(deltas))))
30
+
31
+ if dty is ContentAiDelta:
32
+ cds = ta.cast(ta.Sequence[ContentAiDelta], deltas)
33
+ return AiMessage(''.join(check.isinstance(cd.c, str) for cd in cds))
34
+
35
+ elif dty is ToolUseAiDelta:
36
+ raise TypeError(dty)
37
+
38
+ elif dty is PartialToolUseAiDelta:
39
+ tds = ta.cast(ta.Sequence[PartialToolUseAiDelta], deltas)
40
+ for td in ta.cast(ta.Sequence[PartialToolUseAiDelta], deltas)[1:]:
41
+ check.none(td.id)
42
+ check.none(td.name)
43
+
44
+ ra = ''.join(filter(None, (td.raw_args for td in tds)))
45
+
46
+ return ToolUseMessage(ToolUse(
47
+ id=tds[0].id,
48
+ name=check.non_empty_str(tds[0].name),
49
+ args=json.loads(ra),
50
+ raw_args=ra,
51
+ ))
52
+
53
+ else:
54
+ raise TypeError(dty)
55
+
56
+ def _maybe_join(self) -> None:
57
+ if not self._deltas:
58
+ return
59
+
60
+ self._messages.append(self._build_joined(self._deltas))
61
+ self._deltas.clear()
62
+
63
+ def _add_one(self, d: AiDelta) -> None:
64
+ if self._deltas and type(self._deltas[0]) is not type(d):
65
+ self._maybe_join()
66
+
67
+ if isinstance(d, ToolUseAiDelta):
68
+ self._messages.append(ToolUseMessage(ToolUse(
69
+ id=d.id,
70
+ name=check.not_none(d.name),
71
+ args=d.args or {},
72
+ raw_args=json.dumps_compact(d.args),
73
+ )))
74
+
75
+ else:
76
+ self._deltas.append(d)
77
+
78
+ def add(self, deltas: AiDeltas) -> None:
79
+ for d in deltas:
80
+ self._add_one(d)
81
+
82
+ def build(self) -> AiChat:
83
+ self._maybe_join()
84
+
85
+ return list(self._messages)
@@ -7,39 +7,39 @@ from ...registries.globals import register_type
7
7
  from ...services import Request
8
8
  from ...services import Service
9
9
  from ...stream.services import StreamResponse
10
- from ..choices.types import ChatChoicesOutputs
11
10
  from ..messages import Chat
12
- from .types import AiChoicesDeltas
13
- from .types import ChatChoicesStreamOptions
14
- from .types import ChatChoicesStreamOutputs
11
+ from ..types import ChatOutputs
12
+ from .types import AiDeltas
13
+ from .types import ChatStreamOptions
14
+ from .types import ChatStreamOutputs
15
15
 
16
16
 
17
17
  ##
18
18
 
19
19
 
20
- ChatChoicesStreamRequest: ta.TypeAlias = Request[Chat, ChatChoicesStreamOptions]
20
+ ChatStreamRequest: ta.TypeAlias = Request[Chat, ChatStreamOptions]
21
21
 
22
- ChatChoicesStreamResponse: ta.TypeAlias = StreamResponse[
23
- AiChoicesDeltas,
24
- ChatChoicesOutputs,
25
- ChatChoicesStreamOutputs,
22
+ ChatStreamResponse: ta.TypeAlias = StreamResponse[
23
+ AiDeltas,
24
+ ChatOutputs,
25
+ ChatStreamOutputs,
26
26
  ]
27
27
 
28
28
  # @omlish-manifest $.minichain.registries.manifests.RegistryTypeManifest
29
- ChatChoicesStreamService: ta.TypeAlias = Service[ChatChoicesStreamRequest, ChatChoicesStreamResponse]
29
+ ChatStreamService: ta.TypeAlias = Service[ChatStreamRequest, ChatStreamResponse]
30
30
 
31
- register_type(ChatChoicesStreamService, module=__name__)
31
+ register_type(ChatStreamService, module=__name__)
32
32
 
33
33
 
34
- def static_check_is_chat_choices_stream_service[T: ChatChoicesStreamService](t: type[T]) -> type[T]:
34
+ def static_check_is_chat_stream_service[T: ChatStreamService](t: type[T]) -> type[T]:
35
35
  return t
36
36
 
37
37
 
38
38
  ##
39
39
 
40
40
 
41
- @static_check_is_chat_choices_stream_service
42
- class AbstractChatChoicesStreamService(lang.Abstract):
41
+ @static_check_is_chat_stream_service
42
+ class AbstractChatStreamService(lang.Abstract):
43
43
  @abc.abstractmethod
44
- def invoke(self, request: ChatChoicesStreamRequest) -> ta.Awaitable[ChatChoicesStreamResponse]:
44
+ def invoke(self, request: ChatStreamRequest) -> ta.Awaitable[ChatStreamResponse]:
45
45
  raise NotImplementedError
@@ -6,10 +6,9 @@ from omlish import marshal as msh
6
6
 
7
7
  from ...content.types import Content
8
8
  from ...stream.services import StreamOptions
9
- from ...tools.types import ToolUse
10
9
  from ...types import Option
11
10
  from ...types import Output
12
- from ..choices.types import ChatChoicesOptions
11
+ from ..types import ChatOptions
13
12
 
14
13
 
15
14
  msh.register_global_module_import('._marshal', __package__)
@@ -18,49 +17,56 @@ msh.register_global_module_import('._marshal', __package__)
18
17
  ##
19
18
 
20
19
 
21
- class ChatChoicesStreamOption(Option, lang.Abstract, lang.PackageSealed):
20
+ class ChatStreamOption(Option, lang.Abstract, lang.PackageSealed):
22
21
  pass
23
22
 
24
23
 
25
- ChatChoicesStreamOptions: ta.TypeAlias = ChatChoicesStreamOption | StreamOptions | ChatChoicesOptions
24
+ ChatStreamOptions: ta.TypeAlias = ChatStreamOption | StreamOptions | ChatOptions
26
25
 
27
26
 
28
27
  ##
29
28
 
30
29
 
31
- class ChatChoicesStreamOutput(Output, lang.Abstract, lang.PackageSealed):
30
+ class ChatStreamOutput(Output, lang.Abstract, lang.PackageSealed):
32
31
  pass
33
32
 
34
33
 
35
- ChatChoicesStreamOutputs: ta.TypeAlias = ChatChoicesStreamOutput
34
+ ChatStreamOutputs: ta.TypeAlias = ChatStreamOutput
36
35
 
37
36
 
38
37
  ##
39
38
 
40
39
 
41
40
  @dc.dataclass(frozen=True)
42
- class AiChoiceDelta(lang.Sealed, lang.Abstract):
41
+ class AiDelta(lang.Sealed, lang.Abstract):
43
42
  pass
44
43
 
45
44
 
46
- @dc.dataclass(frozen=True)
47
- class ContentAiChoiceDelta(AiChoiceDelta, lang.Final):
48
- c: Content
45
+ AiDeltas: ta.TypeAlias = ta.Sequence[AiDelta]
46
+
47
+
48
+ #
49
49
 
50
50
 
51
51
  @dc.dataclass(frozen=True)
52
- class ToolUseAiChoiceDelta(AiChoiceDelta, lang.Final):
53
- tu: ToolUse
52
+ class ContentAiDelta(AiDelta, lang.Final):
53
+ c: Content
54
54
 
55
55
 
56
56
  #
57
57
 
58
58
 
59
- @dc.dataclass(frozen=True)
60
- class AiChoiceDeltas(lang.Final):
61
- deltas: ta.Sequence[AiChoiceDelta]
59
+ @dc.dataclass(frozen=True, kw_only=True)
60
+ class AnyToolUseAiDelta(AiDelta, lang.Abstract):
61
+ id: str | None = None
62
+ name: str | None = None
62
63
 
63
64
 
64
- @dc.dataclass(frozen=True)
65
- class AiChoicesDeltas(lang.Final):
66
- choices: ta.Sequence[AiChoiceDeltas]
65
+ @dc.dataclass(frozen=True, kw_only=True)
66
+ class ToolUseAiDelta(AnyToolUseAiDelta, lang.Final):
67
+ args: ta.Mapping[str, ta.Any] | None = None
68
+
69
+
70
+ @dc.dataclass(frozen=True, kw_only=True)
71
+ class PartialToolUseAiDelta(AnyToolUseAiDelta, lang.Final):
72
+ raw_args: ta.Any | None = None
@@ -31,6 +31,10 @@ class MaxTokens(LlmOption, tv.UniqueScalarTypedValue[int]):
31
31
  pass
32
32
 
33
33
 
34
+ class MaxCompletionTokens(LlmOption, tv.UniqueScalarTypedValue[int]):
35
+ pass
36
+
37
+
34
38
  ##
35
39
 
36
40
 
@@ -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,6 +31,7 @@ class ResourcesRefNotRegisteredError(Exception):
31
31
  pass
32
32
 
33
33
 
34
+ @ta.final
34
35
  class Resources(lang.Final, lang.NotPicklable):
35
36
  def __init__(
36
37
  self,
@@ -145,24 +146,48 @@ class Resources(lang.Final, lang.NotPicklable):
145
146
  ##
146
147
 
147
148
 
149
+ @ta.final
148
150
  class ResourceManaged(ResourcesRef, lang.Final, lang.NotPicklable, ta.Generic[T]):
151
+ """
152
+ A class to 'handoff' a ref to a `Resources`, allowing the `Resources` to temporarily survive being passed from
153
+ instantiation within a callee to being `__aenter__`'d in the caller.
154
+
155
+ The ref to the `Resources` is allocated in the ctor, so the contract is that an instance of this must be immediately
156
+ `__aenter__`'d before doing anything else with the return value of the call. Failure to do so leaks the `Resources`.
157
+ """
158
+
149
159
  def __init__(self, v: T, resources: Resources) -> None:
150
160
  super().__init__()
151
161
 
152
- self._v = v
162
+ self.__v = v
153
163
  self.__resources = resources
154
164
 
155
165
  resources.add_ref(self)
156
166
 
167
+ __state: ta.Literal['new', 'entered', 'exited'] = 'new'
168
+
157
169
  def __repr__(self) -> str:
158
- return f'{self.__class__.__name__}<{self._v!r}>'
170
+ return f'{self.__class__.__name__}<{self.__v!r}, {self.__state}>'
159
171
 
160
172
  async def __aenter__(self) -> T:
161
- return self._v
173
+ check.state(self.__state == 'new')
174
+ self.__state = 'entered'
175
+ return self.__v
162
176
 
163
177
  async def __aexit__(self, exc_type, exc_val, exc_tb):
178
+ check.state(self.__state == 'entered')
179
+ self.__state = 'exited'
164
180
  await self.__resources.remove_ref(self)
165
181
 
182
+ def __del__(self) -> None:
183
+ if self.__state != 'exited':
184
+ log.error(
185
+ f'{__package__}.{self.__class__.__name__}.__del__: ' # noqa
186
+ f'%r deleted without being entered and exited! '
187
+ f'resources: %s',
188
+ repr(self),
189
+ repr(self.__resources),
190
+ )
166
191
 
167
192
  ##
168
193
 
@@ -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)
@@ -25,6 +25,14 @@ class Device(tv.UniqueScalarTypedValue[ta.Any], Config):
25
25
  ##
26
26
 
27
27
 
28
+ # TODO: ApiEndpointPath, ApiEndpointUrl, ApiBaseUrl, ...
29
+ class ApiUrl(tv.UniqueScalarTypedValue[str], Config):
30
+ pass
31
+
32
+
33
+ ##
34
+
35
+
28
36
  @dc.dataclass(frozen=True)
29
37
  class SecretConfig(Config, lang.Abstract):
30
38
  v: sec.SecretRefOrStr = dc.field() | sec.secret_field
@@ -125,17 +125,18 @@ class _StreamServiceResponse(StreamResponseIterator[V, OutputT]):
125
125
  return
126
126
  if self._cr.cr_running or self._cr.cr_suspended:
127
127
  cex = StreamServiceCancelledError()
128
- for i in itertools.count():
128
+ i = None
129
+ for n in itertools.count():
129
130
  try:
130
- if not i:
131
+ if not n:
131
132
  x = self._g.throw(cex)
132
133
  else:
133
- x = self._g.send(None)
134
+ x = self._g.send(i)
134
135
  except StreamServiceCancelledError as cex2:
135
136
  if cex2 is cex:
136
137
  break
137
138
  raise
138
- yield x
139
+ i = yield x
139
140
  if self._cr.cr_running:
140
141
  raise RuntimeError(f'Coroutine {self._cr!r} not terminated')
141
142
  if self._g is not self._a:
@@ -155,9 +156,10 @@ class _StreamServiceResponse(StreamResponseIterator[V, OutputT]):
155
156
  @types.coroutine
156
157
  def _anext(self):
157
158
  check.state(self._state == 'running')
159
+ i = None
158
160
  while True:
159
161
  try:
160
- x = self._g.send(None)
162
+ x = self._g.send(i)
161
163
  except StopIteration as e:
162
164
  if e.value is not None:
163
165
  self._outputs = tv.TypedValues(*check.isinstance(e.value, ta.Sequence))
@@ -170,7 +172,7 @@ class _StreamServiceResponse(StreamResponseIterator[V, OutputT]):
170
172
  x.done = True
171
173
  return x.value
172
174
 
173
- yield x
175
+ i = yield x
174
176
 
175
177
  async def __anext__(self) -> V:
176
178
  return await self._anext()
@@ -195,13 +197,14 @@ async def new_stream_response(
195
197
  fn: ta.Callable[[StreamResponseSink[V]], ta.Awaitable[ta.Sequence[OutputT] | None]],
196
198
  outputs: ta.Sequence[StreamOutputT] | None = None,
197
199
  ) -> StreamResponse[V, OutputT, StreamOutputT]:
198
- return StreamResponse(
199
- rs.new_managed(
200
- await rs.enter_async_context(
201
- _StreamServiceResponse(
202
- fn,
203
- ),
204
- ),
205
- ),
206
- outputs or [],
207
- )
200
+ ssr = _StreamServiceResponse(fn)
201
+
202
+ v = rs.new_managed(await rs.enter_async_context(ssr))
203
+ try:
204
+ return StreamResponse(v, outputs or [])
205
+ except BaseException: # noqa
206
+ # The StreamResponse ctor can raise - for example in `_tv_field_coercer` - in which case we need to clean up the
207
+ # resources ref we have already allocated before reraising.
208
+ async with v:
209
+ pass
210
+ raise
@@ -15,10 +15,10 @@ import textwrap
15
15
  import types
16
16
  import typing as ta
17
17
 
18
- from omdev.py import docstrings
19
18
  from omlish import check
20
19
  from omlish import collections as col
21
20
  from omlish import dataclasses as dc
21
+ from omlish import lang
22
22
  from omlish import metadata as md
23
23
  from omlish import reflect as rfl
24
24
  from omlish.lite.cached import cached_nullary
@@ -37,6 +37,10 @@ from .types import TupleToolDtype
37
37
  from .types import UnionToolDtype
38
38
 
39
39
 
40
+ with lang.auto_proxy_import(globals()):
41
+ from omdev.py import docstrings
42
+
43
+
40
44
  ##
41
45
 
42
46
 
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Andrej Karpathy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
File without changes
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Andrej Karpathy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.