agentic-runner 2.6.0__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.
Files changed (54) hide show
  1. agentic_runner/__init__.py +12 -0
  2. agentic_runner/activities.py +4918 -0
  3. agentic_runner/callback.py +342 -0
  4. agentic_runner/child_watcher.py +66 -0
  5. agentic_runner/cli.py +416 -0
  6. agentic_runner/config.py +105 -0
  7. agentic_runner/credentials.py +252 -0
  8. agentic_runner/device_login_activities.py +79 -0
  9. agentic_runner/egress.py +243 -0
  10. agentic_runner/heartbeat_link.py +249 -0
  11. agentic_runner/hooks.py +455 -0
  12. agentic_runner/host_store.py +295 -0
  13. agentic_runner/integrations/__init__.py +0 -0
  14. agentic_runner/integrations/git/__init__.py +1 -0
  15. agentic_runner/integrations/git/contracts.py +198 -0
  16. agentic_runner/integrations/git/evidence.py +442 -0
  17. agentic_runner/integrations/git/fake_workspace.py +339 -0
  18. agentic_runner/integrations/git/workspace.py +921 -0
  19. agentic_runner/integrations/github/__init__.py +53 -0
  20. agentic_runner/integrations/github/auth.py +171 -0
  21. agentic_runner/integrations/github/fake_client.py +494 -0
  22. agentic_runner/integrations/github/gh_client.py +944 -0
  23. agentic_runner/lifecycle.py +48 -0
  24. agentic_runner/llm_proxy.py +937 -0
  25. agentic_runner/mcp.py +342 -0
  26. agentic_runner/message_store.py +341 -0
  27. agentic_runner/py.typed +0 -0
  28. agentic_runner/recipient_key_secret.py +134 -0
  29. agentic_runner/registration.py +363 -0
  30. agentic_runner/runtime/__init__.py +0 -0
  31. agentic_runner/runtime/verifier_command.py +344 -0
  32. agentic_runner/sealed_box.py +509 -0
  33. agentic_runner/service.py +1068 -0
  34. agentic_runner/tiny_http.py +133 -0
  35. agentic_runner/triage_activities.py +113 -0
  36. agentic_runner/user_sources.py +546 -0
  37. agentic_runner/workers/__init__.py +1 -0
  38. agentic_runner/workers/_runtime_support.py +388 -0
  39. agentic_runner/workers/agent_runtime.py +93 -0
  40. agentic_runner/workers/claude_runtime.py +226 -0
  41. agentic_runner/workers/codex_runtime.py +311 -0
  42. agentic_runner/workers/command_policy.py +250 -0
  43. agentic_runner/workers/contract_device_login.py +211 -0
  44. agentic_runner/workers/contract_isolation.py +500 -0
  45. agentic_runner/workers/fastapi_client.py +396 -0
  46. agentic_runner/workers/harness_usage.py +65 -0
  47. agentic_runner/workers/mcp_config.py +111 -0
  48. agentic_runner/workers/settings.py +314 -0
  49. agentic_runner/workstation.py +687 -0
  50. agentic_runner-2.6.0.dist-info/METADATA +49 -0
  51. agentic_runner-2.6.0.dist-info/RECORD +54 -0
  52. agentic_runner-2.6.0.dist-info/WHEEL +4 -0
  53. agentic_runner-2.6.0.dist-info/entry_points.txt +2 -0
  54. agentic_runner-2.6.0.dist-info/licenses/LICENSE +661 -0
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ from agentic_runner.integrations.github.auth import (
4
+ GitHubAppAuthenticationError,
5
+ GitHubAppAuthProvider,
6
+ )
7
+ from agentic_runner.integrations.github.fake_client import FakeGitHubClient
8
+ from agentic_runner.integrations.github.gh_client import GitHubAppClient
9
+ from agentic_runner_contracts.github_port import (
10
+ ApprovalRequest,
11
+ ApprovalResponse,
12
+ ApprovalState,
13
+ BranchRequest,
14
+ BranchResponse,
15
+ CommentRequest,
16
+ CommentResponse,
17
+ GitHubClient,
18
+ MergeRequest,
19
+ MergeResponse,
20
+ PullRequestReadyRequest,
21
+ PullRequestReadyResponse,
22
+ PullRequestRequest,
23
+ PullRequestResponse,
24
+ PullRequestRetargetRequest,
25
+ PullRequestRetargetResponse,
26
+ ReviewRequest,
27
+ ReviewResponse,
28
+ )
29
+
30
+ __all__ = [
31
+ "ApprovalRequest",
32
+ "ApprovalResponse",
33
+ "ApprovalState",
34
+ "BranchRequest",
35
+ "BranchResponse",
36
+ "CommentRequest",
37
+ "CommentResponse",
38
+ "FakeGitHubClient",
39
+ "GitHubAppAuthenticationError",
40
+ "GitHubAppAuthProvider",
41
+ "GitHubAppClient",
42
+ "GitHubClient",
43
+ "MergeRequest",
44
+ "MergeResponse",
45
+ "PullRequestReadyRequest",
46
+ "PullRequestReadyResponse",
47
+ "PullRequestRequest",
48
+ "PullRequestResponse",
49
+ "PullRequestRetargetRequest",
50
+ "PullRequestRetargetResponse",
51
+ "ReviewRequest",
52
+ "ReviewResponse",
53
+ ]
@@ -0,0 +1,171 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from collections.abc import Callable
5
+ from datetime import UTC, datetime, timedelta
6
+ from typing import Any
7
+
8
+ import httpx
9
+ import jwt
10
+
11
+ _DEFAULT_API_BASE_URL = "https://api.github.com"
12
+ _REQUEST_TIMEOUT_SECONDS = 10.0
13
+ _INSTALLATION_TOKEN_REFRESH_WINDOW = timedelta(minutes=5)
14
+
15
+
16
+ class GitHubAppAuthenticationError(RuntimeError):
17
+ """Sanitized GitHub App authentication failure."""
18
+
19
+
20
+ class GitHubAppAuthProvider:
21
+ """GitHub App authentication provider for app JWTs and installation tokens."""
22
+
23
+ def __init__(
24
+ self,
25
+ *,
26
+ app_id: str,
27
+ installation_id: str,
28
+ private_key_pem: str,
29
+ api_base_url: str = _DEFAULT_API_BASE_URL,
30
+ http_client: httpx.Client | None = None,
31
+ transport: httpx.BaseTransport | None = None,
32
+ clock: Callable[[], datetime] | None = None,
33
+ ) -> None:
34
+ self._validate_credentials(app_id, installation_id, private_key_pem)
35
+ if http_client is not None and transport is not None:
36
+ raise ValueError("Provide either http_client or transport, not both")
37
+
38
+ self._app_id = app_id
39
+ self._installation_id = installation_id
40
+ self._private_key_pem = private_key_pem
41
+ self._api_base_url = api_base_url.rstrip("/")
42
+ self._http_client = http_client or httpx.Client(
43
+ transport=transport,
44
+ timeout=_REQUEST_TIMEOUT_SECONDS,
45
+ )
46
+ self._installation_token: str | None = None
47
+ self._installation_token_expires_at: datetime | None = None
48
+ self._clock = clock or (lambda: datetime.now(UTC))
49
+
50
+ @staticmethod
51
+ def _validate_credentials(app_id: str, installation_id: str, private_key_pem: str) -> None:
52
+ missing_fields = [
53
+ field
54
+ for field, value in (
55
+ ("app_id", app_id),
56
+ ("installation_id", installation_id),
57
+ ("private_key_pem", private_key_pem),
58
+ )
59
+ if value.strip() == ""
60
+ ]
61
+ if missing_fields:
62
+ raise ValueError(
63
+ "GitHub App credentials are required and must be non-empty: "
64
+ + ", ".join(missing_fields)
65
+ )
66
+
67
+ def generate_app_jwt(self) -> str:
68
+ issued_at = int(time.time()) - 60
69
+ claims = {
70
+ "iat": issued_at,
71
+ "exp": issued_at + 600,
72
+ "iss": self._app_id,
73
+ }
74
+ try:
75
+ token = jwt.encode(claims, self._private_key_pem, algorithm="RS256")
76
+ except Exception:
77
+ raise GitHubAppAuthenticationError("GitHub App JWT generation failed") from None
78
+ return token
79
+
80
+ def get_installation_token(self) -> str:
81
+ if self._cached_installation_token_is_reusable():
82
+ assert self._installation_token is not None
83
+ return self._installation_token
84
+
85
+ app_jwt = self.generate_app_jwt()
86
+ response_data = self._send_json_request(
87
+ "POST",
88
+ f"/app/installations/{self._installation_id}/access_tokens",
89
+ headers=self.github_headers(f"Bearer {app_jwt}"),
90
+ )
91
+ token = response_data.get("token")
92
+ if not isinstance(token, str) or token.strip() == "":
93
+ raise GitHubAppAuthenticationError(
94
+ "GitHub App installation token response was invalid"
95
+ ) from None
96
+ expires_at = self._parse_installation_token_expires_at(response_data)
97
+ self._installation_token = token
98
+ self._installation_token_expires_at = expires_at
99
+ return token
100
+
101
+ def _cached_installation_token_is_reusable(self) -> bool:
102
+ if self._installation_token is None or self._installation_token_expires_at is None:
103
+ return False
104
+ refresh_deadline = self._clock() + _INSTALLATION_TOKEN_REFRESH_WINDOW
105
+ return self._installation_token_expires_at > refresh_deadline
106
+
107
+ @staticmethod
108
+ def _parse_installation_token_expires_at(response_data: dict[str, Any]) -> datetime:
109
+ expires_at_value = response_data.get("expires_at")
110
+ if not isinstance(expires_at_value, str) or expires_at_value.strip() == "":
111
+ raise GitHubAppAuthenticationError(
112
+ "GitHub App installation token response was invalid"
113
+ ) from None
114
+
115
+ try:
116
+ parsed_expires_at = datetime.fromisoformat(expires_at_value.replace("Z", "+00:00"))
117
+ except ValueError:
118
+ raise GitHubAppAuthenticationError(
119
+ "GitHub App installation token response was invalid"
120
+ ) from None
121
+
122
+ if parsed_expires_at.tzinfo is None:
123
+ raise GitHubAppAuthenticationError(
124
+ "GitHub App installation token response was invalid"
125
+ ) from None
126
+ return parsed_expires_at.astimezone(UTC)
127
+
128
+ @staticmethod
129
+ def github_headers(authorization: str) -> dict[str, str]:
130
+ return {
131
+ "Accept": "application/vnd.github+json",
132
+ "Authorization": authorization,
133
+ "X-GitHub-Api-Version": "2022-11-28",
134
+ }
135
+
136
+ def _send_json_request(
137
+ self,
138
+ method: str,
139
+ path: str,
140
+ *,
141
+ headers: dict[str, str],
142
+ json_body: dict[str, Any] | None = None,
143
+ ) -> dict[str, Any]:
144
+ url = f"{self._api_base_url}/{path.lstrip('/')}"
145
+ try:
146
+ response = self._http_client.request(
147
+ method,
148
+ url,
149
+ headers=headers,
150
+ json=json_body,
151
+ timeout=_REQUEST_TIMEOUT_SECONDS,
152
+ )
153
+ except httpx.HTTPError:
154
+ raise GitHubAppAuthenticationError("GitHub App authentication request failed") from None
155
+
156
+ if response.status_code < 200 or response.status_code >= 300:
157
+ raise GitHubAppAuthenticationError(
158
+ f"GitHub App authentication request failed with status {response.status_code}"
159
+ ) from None
160
+
161
+ try:
162
+ response_data = response.json()
163
+ except ValueError:
164
+ raise GitHubAppAuthenticationError(
165
+ "GitHub App authentication response was not valid JSON"
166
+ ) from None
167
+ if not isinstance(response_data, dict):
168
+ raise GitHubAppAuthenticationError(
169
+ "GitHub App authentication response JSON object was invalid"
170
+ ) from None
171
+ return response_data