indent 0.1.7__tar.gz → 0.1.8__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.
Potentially problematic release.
This version of indent might be problematic. Click here for more details.
- {indent-0.1.7 → indent-0.1.8}/.gitignore +2 -1
- {indent-0.1.7 → indent-0.1.8}/PKG-INFO +2 -2
- indent-0.1.8/exponent/__init__.py +1 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/graphql/queries.py +13 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/graphql/subscriptions.py +13 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/cli_rpc_types.py +25 -1
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/client.py +22 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/files.py +0 -12
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/http_fetch.py +23 -15
- indent-0.1.8/exponent/core/remote_execution/system_context.py +27 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/types.py +0 -50
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/utils.py +3 -98
- {indent-0.1.7 → indent-0.1.8}/pyproject.toml +3 -3
- indent-0.1.7/exponent/__init__.py +0 -1
- indent-0.1.7/exponent/core/remote_execution/system_context.py +0 -54
- {indent-0.1.7 → indent-0.1.8}/exponent/cli.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/commands/cloud_commands.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/commands/common.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/commands/config_commands.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/commands/github_app_commands.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/commands/listen_commands.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/commands/run_commands.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/commands/settings.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/commands/shell_commands.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/commands/theme.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/commands/types.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/commands/upgrade.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/commands/utils.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/commands/workflow_commands.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/config.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/graphql/__init__.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/graphql/client.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/graphql/cloud_config_queries.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/graphql/get_chats_query.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/graphql/github_config_queries.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/graphql/mutations.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/checkpoints.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/code_execution.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/error_info.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/exceptions.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/file_write.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/git.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/languages/python_execution.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/languages/shell_streaming.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/languages/types.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/session.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/tool_execution.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/truncation.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/types/__init__.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/types/command_data.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/types/event_types.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/types/generated/__init__.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/core/types/generated/strategy_info.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/migration-docs/login.md +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/py.typed +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/utils/__init__.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/utils/colors.py +0 -0
- {indent-0.1.7 → indent-0.1.8}/exponent/utils/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: indent
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: Indent is an AI Pair Programmer
|
|
5
5
|
Author-email: Sashank Thupukari <sashank@exponent.run>
|
|
6
6
|
Requires-Python: <3.13,>=3.10
|
|
@@ -15,7 +15,7 @@ Requires-Dist: eval-type-backport<0.3,>=0.2.0
|
|
|
15
15
|
Requires-Dist: git-python>=1.0.3
|
|
16
16
|
Requires-Dist: gitignore-parser<0.2,>=0.1.11
|
|
17
17
|
Requires-Dist: gql[httpx,websockets]<4,>=3.5.0
|
|
18
|
-
Requires-Dist: httpx
|
|
18
|
+
Requires-Dist: httpx>=0.28.1
|
|
19
19
|
Requires-Dist: ipykernel<7,>=6.29.4
|
|
20
20
|
Requires-Dist: jupyter-client<9,>=8.6.1
|
|
21
21
|
Requires-Dist: msgspec>=0.19.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.8" # Keep in sync with pyproject.toml
|
|
@@ -15,6 +15,19 @@ EVENTS_FOR_CHAT_QUERY: str = """query EventsForChat($chatUuid: UUID!) {
|
|
|
15
15
|
... on TextMessage {
|
|
16
16
|
text
|
|
17
17
|
}
|
|
18
|
+
... on ToolCallMessage {
|
|
19
|
+
messageId
|
|
20
|
+
toolUseId
|
|
21
|
+
toolName
|
|
22
|
+
toolInput {
|
|
23
|
+
... on BashToolInput {
|
|
24
|
+
command
|
|
25
|
+
}
|
|
26
|
+
... on ReadToolInput {
|
|
27
|
+
filePath
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
18
31
|
... on ToolResultMessage {
|
|
19
32
|
messageId
|
|
20
33
|
toolUseId
|
|
@@ -76,6 +76,19 @@ INDENT_EVENTS_SUBSCRIPTION = """
|
|
|
76
76
|
content
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
+
}
|
|
80
|
+
... on ToolCallMessage {
|
|
81
|
+
messageId
|
|
82
|
+
toolUseId
|
|
83
|
+
toolName
|
|
84
|
+
toolInput {
|
|
85
|
+
... on BashToolInput {
|
|
86
|
+
command
|
|
87
|
+
}
|
|
88
|
+
... on ReadToolInput {
|
|
89
|
+
filePath
|
|
90
|
+
}
|
|
91
|
+
}
|
|
79
92
|
}
|
|
80
93
|
... on PartialToolResultMessage {
|
|
81
94
|
messageId
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import msgspec
|
|
2
2
|
import yaml
|
|
3
|
+
from typing import Any
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class PartialToolResult(msgspec.Struct, tag_field="tool_name", omit_defaults=True):
|
|
@@ -9,6 +10,17 @@ class PartialToolResult(msgspec.Struct, tag_field="tool_name", omit_defaults=Tru
|
|
|
9
10
|
class ToolInput(msgspec.Struct, tag_field="tool_name", omit_defaults=True):
|
|
10
11
|
"""Concrete subclasses describe the full input schema for a tool."""
|
|
11
12
|
|
|
13
|
+
def to_llm(self) -> dict[str, Any]:
|
|
14
|
+
"""Convert ToolInput to LLM-friendly typed dict format.
|
|
15
|
+
|
|
16
|
+
Returns a dictionary with the tool parameters, excluding the tool_name
|
|
17
|
+
which is handled separately by the LLM integration layer.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
self by default, which msgspec will serialize appropriately.
|
|
21
|
+
"""
|
|
22
|
+
return msgspec.to_builtins(self) # type: ignore[no-any-return]
|
|
23
|
+
|
|
12
24
|
|
|
13
25
|
class ToolResult(msgspec.Struct, tag_field="tool_name", omit_defaults=True):
|
|
14
26
|
"""Concrete subclasses return data from a tool execution."""
|
|
@@ -107,6 +119,7 @@ class GrepToolResult(ToolResult, tag=GREP_TOOL_NAME):
|
|
|
107
119
|
EDIT_TOOL_NAME = "edit"
|
|
108
120
|
|
|
109
121
|
|
|
122
|
+
# This is only used in the CLI. The server side type is edit_tool.py
|
|
110
123
|
class EditToolInput(ToolInput, tag=EDIT_TOOL_NAME):
|
|
111
124
|
file_path: str
|
|
112
125
|
old_string: str
|
|
@@ -139,13 +152,13 @@ class BashToolResult(ToolResult, tag=BASH_TOOL_NAME):
|
|
|
139
152
|
stopped_by_user: bool
|
|
140
153
|
|
|
141
154
|
|
|
142
|
-
|
|
143
155
|
class HttpRequest(msgspec.Struct, tag="http_fetch_cli"):
|
|
144
156
|
url: str
|
|
145
157
|
method: str = "GET"
|
|
146
158
|
headers: dict[str, str] | None = None
|
|
147
159
|
timeout: int | None = None
|
|
148
160
|
|
|
161
|
+
|
|
149
162
|
class HttpResponse(msgspec.Struct, tag="http_fetch_cli"):
|
|
150
163
|
status_code: int | None = None
|
|
151
164
|
content: str | None = None
|
|
@@ -153,6 +166,7 @@ class HttpResponse(msgspec.Struct, tag="http_fetch_cli"):
|
|
|
153
166
|
duration_ms: int | None = None
|
|
154
167
|
headers: dict[str, str] | None = None
|
|
155
168
|
|
|
169
|
+
|
|
156
170
|
ToolInputType = (
|
|
157
171
|
ReadToolInput
|
|
158
172
|
| WriteToolInput
|
|
@@ -188,6 +202,10 @@ class TerminateRequest(msgspec.Struct, tag="terminate"):
|
|
|
188
202
|
pass
|
|
189
203
|
|
|
190
204
|
|
|
205
|
+
class SwitchCLIChatRequest(msgspec.Struct, tag="switch_cli_chat"):
|
|
206
|
+
new_chat_uuid: str
|
|
207
|
+
|
|
208
|
+
|
|
191
209
|
class BatchToolExecutionRequest(msgspec.Struct, tag="batch_tool_execution"):
|
|
192
210
|
tool_inputs: list[ToolInputType]
|
|
193
211
|
|
|
@@ -204,6 +222,10 @@ class BatchToolExecutionResponse(msgspec.Struct, tag="batch_tool_execution"):
|
|
|
204
222
|
tool_results: list[ToolResultType]
|
|
205
223
|
|
|
206
224
|
|
|
225
|
+
class SwitchCLIChatResponse(msgspec.Struct, tag="switch_cli_chat"):
|
|
226
|
+
pass
|
|
227
|
+
|
|
228
|
+
|
|
207
229
|
class CliRpcRequest(msgspec.Struct):
|
|
208
230
|
request_id: str
|
|
209
231
|
request: (
|
|
@@ -212,6 +234,7 @@ class CliRpcRequest(msgspec.Struct):
|
|
|
212
234
|
| TerminateRequest
|
|
213
235
|
| HttpRequest
|
|
214
236
|
| BatchToolExecutionRequest
|
|
237
|
+
| SwitchCLIChatRequest
|
|
215
238
|
)
|
|
216
239
|
|
|
217
240
|
|
|
@@ -232,4 +255,5 @@ class CliRpcResponse(msgspec.Struct):
|
|
|
232
255
|
| TerminateResponse
|
|
233
256
|
| BatchToolExecutionResponse
|
|
234
257
|
| HttpResponse
|
|
258
|
+
| SwitchCLIChatResponse
|
|
235
259
|
)
|
|
@@ -36,6 +36,8 @@ from exponent.core.remote_execution.cli_rpc_types import (
|
|
|
36
36
|
ToolExecutionRequest,
|
|
37
37
|
ToolExecutionResponse,
|
|
38
38
|
ToolResultType,
|
|
39
|
+
SwitchCLIChatRequest,
|
|
40
|
+
SwitchCLIChatResponse,
|
|
39
41
|
)
|
|
40
42
|
from exponent.core.remote_execution.code_execution import (
|
|
41
43
|
execute_code_streaming,
|
|
@@ -168,6 +170,21 @@ class RemoteExecutionClient:
|
|
|
168
170
|
)
|
|
169
171
|
)
|
|
170
172
|
return None
|
|
173
|
+
elif isinstance(request.request, SwitchCLIChatRequest):
|
|
174
|
+
await websocket.send(
|
|
175
|
+
json.dumps(
|
|
176
|
+
{
|
|
177
|
+
"type": "result",
|
|
178
|
+
"data": msgspec.to_builtins(
|
|
179
|
+
CliRpcResponse(
|
|
180
|
+
request_id=request.request_id,
|
|
181
|
+
response=SwitchCLIChatResponse(),
|
|
182
|
+
)
|
|
183
|
+
),
|
|
184
|
+
}
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
return SwitchCLIChat(new_chat_uuid=request.request.new_chat_uuid)
|
|
171
188
|
else:
|
|
172
189
|
if isinstance(request.request, ToolExecutionRequest) and isinstance(
|
|
173
190
|
request.request.tool_input, BashToolInput
|
|
@@ -502,6 +519,11 @@ class RemoteExecutionClient:
|
|
|
502
519
|
"TerminateRequest should not be handled by handle_request"
|
|
503
520
|
)
|
|
504
521
|
|
|
522
|
+
elif isinstance(request.request, SwitchCLIChatRequest):
|
|
523
|
+
raise ValueError(
|
|
524
|
+
"SwitchCLIChatRequest should not be handled by handle_request"
|
|
525
|
+
)
|
|
526
|
+
|
|
505
527
|
raise ValueError(f"Unhandled request type: {type(request)}")
|
|
506
528
|
|
|
507
529
|
except Exception as e:
|
|
@@ -10,8 +10,6 @@ from exponent.core.remote_execution.cli_rpc_types import ErrorToolResult, GrepTo
|
|
|
10
10
|
from exponent.core.remote_execution.types import (
|
|
11
11
|
FileAttachment,
|
|
12
12
|
FilePath,
|
|
13
|
-
GetAllTrackedFilesRequest,
|
|
14
|
-
GetAllTrackedFilesResponse,
|
|
15
13
|
GetFileAttachmentRequest,
|
|
16
14
|
GetFileAttachmentResponse,
|
|
17
15
|
GetFileAttachmentsRequest,
|
|
@@ -209,16 +207,6 @@ async def get_matching_files(
|
|
|
209
207
|
)
|
|
210
208
|
|
|
211
209
|
|
|
212
|
-
async def get_all_tracked_files(
|
|
213
|
-
request: GetAllTrackedFilesRequest,
|
|
214
|
-
working_directory: str,
|
|
215
|
-
) -> GetAllTrackedFilesResponse:
|
|
216
|
-
return GetAllTrackedFilesResponse(
|
|
217
|
-
correlation_id=request.correlation_id,
|
|
218
|
-
files=await get_all_non_ignored_files(working_directory),
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
|
|
222
210
|
async def search_files(
|
|
223
211
|
path_str: str,
|
|
224
212
|
file_pattern: str | None,
|
|
@@ -19,24 +19,28 @@ DEFAULT_USER_AGENT = "Indent-HTTP-Client/1.0"
|
|
|
19
19
|
async def fetch_http_content(http_request: HttpRequest) -> HttpResponse:
|
|
20
20
|
"""
|
|
21
21
|
Fetch content from an HTTP URL and return the response.
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
Args:
|
|
24
24
|
http_request: HttpRequest containing URL, method, headers, and timeout
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
Returns:
|
|
27
27
|
HttpResponse with status code, content, and error message if any
|
|
28
28
|
"""
|
|
29
29
|
logger.info(f"Fetching {http_request.method} {http_request.url}")
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
try:
|
|
32
32
|
# Set up timeout
|
|
33
|
-
timeout =
|
|
34
|
-
|
|
33
|
+
timeout = (
|
|
34
|
+
http_request.timeout
|
|
35
|
+
if http_request.timeout is not None
|
|
36
|
+
else DEFAULT_TIMEOUT
|
|
37
|
+
)
|
|
38
|
+
|
|
35
39
|
# Set up headers with default User-Agent
|
|
36
40
|
headers = http_request.headers or {}
|
|
37
41
|
if "User-Agent" not in headers:
|
|
38
42
|
headers["User-Agent"] = DEFAULT_USER_AGENT
|
|
39
|
-
|
|
43
|
+
|
|
40
44
|
# Create HTTP client with timeout
|
|
41
45
|
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
42
46
|
# Make the HTTP request
|
|
@@ -45,23 +49,27 @@ async def fetch_http_content(http_request: HttpRequest) -> HttpResponse:
|
|
|
45
49
|
url=http_request.url,
|
|
46
50
|
headers=headers,
|
|
47
51
|
)
|
|
48
|
-
|
|
52
|
+
|
|
49
53
|
# Get response content as text
|
|
50
54
|
try:
|
|
51
55
|
content = response.text
|
|
52
56
|
except UnicodeDecodeError:
|
|
53
57
|
# If content can't be decoded as text, provide a fallback
|
|
54
58
|
content = f"Binary content ({len(response.content)} bytes)"
|
|
55
|
-
logger.warning(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
logger.warning(
|
|
60
|
+
f"Could not decode response content as text for {http_request.url}"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
logger.info(
|
|
64
|
+
f"HTTP {http_request.method} {http_request.url} -> {response.status_code}"
|
|
65
|
+
)
|
|
66
|
+
|
|
59
67
|
return HttpResponse(
|
|
60
68
|
status_code=response.status_code,
|
|
61
69
|
content=content,
|
|
62
70
|
error_message=None,
|
|
63
71
|
)
|
|
64
|
-
|
|
72
|
+
|
|
65
73
|
except httpx.TimeoutException:
|
|
66
74
|
error_msg = f"Request to {http_request.url} timed out after {timeout} seconds"
|
|
67
75
|
return HttpResponse(
|
|
@@ -69,7 +77,7 @@ async def fetch_http_content(http_request: HttpRequest) -> HttpResponse:
|
|
|
69
77
|
content="",
|
|
70
78
|
error_message=error_msg,
|
|
71
79
|
)
|
|
72
|
-
|
|
80
|
+
|
|
73
81
|
except httpx.RequestError as e:
|
|
74
82
|
error_msg = f"Request error for {http_request.url}: {str(e)}"
|
|
75
83
|
return HttpResponse(
|
|
@@ -77,11 +85,11 @@ async def fetch_http_content(http_request: HttpRequest) -> HttpResponse:
|
|
|
77
85
|
content="",
|
|
78
86
|
error_message=error_msg,
|
|
79
87
|
)
|
|
80
|
-
|
|
88
|
+
|
|
81
89
|
except Exception as e:
|
|
82
90
|
error_msg = f"Unexpected error fetching {http_request.url}: {str(e)}"
|
|
83
91
|
return HttpResponse(
|
|
84
92
|
status_code=None,
|
|
85
93
|
content="",
|
|
86
94
|
error_message=error_msg,
|
|
87
|
-
)
|
|
95
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import getpass
|
|
2
|
+
import os
|
|
3
|
+
import platform
|
|
4
|
+
|
|
5
|
+
from anyio import Path as AsyncPath
|
|
6
|
+
|
|
7
|
+
from exponent.core.remote_execution.git import get_git_info
|
|
8
|
+
from exponent.core.remote_execution.languages import python_execution
|
|
9
|
+
from exponent.core.remote_execution.types import (
|
|
10
|
+
SystemInfo,
|
|
11
|
+
)
|
|
12
|
+
from exponent.core.remote_execution.utils import safe_read_file
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def get_system_info(working_directory: str) -> SystemInfo:
|
|
16
|
+
return SystemInfo(
|
|
17
|
+
name=getpass.getuser(),
|
|
18
|
+
cwd=working_directory,
|
|
19
|
+
os=platform.system(),
|
|
20
|
+
shell=_get_user_shell(),
|
|
21
|
+
git=await get_git_info(working_directory),
|
|
22
|
+
python_env=python_execution.get_python_env_info(),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _get_user_shell() -> str:
|
|
27
|
+
return os.environ.get("SHELL", "bash")
|
|
@@ -229,10 +229,6 @@ Namespace = Literal[
|
|
|
229
229
|
"get_file_attachment",
|
|
230
230
|
"get_file_attachments",
|
|
231
231
|
"get_matching_files",
|
|
232
|
-
"system_context",
|
|
233
|
-
"get_all_tracked_files",
|
|
234
|
-
"halt",
|
|
235
|
-
"switch_cli_chat",
|
|
236
232
|
"error",
|
|
237
233
|
"create_checkpoint",
|
|
238
234
|
"rollback_to_checkpoint",
|
|
@@ -355,27 +351,6 @@ class GetMatchingFilesResponse(RemoteExecutionResponse):
|
|
|
355
351
|
files: list[RemoteFile]
|
|
356
352
|
|
|
357
353
|
|
|
358
|
-
class GetAllTrackedFilesResponse(RemoteExecutionResponse):
|
|
359
|
-
namespace: ClassVar[Namespace] = "get_all_tracked_files"
|
|
360
|
-
|
|
361
|
-
files: list[RemoteFile]
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
class SystemContextResponse(RemoteExecutionResponse):
|
|
365
|
-
namespace: ClassVar[Namespace] = "system_context"
|
|
366
|
-
|
|
367
|
-
exponent_txt: str | None
|
|
368
|
-
system_info: SystemInfo | None
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
class HaltResponse(RemoteExecutionResponse):
|
|
372
|
-
namespace: ClassVar[Namespace] = "halt"
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
class SwitchCLIChatResponse(RemoteExecutionResponse):
|
|
376
|
-
namespace: ClassVar[Namespace] = "switch_cli_chat"
|
|
377
|
-
|
|
378
|
-
|
|
379
354
|
class ErrorResponse(RemoteExecutionResponse):
|
|
380
355
|
namespace: ClassVar[Namespace] = "error"
|
|
381
356
|
# The namespace of the request that caused the error.
|
|
@@ -460,15 +435,6 @@ class StreamingCodeExecutionRequest(
|
|
|
460
435
|
timeout: int
|
|
461
436
|
|
|
462
437
|
|
|
463
|
-
class HaltRequest(RemoteExecutionRequest[HaltResponse]):
|
|
464
|
-
namespace: ClassVar[Namespace] = "halt"
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
class SwitchCLIChatRequest(RemoteExecutionRequest[SwitchCLIChatResponse]):
|
|
468
|
-
namespace: ClassVar[Namespace] = "switch_cli_chat"
|
|
469
|
-
new_chat_uuid: str
|
|
470
|
-
|
|
471
|
-
|
|
472
438
|
class FileWriteRequest(RemoteExecutionRequest[FileWriteResponse]):
|
|
473
439
|
namespace: ClassVar[Namespace] = "file_write"
|
|
474
440
|
|
|
@@ -504,14 +470,6 @@ class GetMatchingFilesRequest(RemoteExecutionRequest[GetMatchingFilesResponse]):
|
|
|
504
470
|
search_term: str
|
|
505
471
|
|
|
506
472
|
|
|
507
|
-
class GetAllTrackedFilesRequest(RemoteExecutionRequest[GetAllTrackedFilesResponse]):
|
|
508
|
-
namespace: ClassVar[Namespace] = "get_all_tracked_files"
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
class SystemContextRequest(RemoteExecutionRequest[SystemContextResponse]):
|
|
512
|
-
namespace: ClassVar[Namespace] = "system_context"
|
|
513
|
-
|
|
514
|
-
|
|
515
473
|
class CreateCheckpointRequest(RemoteExecutionRequest[CreateCheckpointResponse]):
|
|
516
474
|
namespace: ClassVar[Namespace] = "create_checkpoint"
|
|
517
475
|
|
|
@@ -557,12 +515,8 @@ RemoteExecutionRequestType = Union[
|
|
|
557
515
|
GetFileAttachmentRequest,
|
|
558
516
|
GetFileAttachmentsRequest,
|
|
559
517
|
GetMatchingFilesRequest,
|
|
560
|
-
SystemContextRequest,
|
|
561
|
-
GetAllTrackedFilesRequest,
|
|
562
518
|
CommandRequest,
|
|
563
|
-
HaltRequest,
|
|
564
519
|
StreamingCodeExecutionRequest,
|
|
565
|
-
SwitchCLIChatRequest,
|
|
566
520
|
CreateCheckpointRequest,
|
|
567
521
|
RollbackToCheckpointRequest,
|
|
568
522
|
]
|
|
@@ -576,11 +530,7 @@ RemoteExecutionResponseType = Union[
|
|
|
576
530
|
GetFileAttachmentResponse,
|
|
577
531
|
GetFileAttachmentsResponse,
|
|
578
532
|
GetMatchingFilesResponse,
|
|
579
|
-
GetAllTrackedFilesResponse,
|
|
580
|
-
SystemContextResponse,
|
|
581
533
|
CommandResponse,
|
|
582
|
-
HaltResponse,
|
|
583
|
-
SwitchCLIChatResponse,
|
|
584
534
|
ErrorResponse,
|
|
585
535
|
CreateCheckpointResponse,
|
|
586
536
|
RollbackToCheckpointResponse,
|
|
@@ -35,16 +35,12 @@ from exponent.core.remote_execution.types import (
|
|
|
35
35
|
FilePath,
|
|
36
36
|
FileWriteRequest,
|
|
37
37
|
FileWriteResponse,
|
|
38
|
-
GetAllTrackedFilesRequest,
|
|
39
|
-
GetAllTrackedFilesResponse,
|
|
40
38
|
GetFileAttachmentRequest,
|
|
41
39
|
GetFileAttachmentResponse,
|
|
42
40
|
GetFileAttachmentsRequest,
|
|
43
41
|
GetFileAttachmentsResponse,
|
|
44
42
|
GetMatchingFilesRequest,
|
|
45
43
|
GetMatchingFilesResponse,
|
|
46
|
-
HaltRequest,
|
|
47
|
-
HaltResponse,
|
|
48
44
|
ListFilesRequest,
|
|
49
45
|
ListFilesResponse,
|
|
50
46
|
RemoteExecutionMessage,
|
|
@@ -61,10 +57,6 @@ from exponent.core.remote_execution.types import (
|
|
|
61
57
|
StreamingCodeExecutionResponse,
|
|
62
58
|
StreamingCodeExecutionResponseChunk,
|
|
63
59
|
SupportedLanguage,
|
|
64
|
-
SwitchCLIChatRequest,
|
|
65
|
-
SwitchCLIChatResponse,
|
|
66
|
-
SystemContextRequest,
|
|
67
|
-
SystemContextResponse,
|
|
68
60
|
)
|
|
69
61
|
from exponent.core.types.command_data import NaturalEditContent
|
|
70
62
|
from exponent.core.types.event_types import (
|
|
@@ -78,82 +70,6 @@ from exponent.utils.version import get_installed_version
|
|
|
78
70
|
### Serde
|
|
79
71
|
|
|
80
72
|
|
|
81
|
-
def deserialize_message_data(
|
|
82
|
-
message_data: RemoteExecutionMessageData | str,
|
|
83
|
-
) -> RemoteExecutionMessage:
|
|
84
|
-
if isinstance(message_data, str):
|
|
85
|
-
message_data = RemoteExecutionMessageData.model_validate_json(message_data)
|
|
86
|
-
if message_data.direction == "request":
|
|
87
|
-
return deserialize_request_data(cast(RemoteExecutionRequestData, message_data))
|
|
88
|
-
elif message_data.direction == "response":
|
|
89
|
-
return deserialize_response_data(
|
|
90
|
-
cast(RemoteExecutionResponseData, message_data)
|
|
91
|
-
)
|
|
92
|
-
else:
|
|
93
|
-
# type checking trick, if you miss a namespace then
|
|
94
|
-
# this won't typecheck due to the input parameter
|
|
95
|
-
# having a potential type other than no-return
|
|
96
|
-
assert_unreachable(message_data.direction)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def deserialize_request_data(
|
|
100
|
-
request_data: RemoteExecutionRequestData | str,
|
|
101
|
-
) -> RemoteExecutionRequestType:
|
|
102
|
-
request: RemoteExecutionRequestType
|
|
103
|
-
if isinstance(request_data, str):
|
|
104
|
-
request_data = RemoteExecutionRequestData.model_validate_json(request_data)
|
|
105
|
-
if request_data.direction != "request":
|
|
106
|
-
raise ValueError(f"Expected request, but got {request_data.direction}")
|
|
107
|
-
if request_data.namespace == "code_execution":
|
|
108
|
-
request = CodeExecutionRequest.model_validate_json(request_data.message_data)
|
|
109
|
-
elif request_data.namespace == "file_write":
|
|
110
|
-
request = FileWriteRequest.model_validate_json(request_data.message_data)
|
|
111
|
-
elif request_data.namespace == "list_files":
|
|
112
|
-
request = ListFilesRequest.model_validate_json(request_data.message_data)
|
|
113
|
-
elif request_data.namespace == "get_file_attachment":
|
|
114
|
-
request = GetFileAttachmentRequest.model_validate_json(
|
|
115
|
-
request_data.message_data
|
|
116
|
-
)
|
|
117
|
-
elif request_data.namespace == "get_file_attachments":
|
|
118
|
-
request = GetFileAttachmentsRequest.model_validate_json(
|
|
119
|
-
request_data.message_data
|
|
120
|
-
)
|
|
121
|
-
elif request_data.namespace == "get_matching_files":
|
|
122
|
-
request = GetMatchingFilesRequest.model_validate_json(request_data.message_data)
|
|
123
|
-
elif request_data.namespace == "system_context":
|
|
124
|
-
request = SystemContextRequest.model_validate_json(request_data.message_data)
|
|
125
|
-
elif request_data.namespace == "get_all_tracked_files":
|
|
126
|
-
request = GetAllTrackedFilesRequest.model_validate_json(
|
|
127
|
-
request_data.message_data
|
|
128
|
-
)
|
|
129
|
-
elif request_data.namespace == "command":
|
|
130
|
-
request = CommandRequest.model_validate_json(request_data.message_data)
|
|
131
|
-
elif request_data.namespace == "halt":
|
|
132
|
-
request = HaltRequest.model_validate_json(request_data.message_data)
|
|
133
|
-
elif request_data.namespace == "streaming_code_execution":
|
|
134
|
-
request = StreamingCodeExecutionRequest.model_validate_json(
|
|
135
|
-
request_data.message_data
|
|
136
|
-
)
|
|
137
|
-
elif request_data.namespace == "switch_cli_chat":
|
|
138
|
-
request = SwitchCLIChatRequest.model_validate_json(request_data.message_data)
|
|
139
|
-
elif request_data.namespace == "streaming_code_execution_chunk":
|
|
140
|
-
assert False, "Streaming code execution chunk is a response, not a request"
|
|
141
|
-
elif request_data.namespace == "error":
|
|
142
|
-
assert False, "Error is a response, not a request"
|
|
143
|
-
elif request_data.namespace == "create_checkpoint":
|
|
144
|
-
request = CreateCheckpointRequest.model_validate_json(request_data.message_data)
|
|
145
|
-
elif request_data.namespace == "rollback_to_checkpoint":
|
|
146
|
-
request = RollbackToCheckpointRequest.model_validate_json(
|
|
147
|
-
request_data.message_data
|
|
148
|
-
)
|
|
149
|
-
else:
|
|
150
|
-
# type checking trick, if you miss a namespace then
|
|
151
|
-
# this won't typecheck due to the input parameter
|
|
152
|
-
# having a potential type other than no-return
|
|
153
|
-
request = assert_unreachable(request_data.namespace)
|
|
154
|
-
return truncate_message(request)
|
|
155
|
-
|
|
156
|
-
|
|
157
73
|
def deserialize_response_data(
|
|
158
74
|
response_data: RemoteExecutionResponseData | str,
|
|
159
75
|
) -> RemoteExecutionResponseType:
|
|
@@ -188,18 +104,9 @@ def deserialize_response_data(
|
|
|
188
104
|
response = GetFileAttachmentsResponse.model_validate_json(
|
|
189
105
|
response_data.message_data
|
|
190
106
|
)
|
|
191
|
-
|
|
192
|
-
response = SystemContextResponse.model_validate_json(response_data.message_data)
|
|
193
|
-
elif response_data.namespace == "get_all_tracked_files":
|
|
194
|
-
response = GetAllTrackedFilesResponse.model_validate_json(
|
|
195
|
-
response_data.message_data
|
|
196
|
-
)
|
|
107
|
+
|
|
197
108
|
elif response_data.namespace == "command":
|
|
198
109
|
response = CommandResponse.model_validate_json(response_data.message_data)
|
|
199
|
-
elif response_data.namespace == "halt":
|
|
200
|
-
response = HaltResponse.model_validate_json(response_data.message_data)
|
|
201
|
-
elif response_data.namespace == "switch_cli_chat":
|
|
202
|
-
response = SwitchCLIChatResponse.model_validate_json(response_data.message_data)
|
|
203
110
|
elif response_data.namespace == "error":
|
|
204
111
|
response = ErrorResponse.model_validate_json(response_data.message_data)
|
|
205
112
|
elif response_data.namespace == "create_checkpoint":
|
|
@@ -424,10 +331,8 @@ def truncate_message(response: GetMatchingFilesRequest) -> GetMatchingFilesReque
|
|
|
424
331
|
def truncate_message(
|
|
425
332
|
response: GetMatchingFilesResponse,
|
|
426
333
|
) -> GetMatchingFilesResponse: ...
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
@overload
|
|
430
|
-
def truncate_message(response: SystemContextResponse) -> SystemContextResponse: ...
|
|
334
|
+
|
|
335
|
+
|
|
431
336
|
@overload
|
|
432
337
|
def truncate_message(
|
|
433
338
|
response: RemoteExecutionRequestType,
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "indent"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.8"
|
|
8
8
|
description = "Indent is an AI Pair Programmer"
|
|
9
9
|
authors = [{ name = "Sashank Thupukari", email = "sashank@exponent.run" }]
|
|
10
10
|
requires-python = ">=3.10,<3.13"
|
|
@@ -14,7 +14,7 @@ dependencies = [
|
|
|
14
14
|
"diff-match-patch>=20230430,<20230431",
|
|
15
15
|
"gitignore-parser>=0.1.11,<0.2",
|
|
16
16
|
"gql[httpx, websockets]>=3.5.0,<4",
|
|
17
|
-
"httpx>=0.
|
|
17
|
+
"httpx>=0.28.1",
|
|
18
18
|
"ipykernel>=6.29.4,<7",
|
|
19
19
|
"jupyter-client>=8.6.1,<9",
|
|
20
20
|
"packaging~=24.1",
|
|
@@ -50,7 +50,7 @@ dev = [
|
|
|
50
50
|
"pytest>=8.3.3,<9",
|
|
51
51
|
"pytest-asyncio>=0.24.0,<0.25",
|
|
52
52
|
"pytest-cov>=5.0.0,<6",
|
|
53
|
-
"pytest-httpx>=0.
|
|
53
|
+
"pytest-httpx>=0.31.0",
|
|
54
54
|
"pytest-xdist>=3.6.1,<4",
|
|
55
55
|
"types-passlib>=1.7.7.20240819,<2",
|
|
56
56
|
"requests>=2.0.0,<3",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.7" # Keep in sync with pyproject.toml
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import getpass
|
|
2
|
-
import os
|
|
3
|
-
import platform
|
|
4
|
-
|
|
5
|
-
from anyio import Path as AsyncPath
|
|
6
|
-
|
|
7
|
-
from exponent.core.remote_execution.git import get_git_info
|
|
8
|
-
from exponent.core.remote_execution.languages import python_execution
|
|
9
|
-
from exponent.core.remote_execution.types import (
|
|
10
|
-
SystemContextRequest,
|
|
11
|
-
SystemContextResponse,
|
|
12
|
-
SystemInfo,
|
|
13
|
-
)
|
|
14
|
-
from exponent.core.remote_execution.utils import safe_read_file
|
|
15
|
-
|
|
16
|
-
EXPONENT_TXT_FILENAMES = [
|
|
17
|
-
"exponent.txt",
|
|
18
|
-
]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
async def get_system_context(
|
|
22
|
-
request: SystemContextRequest, working_directory: str
|
|
23
|
-
) -> SystemContextResponse:
|
|
24
|
-
return SystemContextResponse(
|
|
25
|
-
correlation_id=request.correlation_id,
|
|
26
|
-
exponent_txt=await _read_exponent_txt(working_directory),
|
|
27
|
-
system_info=await get_system_info(working_directory),
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
async def get_system_info(working_directory: str) -> SystemInfo:
|
|
32
|
-
return SystemInfo(
|
|
33
|
-
name=getpass.getuser(),
|
|
34
|
-
cwd=working_directory,
|
|
35
|
-
os=platform.system(),
|
|
36
|
-
shell=_get_user_shell(),
|
|
37
|
-
git=await get_git_info(working_directory),
|
|
38
|
-
python_env=python_execution.get_python_env_info(),
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
async def _read_exponent_txt(working_directory: str) -> str | None:
|
|
43
|
-
for filename in EXPONENT_TXT_FILENAMES:
|
|
44
|
-
file_path = AsyncPath(os.path.join(working_directory, filename.lower()))
|
|
45
|
-
exists = await file_path.exists()
|
|
46
|
-
|
|
47
|
-
if exists:
|
|
48
|
-
return await safe_read_file(file_path)
|
|
49
|
-
|
|
50
|
-
return None
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _get_user_shell() -> str:
|
|
54
|
-
return os.environ.get("SHELL", "bash")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|