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.

Files changed (58) hide show
  1. {indent-0.1.7 → indent-0.1.8}/.gitignore +2 -1
  2. {indent-0.1.7 → indent-0.1.8}/PKG-INFO +2 -2
  3. indent-0.1.8/exponent/__init__.py +1 -0
  4. {indent-0.1.7 → indent-0.1.8}/exponent/core/graphql/queries.py +13 -0
  5. {indent-0.1.7 → indent-0.1.8}/exponent/core/graphql/subscriptions.py +13 -0
  6. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/cli_rpc_types.py +25 -1
  7. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/client.py +22 -0
  8. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/files.py +0 -12
  9. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/http_fetch.py +23 -15
  10. indent-0.1.8/exponent/core/remote_execution/system_context.py +27 -0
  11. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/types.py +0 -50
  12. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/utils.py +3 -98
  13. {indent-0.1.7 → indent-0.1.8}/pyproject.toml +3 -3
  14. indent-0.1.7/exponent/__init__.py +0 -1
  15. indent-0.1.7/exponent/core/remote_execution/system_context.py +0 -54
  16. {indent-0.1.7 → indent-0.1.8}/exponent/cli.py +0 -0
  17. {indent-0.1.7 → indent-0.1.8}/exponent/commands/cloud_commands.py +0 -0
  18. {indent-0.1.7 → indent-0.1.8}/exponent/commands/common.py +0 -0
  19. {indent-0.1.7 → indent-0.1.8}/exponent/commands/config_commands.py +0 -0
  20. {indent-0.1.7 → indent-0.1.8}/exponent/commands/github_app_commands.py +0 -0
  21. {indent-0.1.7 → indent-0.1.8}/exponent/commands/listen_commands.py +0 -0
  22. {indent-0.1.7 → indent-0.1.8}/exponent/commands/run_commands.py +0 -0
  23. {indent-0.1.7 → indent-0.1.8}/exponent/commands/settings.py +0 -0
  24. {indent-0.1.7 → indent-0.1.8}/exponent/commands/shell_commands.py +0 -0
  25. {indent-0.1.7 → indent-0.1.8}/exponent/commands/theme.py +0 -0
  26. {indent-0.1.7 → indent-0.1.8}/exponent/commands/types.py +0 -0
  27. {indent-0.1.7 → indent-0.1.8}/exponent/commands/upgrade.py +0 -0
  28. {indent-0.1.7 → indent-0.1.8}/exponent/commands/utils.py +0 -0
  29. {indent-0.1.7 → indent-0.1.8}/exponent/commands/workflow_commands.py +0 -0
  30. {indent-0.1.7 → indent-0.1.8}/exponent/core/config.py +0 -0
  31. {indent-0.1.7 → indent-0.1.8}/exponent/core/graphql/__init__.py +0 -0
  32. {indent-0.1.7 → indent-0.1.8}/exponent/core/graphql/client.py +0 -0
  33. {indent-0.1.7 → indent-0.1.8}/exponent/core/graphql/cloud_config_queries.py +0 -0
  34. {indent-0.1.7 → indent-0.1.8}/exponent/core/graphql/get_chats_query.py +0 -0
  35. {indent-0.1.7 → indent-0.1.8}/exponent/core/graphql/github_config_queries.py +0 -0
  36. {indent-0.1.7 → indent-0.1.8}/exponent/core/graphql/mutations.py +0 -0
  37. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/checkpoints.py +0 -0
  38. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/code_execution.py +0 -0
  39. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/error_info.py +0 -0
  40. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/exceptions.py +0 -0
  41. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/file_write.py +0 -0
  42. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/git.py +0 -0
  43. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/languages/python_execution.py +0 -0
  44. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/languages/shell_streaming.py +0 -0
  45. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/languages/types.py +0 -0
  46. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/session.py +0 -0
  47. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/tool_execution.py +0 -0
  48. {indent-0.1.7 → indent-0.1.8}/exponent/core/remote_execution/truncation.py +0 -0
  49. {indent-0.1.7 → indent-0.1.8}/exponent/core/types/__init__.py +0 -0
  50. {indent-0.1.7 → indent-0.1.8}/exponent/core/types/command_data.py +0 -0
  51. {indent-0.1.7 → indent-0.1.8}/exponent/core/types/event_types.py +0 -0
  52. {indent-0.1.7 → indent-0.1.8}/exponent/core/types/generated/__init__.py +0 -0
  53. {indent-0.1.7 → indent-0.1.8}/exponent/core/types/generated/strategy_info.py +0 -0
  54. {indent-0.1.7 → indent-0.1.8}/exponent/migration-docs/login.md +0 -0
  55. {indent-0.1.7 → indent-0.1.8}/exponent/py.typed +0 -0
  56. {indent-0.1.7 → indent-0.1.8}/exponent/utils/__init__.py +0 -0
  57. {indent-0.1.7 → indent-0.1.8}/exponent/utils/colors.py +0 -0
  58. {indent-0.1.7 → indent-0.1.8}/exponent/utils/version.py +0 -0
@@ -9,10 +9,11 @@ __pycache__/
9
9
  .env.production
10
10
  .env.*_db
11
11
  .gcp-sa-credentials.json
12
- .indent-gh-app.pem
12
+ *.pem
13
13
  workflow_input.json
14
14
  .anthropic.log.json
15
15
  CLAUDE.local.md
16
+ indent.local.md
16
17
 
17
18
  # C extensions
18
19
  *.so
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: indent
3
- Version: 0.1.7
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<0.28,>=0.27.0
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 = http_request.timeout if http_request.timeout is not None else DEFAULT_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(f"Could not decode response content as text for {http_request.url}")
56
-
57
- logger.info(f"HTTP {http_request.method} {http_request.url} -> {response.status_code}")
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
- elif response_data.namespace == "system_context":
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
- @overload
428
- def truncate_message(response: SystemContextRequest) -> SystemContextRequest: ...
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"
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.27.0,<0.28",
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.30.0,<0.31",
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