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.
- ommlds/.omlish-manifests.json +100 -33
- ommlds/README.md +11 -0
- ommlds/__about__.py +9 -6
- ommlds/backends/anthropic/protocol/__init__.py +13 -1
- ommlds/backends/anthropic/protocol/_dataclasses.py +1625 -0
- ommlds/backends/anthropic/protocol/sse/events.py +2 -0
- ommlds/backends/cerebras/__init__.py +7 -0
- ommlds/backends/cerebras/_dataclasses.py +4254 -0
- ommlds/backends/cerebras/_marshal.py +24 -0
- ommlds/backends/cerebras/protocol.py +312 -0
- ommlds/backends/google/protocol/__init__.py +13 -0
- ommlds/backends/google/protocol/_dataclasses.py +5997 -0
- ommlds/backends/groq/__init__.py +7 -0
- ommlds/backends/groq/_dataclasses.py +3901 -0
- ommlds/backends/groq/clients.py +9 -0
- ommlds/backends/llamacpp/logging.py +4 -1
- ommlds/backends/mlx/caching.py +7 -3
- ommlds/backends/mlx/cli.py +10 -7
- ommlds/backends/mlx/generation.py +18 -16
- ommlds/backends/mlx/limits.py +10 -6
- ommlds/backends/mlx/loading.py +7 -4
- ommlds/backends/ollama/__init__.py +7 -0
- ommlds/backends/ollama/_dataclasses.py +3488 -0
- ommlds/backends/ollama/protocol.py +3 -0
- ommlds/backends/openai/protocol/__init__.py +15 -1
- ommlds/backends/openai/protocol/_dataclasses.py +7708 -0
- ommlds/backends/tavily/__init__.py +7 -0
- ommlds/backends/tavily/_dataclasses.py +1734 -0
- ommlds/backends/transformers/__init__.py +14 -0
- ommlds/cli/__init__.py +7 -0
- ommlds/cli/_dataclasses.py +3515 -0
- ommlds/cli/backends/catalog.py +0 -5
- ommlds/cli/backends/inject.py +70 -7
- ommlds/cli/backends/meta.py +82 -0
- ommlds/cli/content/messages.py +1 -1
- ommlds/cli/inject.py +11 -3
- ommlds/cli/main.py +137 -68
- ommlds/cli/rendering/types.py +6 -0
- ommlds/cli/secrets.py +2 -1
- ommlds/cli/sessions/base.py +1 -10
- ommlds/cli/sessions/chat/configs.py +9 -17
- ommlds/cli/sessions/chat/{chat → drivers}/ai/configs.py +3 -1
- ommlds/cli/sessions/chat/drivers/ai/events.py +57 -0
- ommlds/cli/sessions/chat/{chat → drivers}/ai/inject.py +10 -3
- ommlds/cli/sessions/chat/{chat → drivers}/ai/rendering.py +1 -1
- ommlds/cli/sessions/chat/{chat → drivers}/ai/services.py +1 -1
- ommlds/cli/sessions/chat/{chat → drivers}/ai/tools.py +4 -8
- ommlds/cli/sessions/chat/{chat → drivers}/ai/types.py +9 -0
- ommlds/cli/sessions/chat/drivers/configs.py +25 -0
- ommlds/cli/sessions/chat/drivers/events/inject.py +27 -0
- ommlds/cli/sessions/chat/drivers/events/injection.py +14 -0
- ommlds/cli/sessions/chat/drivers/events/manager.py +16 -0
- ommlds/cli/sessions/chat/drivers/events/types.py +38 -0
- ommlds/cli/sessions/chat/drivers/impl.py +50 -0
- ommlds/cli/sessions/chat/drivers/inject.py +70 -0
- ommlds/cli/sessions/chat/{chat → drivers}/state/configs.py +2 -0
- ommlds/cli/sessions/chat/drivers/state/ids.py +25 -0
- ommlds/cli/sessions/chat/drivers/state/inject.py +83 -0
- ommlds/cli/sessions/chat/{chat → drivers}/state/inmemory.py +0 -4
- ommlds/cli/sessions/chat/{chat → drivers}/state/storage.py +17 -10
- ommlds/cli/sessions/chat/{chat → drivers}/state/types.py +10 -5
- ommlds/cli/sessions/chat/{tools → drivers/tools}/configs.py +2 -2
- ommlds/cli/sessions/chat/drivers/tools/confirmation.py +44 -0
- ommlds/cli/sessions/chat/drivers/tools/errorhandling.py +39 -0
- ommlds/cli/sessions/chat/{tools → drivers/tools}/execution.py +3 -4
- ommlds/cli/sessions/chat/{tools → drivers/tools}/fs/inject.py +3 -3
- ommlds/cli/sessions/chat/{tools → drivers/tools}/inject.py +7 -12
- ommlds/cli/sessions/chat/{tools → drivers/tools}/injection.py +5 -5
- ommlds/cli/sessions/chat/{tools → drivers/tools}/rendering.py +3 -3
- ommlds/cli/sessions/chat/{tools → drivers/tools}/todo/inject.py +3 -3
- ommlds/cli/sessions/chat/{tools → drivers/tools}/weather/tools.py +1 -1
- ommlds/cli/sessions/chat/drivers/types.py +31 -0
- ommlds/cli/sessions/chat/{chat → drivers}/user/configs.py +0 -3
- ommlds/cli/sessions/chat/drivers/user/inject.py +41 -0
- ommlds/cli/sessions/chat/facades/__init__.py +0 -0
- ommlds/cli/sessions/chat/facades/commands/__init__.py +0 -0
- ommlds/cli/sessions/chat/facades/commands/base.py +83 -0
- ommlds/cli/sessions/chat/facades/commands/configs.py +9 -0
- ommlds/cli/sessions/chat/facades/commands/inject.py +41 -0
- ommlds/cli/sessions/chat/facades/commands/injection.py +15 -0
- ommlds/cli/sessions/chat/facades/commands/manager.py +59 -0
- ommlds/cli/sessions/chat/facades/commands/simple.py +34 -0
- ommlds/cli/sessions/chat/facades/commands/types.py +13 -0
- ommlds/cli/sessions/chat/facades/configs.py +11 -0
- ommlds/cli/sessions/chat/facades/facade.py +26 -0
- ommlds/cli/sessions/chat/facades/inject.py +35 -0
- ommlds/cli/sessions/chat/facades/ui.py +34 -0
- ommlds/cli/sessions/chat/inject.py +8 -31
- ommlds/cli/sessions/chat/interfaces/__init__.py +0 -0
- ommlds/cli/sessions/chat/interfaces/bare/__init__.py +0 -0
- ommlds/cli/sessions/chat/interfaces/bare/configs.py +15 -0
- ommlds/cli/sessions/chat/interfaces/bare/inject.py +69 -0
- ommlds/cli/sessions/chat/interfaces/bare/interactive.py +49 -0
- ommlds/cli/sessions/chat/interfaces/bare/oneshot.py +21 -0
- ommlds/cli/sessions/chat/{tools/confirmation.py → interfaces/bare/tools.py} +3 -22
- ommlds/cli/sessions/chat/interfaces/base.py +13 -0
- ommlds/cli/sessions/chat/interfaces/configs.py +11 -0
- ommlds/cli/sessions/chat/interfaces/inject.py +29 -0
- ommlds/cli/sessions/chat/interfaces/textual/__init__.py +0 -0
- ommlds/cli/sessions/chat/interfaces/textual/app.py +310 -0
- ommlds/cli/sessions/chat/interfaces/textual/configs.py +11 -0
- ommlds/cli/sessions/chat/interfaces/textual/facades.py +19 -0
- ommlds/cli/sessions/chat/interfaces/textual/inject.py +97 -0
- ommlds/cli/sessions/chat/interfaces/textual/interface.py +24 -0
- ommlds/cli/sessions/chat/interfaces/textual/styles/__init__.py +29 -0
- ommlds/cli/sessions/chat/interfaces/textual/styles/input.tcss +53 -0
- ommlds/cli/sessions/chat/interfaces/textual/styles/markdown.tcss +7 -0
- ommlds/cli/sessions/chat/interfaces/textual/styles/messages.tcss +157 -0
- ommlds/cli/sessions/chat/interfaces/textual/tools.py +38 -0
- ommlds/cli/sessions/chat/interfaces/textual/widgets/__init__.py +0 -0
- ommlds/cli/sessions/chat/interfaces/textual/widgets/input.py +36 -0
- ommlds/cli/sessions/chat/interfaces/textual/widgets/messages.py +197 -0
- ommlds/cli/sessions/chat/session.py +8 -13
- ommlds/cli/sessions/completion/configs.py +3 -4
- ommlds/cli/sessions/completion/inject.py +1 -2
- ommlds/cli/sessions/completion/session.py +4 -8
- ommlds/cli/sessions/configs.py +10 -0
- ommlds/cli/sessions/embedding/configs.py +3 -4
- ommlds/cli/sessions/embedding/inject.py +1 -2
- ommlds/cli/sessions/embedding/session.py +4 -8
- ommlds/cli/sessions/inject.py +15 -15
- ommlds/cli/state/storage.py +7 -1
- ommlds/minichain/__init__.py +161 -38
- ommlds/minichain/_dataclasses.py +20452 -0
- ommlds/minichain/_typedvalues.py +11 -4
- ommlds/minichain/backends/impls/anthropic/names.py +3 -3
- ommlds/minichain/backends/impls/anthropic/protocol.py +2 -2
- ommlds/minichain/backends/impls/anthropic/stream.py +1 -1
- ommlds/minichain/backends/impls/cerebras/__init__.py +0 -0
- ommlds/minichain/backends/impls/cerebras/chat.py +80 -0
- ommlds/minichain/backends/impls/cerebras/names.py +45 -0
- ommlds/minichain/backends/impls/cerebras/protocol.py +143 -0
- ommlds/minichain/backends/impls/cerebras/stream.py +125 -0
- ommlds/minichain/backends/impls/duckduckgo/search.py +5 -1
- ommlds/minichain/backends/impls/google/names.py +6 -0
- ommlds/minichain/backends/impls/google/stream.py +1 -1
- ommlds/minichain/backends/impls/google/tools.py +2 -2
- ommlds/minichain/backends/impls/groq/chat.py +2 -0
- ommlds/minichain/backends/impls/groq/protocol.py +2 -2
- ommlds/minichain/backends/impls/groq/stream.py +3 -1
- ommlds/minichain/backends/impls/huggingface/repos.py +1 -5
- ommlds/minichain/backends/impls/llamacpp/chat.py +6 -3
- ommlds/minichain/backends/impls/llamacpp/completion.py +7 -3
- ommlds/minichain/backends/impls/llamacpp/stream.py +6 -3
- ommlds/minichain/backends/impls/mlx/chat.py +6 -3
- ommlds/minichain/backends/impls/ollama/chat.py +51 -57
- ommlds/minichain/backends/impls/ollama/protocol.py +144 -0
- ommlds/minichain/backends/impls/openai/format.py +4 -3
- ommlds/minichain/backends/impls/openai/names.py +3 -1
- ommlds/minichain/backends/impls/openai/stream.py +33 -1
- ommlds/minichain/backends/impls/sentencepiece/tokens.py +9 -6
- ommlds/minichain/backends/impls/tinygrad/chat.py +7 -4
- ommlds/minichain/backends/impls/tokenizers/tokens.py +9 -6
- ommlds/minichain/backends/impls/transformers/sentence.py +5 -2
- ommlds/minichain/backends/impls/transformers/tokens.py +9 -6
- ommlds/minichain/backends/impls/transformers/transformers.py +10 -8
- ommlds/minichain/backends/strings/resolving.py +1 -1
- ommlds/minichain/chat/content.py +42 -0
- ommlds/minichain/chat/messages.py +43 -39
- ommlds/minichain/chat/stream/joining.py +36 -12
- ommlds/minichain/chat/stream/types.py +1 -1
- ommlds/minichain/chat/templating.py +3 -3
- ommlds/minichain/content/__init__.py +19 -3
- ommlds/minichain/content/_marshal.py +181 -55
- ommlds/minichain/content/code.py +26 -0
- ommlds/minichain/content/composite.py +28 -0
- ommlds/minichain/content/content.py +27 -0
- ommlds/minichain/content/dynamic.py +12 -0
- ommlds/minichain/content/emphasis.py +27 -0
- ommlds/minichain/content/images.py +2 -2
- ommlds/minichain/content/json.py +2 -2
- ommlds/minichain/content/link.py +13 -0
- ommlds/minichain/content/markdown.py +12 -0
- ommlds/minichain/content/metadata.py +10 -0
- ommlds/minichain/content/namespaces.py +8 -0
- ommlds/minichain/content/placeholders.py +10 -9
- ommlds/minichain/content/quote.py +26 -0
- ommlds/minichain/content/raw.py +49 -0
- ommlds/minichain/content/recursive.py +12 -0
- ommlds/minichain/content/section.py +26 -0
- ommlds/minichain/content/sequence.py +17 -3
- ommlds/minichain/content/standard.py +32 -0
- ommlds/minichain/content/tag.py +28 -0
- ommlds/minichain/content/templates.py +13 -0
- ommlds/minichain/content/text.py +2 -2
- ommlds/minichain/content/transform/__init__.py +0 -0
- ommlds/minichain/content/transform/json.py +55 -0
- ommlds/minichain/content/transform/markdown.py +8 -0
- ommlds/minichain/content/transform/materialize.py +51 -0
- ommlds/minichain/content/transform/metadata.py +16 -0
- ommlds/minichain/content/{prepare.py → transform/prepare.py} +10 -15
- ommlds/minichain/content/transform/recursive.py +97 -0
- ommlds/minichain/content/transform/standard.py +43 -0
- ommlds/minichain/content/{transforms → transform}/stringify.py +1 -7
- ommlds/minichain/content/transform/strings.py +33 -0
- ommlds/minichain/content/transform/templates.py +25 -0
- ommlds/minichain/content/visitors.py +231 -0
- ommlds/minichain/lib/fs/tools/read.py +1 -1
- ommlds/minichain/lib/fs/tools/recursivels/rendering.py +1 -1
- ommlds/minichain/lib/fs/tools/recursivels/running.py +1 -1
- ommlds/minichain/lib/todo/tools/write.py +2 -1
- ommlds/minichain/lib/todo/types.py +1 -1
- ommlds/minichain/metadata.py +56 -2
- ommlds/minichain/resources.py +22 -1
- ommlds/minichain/services/README.md +154 -0
- ommlds/minichain/services/__init__.py +6 -2
- ommlds/minichain/services/_marshal.py +46 -10
- ommlds/minichain/services/_origclasses.py +11 -0
- ommlds/minichain/services/_typedvalues.py +8 -3
- ommlds/minichain/services/requests.py +73 -3
- ommlds/minichain/services/responses.py +73 -3
- ommlds/minichain/services/services.py +9 -0
- ommlds/minichain/stream/services.py +24 -1
- ommlds/minichain/text/applypatch.py +2 -1
- ommlds/minichain/text/toolparsing/llamacpp/types.py +1 -1
- ommlds/minichain/tokens/specials.py +1 -1
- ommlds/minichain/tools/execution/catalog.py +1 -1
- ommlds/minichain/tools/execution/errorhandling.py +36 -0
- ommlds/minichain/tools/execution/errors.py +2 -2
- ommlds/minichain/tools/execution/executors.py +1 -1
- ommlds/minichain/tools/fns.py +1 -1
- ommlds/minichain/tools/jsonschema.py +2 -2
- ommlds/minichain/tools/reflect.py +6 -6
- ommlds/minichain/tools/types.py +12 -15
- ommlds/minichain/vectors/_marshal.py +1 -1
- ommlds/minichain/vectors/embeddings.py +1 -1
- ommlds/minichain/wrappers/__init__.py +7 -0
- ommlds/minichain/wrappers/firstinwins.py +144 -0
- ommlds/minichain/wrappers/instrument.py +146 -0
- ommlds/minichain/wrappers/retry.py +168 -0
- ommlds/minichain/wrappers/services.py +98 -0
- ommlds/minichain/wrappers/stream.py +57 -0
- ommlds/nanochat/rustbpe/README.md +9 -0
- ommlds/nanochat/tokenizers.py +40 -6
- ommlds/specs/mcp/clients.py +146 -0
- ommlds/specs/mcp/protocol.py +123 -18
- ommlds/tools/git.py +82 -65
- {ommlds-0.0.0.dev480.dist-info → ommlds-0.0.0.dev503.dist-info}/METADATA +13 -11
- ommlds-0.0.0.dev503.dist-info/RECORD +520 -0
- ommlds/cli/sessions/chat/chat/state/inject.py +0 -36
- ommlds/cli/sessions/chat/chat/user/inject.py +0 -62
- ommlds/cli/sessions/chat/chat/user/interactive.py +0 -31
- ommlds/cli/sessions/chat/chat/user/oneshot.py +0 -25
- ommlds/cli/sessions/chat/chat/user/types.py +0 -15
- ommlds/cli/sessions/chat/driver.py +0 -43
- ommlds/minichain/content/materialize.py +0 -196
- ommlds/minichain/content/simple.py +0 -47
- ommlds/minichain/content/transforms/base.py +0 -46
- ommlds/minichain/content/transforms/interleave.py +0 -70
- ommlds/minichain/content/transforms/squeeze.py +0 -72
- ommlds/minichain/content/transforms/strings.py +0 -24
- ommlds/minichain/content/types.py +0 -43
- ommlds/minichain/stream/wrap.py +0 -62
- ommlds-0.0.0.dev480.dist-info/RECORD +0 -427
- /ommlds/cli/sessions/chat/{chat → drivers}/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{chat → drivers}/ai/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{chat → drivers}/ai/injection.py +0 -0
- /ommlds/cli/sessions/chat/{chat/state → drivers/events}/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{chat/user → drivers/phases}/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{phases → drivers/phases}/inject.py +0 -0
- /ommlds/cli/sessions/chat/{phases → drivers/phases}/injection.py +0 -0
- /ommlds/cli/sessions/chat/{phases → drivers/phases}/manager.py +0 -0
- /ommlds/cli/sessions/chat/{phases → drivers/phases}/types.py +0 -0
- /ommlds/cli/sessions/chat/{phases → drivers/state}/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{tools → drivers/tools}/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{tools → drivers/tools}/fs/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{tools → drivers/tools}/fs/configs.py +0 -0
- /ommlds/cli/sessions/chat/{tools → drivers/tools}/todo/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{tools → drivers/tools}/todo/configs.py +0 -0
- /ommlds/cli/sessions/chat/{tools → drivers/tools}/weather/__init__.py +0 -0
- /ommlds/cli/sessions/chat/{tools → drivers/tools}/weather/configs.py +0 -0
- /ommlds/cli/sessions/chat/{tools → drivers/tools}/weather/inject.py +0 -0
- /ommlds/{minichain/content/transforms → cli/sessions/chat/drivers/user}/__init__.py +0 -0
- {ommlds-0.0.0.dev480.dist-info → ommlds-0.0.0.dev503.dist-info}/WHEEL +0 -0
- {ommlds-0.0.0.dev480.dist-info → ommlds-0.0.0.dev503.dist-info}/entry_points.txt +0 -0
- {ommlds-0.0.0.dev480.dist-info → ommlds-0.0.0.dev503.dist-info}/licenses/LICENSE +0 -0
- {ommlds-0.0.0.dev480.dist-info → ommlds-0.0.0.dev503.dist-info}/top_level.txt +0 -0
|
@@ -23,7 +23,7 @@ from omlish import metadata as md
|
|
|
23
23
|
from omlish import reflect as rfl
|
|
24
24
|
from omlish.lite.cached import cached_nullary
|
|
25
25
|
|
|
26
|
-
from ..content.
|
|
26
|
+
from ..content.content import Content
|
|
27
27
|
from .types import EnumToolDtype
|
|
28
28
|
from .types import MappingToolDtype
|
|
29
29
|
from .types import NullableToolDtype
|
|
@@ -159,7 +159,7 @@ class ToolReflector:
|
|
|
159
159
|
|
|
160
160
|
#
|
|
161
161
|
|
|
162
|
-
def _prepare_desc(self, s:
|
|
162
|
+
def _prepare_desc(self, s: Content | None) -> Content | None:
|
|
163
163
|
if s is None:
|
|
164
164
|
return None
|
|
165
165
|
if not self._raw_descs and isinstance(s, str):
|
|
@@ -178,14 +178,14 @@ class ToolReflector:
|
|
|
178
178
|
for o in md.get_object_metadata(fn, type=_ToolSpecOverride):
|
|
179
179
|
ts_ovr.update({
|
|
180
180
|
k: v
|
|
181
|
-
for k, v in dc.
|
|
181
|
+
for k, v in dc.shallow_asdict(o).items()
|
|
182
182
|
if k != 'params'
|
|
183
183
|
and v is not None
|
|
184
184
|
})
|
|
185
185
|
for op in (o.params or []):
|
|
186
186
|
p_ovr_dct.setdefault(check.non_empty_str(op.name), {}).update({
|
|
187
187
|
k: v
|
|
188
|
-
for k, v in dc.
|
|
188
|
+
for k, v in dc.shallow_asdict(op).items()
|
|
189
189
|
if v is not None
|
|
190
190
|
})
|
|
191
191
|
|
|
@@ -240,7 +240,7 @@ class ToolReflector:
|
|
|
240
240
|
ds_p = ds_p_dct.get(pn)
|
|
241
241
|
sig_p = sig_p_dct.get(pn)
|
|
242
242
|
|
|
243
|
-
p_desc:
|
|
243
|
+
p_desc: Content | None
|
|
244
244
|
if (p_desc := ovr_p.get('desc')) is None:
|
|
245
245
|
if ds_p is not None:
|
|
246
246
|
p_desc = ds_p.description
|
|
@@ -286,7 +286,7 @@ class ToolReflector:
|
|
|
286
286
|
if md_tp is not None:
|
|
287
287
|
tp_kw.update({
|
|
288
288
|
k: v
|
|
289
|
-
for k, v in dc.
|
|
289
|
+
for k, v in dc.shallow_asdict(md_tp).items()
|
|
290
290
|
if v is not None
|
|
291
291
|
})
|
|
292
292
|
|
ommlds/minichain/tools/types.py
CHANGED
|
@@ -6,14 +6,11 @@ from omlish import cached
|
|
|
6
6
|
from omlish import check
|
|
7
7
|
from omlish import collections as col
|
|
8
8
|
from omlish import dataclasses as dc
|
|
9
|
-
from omlish import dispatch
|
|
10
9
|
from omlish import lang
|
|
11
10
|
from omlish import marshal as msh
|
|
12
11
|
from omlish import reflect as rfl
|
|
13
12
|
|
|
14
|
-
from ..content.
|
|
15
|
-
from ..content.transforms.base import ContentTransform
|
|
16
|
-
from ..content.types import Content
|
|
13
|
+
from ..content.content import Content
|
|
17
14
|
|
|
18
15
|
|
|
19
16
|
msh.register_global_module_import('._marshal', __package__)
|
|
@@ -150,7 +147,7 @@ class ToolParam:
|
|
|
150
147
|
|
|
151
148
|
_: dc.KW_ONLY
|
|
152
149
|
|
|
153
|
-
desc:
|
|
150
|
+
desc: Content | None = None
|
|
154
151
|
|
|
155
152
|
type: ToolDtype | None = None
|
|
156
153
|
|
|
@@ -168,12 +165,12 @@ class ToolSpec:
|
|
|
168
165
|
|
|
169
166
|
_: dc.KW_ONLY
|
|
170
167
|
|
|
171
|
-
desc:
|
|
168
|
+
desc: Content | None = None
|
|
172
169
|
|
|
173
170
|
params: ta.Sequence[ToolParam] | None = None
|
|
174
171
|
allow_additional_params: bool | None = None
|
|
175
172
|
|
|
176
|
-
returns_desc:
|
|
173
|
+
returns_desc: Content | None = None
|
|
177
174
|
returns_type: ToolDtype | None = None
|
|
178
175
|
|
|
179
176
|
@cached.property
|
|
@@ -207,11 +204,11 @@ class ToolUseResult(lang.Final):
|
|
|
207
204
|
##
|
|
208
205
|
|
|
209
206
|
|
|
210
|
-
class _ToolUseContentTransform(ContentTransform, lang.Final, lang.NotInstantiable):
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
207
|
+
# class _ToolUseContentTransform(ContentTransform, lang.Final, lang.NotInstantiable):
|
|
208
|
+
# @dispatch.install_method(ContentTransform.apply)
|
|
209
|
+
# def apply_tool_use(self, tu: ToolUse) -> ToolUse:
|
|
210
|
+
# return tu # TODO: args are Content
|
|
211
|
+
#
|
|
212
|
+
# @dispatch.install_method(ContentTransform.apply)
|
|
213
|
+
# def apply_tool_use_result(self, tur: ToolUseResult) -> ToolUseResult:
|
|
214
|
+
# return dc.replace(tur, c=self.apply(tur.c)) # noqa
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
TODO:
|
|
3
3
|
- serialize as base64 bytes? at least support deserializing as it
|
|
4
4
|
"""
|
|
5
|
-
import dataclasses as dc
|
|
6
5
|
import typing as ta
|
|
7
6
|
|
|
8
7
|
from omlish import check
|
|
8
|
+
from omlish import dataclasses as dc
|
|
9
9
|
from omlish import lang
|
|
10
10
|
from omlish import marshal as msh
|
|
11
11
|
from omlish import reflect as rfl
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- stream lol
|
|
4
|
+
- take first open vs take first chunk
|
|
5
|
+
"""
|
|
6
|
+
import typing as ta
|
|
7
|
+
|
|
8
|
+
from omlish import dataclasses as dc
|
|
9
|
+
from omlish import lang
|
|
10
|
+
|
|
11
|
+
from ..services.services import Service
|
|
12
|
+
from ..types import Output
|
|
13
|
+
from .services import MultiWrapperService
|
|
14
|
+
from .services import WrappedOptionT
|
|
15
|
+
from .services import WrappedOutputT
|
|
16
|
+
from .services import WrappedRequest
|
|
17
|
+
from .services import WrappedRequestV
|
|
18
|
+
from .services import WrappedResponse
|
|
19
|
+
from .services import WrappedResponseV
|
|
20
|
+
from .stream import WrappedStreamOutputT
|
|
21
|
+
from .stream import WrapperStreamService
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
with lang.auto_proxy_import(globals()):
|
|
25
|
+
import asyncio
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
AnyFirstInWinsService: ta.TypeAlias = ta.Union[
|
|
29
|
+
'FirstInWinsService',
|
|
30
|
+
'FirstInWinsStreamService',
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dc.dataclass()
|
|
38
|
+
class FirstInWinsServiceCancelledError(Exception):
|
|
39
|
+
e: BaseException
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class FirstInWinsServiceExceptionGroup(ExceptionGroup):
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dc.dataclass(frozen=True)
|
|
47
|
+
class FirstInWinsServiceOutput(Output):
|
|
48
|
+
first_in_wins_service: AnyFirstInWinsService
|
|
49
|
+
response_service: Service
|
|
50
|
+
service_exceptions: ta.Mapping[Service, Exception] | None = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class FirstInWinsService(
|
|
57
|
+
MultiWrapperService[
|
|
58
|
+
WrappedRequestV,
|
|
59
|
+
WrappedOptionT,
|
|
60
|
+
WrappedResponseV,
|
|
61
|
+
WrappedOutputT,
|
|
62
|
+
],
|
|
63
|
+
lang.Abstract,
|
|
64
|
+
):
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
##
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class FirstInWinsStreamService(
|
|
72
|
+
WrapperStreamService[
|
|
73
|
+
WrappedRequestV,
|
|
74
|
+
WrappedOptionT,
|
|
75
|
+
WrappedResponseV,
|
|
76
|
+
WrappedOutputT,
|
|
77
|
+
WrappedStreamOutputT,
|
|
78
|
+
],
|
|
79
|
+
lang.Abstract,
|
|
80
|
+
):
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
##
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class AsyncioFirstInWinsService(
|
|
88
|
+
FirstInWinsService[
|
|
89
|
+
WrappedRequestV,
|
|
90
|
+
WrappedOptionT,
|
|
91
|
+
WrappedResponseV,
|
|
92
|
+
WrappedOutputT,
|
|
93
|
+
],
|
|
94
|
+
):
|
|
95
|
+
async def invoke(self, request: WrappedRequest) -> WrappedResponse:
|
|
96
|
+
tasks: list = []
|
|
97
|
+
services_by_task: dict = {}
|
|
98
|
+
for svc in self._services:
|
|
99
|
+
task: asyncio.Task = asyncio.create_task(svc.invoke(request)) # type: ignore[arg-type]
|
|
100
|
+
tasks.append(task)
|
|
101
|
+
services_by_task[task] = svc
|
|
102
|
+
|
|
103
|
+
failures_by_service: dict[Service, Exception] = {}
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
pending = set(tasks)
|
|
107
|
+
|
|
108
|
+
while pending:
|
|
109
|
+
done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
|
|
110
|
+
|
|
111
|
+
for t in done:
|
|
112
|
+
svc = services_by_task[t]
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
response = t.result()
|
|
116
|
+
except asyncio.CancelledError as exc:
|
|
117
|
+
failures_by_service[svc] = FirstInWinsServiceCancelledError(exc)
|
|
118
|
+
continue
|
|
119
|
+
except Exception as exc: # noqa
|
|
120
|
+
failures_by_service[svc] = exc
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
for p in pending:
|
|
124
|
+
p.cancel()
|
|
125
|
+
|
|
126
|
+
await asyncio.gather(*pending, return_exceptions=True)
|
|
127
|
+
|
|
128
|
+
return response.with_outputs(FirstInWinsServiceOutput(
|
|
129
|
+
self,
|
|
130
|
+
svc,
|
|
131
|
+
failures_by_service,
|
|
132
|
+
))
|
|
133
|
+
|
|
134
|
+
raise FirstInWinsServiceExceptionGroup(
|
|
135
|
+
'All service calls failed',
|
|
136
|
+
list(failures_by_service.values()),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
finally:
|
|
140
|
+
for t in tasks:
|
|
141
|
+
if not t.done():
|
|
142
|
+
t.cancel()
|
|
143
|
+
|
|
144
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- final stream outputs
|
|
4
|
+
"""
|
|
5
|
+
import datetime
|
|
6
|
+
import typing as ta
|
|
7
|
+
|
|
8
|
+
from omlish import dataclasses as dc
|
|
9
|
+
from omlish import lang
|
|
10
|
+
|
|
11
|
+
from ..services.requests import Request
|
|
12
|
+
from ..services.responses import Response
|
|
13
|
+
from .services import WrappedOptionT
|
|
14
|
+
from .services import WrappedOutputT
|
|
15
|
+
from .services import WrappedRequest
|
|
16
|
+
from .services import WrappedRequestV
|
|
17
|
+
from .services import WrappedResponse
|
|
18
|
+
from .services import WrappedResponseV
|
|
19
|
+
from .services import WrappedService
|
|
20
|
+
from .services import WrapperService
|
|
21
|
+
from .stream import WrappedStreamOutputT
|
|
22
|
+
from .stream import WrappedStreamResponse
|
|
23
|
+
from .stream import WrappedStreamService
|
|
24
|
+
from .stream import WrapperStreamService
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
|
31
|
+
@dc.extra_class_params(default_repr_fn=lang.opt_or_just_repr)
|
|
32
|
+
class InstrumentedServiceEvent:
|
|
33
|
+
dt: datetime.datetime = dc.field(default_factory=lang.utcnow)
|
|
34
|
+
|
|
35
|
+
req: Request | None = None
|
|
36
|
+
|
|
37
|
+
resp: Response | None = None
|
|
38
|
+
exc: BaseException | None = None
|
|
39
|
+
|
|
40
|
+
stream_v: lang.Maybe[ta.Any] = lang.empty()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class InstrumentedServiceEventSink(ta.Protocol):
|
|
44
|
+
def __call__(self, ev: InstrumentedServiceEvent) -> ta.Awaitable[None]: ...
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ListInstrumentedServiceEventSink:
|
|
48
|
+
def __init__(self, lst: list[InstrumentedServiceEvent] | None = None) -> None:
|
|
49
|
+
super().__init__()
|
|
50
|
+
|
|
51
|
+
if lst is None:
|
|
52
|
+
lst = []
|
|
53
|
+
self._lst = lst
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def events(self) -> ta.Sequence[InstrumentedServiceEvent]:
|
|
57
|
+
return self._lst
|
|
58
|
+
|
|
59
|
+
async def __call__(self, ev: InstrumentedServiceEvent) -> None:
|
|
60
|
+
self._lst.append(ev)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class InstrumentedService(
|
|
67
|
+
WrapperService[
|
|
68
|
+
WrappedRequestV,
|
|
69
|
+
WrappedOptionT,
|
|
70
|
+
WrappedResponseV,
|
|
71
|
+
WrappedOutputT,
|
|
72
|
+
],
|
|
73
|
+
):
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
service: WrappedService,
|
|
77
|
+
sink: InstrumentedServiceEventSink | None = None,
|
|
78
|
+
) -> None:
|
|
79
|
+
super().__init__(service)
|
|
80
|
+
|
|
81
|
+
if sink is None:
|
|
82
|
+
sink = ListInstrumentedServiceEventSink()
|
|
83
|
+
self._sink = sink
|
|
84
|
+
|
|
85
|
+
async def invoke(self, request: WrappedRequest) -> WrappedResponse:
|
|
86
|
+
await self._sink(InstrumentedServiceEvent(req=request))
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
resp = await self._service.invoke(request)
|
|
90
|
+
|
|
91
|
+
except Exception as e: # noqa
|
|
92
|
+
await self._sink(InstrumentedServiceEvent(req=request, exc=e))
|
|
93
|
+
|
|
94
|
+
raise
|
|
95
|
+
|
|
96
|
+
await self._sink(InstrumentedServiceEvent(req=request, resp=resp))
|
|
97
|
+
|
|
98
|
+
return resp
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
##
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class InstrumentedStreamService(
|
|
105
|
+
WrapperStreamService[
|
|
106
|
+
WrappedRequestV,
|
|
107
|
+
WrappedOptionT,
|
|
108
|
+
WrappedResponseV,
|
|
109
|
+
WrappedOutputT,
|
|
110
|
+
WrappedStreamOutputT,
|
|
111
|
+
],
|
|
112
|
+
):
|
|
113
|
+
def __init__(
|
|
114
|
+
self,
|
|
115
|
+
service: WrappedStreamService,
|
|
116
|
+
sink: InstrumentedServiceEventSink | None = None,
|
|
117
|
+
) -> None:
|
|
118
|
+
super().__init__(service)
|
|
119
|
+
|
|
120
|
+
if sink is None:
|
|
121
|
+
sink = ListInstrumentedServiceEventSink()
|
|
122
|
+
self._sink = sink
|
|
123
|
+
|
|
124
|
+
async def invoke(self, request: WrappedRequest) -> WrappedStreamResponse:
|
|
125
|
+
await self._sink(InstrumentedServiceEvent(req=request))
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
resp = await self._service.invoke(request)
|
|
129
|
+
|
|
130
|
+
except Exception as e: # noqa
|
|
131
|
+
await self._sink(InstrumentedServiceEvent(req=request, exc=e))
|
|
132
|
+
|
|
133
|
+
raise
|
|
134
|
+
|
|
135
|
+
await self._sink(InstrumentedServiceEvent(req=request, resp=resp))
|
|
136
|
+
|
|
137
|
+
async def inner(sink): # noqa
|
|
138
|
+
async with resp.v as st:
|
|
139
|
+
async for v in st:
|
|
140
|
+
await self._sink(InstrumentedServiceEvent(req=request, resp=resp, stream_v=v))
|
|
141
|
+
|
|
142
|
+
await sink(v)
|
|
143
|
+
|
|
144
|
+
# return resp.with_v(inner())
|
|
145
|
+
|
|
146
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- tenacity shit
|
|
4
|
+
- exception filter - retryable vs not
|
|
5
|
+
- explicit RetryableError wrapper?
|
|
6
|
+
- sleep
|
|
7
|
+
- jitter
|
|
8
|
+
- log, on retry / on except callbacks, blah blah
|
|
9
|
+
- stream retry:
|
|
10
|
+
- failed to open
|
|
11
|
+
- failed during stream
|
|
12
|
+
- buffer and replay??
|
|
13
|
+
- accept death mid-stream?
|
|
14
|
+
- ** probably **: cannot sanely impose any nontrivial stream retry strat at this layer -
|
|
15
|
+
"""
|
|
16
|
+
import typing as ta
|
|
17
|
+
|
|
18
|
+
from omlish import dataclasses as dc
|
|
19
|
+
|
|
20
|
+
from ..resources import Resources
|
|
21
|
+
from ..stream.services import StreamResponseSink
|
|
22
|
+
from ..stream.services import new_stream_response
|
|
23
|
+
from ..types import Output
|
|
24
|
+
from .services import WrappedOptionT
|
|
25
|
+
from .services import WrappedOutputT
|
|
26
|
+
from .services import WrappedRequest
|
|
27
|
+
from .services import WrappedRequestV
|
|
28
|
+
from .services import WrappedResponse
|
|
29
|
+
from .services import WrappedResponseV
|
|
30
|
+
from .services import WrappedService
|
|
31
|
+
from .services import WrapperService
|
|
32
|
+
from .stream import WrappedStreamOutputT
|
|
33
|
+
from .stream import WrappedStreamResponse
|
|
34
|
+
from .stream import WrappedStreamService
|
|
35
|
+
from .stream import WrapperStreamService
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
AnyRetryService: ta.TypeAlias = ta.Union[
|
|
39
|
+
'RetryService',
|
|
40
|
+
'RetryStreamService',
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dc.dataclass()
|
|
48
|
+
class RetryServiceMaxRetriesExceededError(Exception):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dc.dataclass(frozen=True)
|
|
53
|
+
class RetryServiceOutput(Output):
|
|
54
|
+
retry_service: AnyRetryService
|
|
55
|
+
num_retries: int
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
##
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class RetryService(
|
|
62
|
+
WrapperService[
|
|
63
|
+
WrappedRequestV,
|
|
64
|
+
WrappedOptionT,
|
|
65
|
+
WrappedResponseV,
|
|
66
|
+
WrappedOutputT,
|
|
67
|
+
],
|
|
68
|
+
):
|
|
69
|
+
DEFAULT_MAX_RETRIES: ta.ClassVar[int] = 3
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
service: WrappedService,
|
|
74
|
+
*,
|
|
75
|
+
max_retries: int | None = None,
|
|
76
|
+
) -> None:
|
|
77
|
+
super().__init__(service)
|
|
78
|
+
|
|
79
|
+
if max_retries is None:
|
|
80
|
+
max_retries = self.DEFAULT_MAX_RETRIES
|
|
81
|
+
self._max_retries = max_retries
|
|
82
|
+
|
|
83
|
+
async def invoke(self, request: WrappedRequest) -> WrappedResponse:
|
|
84
|
+
n = 0
|
|
85
|
+
|
|
86
|
+
while True:
|
|
87
|
+
try:
|
|
88
|
+
resp = await self._service.invoke(request)
|
|
89
|
+
|
|
90
|
+
except Exception as e: # noqa
|
|
91
|
+
if n < self._max_retries:
|
|
92
|
+
n += 1
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
raise RetryServiceMaxRetriesExceededError from e
|
|
96
|
+
|
|
97
|
+
return resp.with_outputs(RetryServiceOutput(
|
|
98
|
+
retry_service=self,
|
|
99
|
+
num_retries=n,
|
|
100
|
+
))
|
|
101
|
+
|
|
102
|
+
raise RuntimeError # unreachable
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
##
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class RetryStreamService(
|
|
109
|
+
WrapperStreamService[
|
|
110
|
+
WrappedRequestV,
|
|
111
|
+
WrappedOptionT,
|
|
112
|
+
WrappedResponseV,
|
|
113
|
+
WrappedOutputT,
|
|
114
|
+
WrappedStreamOutputT,
|
|
115
|
+
],
|
|
116
|
+
):
|
|
117
|
+
DEFAULT_MAX_RETRIES: ta.ClassVar[int] = 3
|
|
118
|
+
|
|
119
|
+
def __init__(
|
|
120
|
+
self,
|
|
121
|
+
service: WrappedStreamService,
|
|
122
|
+
*,
|
|
123
|
+
max_retries: int | None = None,
|
|
124
|
+
) -> None:
|
|
125
|
+
super().__init__(service)
|
|
126
|
+
|
|
127
|
+
if max_retries is None:
|
|
128
|
+
max_retries = self.DEFAULT_MAX_RETRIES
|
|
129
|
+
self._max_retries = max_retries
|
|
130
|
+
|
|
131
|
+
async def invoke(self, request: WrappedRequest) -> WrappedStreamResponse:
|
|
132
|
+
n = 0
|
|
133
|
+
|
|
134
|
+
while True:
|
|
135
|
+
try:
|
|
136
|
+
async with Resources.new() as rs:
|
|
137
|
+
in_resp = await self._service.invoke(request)
|
|
138
|
+
in_vs = await rs.enter_async_context(in_resp.v)
|
|
139
|
+
|
|
140
|
+
async def inner(sink: StreamResponseSink[WrappedResponseV]) -> ta.Sequence[WrappedOutputT] | None:
|
|
141
|
+
async for v in in_vs:
|
|
142
|
+
await sink.emit(v)
|
|
143
|
+
|
|
144
|
+
return in_vs.outputs
|
|
145
|
+
|
|
146
|
+
outs = [
|
|
147
|
+
*in_resp.outputs,
|
|
148
|
+
RetryServiceOutput(
|
|
149
|
+
retry_service=self,
|
|
150
|
+
num_retries=n,
|
|
151
|
+
),
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
# FIXME: ??
|
|
155
|
+
# if (ur := tv.as_collection(request.options).get(UseResources)) is not None:
|
|
156
|
+
|
|
157
|
+
return await new_stream_response(
|
|
158
|
+
rs,
|
|
159
|
+
inner,
|
|
160
|
+
outs,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
except Exception as e: # noqa
|
|
164
|
+
if n < self._max_retries:
|
|
165
|
+
n += 1
|
|
166
|
+
continue
|
|
167
|
+
|
|
168
|
+
raise RetryServiceMaxRetriesExceededError from e
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
|
|
3
|
+
from omlish import check
|
|
4
|
+
from omlish import lang
|
|
5
|
+
|
|
6
|
+
from ..services.requests import Request
|
|
7
|
+
from ..services.responses import Response
|
|
8
|
+
from ..services.services import Service
|
|
9
|
+
from ..types import Option
|
|
10
|
+
from ..types import Output
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
P = ta.ParamSpec('P')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
WrappedRequestV = ta.TypeVar('WrappedRequestV')
|
|
17
|
+
WrappedOptionT = ta.TypeVar('WrappedOptionT', bound=Option)
|
|
18
|
+
|
|
19
|
+
WrappedResponseV = ta.TypeVar('WrappedResponseV')
|
|
20
|
+
WrappedOutputT = ta.TypeVar('WrappedOutputT', bound=Output)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
WrappedRequest: ta.TypeAlias = Request[
|
|
24
|
+
WrappedRequestV,
|
|
25
|
+
WrappedOptionT,
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
WrappedResponse: ta.TypeAlias = Response[
|
|
29
|
+
WrappedResponseV,
|
|
30
|
+
WrappedOutputT,
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
WrappedService: ta.TypeAlias = Service[
|
|
34
|
+
WrappedRequest,
|
|
35
|
+
WrappedResponse,
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
##
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class WrapperService(
|
|
43
|
+
lang.Abstract,
|
|
44
|
+
ta.Generic[
|
|
45
|
+
WrappedRequestV,
|
|
46
|
+
WrappedOptionT,
|
|
47
|
+
WrappedResponseV,
|
|
48
|
+
WrappedOutputT,
|
|
49
|
+
],
|
|
50
|
+
):
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
service: WrappedService,
|
|
54
|
+
) -> None:
|
|
55
|
+
super().__init__()
|
|
56
|
+
|
|
57
|
+
self._service = service
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class MultiWrapperService(
|
|
61
|
+
lang.Abstract,
|
|
62
|
+
ta.Generic[
|
|
63
|
+
WrappedRequestV,
|
|
64
|
+
WrappedOptionT,
|
|
65
|
+
WrappedResponseV,
|
|
66
|
+
WrappedOutputT,
|
|
67
|
+
],
|
|
68
|
+
):
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
*services: WrappedService,
|
|
72
|
+
) -> None:
|
|
73
|
+
super().__init__()
|
|
74
|
+
|
|
75
|
+
self._services = check.not_empty(services)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def wrap_service(
|
|
82
|
+
wrapped: WrappedService,
|
|
83
|
+
wrapper: ta.Callable[
|
|
84
|
+
ta.Concatenate[
|
|
85
|
+
WrappedService,
|
|
86
|
+
P,
|
|
87
|
+
],
|
|
88
|
+
WrapperService[
|
|
89
|
+
WrappedRequestV,
|
|
90
|
+
WrappedOptionT,
|
|
91
|
+
WrappedResponseV,
|
|
92
|
+
WrappedOutputT,
|
|
93
|
+
],
|
|
94
|
+
],
|
|
95
|
+
*args: P.args,
|
|
96
|
+
**kwargs: P.kwargs,
|
|
97
|
+
) -> WrappedService:
|
|
98
|
+
return ta.cast(ta.Any, wrapper(wrapped, *args, **kwargs))
|