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.
- agentic_runner/__init__.py +12 -0
- agentic_runner/activities.py +4918 -0
- agentic_runner/callback.py +342 -0
- agentic_runner/child_watcher.py +66 -0
- agentic_runner/cli.py +416 -0
- agentic_runner/config.py +105 -0
- agentic_runner/credentials.py +252 -0
- agentic_runner/device_login_activities.py +79 -0
- agentic_runner/egress.py +243 -0
- agentic_runner/heartbeat_link.py +249 -0
- agentic_runner/hooks.py +455 -0
- agentic_runner/host_store.py +295 -0
- agentic_runner/integrations/__init__.py +0 -0
- agentic_runner/integrations/git/__init__.py +1 -0
- agentic_runner/integrations/git/contracts.py +198 -0
- agentic_runner/integrations/git/evidence.py +442 -0
- agentic_runner/integrations/git/fake_workspace.py +339 -0
- agentic_runner/integrations/git/workspace.py +921 -0
- agentic_runner/integrations/github/__init__.py +53 -0
- agentic_runner/integrations/github/auth.py +171 -0
- agentic_runner/integrations/github/fake_client.py +494 -0
- agentic_runner/integrations/github/gh_client.py +944 -0
- agentic_runner/lifecycle.py +48 -0
- agentic_runner/llm_proxy.py +937 -0
- agentic_runner/mcp.py +342 -0
- agentic_runner/message_store.py +341 -0
- agentic_runner/py.typed +0 -0
- agentic_runner/recipient_key_secret.py +134 -0
- agentic_runner/registration.py +363 -0
- agentic_runner/runtime/__init__.py +0 -0
- agentic_runner/runtime/verifier_command.py +344 -0
- agentic_runner/sealed_box.py +509 -0
- agentic_runner/service.py +1068 -0
- agentic_runner/tiny_http.py +133 -0
- agentic_runner/triage_activities.py +113 -0
- agentic_runner/user_sources.py +546 -0
- agentic_runner/workers/__init__.py +1 -0
- agentic_runner/workers/_runtime_support.py +388 -0
- agentic_runner/workers/agent_runtime.py +93 -0
- agentic_runner/workers/claude_runtime.py +226 -0
- agentic_runner/workers/codex_runtime.py +311 -0
- agentic_runner/workers/command_policy.py +250 -0
- agentic_runner/workers/contract_device_login.py +211 -0
- agentic_runner/workers/contract_isolation.py +500 -0
- agentic_runner/workers/fastapi_client.py +396 -0
- agentic_runner/workers/harness_usage.py +65 -0
- agentic_runner/workers/mcp_config.py +111 -0
- agentic_runner/workers/settings.py +314 -0
- agentic_runner/workstation.py +687 -0
- agentic_runner-2.6.0.dist-info/METADATA +49 -0
- agentic_runner-2.6.0.dist-info/RECORD +54 -0
- agentic_runner-2.6.0.dist-info/WHEEL +4 -0
- agentic_runner-2.6.0.dist-info/entry_points.txt +2 -0
- 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
|