indent 0.1.19__tar.gz → 0.1.24__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.
Files changed (55) hide show
  1. {indent-0.1.19 → indent-0.1.24}/.gitignore +4 -2
  2. {indent-0.1.19 → indent-0.1.24}/PKG-INFO +2 -1
  3. {indent-0.1.19 → indent-0.1.24}/exponent/__init__.py +2 -2
  4. {indent-0.1.19 → indent-0.1.24}/exponent/commands/cloud_commands.py +2 -84
  5. {indent-0.1.19 → indent-0.1.24}/exponent/commands/config_commands.py +0 -87
  6. {indent-0.1.19 → indent-0.1.24}/exponent/commands/run_commands.py +1 -1
  7. {indent-0.1.19 → indent-0.1.24}/exponent/core/graphql/mutations.py +2 -31
  8. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/cli_rpc_types.py +141 -13
  9. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/client.py +265 -64
  10. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/code_execution.py +26 -7
  11. indent-0.1.24/exponent/core/remote_execution/default_env.py +31 -0
  12. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/languages/shell_streaming.py +7 -5
  13. indent-0.1.24/exponent/core/remote_execution/port_utils.py +73 -0
  14. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/system_context.py +2 -0
  15. indent-0.1.24/exponent/core/remote_execution/terminal_session.py +428 -0
  16. indent-0.1.24/exponent/core/remote_execution/terminal_types.py +29 -0
  17. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/tool_execution.py +11 -9
  18. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/truncation.py +1 -3
  19. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/types.py +39 -1
  20. {indent-0.1.19 → indent-0.1.24}/pyproject.toml +1 -0
  21. indent-0.1.19/exponent/core/graphql/github_config_queries.py +0 -56
  22. {indent-0.1.19 → indent-0.1.24}/exponent/cli.py +0 -0
  23. {indent-0.1.19 → indent-0.1.24}/exponent/commands/common.py +0 -0
  24. {indent-0.1.19 → indent-0.1.24}/exponent/commands/settings.py +0 -0
  25. {indent-0.1.19 → indent-0.1.24}/exponent/commands/types.py +0 -0
  26. {indent-0.1.19 → indent-0.1.24}/exponent/commands/upgrade.py +0 -0
  27. {indent-0.1.19 → indent-0.1.24}/exponent/commands/utils.py +0 -0
  28. {indent-0.1.19 → indent-0.1.24}/exponent/core/config.py +0 -0
  29. {indent-0.1.19 → indent-0.1.24}/exponent/core/graphql/__init__.py +0 -0
  30. {indent-0.1.19 → indent-0.1.24}/exponent/core/graphql/client.py +0 -0
  31. {indent-0.1.19 → indent-0.1.24}/exponent/core/graphql/get_chats_query.py +0 -0
  32. {indent-0.1.19 → indent-0.1.24}/exponent/core/graphql/queries.py +0 -0
  33. {indent-0.1.19 → indent-0.1.24}/exponent/core/graphql/subscriptions.py +0 -0
  34. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/checkpoints.py +0 -0
  35. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/error_info.py +0 -0
  36. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/exceptions.py +0 -0
  37. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/file_write.py +0 -0
  38. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/files.py +0 -0
  39. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/git.py +0 -0
  40. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/http_fetch.py +0 -0
  41. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/languages/python_execution.py +0 -0
  42. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/languages/types.py +0 -0
  43. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/session.py +0 -0
  44. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/tool_type_utils.py +0 -0
  45. {indent-0.1.19 → indent-0.1.24}/exponent/core/remote_execution/utils.py +0 -0
  46. {indent-0.1.19 → indent-0.1.24}/exponent/core/types/__init__.py +0 -0
  47. {indent-0.1.19 → indent-0.1.24}/exponent/core/types/command_data.py +0 -0
  48. {indent-0.1.19 → indent-0.1.24}/exponent/core/types/event_types.py +0 -0
  49. {indent-0.1.19 → indent-0.1.24}/exponent/core/types/generated/__init__.py +0 -0
  50. {indent-0.1.19 → indent-0.1.24}/exponent/core/types/generated/strategy_info.py +0 -0
  51. {indent-0.1.19 → indent-0.1.24}/exponent/migration-docs/login.md +0 -0
  52. {indent-0.1.19 → indent-0.1.24}/exponent/py.typed +0 -0
  53. {indent-0.1.19 → indent-0.1.24}/exponent/utils/__init__.py +0 -0
  54. {indent-0.1.19 → indent-0.1.24}/exponent/utils/colors.py +0 -0
  55. {indent-0.1.19 → indent-0.1.24}/exponent/utils/version.py +0 -0
@@ -15,6 +15,7 @@ workflow_input.json
15
15
  CLAUDE.local.md
16
16
  indent.local.md
17
17
  plan.md
18
+ AGENTS.md
18
19
 
19
20
  # C extensions
20
21
  *.so
@@ -164,8 +165,9 @@ dmypy.json
164
165
  # Aider
165
166
  .aider*
166
167
 
167
- # Debug logs
168
- debug/
168
+
169
+ # Local test scripts
170
+ local_test_scripts/
169
171
 
170
172
  # Benchmarks
171
173
  .benchy.*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: indent
3
- Version: 0.1.19
3
+ Version: 0.1.24
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
@@ -22,6 +22,7 @@ Requires-Dist: msgspec>=0.19.0
22
22
  Requires-Dist: packaging~=24.1
23
23
  Requires-Dist: pip<26,>=25.0.1
24
24
  Requires-Dist: prompt-toolkit<4,>=3.0.36
25
+ Requires-Dist: psutil<7,>=5.9.0
25
26
  Requires-Dist: pydantic-ai==0.0.30
26
27
  Requires-Dist: pydantic-settings<3,>=2.2.1
27
28
  Requires-Dist: pydantic[email]<3,>=2.6.4
@@ -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.19'
32
- __version_tuple__ = version_tuple = (0, 1, 19)
31
+ __version__ = version = '0.1.24'
32
+ __version_tuple__ = version_tuple = (0, 1, 24)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -23,7 +23,6 @@ from exponent.core.graphql.client import GraphQLClient
23
23
  from exponent.core.graphql.mutations import (
24
24
  CREATE_CLOUD_CHAT_FROM_REPOSITORY_MUTATION,
25
25
  ENABLE_CLOUD_REPOSITORY_MUTATION,
26
- INCREMENTAL_BUILD_CLOUD_REPOSITORY_MUTATION,
27
26
  REBUILD_CLOUD_REPOSITORY_MUTATION,
28
27
  START_CHAT_TURN_MUTATION,
29
28
  )
@@ -62,32 +61,6 @@ async def enable_cloud_repository(
62
61
  return cast(dict[str, Any], result["enableCloudRepository"])
63
62
 
64
63
 
65
- async def incremental_build_cloud_repository(
66
- api_key: str,
67
- base_api_url: str,
68
- base_ws_url: str,
69
- org_name: str,
70
- repo_name: str,
71
- ) -> dict[str, Any]:
72
- graphql_client = GraphQLClient(
73
- api_key=api_key, base_api_url=base_api_url, base_ws_url=base_ws_url
74
- )
75
-
76
- variables = {
77
- "orgName": org_name,
78
- "repoName": repo_name,
79
- }
80
-
81
- result = await graphql_client.execute(
82
- INCREMENTAL_BUILD_CLOUD_REPOSITORY_MUTATION,
83
- variables,
84
- "IncrementalBuildCloudRepository",
85
- timeout=120,
86
- )
87
-
88
- return cast(dict[str, Any], result["incrementalBuildCloudRepository"])
89
-
90
-
91
64
  async def rebuild_cloud_repository(
92
65
  api_key: str,
93
66
  base_api_url: str,
@@ -138,6 +111,7 @@ async def create_cloud_chat_from_repository(
138
111
  base_api_url: str,
139
112
  base_ws_url: str,
140
113
  repository_id: str,
114
+ provider: str | None = None,
141
115
  ) -> dict[str, Any]:
142
116
  graphql_client = GraphQLClient(
143
117
  api_key=api_key, base_api_url=base_api_url, base_ws_url=base_ws_url
@@ -145,6 +119,7 @@ async def create_cloud_chat_from_repository(
145
119
 
146
120
  variables = {
147
121
  "repositoryId": repository_id,
122
+ "provider": provider,
148
123
  }
149
124
 
150
125
  result = await graphql_client.execute(
@@ -243,63 +218,6 @@ def enable_repo(
243
218
  sys.exit(1)
244
219
 
245
220
 
246
- @cloud_cli.command(hidden=True)
247
- @click.option(
248
- "--org-name",
249
- help="GitHub organization name",
250
- required=True,
251
- )
252
- @click.option(
253
- "--repo-name",
254
- help="GitHub repository name",
255
- required=True,
256
- )
257
- @use_settings
258
- def incremental_build(
259
- settings: Settings,
260
- org_name: str,
261
- repo_name: str,
262
- ) -> None:
263
- """Test utility for incremental build of cloud repository."""
264
- check_exponent_version_and_upgrade(settings)
265
-
266
- if not settings.api_key:
267
- redirect_to_login(settings)
268
- return
269
-
270
- loop = asyncio.get_event_loop()
271
-
272
- api_key = settings.api_key
273
- base_api_url = settings.get_base_api_url()
274
- base_ws_url = settings.get_base_ws_url()
275
-
276
- try:
277
- result = loop.run_until_complete(
278
- incremental_build_cloud_repository(
279
- api_key, base_api_url, base_ws_url, org_name, repo_name
280
- )
281
- )
282
-
283
- if result["__typename"] == "ContainerImage":
284
- click.secho(
285
- f"✓ Successfully triggered incremental build for {org_name}/{repo_name}",
286
- fg="green",
287
- )
288
- click.echo(f" Build ref: {result.get('buildRef', 'N/A')}")
289
- click.echo(f" Created at: {result.get('createdAt', 'N/A')}")
290
- click.echo(f" Updated at: {result.get('updatedAt', 'N/A')}")
291
- else:
292
- click.secho(
293
- f"✗ Failed to trigger incremental build: {result.get('message', 'Unknown error')}",
294
- fg="red",
295
- )
296
- click.echo(f" Error type: {result['__typename']}")
297
-
298
- except Exception as e:
299
- click.secho(f"✗ Error triggering incremental build: {e!s}", fg="red")
300
- sys.exit(1)
301
-
302
-
303
221
  @cloud_cli.command(hidden=True)
304
222
  @click.option(
305
223
  "--org-name",
@@ -14,11 +14,6 @@ from exponent.commands.types import exponent_cli_group
14
14
  from exponent.core.config import Settings
15
15
  from exponent.core.graphql.client import GraphQLClient
16
16
  from exponent.core.graphql.get_chats_query import GET_CHATS_QUERY
17
- from exponent.core.graphql.github_config_queries import (
18
- CHECK_GITHUB_CONFIG_VALIDITY_QUERY,
19
- CREATE_GITHUB_CONFIG_MUTATION,
20
- REPOS_FOR_GITHUB_CONFIG_QUERY,
21
- )
22
17
  from exponent.core.graphql.subscriptions import AUTHENTICATED_USER_SUBSCRIPTION
23
18
  from exponent.utils.version import (
24
19
  get_installed_metadata,
@@ -56,74 +51,6 @@ def debug(
56
51
  click.echo(get_installed_metadata())
57
52
 
58
53
 
59
- @config_cli.command(hidden=True)
60
- @use_settings
61
- def check_github_config_validity(
62
- settings: Settings,
63
- ) -> None:
64
- if not settings.api_key:
65
- redirect_to_login(settings)
66
- return
67
-
68
- run_until_complete(
69
- check_github_config_validity_task(
70
- api_key=settings.api_key,
71
- base_api_url=settings.get_base_api_url(),
72
- base_ws_url=settings.get_base_ws_url(),
73
- )
74
- )
75
-
76
-
77
- async def check_github_config_validity_task(
78
- api_key: str,
79
- base_api_url: str,
80
- base_ws_url: str,
81
- ) -> None:
82
- graphql_client = GraphQLClient(api_key, base_api_url, base_ws_url)
83
- result = await graphql_client.execute(CHECK_GITHUB_CONFIG_VALIDITY_QUERY)
84
- click.echo(result)
85
-
86
-
87
- @config_cli.command(hidden=True)
88
- @use_settings
89
- def repos_for_github_config(
90
- settings: Settings,
91
- ) -> None:
92
- if not settings.api_key:
93
- redirect_to_login(settings)
94
- return
95
-
96
- run_until_complete(
97
- repos_for_github_config_task(
98
- api_key=settings.api_key,
99
- base_api_url=settings.get_base_api_url(),
100
- base_ws_url=settings.get_base_ws_url(),
101
- )
102
- )
103
-
104
-
105
- async def repos_for_github_config_task(
106
- api_key: str,
107
- base_api_url: str,
108
- base_ws_url: str,
109
- ) -> None:
110
- graphql_client = GraphQLClient(api_key, base_api_url, base_ws_url)
111
- try:
112
- click.echo("Sending request to fetch repos...")
113
- result = await graphql_client.execute(
114
- REPOS_FOR_GITHUB_CONFIG_QUERY, timeout=120
115
- ) # 120 seconds timeout
116
- click.echo("Request completed. Result:")
117
- click.echo(result)
118
- except Exception as e:
119
- click.echo(f"An error occurred while fetching repos: {e!s}")
120
- click.echo(f"Error type: {type(e).__name__}")
121
- # Add more detailed error information if available
122
- if hasattr(e, "response"):
123
- click.echo(f"Response status: {e.response.status_code}")
124
- click.echo(f"Response content: {e.response.text}")
125
-
126
-
127
54
  @config_cli.command(hidden=True)
128
55
  @click.option(
129
56
  "--set-git-warning-disabled",
@@ -389,20 +316,6 @@ async def get_authenticated_user_task(
389
316
  click.echo(it)
390
317
 
391
318
 
392
- async def create_github_config_task(
393
- api_key: str,
394
- base_api_url: str,
395
- base_ws_url: str,
396
- github_pat: str,
397
- ) -> None:
398
- graphql_client = GraphQLClient(api_key, base_api_url, base_ws_url)
399
- variables = {
400
- "githubPat": github_pat,
401
- }
402
- result = await graphql_client.execute(CREATE_GITHUB_CONFIG_MUTATION, variables)
403
- click.echo(result)
404
-
405
-
406
319
  @config_cli.command(hidden=True)
407
320
  @use_settings
408
321
  def refresh_key(settings: Settings) -> None:
@@ -97,7 +97,7 @@ 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:
100
+ if isinstance(timeout_seconds, int) and timeout_seconds <= 0:
101
101
  click.secho("Error: --timeout-seconds must be a positive integer", fg="red")
102
102
  sys.exit(1)
103
103
 
@@ -76,8 +76,8 @@ mutation CreateCloudChat($configId: String!) {
76
76
 
77
77
 
78
78
  CREATE_CLOUD_CHAT_FROM_REPOSITORY_MUTATION = """
79
- mutation CreateCloudChatFromRepository($repositoryId: String!) {
80
- createCloudChat(repositoryId: $repositoryId) {
79
+ mutation CreateCloudChatFromRepository($repositoryId: String!, $provider: String) {
80
+ createCloudChat(repositoryId: $repositoryId, provider: $provider) {
81
81
  __typename
82
82
  ...on Chat {
83
83
  chatUuid
@@ -131,35 +131,6 @@ mutation EnableCloudRepository($orgName: String!, $repoName: String!) {
131
131
  """
132
132
 
133
133
 
134
- INCREMENTAL_BUILD_CLOUD_REPOSITORY_MUTATION = """
135
- mutation IncrementalBuildCloudRepository($orgName: String!, $repoName: String!) {
136
- incrementalBuildCloudRepository(orgName: $orgName, repoName: $repoName) {
137
- __typename
138
- ...on ContainerImage {
139
- buildRef
140
- createdAt
141
- updatedAt
142
- }
143
- ...on UnauthenticatedError {
144
- message
145
- }
146
- ...on CloudConfigNotFoundError {
147
- message
148
- }
149
- ...on GithubConfigNotFoundError {
150
- message
151
- }
152
- ...on CloudSessionError {
153
- message
154
- }
155
- ...on Error {
156
- message
157
- }
158
- }
159
- }
160
- """
161
-
162
-
163
134
  REBUILD_CLOUD_REPOSITORY_MUTATION = """
164
135
  mutation RebuildCloudRepository($orgName: String!, $repoName: String!) {
165
136
  rebuildCloudRepository(orgName: $orgName, repoName: $repoName) {
@@ -48,6 +48,7 @@ class ToolResult(msgspec.Struct, tag_field="tool_name", omit_defaults=True):
48
48
 
49
49
  class ErrorToolResult(ToolResult, tag="error"):
50
50
  error_message: str
51
+ is_assistant_error: bool = False
51
52
 
52
53
 
53
54
  READ_TOOL_NAME = "read"
@@ -65,14 +66,28 @@ class FileMetadata(msgspec.Struct):
65
66
  file_mode: str
66
67
 
67
68
 
69
+ class ReadToolArtifactResult(ToolResult, tag=READ_TOOL_ARTIFACT_NAME):
70
+ s3_uri: str
71
+ file_path: str
72
+ media_type: str
73
+
74
+ def to_text(self) -> str:
75
+ return f"[Image artifact uploaded to {self.s3_uri}]"
76
+
77
+
68
78
  class ReadToolResult(ToolResult, tag=READ_TOOL_NAME):
69
- content: str
70
- num_lines: int
71
- start_line: int
72
- total_lines: int
79
+ content: str | None = None
80
+ num_lines: int | None = None
81
+ start_line: int | None = None
82
+ total_lines: int | None = None
73
83
  metadata: FileMetadata | None = None
84
+ artifact: ReadToolArtifactResult | None = None
74
85
 
75
86
  def to_text(self) -> str:
87
+ if self.artifact:
88
+ return self.artifact.to_text()
89
+ assert self.content is not None
90
+ assert self.start_line is not None
76
91
  lines = self.content.splitlines()
77
92
  lines = [
78
93
  f"{str(i).rjust(6)}→{line}"
@@ -81,15 +96,6 @@ class ReadToolResult(ToolResult, tag=READ_TOOL_NAME):
81
96
  return "\n".join(lines)
82
97
 
83
98
 
84
- class ReadToolArtifactResult(ToolResult, tag=READ_TOOL_ARTIFACT_NAME):
85
- s3_uri: str
86
- file_path: str
87
- media_type: str
88
-
89
- def to_text(self) -> str:
90
- return f"[Image artifact uploaded to {self.s3_uri}]"
91
-
92
-
93
99
  LIST_TOOL_NAME = "ls"
94
100
 
95
101
 
@@ -272,6 +278,10 @@ class TerminateResponse(msgspec.Struct, tag="terminate"):
272
278
  pass
273
279
 
274
280
 
281
+ class TimeoutResponse(msgspec.Struct, tag="timeout"):
282
+ pass
283
+
284
+
275
285
  class BatchToolExecutionResponse(msgspec.Struct, tag="batch_tool_execution"):
276
286
  tool_results: list[ToolResultType]
277
287
 
@@ -294,6 +304,77 @@ class GenerateUploadUrlResponse(msgspec.Struct, tag="generate_upload_url"):
294
304
  s3_uri: str
295
305
 
296
306
 
307
+ # Terminal session management
308
+ class StartTerminalRequest(msgspec.Struct, tag="start_terminal"):
309
+ """Start a new terminal session with PTY"""
310
+
311
+ session_id: str
312
+ command: list[str] | None = None # None = default shell, or specific command
313
+ cols: int = 80
314
+ rows: int = 24
315
+ env: dict[str, str] | None = None # Additional environment variables
316
+
317
+
318
+ class StartTerminalResponse(msgspec.Struct, tag="start_terminal"):
319
+ """Response after starting terminal"""
320
+
321
+ session_id: str
322
+ success: bool
323
+ error_message: str | None = None
324
+
325
+
326
+ # Terminal input (user typing)
327
+ class TerminalInputRequest(msgspec.Struct, tag="terminal_input"):
328
+ """Send user input to terminal"""
329
+
330
+ session_id: str
331
+ data: str # Raw input data from user
332
+
333
+
334
+ class TerminalInputResponse(msgspec.Struct, tag="terminal_input"):
335
+ """Acknowledge input received"""
336
+
337
+ session_id: str
338
+ success: bool
339
+
340
+
341
+ # Terminal resize
342
+ class TerminalResizeRequest(msgspec.Struct, tag="terminal_resize"):
343
+ """Resize terminal dimensions"""
344
+
345
+ session_id: str
346
+ cols: int
347
+ rows: int
348
+
349
+
350
+ class TerminalResizeResponse(msgspec.Struct, tag="terminal_resize"):
351
+ """Acknowledge resize"""
352
+
353
+ session_id: str
354
+ success: bool
355
+
356
+
357
+ # Terminal stop
358
+ class StopTerminalRequest(msgspec.Struct, tag="stop_terminal"):
359
+ """Stop a terminal session"""
360
+
361
+ session_id: str
362
+
363
+
364
+ class StopTerminalResponse(msgspec.Struct, tag="stop_terminal"):
365
+ """Acknowledge terminal stopped"""
366
+
367
+ session_id: str
368
+ success: bool
369
+
370
+
371
+ class StreamingCodeExecutionRequest(msgspec.Struct, tag="streaming_code_execution"):
372
+ correlation_id: str
373
+ language: str # "python" or "shell"
374
+ content: str
375
+ timeout: int
376
+
377
+
297
378
  class CliRpcRequest(msgspec.Struct):
298
379
  request_id: str
299
380
  request: (
@@ -305,6 +386,11 @@ class CliRpcRequest(msgspec.Struct):
305
386
  | SwitchCLIChatRequest
306
387
  | KeepAliveCliChatRequest
307
388
  | GenerateUploadUrlRequest
389
+ | StartTerminalRequest
390
+ | TerminalInputRequest
391
+ | TerminalResizeRequest
392
+ | StopTerminalRequest
393
+ | StreamingCodeExecutionRequest
308
394
  )
309
395
 
310
396
 
@@ -316,6 +402,40 @@ class ErrorResponse(msgspec.Struct, tag="error"):
316
402
  error_message: str
317
403
 
318
404
 
405
+ class StreamingCodeExecutionResponseChunk(
406
+ msgspec.Struct, tag="streaming_code_execution_chunk"
407
+ ):
408
+ correlation_id: str
409
+ content: str
410
+ truncated: bool = False
411
+
412
+ def add(
413
+ self, new_chunk: "StreamingCodeExecutionResponseChunk"
414
+ ) -> "StreamingCodeExecutionResponseChunk":
415
+ """Aggregates content of this and a new chunk."""
416
+ assert self.correlation_id == new_chunk.correlation_id
417
+ return StreamingCodeExecutionResponseChunk(
418
+ correlation_id=self.correlation_id,
419
+ content=self.content + new_chunk.content,
420
+ truncated=self.truncated or new_chunk.truncated,
421
+ )
422
+
423
+
424
+ class StreamingCodeExecutionResponse(msgspec.Struct, tag="streaming_code_execution"):
425
+ correlation_id: str
426
+ content: str
427
+ truncated: bool = False
428
+ # Only present for shell code execution
429
+ cancelled_for_timeout: bool = False
430
+ exit_code: int | None = None
431
+ halted: bool = False
432
+
433
+
434
+ class StreamingErrorResponse(msgspec.Struct, tag="streaming_error"):
435
+ correlation_id: str
436
+ error_message: str
437
+
438
+
319
439
  class CliRpcResponse(msgspec.Struct):
320
440
  request_id: str
321
441
  response: (
@@ -323,9 +443,17 @@ class CliRpcResponse(msgspec.Struct):
323
443
  | GetAllFilesResponse
324
444
  | ErrorResponse
325
445
  | TerminateResponse
446
+ | TimeoutResponse
326
447
  | BatchToolExecutionResponse
327
448
  | HttpResponse
328
449
  | SwitchCLIChatResponse
329
450
  | KeepAliveCliChatResponse
330
451
  | GenerateUploadUrlResponse
452
+ | StartTerminalResponse
453
+ | TerminalInputResponse
454
+ | TerminalResizeResponse
455
+ | StopTerminalResponse
456
+ | StreamingCodeExecutionResponseChunk
457
+ | StreamingCodeExecutionResponse
458
+ | StreamingErrorResponse
331
459
  )