indent 0.1.26__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- exponent/__init__.py +34 -0
- exponent/cli.py +110 -0
- exponent/commands/cloud_commands.py +585 -0
- exponent/commands/common.py +411 -0
- exponent/commands/config_commands.py +334 -0
- exponent/commands/run_commands.py +222 -0
- exponent/commands/settings.py +56 -0
- exponent/commands/types.py +111 -0
- exponent/commands/upgrade.py +29 -0
- exponent/commands/utils.py +146 -0
- exponent/core/config.py +180 -0
- exponent/core/graphql/__init__.py +0 -0
- exponent/core/graphql/client.py +61 -0
- exponent/core/graphql/get_chats_query.py +47 -0
- exponent/core/graphql/mutations.py +160 -0
- exponent/core/graphql/queries.py +146 -0
- exponent/core/graphql/subscriptions.py +16 -0
- exponent/core/remote_execution/checkpoints.py +212 -0
- exponent/core/remote_execution/cli_rpc_types.py +499 -0
- exponent/core/remote_execution/client.py +999 -0
- exponent/core/remote_execution/code_execution.py +77 -0
- exponent/core/remote_execution/default_env.py +31 -0
- exponent/core/remote_execution/error_info.py +45 -0
- exponent/core/remote_execution/exceptions.py +10 -0
- exponent/core/remote_execution/file_write.py +35 -0
- exponent/core/remote_execution/files.py +330 -0
- exponent/core/remote_execution/git.py +268 -0
- exponent/core/remote_execution/http_fetch.py +94 -0
- exponent/core/remote_execution/languages/python_execution.py +239 -0
- exponent/core/remote_execution/languages/shell_streaming.py +226 -0
- exponent/core/remote_execution/languages/types.py +20 -0
- exponent/core/remote_execution/port_utils.py +73 -0
- exponent/core/remote_execution/session.py +128 -0
- exponent/core/remote_execution/system_context.py +26 -0
- exponent/core/remote_execution/terminal_session.py +375 -0
- exponent/core/remote_execution/terminal_types.py +29 -0
- exponent/core/remote_execution/tool_execution.py +595 -0
- exponent/core/remote_execution/tool_type_utils.py +39 -0
- exponent/core/remote_execution/truncation.py +296 -0
- exponent/core/remote_execution/types.py +635 -0
- exponent/core/remote_execution/utils.py +477 -0
- exponent/core/types/__init__.py +0 -0
- exponent/core/types/command_data.py +206 -0
- exponent/core/types/event_types.py +89 -0
- exponent/core/types/generated/__init__.py +0 -0
- exponent/core/types/generated/strategy_info.py +213 -0
- exponent/migration-docs/login.md +112 -0
- exponent/py.typed +4 -0
- exponent/utils/__init__.py +0 -0
- exponent/utils/colors.py +92 -0
- exponent/utils/version.py +289 -0
- indent-0.1.26.dist-info/METADATA +38 -0
- indent-0.1.26.dist-info/RECORD +55 -0
- indent-0.1.26.dist-info/WHEEL +4 -0
- indent-0.1.26.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
from exponent.commands.common import (
|
|
7
|
+
redirect_to_login,
|
|
8
|
+
refresh_api_key_task,
|
|
9
|
+
run_until_complete,
|
|
10
|
+
set_login_complete,
|
|
11
|
+
)
|
|
12
|
+
from exponent.commands.settings import use_settings
|
|
13
|
+
from exponent.commands.types import exponent_cli_group
|
|
14
|
+
from exponent.core.config import Settings
|
|
15
|
+
from exponent.core.graphql.client import GraphQLClient
|
|
16
|
+
from exponent.core.graphql.get_chats_query import GET_CHATS_QUERY
|
|
17
|
+
from exponent.core.graphql.subscriptions import AUTHENTICATED_USER_SUBSCRIPTION
|
|
18
|
+
from exponent.utils.version import (
|
|
19
|
+
get_installed_metadata,
|
|
20
|
+
get_installed_version,
|
|
21
|
+
get_installer,
|
|
22
|
+
get_python_path,
|
|
23
|
+
get_sys_executable,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@exponent_cli_group()
|
|
28
|
+
def config_cli() -> None:
|
|
29
|
+
"""Manage Indent configuration settings."""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@config_cli.command(hidden=True)
|
|
34
|
+
@use_settings
|
|
35
|
+
def debug(
|
|
36
|
+
settings: Settings,
|
|
37
|
+
) -> None:
|
|
38
|
+
click.echo("Settings:")
|
|
39
|
+
click.echo(settings)
|
|
40
|
+
click.echo("\nInstalled Version:")
|
|
41
|
+
click.echo(get_installed_version())
|
|
42
|
+
click.echo("\nPython Version:")
|
|
43
|
+
click.echo(sys.version)
|
|
44
|
+
click.echo("\nPython Path:")
|
|
45
|
+
click.echo(get_python_path())
|
|
46
|
+
click.echo("\nSys Executable Path:")
|
|
47
|
+
click.echo(get_sys_executable())
|
|
48
|
+
click.echo("\nInstaller:")
|
|
49
|
+
click.echo(get_installer())
|
|
50
|
+
click.echo("\nInstalled Metadata:")
|
|
51
|
+
click.echo(get_installed_metadata())
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@config_cli.command(hidden=True)
|
|
55
|
+
@click.option(
|
|
56
|
+
"--set-git-warning-disabled",
|
|
57
|
+
is_flag=True,
|
|
58
|
+
help="Disable the git warning for running Indent from a non-git repository",
|
|
59
|
+
)
|
|
60
|
+
@click.option(
|
|
61
|
+
"--set-git-warning-enabled",
|
|
62
|
+
is_flag=True,
|
|
63
|
+
help="Enable the git warning for running Indent from a non-git repository",
|
|
64
|
+
)
|
|
65
|
+
@click.option(
|
|
66
|
+
"--set-auto-upgrade-enabled",
|
|
67
|
+
is_flag=True,
|
|
68
|
+
help="Enable automatic upgrades",
|
|
69
|
+
)
|
|
70
|
+
@click.option(
|
|
71
|
+
"--set-auto-upgrade-disabled",
|
|
72
|
+
is_flag=True,
|
|
73
|
+
help="Disable automatic upgrades",
|
|
74
|
+
)
|
|
75
|
+
@click.option(
|
|
76
|
+
"--set-base-api-url-override",
|
|
77
|
+
required=False,
|
|
78
|
+
hidden=True,
|
|
79
|
+
help="Override base API URL",
|
|
80
|
+
)
|
|
81
|
+
@click.option(
|
|
82
|
+
"--clear-base-api-url-override",
|
|
83
|
+
is_flag=True,
|
|
84
|
+
hidden=True,
|
|
85
|
+
help="Clear base API URL override",
|
|
86
|
+
)
|
|
87
|
+
@click.option(
|
|
88
|
+
"--set-base-ws-url-override",
|
|
89
|
+
required=False,
|
|
90
|
+
hidden=True,
|
|
91
|
+
help="Override base WS URL",
|
|
92
|
+
)
|
|
93
|
+
@click.option(
|
|
94
|
+
"--clear-base-ws-url-override",
|
|
95
|
+
is_flag=True,
|
|
96
|
+
hidden=True,
|
|
97
|
+
help="Clear base WS URL override",
|
|
98
|
+
)
|
|
99
|
+
@click.option(
|
|
100
|
+
"--set-use-default-colors",
|
|
101
|
+
is_flag=True,
|
|
102
|
+
help="Use default colors",
|
|
103
|
+
)
|
|
104
|
+
@click.option(
|
|
105
|
+
"--clear-use-default-colors",
|
|
106
|
+
is_flag=True,
|
|
107
|
+
help="Clear use default colors",
|
|
108
|
+
)
|
|
109
|
+
@use_settings
|
|
110
|
+
def config( # noqa: PLR0913
|
|
111
|
+
settings: Settings,
|
|
112
|
+
set_git_warning_disabled: bool,
|
|
113
|
+
set_git_warning_enabled: bool,
|
|
114
|
+
set_auto_upgrade_enabled: bool,
|
|
115
|
+
set_auto_upgrade_disabled: bool,
|
|
116
|
+
clear_base_api_url_override: bool,
|
|
117
|
+
clear_base_ws_url_override: bool,
|
|
118
|
+
set_use_default_colors: bool,
|
|
119
|
+
clear_use_default_colors: bool,
|
|
120
|
+
set_base_api_url_override: str | None = None,
|
|
121
|
+
set_base_ws_url_override: str | None = None,
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Display current Indent configuration."""
|
|
124
|
+
|
|
125
|
+
num_options_set = sum(
|
|
126
|
+
[
|
|
127
|
+
set_git_warning_disabled,
|
|
128
|
+
set_git_warning_enabled,
|
|
129
|
+
set_auto_upgrade_enabled,
|
|
130
|
+
set_auto_upgrade_disabled,
|
|
131
|
+
clear_base_ws_url_override,
|
|
132
|
+
clear_base_api_url_override,
|
|
133
|
+
set_base_api_url_override is not None,
|
|
134
|
+
set_base_ws_url_override is not None,
|
|
135
|
+
set_use_default_colors,
|
|
136
|
+
clear_use_default_colors,
|
|
137
|
+
]
|
|
138
|
+
)
|
|
139
|
+
if num_options_set == 0:
|
|
140
|
+
click.secho(
|
|
141
|
+
json.dumps(settings.get_config_file_settings(), indent=2),
|
|
142
|
+
fg="green",
|
|
143
|
+
)
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
if num_options_set > 1:
|
|
147
|
+
click.secho("Cannot set multiple options at the same time.", fg="red")
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
if set_git_warning_enabled:
|
|
151
|
+
settings.options.git_warning_disabled = False
|
|
152
|
+
settings.write_settings_to_config_file()
|
|
153
|
+
|
|
154
|
+
click.secho(
|
|
155
|
+
"Git warning enabled. Indent will now check for a git repository.\n",
|
|
156
|
+
fg="yellow",
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if set_git_warning_disabled:
|
|
160
|
+
settings.options.git_warning_disabled = True
|
|
161
|
+
settings.write_settings_to_config_file()
|
|
162
|
+
|
|
163
|
+
click.secho(
|
|
164
|
+
"Git warning disabled. Indent will no longer check for a git repository.\n",
|
|
165
|
+
fg="yellow",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if set_auto_upgrade_enabled:
|
|
169
|
+
settings.options.auto_upgrade = True
|
|
170
|
+
settings.write_settings_to_config_file()
|
|
171
|
+
|
|
172
|
+
click.secho(
|
|
173
|
+
"Automatic upgrades enabled. Indent will now check for updates.\n",
|
|
174
|
+
fg="yellow",
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if set_auto_upgrade_disabled:
|
|
178
|
+
settings.options.auto_upgrade = False
|
|
179
|
+
settings.write_settings_to_config_file()
|
|
180
|
+
|
|
181
|
+
click.secho(
|
|
182
|
+
"Automatic upgrades disabled. Indent will no longer check for updates.\n",
|
|
183
|
+
fg="yellow",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if clear_base_api_url_override:
|
|
187
|
+
settings.options.base_api_url_override = None
|
|
188
|
+
settings.write_settings_to_config_file()
|
|
189
|
+
|
|
190
|
+
click.secho(
|
|
191
|
+
"API URL override cleared.",
|
|
192
|
+
fg="yellow",
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if clear_base_ws_url_override:
|
|
196
|
+
settings.options.base_ws_url_override = None
|
|
197
|
+
settings.write_settings_to_config_file()
|
|
198
|
+
|
|
199
|
+
click.secho(
|
|
200
|
+
"WS URL override cleared.",
|
|
201
|
+
fg="yellow",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if set_base_api_url_override:
|
|
205
|
+
settings.options.base_api_url_override = set_base_api_url_override
|
|
206
|
+
settings.write_settings_to_config_file()
|
|
207
|
+
|
|
208
|
+
click.secho(
|
|
209
|
+
"API URL override set.",
|
|
210
|
+
fg="yellow",
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if set_base_ws_url_override:
|
|
214
|
+
settings.options.base_ws_url_override = set_base_ws_url_override
|
|
215
|
+
settings.write_settings_to_config_file()
|
|
216
|
+
|
|
217
|
+
click.secho(
|
|
218
|
+
"WS URL override set.",
|
|
219
|
+
fg="yellow",
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
if set_use_default_colors:
|
|
223
|
+
settings.options.use_default_colors = True
|
|
224
|
+
settings.write_settings_to_config_file()
|
|
225
|
+
|
|
226
|
+
click.secho(
|
|
227
|
+
"Use default colors set.",
|
|
228
|
+
fg="yellow",
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
if clear_use_default_colors:
|
|
232
|
+
settings.options.use_default_colors = False
|
|
233
|
+
settings.write_settings_to_config_file()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@config_cli.command()
|
|
237
|
+
@click.option("--key", help="Your Indent API Key")
|
|
238
|
+
@use_settings
|
|
239
|
+
def login(settings: Settings, key: str) -> None:
|
|
240
|
+
"""Log in to Indent."""
|
|
241
|
+
|
|
242
|
+
if not key:
|
|
243
|
+
redirect_to_login(settings, "provided")
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
click.echo("Verifying API key...")
|
|
247
|
+
run_until_complete(
|
|
248
|
+
set_login_complete(key, settings.get_base_api_url(), settings.get_base_ws_url())
|
|
249
|
+
)
|
|
250
|
+
click.secho("Success!", fg="green")
|
|
251
|
+
|
|
252
|
+
click.echo(f"Saving API Key to {settings.config_file_path}")
|
|
253
|
+
|
|
254
|
+
if settings.api_key and settings.api_key != key:
|
|
255
|
+
click.confirm("Detected existing API Key, continue? ", default=True, abort=True)
|
|
256
|
+
|
|
257
|
+
settings.update_api_key(key)
|
|
258
|
+
settings.write_settings_to_config_file()
|
|
259
|
+
|
|
260
|
+
click.echo("API Key saved.")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@config_cli.command(hidden=True)
|
|
264
|
+
@use_settings
|
|
265
|
+
def get_chats(
|
|
266
|
+
settings: Settings,
|
|
267
|
+
) -> None:
|
|
268
|
+
if not settings.api_key:
|
|
269
|
+
redirect_to_login(settings)
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
run_until_complete(
|
|
273
|
+
get_chats_task(
|
|
274
|
+
api_key=settings.api_key,
|
|
275
|
+
base_api_url=settings.get_base_api_url(),
|
|
276
|
+
base_ws_url=settings.get_base_ws_url(),
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@config_cli.command(hidden=True)
|
|
282
|
+
@use_settings
|
|
283
|
+
def get_authenticated_user(
|
|
284
|
+
settings: Settings,
|
|
285
|
+
) -> None:
|
|
286
|
+
if not settings.api_key:
|
|
287
|
+
redirect_to_login(settings)
|
|
288
|
+
return
|
|
289
|
+
|
|
290
|
+
run_until_complete(
|
|
291
|
+
get_authenticated_user_task(
|
|
292
|
+
api_key=settings.api_key,
|
|
293
|
+
base_api_url=settings.get_base_api_url(),
|
|
294
|
+
base_ws_url=settings.get_base_ws_url(),
|
|
295
|
+
)
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
async def get_chats_task(
|
|
300
|
+
api_key: str,
|
|
301
|
+
base_api_url: str,
|
|
302
|
+
base_ws_url: str,
|
|
303
|
+
) -> None:
|
|
304
|
+
graphql_client = GraphQLClient(api_key, base_api_url, base_ws_url)
|
|
305
|
+
result = await graphql_client.execute(GET_CHATS_QUERY)
|
|
306
|
+
click.echo(result)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
async def get_authenticated_user_task(
|
|
310
|
+
api_key: str,
|
|
311
|
+
base_api_url: str,
|
|
312
|
+
base_ws_url: str,
|
|
313
|
+
) -> None:
|
|
314
|
+
graphql_client = GraphQLClient(api_key, base_api_url, base_ws_url)
|
|
315
|
+
async for it in graphql_client.subscribe(AUTHENTICATED_USER_SUBSCRIPTION):
|
|
316
|
+
click.echo(it)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@config_cli.command(hidden=True)
|
|
320
|
+
@use_settings
|
|
321
|
+
def refresh_key(settings: Settings) -> None:
|
|
322
|
+
"""Refresh your API key."""
|
|
323
|
+
if not settings.api_key:
|
|
324
|
+
redirect_to_login(settings)
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
click.echo("Refreshing API key...")
|
|
328
|
+
run_until_complete(
|
|
329
|
+
refresh_api_key_task(
|
|
330
|
+
api_key=settings.api_key,
|
|
331
|
+
base_api_url=settings.get_base_api_url(),
|
|
332
|
+
base_ws_url=settings.get_base_ws_url(),
|
|
333
|
+
)
|
|
334
|
+
)
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from exponent.commands.common import (
|
|
8
|
+
check_inside_git_repo,
|
|
9
|
+
check_running_from_home_directory,
|
|
10
|
+
check_ssl,
|
|
11
|
+
create_chat,
|
|
12
|
+
inside_ssh_session,
|
|
13
|
+
redirect_to_login,
|
|
14
|
+
start_client,
|
|
15
|
+
)
|
|
16
|
+
from exponent.commands.settings import use_settings
|
|
17
|
+
from exponent.commands.types import exponent_cli_group
|
|
18
|
+
from exponent.commands.utils import (
|
|
19
|
+
ConnectionTracker,
|
|
20
|
+
Spinner,
|
|
21
|
+
launch_exponent_browser,
|
|
22
|
+
print_exponent_message,
|
|
23
|
+
)
|
|
24
|
+
from exponent.core.config import Settings
|
|
25
|
+
from exponent.core.remote_execution.client import (
|
|
26
|
+
REMOTE_EXECUTION_CLIENT_EXIT_INFO,
|
|
27
|
+
SwitchCLIChat,
|
|
28
|
+
WSDisconnected,
|
|
29
|
+
)
|
|
30
|
+
from exponent.core.remote_execution.types import ChatSource
|
|
31
|
+
from exponent.core.remote_execution.utils import assert_unreachable
|
|
32
|
+
from exponent.utils.version import check_exponent_version_and_upgrade
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
# this is an optional dependency for python <3.11
|
|
36
|
+
from async_timeout import timeout
|
|
37
|
+
except ImportError: # pragma: no cover
|
|
38
|
+
from asyncio import timeout
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@exponent_cli_group()
|
|
42
|
+
def run_cli() -> None:
|
|
43
|
+
"""Run AI-powered chat sessions."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@run_cli.command()
|
|
48
|
+
@click.option(
|
|
49
|
+
"--chat-id",
|
|
50
|
+
help="ID of an existing chat session to reconnect",
|
|
51
|
+
required=False,
|
|
52
|
+
prompt_required=False,
|
|
53
|
+
prompt="Enter the chat ID to reconnect to",
|
|
54
|
+
)
|
|
55
|
+
@click.option(
|
|
56
|
+
"--prompt",
|
|
57
|
+
help="Start a chat with a given prompt.",
|
|
58
|
+
)
|
|
59
|
+
@click.option(
|
|
60
|
+
"--workflow-id",
|
|
61
|
+
hidden=True,
|
|
62
|
+
required=False,
|
|
63
|
+
)
|
|
64
|
+
@click.option(
|
|
65
|
+
"--timeout-seconds",
|
|
66
|
+
type=int,
|
|
67
|
+
help="Number of seconds without receiving a request before shutting down",
|
|
68
|
+
envvar="INDENT_TIMEOUT_SECONDS",
|
|
69
|
+
)
|
|
70
|
+
@use_settings
|
|
71
|
+
def run(
|
|
72
|
+
settings: Settings,
|
|
73
|
+
chat_id: str | None = None,
|
|
74
|
+
prompt: str | None = None,
|
|
75
|
+
workflow_id: str | None = None,
|
|
76
|
+
timeout_seconds: int | None = None,
|
|
77
|
+
) -> None:
|
|
78
|
+
"""[default] Start or reconnect to an Indent session."""
|
|
79
|
+
check_exponent_version_and_upgrade(settings)
|
|
80
|
+
|
|
81
|
+
if not settings.api_key:
|
|
82
|
+
redirect_to_login(settings)
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
loop = asyncio.get_event_loop()
|
|
86
|
+
|
|
87
|
+
check_running_from_home_directory()
|
|
88
|
+
check_ssl()
|
|
89
|
+
loop.run_until_complete(check_inside_git_repo(settings))
|
|
90
|
+
|
|
91
|
+
api_key = settings.api_key
|
|
92
|
+
base_url = settings.base_url
|
|
93
|
+
base_api_url = settings.get_base_api_url()
|
|
94
|
+
base_ws_url = settings.get_base_ws_url()
|
|
95
|
+
|
|
96
|
+
chat_uuid = chat_id or loop.run_until_complete(
|
|
97
|
+
create_chat(api_key, base_api_url, base_ws_url, ChatSource.CLI_RUN)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if isinstance(timeout_seconds, int) and timeout_seconds <= 0:
|
|
101
|
+
click.secho("Error: --timeout-seconds must be a positive integer", fg="red")
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
|
|
104
|
+
if chat_uuid is None:
|
|
105
|
+
sys.exit(1)
|
|
106
|
+
|
|
107
|
+
if (
|
|
108
|
+
(not inside_ssh_session())
|
|
109
|
+
and (not workflow_id)
|
|
110
|
+
# If the user specified a chat ID, they probably don't want to re-launch the chat
|
|
111
|
+
and (not chat_id)
|
|
112
|
+
):
|
|
113
|
+
# Open the chat in the browser
|
|
114
|
+
launch_exponent_browser(settings.environment, base_url, chat_uuid)
|
|
115
|
+
|
|
116
|
+
while True:
|
|
117
|
+
result = run_chat(
|
|
118
|
+
loop, api_key, chat_uuid, settings, prompt, workflow_id, timeout_seconds
|
|
119
|
+
)
|
|
120
|
+
if result is None or isinstance(result, WSDisconnected):
|
|
121
|
+
# NOTE: None here means that handle_connection_changes exited
|
|
122
|
+
# first. We should likely have a different message for this.
|
|
123
|
+
if result and result.error_message:
|
|
124
|
+
click.secho(f"Error: {result.error_message}", fg="red")
|
|
125
|
+
sys.exit(10)
|
|
126
|
+
else:
|
|
127
|
+
click.echo("Disconnected upon user request, shutting down...")
|
|
128
|
+
break
|
|
129
|
+
elif isinstance(result, SwitchCLIChat):
|
|
130
|
+
chat_uuid = result.new_chat_uuid
|
|
131
|
+
click.echo("\nSwitching chats...")
|
|
132
|
+
else:
|
|
133
|
+
assert_unreachable(result)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def run_chat(
|
|
137
|
+
loop: asyncio.AbstractEventLoop,
|
|
138
|
+
api_key: str,
|
|
139
|
+
chat_uuid: str,
|
|
140
|
+
settings: Settings,
|
|
141
|
+
prompt: str | None,
|
|
142
|
+
workflow_id: str | None,
|
|
143
|
+
timeout_seconds: int | None,
|
|
144
|
+
) -> REMOTE_EXECUTION_CLIENT_EXIT_INFO | None:
|
|
145
|
+
start_ts = time.time()
|
|
146
|
+
base_url = settings.base_url
|
|
147
|
+
base_api_url = settings.get_base_api_url()
|
|
148
|
+
base_ws_url = settings.get_base_ws_url()
|
|
149
|
+
|
|
150
|
+
print_exponent_message(base_url, chat_uuid)
|
|
151
|
+
click.echo()
|
|
152
|
+
|
|
153
|
+
connection_tracker = ConnectionTracker()
|
|
154
|
+
|
|
155
|
+
client_fut = loop.create_task(
|
|
156
|
+
start_client(
|
|
157
|
+
api_key,
|
|
158
|
+
base_url,
|
|
159
|
+
base_api_url,
|
|
160
|
+
base_ws_url,
|
|
161
|
+
chat_uuid,
|
|
162
|
+
prompt=prompt,
|
|
163
|
+
workflow_id=workflow_id,
|
|
164
|
+
connection_tracker=connection_tracker,
|
|
165
|
+
timeout_seconds=timeout_seconds,
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
conn_fut = loop.create_task(handle_connection_changes(connection_tracker, start_ts))
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
done, _ = loop.run_until_complete(
|
|
173
|
+
asyncio.wait({client_fut, conn_fut}, return_when=asyncio.FIRST_COMPLETED)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if client_fut in done:
|
|
177
|
+
return client_fut.result()
|
|
178
|
+
else:
|
|
179
|
+
return None
|
|
180
|
+
finally:
|
|
181
|
+
for task in asyncio.all_tasks(loop):
|
|
182
|
+
task.cancel()
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
loop.run_until_complete(asyncio.wait(asyncio.all_tasks(loop)))
|
|
186
|
+
except asyncio.CancelledError:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def handle_connection_changes(
|
|
191
|
+
connection_tracker: ConnectionTracker, start_ts: float
|
|
192
|
+
) -> None:
|
|
193
|
+
try:
|
|
194
|
+
async with timeout(5):
|
|
195
|
+
assert await connection_tracker.next_change()
|
|
196
|
+
click.echo(ready_message(start_ts))
|
|
197
|
+
except TimeoutError:
|
|
198
|
+
spinner = Spinner("Connecting...")
|
|
199
|
+
spinner.show()
|
|
200
|
+
assert await connection_tracker.next_change()
|
|
201
|
+
spinner.hide()
|
|
202
|
+
click.echo(ready_message(start_ts))
|
|
203
|
+
|
|
204
|
+
while True:
|
|
205
|
+
assert not await connection_tracker.next_change()
|
|
206
|
+
|
|
207
|
+
click.echo("Disconnected...", nl=False)
|
|
208
|
+
await asyncio.sleep(1)
|
|
209
|
+
spinner = Spinner("Reconnecting...")
|
|
210
|
+
spinner.show()
|
|
211
|
+
assert await connection_tracker.next_change()
|
|
212
|
+
spinner.hide()
|
|
213
|
+
click.echo("\x1b[1;32m✓ Reconnected", nl=False)
|
|
214
|
+
sys.stdout.flush()
|
|
215
|
+
await asyncio.sleep(1)
|
|
216
|
+
click.echo("\r\x1b[0m\x1b[2K", nl=False)
|
|
217
|
+
sys.stdout.flush()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def ready_message(start_ts: float) -> str:
|
|
221
|
+
elapsed = round(time.time() - start_ts, 2)
|
|
222
|
+
return f"\x1b[32m✓\x1b[0m Ready in {elapsed}s"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from exponent.commands.utils import (
|
|
8
|
+
print_editable_install_forced_prod_warning,
|
|
9
|
+
print_editable_install_warning,
|
|
10
|
+
)
|
|
11
|
+
from exponent.core.config import (
|
|
12
|
+
Environment,
|
|
13
|
+
get_settings,
|
|
14
|
+
is_editable_install,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def use_settings(f: Callable[..., Any]) -> Callable[..., Any]:
|
|
19
|
+
@click.option(
|
|
20
|
+
"--prod",
|
|
21
|
+
is_flag=True,
|
|
22
|
+
hidden=True,
|
|
23
|
+
help="Use production URLs even if in editable mode",
|
|
24
|
+
)
|
|
25
|
+
@click.option(
|
|
26
|
+
"--staging",
|
|
27
|
+
is_flag=True,
|
|
28
|
+
hidden=True,
|
|
29
|
+
help="Use staging URLs even if in editable mode",
|
|
30
|
+
)
|
|
31
|
+
@wraps(f)
|
|
32
|
+
def decorated_function(*args: Any, **kwargs: Any) -> Any:
|
|
33
|
+
prod = kwargs.pop("prod", False)
|
|
34
|
+
staging = kwargs.pop("staging", False)
|
|
35
|
+
settings = get_settings(use_prod=prod, use_staging=staging)
|
|
36
|
+
|
|
37
|
+
if is_editable_install() and not (prod or staging):
|
|
38
|
+
assert settings.environment == Environment.development
|
|
39
|
+
print_editable_install_warning(settings)
|
|
40
|
+
|
|
41
|
+
return f(*args, settings=settings, **kwargs)
|
|
42
|
+
|
|
43
|
+
return decorated_function
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def use_prod_settings(f: Callable[..., Any]) -> Callable[..., Any]:
|
|
47
|
+
@wraps(f)
|
|
48
|
+
def decorated_function(*args: Any, **kwargs: Any) -> Any:
|
|
49
|
+
settings = get_settings(use_prod=True)
|
|
50
|
+
|
|
51
|
+
if is_editable_install():
|
|
52
|
+
print_editable_install_forced_prod_warning(settings)
|
|
53
|
+
|
|
54
|
+
return f(*args, settings=settings, **kwargs)
|
|
55
|
+
|
|
56
|
+
return decorated_function
|