indent 0.1.13__tar.gz → 0.1.15__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 (52) hide show
  1. {indent-0.1.13 → indent-0.1.15}/PKG-INFO +1 -1
  2. {indent-0.1.13 → indent-0.1.15}/exponent/__init__.py +2 -2
  3. {indent-0.1.13 → indent-0.1.15}/exponent/commands/common.py +10 -10
  4. {indent-0.1.13 → indent-0.1.15}/exponent/commands/run_commands.py +4 -0
  5. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/cli_rpc_types.py +3 -1
  6. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/client.py +25 -19
  7. indent-0.1.15/exponent/core/remote_execution/tool_type_utils.py +39 -0
  8. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/types.py +14 -1
  9. {indent-0.1.13 → indent-0.1.15}/exponent/utils/version.py +6 -6
  10. {indent-0.1.13 → indent-0.1.15}/.gitignore +0 -0
  11. {indent-0.1.13 → indent-0.1.15}/exponent/cli.py +0 -0
  12. {indent-0.1.13 → indent-0.1.15}/exponent/commands/cloud_commands.py +0 -0
  13. {indent-0.1.13 → indent-0.1.15}/exponent/commands/config_commands.py +0 -0
  14. {indent-0.1.13 → indent-0.1.15}/exponent/commands/settings.py +0 -0
  15. {indent-0.1.13 → indent-0.1.15}/exponent/commands/types.py +0 -0
  16. {indent-0.1.13 → indent-0.1.15}/exponent/commands/upgrade.py +0 -0
  17. {indent-0.1.13 → indent-0.1.15}/exponent/commands/utils.py +0 -0
  18. {indent-0.1.13 → indent-0.1.15}/exponent/commands/workflow_commands.py +0 -0
  19. {indent-0.1.13 → indent-0.1.15}/exponent/core/config.py +0 -0
  20. {indent-0.1.13 → indent-0.1.15}/exponent/core/graphql/__init__.py +0 -0
  21. {indent-0.1.13 → indent-0.1.15}/exponent/core/graphql/client.py +0 -0
  22. {indent-0.1.13 → indent-0.1.15}/exponent/core/graphql/get_chats_query.py +0 -0
  23. {indent-0.1.13 → indent-0.1.15}/exponent/core/graphql/github_config_queries.py +0 -0
  24. {indent-0.1.13 → indent-0.1.15}/exponent/core/graphql/mutations.py +0 -0
  25. {indent-0.1.13 → indent-0.1.15}/exponent/core/graphql/queries.py +0 -0
  26. {indent-0.1.13 → indent-0.1.15}/exponent/core/graphql/subscriptions.py +0 -0
  27. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/checkpoints.py +0 -0
  28. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/code_execution.py +0 -0
  29. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/error_info.py +0 -0
  30. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/exceptions.py +0 -0
  31. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/file_write.py +0 -0
  32. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/files.py +0 -0
  33. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/git.py +0 -0
  34. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/http_fetch.py +0 -0
  35. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/languages/python_execution.py +0 -0
  36. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/languages/shell_streaming.py +0 -0
  37. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/languages/types.py +0 -0
  38. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/session.py +0 -0
  39. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/system_context.py +0 -0
  40. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/tool_execution.py +0 -0
  41. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/truncation.py +0 -0
  42. {indent-0.1.13 → indent-0.1.15}/exponent/core/remote_execution/utils.py +0 -0
  43. {indent-0.1.13 → indent-0.1.15}/exponent/core/types/__init__.py +0 -0
  44. {indent-0.1.13 → indent-0.1.15}/exponent/core/types/command_data.py +0 -0
  45. {indent-0.1.13 → indent-0.1.15}/exponent/core/types/event_types.py +0 -0
  46. {indent-0.1.13 → indent-0.1.15}/exponent/core/types/generated/__init__.py +0 -0
  47. {indent-0.1.13 → indent-0.1.15}/exponent/core/types/generated/strategy_info.py +0 -0
  48. {indent-0.1.13 → indent-0.1.15}/exponent/migration-docs/login.md +0 -0
  49. {indent-0.1.13 → indent-0.1.15}/exponent/py.typed +0 -0
  50. {indent-0.1.13 → indent-0.1.15}/exponent/utils/__init__.py +0 -0
  51. {indent-0.1.13 → indent-0.1.15}/exponent/utils/colors.py +0 -0
  52. {indent-0.1.13 → indent-0.1.15}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: indent
3
- Version: 0.1.13
3
+ Version: 0.1.15
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
@@ -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.13'
32
- __version_tuple__ = version_tuple = (0, 1, 13)
31
+ __version__ = version = '0.1.15'
32
+ __version_tuple__ = version_tuple = (0, 1, 15)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -132,13 +132,13 @@ async def check_inside_git_repo(settings: Settings) -> None:
132
132
  )
133
133
  )
134
134
  click.echo(
135
- "This is a check to make sure you are running Exponent from the root of your project."
135
+ "This is a check to make sure you are running Indent from the root of your project."
136
136
  )
137
137
 
138
138
  click.echo(f"\nCurrent directory: {click.style(os.getcwd(), fg='cyan')}")
139
139
 
140
140
  click.echo("\nRecommendation:")
141
- click.echo(" Run Exponent from the root directory of your codebase.")
141
+ click.echo(" Run Indent from the root directory of your codebase.")
142
142
  click.echo("\nExample:")
143
143
  click.echo(
144
144
  f" If your project is in {click.style('~/my-project', fg='cyan')}, run:"
@@ -147,7 +147,7 @@ async def check_inside_git_repo(settings: Settings) -> None:
147
147
 
148
148
  # Tell the user they can run exponent config --no-git-warning to disable this check
149
149
  click.echo(
150
- f"\nYou can run {click.style('exponent config --set-git-warning-disabled', fg='green')} to disable this check."
150
+ f"\nYou can run {click.style('indent config --set-git-warning-disabled', fg='green')} to disable this check."
151
151
  )
152
152
 
153
153
  if not click.confirm(
@@ -165,26 +165,26 @@ def check_running_from_home_directory(require_confirmation: bool = True) -> bool
165
165
  if os.path.expanduser("~") == os.getcwd():
166
166
  click.echo(
167
167
  click.style(
168
- "\nWarning: Running Exponent from Home Directory",
168
+ "\nWarning: Running Indent from Home Directory",
169
169
  fg="yellow",
170
170
  bold=True,
171
171
  )
172
172
  )
173
173
  click.echo(
174
- "Running Exponent from your home directory can cause unexpected issues."
174
+ "Running Indent from your home directory can cause unexpected issues."
175
175
  )
176
176
  click.echo("\nRecommendation:")
177
- click.echo(" Run Exponent from the root directory of your codebase.")
177
+ click.echo(" Run Indent from the root directory of your codebase.")
178
178
  click.echo("\nExample:")
179
179
  click.echo(
180
180
  f" If your project is in {click.style('~/my-project', fg='cyan')}, run:"
181
181
  )
182
- click.echo(f" {click.style('cd ~/my-project && exponent run', fg='green')}")
182
+ click.echo(f" {click.style('cd ~/my-project && indent run', fg='green')}")
183
183
 
184
184
  if require_confirmation:
185
185
  if not click.confirm(
186
186
  click.style(
187
- f"\nDo you want to continue running Exponent from {os.getcwd()}?",
187
+ f"\nDo you want to continue running indent from {os.getcwd()}?",
188
188
  fg="yellow",
189
189
  ),
190
190
  default=True,
@@ -214,7 +214,7 @@ def run_until_complete(coro: Coroutine[Any, Any, Any]) -> Any:
214
214
  except ExponentError as e:
215
215
  click.secho(f"Encountered error: {e}", fg="red")
216
216
  click.secho(
217
- "The Exponent team has been notified, "
217
+ "The Indent team has been notified, "
218
218
  "please try again and reach out if the problem persists.",
219
219
  fg="yellow",
220
220
  )
@@ -392,7 +392,7 @@ async def set_login_complete(api_key: str, base_api_url: str, base_ws_url: str)
392
392
  # than the one used in the user's request...
393
393
  # This should never happen
394
394
  raise HandledExponentError(
395
- "Invalid API key, login to https://exponent.run to find your API key."
395
+ "Invalid API key, login to https://indent.com to find your API key."
396
396
  )
397
397
 
398
398
 
@@ -97,6 +97,10 @@ 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 is not None and timeout_seconds <= 0:
101
+ click.secho("Error: --timeout-seconds must be a positive integer", fg="red")
102
+ sys.exit(1)
103
+
100
104
  if chat_uuid is None:
101
105
  sys.exit(1)
102
106
 
@@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Any
2
2
 
3
3
  import msgspec
4
4
  import yaml
5
+ from pydantic_ai.format_as_xml import format_as_xml
5
6
 
6
7
  if TYPE_CHECKING:
7
8
  from exponent_server.core.tools.edit_tool import (
@@ -28,6 +29,7 @@ class ToolInput(msgspec.Struct, tag_field="tool_name", omit_defaults=True):
28
29
  return msgspec.to_builtins(self) # type: ignore[no-any-return]
29
30
 
30
31
  def to_text(self) -> str:
32
+ """This text is purely used for human debugging, it's not fed to the llm"""
31
33
  d = msgspec.to_builtins(self)
32
34
  del d["tool_name"]
33
35
  return yaml.dump(d)
@@ -41,7 +43,7 @@ class ToolResult(msgspec.Struct, tag_field="tool_name", omit_defaults=True):
41
43
  This provides a default textual representation of the tool result. Override it as needed for your tool."""
42
44
  d = msgspec.to_builtins(self)
43
45
  del d["tool_name"]
44
- return yaml.dump(d)
46
+ return format_as_xml(d, include_root_tag=False, item_tag="item")
45
47
 
46
48
 
47
49
  class ErrorToolResult(ToolResult, tag="error"):
@@ -5,7 +5,7 @@ import json
5
5
  import logging
6
6
  import time
7
7
  import uuid
8
- from collections.abc import AsyncGenerator, Callable, Generator
8
+ from collections.abc import AsyncGenerator, Callable, Coroutine, Generator
9
9
  from contextlib import asynccontextmanager
10
10
  from dataclasses import dataclass
11
11
  from typing import Any, TypeVar, cast
@@ -30,6 +30,7 @@ from exponent.core.remote_execution.cli_rpc_types import (
30
30
  CliRpcRequest,
31
31
  CliRpcResponse,
32
32
  ErrorResponse,
33
+ ErrorToolResult,
33
34
  GetAllFilesRequest,
34
35
  GetAllFilesResponse,
35
36
  HttpRequest,
@@ -63,10 +64,10 @@ from exponent.core.remote_execution.types import (
63
64
  CreateChatResponse,
64
65
  GitInfo,
65
66
  HeartbeatInfo,
66
- PrReviewWorkflowInput,
67
67
  RemoteExecutionResponseType,
68
68
  RunWorkflowRequest,
69
69
  StreamingCodeExecutionRequest,
70
+ WorkflowInput,
70
71
  WorkflowTriggerRequest,
71
72
  WorkflowTriggerResponse,
72
73
  )
@@ -182,6 +183,8 @@ class RemoteExecutionClient:
182
183
  """Handle an incoming websocket message.
183
184
  Returns None to continue processing, or a REMOTE_EXECUTION_CLIENT_EXIT_INFO to exit."""
184
185
 
186
+ self._last_request_time = time.time()
187
+
185
188
  msg_data = json.loads(msg)
186
189
  if msg_data["type"] != "request":
187
190
  return None
@@ -544,7 +547,7 @@ class RemoteExecutionClient:
544
547
  return cast(dict[str, Any], response.json())
545
548
 
546
549
  async def trigger_workflow(
547
- self, workflow_name: str, workflow_input: PrReviewWorkflowInput
550
+ self, workflow_name: str, workflow_input: WorkflowInput
548
551
  ) -> WorkflowTriggerResponse:
549
552
  response = await self.api_client.post(
550
553
  "/api/remote_execution/trigger_workflow",
@@ -609,33 +612,36 @@ class RemoteExecutionClient:
609
612
  response=GetAllFilesResponse(files=files),
610
613
  )
611
614
  elif isinstance(request.request, BatchToolExecutionRequest):
612
- results: list[ToolResultType] = []
615
+ coros: list[Coroutine[Any, Any, ToolResultType]] = []
613
616
  for tool_input in request.request.tool_inputs:
614
- try:
615
- if isinstance(tool_input, BashToolInput):
616
- raw_result = await execute_bash_tool(
617
+ if isinstance(tool_input, BashToolInput):
618
+ coros.append(
619
+ execute_bash_tool(
617
620
  tool_input,
618
621
  self.working_directory,
619
622
  should_halt=self.get_halt_check(request.request_id),
620
623
  )
621
- else:
622
- raw_result = await execute_tool( # type: ignore[assignment]
623
- tool_input, self.working_directory
624
- )
625
- tool_result = truncate_result(raw_result)
626
- results.append(tool_result)
627
- except Exception as e:
628
- logger.error(f"Error executing tool {tool_input}: {e}")
629
- from exponent.core.remote_execution.cli_rpc_types import (
630
- ErrorToolResult,
631
624
  )
625
+ else:
626
+ coros.append(execute_tool(tool_input, self.working_directory))
632
627
 
633
- results.append(ErrorToolResult(error_message=str(e)))
628
+ results: list[ToolResultType | BaseException] = await asyncio.gather(
629
+ *coros, return_exceptions=True
630
+ )
631
+
632
+ processed_results: list[ToolResultType] = []
633
+ for result in results:
634
+ if not isinstance(result, BaseException):
635
+ processed_results.append(truncate_result(result))
636
+ else:
637
+ processed_results.append(
638
+ ErrorToolResult(error_message=str(result))
639
+ )
634
640
 
635
641
  return CliRpcResponse(
636
642
  request_id=request.request_id,
637
643
  response=BatchToolExecutionResponse(
638
- tool_results=results,
644
+ tool_results=processed_results,
639
645
  ),
640
646
  )
641
647
  elif isinstance(request.request, HttpRequest):
@@ -0,0 +1,39 @@
1
+ from typing import Any
2
+
3
+ import msgspec
4
+
5
+ from exponent.core.remote_execution.cli_rpc_types import ToolResult
6
+
7
+
8
+ def to_mostly_xml(tool_result: ToolResult) -> str:
9
+ """
10
+ This provides a default textual representation of the tool result. Override it as needed for your tool."""
11
+ d = msgspec.to_builtins(tool_result)
12
+ del d["tool_name"]
13
+ return to_mostly_xml_helper(d)
14
+
15
+
16
+ def to_mostly_xml_helper(
17
+ d: Any,
18
+ ) -> str:
19
+ if isinstance(d, dict):
20
+ # No outer wrapper at top level, each field gets XML tags
21
+ parts = []
22
+ for key, value in d.items():
23
+ if isinstance(value, list):
24
+ # Handle lists with item tags
25
+ list_items = "\n".join(
26
+ f"<item>\n{to_mostly_xml_helper(item)}\n</item>" for item in value
27
+ )
28
+ parts.append(f"<{key}>\n{list_items}\n</{key}>")
29
+ elif isinstance(value, dict):
30
+ # Nested dict
31
+ parts.append(f"<{key}>\n{to_mostly_xml_helper(value)}\n</{key}>")
32
+ else:
33
+ # Scalar value
34
+ parts.append(f"<{key}>\n{value!s}\n</{key}>")
35
+ return "\n".join(parts)
36
+ elif isinstance(d, list):
37
+ raise ValueError("Lists are not allowed at the top level")
38
+ else:
39
+ return str(d)
@@ -49,9 +49,17 @@ class PrReviewWorkflowInput(BaseModel):
49
49
  pr_number: int
50
50
 
51
51
 
52
+ class SlackWorkflowInput(BaseModel):
53
+ channel_id: str
54
+ thread_ts: str
55
+
56
+
57
+ WorkflowInput = PrReviewWorkflowInput | SlackWorkflowInput
58
+
59
+
52
60
  class WorkflowTriggerRequest(BaseModel):
53
61
  workflow_name: str
54
- workflow_input: PrReviewWorkflowInput
62
+ workflow_input: WorkflowInput
55
63
 
56
64
 
57
65
  class WorkflowTriggerResponse(BaseModel):
@@ -509,6 +517,10 @@ class ChatMode(str, Enum):
509
517
  DATABASE = "DATABASE" # chat with database connection
510
518
  WORKFLOW = "WORKFLOW"
511
519
 
520
+ @classmethod
521
+ def requires_cli(cls, mode: "ChatMode") -> bool:
522
+ return mode not in [cls.DATABASE]
523
+
512
524
 
513
525
  class ChatSource(str, Enum):
514
526
  CLI_SHELL = "CLI_SHELL"
@@ -516,6 +528,7 @@ class ChatSource(str, Enum):
516
528
  WEB = "WEB"
517
529
  DESKTOP_APP = "DESKTOP_APP"
518
530
  VSCODE_EXTENSION = "VSCODE_EXTENSION"
531
+ SLACK_APP = "SLACK_APP"
519
532
 
520
533
 
521
534
  class CLIConnectedState(BaseModel):
@@ -135,8 +135,8 @@ def _get_upgrade_command_str(version: str) -> str:
135
135
 
136
136
  def _new_version_str(current_version: str, new_version: str) -> str:
137
137
  return (
138
- f"\n{click.style('A new Exponent version is available:', fg='cyan')} {new_version} (current: {current_version})\n"
139
- f"See {click.style('https://docs.exponent.run/installation', underline=True)} for details.\n"
138
+ f"\n{click.style('A new Indent version is available:', fg='cyan')} {new_version} (current: {current_version})\n"
139
+ f"See {click.style('https://docs.indent.com/help_and_resources/troubleshooting#installation-methods', underline=True)} for details.\n"
140
140
  )
141
141
 
142
142
 
@@ -196,15 +196,15 @@ def upgrade_exponent(
196
196
 
197
197
  if result.returncode != 0:
198
198
  click.secho(
199
- "\nFailed to upgrade Exponent. See https://docs.exponent.run/installation for help, or reach out to team@exponent.run.",
199
+ "\nFailed to upgrade Indent. See https://docs.indent.com/help_and_resources/troubleshooting#installation-methods for help, or reach out to team@indent.com",
200
200
  fg="red",
201
201
  )
202
202
  sys.exit(2)
203
203
 
204
- click.secho(f"Successfully upgraded Exponent to version {new_version}!", fg="green")
204
+ click.secho(f"Successfully upgraded Indent to version {new_version}!", fg="green")
205
205
 
206
- click.echo("Re-run exponent to use the latest version.")
207
- sys.exit(1)
206
+ click.echo("Re-run indent to use the latest version.")
207
+ sys.exit(0)
208
208
 
209
209
 
210
210
  def _upgrade_thread_worker(
File without changes
File without changes
File without changes
File without changes
File without changes