vibecore 0.2.0__py3-none-any.whl → 0.3.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.
- vibecore/agents/default.py +6 -11
- vibecore/agents/{task_agent.py → task.py} +2 -6
- vibecore/auth/__init__.py +15 -0
- vibecore/auth/config.py +38 -0
- vibecore/auth/interceptor.py +141 -0
- vibecore/auth/manager.py +173 -0
- vibecore/auth/models.py +54 -0
- vibecore/auth/oauth_flow.py +129 -0
- vibecore/auth/pkce.py +29 -0
- vibecore/auth/storage.py +111 -0
- vibecore/auth/token_manager.py +131 -0
- vibecore/cli.py +98 -9
- vibecore/flow.py +105 -0
- vibecore/handlers/stream_handler.py +11 -0
- vibecore/main.py +28 -6
- vibecore/models/anthropic_auth.py +226 -0
- vibecore/settings.py +61 -5
- vibecore/tools/task/executor.py +1 -1
- vibecore/tools/webfetch/__init__.py +7 -0
- vibecore/tools/webfetch/executor.py +127 -0
- vibecore/tools/webfetch/models.py +22 -0
- vibecore/tools/webfetch/tools.py +46 -0
- vibecore/tools/websearch/__init__.py +5 -0
- vibecore/tools/websearch/base.py +27 -0
- vibecore/tools/websearch/ddgs/__init__.py +5 -0
- vibecore/tools/websearch/ddgs/backend.py +64 -0
- vibecore/tools/websearch/executor.py +43 -0
- vibecore/tools/websearch/models.py +20 -0
- vibecore/tools/websearch/tools.py +49 -0
- vibecore/widgets/tool_message_factory.py +24 -0
- vibecore/widgets/tool_messages.py +219 -0
- vibecore/widgets/tool_messages.tcss +94 -0
- {vibecore-0.2.0.dist-info → vibecore-0.3.0.dist-info}/METADATA +107 -1
- {vibecore-0.2.0.dist-info → vibecore-0.3.0.dist-info}/RECORD +37 -15
- vibecore-0.3.0.dist-info/entry_points.txt +2 -0
- vibecore-0.2.0.dist-info/entry_points.txt +0 -2
- {vibecore-0.2.0.dist-info → vibecore-0.3.0.dist-info}/WHEEL +0 -0
- {vibecore-0.2.0.dist-info → vibecore-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Websearch execution logic with backend selection."""
|
|
2
|
+
|
|
3
|
+
from .base import WebSearchBackend
|
|
4
|
+
from .ddgs import DDGSBackend
|
|
5
|
+
from .models import SearchParams
|
|
6
|
+
|
|
7
|
+
# Default backend
|
|
8
|
+
_default_backend: WebSearchBackend = DDGSBackend()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def set_default_backend(backend: WebSearchBackend) -> None:
|
|
12
|
+
"""Set the default search backend.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
backend: The backend to use for searches
|
|
16
|
+
"""
|
|
17
|
+
global _default_backend
|
|
18
|
+
_default_backend = backend
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_default_backend() -> WebSearchBackend:
|
|
22
|
+
"""Get the current default search backend.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
The current default backend
|
|
26
|
+
"""
|
|
27
|
+
return _default_backend
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def perform_websearch(params: SearchParams, backend: WebSearchBackend | None = None) -> str:
|
|
31
|
+
"""Perform a web search using the specified or default backend.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
params: Search parameters
|
|
35
|
+
backend: Optional specific backend to use (defaults to default backend)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
JSON string containing search results or error message
|
|
39
|
+
"""
|
|
40
|
+
if backend is None:
|
|
41
|
+
backend = _default_backend
|
|
42
|
+
|
|
43
|
+
return await backend.search(params)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Models for websearch tool."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SearchResult(BaseModel):
|
|
7
|
+
"""A single search result."""
|
|
8
|
+
|
|
9
|
+
title: str = Field(description="Title of the search result")
|
|
10
|
+
href: str = Field(description="URL of the search result")
|
|
11
|
+
body: str = Field(description="Description/snippet of the result")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SearchParams(BaseModel):
|
|
15
|
+
"""Parameters for web search."""
|
|
16
|
+
|
|
17
|
+
query: str = Field(description="Search query")
|
|
18
|
+
max_results: int = Field(default=5, description="Maximum number of results to return")
|
|
19
|
+
region: str | None = Field(default=None, description="Region code (e.g., 'us-en')")
|
|
20
|
+
safesearch: str = Field(default="moderate", description="SafeSearch setting: 'on', 'moderate', or 'off'")
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Websearch tool for Vibecore agents."""
|
|
2
|
+
|
|
3
|
+
from agents import RunContextWrapper, function_tool
|
|
4
|
+
|
|
5
|
+
from vibecore.context import VibecoreContext
|
|
6
|
+
|
|
7
|
+
from .executor import perform_websearch
|
|
8
|
+
from .models import SearchParams
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@function_tool
|
|
12
|
+
async def websearch(
|
|
13
|
+
ctx: RunContextWrapper[VibecoreContext],
|
|
14
|
+
query: str,
|
|
15
|
+
max_results: int = 5,
|
|
16
|
+
region: str | None = None,
|
|
17
|
+
safesearch: str = "moderate",
|
|
18
|
+
) -> str:
|
|
19
|
+
"""Search the web for information using DuckDuckGo.
|
|
20
|
+
|
|
21
|
+
This tool allows you to search the web for current information, news, and general knowledge.
|
|
22
|
+
It supports advanced search operators like quotes for exact phrases, minus for exclusions,
|
|
23
|
+
site: for specific domains, and filetype: for specific file types.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
ctx: The run context wrapper
|
|
27
|
+
query: The search query (supports advanced operators like "exact phrase", -exclude, site:example.com)
|
|
28
|
+
max_results: Maximum number of results to return (default: 5)
|
|
29
|
+
region: Optional region code for localized results (e.g., 'us-en' for US English)
|
|
30
|
+
safesearch: SafeSearch filter level ('on', 'moderate', or 'off', default: 'moderate')
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
JSON string containing search results with title, URL, and snippet for each result
|
|
34
|
+
|
|
35
|
+
Examples:
|
|
36
|
+
- Basic search: query="python programming"
|
|
37
|
+
- Exact phrase: query='"machine learning algorithms"'
|
|
38
|
+
- Exclude terms: query="python -javascript"
|
|
39
|
+
- Site-specific: query="AI site:github.com"
|
|
40
|
+
- File type: query="climate change filetype:pdf"
|
|
41
|
+
"""
|
|
42
|
+
params = SearchParams(
|
|
43
|
+
query=query,
|
|
44
|
+
max_results=max_results,
|
|
45
|
+
region=region,
|
|
46
|
+
safesearch=safesearch,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return await perform_websearch(params)
|
|
@@ -9,15 +9,19 @@ import contextlib
|
|
|
9
9
|
import json
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
+
from vibecore.settings import settings
|
|
12
13
|
from vibecore.widgets.messages import MessageStatus
|
|
13
14
|
from vibecore.widgets.tool_messages import (
|
|
14
15
|
BaseToolMessage,
|
|
15
16
|
MCPToolMessage,
|
|
16
17
|
PythonToolMessage,
|
|
17
18
|
ReadToolMessage,
|
|
19
|
+
RichToolMessage,
|
|
18
20
|
TaskToolMessage,
|
|
19
21
|
TodoWriteToolMessage,
|
|
20
22
|
ToolMessage,
|
|
23
|
+
WebFetchToolMessage,
|
|
24
|
+
WebSearchToolMessage,
|
|
21
25
|
WriteToolMessage,
|
|
22
26
|
)
|
|
23
27
|
|
|
@@ -113,6 +117,26 @@ def create_tool_message(
|
|
|
113
117
|
else:
|
|
114
118
|
return WriteToolMessage(file_path=file_path, content=content, status=status)
|
|
115
119
|
|
|
120
|
+
elif tool_name == "websearch":
|
|
121
|
+
query = args_dict.get("query", "") if args_dict else ""
|
|
122
|
+
if output is not None:
|
|
123
|
+
return WebSearchToolMessage(query=query, output=output, status=status)
|
|
124
|
+
else:
|
|
125
|
+
return WebSearchToolMessage(query=query, status=status)
|
|
126
|
+
|
|
127
|
+
elif tool_name == "webfetch":
|
|
128
|
+
url = args_dict.get("url", "") if args_dict else ""
|
|
129
|
+
if output is not None:
|
|
130
|
+
return WebFetchToolMessage(url=url, output=output, status=status)
|
|
131
|
+
else:
|
|
132
|
+
return WebFetchToolMessage(url=url, status=status)
|
|
133
|
+
|
|
134
|
+
elif tool_name in settings.rich_tool_names:
|
|
135
|
+
if output is not None:
|
|
136
|
+
return RichToolMessage(tool_name=tool_name, arguments=arguments, output=output, status=status)
|
|
137
|
+
else:
|
|
138
|
+
return RichToolMessage(tool_name=tool_name, arguments=arguments, status=status)
|
|
139
|
+
|
|
116
140
|
# Default to generic ToolMessage for all other tools
|
|
117
141
|
else:
|
|
118
142
|
if output is not None:
|
|
@@ -481,3 +481,222 @@ class MCPToolMessage(BaseToolMessage):
|
|
|
481
481
|
yield ExpandableMarkdown(
|
|
482
482
|
processed_output, language="", truncated_lines=5, classes="mcp-output-markdown"
|
|
483
483
|
)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
class RichToolMessage(BaseToolMessage):
|
|
487
|
+
"""A widget to display rich (json/markdown) custom tool execution messages."""
|
|
488
|
+
|
|
489
|
+
tool_name: reactive[str] = reactive("")
|
|
490
|
+
arguments: reactive[str] = reactive("")
|
|
491
|
+
|
|
492
|
+
def __init__(
|
|
493
|
+
self,
|
|
494
|
+
tool_name: str,
|
|
495
|
+
arguments: str,
|
|
496
|
+
output: str = "",
|
|
497
|
+
status: MessageStatus = MessageStatus.EXECUTING,
|
|
498
|
+
**kwargs,
|
|
499
|
+
) -> None:
|
|
500
|
+
"""
|
|
501
|
+
Construct an RichToolMessage.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
tool_name: The name of the tool being called.
|
|
505
|
+
arguments: JSON string of tool arguments.
|
|
506
|
+
output: The output from the tool (optional, can be set later).
|
|
507
|
+
status: The status of execution.
|
|
508
|
+
**kwargs: Additional keyword arguments for Widget.
|
|
509
|
+
"""
|
|
510
|
+
super().__init__(status=status, **kwargs)
|
|
511
|
+
self.tool_name = tool_name
|
|
512
|
+
self.arguments = arguments
|
|
513
|
+
self.output = output
|
|
514
|
+
|
|
515
|
+
def _prettify_json_output(self, output: str) -> tuple[bool, str]:
|
|
516
|
+
"""Try to prettify JSON output.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
output: The raw output string.
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
A tuple of (is_json, formatted_output).
|
|
523
|
+
"""
|
|
524
|
+
if not output or not output.strip():
|
|
525
|
+
return False, output
|
|
526
|
+
|
|
527
|
+
try:
|
|
528
|
+
# Try to parse as JSON
|
|
529
|
+
json_obj = json.loads(output)
|
|
530
|
+
# Pretty print with 2-space indentation
|
|
531
|
+
formatted = json.dumps(json_obj, indent=2, ensure_ascii=False)
|
|
532
|
+
return True, formatted
|
|
533
|
+
except (json.JSONDecodeError, TypeError, ValueError):
|
|
534
|
+
# Not valid JSON, return as-is
|
|
535
|
+
return False, output
|
|
536
|
+
|
|
537
|
+
def compose(self) -> ComposeResult:
|
|
538
|
+
"""Create child widgets for the rich tool message."""
|
|
539
|
+
# Header line showing MCP server and tool
|
|
540
|
+
# Access the actual values, not the reactive descriptors
|
|
541
|
+
yield MessageHeader("⏺", self.tool_name, status=self.status)
|
|
542
|
+
|
|
543
|
+
# Arguments display (if any)
|
|
544
|
+
if self.arguments and self.arguments != "{}":
|
|
545
|
+
with Horizontal(classes="rich-arguments"):
|
|
546
|
+
yield Static("└─", classes="rich-arguments-prefix")
|
|
547
|
+
with Vertical(classes="rich-arguments-content"):
|
|
548
|
+
# Truncate arguments if too long
|
|
549
|
+
max_args_length = 100
|
|
550
|
+
arg_dict = json.loads(self.arguments)
|
|
551
|
+
SQL_QUERY_KEY = "query"
|
|
552
|
+
if SQL_QUERY_KEY in arg_dict:
|
|
553
|
+
# XXX(serialx): Special case for SQL queries
|
|
554
|
+
query = arg_dict[SQL_QUERY_KEY]
|
|
555
|
+
yield ExpandableMarkdown(query, language="sql", truncated_lines=8, classes="rich-output-sql")
|
|
556
|
+
del arg_dict[SQL_QUERY_KEY]
|
|
557
|
+
|
|
558
|
+
display_args = arg_dict[:max_args_length] + "…" if len(arg_dict) > max_args_length else arg_dict
|
|
559
|
+
yield Static(f"Args: {display_args}", classes="rich-arguments-text")
|
|
560
|
+
|
|
561
|
+
# Output - check if it's JSON and prettify if so
|
|
562
|
+
if self.output:
|
|
563
|
+
try:
|
|
564
|
+
json_output = json.loads(self.output)
|
|
565
|
+
except json.JSONDecodeError:
|
|
566
|
+
json_output = None
|
|
567
|
+
if json_output:
|
|
568
|
+
assert json_output.get("type") == "text", "Expected JSON output type to be 'text'"
|
|
569
|
+
is_json, processed_output = self._prettify_json_output(json_output.get("text", ""))
|
|
570
|
+
else:
|
|
571
|
+
# output should always be a JSON string, but if not, treat it as plain text
|
|
572
|
+
is_json, processed_output = False, self.output
|
|
573
|
+
with Horizontal(classes="tool-output"):
|
|
574
|
+
yield Static("└─", classes="tool-output-prefix")
|
|
575
|
+
with Vertical(classes="tool-output-content"):
|
|
576
|
+
if is_json:
|
|
577
|
+
# Use ExpandableMarkdown for JSON with syntax highlighting
|
|
578
|
+
yield ExpandableMarkdown(
|
|
579
|
+
processed_output, language="json", truncated_lines=8, classes="rich-output-json"
|
|
580
|
+
)
|
|
581
|
+
else:
|
|
582
|
+
# Use ExpandableMarkdown for non-JSON content (renders as markdown without code block)
|
|
583
|
+
yield ExpandableMarkdown(
|
|
584
|
+
processed_output, language="", truncated_lines=5, classes="rich-output-markdown"
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
class WebSearchToolMessage(BaseToolMessage):
|
|
589
|
+
"""A widget to display web search results."""
|
|
590
|
+
|
|
591
|
+
search_query: reactive[str] = reactive("")
|
|
592
|
+
|
|
593
|
+
def __init__(self, query: str, output: str = "", status: MessageStatus = MessageStatus.EXECUTING, **kwargs) -> None:
|
|
594
|
+
"""
|
|
595
|
+
Construct a WebSearchToolMessage.
|
|
596
|
+
|
|
597
|
+
Args:
|
|
598
|
+
query: The search query.
|
|
599
|
+
output: The search results as JSON string (optional, can be set later).
|
|
600
|
+
status: The status of execution.
|
|
601
|
+
**kwargs: Additional keyword arguments for Widget.
|
|
602
|
+
"""
|
|
603
|
+
super().__init__(status=status, **kwargs)
|
|
604
|
+
self.search_query = query
|
|
605
|
+
self.output = output
|
|
606
|
+
|
|
607
|
+
def compose(self) -> ComposeResult:
|
|
608
|
+
"""Create child widgets for the search message."""
|
|
609
|
+
# Header line
|
|
610
|
+
header = f"WebSearch({self.search_query})"
|
|
611
|
+
yield MessageHeader("⏺", header, status=self.status)
|
|
612
|
+
|
|
613
|
+
# Process and display search results
|
|
614
|
+
if self.output:
|
|
615
|
+
try:
|
|
616
|
+
result_data = json.loads(self.output)
|
|
617
|
+
if result_data.get("success") and result_data.get("results"):
|
|
618
|
+
with Horizontal(classes="tool-output"):
|
|
619
|
+
yield Static("└─", classes="tool-output-prefix")
|
|
620
|
+
with Vertical(classes="tool-output-content"):
|
|
621
|
+
# Format results as markdown
|
|
622
|
+
markdown_results = []
|
|
623
|
+
for i, result in enumerate(result_data["results"], 1):
|
|
624
|
+
title = result.get("title", "No title")
|
|
625
|
+
href = result.get("href", "")
|
|
626
|
+
body = result.get("body", "")
|
|
627
|
+
|
|
628
|
+
# Format each result
|
|
629
|
+
result_md = f"**{i}. [{title}]({href})**"
|
|
630
|
+
if body:
|
|
631
|
+
# Truncate body if too long
|
|
632
|
+
max_body_length = 200
|
|
633
|
+
if len(body) > max_body_length:
|
|
634
|
+
body = body[:max_body_length] + "..."
|
|
635
|
+
result_md += f"\n {body}"
|
|
636
|
+
if href:
|
|
637
|
+
result_md += f"\n 🔗 {href}"
|
|
638
|
+
|
|
639
|
+
markdown_results.append(result_md)
|
|
640
|
+
|
|
641
|
+
# Join all results with spacing
|
|
642
|
+
all_results = "\n\n".join(markdown_results)
|
|
643
|
+
|
|
644
|
+
# Add result count message
|
|
645
|
+
count_msg = result_data.get("message", "")
|
|
646
|
+
if count_msg:
|
|
647
|
+
all_results = f"_{count_msg}_\n\n{all_results}"
|
|
648
|
+
|
|
649
|
+
yield ExpandableMarkdown(
|
|
650
|
+
all_results, language="", truncated_lines=10, classes="websearch-results"
|
|
651
|
+
)
|
|
652
|
+
else:
|
|
653
|
+
# No results or error
|
|
654
|
+
with Horizontal(classes="tool-output"):
|
|
655
|
+
yield Static("└─", classes="tool-output-prefix")
|
|
656
|
+
with Vertical(classes="tool-output-content"):
|
|
657
|
+
message = result_data.get("message", "No results found")
|
|
658
|
+
yield Static(message, classes="websearch-no-results")
|
|
659
|
+
except (json.JSONDecodeError, KeyError, TypeError):
|
|
660
|
+
# Fallback to raw output if JSON parsing fails
|
|
661
|
+
yield from self._render_output(self.output, truncated_lines=5)
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
class WebFetchToolMessage(BaseToolMessage):
|
|
665
|
+
"""A widget to display fetched web content."""
|
|
666
|
+
|
|
667
|
+
fetch_url: reactive[str] = reactive("")
|
|
668
|
+
|
|
669
|
+
def __init__(self, url: str, output: str = "", status: MessageStatus = MessageStatus.EXECUTING, **kwargs) -> None:
|
|
670
|
+
"""
|
|
671
|
+
Construct a WebFetchToolMessage.
|
|
672
|
+
|
|
673
|
+
Args:
|
|
674
|
+
url: The URL that was fetched.
|
|
675
|
+
output: The fetched content as Markdown (optional, can be set later).
|
|
676
|
+
status: The status of execution.
|
|
677
|
+
**kwargs: Additional keyword arguments for Widget.
|
|
678
|
+
"""
|
|
679
|
+
super().__init__(status=status, **kwargs)
|
|
680
|
+
self.fetch_url = url
|
|
681
|
+
self.output = output
|
|
682
|
+
|
|
683
|
+
def compose(self) -> ComposeResult:
|
|
684
|
+
"""Create child widgets for the fetch message."""
|
|
685
|
+
# Header line
|
|
686
|
+
header = f"WebFetch({self.fetch_url})"
|
|
687
|
+
yield MessageHeader("⏺", header, status=self.status)
|
|
688
|
+
|
|
689
|
+
# Display fetched content
|
|
690
|
+
if self.output:
|
|
691
|
+
with Horizontal(classes="tool-output"):
|
|
692
|
+
yield Static("└─", classes="tool-output-prefix")
|
|
693
|
+
with Vertical(classes="tool-output-content"):
|
|
694
|
+
# Check if it's an error message
|
|
695
|
+
if self.output.startswith("Error:"):
|
|
696
|
+
yield Static(self.output, classes="webfetch-error")
|
|
697
|
+
else:
|
|
698
|
+
# Display as expandable markdown content
|
|
699
|
+
# Default to showing first 15 lines since web content can be long
|
|
700
|
+
yield ExpandableMarkdown(
|
|
701
|
+
self.output, language="", truncated_lines=15, classes="webfetch-content"
|
|
702
|
+
)
|
|
@@ -286,4 +286,98 @@ MCPToolMessage {
|
|
|
286
286
|
width: 1fr;
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
RichToolMessage {
|
|
292
|
+
Horizontal.rich-arguments {
|
|
293
|
+
height: auto;
|
|
294
|
+
|
|
295
|
+
&> .rich-arguments-prefix {
|
|
296
|
+
height: 1;
|
|
297
|
+
width: 5;
|
|
298
|
+
padding-left: 2;
|
|
299
|
+
padding-right: 1;
|
|
300
|
+
color: $text-muted;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
&> Vertical.rich-arguments-content {
|
|
304
|
+
height: auto;
|
|
305
|
+
width: 1fr;
|
|
306
|
+
|
|
307
|
+
.rich-arguments-label {
|
|
308
|
+
color: $text-muted;
|
|
309
|
+
# margin-bottom: 1;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.rich-arguments-text {
|
|
313
|
+
color: $text-muted;
|
|
314
|
+
text-overflow: ellipsis;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
Horizontal.tool-output {
|
|
320
|
+
height: auto;
|
|
321
|
+
|
|
322
|
+
&> .tool-output-prefix {
|
|
323
|
+
height: 1;
|
|
324
|
+
width: 5;
|
|
325
|
+
padding-left: 2;
|
|
326
|
+
padding-right: 1;
|
|
327
|
+
color: $text-muted;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
&> Vertical.tool-output-content {
|
|
331
|
+
height: auto;
|
|
332
|
+
width: 1fr;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
WebSearchToolMessage {
|
|
338
|
+
Horizontal.tool-output {
|
|
339
|
+
height: auto;
|
|
340
|
+
|
|
341
|
+
&> .tool-output-prefix {
|
|
342
|
+
height: 1;
|
|
343
|
+
width: 5;
|
|
344
|
+
padding-left: 2;
|
|
345
|
+
padding-right: 1;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
&> Vertical.tool-output-content {
|
|
349
|
+
height: auto;
|
|
350
|
+
|
|
351
|
+
&> .tool-output-content {
|
|
352
|
+
# color: $text-muted;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
&> .tool-output-content-more {
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
WebFetchToolMessage {
|
|
362
|
+
Horizontal.tool-output {
|
|
363
|
+
height: auto;
|
|
364
|
+
|
|
365
|
+
&> .tool-output-prefix {
|
|
366
|
+
height: 1;
|
|
367
|
+
width: 5;
|
|
368
|
+
padding-left: 2;
|
|
369
|
+
padding-right: 1;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
&> Vertical.tool-output-content {
|
|
373
|
+
height: auto;
|
|
374
|
+
|
|
375
|
+
&> .tool-output-content {
|
|
376
|
+
# color: $text-muted;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
&> .tool-output-content-more {
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
289
383
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vibecore
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Build your own AI-powered automation tools in the terminal with this extensible agent framework
|
|
5
5
|
Project-URL: Homepage, https://github.com/serialx/vibecore
|
|
6
6
|
Project-URL: Repository, https://github.com/serialx/vibecore
|
|
@@ -27,6 +27,9 @@ Classifier: Topic :: Terminals
|
|
|
27
27
|
Classifier: Topic :: Text Processing :: Linguistic
|
|
28
28
|
Classifier: Typing :: Typed
|
|
29
29
|
Requires-Python: >=3.11
|
|
30
|
+
Requires-Dist: ddgs>=9.5.4
|
|
31
|
+
Requires-Dist: html2text>=2024.2.26
|
|
32
|
+
Requires-Dist: httpx>=0.24.0
|
|
30
33
|
Requires-Dist: litellm>=1.72.4
|
|
31
34
|
Requires-Dist: openai-agents[litellm]>=0.2.2
|
|
32
35
|
Requires-Dist: pydantic-settings>=2.10.1
|
|
@@ -65,6 +68,7 @@ Built on [Textual](https://textual.textualize.io/) and the [OpenAI Agents SDK](h
|
|
|
65
68
|
|
|
66
69
|
### Key Features
|
|
67
70
|
|
|
71
|
+
- **Flow Mode (Experimental)** - Build structured agent-based applications with programmatic conversation control
|
|
68
72
|
- **AI-Powered Chat Interface** - Interact with state-of-the-art language models through an intuitive terminal interface
|
|
69
73
|
- **Rich Tool Integration** - Built-in tools for file operations, shell commands, Python execution, and task management
|
|
70
74
|
- **MCP Support** - Connect to external tools and services via Model Context Protocol servers
|
|
@@ -79,6 +83,26 @@ Built on [Textual](https://textual.textualize.io/) and the [OpenAI Agents SDK](h
|
|
|
79
83
|
### Prerequisites
|
|
80
84
|
|
|
81
85
|
- Python 3.11 or higher
|
|
86
|
+
- (Optional) [uv](https://docs.astral.sh/uv/) for quick testing and better package management
|
|
87
|
+
|
|
88
|
+
### Quick Test (No Installation)
|
|
89
|
+
|
|
90
|
+
Try vibecore instantly without installing it:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Install uv if you don't have it (optional)
|
|
94
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
95
|
+
|
|
96
|
+
# Configure your API key
|
|
97
|
+
export ANTHROPIC_API_KEY="your-api-key-here"
|
|
98
|
+
# or
|
|
99
|
+
export OPENAI_API_KEY="your-api-key-here"
|
|
100
|
+
|
|
101
|
+
# Run vibecore directly with uvx
|
|
102
|
+
uvx vibecore
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
This will download and run vibecore in an isolated environment without affecting your system Python installation.
|
|
82
106
|
|
|
83
107
|
### Install from PyPI
|
|
84
108
|
|
|
@@ -136,6 +160,88 @@ Once vibecore is running, you can:
|
|
|
136
160
|
- `/help` - Show help and keyboard shortcuts
|
|
137
161
|
- `/clear` - Clear the current session and start a new one
|
|
138
162
|
|
|
163
|
+
## Flow Mode (Experimental)
|
|
164
|
+
|
|
165
|
+
Flow Mode is vibecore's **key differentiator** - it transforms the framework from a chat interface into a platform for building structured agent-based applications with programmatic conversation control.
|
|
166
|
+
|
|
167
|
+
### What is Flow Mode?
|
|
168
|
+
|
|
169
|
+
Flow Mode allows you to:
|
|
170
|
+
- **Define custom conversation logic** that controls how agents process user input
|
|
171
|
+
- **Build multi-step workflows** with defined sequences and decision points
|
|
172
|
+
- **Orchestrate multiple agents** with handoffs and shared context
|
|
173
|
+
- **Maintain conversation state** across interactions
|
|
174
|
+
- **Create agent-based applications** rather than just chatbots
|
|
175
|
+
|
|
176
|
+
### Example: Simple Flow
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
import asyncio
|
|
180
|
+
from agents import Agent, Runner
|
|
181
|
+
from vibecore.flow import flow, UserInputFunc
|
|
182
|
+
from vibecore.context import VibecoreContext
|
|
183
|
+
|
|
184
|
+
# Define your agent with tools
|
|
185
|
+
agent = Agent[VibecoreContext](
|
|
186
|
+
name="Assistant",
|
|
187
|
+
instructions="You are a helpful assistant",
|
|
188
|
+
tools=[...], # Your tools here
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Define your conversation logic
|
|
192
|
+
async def logic(app, ctx: VibecoreContext, user_input: UserInputFunc):
|
|
193
|
+
# Get user input programmatically
|
|
194
|
+
user_message = await user_input("What would you like to do?")
|
|
195
|
+
|
|
196
|
+
# Process with agent
|
|
197
|
+
result = Runner.run_streamed(
|
|
198
|
+
agent,
|
|
199
|
+
input=user_message,
|
|
200
|
+
context=ctx,
|
|
201
|
+
session=app.session,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Handle the response
|
|
205
|
+
app.current_worker = app.handle_streamed_response(result)
|
|
206
|
+
await app.current_worker.wait()
|
|
207
|
+
|
|
208
|
+
# Run the flow
|
|
209
|
+
async def main():
|
|
210
|
+
await flow(agent, logic)
|
|
211
|
+
|
|
212
|
+
if __name__ == "__main__":
|
|
213
|
+
asyncio.run(main())
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Example: Multi-Agent Customer Service
|
|
217
|
+
|
|
218
|
+
Flow Mode shines when building complex multi-agent systems. See `examples/customer_service.py` for a complete implementation featuring:
|
|
219
|
+
|
|
220
|
+
- **Triage Agent**: Routes requests to appropriate specialists
|
|
221
|
+
- **FAQ Agent**: Handles frequently asked questions
|
|
222
|
+
- **Booking Agent**: Manages seat reservations
|
|
223
|
+
- **Agent Handoffs**: Seamless transitions between agents with context preservation
|
|
224
|
+
- **Shared State**: Maintains customer information across the conversation
|
|
225
|
+
|
|
226
|
+
### Key Components
|
|
227
|
+
|
|
228
|
+
- **`flow()`**: Entry point that sets up the Vibecore app with your custom logic
|
|
229
|
+
- **`logic()`**: Your async function that controls the conversation flow
|
|
230
|
+
- **`UserInputFunc`**: Provides programmatic user input collection
|
|
231
|
+
- **`VibecoreContext`**: Shared state across tools and agents
|
|
232
|
+
- **Agent Handoffs**: Transfer control between specialized agents
|
|
233
|
+
|
|
234
|
+
### Use Cases
|
|
235
|
+
|
|
236
|
+
Flow Mode enables building:
|
|
237
|
+
- **Customer service systems** with routing and escalation
|
|
238
|
+
- **Guided workflows** for complex tasks
|
|
239
|
+
- **Interactive tutorials** with step-by-step guidance
|
|
240
|
+
- **Task automation** with human-in-the-loop controls
|
|
241
|
+
- **Multi-stage data processing** pipelines
|
|
242
|
+
|
|
243
|
+
The examples in the `examples/` directory are adapted from the official OpenAI Agents SDK with minimal modifications, demonstrating how easily you can build sophisticated agent applications with vibecore.
|
|
244
|
+
|
|
139
245
|
### Available Tools
|
|
140
246
|
|
|
141
247
|
vibecore comes with powerful built-in tools:
|