flyteplugins-agents-core 2.5.9__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.
@@ -0,0 +1,42 @@
1
+ """flyteplugins-agents-core — the shared contract every agent-SDK adapter implements.
2
+
3
+ This package holds the SDK-agnostic machinery that makes Flyte the durable
4
+ runtime under any agent framework, so each ``flyteplugins-agents-<sdk>`` adapter
5
+ stays thin and consistent:
6
+
7
+ - :func:`durable_step` — record a call as a durable, replayable ``flyte.trace``
8
+ leaf (the model-turn durability mechanism), keyed by a fingerprint.
9
+ - :func:`fingerprint` — a deterministic memo key from a request payload.
10
+ - :class:`ToolTaskResolver` / :func:`attach_tool_resolver` — make a tool-backing
11
+ task resolve to itself on the worker.
12
+ - :class:`ReportTimeline` — render agent events into the Flyte task report.
13
+ - :mod:`flyteplugins.agents.core.testing` — :func:`assert_adapter_conforms`, the
14
+ CI-enforced conformance check every adapter runs.
15
+
16
+ The division of labor every adapter follows: the agent run is a Flyte
17
+ ``@env.task`` (durable parent), each model turn is a ``flyte.trace`` (a
18
+ memoized, replayable leaf), and each tool is a Flyte task invoked as a
19
+ durable child action.
20
+ """
21
+
22
+ from ._durable import durable_step
23
+ from ._fingerprint import fingerprint, jsonable
24
+ from ._memory import resolve_memory
25
+ from ._observability import ReportTimeline, abbrev, duration_ms, flush_report
26
+ from ._tools import ToolTaskResolver, attach_tool_resolver, coerce_tool_args, task_json_schema, tool
27
+
28
+ __all__ = [
29
+ "ReportTimeline",
30
+ "ToolTaskResolver",
31
+ "abbrev",
32
+ "attach_tool_resolver",
33
+ "coerce_tool_args",
34
+ "durable_step",
35
+ "duration_ms",
36
+ "fingerprint",
37
+ "flush_report",
38
+ "jsonable",
39
+ "resolve_memory",
40
+ "task_json_schema",
41
+ "tool",
42
+ ]
@@ -0,0 +1,54 @@
1
+ """Durable, replayable steps via ``flyte.trace`` — the shared mechanism.
2
+
3
+ A model turn (or any expensive, replay-worthy call) is recorded as a
4
+ ``flyte.trace`` leaf so that, inside a Flyte task, a crash/retry replays the
5
+ recorded result instead of re-running it. The real call is usually
6
+ non-serializable (closures, SDK objects), but ``flyte.trace`` keys its memo on
7
+ the decorated function's serializable arguments. :func:`durable_step` resolves
8
+ that by capturing the real call in a closure and feeding the trace only a
9
+ deterministic ``request_key``.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import typing
15
+
16
+ import flyte
17
+
18
+ T = typing.TypeVar("T")
19
+
20
+
21
+ async def durable_step(
22
+ request_key: str,
23
+ run: typing.Callable[[], typing.Awaitable[typing.Any]],
24
+ *,
25
+ name: str = "durable_step",
26
+ dumps: typing.Callable[[typing.Any], str] = lambda v: v,
27
+ loads: typing.Callable[[str], typing.Any] = lambda v: v,
28
+ ) -> typing.Any:
29
+ """Run ``run()`` once as a durable, replayable trace step keyed by ``request_key``.
30
+
31
+ The real (possibly non-serializable) work is captured in the ``run`` closure,
32
+ so the traced function only ever sees the serializable ``request_key``. The
33
+ result is serialized with ``dumps`` for the trace record (a ``str`` is stored
34
+ inline and is human-readable in the Flyte UI) and rebuilt with ``loads`` on
35
+ the way out and on replay.
36
+
37
+ Args:
38
+ request_key: A deterministic fingerprint of the call; the trace memo key.
39
+ run: Zero-arg async callable performing the real work.
40
+ name: Label for the trace action in the Flyte UI.
41
+ dumps: Serialize the result to a ``str`` for the trace record.
42
+ loads: Rebuild the result from the recorded ``str``.
43
+
44
+ Outside a task context ``flyte.trace`` is a transparent pass-through, so this
45
+ also works unchanged for local runs and unit tests.
46
+ """
47
+
48
+ async def _step(key: str) -> str:
49
+ return dumps(await run())
50
+
51
+ # Name the closure so the trace action reads meaningfully in the UI.
52
+ _step.__name__ = name
53
+ _step.__qualname__ = name
54
+ return loads(await flyte.trace(_step)(request_key))
@@ -0,0 +1,33 @@
1
+ """Deterministic request fingerprints for keying durable steps."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import typing
8
+
9
+
10
+ def fingerprint(payload: typing.Mapping[str, typing.Any]) -> str:
11
+ """A deterministic sha256 hex of a request ``payload``.
12
+
13
+ Must be a pure function of the semantic request so replays line up. Pass a
14
+ mapping of the request's identifying fields (e.g. system prompt, input
15
+ items, model, tool names) — not callables or live objects. Anything not
16
+ natively JSON-serializable is coerced with ``str``.
17
+ """
18
+ blob = json.dumps(payload, sort_keys=True, default=str)
19
+ return hashlib.sha256(blob.encode("utf-8")).hexdigest()
20
+
21
+
22
+ def jsonable(obj: typing.Any) -> typing.Any:
23
+ """Best-effort conversion of an SDK object to something JSON-dumpable."""
24
+ if obj is None or isinstance(obj, (str, int, float, bool)):
25
+ return obj
26
+ for attr in ("to_json_dict", "model_dump"):
27
+ method = getattr(obj, attr, None)
28
+ if callable(method):
29
+ try:
30
+ return method()
31
+ except Exception: # pragma: no cover - defensive
32
+ pass
33
+ return str(obj)
@@ -0,0 +1,54 @@
1
+ """Cross-run agent memory — a thin handle over Flyte's keyed ``MemoryStore``.
2
+
3
+ "Agent memory" = durable state addressable by a stable ``memory_key`` (a user or
4
+ thread id), shared across runs and workers. We don't reinvent it: Flyte already
5
+ ships a keyed, blob-store-backed store (``flyte.ai.agents.memory.MemoryStore``)
6
+ that resolves a deterministic remote path from the key and the active run context
7
+ — stripping the per-run scratch so two runs with the same key share one store,
8
+ and encapsulating all the storage details.
9
+
10
+ It is decoupled from the agent loop - importing it does not pull in any agent
11
+ harness — so adapters use it purely as a durable store. Each adapter then maps its
12
+ SDK's own conversation state onto the returned store.
13
+
14
+ The store carries two complementary surfaces, so one ``memory_key`` covers both
15
+ kinds of memory:
16
+
17
+ - ``messages`` (``append``/``extend``) — the conversation transcript;
18
+ - path-addressed ``read_json``/``write_json``/``list_paths`` — durable named facts
19
+ (the ``remember`` / ``recall`` substrate), with audit + version history.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import typing
25
+
26
+ from flyte._logging import logger
27
+
28
+ if typing.TYPE_CHECKING:
29
+ from flyte.ai.agents.memory import MemoryStore
30
+
31
+
32
+ async def resolve_memory(memory_key: str | None, *, audit: bool = True) -> "MemoryStore | None":
33
+ """Open (or create) the keyed agent-memory store, or ``None`` if unavailable.
34
+
35
+ Best-effort: returns ``None`` (and logs) when ``memory_key`` is falsy or no
36
+ durable store can be resolved — e.g. no Flyte context/org, or an invalid key —
37
+ so memory never breaks a run. Call from inside an ``@env.task``; the store's
38
+ remote path is derived from the run context and is stable across runs for the
39
+ same key.
40
+
41
+ Args:
42
+ memory_key: A stable single-segment id (a user/thread id). ``None`` or
43
+ empty disables memory.
44
+ audit: Keep the store's append-only audit log (the ``MemoryStore`` default).
45
+ """
46
+ if not memory_key:
47
+ return None
48
+ try:
49
+ from flyte.ai.agents.memory import MemoryStore
50
+
51
+ return await MemoryStore.get_or_create.aio(key=memory_key, audit=audit)
52
+ except Exception: # pragma: no cover - memory is best-effort, never fatal
53
+ logger.warning("Could not open agent memory for key %r; continuing without memory.", memory_key)
54
+ return None
@@ -0,0 +1,35 @@
1
+ """Shared report timeline — render agent events into the Flyte task report.
2
+
3
+ Each adapter maps its own SDK's trace/span/event shape onto :class:`ReportTimeline`
4
+ rows; the row formatting, abbreviation, and best-effort error handling now live in
5
+ ``flyte.report`` (:class:`flyte.report.Timeline`), so the native ``flyte.ai.agents``
6
+ loop and every adapter render through one implementation. This module keeps the
7
+ adapter-facing names (``ReportTimeline`` defaulting to the ``Agent`` tab, plus the
8
+ ``abbrev``/``duration_ms``/``flush_report`` helpers) stable.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import flyte.report
14
+ from flyte.report import Timeline, duration_ms
15
+ from flyte.report import abbreviate as abbrev
16
+
17
+ __all__ = ["ReportTimeline", "abbrev", "duration_ms", "flush_report"]
18
+
19
+
20
+ class ReportTimeline(Timeline):
21
+ """A :class:`flyte.report.Timeline` that defaults to the ``Agent`` report tab."""
22
+
23
+ def __init__(self, tab_name: str = "Agent"):
24
+ super().__init__(tab_name)
25
+
26
+
27
+ async def flush_report() -> None:
28
+ """Flush the active Flyte report — a best-effort no-op when there is none.
29
+
30
+ Adapters call this once after a run so the rendered timeline is published.
31
+ """
32
+ try:
33
+ await flyte.report.flush()
34
+ except Exception: # pragma: no cover - no active report / local run
35
+ pass
@@ -0,0 +1,181 @@
1
+ """Make a tool-backing Flyte task resolve to itself on the worker.
2
+
3
+ Every adapter stacks its ``tool`` on top of ``@env.task``, which rebinds
4
+ the module attribute to the tool and shadows the task. Without a guard, the
5
+ worker's default resolver loads the tool, the task runner calls the tool's
6
+ ``execute``, and the task re-dispatches itself — recursing without end.
7
+
8
+ :class:`ToolTaskResolver` recovers the real task via the tool's
9
+ ``__wrapped_task__`` hook; :func:`attach_tool_resolver` wires it onto the task.
10
+ Each adapter's tool object only has to expose ``__wrapped_task__`` returning its
11
+ backing :class:`~flyte._task.TaskTemplate`.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import functools
17
+ import typing
18
+ from functools import partial
19
+
20
+ from flyte._internal.resolvers.default import DefaultTaskResolver
21
+ from flyte._task import AsyncFunctionTaskTemplate, TaskTemplate
22
+
23
+
24
+ class ToolTaskResolver(DefaultTaskResolver):
25
+ """Resolver for a task shadowed at module scope by a tool wrapper.
26
+
27
+ Recovers the underlying task via the wrapper's ``__wrapped_task__`` hook so
28
+ the worker runs the task's own body instead of re-dispatching the tool.
29
+ """
30
+
31
+ @property
32
+ def import_path(self) -> str:
33
+ return "flyteplugins.agents.core._tools.ToolTaskResolver"
34
+
35
+ def load_task(self, loader_args): # type: ignore[override]
36
+ task_def = super().load_task(loader_args)
37
+ if isinstance(task_def, TaskTemplate):
38
+ return task_def
39
+ unwrapped = getattr(task_def, "__wrapped_task__", None)
40
+ if isinstance(unwrapped, TaskTemplate):
41
+ return unwrapped
42
+ return task_def
43
+
44
+
45
+ def attach_tool_resolver(task: typing.Any) -> None:
46
+ """Point a tool-backing ``@env.task`` at :class:`ToolTaskResolver`.
47
+
48
+ No-op for anything that isn't an async-function task or that already declares
49
+ a custom resolver, so the default resolver is left untouched elsewhere.
50
+ """
51
+ if isinstance(task, AsyncFunctionTaskTemplate) and task.task_resolver is None:
52
+ task.task_resolver = ToolTaskResolver()
53
+
54
+
55
+ def coerce_tool_args(task: AsyncFunctionTaskTemplate, kwargs: dict[str, typing.Any]) -> dict[str, typing.Any]:
56
+ """Coerce LLM-supplied tool args to the task's declared parameter types.
57
+
58
+ LLMs emit JSON numbers without a fractional part as ``int`` (e.g. ``42``), but
59
+ Flyte's type engine rejects an ``int`` where a ``float`` is declared — so the tool
60
+ call fails during input conversion, before the child action is even submitted (the
61
+ action never appears in the UI, and the task body never runs). This converts ``int``
62
+ -> ``float`` for float-annotated params (leaving ``bool`` alone). Best-effort: returns
63
+ the kwargs unchanged if the task's annotations can't be resolved.
64
+ """
65
+ try:
66
+ hints = typing.get_type_hints(task.func)
67
+ except Exception: # pragma: no cover - unresolved annotations; pass through
68
+ return kwargs
69
+ coerced = dict(kwargs)
70
+ for name, value in kwargs.items():
71
+ if hints.get(name) is float and isinstance(value, int) and not isinstance(value, bool):
72
+ coerced[name] = float(value)
73
+ return coerced
74
+
75
+
76
+ def tool(
77
+ func: AsyncFunctionTaskTemplate | typing.Callable | None = None,
78
+ *,
79
+ name: str | None = None,
80
+ description: str | None = None,
81
+ ) -> typing.Callable:
82
+ """Wrap a Flyte ``@env.task`` as a plain async tool function — the generic default.
83
+
84
+ For SDKs that accept plain Python callables as tools (deriving the schema from the
85
+ signature + docstring), this is the whole adapter ``tool``: the returned
86
+ function carries the task's signature (``functools.wraps``), dispatches to
87
+ ``task.aio()`` (so each call is a durable Flyte child action), exposes
88
+ ``__wrapped_task__``, and wires the backing task to :class:`ToolTaskResolver`.
89
+ Adapters whose SDK needs a native tool type (e.g. OpenAI's
90
+ ``FunctionTool``, Claude's MCP ``SdkMcpTool``) provide their own instead.
91
+
92
+ Also accepts any other callable — a plain function or an instance of a callable
93
+ class defining ``__call__`` — and returns it usable as a tool as-is, since the
94
+ plain-callable SDKs derive the schema by inspecting the callable (a class instance
95
+ is inspected through its ``__call__``). A ``name`` or ``description`` override is
96
+ applied to the callable best-effort.
97
+
98
+ Usable bare, parametrized or as a direct call::
99
+
100
+ @tool
101
+ @env.task
102
+ async def get_weather(city: str) -> str: ...
103
+ """
104
+
105
+ if func is None:
106
+ return partial(tool, name=name, description=description)
107
+ if isinstance(func, AsyncFunctionTaskTemplate):
108
+ return _task_to_tool(func, name=name, description=description)
109
+ if not callable(func):
110
+ raise TypeError(f"tool() expects a Flyte @env.task or a callable, got {type(func).__name__!r}.")
111
+ # A plain function or a callable class instance is already usable as a tool as-is.
112
+ return _relabel_callable(func, name=name, description=description)
113
+
114
+
115
+ def _relabel_callable(
116
+ func: typing.Callable,
117
+ *,
118
+ name: str | None = None,
119
+ description: str | None = None,
120
+ ) -> typing.Callable:
121
+ """Apply ``name`` / ``description`` overrides to a callable in place, best-effort.
122
+
123
+ Functions accept ``__name__`` / ``__doc__`` assignment; a callable class instance
124
+ may reject it (e.g. ``__slots__``), in which case the override is silently skipped —
125
+ the callable is still returned and usable as a tool.
126
+ """
127
+ if name:
128
+ try:
129
+ func.__name__ = name # type: ignore[attr-defined]
130
+ except (
131
+ AttributeError,
132
+ TypeError,
133
+ ): # pragma: no cover - slotted/immutable callable
134
+ pass
135
+ if description:
136
+ try:
137
+ func.__doc__ = description
138
+ except (
139
+ AttributeError,
140
+ TypeError,
141
+ ): # pragma: no cover - slotted/immutable callable
142
+ pass
143
+ return func
144
+
145
+
146
+ def _task_to_tool(
147
+ task: AsyncFunctionTaskTemplate,
148
+ *,
149
+ name: str | None = None,
150
+ description: str | None = None,
151
+ ) -> typing.Callable:
152
+ inner = task.func
153
+
154
+ @functools.wraps(inner)
155
+ async def tool(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
156
+ # In a Flyte task context this submits a durable child action; locally it runs
157
+ # inline. ``functools.wraps`` keeps ``inner``'s signature so the SDK builds the
158
+ # right tool declaration; ``coerce_tool_args`` relaxes LLM int->float args.
159
+ return await task.aio(*args, **coerce_tool_args(task, kwargs))
160
+
161
+ if name:
162
+ tool.__name__ = name
163
+ if description:
164
+ tool.__doc__ = description
165
+
166
+ # The wrapper shadows the task at module scope; expose the real task and wire the
167
+ # shared resolver so it resolves to itself on the worker (no recursion).
168
+ tool.__wrapped_task__ = task # type: ignore[attr-defined]
169
+ tool.task = task # type: ignore[attr-defined]
170
+ attach_tool_resolver(task)
171
+ return tool
172
+
173
+
174
+ def task_json_schema(task: AsyncFunctionTaskTemplate) -> dict[str, typing.Any]:
175
+ """The JSON schema of a Flyte task's inputs, via the Flyte type engine.
176
+
177
+ Useful for adapters whose SDK wants a JSON-schema tool definition and that
178
+ prefer Flyte's type-engine schema (correct ``Literal`` enums, ``File``/``Dir``
179
+ /``DataFrame``, dataclasses) over the SDK's own signature inspection.
180
+ """
181
+ return task.json_schema
@@ -0,0 +1,65 @@
1
+ """Conformance harness — enforce the common adapter format.
2
+
3
+ Every ``flyteplugins.agents.<sdk>`` adapter ships a one-line test::
4
+
5
+ from flyteplugins.agents.core.testing import assert_adapter_conforms
6
+ import flyteplugins.agents.openai as adapter
7
+
8
+ def test_conformance():
9
+ assert_adapter_conforms(adapter)
10
+
11
+ CI then fails if an adapter drifts from the shared format.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import inspect
17
+ import typing
18
+
19
+ # The keyword surface every ``run_agent`` must accept, so the call shape is the
20
+ # same across SDKs.
21
+ REQUIRED_RUN_AGENT_PARAMS = ("tools", "model", "instructions", "durable", "observability", "memory_key")
22
+
23
+
24
+ def assert_adapter_conforms(adapter: typing.Any) -> None:
25
+ """Assert that an adapter module implements the common agent-adapter contract.
26
+
27
+ The contract:
28
+
29
+ 1. exports a callable ``tool`` that turns an ``@env.task`` into the
30
+ SDK's tool type, attaching :class:`~flyteplugins.agents.core.ToolTaskResolver`
31
+ and exposing ``__wrapped_task__`` (so the task does not self-recurse on the
32
+ worker);
33
+ 2. exports an async ``run_agent`` accepting the standard keyword surface.
34
+
35
+ Raises ``AssertionError`` with a specific message on any deviation.
36
+ """
37
+ import flyte
38
+
39
+ from flyteplugins.agents.core import ToolTaskResolver
40
+
41
+ name = getattr(adapter, "__name__", repr(adapter))
42
+
43
+ tool = getattr(adapter, "tool", None)
44
+ assert callable(tool), f"{name}: must export a callable `tool`"
45
+
46
+ run_agent = getattr(adapter, "run_agent", None)
47
+ assert inspect.iscoroutinefunction(run_agent), f"{name}: must export an async `run_agent`"
48
+ params = inspect.signature(run_agent).parameters
49
+ for required in REQUIRED_RUN_AGENT_PARAMS:
50
+ assert required in params, f"{name}: `run_agent` must accept `{required}`"
51
+
52
+ # tool on a task must expose the real task and wire the resolver.
53
+ env = flyte.TaskEnvironment("agents-core-conformance")
54
+
55
+ @tool
56
+ @env.task
57
+ def _sample(city: str) -> str:
58
+ """A sample tool task."""
59
+ return city
60
+
61
+ wrapped = getattr(_sample, "__wrapped_task__", None)
62
+ assert wrapped is not None, f"{name}: `tool` result must expose `__wrapped_task__`"
63
+ assert isinstance(getattr(wrapped, "task_resolver", None), ToolTaskResolver), (
64
+ f"{name}: the tool's backing task must use ToolTaskResolver (or it self-recurses on the worker)"
65
+ )
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: flyteplugins-agents-core
3
+ Version: 2.5.9
4
+ Summary: Shared contract for Flyte agent-SDK adapters.
5
+ Author-email: Samhita Alla <samhita@union.ai>
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: flyte
9
+
10
+ # flyteplugins-agents-core
11
+
12
+ The shared contract every Flyte agent-SDK adapter is built on, so they all
13
+ follow one common format.
14
+
15
+ It is SDK-agnostic — no agent SDK is a dependency here. Adapters depend on it and
16
+ implement the contract; `assert_adapter_conforms` enforces that in CI.
17
+
18
+ ## What's in it
19
+
20
+ - `durable_step(request_key, run, *, name, dumps, loads)` — record a call as a
21
+ durable, replayable `flyte.trace` leaf (the model-turn durability mechanism).
22
+ The real, non-serializable call is captured in the `run` closure, so the trace
23
+ only ever sees the serializable `request_key`.
24
+ - `fingerprint(payload)` — a deterministic memo key from a request payload.
25
+ - `ToolTaskResolver` / `attach_tool_resolver(task)` — make a tool-backing task
26
+ resolve to itself on the worker (via the tool's `__wrapped_task__` hook)
27
+ instead of re-dispatching, which would otherwise recurse indefinitely.
28
+ - `ReportTimeline` — render agent events into a tab of the Flyte task report.
29
+ - `flyteplugins.agents.core.testing.assert_adapter_conforms(module)` — the
30
+ conformance check every adapter runs as a one-line test.
31
+
32
+ ## The rule every adapter follows
33
+
34
+ The agent run is a Flyte `@env.task` (the durable parent); each model turn
35
+ is a `flyte.trace` (a memoized, replayable leaf); each tool is a Flyte task
36
+ invoked as a durable child action.
37
+
38
+ ## Writing an adapter
39
+
40
+ A `flyteplugins-agents-<sdk>` package depends on this core and exposes, at
41
+ minimum, `tool` and `run_agent` (see the conformance contract). Its tool
42
+ object exposes `__wrapped_task__`; `run_agent` runs the SDK's loop inside the
43
+ calling `@env.task`, wraps the model in a durable provider built on `durable_step`,
44
+ and renders the trace via `ReportTimeline`.
@@ -0,0 +1,11 @@
1
+ flyteplugins/agents/core/__init__.py,sha256=5ZPSn5d2i4L66u8UjcMeOjwX9WZu95Gg7CYoEMcums4,1654
2
+ flyteplugins/agents/core/_durable.py,sha256=Pxh0S1uju4EhfbO7KwMwZmdr-tsRpF3Uhrj_MfmO-fo,2128
3
+ flyteplugins/agents/core/_fingerprint.py,sha256=Rg3f9YkPLYaaeGeVyRN6-8IsRwkplqsFAjTqxF1ERYA,1169
4
+ flyteplugins/agents/core/_memory.py,sha256=9xSerEnMhFRGZyC8Hu4JYvU9hdyacj6iWxU-lOlighs,2380
5
+ flyteplugins/agents/core/_observability.py,sha256=xFZgdww7RjnnIkclAxASrCfwrd6AVt70CFavj7XlsDg,1320
6
+ flyteplugins/agents/core/_tools.py,sha256=oKN2msr5hGpvUW0Vzy6EI4nbhkgIJyjv8GO4gnc2rRk,7383
7
+ flyteplugins/agents/core/testing.py,sha256=k3cROYEGlS_SmO_jz3EI5kTLOx7ZYsgd2mZkFTR34ic,2398
8
+ flyteplugins_agents_core-2.5.9.dist-info/METADATA,sha256=Z0MEvUwrwFJ1yfmFhNidHeojmi7BU8yWffumjaTD080,1972
9
+ flyteplugins_agents_core-2.5.9.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
10
+ flyteplugins_agents_core-2.5.9.dist-info/top_level.txt,sha256=cgd779rPu9EsvdtuYgUxNHHgElaQvPn74KhB5XSeMBE,13
11
+ flyteplugins_agents_core-2.5.9.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ flyteplugins