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.
Files changed (75) hide show
  1. {axio-0.12.0 → axio-0.12.2}/PKG-INFO +1 -1
  2. {axio-0.12.0 → axio-0.12.2}/pyproject.toml +1 -1
  3. {axio-0.12.0 → axio-0.12.2}/src/axio/credentials/codec.py +13 -4
  4. {axio-0.12.0 → axio-0.12.2}/src/axio/models.py +1 -0
  5. {axio-0.12.0 → axio-0.12.2}/src/axio/transport.py +34 -1
  6. axio-0.12.2/tests/test_credential_schema_evolution.py +58 -0
  7. {axio-0.12.0 → axio-0.12.2}/.gitignore +0 -0
  8. {axio-0.12.0 → axio-0.12.2}/LICENSE +0 -0
  9. {axio-0.12.0 → axio-0.12.2}/Makefile +0 -0
  10. {axio-0.12.0 → axio-0.12.2}/README.md +0 -0
  11. {axio-0.12.0 → axio-0.12.2}/src/axio/__init__.py +0 -0
  12. {axio-0.12.0 → axio-0.12.2}/src/axio/agent.py +0 -0
  13. {axio-0.12.0 → axio-0.12.2}/src/axio/agent_loader.py +0 -0
  14. {axio-0.12.0 → axio-0.12.2}/src/axio/blocks.py +0 -0
  15. {axio-0.12.0 → axio-0.12.2}/src/axio/compaction.py +0 -0
  16. {axio-0.12.0 → axio-0.12.2}/src/axio/context.py +0 -0
  17. {axio-0.12.0 → axio-0.12.2}/src/axio/credentials/__init__.py +0 -0
  18. {axio-0.12.0 → axio-0.12.2}/src/axio/credentials/files.py +0 -0
  19. {axio-0.12.0 → axio-0.12.2}/src/axio/credentials/stores.py +0 -0
  20. {axio-0.12.0 → axio-0.12.2}/src/axio/credentials/types.py +0 -0
  21. {axio-0.12.0 → axio-0.12.2}/src/axio/events.py +0 -0
  22. {axio-0.12.0 → axio-0.12.2}/src/axio/exceptions.py +0 -0
  23. {axio-0.12.0 → axio-0.12.2}/src/axio/field.py +0 -0
  24. {axio-0.12.0 → axio-0.12.2}/src/axio/instructions.py +0 -0
  25. {axio-0.12.0 → axio-0.12.2}/src/axio/messages.py +0 -0
  26. {axio-0.12.0 → axio-0.12.2}/src/axio/permission.py +0 -0
  27. {axio-0.12.0 → axio-0.12.2}/src/axio/permission_policy.py +0 -0
  28. {axio-0.12.0 → axio-0.12.2}/src/axio/plugins.py +0 -0
  29. {axio-0.12.0 → axio-0.12.2}/src/axio/py.typed +0 -0
  30. {axio-0.12.0 → axio-0.12.2}/src/axio/realtime.py +0 -0
  31. {axio-0.12.0 → axio-0.12.2}/src/axio/retry.py +0 -0
  32. {axio-0.12.0 → axio-0.12.2}/src/axio/schema.py +0 -0
  33. {axio-0.12.0 → axio-0.12.2}/src/axio/selector.py +0 -0
  34. {axio-0.12.0 → axio-0.12.2}/src/axio/stream.py +0 -0
  35. {axio-0.12.0 → axio-0.12.2}/src/axio/testing.py +0 -0
  36. {axio-0.12.0 → axio-0.12.2}/src/axio/tool.py +0 -0
  37. {axio-0.12.0 → axio-0.12.2}/src/axio/tool_access.py +0 -0
  38. {axio-0.12.0 → axio-0.12.2}/src/axio/tool_args.py +0 -0
  39. {axio-0.12.0 → axio-0.12.2}/src/axio/types.py +0 -0
  40. {axio-0.12.0 → axio-0.12.2}/tests/conftest.py +0 -0
  41. {axio-0.12.0 → axio-0.12.2}/tests/test_agent_branch.py +0 -0
  42. {axio-0.12.0 → axio-0.12.2}/tests/test_agent_capabilities.py +0 -0
  43. {axio-0.12.0 → axio-0.12.2}/tests/test_agent_instructions.py +0 -0
  44. {axio-0.12.0 → axio-0.12.2}/tests/test_agent_loader.py +0 -0
  45. {axio-0.12.0 → axio-0.12.2}/tests/test_agent_permission.py +0 -0
  46. {axio-0.12.0 → axio-0.12.2}/tests/test_agent_run.py +0 -0
  47. {axio-0.12.0 → axio-0.12.2}/tests/test_agent_stream.py +0 -0
  48. {axio-0.12.0 → axio-0.12.2}/tests/test_agent_tools.py +0 -0
  49. {axio-0.12.0 → axio-0.12.2}/tests/test_argument_coercion.py +0 -0
  50. {axio-0.12.0 → axio-0.12.2}/tests/test_blocks.py +0 -0
  51. {axio-0.12.0 → axio-0.12.2}/tests/test_compaction.py +0 -0
  52. {axio-0.12.0 → axio-0.12.2}/tests/test_context.py +0 -0
  53. {axio-0.12.0 → axio-0.12.2}/tests/test_credentials.py +0 -0
  54. {axio-0.12.0 → axio-0.12.2}/tests/test_events.py +0 -0
  55. {axio-0.12.0 → axio-0.12.2}/tests/test_exceptions.py +0 -0
  56. {axio-0.12.0 → axio-0.12.2}/tests/test_field.py +0 -0
  57. {axio-0.12.0 → axio-0.12.2}/tests/test_init.py +0 -0
  58. {axio-0.12.0 → axio-0.12.2}/tests/test_instructions.py +0 -0
  59. {axio-0.12.0 → axio-0.12.2}/tests/test_models.py +0 -0
  60. {axio-0.12.0 → axio-0.12.2}/tests/test_permission.py +0 -0
  61. {axio-0.12.0 → axio-0.12.2}/tests/test_permission_policy.py +0 -0
  62. {axio-0.12.0 → axio-0.12.2}/tests/test_plugins.py +0 -0
  63. {axio-0.12.0 → axio-0.12.2}/tests/test_preflight.py +0 -0
  64. {axio-0.12.0 → axio-0.12.2}/tests/test_realtime.py +0 -0
  65. {axio-0.12.0 → axio-0.12.2}/tests/test_retry.py +0 -0
  66. {axio-0.12.0 → axio-0.12.2}/tests/test_schema.py +0 -0
  67. {axio-0.12.0 → axio-0.12.2}/tests/test_selector.py +0 -0
  68. {axio-0.12.0 → axio-0.12.2}/tests/test_stream.py +0 -0
  69. {axio-0.12.0 → axio-0.12.2}/tests/test_testing.py +0 -0
  70. {axio-0.12.0 → axio-0.12.2}/tests/test_tool.py +0 -0
  71. {axio-0.12.0 → axio-0.12.2}/tests/test_tool_args.py +0 -0
  72. {axio-0.12.0 → axio-0.12.2}/tests/test_transport.py +0 -0
  73. {axio-0.12.0 → axio-0.12.2}/tests/test_transport_plugins.py +0 -0
  74. {axio-0.12.0 → axio-0.12.2}/tests/test_types.py +0 -0
  75. {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.0
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.0" # bumped to the git tag by CI on publish
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
- if value.keys() != declared:
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(value[name], hints[name]) for name in declared})
114
+ return annotation(**{name: restore(item, hints[name]) for name, item in value.items()})
106
115
  raise ValueError
107
116
 
108
117
 
@@ -15,6 +15,7 @@ class Capability(StrEnum):
15
15
  audio = "audio"
16
16
  # Output modalities
17
17
  image_generation = "image_generation"
18
+ image_editing = "image_editing"
18
19
  video_generation = "video_generation"
19
20
  # Processing capabilities
20
21
  reasoning = "reasoning"
@@ -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