indent 0.1.7__tar.gz → 0.1.9__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.9}/.gitignore +5 -1
- {indent-0.1.7 → indent-0.1.9}/PKG-INFO +2 -2
- indent-0.1.9/exponent/__init__.py +21 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/cli.py +8 -10
- {indent-0.1.7 → indent-0.1.9}/exponent/commands/config_commands.py +10 -10
- {indent-0.1.7 → indent-0.1.9}/exponent/commands/run_commands.py +1 -1
- {indent-0.1.7 → indent-0.1.9}/exponent/commands/upgrade.py +3 -3
- {indent-0.1.7 → indent-0.1.9}/exponent/commands/utils.py +0 -90
- {indent-0.1.7 → indent-0.1.9}/exponent/commands/workflow_commands.py +1 -1
- {indent-0.1.7 → indent-0.1.9}/exponent/core/config.py +2 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/graphql/queries.py +13 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/graphql/subscriptions.py +13 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/cli_rpc_types.py +25 -1
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/client.py +79 -1
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/files.py +0 -12
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/http_fetch.py +23 -15
- indent-0.1.9/exponent/core/remote_execution/system_context.py +27 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/types.py +0 -50
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/utils.py +3 -98
- {indent-0.1.7 → indent-0.1.9}/pyproject.toml +12 -4
- indent-0.1.7/exponent/__init__.py +0 -1
- indent-0.1.7/exponent/commands/github_app_commands.py +0 -211
- indent-0.1.7/exponent/commands/listen_commands.py +0 -96
- indent-0.1.7/exponent/commands/shell_commands.py +0 -2840
- indent-0.1.7/exponent/commands/theme.py +0 -246
- indent-0.1.7/exponent/core/remote_execution/system_context.py +0 -54
- {indent-0.1.7 → indent-0.1.9}/exponent/commands/cloud_commands.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/commands/common.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/commands/settings.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/commands/types.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/graphql/__init__.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/graphql/client.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/graphql/cloud_config_queries.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/graphql/get_chats_query.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/graphql/github_config_queries.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/graphql/mutations.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/checkpoints.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/code_execution.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/error_info.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/exceptions.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/file_write.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/git.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/languages/python_execution.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/languages/shell_streaming.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/languages/types.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/session.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/tool_execution.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/remote_execution/truncation.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/types/__init__.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/types/command_data.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/types/event_types.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/types/generated/__init__.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/core/types/generated/strategy_info.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/migration-docs/login.md +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/py.typed +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/utils/__init__.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/exponent/utils/colors.py +0 -0
- {indent-0.1.7 → indent-0.1.9}/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
|
-
|
|
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
|
|
@@ -174,3 +175,6 @@ private-key.pem
|
|
|
174
175
|
|
|
175
176
|
# Claude Code
|
|
176
177
|
.claude
|
|
178
|
+
|
|
179
|
+
# Generated version files
|
|
180
|
+
python_modules/exponent/exponent/__init__.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: indent
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
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,21 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
6
|
+
TYPE_CHECKING = False
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
12
|
+
else:
|
|
13
|
+
VERSION_TUPLE = object
|
|
14
|
+
|
|
15
|
+
version: str
|
|
16
|
+
__version__: str
|
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
|
18
|
+
version_tuple: VERSION_TUPLE
|
|
19
|
+
|
|
20
|
+
__version__ = version = '0.1.9'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 1, 9)
|
|
@@ -4,10 +4,7 @@ from click import Context, HelpFormatter
|
|
|
4
4
|
from exponent.commands.cloud_commands import cloud_cli
|
|
5
5
|
from exponent.commands.common import set_log_level
|
|
6
6
|
from exponent.commands.config_commands import config_cli
|
|
7
|
-
from exponent.commands.
|
|
8
|
-
from exponent.commands.listen_commands import listen_cli
|
|
9
|
-
from exponent.commands.run_commands import run_cli
|
|
10
|
-
from exponent.commands.shell_commands import shell_cli
|
|
7
|
+
from exponent.commands.run_commands import run, run_cli
|
|
11
8
|
from exponent.commands.types import ExponentGroup, exponent_cli_group
|
|
12
9
|
from exponent.commands.upgrade import upgrade_cli
|
|
13
10
|
from exponent.commands.workflow_commands import workflow_cli
|
|
@@ -16,26 +13,27 @@ from exponent.utils.version import (
|
|
|
16
13
|
)
|
|
17
14
|
|
|
18
15
|
|
|
19
|
-
@exponent_cli_group()
|
|
16
|
+
@exponent_cli_group(invoke_without_command=True)
|
|
20
17
|
@click.version_option(get_installed_version(), prog_name="Indent CLI")
|
|
21
18
|
@click.pass_context
|
|
22
19
|
def cli(ctx: Context) -> None:
|
|
23
20
|
"""
|
|
24
21
|
Indent: Your AI pair programmer.
|
|
25
22
|
|
|
26
|
-
Run indent
|
|
23
|
+
Run indent to start (or indent run)
|
|
27
24
|
"""
|
|
28
25
|
set_log_level()
|
|
29
26
|
|
|
27
|
+
# If no command is provided, invoke the 'run' command
|
|
28
|
+
if ctx.invoked_subcommand is None:
|
|
29
|
+
ctx.invoke(run)
|
|
30
|
+
|
|
30
31
|
|
|
31
32
|
sources: list[ExponentGroup] = [
|
|
32
33
|
config_cli, # Configuration commands
|
|
33
34
|
run_cli, # Run AI chat commands
|
|
34
|
-
shell_cli, # Shell interaction commands
|
|
35
35
|
upgrade_cli, # Upgrade-related commands
|
|
36
36
|
cloud_cli, # Cloud commands
|
|
37
|
-
listen_cli, # Listen to chat events
|
|
38
|
-
github_app_cli, # Setup github app installation
|
|
39
37
|
workflow_cli, # Workflow commands
|
|
40
38
|
]
|
|
41
39
|
|
|
@@ -99,7 +97,7 @@ def hidden(ctx: Context) -> None:
|
|
|
99
97
|
with formatter.section("Usage"):
|
|
100
98
|
if ctx.parent and ctx.parent.command:
|
|
101
99
|
formatter.write_usage(
|
|
102
|
-
ctx.parent.command.name or "
|
|
100
|
+
ctx.parent.command.name or "indent", "COMMAND [ARGS]..."
|
|
103
101
|
)
|
|
104
102
|
formatter.write_paragraph()
|
|
105
103
|
with formatter.indentation():
|
|
@@ -36,7 +36,7 @@ from exponent.utils.version import (
|
|
|
36
36
|
|
|
37
37
|
@exponent_cli_group()
|
|
38
38
|
def config_cli() -> None:
|
|
39
|
-
"""Manage
|
|
39
|
+
"""Manage Indent configuration settings."""
|
|
40
40
|
pass
|
|
41
41
|
|
|
42
42
|
|
|
@@ -133,12 +133,12 @@ async def repos_for_github_config_task(
|
|
|
133
133
|
@click.option(
|
|
134
134
|
"--set-git-warning-disabled",
|
|
135
135
|
is_flag=True,
|
|
136
|
-
help="Disable the git warning for running
|
|
136
|
+
help="Disable the git warning for running Indent from a non-git repository",
|
|
137
137
|
)
|
|
138
138
|
@click.option(
|
|
139
139
|
"--set-git-warning-enabled",
|
|
140
140
|
is_flag=True,
|
|
141
|
-
help="Enable the git warning for running
|
|
141
|
+
help="Enable the git warning for running Indent from a non-git repository",
|
|
142
142
|
)
|
|
143
143
|
@click.option(
|
|
144
144
|
"--set-auto-upgrade-enabled",
|
|
@@ -198,7 +198,7 @@ def config(
|
|
|
198
198
|
set_base_api_url_override: str | None = None,
|
|
199
199
|
set_base_ws_url_override: str | None = None,
|
|
200
200
|
) -> None:
|
|
201
|
-
"""Display current
|
|
201
|
+
"""Display current Indent configuration."""
|
|
202
202
|
|
|
203
203
|
num_options_set = sum(
|
|
204
204
|
[
|
|
@@ -230,7 +230,7 @@ def config(
|
|
|
230
230
|
settings.write_settings_to_config_file()
|
|
231
231
|
|
|
232
232
|
click.secho(
|
|
233
|
-
"Git warning enabled.
|
|
233
|
+
"Git warning enabled. Indent will now check for a git repository.\n",
|
|
234
234
|
fg="yellow",
|
|
235
235
|
)
|
|
236
236
|
|
|
@@ -239,7 +239,7 @@ def config(
|
|
|
239
239
|
settings.write_settings_to_config_file()
|
|
240
240
|
|
|
241
241
|
click.secho(
|
|
242
|
-
"Git warning disabled.
|
|
242
|
+
"Git warning disabled. Indent will no longer check for a git repository.\n",
|
|
243
243
|
fg="yellow",
|
|
244
244
|
)
|
|
245
245
|
|
|
@@ -248,7 +248,7 @@ def config(
|
|
|
248
248
|
settings.write_settings_to_config_file()
|
|
249
249
|
|
|
250
250
|
click.secho(
|
|
251
|
-
"Automatic upgrades enabled.
|
|
251
|
+
"Automatic upgrades enabled. Indent will now check for updates.\n",
|
|
252
252
|
fg="yellow",
|
|
253
253
|
)
|
|
254
254
|
|
|
@@ -257,7 +257,7 @@ def config(
|
|
|
257
257
|
settings.write_settings_to_config_file()
|
|
258
258
|
|
|
259
259
|
click.secho(
|
|
260
|
-
"Automatic upgrades disabled.
|
|
260
|
+
"Automatic upgrades disabled. Indent will no longer check for updates.\n",
|
|
261
261
|
fg="yellow",
|
|
262
262
|
)
|
|
263
263
|
|
|
@@ -312,10 +312,10 @@ def config(
|
|
|
312
312
|
|
|
313
313
|
|
|
314
314
|
@config_cli.command()
|
|
315
|
-
@click.option("--key", help="Your
|
|
315
|
+
@click.option("--key", help="Your Indent API Key")
|
|
316
316
|
@use_settings
|
|
317
317
|
def login(settings: Settings, key: str) -> None:
|
|
318
|
-
"""Log in to
|
|
318
|
+
"""Log in to Indent."""
|
|
319
319
|
|
|
320
320
|
if not key:
|
|
321
321
|
redirect_to_login(settings, "provided")
|
|
@@ -68,7 +68,7 @@ def run(
|
|
|
68
68
|
prompt: str | None = None,
|
|
69
69
|
workflow_id: str | None = None,
|
|
70
70
|
) -> None:
|
|
71
|
-
"""Start or reconnect to an
|
|
71
|
+
"""[default] Start or reconnect to an Indent session."""
|
|
72
72
|
check_exponent_version_and_upgrade(settings)
|
|
73
73
|
|
|
74
74
|
if not settings.api_key:
|
|
@@ -6,7 +6,7 @@ from exponent.utils.version import check_exponent_version, upgrade_exponent
|
|
|
6
6
|
|
|
7
7
|
@exponent_cli_group()
|
|
8
8
|
def upgrade_cli() -> None:
|
|
9
|
-
"""Manage
|
|
9
|
+
"""Manage Indent version upgrades."""
|
|
10
10
|
pass
|
|
11
11
|
|
|
12
12
|
|
|
@@ -17,7 +17,7 @@ def upgrade_cli() -> None:
|
|
|
17
17
|
help="Upgrade without prompting for confirmation, if a new version is available.",
|
|
18
18
|
)
|
|
19
19
|
def upgrade(force: bool = False) -> None:
|
|
20
|
-
"""Upgrade
|
|
20
|
+
"""Upgrade Indent to the latest version."""
|
|
21
21
|
if result := check_exponent_version():
|
|
22
22
|
installed_version, latest_version = result
|
|
23
23
|
upgrade_exponent(
|
|
@@ -26,4 +26,4 @@ def upgrade(force: bool = False) -> None:
|
|
|
26
26
|
force=force,
|
|
27
27
|
)
|
|
28
28
|
else:
|
|
29
|
-
click.echo("
|
|
29
|
+
click.echo("Indent is already up to date.")
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import os
|
|
3
3
|
import sys
|
|
4
|
-
import threading
|
|
5
4
|
import time
|
|
6
5
|
import webbrowser
|
|
7
6
|
|
|
@@ -69,43 +68,6 @@ def launch_exponent_browser(
|
|
|
69
68
|
webbrowser.open(url)
|
|
70
69
|
|
|
71
70
|
|
|
72
|
-
def start_background_event_loop() -> asyncio.AbstractEventLoop:
|
|
73
|
-
def run_event_loop(loop: asyncio.AbstractEventLoop) -> None:
|
|
74
|
-
asyncio.set_event_loop(loop)
|
|
75
|
-
loop.run_forever()
|
|
76
|
-
|
|
77
|
-
loop = asyncio.new_event_loop()
|
|
78
|
-
thread = threading.Thread(target=run_event_loop, args=(loop,), daemon=True)
|
|
79
|
-
thread.start()
|
|
80
|
-
return loop
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def read_input(prompt: str) -> str:
|
|
84
|
-
sys.stdout.write(prompt)
|
|
85
|
-
sys.stdout.flush()
|
|
86
|
-
return sys.stdin.readline()
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def ask_for_quit_confirmation(program_name: str = "Exponent") -> bool:
|
|
90
|
-
while True:
|
|
91
|
-
try:
|
|
92
|
-
answer = (
|
|
93
|
-
input(f"Do you want to quit {program_name}? [y/\x1b[1mN\x1b[0m]")
|
|
94
|
-
.strip()
|
|
95
|
-
.lower()
|
|
96
|
-
)
|
|
97
|
-
if answer in {"y", "yes"}:
|
|
98
|
-
return True
|
|
99
|
-
elif answer in {"n", "no", ""}:
|
|
100
|
-
return False
|
|
101
|
-
except KeyboardInterrupt:
|
|
102
|
-
print()
|
|
103
|
-
return True
|
|
104
|
-
except EOFError:
|
|
105
|
-
print()
|
|
106
|
-
return True
|
|
107
|
-
|
|
108
|
-
|
|
109
71
|
class Spinner:
|
|
110
72
|
def __init__(self, text: str) -> None:
|
|
111
73
|
self.text = text
|
|
@@ -154,58 +116,6 @@ class Spinner:
|
|
|
154
116
|
sys.stdout.flush()
|
|
155
117
|
|
|
156
118
|
|
|
157
|
-
class ThinkingSpinner(Spinner):
|
|
158
|
-
def __init__(
|
|
159
|
-
self,
|
|
160
|
-
fg_color: tuple[int, int, int] | None = None,
|
|
161
|
-
text: str = "Exponent is thinking",
|
|
162
|
-
) -> None:
|
|
163
|
-
super().__init__(text)
|
|
164
|
-
self.fg_color = fg_color
|
|
165
|
-
self.bold = True
|
|
166
|
-
self.animation_chars = "⣾⣽⣻⢿⡿⣟⣯⣷" # More subtle animation
|
|
167
|
-
self.animation_speed = 8 # Medium speed animation
|
|
168
|
-
self.start_time = time.time() # Track when thinking started
|
|
169
|
-
|
|
170
|
-
def show(self) -> None:
|
|
171
|
-
if self.task is not None:
|
|
172
|
-
return
|
|
173
|
-
|
|
174
|
-
async def spinner(base_time: float) -> None:
|
|
175
|
-
color_start = ""
|
|
176
|
-
if self.fg_color:
|
|
177
|
-
if isinstance(self.fg_color, tuple) and len(self.fg_color) == 3:
|
|
178
|
-
r, g, b = self.fg_color
|
|
179
|
-
color_start = f"\x1b[38;2;{r};{g};{b}m"
|
|
180
|
-
elif isinstance(self.fg_color, int):
|
|
181
|
-
color_start = f"\x1b[{30 + self.fg_color}m"
|
|
182
|
-
|
|
183
|
-
bold_start = "\x1b[1m" if self.bold else ""
|
|
184
|
-
style_start = f"{color_start}{bold_start}"
|
|
185
|
-
style_end = "\x1b[0m"
|
|
186
|
-
|
|
187
|
-
while True:
|
|
188
|
-
t = time.time() - base_time
|
|
189
|
-
i = round(t * self.animation_speed) % len(self.animation_chars)
|
|
190
|
-
|
|
191
|
-
# Calculate elapsed seconds
|
|
192
|
-
elapsed = time.time() - self.start_time
|
|
193
|
-
if elapsed < 60:
|
|
194
|
-
timer = f"{int(elapsed)}s"
|
|
195
|
-
else:
|
|
196
|
-
mins = int(elapsed // 60)
|
|
197
|
-
secs = int(elapsed % 60)
|
|
198
|
-
timer = f"{mins}m {secs}s"
|
|
199
|
-
|
|
200
|
-
print(
|
|
201
|
-
f"\r{style_start}{self.animation_chars[i]} {self.text} ({timer}){style_end}",
|
|
202
|
-
end="",
|
|
203
|
-
)
|
|
204
|
-
await asyncio.sleep(0.1)
|
|
205
|
-
|
|
206
|
-
self.task = asyncio.get_event_loop().create_task(spinner(self.base_time))
|
|
207
|
-
|
|
208
|
-
|
|
209
119
|
class ConnectionTracker:
|
|
210
120
|
def __init__(self) -> None:
|
|
211
121
|
self.connected = True
|
|
@@ -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,
|
|
@@ -150,7 +152,63 @@ class RemoteExecutionClient:
|
|
|
150
152
|
return None
|
|
151
153
|
|
|
152
154
|
data = json.dumps(msg_data["data"])
|
|
153
|
-
|
|
155
|
+
try:
|
|
156
|
+
request = msgspec.json.decode(data, type=CliRpcRequest)
|
|
157
|
+
except msgspec.DecodeError as e:
|
|
158
|
+
# Try and decode to get request_id if possible
|
|
159
|
+
request = msgspec.json.decode(data)
|
|
160
|
+
if isinstance(request, dict) and "request_id" in request:
|
|
161
|
+
request_id = request["request_id"]
|
|
162
|
+
if (
|
|
163
|
+
request.get("request", {}).get("type", {}) == "tool_execution"
|
|
164
|
+
) and (
|
|
165
|
+
"tool_input" in request["request"]
|
|
166
|
+
and "tool_name" in request["request"]["tool_input"]
|
|
167
|
+
):
|
|
168
|
+
tool_name = request["request"]["tool_input"]["tool_name"]
|
|
169
|
+
logger.error(
|
|
170
|
+
f"Error tool {tool_name} received in a request."
|
|
171
|
+
"Please ensure you are running the latest version of Indent. If this issue persists, please contact support."
|
|
172
|
+
)
|
|
173
|
+
await websocket.send(
|
|
174
|
+
json.dumps(
|
|
175
|
+
{
|
|
176
|
+
"type": "result",
|
|
177
|
+
"data": msgspec.to_builtins(
|
|
178
|
+
CliRpcResponse(
|
|
179
|
+
request_id=request_id,
|
|
180
|
+
response=ErrorResponse(
|
|
181
|
+
error_message=f"Unknown tool: {tool_name}. If you are running an older version of Indent, please upgrade to the latest version to ensure compatibility."
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
),
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
else:
|
|
189
|
+
logger.error(
|
|
190
|
+
"Error decoding cli rpc request. Please ensure you are running the latest version of Indent."
|
|
191
|
+
)
|
|
192
|
+
await websocket.send(
|
|
193
|
+
json.dumps(
|
|
194
|
+
{
|
|
195
|
+
"type": "result",
|
|
196
|
+
"data": msgspec.to_builtins(
|
|
197
|
+
CliRpcResponse(
|
|
198
|
+
request_id=request_id,
|
|
199
|
+
response=ErrorResponse(
|
|
200
|
+
error_message=f"Unknown cli rpc request type: {request}",
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
),
|
|
204
|
+
}
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
return None
|
|
209
|
+
else:
|
|
210
|
+
# If we couldn't get a request_id, re-raise and fail noisily
|
|
211
|
+
raise e
|
|
154
212
|
|
|
155
213
|
if isinstance(request.request, TerminateRequest):
|
|
156
214
|
await self.halt_all_code_executions()
|
|
@@ -168,6 +226,21 @@ class RemoteExecutionClient:
|
|
|
168
226
|
)
|
|
169
227
|
)
|
|
170
228
|
return None
|
|
229
|
+
elif isinstance(request.request, SwitchCLIChatRequest):
|
|
230
|
+
await websocket.send(
|
|
231
|
+
json.dumps(
|
|
232
|
+
{
|
|
233
|
+
"type": "result",
|
|
234
|
+
"data": msgspec.to_builtins(
|
|
235
|
+
CliRpcResponse(
|
|
236
|
+
request_id=request.request_id,
|
|
237
|
+
response=SwitchCLIChatResponse(),
|
|
238
|
+
)
|
|
239
|
+
),
|
|
240
|
+
}
|
|
241
|
+
)
|
|
242
|
+
)
|
|
243
|
+
return SwitchCLIChat(new_chat_uuid=request.request.new_chat_uuid)
|
|
171
244
|
else:
|
|
172
245
|
if isinstance(request.request, ToolExecutionRequest) and isinstance(
|
|
173
246
|
request.request.tool_input, BashToolInput
|
|
@@ -502,6 +575,11 @@ class RemoteExecutionClient:
|
|
|
502
575
|
"TerminateRequest should not be handled by handle_request"
|
|
503
576
|
)
|
|
504
577
|
|
|
578
|
+
elif isinstance(request.request, SwitchCLIChatRequest):
|
|
579
|
+
raise ValueError(
|
|
580
|
+
"SwitchCLIChatRequest should not be handled by handle_request"
|
|
581
|
+
)
|
|
582
|
+
|
|
505
583
|
raise ValueError(f"Unhandled request type: {type(request)}")
|
|
506
584
|
|
|
507
585
|
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,
|