v2.7.4 (#579)
Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai> Co-authored-by: Kim-Adeline Miguel <kimadeline.miguel@mistral.ai> Co-authored-by: Lucas Marandat <31749711+lucasmrdt@users.noreply.github.com> Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com> Co-authored-by: Paul Cacheux <paul.cacheux@mistral.ai> Co-authored-by: Peter Evers <pevers90@gmail.com> Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai> Co-authored-by: Pierre Rossinès <pierre.rossines@protonmail.com> Co-authored-by: Quentin <quentin.torroba@mistral.ai> Co-authored-by: Simon Van de Kerckhove <simon.vandekerckhove@mistral.ai> Co-authored-by: Val <102326092+vdeva@users.noreply.github.com> Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
parent
90763daf81
commit
e9a9217cc8
113 changed files with 7202 additions and 541 deletions
|
|
@ -18,7 +18,7 @@ from vibe.core.types import ToolCallEvent, ToolResultEvent
|
|||
@pytest.fixture
|
||||
def tool():
|
||||
config = AskUserQuestionConfig()
|
||||
return AskUserQuestion(config=config, state=BaseToolState())
|
||||
return AskUserQuestion(config_getter=lambda: config, state=BaseToolState())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from vibe.core.tools.permissions import PermissionContext
|
|||
def bash(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
config = BashToolConfig()
|
||||
return Bash(config=config, state=BaseToolState())
|
||||
return Bash(config_getter=lambda: config, state=BaseToolState())
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -39,7 +39,7 @@ async def test_fails_cat_command_with_missing_file(bash):
|
|||
async def test_uses_effective_workdir(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
config = BashToolConfig()
|
||||
bash_tool = Bash(config=config, state=BaseToolState())
|
||||
bash_tool = Bash(config_getter=lambda: config, state=BaseToolState())
|
||||
|
||||
result = await collect_result(bash_tool.run(BashArgs(command="pwd")))
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ async def test_handles_timeout(bash):
|
|||
@pytest.mark.asyncio
|
||||
async def test_truncates_output_to_max_bytes(bash):
|
||||
config = BashToolConfig(max_output_bytes=5)
|
||||
bash_tool = Bash(config=config, state=BaseToolState())
|
||||
bash_tool = Bash(config_getter=lambda: config, state=BaseToolState())
|
||||
|
||||
result = await collect_result(
|
||||
bash_tool.run(BashArgs(command="printf 'abcdefghij'"))
|
||||
|
|
@ -78,7 +78,7 @@ async def test_decodes_non_utf8_bytes(bash):
|
|||
|
||||
|
||||
def test_find_not_in_default_allowlist():
|
||||
bash_tool = Bash(config=BashToolConfig(), state=BaseToolState())
|
||||
bash_tool = Bash(config_getter=lambda: BashToolConfig(), state=BaseToolState())
|
||||
# find -exec runs arbitrary commands; must not be allowlisted by default
|
||||
permission = bash_tool.resolve_permission(BashArgs(command="find . -exec id \\;"))
|
||||
assert (
|
||||
|
|
@ -89,7 +89,7 @@ def test_find_not_in_default_allowlist():
|
|||
|
||||
def test_resolve_permission():
|
||||
config = BashToolConfig(allowlist=["echo", "pwd"], denylist=["rm"])
|
||||
bash_tool = Bash(config=config, state=BaseToolState())
|
||||
bash_tool = Bash(config_getter=lambda: config, state=BaseToolState())
|
||||
|
||||
allowlisted = bash_tool.resolve_permission(BashArgs(command="echo hi"))
|
||||
denylisted = bash_tool.resolve_permission(BashArgs(command="rm -rf /tmp"))
|
||||
|
|
@ -111,7 +111,7 @@ class TestResolvePermissionWindowsSyntax:
|
|||
|
||||
def _make_bash(self, **kwargs) -> Bash:
|
||||
config = BashToolConfig(**kwargs)
|
||||
return Bash(config=config, state=BaseToolState())
|
||||
return Bash(config_getter=lambda: config, state=BaseToolState())
|
||||
|
||||
def test_dir_with_windows_flags_allowlisted(self):
|
||||
bash_tool = self._make_bash(allowlist=["dir"])
|
||||
|
|
@ -215,7 +215,7 @@ class TestDenylistWordBoundary:
|
|||
|
||||
def _make_bash(self, **kwargs) -> Bash:
|
||||
config = BashToolConfig(**kwargs)
|
||||
return Bash(config=config, state=BaseToolState())
|
||||
return Bash(config_getter=lambda: config, state=BaseToolState())
|
||||
|
||||
def test_vi_blocks_vi_exact(self):
|
||||
bash_tool = self._make_bash(denylist=["vi"])
|
||||
|
|
|
|||
|
|
@ -56,7 +56,9 @@ def _default_profile() -> AgentProfile:
|
|||
|
||||
@pytest.fixture
|
||||
def tool() -> ExitPlanMode:
|
||||
return ExitPlanMode(config=ExitPlanModeConfig(), state=BaseToolState())
|
||||
return ExitPlanMode(
|
||||
config_getter=lambda: ExitPlanModeConfig(), state=BaseToolState()
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class TestBashGranularPermissions:
|
|||
|
||||
def _bash(self, **kwargs):
|
||||
config = BashToolConfig(**kwargs)
|
||||
return Bash(config=config, state=BaseToolState())
|
||||
return Bash(config_getter=lambda: config, state=BaseToolState())
|
||||
|
||||
def test_allowlisted_command_always(self):
|
||||
bash = self._bash()
|
||||
|
|
@ -264,7 +264,7 @@ class TestReadFileGranularPermissions:
|
|||
|
||||
def _read_file(self, **kwargs):
|
||||
config = ReadFileToolConfig(**kwargs)
|
||||
return ReadFile(config=config, state=ReadFileState())
|
||||
return ReadFile(config_getter=lambda: config, state=ReadFileState())
|
||||
|
||||
def test_in_workdir_normal_file_returns_none(self):
|
||||
(self.workdir / "test.py").touch()
|
||||
|
|
@ -346,7 +346,7 @@ class TestWriteFileGranularPermissions:
|
|||
|
||||
def _write_file(self):
|
||||
config = WriteFileConfig()
|
||||
return WriteFile(config=config, state=BaseToolState())
|
||||
return WriteFile(config_getter=lambda: config, state=BaseToolState())
|
||||
|
||||
def test_in_workdir_returns_none(self):
|
||||
tool = self._write_file()
|
||||
|
|
@ -379,7 +379,7 @@ class TestSearchReplaceGranularPermissions:
|
|||
|
||||
def test_outside_workdir_returns_permission_context(self):
|
||||
config = SearchReplaceConfig()
|
||||
tool = SearchReplace(config=config, state=BaseToolState())
|
||||
tool = SearchReplace(config_getter=lambda: config, state=BaseToolState())
|
||||
result = tool.resolve_permission(
|
||||
SearchReplaceArgs(file_path="/tmp/file.py", content="x")
|
||||
)
|
||||
|
|
@ -395,7 +395,7 @@ class TestGrepGranularPermissions:
|
|||
|
||||
def _grep(self):
|
||||
config = GrepToolConfig()
|
||||
return Grep(config=config, state=BaseToolState())
|
||||
return Grep(config_getter=lambda: config, state=BaseToolState())
|
||||
|
||||
def test_in_workdir_normal_path_returns_none(self):
|
||||
tool = self._grep()
|
||||
|
|
@ -442,7 +442,7 @@ class TestApprovalFlowSimulation:
|
|||
session_pattern="mkdir *",
|
||||
)
|
||||
]
|
||||
bash = Bash(config=BashToolConfig(), state=BaseToolState())
|
||||
bash = Bash(config_getter=lambda: BashToolConfig(), state=BaseToolState())
|
||||
result = bash.resolve_permission(BashArgs(command="mkdir another_dir"))
|
||||
assert isinstance(result, PermissionContext)
|
||||
uncovered = [
|
||||
|
|
@ -460,7 +460,7 @@ class TestApprovalFlowSimulation:
|
|||
session_pattern="mkdir *",
|
||||
)
|
||||
]
|
||||
bash = Bash(config=BashToolConfig(), state=BaseToolState())
|
||||
bash = Bash(config_getter=lambda: BashToolConfig(), state=BaseToolState())
|
||||
result = bash.resolve_permission(BashArgs(command="npm install"))
|
||||
assert isinstance(result, PermissionContext)
|
||||
uncovered = [
|
||||
|
|
@ -472,7 +472,7 @@ class TestApprovalFlowSimulation:
|
|||
assert uncovered[0].session_pattern == "npm install *"
|
||||
|
||||
def test_outside_dir_approved_covers_subsequent(self):
|
||||
bash = Bash(config=BashToolConfig(), state=BaseToolState())
|
||||
bash = Bash(config_getter=lambda: BashToolConfig(), state=BaseToolState())
|
||||
result = bash.resolve_permission(BashArgs(command="mkdir /tmp/newdir"))
|
||||
assert isinstance(result, PermissionContext)
|
||||
outside_rps = [
|
||||
|
|
@ -509,7 +509,7 @@ class TestApprovalFlowSimulation:
|
|||
session_pattern="rm *",
|
||||
)
|
||||
]
|
||||
bash = Bash(config=BashToolConfig(), state=BaseToolState())
|
||||
bash = Bash(config_getter=lambda: BashToolConfig(), state=BaseToolState())
|
||||
result = bash.resolve_permission(BashArgs(command="rm -rf /tmp/something"))
|
||||
assert isinstance(result, PermissionContext)
|
||||
cmd_perms = [
|
||||
|
|
@ -529,7 +529,7 @@ class TestApprovalFlowSimulation:
|
|||
session_pattern="sudo apt install foo",
|
||||
)
|
||||
]
|
||||
bash = Bash(config=BashToolConfig(), state=BaseToolState())
|
||||
bash = Bash(config_getter=lambda: BashToolConfig(), state=BaseToolState())
|
||||
result = bash.resolve_permission(BashArgs(command="sudo apt install bar"))
|
||||
assert isinstance(result, PermissionContext)
|
||||
cmd_perms = [
|
||||
|
|
@ -575,7 +575,7 @@ class TestApprovalFlowSimulation:
|
|||
|
||||
class TestWebFetchPermissions:
|
||||
def _make_webfetch(self) -> WebFetch:
|
||||
return WebFetch(config=WebFetchConfig(), state=BaseToolState())
|
||||
return WebFetch(config_getter=lambda: WebFetchConfig(), state=BaseToolState())
|
||||
|
||||
def test_returns_url_pattern_with_domain(self):
|
||||
wf = self._make_webfetch()
|
||||
|
|
@ -671,7 +671,7 @@ class TestWebFetchPermissions:
|
|||
|
||||
def test_config_permission_always_honored(self):
|
||||
wf = WebFetch(
|
||||
config=WebFetchConfig(permission=ToolPermission.ALWAYS),
|
||||
config_getter=lambda: WebFetchConfig(permission=ToolPermission.ALWAYS),
|
||||
state=BaseToolState(),
|
||||
)
|
||||
result = wf.resolve_permission(WebFetchArgs(url="https://example.com"))
|
||||
|
|
@ -680,7 +680,7 @@ class TestWebFetchPermissions:
|
|||
|
||||
def test_config_permission_never_honored(self):
|
||||
wf = WebFetch(
|
||||
config=WebFetchConfig(permission=ToolPermission.NEVER),
|
||||
config_getter=lambda: WebFetchConfig(permission=ToolPermission.NEVER),
|
||||
state=BaseToolState(),
|
||||
)
|
||||
result = wf.resolve_permission(WebFetchArgs(url="https://example.com"))
|
||||
|
|
@ -689,7 +689,8 @@ class TestWebFetchPermissions:
|
|||
|
||||
def test_config_permission_ask_falls_through_to_domain(self):
|
||||
wf = WebFetch(
|
||||
config=WebFetchConfig(permission=ToolPermission.ASK), state=BaseToolState()
|
||||
config_getter=lambda: WebFetchConfig(permission=ToolPermission.ASK),
|
||||
state=BaseToolState(),
|
||||
)
|
||||
result = wf.resolve_permission(WebFetchArgs(url="https://example.com"))
|
||||
assert isinstance(result, PermissionContext)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from vibe.core.tools.builtins.grep import Grep, GrepArgs, GrepBackend, GrepToolC
|
|||
def grep(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
config = GrepToolConfig()
|
||||
return Grep(config=config, state=BaseToolState())
|
||||
return Grep(config_getter=lambda: config, state=BaseToolState())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -28,7 +28,7 @@ def grep_gnu_only(tmp_path, monkeypatch):
|
|||
|
||||
monkeypatch.setattr("shutil.which", mock_which)
|
||||
config = GrepToolConfig()
|
||||
return Grep(config=config, state=BaseToolState())
|
||||
return Grep(config_getter=lambda: config, state=BaseToolState())
|
||||
|
||||
|
||||
def test_detects_ripgrep_when_available(grep):
|
||||
|
|
@ -137,7 +137,7 @@ async def test_truncates_to_max_matches(grep, tmp_path):
|
|||
async def test_truncates_to_max_output_bytes(grep, tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
config = GrepToolConfig(max_output_bytes=100)
|
||||
grep_tool = Grep(config=config, state=BaseToolState())
|
||||
grep_tool = Grep(config_getter=lambda: config, state=BaseToolState())
|
||||
(tmp_path / "test.py").write_text("\n".join("x" * 100 for _ in range(10)))
|
||||
|
||||
result = await collect_result(grep_tool.run(GrepArgs(pattern="x")))
|
||||
|
|
@ -189,7 +189,7 @@ async def test_ignores_comments_in_vibeignore(grep, tmp_path):
|
|||
async def test_uses_effective_workdir(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
config = GrepToolConfig()
|
||||
grep_tool = Grep(config=config, state=BaseToolState())
|
||||
grep_tool = Grep(config_getter=lambda: config, state=BaseToolState())
|
||||
(tmp_path / "test.py").write_text("match\n")
|
||||
|
||||
result = await collect_result(grep_tool.run(GrepArgs(pattern="match", path=".")))
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class SimpleTool(BaseTool[SimpleArgs, SimpleResult, BaseToolConfig, BaseToolStat
|
|||
|
||||
@pytest.fixture
|
||||
def simple_tool() -> SimpleTool:
|
||||
return SimpleTool(config=BaseToolConfig(), state=BaseToolState())
|
||||
return SimpleTool(config_getter=lambda: BaseToolConfig(), state=BaseToolState())
|
||||
|
||||
|
||||
class TestInvokeContext:
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ def _make_ctx(skill_manager: SkillManager | None = None) -> InvokeContext:
|
|||
|
||||
@pytest.fixture
|
||||
def skill_tool() -> Skill:
|
||||
return Skill(config=SkillToolConfig(), state=BaseToolState())
|
||||
return Skill(config_getter=lambda: SkillToolConfig(), state=BaseToolState())
|
||||
|
||||
|
||||
class TestSkillRun:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from vibe.core.types import AssistantEvent, LLMMessage, Role
|
|||
|
||||
@pytest.fixture
|
||||
def task_tool() -> Task:
|
||||
return Task(config=TaskToolConfig(), state=BaseToolState())
|
||||
return Task(config_getter=lambda: TaskToolConfig(), state=BaseToolState())
|
||||
|
||||
|
||||
class TestTaskArgs:
|
||||
|
|
@ -91,7 +91,7 @@ class TestTaskToolResolvePermission:
|
|||
|
||||
def test_denylist_takes_precedence(self) -> None:
|
||||
config = TaskToolConfig(allowlist=["explore"], denylist=["explore"])
|
||||
tool = Task(config=config, state=BaseToolState())
|
||||
tool = Task(config_getter=lambda: config, state=BaseToolState())
|
||||
args = TaskArgs(task="do something", agent="explore")
|
||||
result = tool.resolve_permission(args)
|
||||
assert isinstance(result, PermissionContext)
|
||||
|
|
@ -99,7 +99,7 @@ class TestTaskToolResolvePermission:
|
|||
|
||||
def test_glob_pattern_in_allowlist(self) -> None:
|
||||
config = TaskToolConfig(allowlist=["exp*"])
|
||||
tool = Task(config=config, state=BaseToolState())
|
||||
tool = Task(config_getter=lambda: config, state=BaseToolState())
|
||||
args = TaskArgs(task="do something", agent="explore")
|
||||
result = tool.resolve_permission(args)
|
||||
assert isinstance(result, PermissionContext)
|
||||
|
|
@ -107,7 +107,7 @@ class TestTaskToolResolvePermission:
|
|||
|
||||
def test_glob_pattern_in_denylist(self) -> None:
|
||||
config = TaskToolConfig(denylist=["danger*"])
|
||||
tool = Task(config=config, state=BaseToolState())
|
||||
tool = Task(config_getter=lambda: config, state=BaseToolState())
|
||||
args = TaskArgs(task="do something", agent="dangerous_agent")
|
||||
result = tool.resolve_permission(args)
|
||||
assert isinstance(result, PermissionContext)
|
||||
|
|
@ -115,7 +115,7 @@ class TestTaskToolResolvePermission:
|
|||
|
||||
def test_empty_lists_returns_none(self) -> None:
|
||||
config = TaskToolConfig(allowlist=[], denylist=[])
|
||||
tool = Task(config=config, state=BaseToolState())
|
||||
tool = Task(config_getter=lambda: config, state=BaseToolState())
|
||||
args = TaskArgs(task="do something", agent="explore")
|
||||
result = tool.resolve_permission(args)
|
||||
assert result is None
|
||||
|
|
|
|||
|
|
@ -5,9 +5,13 @@ import time
|
|||
import pytest
|
||||
from textual.widgets import Static
|
||||
|
||||
from tests.conftest import build_test_agent_loop, build_test_vibe_app
|
||||
from tests.mock.utils import mock_llm_chunk
|
||||
from tests.stubs.fake_backend import FakeBackend
|
||||
from vibe.cli.textual_ui.app import VibeApp
|
||||
from vibe.cli.textual_ui.widgets.chat_input.container import ChatInputContainer
|
||||
from vibe.cli.textual_ui.widgets.messages import BashOutputMessage, ErrorMessage
|
||||
from vibe.core.types import Role
|
||||
|
||||
|
||||
async def _wait_for_bash_output_message(
|
||||
|
|
@ -118,3 +122,38 @@ async def test_ui_handles_non_utf8_stderr(vibe_app: VibeApp) -> None:
|
|||
output_widget = message.query_one(".bash-output", Static)
|
||||
assert str(output_widget.render()) == "<EFBFBD><EFBFBD>"
|
||||
assert_no_command_error(vibe_app)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_sends_manual_command_output_to_next_agent_turn() -> None:
|
||||
backend = FakeBackend(mock_llm_chunk(content="I saw it."))
|
||||
vibe_app = build_test_vibe_app(agent_loop=build_test_agent_loop(backend=backend))
|
||||
|
||||
async with vibe_app.run_test() as pilot:
|
||||
chat_input = vibe_app.query_one(ChatInputContainer)
|
||||
chat_input.value = "!echo hello"
|
||||
|
||||
await pilot.press("enter")
|
||||
await _wait_for_bash_output_message(vibe_app, pilot)
|
||||
|
||||
injected_message = vibe_app.agent_loop.messages[-1]
|
||||
assert injected_message.role == Role.user
|
||||
assert injected_message.injected is True
|
||||
assert injected_message.content is not None
|
||||
assert "Manual `!` command result from the user." in injected_message.content
|
||||
assert "Command: `echo hello`" in injected_message.content
|
||||
assert "Exit code: 0" in injected_message.content
|
||||
assert "Stdout:\n```text\nhello\n```" in injected_message.content
|
||||
|
||||
chat_input.value = "what did the command print?"
|
||||
await pilot.press("enter")
|
||||
await pilot.app.workers.wait_for_complete()
|
||||
|
||||
assert len(backend.requests_messages) == 1
|
||||
user_messages = [
|
||||
msg for msg in backend.requests_messages[0] if msg.role == Role.user
|
||||
]
|
||||
assert len(user_messages) >= 2
|
||||
assert user_messages[-2].content == injected_message.content
|
||||
assert user_messages[-2].injected is True
|
||||
assert user_messages[-1].content == "what did the command print?"
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ from vibe.core.tools.builtins.webfetch import WebFetch, WebFetchArgs, WebFetchCo
|
|||
@pytest.fixture
|
||||
def webfetch():
|
||||
config = WebFetchConfig()
|
||||
return WebFetch(config=config, state=BaseToolState())
|
||||
return WebFetch(config_getter=lambda: config, state=BaseToolState())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def webfetch_small():
|
||||
config = WebFetchConfig(max_content_bytes=100)
|
||||
return WebFetch(config=config, state=BaseToolState())
|
||||
return WebFetch(config_getter=lambda: config, state=BaseToolState())
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -32,6 +32,7 @@ async def test_bare_domain_gets_https(webfetch):
|
|||
result = await collect_result(webfetch.run(WebFetchArgs(url="example.com")))
|
||||
assert result.url == "https://example.com"
|
||||
assert result.content == "ok"
|
||||
assert result.was_truncated is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -167,6 +168,7 @@ async def test_truncates_to_max_bytes_with_disclaimer(webfetch_small):
|
|||
)
|
||||
assert result.content.startswith("a" * 100)
|
||||
assert "[Content truncated due to size limit]" in result.content
|
||||
assert result.was_truncated is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -189,6 +191,7 @@ async def test_truncates_html_with_disclaimer(webfetch_small):
|
|||
assert "## first title" in result.content
|
||||
assert "## second title" not in result.content
|
||||
assert "[Content truncated due to size limit]" in result.content
|
||||
assert result.was_truncated is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ def _make_response(
|
|||
def websearch(monkeypatch):
|
||||
monkeypatch.setenv("MISTRAL_API_KEY", "test-key")
|
||||
config = WebSearchConfig()
|
||||
return WebSearch(config=config, state=BaseToolState())
|
||||
return WebSearch(config_getter=lambda: config, state=BaseToolState())
|
||||
|
||||
|
||||
def test_parse_text_chunks(websearch):
|
||||
|
|
@ -104,7 +104,7 @@ def test_parse_skips_non_message_entries(websearch):
|
|||
async def test_run_missing_api_key(monkeypatch):
|
||||
monkeypatch.delenv("MISTRAL_API_KEY", raising=False)
|
||||
config = WebSearchConfig()
|
||||
ws = WebSearch(config=config, state=BaseToolState())
|
||||
ws = WebSearch(config_getter=lambda: config, state=BaseToolState())
|
||||
with pytest.raises(ToolError, match="MISTRAL_API_KEY"):
|
||||
await collect_result(ws.run(WebSearchArgs(query="test")))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue