indent 0.1.19__py3-none-any.whl → 0.1.21__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.
Potentially problematic release.
This version of indent might be problematic. Click here for more details.
- exponent/__init__.py +2 -2
- exponent/commands/cloud_commands.py +0 -84
- exponent/commands/config_commands.py +0 -87
- exponent/commands/run_commands.py +1 -1
- exponent/core/graphql/mutations.py +0 -29
- exponent/core/remote_execution/cli_rpc_types.py +24 -13
- exponent/core/remote_execution/client.py +47 -33
- exponent/core/remote_execution/default_env.py +31 -0
- exponent/core/remote_execution/languages/shell_streaming.py +5 -1
- exponent/core/remote_execution/tool_execution.py +7 -5
- exponent/core/remote_execution/truncation.py +0 -2
- exponent/core/remote_execution/types.py +30 -1
- {indent-0.1.19.dist-info → indent-0.1.21.dist-info}/METADATA +1 -1
- {indent-0.1.19.dist-info → indent-0.1.21.dist-info}/RECORD +16 -16
- exponent/core/graphql/github_config_queries.py +0 -56
- {indent-0.1.19.dist-info → indent-0.1.21.dist-info}/WHEEL +0 -0
- {indent-0.1.19.dist-info → indent-0.1.21.dist-info}/entry_points.txt +0 -0
exponent/__init__.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.1.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
31
|
+
__version__ = version = '0.1.21'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 21)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -23,7 +23,6 @@ from exponent.core.graphql.client import GraphQLClient
|
|
|
23
23
|
from exponent.core.graphql.mutations import (
|
|
24
24
|
CREATE_CLOUD_CHAT_FROM_REPOSITORY_MUTATION,
|
|
25
25
|
ENABLE_CLOUD_REPOSITORY_MUTATION,
|
|
26
|
-
INCREMENTAL_BUILD_CLOUD_REPOSITORY_MUTATION,
|
|
27
26
|
REBUILD_CLOUD_REPOSITORY_MUTATION,
|
|
28
27
|
START_CHAT_TURN_MUTATION,
|
|
29
28
|
)
|
|
@@ -62,32 +61,6 @@ async def enable_cloud_repository(
|
|
|
62
61
|
return cast(dict[str, Any], result["enableCloudRepository"])
|
|
63
62
|
|
|
64
63
|
|
|
65
|
-
async def incremental_build_cloud_repository(
|
|
66
|
-
api_key: str,
|
|
67
|
-
base_api_url: str,
|
|
68
|
-
base_ws_url: str,
|
|
69
|
-
org_name: str,
|
|
70
|
-
repo_name: str,
|
|
71
|
-
) -> dict[str, Any]:
|
|
72
|
-
graphql_client = GraphQLClient(
|
|
73
|
-
api_key=api_key, base_api_url=base_api_url, base_ws_url=base_ws_url
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
variables = {
|
|
77
|
-
"orgName": org_name,
|
|
78
|
-
"repoName": repo_name,
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
result = await graphql_client.execute(
|
|
82
|
-
INCREMENTAL_BUILD_CLOUD_REPOSITORY_MUTATION,
|
|
83
|
-
variables,
|
|
84
|
-
"IncrementalBuildCloudRepository",
|
|
85
|
-
timeout=120,
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
return cast(dict[str, Any], result["incrementalBuildCloudRepository"])
|
|
89
|
-
|
|
90
|
-
|
|
91
64
|
async def rebuild_cloud_repository(
|
|
92
65
|
api_key: str,
|
|
93
66
|
base_api_url: str,
|
|
@@ -243,63 +216,6 @@ def enable_repo(
|
|
|
243
216
|
sys.exit(1)
|
|
244
217
|
|
|
245
218
|
|
|
246
|
-
@cloud_cli.command(hidden=True)
|
|
247
|
-
@click.option(
|
|
248
|
-
"--org-name",
|
|
249
|
-
help="GitHub organization name",
|
|
250
|
-
required=True,
|
|
251
|
-
)
|
|
252
|
-
@click.option(
|
|
253
|
-
"--repo-name",
|
|
254
|
-
help="GitHub repository name",
|
|
255
|
-
required=True,
|
|
256
|
-
)
|
|
257
|
-
@use_settings
|
|
258
|
-
def incremental_build(
|
|
259
|
-
settings: Settings,
|
|
260
|
-
org_name: str,
|
|
261
|
-
repo_name: str,
|
|
262
|
-
) -> None:
|
|
263
|
-
"""Test utility for incremental build of cloud repository."""
|
|
264
|
-
check_exponent_version_and_upgrade(settings)
|
|
265
|
-
|
|
266
|
-
if not settings.api_key:
|
|
267
|
-
redirect_to_login(settings)
|
|
268
|
-
return
|
|
269
|
-
|
|
270
|
-
loop = asyncio.get_event_loop()
|
|
271
|
-
|
|
272
|
-
api_key = settings.api_key
|
|
273
|
-
base_api_url = settings.get_base_api_url()
|
|
274
|
-
base_ws_url = settings.get_base_ws_url()
|
|
275
|
-
|
|
276
|
-
try:
|
|
277
|
-
result = loop.run_until_complete(
|
|
278
|
-
incremental_build_cloud_repository(
|
|
279
|
-
api_key, base_api_url, base_ws_url, org_name, repo_name
|
|
280
|
-
)
|
|
281
|
-
)
|
|
282
|
-
|
|
283
|
-
if result["__typename"] == "ContainerImage":
|
|
284
|
-
click.secho(
|
|
285
|
-
f"✓ Successfully triggered incremental build for {org_name}/{repo_name}",
|
|
286
|
-
fg="green",
|
|
287
|
-
)
|
|
288
|
-
click.echo(f" Build ref: {result.get('buildRef', 'N/A')}")
|
|
289
|
-
click.echo(f" Created at: {result.get('createdAt', 'N/A')}")
|
|
290
|
-
click.echo(f" Updated at: {result.get('updatedAt', 'N/A')}")
|
|
291
|
-
else:
|
|
292
|
-
click.secho(
|
|
293
|
-
f"✗ Failed to trigger incremental build: {result.get('message', 'Unknown error')}",
|
|
294
|
-
fg="red",
|
|
295
|
-
)
|
|
296
|
-
click.echo(f" Error type: {result['__typename']}")
|
|
297
|
-
|
|
298
|
-
except Exception as e:
|
|
299
|
-
click.secho(f"✗ Error triggering incremental build: {e!s}", fg="red")
|
|
300
|
-
sys.exit(1)
|
|
301
|
-
|
|
302
|
-
|
|
303
219
|
@cloud_cli.command(hidden=True)
|
|
304
220
|
@click.option(
|
|
305
221
|
"--org-name",
|
|
@@ -14,11 +14,6 @@ from exponent.commands.types import exponent_cli_group
|
|
|
14
14
|
from exponent.core.config import Settings
|
|
15
15
|
from exponent.core.graphql.client import GraphQLClient
|
|
16
16
|
from exponent.core.graphql.get_chats_query import GET_CHATS_QUERY
|
|
17
|
-
from exponent.core.graphql.github_config_queries import (
|
|
18
|
-
CHECK_GITHUB_CONFIG_VALIDITY_QUERY,
|
|
19
|
-
CREATE_GITHUB_CONFIG_MUTATION,
|
|
20
|
-
REPOS_FOR_GITHUB_CONFIG_QUERY,
|
|
21
|
-
)
|
|
22
17
|
from exponent.core.graphql.subscriptions import AUTHENTICATED_USER_SUBSCRIPTION
|
|
23
18
|
from exponent.utils.version import (
|
|
24
19
|
get_installed_metadata,
|
|
@@ -56,74 +51,6 @@ def debug(
|
|
|
56
51
|
click.echo(get_installed_metadata())
|
|
57
52
|
|
|
58
53
|
|
|
59
|
-
@config_cli.command(hidden=True)
|
|
60
|
-
@use_settings
|
|
61
|
-
def check_github_config_validity(
|
|
62
|
-
settings: Settings,
|
|
63
|
-
) -> None:
|
|
64
|
-
if not settings.api_key:
|
|
65
|
-
redirect_to_login(settings)
|
|
66
|
-
return
|
|
67
|
-
|
|
68
|
-
run_until_complete(
|
|
69
|
-
check_github_config_validity_task(
|
|
70
|
-
api_key=settings.api_key,
|
|
71
|
-
base_api_url=settings.get_base_api_url(),
|
|
72
|
-
base_ws_url=settings.get_base_ws_url(),
|
|
73
|
-
)
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
async def check_github_config_validity_task(
|
|
78
|
-
api_key: str,
|
|
79
|
-
base_api_url: str,
|
|
80
|
-
base_ws_url: str,
|
|
81
|
-
) -> None:
|
|
82
|
-
graphql_client = GraphQLClient(api_key, base_api_url, base_ws_url)
|
|
83
|
-
result = await graphql_client.execute(CHECK_GITHUB_CONFIG_VALIDITY_QUERY)
|
|
84
|
-
click.echo(result)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
@config_cli.command(hidden=True)
|
|
88
|
-
@use_settings
|
|
89
|
-
def repos_for_github_config(
|
|
90
|
-
settings: Settings,
|
|
91
|
-
) -> None:
|
|
92
|
-
if not settings.api_key:
|
|
93
|
-
redirect_to_login(settings)
|
|
94
|
-
return
|
|
95
|
-
|
|
96
|
-
run_until_complete(
|
|
97
|
-
repos_for_github_config_task(
|
|
98
|
-
api_key=settings.api_key,
|
|
99
|
-
base_api_url=settings.get_base_api_url(),
|
|
100
|
-
base_ws_url=settings.get_base_ws_url(),
|
|
101
|
-
)
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
async def repos_for_github_config_task(
|
|
106
|
-
api_key: str,
|
|
107
|
-
base_api_url: str,
|
|
108
|
-
base_ws_url: str,
|
|
109
|
-
) -> None:
|
|
110
|
-
graphql_client = GraphQLClient(api_key, base_api_url, base_ws_url)
|
|
111
|
-
try:
|
|
112
|
-
click.echo("Sending request to fetch repos...")
|
|
113
|
-
result = await graphql_client.execute(
|
|
114
|
-
REPOS_FOR_GITHUB_CONFIG_QUERY, timeout=120
|
|
115
|
-
) # 120 seconds timeout
|
|
116
|
-
click.echo("Request completed. Result:")
|
|
117
|
-
click.echo(result)
|
|
118
|
-
except Exception as e:
|
|
119
|
-
click.echo(f"An error occurred while fetching repos: {e!s}")
|
|
120
|
-
click.echo(f"Error type: {type(e).__name__}")
|
|
121
|
-
# Add more detailed error information if available
|
|
122
|
-
if hasattr(e, "response"):
|
|
123
|
-
click.echo(f"Response status: {e.response.status_code}")
|
|
124
|
-
click.echo(f"Response content: {e.response.text}")
|
|
125
|
-
|
|
126
|
-
|
|
127
54
|
@config_cli.command(hidden=True)
|
|
128
55
|
@click.option(
|
|
129
56
|
"--set-git-warning-disabled",
|
|
@@ -389,20 +316,6 @@ async def get_authenticated_user_task(
|
|
|
389
316
|
click.echo(it)
|
|
390
317
|
|
|
391
318
|
|
|
392
|
-
async def create_github_config_task(
|
|
393
|
-
api_key: str,
|
|
394
|
-
base_api_url: str,
|
|
395
|
-
base_ws_url: str,
|
|
396
|
-
github_pat: str,
|
|
397
|
-
) -> None:
|
|
398
|
-
graphql_client = GraphQLClient(api_key, base_api_url, base_ws_url)
|
|
399
|
-
variables = {
|
|
400
|
-
"githubPat": github_pat,
|
|
401
|
-
}
|
|
402
|
-
result = await graphql_client.execute(CREATE_GITHUB_CONFIG_MUTATION, variables)
|
|
403
|
-
click.echo(result)
|
|
404
|
-
|
|
405
|
-
|
|
406
319
|
@config_cli.command(hidden=True)
|
|
407
320
|
@use_settings
|
|
408
321
|
def refresh_key(settings: Settings) -> None:
|
|
@@ -97,7 +97,7 @@ def run(
|
|
|
97
97
|
create_chat(api_key, base_api_url, base_ws_url, ChatSource.CLI_RUN)
|
|
98
98
|
)
|
|
99
99
|
|
|
100
|
-
if timeout_seconds
|
|
100
|
+
if isinstance(timeout_seconds, int) and timeout_seconds <= 0:
|
|
101
101
|
click.secho("Error: --timeout-seconds must be a positive integer", fg="red")
|
|
102
102
|
sys.exit(1)
|
|
103
103
|
|
|
@@ -131,35 +131,6 @@ mutation EnableCloudRepository($orgName: String!, $repoName: String!) {
|
|
|
131
131
|
"""
|
|
132
132
|
|
|
133
133
|
|
|
134
|
-
INCREMENTAL_BUILD_CLOUD_REPOSITORY_MUTATION = """
|
|
135
|
-
mutation IncrementalBuildCloudRepository($orgName: String!, $repoName: String!) {
|
|
136
|
-
incrementalBuildCloudRepository(orgName: $orgName, repoName: $repoName) {
|
|
137
|
-
__typename
|
|
138
|
-
...on ContainerImage {
|
|
139
|
-
buildRef
|
|
140
|
-
createdAt
|
|
141
|
-
updatedAt
|
|
142
|
-
}
|
|
143
|
-
...on UnauthenticatedError {
|
|
144
|
-
message
|
|
145
|
-
}
|
|
146
|
-
...on CloudConfigNotFoundError {
|
|
147
|
-
message
|
|
148
|
-
}
|
|
149
|
-
...on GithubConfigNotFoundError {
|
|
150
|
-
message
|
|
151
|
-
}
|
|
152
|
-
...on CloudSessionError {
|
|
153
|
-
message
|
|
154
|
-
}
|
|
155
|
-
...on Error {
|
|
156
|
-
message
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
"""
|
|
161
|
-
|
|
162
|
-
|
|
163
134
|
REBUILD_CLOUD_REPOSITORY_MUTATION = """
|
|
164
135
|
mutation RebuildCloudRepository($orgName: String!, $repoName: String!) {
|
|
165
136
|
rebuildCloudRepository(orgName: $orgName, repoName: $repoName) {
|
|
@@ -48,6 +48,7 @@ class ToolResult(msgspec.Struct, tag_field="tool_name", omit_defaults=True):
|
|
|
48
48
|
|
|
49
49
|
class ErrorToolResult(ToolResult, tag="error"):
|
|
50
50
|
error_message: str
|
|
51
|
+
is_assistant_error: bool = False
|
|
51
52
|
|
|
52
53
|
|
|
53
54
|
READ_TOOL_NAME = "read"
|
|
@@ -65,14 +66,28 @@ class FileMetadata(msgspec.Struct):
|
|
|
65
66
|
file_mode: str
|
|
66
67
|
|
|
67
68
|
|
|
69
|
+
class ReadToolArtifactResult(ToolResult, tag=READ_TOOL_ARTIFACT_NAME):
|
|
70
|
+
s3_uri: str
|
|
71
|
+
file_path: str
|
|
72
|
+
media_type: str
|
|
73
|
+
|
|
74
|
+
def to_text(self) -> str:
|
|
75
|
+
return f"[Image artifact uploaded to {self.s3_uri}]"
|
|
76
|
+
|
|
77
|
+
|
|
68
78
|
class ReadToolResult(ToolResult, tag=READ_TOOL_NAME):
|
|
69
|
-
content: str
|
|
70
|
-
num_lines: int
|
|
71
|
-
start_line: int
|
|
72
|
-
total_lines: int
|
|
79
|
+
content: str | None = None
|
|
80
|
+
num_lines: int | None = None
|
|
81
|
+
start_line: int | None = None
|
|
82
|
+
total_lines: int | None = None
|
|
73
83
|
metadata: FileMetadata | None = None
|
|
84
|
+
artifact: ReadToolArtifactResult | None = None
|
|
74
85
|
|
|
75
86
|
def to_text(self) -> str:
|
|
87
|
+
if self.artifact:
|
|
88
|
+
return self.artifact.to_text()
|
|
89
|
+
assert self.content is not None
|
|
90
|
+
assert self.start_line is not None
|
|
76
91
|
lines = self.content.splitlines()
|
|
77
92
|
lines = [
|
|
78
93
|
f"{str(i).rjust(6)}→{line}"
|
|
@@ -81,15 +96,6 @@ class ReadToolResult(ToolResult, tag=READ_TOOL_NAME):
|
|
|
81
96
|
return "\n".join(lines)
|
|
82
97
|
|
|
83
98
|
|
|
84
|
-
class ReadToolArtifactResult(ToolResult, tag=READ_TOOL_ARTIFACT_NAME):
|
|
85
|
-
s3_uri: str
|
|
86
|
-
file_path: str
|
|
87
|
-
media_type: str
|
|
88
|
-
|
|
89
|
-
def to_text(self) -> str:
|
|
90
|
-
return f"[Image artifact uploaded to {self.s3_uri}]"
|
|
91
|
-
|
|
92
|
-
|
|
93
99
|
LIST_TOOL_NAME = "ls"
|
|
94
100
|
|
|
95
101
|
|
|
@@ -272,6 +278,10 @@ class TerminateResponse(msgspec.Struct, tag="terminate"):
|
|
|
272
278
|
pass
|
|
273
279
|
|
|
274
280
|
|
|
281
|
+
class TimeoutResponse(msgspec.Struct, tag="timeout"):
|
|
282
|
+
pass
|
|
283
|
+
|
|
284
|
+
|
|
275
285
|
class BatchToolExecutionResponse(msgspec.Struct, tag="batch_tool_execution"):
|
|
276
286
|
tool_results: list[ToolResultType]
|
|
277
287
|
|
|
@@ -323,6 +333,7 @@ class CliRpcResponse(msgspec.Struct):
|
|
|
323
333
|
| GetAllFilesResponse
|
|
324
334
|
| ErrorResponse
|
|
325
335
|
| TerminateResponse
|
|
336
|
+
| TimeoutResponse
|
|
326
337
|
| BatchToolExecutionResponse
|
|
327
338
|
| HttpResponse
|
|
328
339
|
| SwitchCLIChatResponse
|
|
@@ -413,6 +413,7 @@ class RemoteExecutionClient:
|
|
|
413
413
|
results: asyncio.Queue[CliRpcResponse],
|
|
414
414
|
) -> REMOTE_EXECUTION_CLIENT_EXIT_INFO:
|
|
415
415
|
"""Process messages from the websocket connection."""
|
|
416
|
+
pending: set[asyncio.Task[object]] = set()
|
|
416
417
|
try:
|
|
417
418
|
recv = asyncio.create_task(websocket.recv())
|
|
418
419
|
get_beat = asyncio.create_task(beats.get())
|
|
@@ -462,6 +463,9 @@ class RemoteExecutionClient:
|
|
|
462
463
|
self,
|
|
463
464
|
websocket: ClientConnection,
|
|
464
465
|
connection_tracker: ConnectionTracker | None,
|
|
466
|
+
beats: asyncio.Queue[HeartbeatInfo],
|
|
467
|
+
requests: asyncio.Queue[CliRpcRequest],
|
|
468
|
+
results: asyncio.Queue[CliRpcResponse],
|
|
465
469
|
) -> REMOTE_EXECUTION_CLIENT_EXIT_INFO | None:
|
|
466
470
|
"""Handle a single websocket connection.
|
|
467
471
|
Returns None to continue with reconnection attempts, or an exit info to terminate."""
|
|
@@ -470,12 +474,6 @@ class RemoteExecutionClient:
|
|
|
470
474
|
|
|
471
475
|
self._websocket = websocket
|
|
472
476
|
|
|
473
|
-
beats: asyncio.Queue[HeartbeatInfo] = asyncio.Queue()
|
|
474
|
-
requests: asyncio.Queue[CliRpcRequest] = asyncio.Queue()
|
|
475
|
-
results: asyncio.Queue[CliRpcResponse] = asyncio.Queue()
|
|
476
|
-
|
|
477
|
-
tasks = await self._setup_tasks(beats, requests, results)
|
|
478
|
-
|
|
479
477
|
try:
|
|
480
478
|
return await self._process_websocket_messages(
|
|
481
479
|
websocket, beats, requests, results
|
|
@@ -493,15 +491,13 @@ class RemoteExecutionClient:
|
|
|
493
491
|
)
|
|
494
492
|
return WSDisconnected(error_message=error_message)
|
|
495
493
|
# Otherwise, allow reconnection attempt
|
|
494
|
+
logger.debug("Websocket connection closed by remote.")
|
|
496
495
|
return None
|
|
497
496
|
except TimeoutError:
|
|
498
497
|
# Timeout, allow reconnection attempt
|
|
499
498
|
# TODO: investgate if this is needed, possibly scope it down
|
|
500
499
|
return None
|
|
501
500
|
finally:
|
|
502
|
-
for task in tasks:
|
|
503
|
-
task.cancel()
|
|
504
|
-
await asyncio.gather(*tasks, return_exceptions=True)
|
|
505
501
|
if connection_tracker is not None:
|
|
506
502
|
await connection_tracker.set_connected(False)
|
|
507
503
|
|
|
@@ -517,33 +513,51 @@ class RemoteExecutionClient:
|
|
|
517
513
|
# Initialize last request time for timeout monitoring
|
|
518
514
|
self._last_request_time = time.time()
|
|
519
515
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
[
|
|
525
|
-
asyncio.create_task(
|
|
526
|
-
self._handle_websocket_connection(websocket, connection_tracker)
|
|
527
|
-
),
|
|
528
|
-
asyncio.create_task(self._timeout_monitor(timeout_seconds)),
|
|
529
|
-
],
|
|
530
|
-
return_when=asyncio.FIRST_COMPLETED,
|
|
531
|
-
)
|
|
516
|
+
# Create queues ONCE - persist across reconnections
|
|
517
|
+
beats: asyncio.Queue[HeartbeatInfo] = asyncio.Queue()
|
|
518
|
+
requests: asyncio.Queue[CliRpcRequest] = asyncio.Queue()
|
|
519
|
+
results: asyncio.Queue[CliRpcResponse] = asyncio.Queue()
|
|
532
520
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
task.cancel()
|
|
521
|
+
# Create tasks ONCE - persist across reconnections
|
|
522
|
+
executors = await self._setup_tasks(beats, requests, results)
|
|
536
523
|
|
|
537
|
-
|
|
538
|
-
for
|
|
539
|
-
|
|
540
|
-
# If
|
|
541
|
-
|
|
542
|
-
|
|
524
|
+
try:
|
|
525
|
+
async for websocket in self.ws_connect(f"/api/ws/chat/{chat_uuid}"):
|
|
526
|
+
# Always run connection and timeout monitor concurrently
|
|
527
|
+
# If timeout_seconds is None, timeout monitor will loop indefinitely
|
|
528
|
+
done, pending = await asyncio.wait(
|
|
529
|
+
[
|
|
530
|
+
asyncio.create_task(
|
|
531
|
+
self._handle_websocket_connection(
|
|
532
|
+
websocket, connection_tracker, beats, requests, results
|
|
533
|
+
)
|
|
534
|
+
),
|
|
535
|
+
asyncio.create_task(self._timeout_monitor(timeout_seconds)),
|
|
536
|
+
],
|
|
537
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
538
|
+
)
|
|
543
539
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
540
|
+
# Cancel pending tasks
|
|
541
|
+
for task in pending:
|
|
542
|
+
task.cancel()
|
|
543
|
+
|
|
544
|
+
# Return result from completed task
|
|
545
|
+
for task in done:
|
|
546
|
+
result = await task
|
|
547
|
+
# If we get None, we'll try to reconnect
|
|
548
|
+
if result is not None:
|
|
549
|
+
return result
|
|
550
|
+
|
|
551
|
+
# If we exit the websocket connection loop without returning,
|
|
552
|
+
# it means we couldn't establish a connection
|
|
553
|
+
return WSDisconnected(
|
|
554
|
+
error_message="Could not establish websocket connection"
|
|
555
|
+
)
|
|
556
|
+
finally:
|
|
557
|
+
# Cancel all background tasks when exiting
|
|
558
|
+
for task in executors:
|
|
559
|
+
task.cancel()
|
|
560
|
+
await asyncio.gather(*executors, return_exceptions=True)
|
|
547
561
|
|
|
548
562
|
async def create_chat(self, chat_source: ChatSource) -> CreateChatResponse:
|
|
549
563
|
response = await self.api_client.post(
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_default_env() -> dict[str, str]:
|
|
5
|
+
"""
|
|
6
|
+
Returns default environment variables for CLI-spawned processes.
|
|
7
|
+
These are merged with the parent process environment.
|
|
8
|
+
"""
|
|
9
|
+
return {
|
|
10
|
+
"GIT_EDITOR": "true",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_process_env(env_overrides: dict[str, str] | None = None) -> dict[str, str]:
|
|
15
|
+
"""
|
|
16
|
+
Returns the complete environment for spawned processes.
|
|
17
|
+
Merges parent environment with default variables, then applies overrides.
|
|
18
|
+
|
|
19
|
+
Priority order (lowest to highest):
|
|
20
|
+
1. Parent process environment (os.environ)
|
|
21
|
+
2. Default environment variables (get_default_env())
|
|
22
|
+
3. Explicit overrides (env_overrides parameter)
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
env_overrides: Optional dict of environment variables that override defaults
|
|
26
|
+
"""
|
|
27
|
+
env = os.environ.copy()
|
|
28
|
+
env.update(get_default_env())
|
|
29
|
+
if env_overrides:
|
|
30
|
+
env.update(env_overrides)
|
|
31
|
+
return env
|
|
@@ -6,6 +6,7 @@ import signal
|
|
|
6
6
|
from collections.abc import AsyncGenerator, Callable
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
+
from exponent.core.remote_execution.default_env import get_process_env
|
|
9
10
|
from exponent.core.remote_execution.languages.types import (
|
|
10
11
|
ShellExecutionResult,
|
|
11
12
|
StreamedOutputPiece,
|
|
@@ -52,7 +53,7 @@ async def read_stream(
|
|
|
52
53
|
data = await stream.read(4096)
|
|
53
54
|
if not data:
|
|
54
55
|
break
|
|
55
|
-
chunk = data.decode(encoding=encoding)
|
|
56
|
+
chunk = data.decode(encoding=encoding, errors="replace")
|
|
56
57
|
output.append((fd, chunk))
|
|
57
58
|
yield StreamedOutputPiece(content=chunk)
|
|
58
59
|
except UnicodeDecodeError:
|
|
@@ -80,6 +81,7 @@ async def execute_shell_streaming( # noqa: PLR0915
|
|
|
80
81
|
working_directory: str,
|
|
81
82
|
timeout: int,
|
|
82
83
|
should_halt: Callable[[], bool] | None = None,
|
|
84
|
+
env: dict[str, str] | None = None,
|
|
83
85
|
) -> AsyncGenerator[StreamedOutputPiece | ShellExecutionResult, None]:
|
|
84
86
|
timeout_seconds = min(timeout, MAX_TIMEOUT)
|
|
85
87
|
|
|
@@ -91,6 +93,7 @@ async def execute_shell_streaming( # noqa: PLR0915
|
|
|
91
93
|
stdout=asyncio.subprocess.PIPE,
|
|
92
94
|
stderr=asyncio.subprocess.PIPE,
|
|
93
95
|
cwd=working_directory,
|
|
96
|
+
env=get_process_env(env),
|
|
94
97
|
)
|
|
95
98
|
else:
|
|
96
99
|
# Add rc file sourcing to the command
|
|
@@ -105,6 +108,7 @@ async def execute_shell_streaming( # noqa: PLR0915
|
|
|
105
108
|
stdout=asyncio.subprocess.PIPE,
|
|
106
109
|
stderr=asyncio.subprocess.PIPE,
|
|
107
110
|
cwd=working_directory,
|
|
111
|
+
env=get_process_env(env),
|
|
108
112
|
start_new_session=True if platform.system() != "Windows" else False,
|
|
109
113
|
)
|
|
110
114
|
|
|
@@ -90,7 +90,7 @@ async def execute_read_file( # noqa: PLR0911, PLR0915
|
|
|
90
90
|
tool_input: ReadToolInput,
|
|
91
91
|
working_directory: str,
|
|
92
92
|
upload_client: "RemoteExecutionClient | None" = None,
|
|
93
|
-
) -> ReadToolResult |
|
|
93
|
+
) -> ReadToolResult | ErrorToolResult:
|
|
94
94
|
# Validate absolute path requirement
|
|
95
95
|
if not tool_input.file_path.startswith("/"):
|
|
96
96
|
return ErrorToolResult(
|
|
@@ -136,10 +136,12 @@ async def execute_read_file( # noqa: PLR0911, PLR0915
|
|
|
136
136
|
if status != 200:
|
|
137
137
|
raise RuntimeError(f"Upload failed with status {status}")
|
|
138
138
|
|
|
139
|
-
return
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
return ReadToolResult(
|
|
140
|
+
artifact=ReadToolArtifactResult(
|
|
141
|
+
s3_uri=upload_response.s3_uri,
|
|
142
|
+
file_path=tool_input.file_path,
|
|
143
|
+
media_type=media_type,
|
|
144
|
+
)
|
|
143
145
|
)
|
|
144
146
|
except Exception as e:
|
|
145
147
|
return ErrorToolResult(error_message=f"Failed to upload image to S3: {e!s}")
|
|
@@ -11,7 +11,6 @@ from exponent.core.remote_execution.cli_rpc_types import (
|
|
|
11
11
|
GlobToolResult,
|
|
12
12
|
GrepToolResult,
|
|
13
13
|
ListToolResult,
|
|
14
|
-
ReadToolArtifactResult,
|
|
15
14
|
ReadToolResult,
|
|
16
15
|
ToolResult,
|
|
17
16
|
WriteToolResult,
|
|
@@ -273,7 +272,6 @@ class StringListTruncation(TruncationStrategy):
|
|
|
273
272
|
|
|
274
273
|
TRUNCATION_REGISTRY: dict[type[ToolResult], TruncationStrategy] = {
|
|
275
274
|
ReadToolResult: StringFieldTruncation("content"),
|
|
276
|
-
ReadToolArtifactResult: NoOpTruncation(),
|
|
277
275
|
WriteToolResult: StringFieldTruncation("message"),
|
|
278
276
|
BashToolResult: TailTruncation("shell_output"),
|
|
279
277
|
GrepToolResult: StringListTruncation("matches"),
|
|
@@ -50,6 +50,7 @@ class PrReviewWorkflowInput(BaseModel):
|
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
class SlackWorkflowInput(BaseModel):
|
|
53
|
+
discriminator: Literal["slack_workflow"] = "slack_workflow"
|
|
53
54
|
channel_id: str
|
|
54
55
|
thread_ts: str
|
|
55
56
|
slack_url: str | None = None
|
|
@@ -57,7 +58,34 @@ class SlackWorkflowInput(BaseModel):
|
|
|
57
58
|
message_ts: str | None = None
|
|
58
59
|
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
class SlackPlanApprovalWorkflowInput(BaseModel):
|
|
62
|
+
discriminator: Literal["slack_plan_approval"] = "slack_plan_approval"
|
|
63
|
+
channel_id: str
|
|
64
|
+
thread_ts: str
|
|
65
|
+
slack_url: str
|
|
66
|
+
channel_name: str
|
|
67
|
+
message_ts: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class SentryWorkflowInput(BaseModel):
|
|
71
|
+
title: str
|
|
72
|
+
issue_id: str
|
|
73
|
+
permalink: str
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class GenericCloudWorkflowInput(BaseModel):
|
|
77
|
+
initial_prompt: str
|
|
78
|
+
system_prompt_override: str | None = None
|
|
79
|
+
reasoning_level: str = "LOW"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
WorkflowInput = (
|
|
83
|
+
PrReviewWorkflowInput
|
|
84
|
+
| SlackWorkflowInput
|
|
85
|
+
| SentryWorkflowInput
|
|
86
|
+
| GenericCloudWorkflowInput
|
|
87
|
+
| SlackPlanApprovalWorkflowInput
|
|
88
|
+
)
|
|
61
89
|
|
|
62
90
|
|
|
63
91
|
class WorkflowTriggerRequest(BaseModel):
|
|
@@ -542,6 +570,7 @@ class ChatSource(str, Enum):
|
|
|
542
570
|
DESKTOP_APP = "DESKTOP_APP"
|
|
543
571
|
VSCODE_EXTENSION = "VSCODE_EXTENSION"
|
|
544
572
|
SLACK_APP = "SLACK_APP"
|
|
573
|
+
SENTRY_APP = "SENTRY_APP"
|
|
545
574
|
|
|
546
575
|
|
|
547
576
|
class CLIConnectedState(BaseModel):
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
exponent/__init__.py,sha256=
|
|
1
|
+
exponent/__init__.py,sha256=QwRysHnO-cJwIFSvXReHDj93DEPhlibOTtnnb25J9-c,706
|
|
2
2
|
exponent/cli.py,sha256=QnIeDTgWaQJrRs5WESCkQpVEQiJiAO4qWgB0rYlkd78,3344
|
|
3
3
|
exponent/py.typed,sha256=9XZl5avs8yHp89XP_1Fjtbeg_2rjYorCC9I0k_j-h2c,334
|
|
4
|
-
exponent/commands/cloud_commands.py,sha256=
|
|
4
|
+
exponent/commands/cloud_commands.py,sha256=_qivNDIHIJXxLgVUm5vUNyuzG15llMqB8xY2MZ7F_lc,16710
|
|
5
5
|
exponent/commands/common.py,sha256=M2KI9yKjB8fecPoDBphMa123c35-iNeaE9q4DxhkaFU,12817
|
|
6
|
-
exponent/commands/config_commands.py,sha256=
|
|
7
|
-
exponent/commands/run_commands.py,sha256=
|
|
6
|
+
exponent/commands/config_commands.py,sha256=mmQYuyRosODgawoHWsn9xnWnV37GiQaxJjMv-_xreAU,8902
|
|
7
|
+
exponent/commands/run_commands.py,sha256=xn0SJX0PPrmHu8Nh-kG-lJBhGC4qFCc04aLIzdVyFho,6389
|
|
8
8
|
exponent/commands/settings.py,sha256=UwwwoCgCY5hzAFD9slOBbA9Gr1hNfoyJ2blsFDC6V8w,1559
|
|
9
9
|
exponent/commands/types.py,sha256=iDJL3hdwhO1PrhsJTJBioNYSKo0CWV8Nv-ONcDaWIRs,3670
|
|
10
10
|
exponent/commands/upgrade.py,sha256=JZr0sNazziuLByQHdT8GZb-lDbRG1YpHW8VB94q-r8w,803
|
|
@@ -13,14 +13,14 @@ exponent/core/config.py,sha256=TNFLUgLnfSocRMVSav_7E4VcaNHXZ_3Mg5Lp1smP46U,5731
|
|
|
13
13
|
exponent/core/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
14
|
exponent/core/graphql/client.py,sha256=SRagD3YPyoYZSO1RtfO-OXD7b5dm1NvgoL6CTbN380o,2009
|
|
15
15
|
exponent/core/graphql/get_chats_query.py,sha256=9-2N1VfapXUZB3IFIKw5U_gKdmfyviJp5JSUntB_Yyk,1177
|
|
16
|
-
exponent/core/graphql/
|
|
17
|
-
exponent/core/graphql/mutations.py,sha256=cAAiSefyamBgy1zJEZ7LNk0rbjwawTPxMyj8eU6yRRI,3502
|
|
16
|
+
exponent/core/graphql/mutations.py,sha256=eVoazBZowqLk3Jz-67tR_ATqrQKPhk5Teeq7YV-70b0,2931
|
|
18
17
|
exponent/core/graphql/queries.py,sha256=RYsk8bub0esspqgakilhzX07yJf2652Ey9tBZK1l_lY,3297
|
|
19
18
|
exponent/core/graphql/subscriptions.py,sha256=SQngv_nYVNJjiZ_P2k0UcLIu1pzc4vi7q7lhH89NCZM,393
|
|
20
19
|
exponent/core/remote_execution/checkpoints.py,sha256=3QGYMLa8vT7XmxMYTRcGrW8kNGHwRC0AkUfULribJWg,6354
|
|
21
|
-
exponent/core/remote_execution/cli_rpc_types.py,sha256=
|
|
22
|
-
exponent/core/remote_execution/client.py,sha256=
|
|
20
|
+
exponent/core/remote_execution/cli_rpc_types.py,sha256=1HPLLtzpeGJfkh8nXlo8d_zDajzfDIwLu83Cfm60Cnc,8642
|
|
21
|
+
exponent/core/remote_execution/client.py,sha256=dG2ueANYJH8CznDT5L3-UApkWZ25ANheSvv62JCMq9c,32038
|
|
23
22
|
exponent/core/remote_execution/code_execution.py,sha256=jYPB_7dJzS9BTPLX9fKQpsFPatwjbXuaFFSxT9tDTfI,2388
|
|
23
|
+
exponent/core/remote_execution/default_env.py,sha256=s44A1Cz9EgYuhF17WO3ESVNSLQw57EoOLyi9k6qliIo,911
|
|
24
24
|
exponent/core/remote_execution/error_info.py,sha256=Rd7OA3ps06qYejPVcOaMBB9AtftP3wqQoOfiILFASnc,1378
|
|
25
25
|
exponent/core/remote_execution/exceptions.py,sha256=eT57lBnBhvh-KJ5lsKWcfgGA5-WisAxhjZx-Z6OupZY,135
|
|
26
26
|
exponent/core/remote_execution/file_write.py,sha256=8Sa70ANIDHGxIAq4_Uy2Qoo55K7-cSzU3282zyu7hG8,978
|
|
@@ -29,13 +29,13 @@ exponent/core/remote_execution/git.py,sha256=dGjBpeoKJZsYgRwctSq29GmbsNIN9tbSA3V
|
|
|
29
29
|
exponent/core/remote_execution/http_fetch.py,sha256=aFEyXd0S-MRfisSMuIFiEyc1AEAj9nUZ9Rj_P_YRows,2827
|
|
30
30
|
exponent/core/remote_execution/session.py,sha256=jlQIdeUj0f7uOk3BgzlJtBJ_GyTIjCchBp5ApQuF2-I,3847
|
|
31
31
|
exponent/core/remote_execution/system_context.py,sha256=QY1zY8_fWj3sh-fmLYtewvgxh7uTX4ITIJqlUTDkj6c,648
|
|
32
|
-
exponent/core/remote_execution/tool_execution.py,sha256=
|
|
32
|
+
exponent/core/remote_execution/tool_execution.py,sha256=DVO7jPS6JUGjY3GTevvNn5NlieKDOcx8id_nhi9x6Og,15783
|
|
33
33
|
exponent/core/remote_execution/tool_type_utils.py,sha256=7qi6Qd8fvHts019ZSLPbtiy17BUqgqBg3P_gdfvFf7w,1301
|
|
34
|
-
exponent/core/remote_execution/truncation.py,sha256=
|
|
35
|
-
exponent/core/remote_execution/types.py,sha256=
|
|
34
|
+
exponent/core/remote_execution/truncation.py,sha256=crHzjcUxL3tVe8yuoWNAE5r-uQKjtL-GzZczd-ucrd0,9849
|
|
35
|
+
exponent/core/remote_execution/types.py,sha256=fUT5-3TE3sA2dUqWaen9VgpEllz4RA0B3i-FJpEEXOs,15601
|
|
36
36
|
exponent/core/remote_execution/utils.py,sha256=6PlBqYJ3OQwZ0dgXiIu3br04a-d-glDeDZpD0XGGPAE,14793
|
|
37
37
|
exponent/core/remote_execution/languages/python_execution.py,sha256=nsX_LsXcUcHhiEHpSTjOTVNd7CxM146al0kw_iQX5OU,7724
|
|
38
|
-
exponent/core/remote_execution/languages/shell_streaming.py,sha256=
|
|
38
|
+
exponent/core/remote_execution/languages/shell_streaming.py,sha256=gBACa5uFMGuQzEFpKE61ww6niHWFe58NToI1HaIYGVU,7662
|
|
39
39
|
exponent/core/remote_execution/languages/types.py,sha256=f7FjSRNRSga-ZaE3LddDhxCirUVjlSYMEdoskG6Pta4,314
|
|
40
40
|
exponent/core/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
41
|
exponent/core/types/command_data.py,sha256=_HqQsnamRZeVoVaTpeO3ecVUzNBdG62WXlFy6Q7rtUM,5294
|
|
@@ -46,7 +46,7 @@ exponent/migration-docs/login.md,sha256=KIeXy3m2nzSUgw-4PW1XzXfHael1D4Zu93CplLMb
|
|
|
46
46
|
exponent/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
47
|
exponent/utils/colors.py,sha256=HBkqe_ZmhJ9YiL2Fpulqek4KvLS5mwBTY4LQSM5N8SM,2762
|
|
48
48
|
exponent/utils/version.py,sha256=GHZ9ET1kMyDubJZU3w2sah5Pw8XpiEakS5IOlt3wUnQ,8888
|
|
49
|
-
indent-0.1.
|
|
50
|
-
indent-0.1.
|
|
51
|
-
indent-0.1.
|
|
52
|
-
indent-0.1.
|
|
49
|
+
indent-0.1.21.dist-info/METADATA,sha256=kUlmIYOdjCG1iPkNC0oFscWTh9tsFMVuFUG_MPmNltQ,1308
|
|
50
|
+
indent-0.1.21.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
51
|
+
indent-0.1.21.dist-info/entry_points.txt,sha256=q8q1t1sbl4NULGOR0OV5RmSG4KEjkpEQRU_RUXEGzcs,44
|
|
52
|
+
indent-0.1.21.dist-info/RECORD,,
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
CREATE_GITHUB_CONFIG_MUTATION: str = """
|
|
2
|
-
mutation CreateGithubConfig(
|
|
3
|
-
$githubPat: String!,
|
|
4
|
-
) {
|
|
5
|
-
createGithubConfig(
|
|
6
|
-
githubPat: $githubPat
|
|
7
|
-
) {
|
|
8
|
-
__typename
|
|
9
|
-
... on GithubConfig {
|
|
10
|
-
githubConfigUuid
|
|
11
|
-
githubPat
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
CHECK_GITHUB_CONFIG_VALIDITY_QUERY: str = """
|
|
18
|
-
query CheckGithubConfigValidity {
|
|
19
|
-
checkGithubConfigValidity {
|
|
20
|
-
__typename
|
|
21
|
-
... on GithubConfigValidityResult {
|
|
22
|
-
isValid
|
|
23
|
-
message
|
|
24
|
-
}
|
|
25
|
-
... on Error {
|
|
26
|
-
message
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
REPOS_FOR_GITHUB_CONFIG_QUERY: str = """
|
|
33
|
-
query ReposForGithubConfig {
|
|
34
|
-
reposForGithubConfig {
|
|
35
|
-
__typename
|
|
36
|
-
... on GithubConfigRepos {
|
|
37
|
-
repos {
|
|
38
|
-
id
|
|
39
|
-
name
|
|
40
|
-
fullName
|
|
41
|
-
private
|
|
42
|
-
owner
|
|
43
|
-
description
|
|
44
|
-
}
|
|
45
|
-
orgs {
|
|
46
|
-
login
|
|
47
|
-
id
|
|
48
|
-
url
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
... on Error {
|
|
52
|
-
message
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
"""
|
|
File without changes
|
|
File without changes
|