indent 0.0.8__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.

Potentially problematic release.


This version of indent might be problematic. Click here for more details.

Files changed (56) hide show
  1. exponent/__init__.py +1 -0
  2. exponent/cli.py +112 -0
  3. exponent/commands/cloud_commands.py +85 -0
  4. exponent/commands/common.py +434 -0
  5. exponent/commands/config_commands.py +581 -0
  6. exponent/commands/github_app_commands.py +211 -0
  7. exponent/commands/listen_commands.py +96 -0
  8. exponent/commands/run_commands.py +208 -0
  9. exponent/commands/settings.py +56 -0
  10. exponent/commands/shell_commands.py +2840 -0
  11. exponent/commands/theme.py +246 -0
  12. exponent/commands/types.py +111 -0
  13. exponent/commands/upgrade.py +29 -0
  14. exponent/commands/utils.py +236 -0
  15. exponent/core/config.py +180 -0
  16. exponent/core/graphql/__init__.py +0 -0
  17. exponent/core/graphql/client.py +59 -0
  18. exponent/core/graphql/cloud_config_queries.py +77 -0
  19. exponent/core/graphql/get_chats_query.py +47 -0
  20. exponent/core/graphql/github_config_queries.py +56 -0
  21. exponent/core/graphql/mutations.py +75 -0
  22. exponent/core/graphql/queries.py +110 -0
  23. exponent/core/graphql/subscriptions.py +452 -0
  24. exponent/core/remote_execution/checkpoints.py +212 -0
  25. exponent/core/remote_execution/cli_rpc_types.py +214 -0
  26. exponent/core/remote_execution/client.py +545 -0
  27. exponent/core/remote_execution/code_execution.py +58 -0
  28. exponent/core/remote_execution/command_execution.py +105 -0
  29. exponent/core/remote_execution/error_info.py +45 -0
  30. exponent/core/remote_execution/exceptions.py +10 -0
  31. exponent/core/remote_execution/file_write.py +410 -0
  32. exponent/core/remote_execution/files.py +415 -0
  33. exponent/core/remote_execution/git.py +268 -0
  34. exponent/core/remote_execution/languages/python_execution.py +239 -0
  35. exponent/core/remote_execution/languages/shell_streaming.py +221 -0
  36. exponent/core/remote_execution/languages/types.py +20 -0
  37. exponent/core/remote_execution/session.py +128 -0
  38. exponent/core/remote_execution/system_context.py +54 -0
  39. exponent/core/remote_execution/tool_execution.py +289 -0
  40. exponent/core/remote_execution/truncation.py +284 -0
  41. exponent/core/remote_execution/types.py +670 -0
  42. exponent/core/remote_execution/utils.py +600 -0
  43. exponent/core/types/__init__.py +0 -0
  44. exponent/core/types/command_data.py +206 -0
  45. exponent/core/types/event_types.py +89 -0
  46. exponent/core/types/generated/__init__.py +0 -0
  47. exponent/core/types/generated/strategy_info.py +225 -0
  48. exponent/migration-docs/login.md +112 -0
  49. exponent/py.typed +4 -0
  50. exponent/utils/__init__.py +0 -0
  51. exponent/utils/colors.py +92 -0
  52. exponent/utils/version.py +289 -0
  53. indent-0.0.8.dist-info/METADATA +36 -0
  54. indent-0.0.8.dist-info/RECORD +56 -0
  55. indent-0.0.8.dist-info/WHEEL +4 -0
  56. indent-0.0.8.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,670 @@
1
+ import datetime
2
+ import json
3
+ from enum import Enum
4
+ from functools import cached_property
5
+ from os import PathLike
6
+ from pathlib import Path, PurePath
7
+ from typing import (
8
+ Annotated,
9
+ Any,
10
+ ClassVar,
11
+ Generic,
12
+ Literal,
13
+ TypeVar,
14
+ Union,
15
+ )
16
+
17
+ from anyio import Path as AsyncPath
18
+ from pydantic import BaseModel, Field
19
+
20
+ from exponent.core.remote_execution.error_info import SerializableErrorInfo
21
+ from exponent.core.types.command_data import (
22
+ CommandDataType,
23
+ FileWriteStrategyName,
24
+ )
25
+
26
+ type FilePath = str | PathLike[str]
27
+
28
+
29
+ # DEPRECATED, only around for gql compatibility
30
+ class UseToolsMode(str, Enum):
31
+ read_only = "read_only"
32
+ read_write = "read_write"
33
+ disabled = "disabled"
34
+
35
+
36
+ class CreateChatResponse(BaseModel):
37
+ chat_uuid: str
38
+
39
+
40
+ class RunWorkflowRequest(BaseModel):
41
+ chat_uuid: str
42
+ workflow_id: str
43
+
44
+
45
+ class ExecutionEndResponse(BaseModel):
46
+ execution_ended: bool
47
+
48
+
49
+ class SignalType(str, Enum):
50
+ disconnect = "disconnect"
51
+
52
+ def __str__(self) -> str:
53
+ return self.value
54
+
55
+
56
+ class GitInfo(BaseModel):
57
+ branch: str
58
+ remote: str | None
59
+
60
+
61
+ class PythonEnvInfo(BaseModel):
62
+ interpreter_path: str | None
63
+ interpreter_version: str | None
64
+ name: str | None = "exponent"
65
+ provider: Literal["venv", "pyenv", "pipenv", "conda"] | None = "pyenv"
66
+
67
+
68
+ class SystemInfo(BaseModel):
69
+ name: str
70
+ cwd: str
71
+ os: str
72
+ shell: str
73
+ git: GitInfo | None
74
+ python_env: PythonEnvInfo | None
75
+
76
+
77
+ class HeartbeatInfo(BaseModel):
78
+ exponent_version: str | None = None
79
+ editable_installation: bool = False
80
+ system_info: SystemInfo | None
81
+ timestamp: datetime.datetime = Field(
82
+ default_factory=lambda: datetime.datetime.now(datetime.UTC)
83
+ )
84
+ timestamp_received: datetime.datetime | None = None
85
+
86
+
87
+ class RemoteFile(BaseModel):
88
+ file_path: str
89
+ working_directory: str = "."
90
+
91
+ @cached_property
92
+ def pure_path(self) -> PurePath:
93
+ return PurePath(self.working_directory, self.file_path)
94
+
95
+ @cached_property
96
+ def path(self) -> Path:
97
+ return Path(self.working_directory, self.file_path)
98
+
99
+ @cached_property
100
+ def name(self) -> str:
101
+ return self.pure_path.name
102
+
103
+ @cached_property
104
+ def absolute_path(self) -> str:
105
+ return self.path.absolute().as_posix()
106
+
107
+ async def resolve(self, client_working_directory: str) -> str:
108
+ working_directory = AsyncPath(self.working_directory, self.file_path)
109
+
110
+ if not working_directory.is_absolute():
111
+ working_directory = AsyncPath(client_working_directory, working_directory)
112
+
113
+ return str(await working_directory.resolve())
114
+
115
+ def __eq__(self, other: object) -> bool:
116
+ if not isinstance(other, RemoteFile):
117
+ return False
118
+
119
+ return self.path.name == other.path.name
120
+
121
+ def __lt__(self, other: "RemoteFile") -> bool:
122
+ # Prefer shorter paths
123
+ if (cmp := self._cmp_path_len(other)) is not None:
124
+ return cmp
125
+
126
+ # Prefer paths sorted by parent directory
127
+ if (cmp := self._cmp_path_str(other)) is not None:
128
+ return cmp
129
+
130
+ # Prefer paths with alphabetical first character
131
+ return self._cmp_first_char(other)
132
+
133
+ def __hash__(self) -> int:
134
+ return hash(self.absolute_path)
135
+
136
+ def _cmp_first_char(self, other: "RemoteFile") -> bool:
137
+ return self._cmp_str(self.path.name, other.path.name)
138
+
139
+ def _cmp_path_len(self, other: "RemoteFile") -> bool | None:
140
+ self_parts = self.path.absolute().parent.parts
141
+ other_parts = other.path.absolute().parent.parts
142
+
143
+ if len(self_parts) == len(other_parts):
144
+ return None
145
+
146
+ return len(self_parts) < len(other_parts)
147
+
148
+ def _cmp_path_str(self, other: "RemoteFile") -> bool | None:
149
+ self_parts = self.path.absolute().parent.parts
150
+ other_parts = other.path.absolute().parent.parts
151
+
152
+ if self_parts == other_parts:
153
+ return None
154
+
155
+ for a, b in zip(self_parts, other_parts):
156
+ if a != b:
157
+ return self._cmp_str(a, b)
158
+
159
+ return False
160
+
161
+ @staticmethod
162
+ def _cmp_str(s1: str, s2: str) -> bool:
163
+ if s1[:1].isalpha() == s2[:1].isalpha():
164
+ return s1 < s2
165
+
166
+ return s1[:1].isalpha()
167
+
168
+
169
+ class URLAttachment(BaseModel):
170
+ attachment_type: Literal["url"] = "url"
171
+ url: str
172
+ content: str
173
+
174
+
175
+ class FileAttachment(BaseModel):
176
+ attachment_type: Literal["file"] = "file"
177
+ file: RemoteFile
178
+ content: str
179
+ truncated: bool = False
180
+
181
+
182
+ class TableSchemaAttachment(BaseModel):
183
+ attachment_type: Literal["table_schema"] = "table_schema"
184
+ table_name: str
185
+ table_schema: dict[str, Any]
186
+
187
+
188
+ class PromptAttachment(BaseModel):
189
+ attachment_type: Literal["prompt"] = "prompt"
190
+ prompt_name: str
191
+ prompt_content: str
192
+
193
+
194
+ MessageAttachment = Annotated[
195
+ FileAttachment | URLAttachment | TableSchemaAttachment | PromptAttachment,
196
+ Field(discriminator="attachment_type"),
197
+ ]
198
+
199
+
200
+ Direction = Literal[
201
+ "request",
202
+ "response",
203
+ ]
204
+
205
+ Namespace = Literal[
206
+ "code_execution",
207
+ "streaming_code_execution",
208
+ "streaming_code_execution_chunk",
209
+ "file_write",
210
+ "command",
211
+ "list_files",
212
+ "get_file_attachment",
213
+ "get_file_attachments",
214
+ "get_matching_files",
215
+ "system_context",
216
+ "get_all_tracked_files",
217
+ "halt",
218
+ "switch_cli_chat",
219
+ "error",
220
+ "create_checkpoint",
221
+ "rollback_to_checkpoint",
222
+ ]
223
+
224
+ ErrorType = Literal["unknown_request_type", "request_error"]
225
+
226
+ SupportedLanguage = Literal[
227
+ "python",
228
+ "shell",
229
+ ]
230
+
231
+ SUPPORTED_LANGUAGES: list[SupportedLanguage] = ["python", "shell"]
232
+
233
+
234
+ class RemoteExecutionMessageData(BaseModel):
235
+ namespace: Namespace
236
+ direction: Direction
237
+ message_data: str
238
+
239
+ def message_type(self) -> str:
240
+ return f"{self.namespace}.{self.direction}"
241
+
242
+
243
+ class RemoteExecutionMessage(BaseModel):
244
+ direction: ClassVar[Direction]
245
+ namespace: ClassVar[Namespace]
246
+ correlation_id: str
247
+
248
+ @classmethod
249
+ def message_type(cls) -> str:
250
+ return f"{cls.namespace}.{cls.direction}"
251
+
252
+ @property
253
+ def result_key(self) -> str:
254
+ return f"{self.namespace}:{self.correlation_id}"
255
+
256
+
257
+ ### Response Types
258
+
259
+
260
+ class RemoteExecutionResponseData(RemoteExecutionMessageData):
261
+ pass
262
+
263
+
264
+ class RemoteExecutionResponse(RemoteExecutionMessage):
265
+ direction: ClassVar[Direction] = "response"
266
+
267
+
268
+ ResponseT = TypeVar("ResponseT", bound=RemoteExecutionResponse)
269
+
270
+
271
+ class StreamingCodeExecutionResponseChunk(RemoteExecutionResponse):
272
+ namespace: ClassVar[Namespace] = "streaming_code_execution_chunk"
273
+
274
+ content: str
275
+ truncated: bool = False
276
+
277
+ def add(
278
+ self, new_chunk: "StreamingCodeExecutionResponseChunk"
279
+ ) -> "StreamingCodeExecutionResponseChunk":
280
+ """Aggregates content of this and a new chunk."""
281
+ assert self.correlation_id == new_chunk.correlation_id
282
+ return StreamingCodeExecutionResponseChunk(
283
+ correlation_id=self.correlation_id, content=self.content + new_chunk.content
284
+ )
285
+
286
+
287
+ class StreamingCodeExecutionResponse(RemoteExecutionResponse):
288
+ namespace: ClassVar[Namespace] = "streaming_code_execution"
289
+
290
+ content: str
291
+ truncated: bool = False
292
+
293
+ # Only present for shell code execution
294
+ cancelled_for_timeout: bool = False
295
+ exit_code: int | None = None
296
+ halted: bool = False
297
+
298
+
299
+ class CodeExecutionResponse(RemoteExecutionResponse):
300
+ namespace: ClassVar[Namespace] = "code_execution"
301
+
302
+ content: str
303
+
304
+ # Only present for shell code execution
305
+ cancelled_for_timeout: bool = False
306
+ exit_code: int | None = None
307
+ halted: bool = False
308
+ truncated: bool = False
309
+
310
+
311
+ class FileWriteResponse(RemoteExecutionResponse):
312
+ namespace: ClassVar[Namespace] = "file_write"
313
+
314
+ content: str
315
+
316
+
317
+ class ListFilesResponse(RemoteExecutionResponse):
318
+ namespace: ClassVar[Namespace] = "list_files"
319
+
320
+ files: list[RemoteFile]
321
+
322
+
323
+ class GetFileAttachmentResponse(RemoteExecutionResponse, FileAttachment):
324
+ namespace: ClassVar[Namespace] = "get_file_attachment"
325
+
326
+ exists: bool = Field(default=True)
327
+
328
+
329
+ class GetFileAttachmentsResponse(RemoteExecutionResponse):
330
+ namespace: ClassVar[Namespace] = "get_file_attachments"
331
+
332
+ file_attachments: list[FileAttachment]
333
+
334
+
335
+ class GetMatchingFilesResponse(RemoteExecutionResponse):
336
+ namespace: ClassVar[Namespace] = "get_matching_files"
337
+
338
+ files: list[RemoteFile]
339
+
340
+
341
+ class GetAllTrackedFilesResponse(RemoteExecutionResponse):
342
+ namespace: ClassVar[Namespace] = "get_all_tracked_files"
343
+
344
+ files: list[RemoteFile]
345
+
346
+
347
+ class SystemContextResponse(RemoteExecutionResponse):
348
+ namespace: ClassVar[Namespace] = "system_context"
349
+
350
+ exponent_txt: str | None
351
+ system_info: SystemInfo | None
352
+
353
+
354
+ class HaltResponse(RemoteExecutionResponse):
355
+ namespace: ClassVar[Namespace] = "halt"
356
+
357
+
358
+ class SwitchCLIChatResponse(RemoteExecutionResponse):
359
+ namespace: ClassVar[Namespace] = "switch_cli_chat"
360
+
361
+
362
+ class ErrorResponse(RemoteExecutionResponse):
363
+ namespace: ClassVar[Namespace] = "error"
364
+ # The namespace of the request that caused the error.
365
+ # Not a Namespace to avoid deserialization errors
366
+ request_namespace: str
367
+ error_type: ErrorType
368
+ error_info: SerializableErrorInfo | None = None
369
+
370
+ @property
371
+ def result_key(self) -> str:
372
+ # Match the key of the request that caused the error
373
+ return f"{self.request_namespace}:{self.correlation_id}"
374
+
375
+
376
+ class GitFileChange(BaseModel):
377
+ path: str
378
+ lines_added: int
379
+ lines_deleted: int
380
+
381
+
382
+ class GitDiff(BaseModel):
383
+ files: list[GitFileChange]
384
+ truncated: bool = False # True if there were more files than the limit
385
+ total_files: int # Total number of files changed, even if truncated
386
+
387
+
388
+ class GitCommitMetadata(BaseModel):
389
+ author_name: str
390
+ author_email: str
391
+ author_date: str
392
+ commit_date: str
393
+ commit_message: str
394
+ branch: str
395
+
396
+
397
+ class CreateCheckpointResponse(RemoteExecutionResponse):
398
+ namespace: ClassVar[Namespace] = "create_checkpoint"
399
+
400
+ correlation_id: str
401
+ head_commit_hash: str
402
+ head_commit_metadata: GitCommitMetadata
403
+ uncommitted_changes_commit_hash: str | None = None
404
+ diff_versus_last_checkpoint: GitDiff | None = None
405
+
406
+ debug_info: dict[str, Any] | None = None
407
+
408
+
409
+ class RollbackToCheckpointResponse(RemoteExecutionResponse):
410
+ namespace: ClassVar[Namespace] = "rollback_to_checkpoint"
411
+
412
+ debug_info: dict[str, Any] | None = None
413
+
414
+
415
+ ### Request Types
416
+
417
+
418
+ class RemoteExecutionRequestData(RemoteExecutionMessageData):
419
+ pass
420
+
421
+
422
+ class RemoteExecutionRequest(RemoteExecutionMessage, Generic[ResponseT]):
423
+ direction: ClassVar[Direction] = "request"
424
+
425
+
426
+ class CodeExecutionRequest(RemoteExecutionRequest[CodeExecutionResponse]):
427
+ namespace: ClassVar[Namespace] = "code_execution"
428
+
429
+ language: SupportedLanguage
430
+ content: str
431
+ timeout: int
432
+
433
+
434
+ class StreamingCodeExecutionRequest(
435
+ RemoteExecutionRequest[
436
+ Union[StreamingCodeExecutionResponseChunk, StreamingCodeExecutionResponse]
437
+ ]
438
+ ):
439
+ namespace: ClassVar[Namespace] = "streaming_code_execution"
440
+
441
+ language: SupportedLanguage
442
+ content: str
443
+ timeout: int
444
+
445
+
446
+ class HaltRequest(RemoteExecutionRequest[HaltResponse]):
447
+ namespace: ClassVar[Namespace] = "halt"
448
+
449
+
450
+ class SwitchCLIChatRequest(RemoteExecutionRequest[SwitchCLIChatResponse]):
451
+ namespace: ClassVar[Namespace] = "switch_cli_chat"
452
+ new_chat_uuid: str
453
+
454
+
455
+ class FileWriteRequest(RemoteExecutionRequest[FileWriteResponse]):
456
+ namespace: ClassVar[Namespace] = "file_write"
457
+
458
+ file_path: str
459
+ # Note we don't use SupportedLanguage here because we don't
460
+ # require language-specific execution support for file writes
461
+ language: str
462
+ write_strategy: FileWriteStrategyName
463
+ content: str
464
+
465
+
466
+ class ListFilesRequest(RemoteExecutionRequest[ListFilesResponse]):
467
+ namespace: ClassVar[Namespace] = "list_files"
468
+
469
+ directory: str
470
+
471
+
472
+ class GetFileAttachmentRequest(RemoteExecutionRequest[GetFileAttachmentResponse]):
473
+ namespace: ClassVar[Namespace] = "get_file_attachment"
474
+
475
+ file: RemoteFile
476
+
477
+
478
+ class GetFileAttachmentsRequest(RemoteExecutionRequest[GetFileAttachmentsResponse]):
479
+ namespace: ClassVar[Namespace] = "get_file_attachments"
480
+
481
+ files: list[RemoteFile]
482
+
483
+
484
+ class GetMatchingFilesRequest(RemoteExecutionRequest[GetMatchingFilesResponse]):
485
+ namespace: ClassVar[Namespace] = "get_matching_files"
486
+
487
+ search_term: str
488
+
489
+
490
+ class GetAllTrackedFilesRequest(RemoteExecutionRequest[GetAllTrackedFilesResponse]):
491
+ namespace: ClassVar[Namespace] = "get_all_tracked_files"
492
+
493
+
494
+ class SystemContextRequest(RemoteExecutionRequest[SystemContextResponse]):
495
+ namespace: ClassVar[Namespace] = "system_context"
496
+
497
+
498
+ class CreateCheckpointRequest(RemoteExecutionRequest[CreateCheckpointResponse]):
499
+ namespace: ClassVar[Namespace] = "create_checkpoint"
500
+
501
+ last_checkpoint_head_commit: str | None = None
502
+ last_checkpoint_uncommitted_changes_commit: str | None = None
503
+
504
+
505
+ class RollbackToCheckpointRequest(RemoteExecutionRequest[RollbackToCheckpointResponse]):
506
+ namespace: ClassVar[Namespace] = "rollback_to_checkpoint"
507
+
508
+ head_commit: str
509
+ uncommitted_changes_commit: str | None
510
+
511
+
512
+ ### Commands
513
+
514
+
515
+ ### Command Response Types
516
+
517
+
518
+ class CommandResponse(RemoteExecutionResponse):
519
+ namespace: ClassVar[Namespace] = "command"
520
+
521
+ content: str
522
+ content_json: dict[str, Any] = Field(default_factory=dict)
523
+ subcommand: str = "unknown"
524
+ truncated: bool = False
525
+
526
+
527
+ ### Command Request Types
528
+
529
+
530
+ class CommandRequest(RemoteExecutionRequest[CommandResponse]):
531
+ namespace: ClassVar[Namespace] = "command"
532
+
533
+ data: CommandDataType = Field(..., discriminator="type")
534
+
535
+
536
+ RemoteExecutionRequestType = Union[
537
+ CodeExecutionRequest,
538
+ FileWriteRequest,
539
+ ListFilesRequest,
540
+ GetFileAttachmentRequest,
541
+ GetFileAttachmentsRequest,
542
+ GetMatchingFilesRequest,
543
+ SystemContextRequest,
544
+ GetAllTrackedFilesRequest,
545
+ CommandRequest,
546
+ HaltRequest,
547
+ StreamingCodeExecutionRequest,
548
+ SwitchCLIChatRequest,
549
+ CreateCheckpointRequest,
550
+ RollbackToCheckpointRequest,
551
+ ]
552
+
553
+ RemoteExecutionResponseType = Union[
554
+ CodeExecutionResponse,
555
+ StreamingCodeExecutionResponseChunk,
556
+ StreamingCodeExecutionResponse,
557
+ FileWriteResponse,
558
+ ListFilesResponse,
559
+ GetFileAttachmentResponse,
560
+ GetFileAttachmentsResponse,
561
+ GetMatchingFilesResponse,
562
+ GetAllTrackedFilesResponse,
563
+ SystemContextResponse,
564
+ CommandResponse,
565
+ HaltResponse,
566
+ SwitchCLIChatResponse,
567
+ ErrorResponse,
568
+ CreateCheckpointResponse,
569
+ RollbackToCheckpointResponse,
570
+ ]
571
+
572
+ StreamingResponseType = Union[
573
+ StreamingCodeExecutionResponseChunk,
574
+ StreamingCodeExecutionResponse,
575
+ ErrorResponse,
576
+ ]
577
+
578
+ STREAMING_NAMESPACES = [
579
+ "streaming_code_execution",
580
+ "streaming_code_execution_chunk",
581
+ ]
582
+
583
+
584
+ class ChatMode(str, Enum):
585
+ DEFAULT = "DEFAULT" # chat just with model
586
+ CLI = "CLI"
587
+ CLOUD = "CLOUD" # chat with cloud devbox
588
+ CODEBASE = "CODEBASE" # chat with codebase
589
+ PYTHON_INTERPRETER = "PYTHON_INTERPRETER"
590
+ DATABASE = "DATABASE" # chat with database connection
591
+ WORKFLOW = "WORKFLOW"
592
+
593
+
594
+ DEVBOX_CHAT_MODES = [
595
+ ChatMode.CLOUD,
596
+ ChatMode.CODEBASE,
597
+ ]
598
+
599
+
600
+ class ChatSource(str, Enum):
601
+ CLI_SHELL = "CLI_SHELL"
602
+ CLI_RUN = "CLI_RUN"
603
+ WEB = "WEB"
604
+ DESKTOP_APP = "DESKTOP_APP"
605
+ VSCODE_EXTENSION = "VSCODE_EXTENSION"
606
+
607
+
608
+ class CLIConnectedState(BaseModel):
609
+ chat_uuid: str
610
+ connected: bool
611
+ last_connected_at: datetime.datetime | None
612
+ connection_latency_ms: int | None
613
+ system_info: SystemInfo | None
614
+ exponent_version: str | None = None
615
+ editable_installation: bool = False
616
+
617
+
618
+ class DevboxConnectedState(str, Enum):
619
+ # TODO: Only needed if we create devbox async
620
+ INITIALIZED = "INITIALIZED"
621
+ # The chat has been initialized, but the devbox is still loading
622
+ DEVBOX_LOADING = "DEVBOX_LOADING"
623
+ # Devbox is ready to use but not tied to a chat
624
+ DEVBOX_READY = "DEVBOX_READY"
625
+ # CLI is connected and running on devbox
626
+ CONNECTED = "CONNECTED"
627
+ # CLI has disconnected
628
+ # TODO: what condition?
629
+ CLI_DISCONNECTED = "CLI_DISCONNECTED"
630
+ # CLI has an error, devbox is running
631
+ CLI_ERROR = "CLI_ERROR"
632
+ # Devbox has an error
633
+ DEVBOX_ERROR = "DEVBOX_ERROR"
634
+ # Devbox is going to idle
635
+ GOING_TO_IDLE = "GOING_TO_IDLE"
636
+ # Devbox is idle
637
+ IDLE = "IDLE"
638
+ # Devbox is going to idle
639
+ RESUMING_FROM_IDLE = "RESUMING_FROM_IDLE"
640
+ # Devbox_shutdown
641
+ # TODO: In theory our terminal state, do we want to name something different?
642
+ DEVBOX_SHUTDOWN = "DEVBOX_SHUTDOWN"
643
+
644
+
645
+ class CloudConnectedState(BaseModel):
646
+ chat_uuid: str
647
+ connected_state: DevboxConnectedState
648
+ last_connected_at: datetime.datetime | None
649
+ system_info: SystemInfo | None
650
+
651
+
652
+ class CLIErrorLog(BaseModel):
653
+ event_data: str
654
+ timestamp: datetime.datetime = datetime.datetime.now()
655
+ attachment_data: str | None = None
656
+ version: str | None = None
657
+ chat_uuid: str | None = None
658
+
659
+ @property
660
+ def loaded_event_data(self) -> Any | None:
661
+ try:
662
+ return json.loads(self.event_data)
663
+ except json.JSONDecodeError:
664
+ return None
665
+
666
+ @property
667
+ def attachment_bytes(self) -> bytes | None:
668
+ if not self.attachment_data:
669
+ return None
670
+ return self.attachment_data.encode()