kimi-cli 0.39__tar.gz → 0.40__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 kimi-cli might be problematic. Click here for more details.

Files changed (80) hide show
  1. {kimi_cli-0.39 → kimi_cli-0.40}/PKG-INFO +2 -2
  2. {kimi_cli-0.39 → kimi_cli-0.40}/README.md +1 -1
  3. {kimi_cli-0.39 → kimi_cli-0.40}/pyproject.toml +1 -1
  4. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/CHANGELOG.md +11 -0
  5. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/soul/approval.py +1 -3
  6. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/soul/compaction.py +6 -6
  7. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/soul/kimisoul.py +5 -4
  8. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/soul/message.py +5 -5
  9. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/bash/__init__.py +2 -2
  10. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/file/grep.py +2 -1
  11. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/web/fetch.py +2 -1
  12. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/web/search.py +2 -2
  13. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/ui/shell/__init__.py +4 -2
  14. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/ui/shell/liveview.py +12 -3
  15. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/ui/shell/setup.py +2 -1
  16. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/ui/shell/update.py +2 -1
  17. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/ui/shell/visualize.py +9 -3
  18. kimi_cli-0.40/src/kimi_cli/utils/aiohttp.py +10 -0
  19. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/utils/provider.py +0 -2
  20. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/__init__.py +0 -0
  21. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/agent.py +0 -0
  22. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/agents/koder/README.md +0 -0
  23. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/agents/koder/agent.yaml +0 -0
  24. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/agents/koder/sub.yaml +0 -0
  25. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/agents/koder/system.md +0 -0
  26. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/config.py +0 -0
  27. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/llm.py +0 -0
  28. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/metadata.py +0 -0
  29. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/prompts/__init__.py +0 -0
  30. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/prompts/compact.md +0 -0
  31. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/prompts/init.md +0 -0
  32. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/py.typed +0 -0
  33. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/share.py +0 -0
  34. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/soul/__init__.py +0 -0
  35. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/soul/context.py +0 -0
  36. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/soul/denwarenji.py +0 -0
  37. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/soul/toolset.py +0 -0
  38. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/soul/wire.py +0 -0
  39. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/__init__.py +0 -0
  40. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/bash/bash.md +0 -0
  41. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/dmail/__init__.py +0 -0
  42. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/dmail/dmail.md +0 -0
  43. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/file/__init__.py +0 -0
  44. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/file/glob.md +0 -0
  45. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/file/glob.py +0 -0
  46. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/file/grep.md +0 -0
  47. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/file/patch.md +0 -0
  48. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/file/patch.py +0 -0
  49. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/file/read.md +0 -0
  50. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/file/read.py +0 -0
  51. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/file/replace.md +0 -0
  52. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/file/replace.py +0 -0
  53. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/file/write.md +0 -0
  54. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/file/write.py +0 -0
  55. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/mcp.py +0 -0
  56. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/task/__init__.py +0 -0
  57. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/task/task.md +0 -0
  58. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/test.py +0 -0
  59. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/think/__init__.py +0 -0
  60. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/think/think.md +0 -0
  61. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/todo/__init__.py +0 -0
  62. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/todo/set_todo_list.md +0 -0
  63. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/utils.py +0 -0
  64. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/web/__init__.py +0 -0
  65. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/web/fetch.md +0 -0
  66. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/tools/web/search.md +0 -0
  67. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/ui/__init__.py +0 -0
  68. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/ui/acp/__init__.py +0 -0
  69. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/ui/print/__init__.py +0 -0
  70. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/ui/shell/console.py +0 -0
  71. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/ui/shell/debug.py +0 -0
  72. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/ui/shell/keyboard.py +0 -0
  73. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/ui/shell/metacmd.py +0 -0
  74. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/ui/shell/prompt.py +0 -0
  75. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/utils/changelog.py +0 -0
  76. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/utils/logging.py +0 -0
  77. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/utils/message.py +0 -0
  78. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/utils/path.py +0 -0
  79. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/utils/pyinstaller.py +0 -0
  80. {kimi_cli-0.39 → kimi_cli-0.40}/src/kimi_cli/utils/string.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: kimi-cli
3
- Version: 0.39
3
+ Version: 0.40
4
4
  Summary: Kimi CLI is your next CLI agent.
5
5
  Requires-Dist: agent-client-protocol>=0.4.9
6
6
  Requires-Dist: aiofiles>=25.1.0
@@ -108,7 +108,7 @@ After restarting Zsh, you can switch to agent mode by pressing `Ctrl-K`.
108
108
 
109
109
  ### ACP support
110
110
 
111
- Kimi CLI supports [Agent Client Protocol] out of the box. You can use it together with any ACP-compatible editors or IDEs.
111
+ Kimi CLI supports [Agent Client Protocol] out of the box. You can use it together with any ACP-compatible editor or IDE.
112
112
 
113
113
  For example, to use Kimi CLI with [Zed](https://zed.dev/), add the following configuration to your `~/.config/zed/settings.json`:
114
114
 
@@ -84,7 +84,7 @@ After restarting Zsh, you can switch to agent mode by pressing `Ctrl-K`.
84
84
 
85
85
  ### ACP support
86
86
 
87
- Kimi CLI supports [Agent Client Protocol] out of the box. You can use it together with any ACP-compatible editors or IDEs.
87
+ Kimi CLI supports [Agent Client Protocol] out of the box. You can use it together with any ACP-compatible editor or IDE.
88
88
 
89
89
  For example, to use Kimi CLI with [Zed](https://zed.dev/), add the following configuration to your `~/.config/zed/settings.json`:
90
90
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kimi-cli"
3
- version = "0.39"
3
+ version = "0.40"
4
4
  description = "Kimi CLI is your next CLI agent."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -9,6 +9,17 @@ Internal builds may append content to the Unreleased section.
9
9
  Only write entries that are worth mentioning to users.
10
10
  -->
11
11
 
12
+ ## [0.40] - 2025-10-24
13
+
14
+ ### Added
15
+
16
+ - Support `ESC` key to interrupt the agent loop
17
+
18
+ ### Fixed
19
+
20
+ - Fix SSL certificate verification error in some rare cases
21
+ - Fix possible decoding error in Bash tool
22
+
12
23
  ## [0.39] - 2025-10-24
13
24
 
14
25
  ### Fixed
@@ -9,7 +9,7 @@ class Approval:
9
9
  def __init__(self, yolo: bool = False):
10
10
  self._request_queue = asyncio.Queue[ApprovalRequest]()
11
11
  self._yolo = yolo
12
- self._auto_approve_actions = set() # TODO: persist across sessions
12
+ self._auto_approve_actions: set[str] = set() # TODO: persist across sessions
13
13
  """Set of action names that should automatically be approved."""
14
14
 
15
15
  def set_yolo(self, yolo: bool) -> None:
@@ -60,8 +60,6 @@ class Approval:
60
60
  return True
61
61
  case ApprovalResponse.REJECT:
62
62
  return False
63
- case _:
64
- raise ValueError(f"Unknown approval response: {response}")
65
63
 
66
64
  async def fetch_request(self) -> ApprovalRequest:
67
65
  """
@@ -1,6 +1,6 @@
1
1
  from collections.abc import Sequence
2
2
  from string import Template
3
- from typing import Protocol, runtime_checkable
3
+ from typing import TYPE_CHECKING, Protocol, runtime_checkable
4
4
 
5
5
  from kosong.base import generate
6
6
  from kosong.base.message import ContentPart, Message, TextPart
@@ -32,7 +32,7 @@ class Compaction(Protocol):
32
32
  ...
33
33
 
34
34
 
35
- class SimpleCompaction:
35
+ class SimpleCompaction(Compaction):
36
36
  async def compact(self, messages: Sequence[Message], llm: LLM) -> Sequence[Message]:
37
37
  history = list(messages)
38
38
  if not history:
@@ -99,7 +99,7 @@ class SimpleCompaction:
99
99
  return compacted_messages
100
100
 
101
101
 
102
- def __static_type_check(
103
- simple: SimpleCompaction,
104
- ):
105
- _: Compaction = simple
102
+ if TYPE_CHECKING:
103
+
104
+ def type_check(simple: SimpleCompaction):
105
+ _: Compaction = simple
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  from collections.abc import Sequence
3
3
  from functools import partial
4
+ from typing import TYPE_CHECKING
4
5
 
5
6
  import kosong
6
7
  import tenacity
@@ -302,7 +303,7 @@ class BackToTheFuture(Exception):
302
303
  self.messages = messages
303
304
 
304
305
 
305
- def __static_type_check(
306
- kimi_soul: KimiSoul,
307
- ):
308
- _: Soul = kimi_soul
306
+ if TYPE_CHECKING:
307
+
308
+ def type_check(kimi_soul: KimiSoul):
309
+ _: Soul = kimi_soul
@@ -14,7 +14,7 @@ def tool_result_to_messages(tool_result: ToolResult) -> list[Message]:
14
14
  message = tool_result.result.message
15
15
  if isinstance(tool_result.result, ToolRuntimeError):
16
16
  message += "\nThis is an unexpected error and the tool is probably not working."
17
- content = [system(message)]
17
+ content: list[ContentPart] = [system(message)]
18
18
  if tool_result.result.output:
19
19
  content.append(TextPart(text=tool_result.result.output))
20
20
  return [
@@ -26,8 +26,8 @@ def tool_result_to_messages(tool_result: ToolResult) -> list[Message]:
26
26
  ]
27
27
 
28
28
  content = tool_ok_to_message_content(tool_result.result)
29
- text_parts = []
30
- non_text_parts = []
29
+ text_parts: list[ContentPart] = []
30
+ non_text_parts: list[ContentPart] = []
31
31
  for part in content:
32
32
  if isinstance(part, TextPart):
33
33
  text_parts.append(part)
@@ -60,7 +60,7 @@ def tool_result_to_messages(tool_result: ToolResult) -> list[Message]:
60
60
 
61
61
  def tool_ok_to_message_content(result: ToolOk) -> list[ContentPart]:
62
62
  """Convert a tool return value to a list of message content parts."""
63
- content = []
63
+ content: list[ContentPart] = []
64
64
  if result.message:
65
65
  content.append(system(result.message))
66
66
  match output := result.output:
@@ -70,7 +70,7 @@ def tool_ok_to_message_content(result: ToolOk) -> list[ContentPart]:
70
70
  case ContentPart():
71
71
  content.append(output)
72
72
  case _:
73
- content.extend(list(output))
73
+ content.extend(output)
74
74
  if not content:
75
75
  content.append(system("Tool output is empty."))
76
76
  return content
@@ -45,11 +45,11 @@ class Bash(CallableTool2[Params]):
45
45
  return ToolRejectedError()
46
46
 
47
47
  def stdout_cb(line: bytes):
48
- line_str = line.decode()
48
+ line_str = line.decode(errors="replace")
49
49
  builder.write(line_str)
50
50
 
51
51
  def stderr_cb(line: bytes):
52
- line_str = line.decode()
52
+ line_str = line.decode(errors="replace")
53
53
  builder.write(line_str)
54
54
 
55
55
  try:
@@ -15,6 +15,7 @@ from pydantic import BaseModel, Field
15
15
 
16
16
  import kimi_cli
17
17
  from kimi_cli.share import get_share_dir
18
+ from kimi_cli.utils.aiohttp import new_client_session
18
19
  from kimi_cli.utils.logging import logger
19
20
 
20
21
 
@@ -167,7 +168,7 @@ async def _download_and_install_rg(bin_name: str) -> Path:
167
168
  share_bin_dir.mkdir(parents=True, exist_ok=True)
168
169
  destination = share_bin_dir / bin_name
169
170
 
170
- async with aiohttp.ClientSession() as session:
171
+ async with new_client_session() as session:
171
172
  with tempfile.TemporaryDirectory(prefix="kimi-rg-") as tmpdir:
172
173
  tar_path = Path(tmpdir) / filename
173
174
 
@@ -7,6 +7,7 @@ from kosong.tooling import CallableTool2, ToolReturnType
7
7
  from pydantic import BaseModel, Field
8
8
 
9
9
  from kimi_cli.tools.utils import ToolResultBuilder, load_desc
10
+ from kimi_cli.utils.aiohttp import new_client_session
10
11
 
11
12
 
12
13
  class Params(BaseModel):
@@ -24,7 +25,7 @@ class FetchURL(CallableTool2[Params]):
24
25
 
25
26
  try:
26
27
  async with (
27
- aiohttp.ClientSession() as session,
28
+ new_client_session() as session,
28
29
  session.get(
29
30
  params.url,
30
31
  headers={
@@ -1,7 +1,6 @@
1
1
  from pathlib import Path
2
2
  from typing import override
3
3
 
4
- import aiohttp
5
4
  from kosong.tooling import CallableTool2, ToolReturnType
6
5
  from pydantic import BaseModel, Field, ValidationError
7
6
 
@@ -9,6 +8,7 @@ import kimi_cli
9
8
  from kimi_cli.config import Config
10
9
  from kimi_cli.soul.toolset import get_current_tool_call_or_none
11
10
  from kimi_cli.tools.utils import ToolResultBuilder, load_desc
11
+ from kimi_cli.utils.aiohttp import new_client_session
12
12
 
13
13
 
14
14
  class Params(BaseModel):
@@ -62,7 +62,7 @@ class SearchWeb(CallableTool2[Params]):
62
62
  assert tool_call is not None, "Tool call is expected to be set"
63
63
 
64
64
  async with (
65
- aiohttp.ClientSession() as session,
65
+ new_client_session() as session,
66
66
  session.post(
67
67
  self._base_url,
68
68
  headers={
@@ -1,7 +1,6 @@
1
1
  import asyncio
2
2
  import signal
3
3
  from collections.abc import Awaitable, Coroutine
4
- from functools import partial
5
4
  from typing import Any
6
5
 
7
6
  from kosong.chat_provider import APIStatusError, ChatProviderError
@@ -160,10 +159,13 @@ class ShellApp:
160
159
  loop.add_signal_handler(signal.SIGINT, _handler)
161
160
 
162
161
  try:
162
+ # Use lambda to pass cancel_event via closure
163
163
  await run_soul(
164
164
  self.soul,
165
165
  command,
166
- partial(visualize, initial_status=self.soul.status),
166
+ lambda wire: visualize(
167
+ wire, initial_status=self.soul.status, cancel_event=cancel_event
168
+ ),
167
169
  cancel_event,
168
170
  )
169
171
  return True
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  from collections import deque
2
3
 
3
4
  import streamingjson
@@ -125,7 +126,7 @@ class _ApprovalRequestDisplay:
125
126
 
126
127
 
127
128
  class StepLiveView:
128
- def __init__(self, status: StatusSnapshot):
129
+ def __init__(self, status: StatusSnapshot, cancel_event: asyncio.Event | None = None):
129
130
  # message content
130
131
  self._line_buffer = Text("")
131
132
 
@@ -144,6 +145,9 @@ class StepLiveView:
144
145
  )
145
146
  self._buffer_status: RenderableType | None = None
146
147
 
148
+ # cancel event for ESC key handling
149
+ self._cancel_event = cancel_event
150
+
147
151
  def __enter__(self):
148
152
  self._live = Live(
149
153
  self._compose(),
@@ -243,6 +247,11 @@ class StepLiveView:
243
247
  self._status_text.plain = self._format_status(status)
244
248
 
245
249
  def handle_keyboard_event(self, event: KeyEvent):
250
+ # Handle ESC key to cancel the run
251
+ if event == KeyEvent.ESCAPE and self._cancel_event is not None:
252
+ self._cancel_event.set()
253
+ return
254
+
246
255
  if not self._current_approval:
247
256
  # just ignore any keyboard event when there's no approval request
248
257
  return
@@ -298,8 +307,8 @@ class StepLiveView:
298
307
  class StepLiveViewWithMarkdown(StepLiveView):
299
308
  # TODO: figure out a streaming implementation for this
300
309
 
301
- def __init__(self, status: StatusSnapshot):
302
- super().__init__(status)
310
+ def __init__(self, status: StatusSnapshot, cancel_event: asyncio.Event | None = None):
311
+ super().__init__(status, cancel_event)
303
312
  self._pending_markdown_parts: list[str] = []
304
313
  self._buffer_status_active = False
305
314
  self._buffer_status_obj: Status | None = None
@@ -9,6 +9,7 @@ from pydantic import SecretStr
9
9
  from kimi_cli.config import LLMModel, LLMProvider, MoonshotSearchConfig, load_config, save_config
10
10
  from kimi_cli.ui.shell.console import console
11
11
  from kimi_cli.ui.shell.metacmd import meta_command
12
+ from kimi_cli.utils.aiohttp import new_client_session
12
13
 
13
14
  if TYPE_CHECKING:
14
15
  from kimi_cli.ui.shell import ShellApp
@@ -109,7 +110,7 @@ async def _setup() -> _SetupResult | None:
109
110
  models_url = f"{platform.base_url}/models"
110
111
  try:
111
112
  async with (
112
- aiohttp.ClientSession() as session,
113
+ new_client_session() as session,
113
114
  session.get(
114
115
  models_url,
115
116
  headers={
@@ -13,6 +13,7 @@ import aiohttp
13
13
 
14
14
  from kimi_cli.share import get_share_dir
15
15
  from kimi_cli.ui.shell.console import console
16
+ from kimi_cli.utils.aiohttp import new_client_session
16
17
  from kimi_cli.utils.logging import logger
17
18
 
18
19
  BASE_URL = "https://cdn.kimi.com/binaries/kimi-cli"
@@ -95,7 +96,7 @@ async def _do_update(*, print: bool, check_only: bool) -> UpdateResult:
95
96
  _print("[red]Failed to detect target platform.[/red]")
96
97
  return UpdateResult.UNSUPPORTED
97
98
 
98
- async with aiohttp.ClientSession() as session:
99
+ async with new_client_session() as session:
99
100
  logger.info("Checking for updates...")
100
101
  _print("Checking for updates...")
101
102
  latest_version = await _get_latest_version(session)
@@ -25,7 +25,6 @@ async def _keyboard_listener(step: StepLiveView):
25
25
  async def _keyboard():
26
26
  try:
27
27
  async for event in listen_for_keyboard():
28
- # TODO: ESCAPE to interrupt
29
28
  step.handle_keyboard_event(event)
30
29
  except asyncio.CancelledError:
31
30
  return
@@ -39,10 +38,17 @@ async def _keyboard_listener(step: StepLiveView):
39
38
  await task
40
39
 
41
40
 
42
- async def visualize(wire: Wire, *, initial_status: StatusSnapshot):
41
+ async def visualize(
42
+ wire: Wire, *, initial_status: StatusSnapshot, cancel_event: asyncio.Event | None = None
43
+ ):
43
44
  """
44
45
  A loop to consume agent events and visualize the agent behavior.
45
46
  This loop never raise any exception except asyncio.CancelledError.
47
+
48
+ Args:
49
+ wire: Communication channel with the agent
50
+ initial_status: Initial status snapshot
51
+ cancel_event: Event that can be set (e.g., by ESC key) to cancel the run
46
52
  """
47
53
  latest_status = initial_status
48
54
  try:
@@ -52,7 +58,7 @@ async def visualize(wire: Wire, *, initial_status: StatusSnapshot):
52
58
  while True:
53
59
  # TODO: Maybe we can always have a StepLiveView here.
54
60
  # No need to recreate for each step.
55
- with StepLiveViewWithMarkdown(latest_status) as step:
61
+ with StepLiveViewWithMarkdown(latest_status, cancel_event) as step:
56
62
  async with _keyboard_listener(step):
57
63
  # spin the moon at the beginning of each step
58
64
  with console.status("", spinner="moon"):
@@ -0,0 +1,10 @@
1
+ import ssl
2
+
3
+ import aiohttp
4
+ import certifi
5
+
6
+ _ssl_context = ssl.create_default_context(cafile=certifi.where())
7
+
8
+
9
+ def new_client_session() -> aiohttp.ClientSession:
10
+ return aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=_ssl_context))
@@ -66,7 +66,5 @@ def create_llm(
66
66
  error_types=[429, 500, 503],
67
67
  ),
68
68
  )
69
- case _:
70
- raise ValueError(f"Unsupported provider: {provider.type}")
71
69
 
72
70
  return LLM(chat_provider=chat_provider, max_context_size=model.max_context_size)
File without changes
File without changes
File without changes
File without changes
File without changes