axio-transport-codex 0.2.3__tar.gz → 0.3.3__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,12 @@
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
@@ -8,7 +8,7 @@ lint:
8
8
  uv run ruff check src/ tests/
9
9
 
10
10
  typecheck:
11
- uv run mypy src/
11
+ uv run mypy src/ tests/
12
12
 
13
13
  test:
14
14
  uv run pytest tests/ -v
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: axio-transport-codex
3
- Version: 0.2.3
3
+ Version: 0.3.3
4
4
  Summary: ChatGPT (Codex) OAuth transport for Axio using Responses API
5
5
  Project-URL: Homepage, https://github.com/axio-agent/axio-transport-codex
6
6
  Project-URL: Repository, https://github.com/axio-agent/axio-transport-codex
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "axio-transport-codex"
3
- version = "0.2.3"
3
+ version = "0.3.3"
4
4
  description = "ChatGPT (Codex) OAuth transport for Axio using Responses API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -15,7 +15,7 @@ tui = ["textual>=2.1.0"]
15
15
  codex = "axio_transport_codex:CodexTransport"
16
16
 
17
17
  [project.entry-points."axio.transport.settings"]
18
- codex = "axio_transport_codex:CodexSettingsScreen"
18
+ codex = "axio_transport_codex.tui:CodexSettingsScreen"
19
19
 
20
20
 
21
21
  [project.urls]
@@ -3,10 +3,3 @@
3
3
  from axio_transport_codex.transport import CODEX_MODELS, CodexTransport
4
4
 
5
5
  __all__ = ["CODEX_MODELS", "CodexTransport"]
6
-
7
- try:
8
- from axio_transport_codex.settings import CodexSettingsScreen
9
-
10
- __all__ += ["CodexSettingsScreen"]
11
- except ImportError:
12
- pass
@@ -0,0 +1,5 @@
1
+ """Retained for backwards compatibility — import from tui instead."""
2
+
3
+ from axio_transport_codex.tui.settings import CodexSettingsScreen
4
+
5
+ __all__ = ["CodexSettingsScreen"]
@@ -19,6 +19,7 @@ from axio.exceptions import StreamError
19
19
  from axio.messages import Message
20
20
  from axio.models import Capability, ModelRegistry, ModelSpec, TransportMeta
21
21
  from axio.tool import Tool
22
+ from axio.transport import CompletionTransport
22
23
  from axio.types import StopReason, Usage
23
24
 
24
25
  from .oauth import CLIENT_ID, ORIGINATOR, TOKEN_URL, _decode_jwt_payload
@@ -158,7 +159,7 @@ def _convert_messages(messages: list[Message], system: str) -> tuple[str, list[d
158
159
 
159
160
 
160
161
  @dataclass(slots=True)
161
- class CodexTransport:
162
+ class CodexTransport(CompletionTransport):
162
163
  META: ClassVar[TransportMeta] = TransportMeta(
163
164
  label="ChatGPT (Codex)",
164
165
  api_key_env="",
@@ -0,0 +1,15 @@
1
+ """TUI screens for axio-transport-codex (requires textual)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ try:
6
+ from .settings import CodexSettingsScreen
7
+
8
+ __all__ = ["CodexSettingsScreen"]
9
+ except ImportError as _e:
10
+ import warnings
11
+
12
+ warnings.warn(
13
+ f"axio-transport-codex TUI screens are unavailable: {_e}. Install textual: pip install axio[tui]",
14
+ stacklevel=1,
15
+ )
@@ -0,0 +1,74 @@
1
+ """Settings screen for ChatGPT (Codex) transport."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from textual.app import ComposeResult
6
+ from textual.binding import Binding
7
+ from textual.containers import Container, Horizontal
8
+ from textual.screen import ModalScreen
9
+ from textual.widgets import Button, Static
10
+
11
+
12
+ class CodexSettingsScreen(ModalScreen[dict[str, str] | None]):
13
+ """Sign in / sign out screen for ChatGPT OAuth."""
14
+
15
+ BINDINGS = [Binding("escape", "cancel", "Cancel")]
16
+ CSS = """
17
+ CodexSettingsScreen { align: center middle; }
18
+ #codex-settings {
19
+ width: 70; height: auto; border: heavy $accent;
20
+ background: $panel; padding: 1 2;
21
+ }
22
+ .field-label { margin-top: 1; }
23
+ .settings-buttons { height: auto; margin-top: 1; }
24
+ .settings-buttons Button { margin: 0 1; }
25
+ """
26
+
27
+ def __init__(self, settings: dict[str, str]) -> None:
28
+ super().__init__()
29
+ self._settings = settings
30
+ self._signed_in = bool(settings.get("api_key"))
31
+
32
+ def compose(self) -> ComposeResult:
33
+ with Container(id="codex-settings"):
34
+ yield Static("[bold]ChatGPT (Codex) Settings[/]")
35
+ if self._signed_in:
36
+ account = self._settings.get("account_id", "unknown")
37
+ yield Static(f"Signed in (account: {account[:16]}...)", classes="field-label")
38
+ with Horizontal(classes="settings-buttons"):
39
+ yield Button("Sign Out", id="btn-signout", variant="warning")
40
+ yield Button("Cancel", id="btn-cancel")
41
+ else:
42
+ yield Static(
43
+ "Sign in with your ChatGPT account to use this transport.",
44
+ classes="field-label",
45
+ )
46
+ with Horizontal(classes="settings-buttons"):
47
+ yield Button("Sign in with ChatGPT", id="btn-signin", variant="primary")
48
+ yield Button("Cancel", id="btn-cancel")
49
+
50
+ def on_button_pressed(self, event: Button.Pressed) -> None:
51
+ if event.button.id == "btn-signin":
52
+ self.run_worker(self._do_signin(), exclusive=True)
53
+ elif event.button.id == "btn-signout":
54
+ self.dismiss({})
55
+ else:
56
+ self.dismiss(None)
57
+
58
+ async def _do_signin(self) -> None:
59
+ from axio_transport_codex.oauth import run_oauth_flow
60
+
61
+ try:
62
+ tokens = await run_oauth_flow()
63
+ settings: dict[str, str] = {
64
+ "api_key": tokens["access_token"],
65
+ "refresh_token": tokens.get("refresh_token", ""),
66
+ "expires_at": tokens.get("expires_at", ""),
67
+ "account_id": tokens.get("account_id", ""),
68
+ }
69
+ self.dismiss(settings)
70
+ except Exception as exc:
71
+ self.notify(f"Sign-in failed: {exc}", severity="error")
72
+
73
+ def action_cancel(self) -> None:
74
+ self.dismiss(None)
@@ -1,78 +0,0 @@
1
- """Textual settings screen for ChatGPT (Codex) transport."""
2
-
3
- from __future__ import annotations
4
-
5
- try:
6
- from textual.app import ComposeResult
7
- from textual.binding import Binding
8
- from textual.containers import Container, Horizontal
9
- from textual.screen import ModalScreen
10
- from textual.widgets import Button, Static
11
-
12
- class CodexSettingsScreen(ModalScreen[dict[str, str] | None]):
13
- """Sign in / sign out screen for ChatGPT OAuth."""
14
-
15
- BINDINGS = [Binding("escape", "cancel", "Cancel")]
16
- CSS = """
17
- CodexSettingsScreen { align: center middle; }
18
- #codex-settings {
19
- width: 70; height: auto; border: heavy $accent;
20
- background: $panel; padding: 1 2;
21
- }
22
- .field-label { margin-top: 1; }
23
- .settings-buttons { height: auto; margin-top: 1; }
24
- .settings-buttons Button { margin: 0 1; }
25
- """
26
-
27
- def __init__(self, settings: dict[str, str]) -> None:
28
- super().__init__()
29
- self._settings = settings
30
- self._signed_in = bool(settings.get("api_key"))
31
-
32
- def compose(self) -> ComposeResult:
33
- with Container(id="codex-settings"):
34
- yield Static("[bold]ChatGPT (Codex) Settings[/]")
35
- if self._signed_in:
36
- account = self._settings.get("account_id", "unknown")
37
- yield Static(f"Signed in (account: {account[:16]}...)", classes="field-label")
38
- with Horizontal(classes="settings-buttons"):
39
- yield Button("Sign Out", id="btn-signout", variant="warning")
40
- yield Button("Cancel", id="btn-cancel")
41
- else:
42
- yield Static(
43
- "Sign in with your ChatGPT account to use this transport.",
44
- classes="field-label",
45
- )
46
- with Horizontal(classes="settings-buttons"):
47
- yield Button("Sign in with ChatGPT", id="btn-signin", variant="primary")
48
- yield Button("Cancel", id="btn-cancel")
49
-
50
- def on_button_pressed(self, event: Button.Pressed) -> None:
51
- if event.button.id == "btn-signin":
52
- self.run_worker(self._do_signin(), exclusive=True)
53
- elif event.button.id == "btn-signout":
54
- self.dismiss({})
55
- else:
56
- self.dismiss(None)
57
-
58
- async def _do_signin(self) -> None:
59
- from .oauth import run_oauth_flow
60
-
61
- try:
62
- tokens = await run_oauth_flow()
63
- # Map access_token → api_key for registry compatibility
64
- settings: dict[str, str] = {
65
- "api_key": tokens["access_token"],
66
- "refresh_token": tokens.get("refresh_token", ""),
67
- "expires_at": tokens.get("expires_at", ""),
68
- "account_id": tokens.get("account_id", ""),
69
- }
70
- self.dismiss(settings)
71
- except Exception as exc:
72
- self.notify(f"Sign-in failed: {exc}", severity="error")
73
-
74
- def action_cancel(self) -> None:
75
- self.dismiss(None)
76
-
77
- except ImportError:
78
- pass