kiwimatecoder 0.1.0__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.
- kiwimatecoder/__init__.py +8 -0
- kiwimatecoder/__main__.py +12 -0
- kiwimatecoder/acp/__init__.py +13 -0
- kiwimatecoder/acp/protocol.py +115 -0
- kiwimatecoder/acp/server.py +823 -0
- kiwimatecoder/acp/stdio.py +32 -0
- kiwimatecoder/agent.py +1162 -0
- kiwimatecoder/ai.py +68 -0
- kiwimatecoder/audit.py +65 -0
- kiwimatecoder/browser.py +261 -0
- kiwimatecoder/catalog.py +445 -0
- kiwimatecoder/checkpoints.py +103 -0
- kiwimatecoder/client.py +820 -0
- kiwimatecoder/clipboard.py +99 -0
- kiwimatecoder/commands.py +6757 -0
- kiwimatecoder/config.py +5335 -0
- kiwimatecoder/diagnostics.py +295 -0
- kiwimatecoder/documents.py +299 -0
- kiwimatecoder/evals/__init__.py +39 -0
- kiwimatecoder/evals/cases.py +230 -0
- kiwimatecoder/evals/render.py +46 -0
- kiwimatecoder/evals/runner.py +319 -0
- kiwimatecoder/events.py +95 -0
- kiwimatecoder/forge.py +172 -0
- kiwimatecoder/git.py +164 -0
- kiwimatecoder/headless.py +255 -0
- kiwimatecoder/hooks.py +170 -0
- kiwimatecoder/hunks.py +260 -0
- kiwimatecoder/i18n.py +368 -0
- kiwimatecoder/images.py +195 -0
- kiwimatecoder/index/__init__.py +14 -0
- kiwimatecoder/index/builder.py +376 -0
- kiwimatecoder/index/embeddings.py +151 -0
- kiwimatecoder/index/search.py +247 -0
- kiwimatecoder/index/store.py +251 -0
- kiwimatecoder/instructions.py +83 -0
- kiwimatecoder/jobs.py +594 -0
- kiwimatecoder/lsp/__init__.py +56 -0
- kiwimatecoder/lsp/client.py +548 -0
- kiwimatecoder/lsp/manager.py +353 -0
- kiwimatecoder/lsp/protocol.py +79 -0
- kiwimatecoder/main.py +4258 -0
- kiwimatecoder/mcp/__init__.py +45 -0
- kiwimatecoder/mcp/client.py +385 -0
- kiwimatecoder/mcp/errors.py +7 -0
- kiwimatecoder/mcp/manager.py +253 -0
- kiwimatecoder/mcp/protocol.py +132 -0
- kiwimatecoder/media.py +904 -0
- kiwimatecoder/memory.py +141 -0
- kiwimatecoder/network.py +112 -0
- kiwimatecoder/notify.py +139 -0
- kiwimatecoder/permissions.py +171 -0
- kiwimatecoder/pickers.py +417 -0
- kiwimatecoder/plugins.py +245 -0
- kiwimatecoder/pricing.py +167 -0
- kiwimatecoder/prompts.py +213 -0
- kiwimatecoder/providers.py +342 -0
- kiwimatecoder/redaction.py +66 -0
- kiwimatecoder/remote.py +294 -0
- kiwimatecoder/repl.py +1388 -0
- kiwimatecoder/routing.py +65 -0
- kiwimatecoder/sandbox.py +169 -0
- kiwimatecoder/sdk.py +146 -0
- kiwimatecoder/session.py +574 -0
- kiwimatecoder/share.py +386 -0
- kiwimatecoder/shell.py +660 -0
- kiwimatecoder/skills.py +138 -0
- kiwimatecoder/sync.py +513 -0
- kiwimatecoder/team.py +249 -0
- kiwimatecoder/telemetry.py +392 -0
- kiwimatecoder/templates.py +108 -0
- kiwimatecoder/tools/__init__.py +190 -0
- kiwimatecoder/tools/ask.py +62 -0
- kiwimatecoder/tools/base.py +69 -0
- kiwimatecoder/tools/browser.py +261 -0
- kiwimatecoder/tools/edit_file.py +143 -0
- kiwimatecoder/tools/forge.py +207 -0
- kiwimatecoder/tools/git.py +158 -0
- kiwimatecoder/tools/image.py +300 -0
- kiwimatecoder/tools/io.py +346 -0
- kiwimatecoder/tools/list_dir.py +67 -0
- kiwimatecoder/tools/lsp.py +254 -0
- kiwimatecoder/tools/memory.py +115 -0
- kiwimatecoder/tools/paths.py +234 -0
- kiwimatecoder/tools/read_file.py +113 -0
- kiwimatecoder/tools/registry.py +91 -0
- kiwimatecoder/tools/run_bash.py +189 -0
- kiwimatecoder/tools/search.py +224 -0
- kiwimatecoder/tools/selection.py +108 -0
- kiwimatecoder/tools/shell.py +158 -0
- kiwimatecoder/tools/skill.py +46 -0
- kiwimatecoder/tools/task.py +61 -0
- kiwimatecoder/tools/todo.py +77 -0
- kiwimatecoder/tools/web.py +146 -0
- kiwimatecoder/tools/write_file.py +83 -0
- kiwimatecoder/ui.py +189 -0
- kiwimatecoder/updater.py +502 -0
- kiwimatecoder/web.py +499 -0
- kiwimatecoder-0.1.0.dist-info/METADATA +2016 -0
- kiwimatecoder-0.1.0.dist-info/RECORD +104 -0
- kiwimatecoder-0.1.0.dist-info/WHEEL +5 -0
- kiwimatecoder-0.1.0.dist-info/entry_points.txt +2 -0
- kiwimatecoder-0.1.0.dist-info/licenses/LICENSE +21 -0
- kiwimatecoder-0.1.0.dist-info/top_level.txt +1 -0
kiwimatecoder/routing.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Per-turn model routing.
|
|
2
|
+
|
|
3
|
+
A deliberately small, pure heuristic: short, plain user messages can be sent
|
|
4
|
+
to a cheaper "simple" model, while anything that looks like real engineering
|
|
5
|
+
work stays on the session's model. Nothing here touches the network or global
|
|
6
|
+
state, so the decision is trivial to unit-test.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
#: A fenced code block means the user is showing/handling code.
|
|
15
|
+
_CODE_FENCE = "```"
|
|
16
|
+
|
|
17
|
+
#: A token like ``main.py`` or ``tests/test_x.py`` looks like a file path.
|
|
18
|
+
_FILE_PATH_RE = re.compile(r"\S+\.\w{1,6}")
|
|
19
|
+
|
|
20
|
+
#: A slash command such as ``/help`` or ``/config set``.
|
|
21
|
+
_SLASH_COMMAND_RE = re.compile(r"(?:^|\s)/[A-Za-z]")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _contains_exclude_keyword(text: str, keywords: Any) -> bool:
|
|
25
|
+
if not isinstance(keywords, list):
|
|
26
|
+
return False
|
|
27
|
+
lowered = text.lower()
|
|
28
|
+
for keyword in keywords:
|
|
29
|
+
word = str(keyword).strip().lower()
|
|
30
|
+
if word and re.search(rf"\b{re.escape(word)}\b", lowered):
|
|
31
|
+
return True
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def choose_turn_model(
|
|
36
|
+
user_input: str, session: Any, routing: dict[str, Any]
|
|
37
|
+
) -> str | None:
|
|
38
|
+
"""Return the simple model to use for this turn, or None to stay put.
|
|
39
|
+
|
|
40
|
+
The override applies only when routing is enabled, a simple model is
|
|
41
|
+
configured, the message is short, and it shows no sign of real work: no
|
|
42
|
+
code fence, no file path, no slash command, and no exclude keyword
|
|
43
|
+
(whole-word, case-insensitive).
|
|
44
|
+
"""
|
|
45
|
+
if not routing.get("enabled"):
|
|
46
|
+
return None
|
|
47
|
+
simple_model = str(routing.get("simple_model") or "").strip()
|
|
48
|
+
if not simple_model:
|
|
49
|
+
return None
|
|
50
|
+
text = user_input or ""
|
|
51
|
+
try:
|
|
52
|
+
max_chars = int(routing.get("simple_max_chars") or 0)
|
|
53
|
+
except (TypeError, ValueError):
|
|
54
|
+
return None
|
|
55
|
+
if max_chars <= 0 or len(text) > max_chars:
|
|
56
|
+
return None
|
|
57
|
+
if _CODE_FENCE in text:
|
|
58
|
+
return None
|
|
59
|
+
if _FILE_PATH_RE.search(text):
|
|
60
|
+
return None
|
|
61
|
+
if _SLASH_COMMAND_RE.search(text):
|
|
62
|
+
return None
|
|
63
|
+
if _contains_exclude_keyword(text, routing.get("exclude_keywords")):
|
|
64
|
+
return None
|
|
65
|
+
return simple_model
|
kiwimatecoder/sandbox.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Optional OS-level command sandboxing for ``run_bash`` and the shell tools.
|
|
2
|
+
|
|
3
|
+
This module is pure command construction: it decides *what* to execute (macOS
|
|
4
|
+
seatbelt via ``sandbox-exec`` or Linux bubblewrap via ``bwrap``) but never runs
|
|
5
|
+
anything itself. That keeps it trivially testable and lets callers fall back to
|
|
6
|
+
an unsandboxed execution when a backend is missing.
|
|
7
|
+
|
|
8
|
+
The sandbox is **off by default** and is a damage-limiter, not a security
|
|
9
|
+
boundary against a hostile model: the profile still allows broad reads and,
|
|
10
|
+
unless network is disabled, full network access. Only enable it in workspaces
|
|
11
|
+
you trust. Windows has no supported backend here.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import shutil
|
|
17
|
+
import sys
|
|
18
|
+
from collections.abc import Sequence
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from kiwimatecoder.config import get_sandbox
|
|
23
|
+
|
|
24
|
+
FALLBACK_WARNING = (
|
|
25
|
+
"Sandboxing is enabled but no supported backend is available on this "
|
|
26
|
+
"platform; running the command without a sandbox."
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def sandbox_available(platform: str | None = None) -> tuple[str, str] | None:
|
|
31
|
+
"""Detect the sandbox backend for ``platform`` (defaults to ``sys.platform``).
|
|
32
|
+
|
|
33
|
+
Returns ``(backend, path)`` where ``backend`` is ``"seatbelt"`` on macOS or
|
|
34
|
+
``"bwrap"`` on Linux, or ``None`` when the platform is unsupported or the
|
|
35
|
+
backend executable is not installed.
|
|
36
|
+
"""
|
|
37
|
+
system = platform if platform is not None else sys.platform
|
|
38
|
+
if system == "darwin":
|
|
39
|
+
path = shutil.which("sandbox-exec")
|
|
40
|
+
return ("seatbelt", path) if path else None
|
|
41
|
+
if system.startswith("linux"):
|
|
42
|
+
path = shutil.which("bwrap")
|
|
43
|
+
return ("bwrap", path) if path else None
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _sbpl_string(value: object) -> str:
|
|
48
|
+
"""Quote a path for an SBPL profile string."""
|
|
49
|
+
text = str(value).replace("\\", "\\\\").replace('"', '\\"')
|
|
50
|
+
return f'"{text}"'
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def seatbelt_profile(
|
|
54
|
+
*,
|
|
55
|
+
workspace: str | Path,
|
|
56
|
+
extra_writable: Sequence[str] = (),
|
|
57
|
+
network: bool = True,
|
|
58
|
+
) -> str:
|
|
59
|
+
"""Build the seatbelt (SBPL) profile used by :func:`seatbelt_command`.
|
|
60
|
+
|
|
61
|
+
Reads are allowed broadly; writes are limited to the workspace, ``/tmp``
|
|
62
|
+
(and its macOS alias ``/private/tmp``), and any ``extra_writable`` paths.
|
|
63
|
+
Network access is allowed only when ``network`` is true.
|
|
64
|
+
"""
|
|
65
|
+
writable = [str(Path(workspace).expanduser()), "/tmp", "/private/tmp"]
|
|
66
|
+
writable.extend(str(Path(path).expanduser()) for path in extra_writable)
|
|
67
|
+
write_rule = " ".join(f"(subpath {_sbpl_string(path)})" for path in writable)
|
|
68
|
+
network_rule = "(allow network*)" if network else "(deny network*)"
|
|
69
|
+
return "\n".join(
|
|
70
|
+
[
|
|
71
|
+
"(version 1)",
|
|
72
|
+
"(deny default)",
|
|
73
|
+
"(allow process*)",
|
|
74
|
+
"(allow file-read*)",
|
|
75
|
+
f"(allow file-write* {write_rule})",
|
|
76
|
+
"(allow sysctl-read)",
|
|
77
|
+
network_rule,
|
|
78
|
+
]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def seatbelt_command(
|
|
83
|
+
command: str,
|
|
84
|
+
*,
|
|
85
|
+
workspace: str | Path,
|
|
86
|
+
extra_writable: Sequence[str] = (),
|
|
87
|
+
network: bool = True,
|
|
88
|
+
) -> list[str]:
|
|
89
|
+
"""Build ``sandbox-exec -p <profile> /bin/sh -c <command>``."""
|
|
90
|
+
profile = seatbelt_profile(
|
|
91
|
+
workspace=workspace, extra_writable=extra_writable, network=network
|
|
92
|
+
)
|
|
93
|
+
return ["sandbox-exec", "-p", profile, "/bin/sh", "-c", command]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def bwrap_command(
|
|
97
|
+
command: str,
|
|
98
|
+
*,
|
|
99
|
+
workspace: str | Path,
|
|
100
|
+
extra_writable: Sequence[str] = (),
|
|
101
|
+
network: bool = True,
|
|
102
|
+
) -> list[str]:
|
|
103
|
+
"""Build a bubblewrap invocation with a read-only root and writable workspace.
|
|
104
|
+
|
|
105
|
+
``/`` is bind-mounted read-only, the workspace read-write, ``/tmp`` is a
|
|
106
|
+
private tmpfs, and ``--unshare-net`` is added when ``network`` is false.
|
|
107
|
+
"""
|
|
108
|
+
ws = str(Path(workspace).expanduser())
|
|
109
|
+
argv = [
|
|
110
|
+
"bwrap",
|
|
111
|
+
"--ro-bind",
|
|
112
|
+
"/",
|
|
113
|
+
"/",
|
|
114
|
+
"--bind",
|
|
115
|
+
ws,
|
|
116
|
+
ws,
|
|
117
|
+
"--dev",
|
|
118
|
+
"/dev",
|
|
119
|
+
"--proc",
|
|
120
|
+
"/proc",
|
|
121
|
+
"--tmpfs",
|
|
122
|
+
"/tmp",
|
|
123
|
+
]
|
|
124
|
+
for path in extra_writable:
|
|
125
|
+
resolved = str(Path(path).expanduser())
|
|
126
|
+
argv.extend(["--bind", resolved, resolved])
|
|
127
|
+
if not network:
|
|
128
|
+
argv.append("--unshare-net")
|
|
129
|
+
argv.extend(["/bin/sh", "-c", command])
|
|
130
|
+
return argv
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def wrap_command(
|
|
134
|
+
command: str,
|
|
135
|
+
*,
|
|
136
|
+
workspace: str | Path,
|
|
137
|
+
cfg: dict[str, Any] | None = None,
|
|
138
|
+
) -> tuple[list[str], str | None]:
|
|
139
|
+
"""Return the argv to execute plus an optional warning for the caller.
|
|
140
|
+
|
|
141
|
+
Disabled settings return the plain ``["/bin/sh", "-c", command]`` and no
|
|
142
|
+
warning. Enabled settings with no usable backend also return the plain
|
|
143
|
+
argv, with a warning the caller should surface before running.
|
|
144
|
+
"""
|
|
145
|
+
settings = get_sandbox(cfg)
|
|
146
|
+
fallback = ["/bin/sh", "-c", command]
|
|
147
|
+
if not settings["enabled"]:
|
|
148
|
+
return fallback, None
|
|
149
|
+
available = sandbox_available()
|
|
150
|
+
if available is None:
|
|
151
|
+
return fallback, FALLBACK_WARNING
|
|
152
|
+
backend, path = available
|
|
153
|
+
if backend == "seatbelt":
|
|
154
|
+
argv = seatbelt_command(
|
|
155
|
+
command,
|
|
156
|
+
workspace=workspace,
|
|
157
|
+
extra_writable=settings["extra_writable"],
|
|
158
|
+
network=settings["network"],
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
argv = bwrap_command(
|
|
162
|
+
command,
|
|
163
|
+
workspace=workspace,
|
|
164
|
+
extra_writable=settings["extra_writable"],
|
|
165
|
+
network=settings["network"],
|
|
166
|
+
)
|
|
167
|
+
if path:
|
|
168
|
+
argv[0] = path
|
|
169
|
+
return argv, None
|
kiwimatecoder/sdk.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Embedded SDK for programmatic, headless agent runs.
|
|
2
|
+
|
|
3
|
+
The SDK wraps the same implementation the CLI's ``-p/--print`` mode uses, so a
|
|
4
|
+
host application gets identical session setup, tool behavior, permissions, and
|
|
5
|
+
cost accounting without spawning a subprocess.
|
|
6
|
+
|
|
7
|
+
Example::
|
|
8
|
+
|
|
9
|
+
from kiwimatecoder.sdk import run_agent_sync
|
|
10
|
+
|
|
11
|
+
result = run_agent_sync("Summarize README.md", workspace=".", mode="plan")
|
|
12
|
+
if result.success:
|
|
13
|
+
print(result.text)
|
|
14
|
+
|
|
15
|
+
``run_agent`` is the async entry point for hosts that already own an event
|
|
16
|
+
loop. Both entry points return a :class:`RunResult`. Approvals default to
|
|
17
|
+
deny; pass ``confirm`` to grant them (for example
|
|
18
|
+
``confirm=lambda summary, preview: True``) or run with
|
|
19
|
+
``mode="auto-accept"``.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import asyncio
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any, Callable
|
|
28
|
+
|
|
29
|
+
from rich.console import Console
|
|
30
|
+
|
|
31
|
+
from kiwimatecoder.headless import run_agent_once
|
|
32
|
+
from kiwimatecoder.permissions import ConfirmFn, PermissionMode
|
|
33
|
+
from kiwimatecoder.session import Session
|
|
34
|
+
|
|
35
|
+
EventCallback = Callable[[str, dict[str, Any]], None]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class RunResult:
|
|
40
|
+
"""The outcome of one programmatic agent run.
|
|
41
|
+
|
|
42
|
+
``usage`` totals the billed prompt/completion tokens across every model
|
|
43
|
+
call in the turn. ``cost_usd`` is ``None`` when the model has no price
|
|
44
|
+
entry (see :mod:`kiwimatecoder.pricing`). ``error`` is set only when
|
|
45
|
+
``success`` is False.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
text: str
|
|
49
|
+
usage: dict[str, int]
|
|
50
|
+
cost_usd: float | None
|
|
51
|
+
provider: str
|
|
52
|
+
model: str
|
|
53
|
+
mode: str
|
|
54
|
+
tools_used: list[str]
|
|
55
|
+
messages: int
|
|
56
|
+
success: bool
|
|
57
|
+
error: str | None = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def run_agent(
|
|
61
|
+
prompt: str,
|
|
62
|
+
*,
|
|
63
|
+
workspace: str | Path | None = None,
|
|
64
|
+
provider: str | None = None,
|
|
65
|
+
model: str | None = None,
|
|
66
|
+
mode: PermissionMode | str = PermissionMode.ASK,
|
|
67
|
+
on_event: EventCallback | None = None,
|
|
68
|
+
max_turns: int = 30,
|
|
69
|
+
console: Console | None = None,
|
|
70
|
+
session: Session | None = None,
|
|
71
|
+
confirm: ConfirmFn | None = None,
|
|
72
|
+
) -> RunResult:
|
|
73
|
+
"""Run one agentic turn and return its :class:`RunResult`.
|
|
74
|
+
|
|
75
|
+
``workspace`` defaults to the current directory. ``provider``, ``model``,
|
|
76
|
+
and ``mode`` override the configured defaults for this run only. Text is
|
|
77
|
+
written to ``console`` when one is supplied, otherwise the run is silent.
|
|
78
|
+
``on_event`` receives ``(name, payload)`` for every render event. Without
|
|
79
|
+
``confirm`` (and outside auto-accept/plan modes) approvals are denied.
|
|
80
|
+
"""
|
|
81
|
+
outcome = await run_agent_once(
|
|
82
|
+
prompt,
|
|
83
|
+
workspace=workspace,
|
|
84
|
+
provider=provider,
|
|
85
|
+
model=model,
|
|
86
|
+
mode=mode,
|
|
87
|
+
confirm=confirm,
|
|
88
|
+
on_event=on_event,
|
|
89
|
+
max_turns=max_turns,
|
|
90
|
+
console=console,
|
|
91
|
+
session=session,
|
|
92
|
+
render_text=True,
|
|
93
|
+
)
|
|
94
|
+
return RunResult(
|
|
95
|
+
text=outcome.text,
|
|
96
|
+
usage=outcome.usage,
|
|
97
|
+
cost_usd=outcome.cost_usd,
|
|
98
|
+
provider=outcome.provider,
|
|
99
|
+
model=outcome.model,
|
|
100
|
+
mode=outcome.mode,
|
|
101
|
+
tools_used=outcome.tools_used,
|
|
102
|
+
messages=outcome.messages,
|
|
103
|
+
success=outcome.success,
|
|
104
|
+
error=outcome.error,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def run_agent_sync(
|
|
109
|
+
prompt: str,
|
|
110
|
+
*,
|
|
111
|
+
workspace: str | Path | None = None,
|
|
112
|
+
provider: str | None = None,
|
|
113
|
+
model: str | None = None,
|
|
114
|
+
mode: PermissionMode | str = PermissionMode.ASK,
|
|
115
|
+
on_event: EventCallback | None = None,
|
|
116
|
+
max_turns: int = 30,
|
|
117
|
+
console: Console | None = None,
|
|
118
|
+
session: Session | None = None,
|
|
119
|
+
confirm: ConfirmFn | None = None,
|
|
120
|
+
) -> RunResult:
|
|
121
|
+
"""Blocking wrapper around :func:`run_agent`.
|
|
122
|
+
|
|
123
|
+
Raises :class:`RuntimeError` when called from inside a running event loop;
|
|
124
|
+
``await run_agent(...)`` instead.
|
|
125
|
+
"""
|
|
126
|
+
try:
|
|
127
|
+
asyncio.get_running_loop()
|
|
128
|
+
except RuntimeError:
|
|
129
|
+
return asyncio.run(
|
|
130
|
+
run_agent(
|
|
131
|
+
prompt,
|
|
132
|
+
workspace=workspace,
|
|
133
|
+
provider=provider,
|
|
134
|
+
model=model,
|
|
135
|
+
mode=mode,
|
|
136
|
+
on_event=on_event,
|
|
137
|
+
max_turns=max_turns,
|
|
138
|
+
console=console,
|
|
139
|
+
session=session,
|
|
140
|
+
confirm=confirm,
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
raise RuntimeError(
|
|
144
|
+
"run_agent_sync() cannot run inside a running event loop; "
|
|
145
|
+
"await run_agent(...) instead."
|
|
146
|
+
)
|