axio 0.12.0__tar.gz → 0.12.2__tar.gz
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.
- {axio-0.12.0 → axio-0.12.2}/PKG-INFO +1 -1
- {axio-0.12.0 → axio-0.12.2}/pyproject.toml +1 -1
- {axio-0.12.0 → axio-0.12.2}/src/axio/credentials/codec.py +13 -4
- {axio-0.12.0 → axio-0.12.2}/src/axio/models.py +1 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/transport.py +34 -1
- axio-0.12.2/tests/test_credential_schema_evolution.py +58 -0
- {axio-0.12.0 → axio-0.12.2}/.gitignore +0 -0
- {axio-0.12.0 → axio-0.12.2}/LICENSE +0 -0
- {axio-0.12.0 → axio-0.12.2}/Makefile +0 -0
- {axio-0.12.0 → axio-0.12.2}/README.md +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/__init__.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/agent.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/agent_loader.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/blocks.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/compaction.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/context.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/credentials/__init__.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/credentials/files.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/credentials/stores.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/credentials/types.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/events.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/exceptions.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/field.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/instructions.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/messages.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/permission.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/permission_policy.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/plugins.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/py.typed +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/realtime.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/retry.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/schema.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/selector.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/stream.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/testing.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/tool.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/tool_access.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/tool_args.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/src/axio/types.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/conftest.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_agent_branch.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_agent_capabilities.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_agent_instructions.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_agent_loader.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_agent_permission.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_agent_run.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_agent_stream.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_agent_tools.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_argument_coercion.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_blocks.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_compaction.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_context.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_credentials.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_events.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_exceptions.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_field.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_init.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_instructions.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_models.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_permission.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_permission_policy.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_plugins.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_preflight.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_realtime.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_retry.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_schema.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_selector.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_stream.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_testing.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_tool.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_tool_args.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_transport.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_transport_plugins.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/tests/test_types.py +0 -0
- {axio-0.12.0 → axio-0.12.2}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: axio
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.2
|
|
4
4
|
Summary: Minimal, streaming-first, protocol-driven foundation for LLM-powered agents
|
|
5
5
|
Project-URL: Documentation, https://docs.axio-agent.com
|
|
6
6
|
Project-URL: Homepage, https://github.com/mosquito/axio-agent
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "axio"
|
|
3
|
-
version = "0.12.
|
|
3
|
+
version = "0.12.2" # bumped to the git tag by CI on publish
|
|
4
4
|
description = "Minimal, streaming-first, protocol-driven foundation for LLM-powered agents"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -6,7 +6,7 @@ import hashlib
|
|
|
6
6
|
import json
|
|
7
7
|
import math
|
|
8
8
|
import types
|
|
9
|
-
from dataclasses import asdict, fields, is_dataclass
|
|
9
|
+
from dataclasses import MISSING, asdict, fields, is_dataclass
|
|
10
10
|
from enum import Enum
|
|
11
11
|
from typing import Annotated, Any, Literal, Union, cast, get_args, get_origin, get_type_hints
|
|
12
12
|
|
|
@@ -98,11 +98,20 @@ def restore(value: Any, annotation: Any) -> Any:
|
|
|
98
98
|
raise ValueError
|
|
99
99
|
return {key: restore(item, args[1]) for key, item in value.items()}
|
|
100
100
|
if isinstance(annotation, type) and is_dataclass(annotation) and isinstance(value, dict):
|
|
101
|
-
declared = {item.name for item in fields(annotation) if item.init}
|
|
102
|
-
|
|
101
|
+
declared = {item.name: item for item in fields(annotation) if item.init}
|
|
102
|
+
# Schema additions must explicitly opt into restoring their default from
|
|
103
|
+
# older records. Ordinary defaults (especially recovery markers) remain
|
|
104
|
+
# required so a damaged record cannot silently reset security state.
|
|
105
|
+
optional = {
|
|
106
|
+
name
|
|
107
|
+
for name, item in declared.items()
|
|
108
|
+
if item.metadata.get("credential_optional") is True
|
|
109
|
+
and (item.default is not MISSING or item.default_factory is not MISSING)
|
|
110
|
+
}
|
|
111
|
+
if not value.keys() <= declared.keys() or not declared.keys() - optional <= value.keys():
|
|
103
112
|
raise ValueError
|
|
104
113
|
hints = get_type_hints(annotation, include_extras=True)
|
|
105
|
-
return annotation(**{name: restore(
|
|
114
|
+
return annotation(**{name: restore(item, hints[name]) for name, item in value.items()})
|
|
106
115
|
raise ValueError
|
|
107
116
|
|
|
108
117
|
|
|
@@ -11,7 +11,7 @@ from dataclasses import dataclass, field
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Any, Protocol, runtime_checkable
|
|
13
13
|
|
|
14
|
-
from .blocks import ContentBlock
|
|
14
|
+
from .blocks import ContentBlock, ImageBlock
|
|
15
15
|
from .events import StreamEvent
|
|
16
16
|
from .messages import Message
|
|
17
17
|
from .models import ModelSpec
|
|
@@ -68,6 +68,25 @@ class ImageGenTransport(Protocol):
|
|
|
68
68
|
async def generate_images(self, prompt: str, *, model: str | None = None, n: int = 1) -> list[bytes]: ...
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
@runtime_checkable
|
|
72
|
+
class ImageEditTransport(Protocol):
|
|
73
|
+
"""Edit supplied images according to a prompt, returning complete image bytes.
|
|
74
|
+
|
|
75
|
+
Sources are required. Implementations must reject unsupported masks instead
|
|
76
|
+
of ignoring them; supported formats and counts are provider-specific.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
async def edit_images(
|
|
80
|
+
self,
|
|
81
|
+
prompt: str,
|
|
82
|
+
*,
|
|
83
|
+
images: list[ImageBlock],
|
|
84
|
+
mask: ImageBlock | None = None,
|
|
85
|
+
model: str | None = None,
|
|
86
|
+
n: int = 1,
|
|
87
|
+
) -> list[bytes]: ...
|
|
88
|
+
|
|
89
|
+
|
|
71
90
|
@runtime_checkable
|
|
72
91
|
class VideoGenTransport(Protocol):
|
|
73
92
|
"""Generate ``n`` video samples for a text prompt. Returns raw video bytes
|
|
@@ -205,6 +224,20 @@ class DummyImageGenTransport(DummyTransport, ImageGenTransport):
|
|
|
205
224
|
raise AssertionError("unreachable")
|
|
206
225
|
|
|
207
226
|
|
|
227
|
+
class DummyImageEditTransport(DummyTransport, ImageEditTransport):
|
|
228
|
+
async def edit_images(
|
|
229
|
+
self,
|
|
230
|
+
prompt: str,
|
|
231
|
+
*,
|
|
232
|
+
images: list[ImageBlock],
|
|
233
|
+
mask: ImageBlock | None = None,
|
|
234
|
+
model: str | None = None,
|
|
235
|
+
n: int = 1,
|
|
236
|
+
) -> list[bytes]:
|
|
237
|
+
self._do_fail()
|
|
238
|
+
raise AssertionError("unreachable")
|
|
239
|
+
|
|
240
|
+
|
|
208
241
|
class DummyVideoGenTransport(DummyTransport, VideoGenTransport):
|
|
209
242
|
async def generate_videos(
|
|
210
243
|
self,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Credential schema additions preserve old records without hiding missing state."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from axio.credentials import CredentialCodec, CredentialError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True, slots=True)
|
|
14
|
+
class Account:
|
|
15
|
+
key: str = field(metadata={"credential_key": True})
|
|
16
|
+
native: bool = field(default=False, metadata={"credential_optional": True})
|
|
17
|
+
tags: list[str] = field(default_factory=list, metadata={"credential_optional": True})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, slots=True)
|
|
21
|
+
class Record:
|
|
22
|
+
account: Account
|
|
23
|
+
operation: str | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_explicit_optional_fields_restore_defaults_and_round_trip() -> None:
|
|
27
|
+
codec = CredentialCodec(Record)
|
|
28
|
+
old = {"version": 1, "account": {"key": "account"}, "operation": "refresh"}
|
|
29
|
+
restored = codec.from_dict(old)
|
|
30
|
+
assert restored == Record(Account("account"), operation="refresh")
|
|
31
|
+
assert codec.decode(codec.encode(restored)) == restored
|
|
32
|
+
assert codec.from_dict(old).account.tags is not restored.account.tags
|
|
33
|
+
assert old["account"] == {"key": "account"}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.mark.parametrize(
|
|
37
|
+
"data",
|
|
38
|
+
[
|
|
39
|
+
{"version": 1, "account": {"key": "account"}},
|
|
40
|
+
{"version": 1, "account": {"key": "account", "unknown": False}, "operation": None},
|
|
41
|
+
{"version": 1, "account": {"key": "account", "native": "false"}, "operation": None},
|
|
42
|
+
{"version": 1, "account": {"key": "account", "native": None}, "operation": None},
|
|
43
|
+
{"version": 1, "account": {"key": "account", "tags": [1]}, "operation": None},
|
|
44
|
+
{"version": 1, "account": {}, "operation": None},
|
|
45
|
+
],
|
|
46
|
+
)
|
|
47
|
+
def test_optional_fields_do_not_weaken_record_validation(data: dict[str, Any]) -> None:
|
|
48
|
+
with pytest.raises(CredentialError):
|
|
49
|
+
CredentialCodec(Record).from_dict(data)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_optional_metadata_without_default_does_not_remove_requirement() -> None:
|
|
53
|
+
@dataclass(frozen=True, slots=True)
|
|
54
|
+
class Required:
|
|
55
|
+
key: str = field(metadata={"credential_key": True, "credential_optional": True})
|
|
56
|
+
|
|
57
|
+
with pytest.raises(CredentialError):
|
|
58
|
+
CredentialCodec(Required).from_dict({"version": 1})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|