axio-responses 0.1.0__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.
@@ -0,0 +1,14 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ .pytest_cache/
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ _build/
12
+ .DS_Store
13
+ *.sqlite
14
+ *.db
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.5
2
+ Name: axio-responses
3
+ Version: 0.1.0
4
+ Summary: The OpenAI Responses API as axio speaks it: request items in, StreamEvents out
5
+ Project-URL: Documentation, https://docs.axio-agent.com
6
+ Project-URL: Homepage, https://github.com/mosquito/axio-agent
7
+ Project-URL: Repository, https://github.com/mosquito/axio-agent
8
+ License: MIT
9
+ Keywords: agent,llm,openai,responses,streaming
10
+ Requires-Python: >=3.12
11
+ Requires-Dist: axio
12
+ Requires-Dist: axio-sse
13
+ Description-Content-Type: text/markdown
14
+
15
+ # axio-responses
16
+
17
+ [![PyPI](https://img.shields.io/pypi/v/axio-responses)](https://pypi.org/project/axio-responses/)
18
+ [![Python](https://img.shields.io/pypi/pyversions/axio-responses)](https://pypi.org/project/axio-responses/)
19
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
20
+
21
+ The OpenAI Responses API as [axio](https://github.com/mosquito/axio-agent) speaks it: request items
22
+ in, `StreamEvent`s out.
23
+
24
+ Both halves live here rather than in a transport because two transports speak this API — the public
25
+ `/v1/responses` endpoint and the ChatGPT backend Codex uses. It knows nothing about HTTP and opens
26
+ no connection.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install axio-responses
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ### Building the request
37
+
38
+ <!-- name: test_readme_building_a_request -->
39
+ ```python
40
+ from axio.blocks import TextBlock
41
+ from axio.messages import Message
42
+ from axio_responses import convert_messages, convert_tools
43
+
44
+ messages, system, tools = [Message(role="user", content=[TextBlock(text="hi")])], "be brief", []
45
+
46
+ instructions, items = convert_messages(messages, system)
47
+ payload = {
48
+ "model": "gpt-5.6",
49
+ "instructions": instructions,
50
+ "input": items,
51
+ "stream": True,
52
+ "tools": convert_tools(tools),
53
+ }
54
+ assert payload["instructions"] == "be brief"
55
+ ```
56
+
57
+ `convert_messages` returns the system prompt separately, because this API takes it as
58
+ `instructions` rather than as a message. Tool calls and their outputs become `function_call` and
59
+ `function_call_output` items beside the messages, not blocks inside them.
60
+
61
+ ### Reading the stream
62
+
63
+ `Responses` is an `axio_sse.Reader`: one `@on(...)` method per event, dispatching on the payload's
64
+ own `type`. Its class body names only the events it interprets. The API publishes one event family
65
+ per tool it can run, so that set grows with the tools and not with the protocol; everything else is
66
+ forwarded through `unmatched()` rather than dropped.
67
+
68
+ <!-- name: test_readme_reading_the_stream -->
69
+ ```python
70
+ from collections.abc import AsyncIterator
71
+
72
+ import aiohttp
73
+ from axio.events import StreamEvent
74
+ from axio_responses import Responses
75
+
76
+
77
+ async def stream(resp: aiohttp.ClientResponse) -> AsyncIterator[StreamEvent]:
78
+ turn = Responses()
79
+ async for made in turn.over(resp.content.iter_any(), until="[DONE]"):
80
+ yield made
81
+ yield turn.finished()
82
+ ```
83
+
84
+ Events axio has no type for — the API's own hosted tools, its audio, its bookkeeping — travel as
85
+ `ProviderEvent` under the provider's own name rather than being dropped.
86
+
87
+ ### Holding it against the schema
88
+
89
+ <!-- name: test_readme_names_are_published -->
90
+ ```python
91
+ from axio_responses import Responses
92
+
93
+ PUBLISHED_EVENTS = {"response.output_text.delta", "response.completed", "response.refusal.delta"}
94
+
95
+ # Every name the reader claims is one the schema publishes. A typo is a handler that never runs.
96
+ assert Responses.names() >= PUBLISHED_EVENTS
97
+ ```
98
+
99
+ `names()` answers what the reader claims, so a test can hold it against the union OpenAI publishes.
100
+ The check is `<=`, not `==`: the reader deliberately names fewer events than the API sends. Reading
101
+ with `strict=True` raises `UnknownEvent` on a name it does not claim, which is how a test fails on
102
+ the day OpenAI adds one.
103
+
104
+ ## License
105
+
106
+ MIT
@@ -0,0 +1,92 @@
1
+ # axio-responses
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/axio-responses)](https://pypi.org/project/axio-responses/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/axio-responses)](https://pypi.org/project/axio-responses/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+
7
+ The OpenAI Responses API as [axio](https://github.com/mosquito/axio-agent) speaks it: request items
8
+ in, `StreamEvent`s out.
9
+
10
+ Both halves live here rather than in a transport because two transports speak this API — the public
11
+ `/v1/responses` endpoint and the ChatGPT backend Codex uses. It knows nothing about HTTP and opens
12
+ no connection.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install axio-responses
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ### Building the request
23
+
24
+ <!-- name: test_readme_building_a_request -->
25
+ ```python
26
+ from axio.blocks import TextBlock
27
+ from axio.messages import Message
28
+ from axio_responses import convert_messages, convert_tools
29
+
30
+ messages, system, tools = [Message(role="user", content=[TextBlock(text="hi")])], "be brief", []
31
+
32
+ instructions, items = convert_messages(messages, system)
33
+ payload = {
34
+ "model": "gpt-5.6",
35
+ "instructions": instructions,
36
+ "input": items,
37
+ "stream": True,
38
+ "tools": convert_tools(tools),
39
+ }
40
+ assert payload["instructions"] == "be brief"
41
+ ```
42
+
43
+ `convert_messages` returns the system prompt separately, because this API takes it as
44
+ `instructions` rather than as a message. Tool calls and their outputs become `function_call` and
45
+ `function_call_output` items beside the messages, not blocks inside them.
46
+
47
+ ### Reading the stream
48
+
49
+ `Responses` is an `axio_sse.Reader`: one `@on(...)` method per event, dispatching on the payload's
50
+ own `type`. Its class body names only the events it interprets. The API publishes one event family
51
+ per tool it can run, so that set grows with the tools and not with the protocol; everything else is
52
+ forwarded through `unmatched()` rather than dropped.
53
+
54
+ <!-- name: test_readme_reading_the_stream -->
55
+ ```python
56
+ from collections.abc import AsyncIterator
57
+
58
+ import aiohttp
59
+ from axio.events import StreamEvent
60
+ from axio_responses import Responses
61
+
62
+
63
+ async def stream(resp: aiohttp.ClientResponse) -> AsyncIterator[StreamEvent]:
64
+ turn = Responses()
65
+ async for made in turn.over(resp.content.iter_any(), until="[DONE]"):
66
+ yield made
67
+ yield turn.finished()
68
+ ```
69
+
70
+ Events axio has no type for — the API's own hosted tools, its audio, its bookkeeping — travel as
71
+ `ProviderEvent` under the provider's own name rather than being dropped.
72
+
73
+ ### Holding it against the schema
74
+
75
+ <!-- name: test_readme_names_are_published -->
76
+ ```python
77
+ from axio_responses import Responses
78
+
79
+ PUBLISHED_EVENTS = {"response.output_text.delta", "response.completed", "response.refusal.delta"}
80
+
81
+ # Every name the reader claims is one the schema publishes. A typo is a handler that never runs.
82
+ assert Responses.names() >= PUBLISHED_EVENTS
83
+ ```
84
+
85
+ `names()` answers what the reader claims, so a test can hold it against the union OpenAI publishes.
86
+ The check is `<=`, not `==`: the reader deliberately names fewer events than the API sends. Reading
87
+ with `strict=True` raises `UnknownEvent` on a name it does not claim, which is how a test fails on
88
+ the day OpenAI adds one.
89
+
90
+ ## License
91
+
92
+ MIT
@@ -0,0 +1,47 @@
1
+ [project]
2
+ name = "axio-responses"
3
+ version = "0.1.0"
4
+ description = "The OpenAI Responses API as axio speaks it: request items in, StreamEvents out"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ license = {text = "MIT"}
8
+ keywords = ["openai", "responses", "llm", "agent", "streaming"]
9
+ dependencies = ["axio", "axio-sse"]
10
+
11
+ [project.urls]
12
+ Documentation = "https://docs.axio-agent.com"
13
+ Homepage = "https://github.com/mosquito/axio-agent"
14
+ Repository = "https://github.com/mosquito/axio-agent"
15
+
16
+ [build-system]
17
+ requires = ["hatchling"]
18
+ build-backend = "hatchling.build"
19
+
20
+ [tool.hatch.build.targets.wheel]
21
+ packages = ["src/axio_responses"]
22
+
23
+ [tool.pytest.ini_options]
24
+ asyncio_mode = "auto"
25
+ testpaths = ["tests", "README.md"]
26
+
27
+ [tool.ruff]
28
+ line-length = 119
29
+ output-format = "concise"
30
+ target-version = "py312"
31
+
32
+ [tool.ruff.lint]
33
+ select = ["E", "F", "I", "UP"]
34
+
35
+ [tool.mypy]
36
+ strict = true
37
+ python_version = "3.12"
38
+
39
+ [dependency-groups]
40
+ dev = [
41
+ "pytest>=8",
42
+ "pytest-asyncio>=0.24",
43
+ "mypy>=1.14",
44
+ "ruff>=0.9",
45
+ "pytest-cov>=7.1.0",
46
+ "markdown-pytest>=0.6.0",
47
+ ]
@@ -0,0 +1,67 @@
1
+ """The OpenAI Responses API as axio speaks it.
2
+
3
+ ``convert_messages`` and ``convert_tools`` build the request. ``Responses`` reads the stream it
4
+ answers with. Both halves are here rather than in a transport because two transports speak this
5
+ API: the public ``/v1/responses`` endpoint and the ChatGPT backend Codex uses.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from .reader import (
11
+ Annotation,
12
+ AnnotationAdded,
13
+ AnnotationSource,
14
+ ArgumentsDelta,
15
+ ArgumentsDone,
16
+ Completed,
17
+ ContentPartDone,
18
+ Created,
19
+ Failed,
20
+ Incomplete,
21
+ IncompleteDetails,
22
+ InputDetails,
23
+ ItemAdded,
24
+ ItemDone,
25
+ OutputDetails,
26
+ OutputItem,
27
+ ReasoningDeltaEvent,
28
+ RefusalDeltaEvent,
29
+ ResponseError,
30
+ ResponseObject,
31
+ Responses,
32
+ ResponseUsage,
33
+ StreamFailure,
34
+ TextDeltaEvent,
35
+ )
36
+ from .request import STOP_REASONS, convert_messages, convert_tools, tool_output
37
+
38
+ __all__ = [
39
+ "STOP_REASONS",
40
+ "Annotation",
41
+ "AnnotationAdded",
42
+ "AnnotationSource",
43
+ "ArgumentsDelta",
44
+ "ArgumentsDone",
45
+ "ContentPartDone",
46
+ "Completed",
47
+ "Created",
48
+ "Failed",
49
+ "Incomplete",
50
+ "IncompleteDetails",
51
+ "InputDetails",
52
+ "ItemAdded",
53
+ "ItemDone",
54
+ "OutputDetails",
55
+ "OutputItem",
56
+ "ReasoningDeltaEvent",
57
+ "RefusalDeltaEvent",
58
+ "ResponseError",
59
+ "ResponseObject",
60
+ "ResponseUsage",
61
+ "Responses",
62
+ "StreamFailure",
63
+ "TextDeltaEvent",
64
+ "convert_messages",
65
+ "convert_tools",
66
+ "tool_output",
67
+ ]
File without changes