open-swarm 0.1.1745274976__py3-none-any.whl → 0.1.1748636259__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.
- open_swarm-0.1.1748636259.dist-info/METADATA +188 -0
- open_swarm-0.1.1748636259.dist-info/RECORD +82 -0
- {open_swarm-0.1.1745274976.dist-info → open_swarm-0.1.1748636259.dist-info}/WHEEL +2 -1
- open_swarm-0.1.1748636259.dist-info/entry_points.txt +3 -0
- open_swarm-0.1.1748636259.dist-info/top_level.txt +1 -0
- swarm/agent/agent.py +49 -0
- swarm/auth.py +48 -113
- swarm/consumers.py +0 -19
- swarm/extensions/blueprint/__init__.py +16 -30
- swarm/{core → extensions/blueprint}/agent_utils.py +1 -1
- swarm/extensions/blueprint/blueprint_base.py +458 -0
- swarm/extensions/blueprint/blueprint_discovery.py +112 -0
- swarm/extensions/blueprint/output_utils.py +95 -0
- swarm/{core → extensions/blueprint}/spinner.py +21 -30
- swarm/extensions/cli/cli_args.py +0 -6
- swarm/extensions/cli/commands/blueprint_management.py +9 -47
- swarm/extensions/cli/commands/config_management.py +6 -5
- swarm/extensions/cli/commands/edit_config.py +7 -16
- swarm/extensions/cli/commands/list_blueprints.py +1 -1
- swarm/extensions/cli/commands/validate_env.py +4 -11
- swarm/extensions/cli/commands/validate_envvars.py +6 -6
- swarm/extensions/cli/interactive_shell.py +2 -16
- swarm/extensions/config/config_loader.py +201 -107
- swarm/{core → extensions/config}/config_manager.py +38 -50
- swarm/{core → extensions/config}/server_config.py +0 -32
- swarm/extensions/launchers/build_launchers.py +14 -0
- swarm/{core → extensions/launchers}/build_swarm_wrapper.py +0 -0
- swarm/extensions/launchers/swarm_api.py +64 -8
- swarm/extensions/launchers/swarm_cli.py +300 -8
- swarm/llm/chat_completion.py +195 -0
- swarm/serializers.py +5 -96
- swarm/settings.py +111 -99
- swarm/urls.py +74 -57
- swarm/utils/context_utils.py +4 -10
- swarm/utils/general_utils.py +0 -21
- swarm/utils/redact.py +36 -23
- swarm/views/api_views.py +39 -48
- swarm/views/chat_views.py +70 -237
- swarm/views/core_views.py +87 -80
- swarm/views/model_views.py +121 -64
- swarm/views/utils.py +441 -65
- swarm/views/web_views.py +2 -2
- open_swarm-0.1.1745274976.dist-info/METADATA +0 -874
- open_swarm-0.1.1745274976.dist-info/RECORD +0 -318
- open_swarm-0.1.1745274976.dist-info/entry_points.txt +0 -4
- swarm/blueprints/README.md +0 -68
- swarm/blueprints/blueprint_audit_status.json +0 -27
- swarm/blueprints/chatbot/README.md +0 -40
- swarm/blueprints/chatbot/blueprint_chatbot.py +0 -471
- swarm/blueprints/chatbot/metadata.json +0 -23
- swarm/blueprints/chatbot/templates/chatbot/chatbot.html +0 -33
- swarm/blueprints/chucks_angels/README.md +0 -11
- swarm/blueprints/chucks_angels/blueprint_chucks_angels.py +0 -7
- swarm/blueprints/chucks_angels/test_basic.py +0 -3
- swarm/blueprints/codey/CODEY.md +0 -15
- swarm/blueprints/codey/README.md +0 -115
- swarm/blueprints/codey/blueprint_codey.py +0 -1072
- swarm/blueprints/codey/codey_cli.py +0 -373
- swarm/blueprints/codey/instructions.md +0 -17
- swarm/blueprints/codey/metadata.json +0 -23
- swarm/blueprints/common/operation_box_utils.py +0 -83
- swarm/blueprints/digitalbutlers/README.md +0 -11
- swarm/blueprints/digitalbutlers/__init__.py +0 -1
- swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +0 -7
- swarm/blueprints/digitalbutlers/test_basic.py +0 -3
- swarm/blueprints/divine_code/README.md +0 -3
- swarm/blueprints/divine_code/__init__.py +0 -10
- swarm/blueprints/divine_code/apps.py +0 -11
- swarm/blueprints/divine_code/blueprint_divine_code.py +0 -270
- swarm/blueprints/django_chat/apps.py +0 -6
- swarm/blueprints/django_chat/blueprint_django_chat.py +0 -268
- swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +0 -37
- swarm/blueprints/django_chat/urls.py +0 -8
- swarm/blueprints/django_chat/views.py +0 -32
- swarm/blueprints/echocraft/blueprint_echocraft.py +0 -384
- swarm/blueprints/flock/README.md +0 -11
- swarm/blueprints/flock/__init__.py +0 -8
- swarm/blueprints/flock/blueprint_flock.py +0 -7
- swarm/blueprints/flock/test_basic.py +0 -3
- swarm/blueprints/geese/README.md +0 -97
- swarm/blueprints/geese/blueprint_geese.py +0 -803
- swarm/blueprints/geese/geese_cli.py +0 -102
- swarm/blueprints/jeeves/README.md +0 -41
- swarm/blueprints/jeeves/blueprint_jeeves.py +0 -722
- swarm/blueprints/jeeves/jeeves_cli.py +0 -55
- swarm/blueprints/jeeves/metadata.json +0 -24
- swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +0 -473
- swarm/blueprints/messenger/templates/messenger/messenger.html +0 -46
- swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +0 -423
- swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +0 -340
- swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +0 -265
- swarm/blueprints/omniplex/blueprint_omniplex.py +0 -298
- swarm/blueprints/poets/blueprint_poets.py +0 -546
- swarm/blueprints/poets/poets_cli.py +0 -23
- swarm/blueprints/rue_code/README.md +0 -8
- swarm/blueprints/rue_code/blueprint_rue_code.py +0 -448
- swarm/blueprints/rue_code/rue_code_cli.py +0 -43
- swarm/blueprints/stewie/apps.py +0 -12
- swarm/blueprints/stewie/blueprint_family_ties.py +0 -349
- swarm/blueprints/stewie/models.py +0 -19
- swarm/blueprints/stewie/serializers.py +0 -10
- swarm/blueprints/stewie/settings.py +0 -17
- swarm/blueprints/stewie/urls.py +0 -11
- swarm/blueprints/stewie/views.py +0 -26
- swarm/blueprints/suggestion/blueprint_suggestion.py +0 -222
- swarm/blueprints/whinge_surf/README.md +0 -22
- swarm/blueprints/whinge_surf/__init__.py +0 -1
- swarm/blueprints/whinge_surf/blueprint_whinge_surf.py +0 -565
- swarm/blueprints/whinge_surf/whinge_surf_cli.py +0 -99
- swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
- swarm/blueprints/whiskeytango_foxtrot/apps.py +0 -11
- swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +0 -339
- swarm/blueprints/zeus/__init__.py +0 -2
- swarm/blueprints/zeus/apps.py +0 -4
- swarm/blueprints/zeus/blueprint_zeus.py +0 -270
- swarm/blueprints/zeus/zeus_cli.py +0 -13
- swarm/cli/async_input.py +0 -65
- swarm/cli/async_input_demo.py +0 -32
- swarm/core/blueprint_base.py +0 -769
- swarm/core/blueprint_discovery.py +0 -125
- swarm/core/blueprint_runner.py +0 -59
- swarm/core/blueprint_ux.py +0 -109
- swarm/core/build_launchers.py +0 -15
- swarm/core/cli/__init__.py +0 -1
- swarm/core/cli/commands/__init__.py +0 -1
- swarm/core/cli/commands/blueprint_management.py +0 -7
- swarm/core/cli/interactive_shell.py +0 -14
- swarm/core/cli/main.py +0 -50
- swarm/core/cli/utils/__init__.py +0 -1
- swarm/core/cli/utils/discover_commands.py +0 -18
- swarm/core/config_loader.py +0 -122
- swarm/core/output_utils.py +0 -193
- swarm/core/session_logger.py +0 -42
- swarm/core/slash_commands.py +0 -89
- swarm/core/swarm_api.py +0 -68
- swarm/core/swarm_cli.py +0 -216
- swarm/core/utils/__init__.py +0 -0
- swarm/extensions/blueprint/cli_handler.py +0 -197
- swarm/extensions/blueprint/runnable_blueprint.py +0 -42
- swarm/extensions/cli/utils/__init__.py +0 -1
- swarm/extensions/cli/utils/async_input.py +0 -46
- swarm/extensions/cli/utils/prompt_user.py +0 -3
- swarm/management/__init__.py +0 -0
- swarm/management/commands/__init__.py +0 -0
- swarm/management/commands/runserver.py +0 -58
- swarm/middleware.py +0 -65
- swarm/permissions.py +0 -38
- swarm/static/contrib/fonts/fontawesome-webfont.ttf +0 -7
- swarm/static/contrib/fonts/fontawesome-webfont.woff +0 -7
- swarm/static/contrib/fonts/fontawesome-webfont.woff2 +0 -7
- swarm/static/contrib/markedjs/marked.min.js +0 -6
- swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +0 -27
- swarm/static/contrib/tabler-icons/alert-triangle.svg +0 -21
- swarm/static/contrib/tabler-icons/archive.svg +0 -21
- swarm/static/contrib/tabler-icons/artboard.svg +0 -27
- swarm/static/contrib/tabler-icons/automatic-gearbox.svg +0 -23
- swarm/static/contrib/tabler-icons/box-multiple.svg +0 -19
- swarm/static/contrib/tabler-icons/carambola.svg +0 -19
- swarm/static/contrib/tabler-icons/copy.svg +0 -20
- swarm/static/contrib/tabler-icons/download.svg +0 -21
- swarm/static/contrib/tabler-icons/edit.svg +0 -21
- swarm/static/contrib/tabler-icons/filled/carambola.svg +0 -13
- swarm/static/contrib/tabler-icons/filled/paint.svg +0 -13
- swarm/static/contrib/tabler-icons/headset.svg +0 -22
- swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +0 -21
- swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +0 -21
- swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +0 -21
- swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +0 -21
- swarm/static/contrib/tabler-icons/message-chatbot.svg +0 -22
- swarm/static/contrib/tabler-icons/message-star.svg +0 -22
- swarm/static/contrib/tabler-icons/message-x.svg +0 -23
- swarm/static/contrib/tabler-icons/message.svg +0 -21
- swarm/static/contrib/tabler-icons/paperclip.svg +0 -18
- swarm/static/contrib/tabler-icons/playlist-add.svg +0 -22
- swarm/static/contrib/tabler-icons/robot.svg +0 -26
- swarm/static/contrib/tabler-icons/search.svg +0 -19
- swarm/static/contrib/tabler-icons/settings.svg +0 -20
- swarm/static/contrib/tabler-icons/thumb-down.svg +0 -19
- swarm/static/contrib/tabler-icons/thumb-up.svg +0 -19
- swarm/static/css/dropdown.css +0 -22
- swarm/static/htmx/htmx.min.js +0 -0
- swarm/static/js/dropdown.js +0 -23
- swarm/static/rest_mode/css/base.css +0 -470
- swarm/static/rest_mode/css/chat-history.css +0 -286
- swarm/static/rest_mode/css/chat.css +0 -251
- swarm/static/rest_mode/css/chatbot.css +0 -74
- swarm/static/rest_mode/css/chatgpt.css +0 -62
- swarm/static/rest_mode/css/colors/corporate.css +0 -74
- swarm/static/rest_mode/css/colors/pastel.css +0 -81
- swarm/static/rest_mode/css/colors/tropical.css +0 -82
- swarm/static/rest_mode/css/general.css +0 -142
- swarm/static/rest_mode/css/layout.css +0 -167
- swarm/static/rest_mode/css/layouts/messenger-layout.css +0 -17
- swarm/static/rest_mode/css/layouts/minimalist-layout.css +0 -57
- swarm/static/rest_mode/css/layouts/mobile-layout.css +0 -8
- swarm/static/rest_mode/css/messages.css +0 -84
- swarm/static/rest_mode/css/messenger.css +0 -135
- swarm/static/rest_mode/css/settings.css +0 -91
- swarm/static/rest_mode/css/simple.css +0 -44
- swarm/static/rest_mode/css/slack.css +0 -58
- swarm/static/rest_mode/css/style.css +0 -156
- swarm/static/rest_mode/css/theme.css +0 -30
- swarm/static/rest_mode/css/toast.css +0 -40
- swarm/static/rest_mode/js/auth.js +0 -9
- swarm/static/rest_mode/js/blueprint.js +0 -41
- swarm/static/rest_mode/js/blueprintUtils.js +0 -12
- swarm/static/rest_mode/js/chatLogic.js +0 -79
- swarm/static/rest_mode/js/debug.js +0 -63
- swarm/static/rest_mode/js/events.js +0 -98
- swarm/static/rest_mode/js/main.js +0 -19
- swarm/static/rest_mode/js/messages.js +0 -264
- swarm/static/rest_mode/js/messengerLogic.js +0 -355
- swarm/static/rest_mode/js/modules/apiService.js +0 -84
- swarm/static/rest_mode/js/modules/blueprintManager.js +0 -162
- swarm/static/rest_mode/js/modules/chatHistory.js +0 -110
- swarm/static/rest_mode/js/modules/debugLogger.js +0 -14
- swarm/static/rest_mode/js/modules/eventHandlers.js +0 -107
- swarm/static/rest_mode/js/modules/messageProcessor.js +0 -120
- swarm/static/rest_mode/js/modules/state.js +0 -7
- swarm/static/rest_mode/js/modules/userInteractions.js +0 -29
- swarm/static/rest_mode/js/modules/validation.js +0 -23
- swarm/static/rest_mode/js/rendering.js +0 -119
- swarm/static/rest_mode/js/settings.js +0 -130
- swarm/static/rest_mode/js/sidebar.js +0 -94
- swarm/static/rest_mode/js/simpleLogic.js +0 -37
- swarm/static/rest_mode/js/slackLogic.js +0 -66
- swarm/static/rest_mode/js/splash.js +0 -76
- swarm/static/rest_mode/js/theme.js +0 -111
- swarm/static/rest_mode/js/toast.js +0 -36
- swarm/static/rest_mode/js/ui.js +0 -265
- swarm/static/rest_mode/js/validation.js +0 -57
- swarm/static/rest_mode/svg/animated_spinner.svg +0 -12
- swarm/static/rest_mode/svg/arrow_down.svg +0 -5
- swarm/static/rest_mode/svg/arrow_left.svg +0 -5
- swarm/static/rest_mode/svg/arrow_right.svg +0 -5
- swarm/static/rest_mode/svg/arrow_up.svg +0 -5
- swarm/static/rest_mode/svg/attach.svg +0 -8
- swarm/static/rest_mode/svg/avatar.svg +0 -7
- swarm/static/rest_mode/svg/canvas.svg +0 -6
- swarm/static/rest_mode/svg/chat_history.svg +0 -4
- swarm/static/rest_mode/svg/close.svg +0 -5
- swarm/static/rest_mode/svg/copy.svg +0 -4
- swarm/static/rest_mode/svg/dark_mode.svg +0 -3
- swarm/static/rest_mode/svg/edit.svg +0 -5
- swarm/static/rest_mode/svg/layout.svg +0 -9
- swarm/static/rest_mode/svg/logo.svg +0 -29
- swarm/static/rest_mode/svg/logout.svg +0 -5
- swarm/static/rest_mode/svg/mobile.svg +0 -5
- swarm/static/rest_mode/svg/new_chat.svg +0 -4
- swarm/static/rest_mode/svg/not_visible.svg +0 -5
- swarm/static/rest_mode/svg/plus.svg +0 -7
- swarm/static/rest_mode/svg/run_code.svg +0 -6
- swarm/static/rest_mode/svg/save.svg +0 -4
- swarm/static/rest_mode/svg/search.svg +0 -6
- swarm/static/rest_mode/svg/settings.svg +0 -4
- swarm/static/rest_mode/svg/speaker.svg +0 -5
- swarm/static/rest_mode/svg/stop.svg +0 -6
- swarm/static/rest_mode/svg/thumbs_down.svg +0 -3
- swarm/static/rest_mode/svg/thumbs_up.svg +0 -3
- swarm/static/rest_mode/svg/toggle_off.svg +0 -6
- swarm/static/rest_mode/svg/toggle_on.svg +0 -6
- swarm/static/rest_mode/svg/trash.svg +0 -10
- swarm/static/rest_mode/svg/undo.svg +0 -3
- swarm/static/rest_mode/svg/visible.svg +0 -8
- swarm/static/rest_mode/svg/voice.svg +0 -10
- swarm/templates/account/login.html +0 -22
- swarm/templates/account/signup.html +0 -32
- swarm/templates/base.html +0 -30
- swarm/templates/chat.html +0 -43
- swarm/templates/index.html +0 -35
- swarm/templates/rest_mode/components/chat_sidebar.html +0 -55
- swarm/templates/rest_mode/components/header.html +0 -45
- swarm/templates/rest_mode/components/main_chat_pane.html +0 -41
- swarm/templates/rest_mode/components/settings_dialog.html +0 -97
- swarm/templates/rest_mode/components/splash_screen.html +0 -7
- swarm/templates/rest_mode/components/top_bar.html +0 -28
- swarm/templates/rest_mode/message_ui.html +0 -50
- swarm/templates/rest_mode/slackbot.html +0 -30
- swarm/templates/simple_blueprint_page.html +0 -24
- swarm/templates/websocket_partials/final_system_message.html +0 -3
- swarm/templates/websocket_partials/system_message.html +0 -4
- swarm/templates/websocket_partials/user_message.html +0 -5
- swarm/utils/ansi_box.py +0 -34
- swarm/utils/disable_tracing.py +0 -38
- swarm/utils/log_utils.py +0 -63
- swarm/utils/openai_patch.py +0 -33
- swarm/ux/ansi_box.py +0 -43
- swarm/ux/spinner.py +0 -53
- {open_swarm-0.1.1745274976.dist-info → open_swarm-0.1.1748636259.dist-info}/licenses/LICENSE +0 -0
- /swarm/{core → extensions/blueprint}/blueprint_utils.py +0 -0
- /swarm/{core → extensions/blueprint}/common_utils.py +0 -0
- /swarm/{core → extensions/config}/setup_wizard.py +0 -0
- /swarm/{blueprints/rue_code → extensions/config/utils}/__init__.py +0 -0
- /swarm/{core → extensions/config}/utils/logger.py +0 -0
- /swarm/{core → extensions/launchers}/swarm_wrapper.py +0 -0
@@ -1,803 +0,0 @@
|
|
1
|
-
# SECURITY WARNING: All future log/print statements that output environment variables or config values MUST use redact_sensitive_data or similar redaction utility. Never print or log secrets directly.
|
2
|
-
|
3
|
-
import os
|
4
|
-
import time
|
5
|
-
from dotenv import load_dotenv; load_dotenv(override=True)
|
6
|
-
|
7
|
-
import logging
|
8
|
-
logging.basicConfig(level=logging.INFO, format='[%(levelname)s] %(name)s: %(message)s')
|
9
|
-
import sys
|
10
|
-
from swarm.utils.redact import redact_sensitive_data
|
11
|
-
import asyncio
|
12
|
-
|
13
|
-
def force_info_logging():
|
14
|
-
root = logging.getLogger()
|
15
|
-
for handler in root.handlers[:]:
|
16
|
-
root.removeHandler(handler)
|
17
|
-
loglevel = os.environ.get('LOGLEVEL', None)
|
18
|
-
debug_env = os.environ.get('SWARM_DEBUG', '0') == '1'
|
19
|
-
debug_arg = '--debug' in sys.argv
|
20
|
-
if debug_arg or debug_env or (loglevel and loglevel.upper() == 'DEBUG'):
|
21
|
-
level = logging.DEBUG
|
22
|
-
else:
|
23
|
-
level = logging.INFO
|
24
|
-
logging.basicConfig(level=level, format='[%(levelname)s] %(name)s: %(message)s')
|
25
|
-
root.setLevel(level)
|
26
|
-
|
27
|
-
force_info_logging()
|
28
|
-
|
29
|
-
import argparse
|
30
|
-
from typing import List, Dict, Any, Optional, ClassVar
|
31
|
-
|
32
|
-
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
33
|
-
src_path = os.path.join(project_root, 'src')
|
34
|
-
if src_path not in sys.path: sys.path.insert(0, src_path)
|
35
|
-
|
36
|
-
from typing import Optional
|
37
|
-
from pathlib import Path
|
38
|
-
try:
|
39
|
-
from agents import Agent, Tool, function_tool, Runner
|
40
|
-
from agents.mcp import MCPServer
|
41
|
-
from agents.models.interface import Model
|
42
|
-
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
|
43
|
-
from openai import AsyncOpenAI
|
44
|
-
from swarm.core.blueprint_base import BlueprintBase
|
45
|
-
from swarm.core.blueprint_runner import BlueprintRunner
|
46
|
-
except ImportError as e:
|
47
|
-
print(f"ERROR: Import failed in blueprint_geese: {e}. Check 'openai-agents' install and project structure.")
|
48
|
-
print(f"sys.path: {sys.path}")
|
49
|
-
sys.exit(1)
|
50
|
-
|
51
|
-
import argparse
|
52
|
-
|
53
|
-
def setup_logging():
|
54
|
-
parser = argparse.ArgumentParser(add_help=False)
|
55
|
-
parser.add_argument('--debug', action='store_true', help='Enable debug logging')
|
56
|
-
args, _ = parser.parse_known_args()
|
57
|
-
loglevel = os.environ.get('LOGLEVEL', None)
|
58
|
-
if args.debug or os.environ.get('SWARM_DEBUG', '0') == '1' or (loglevel and loglevel.upper() == 'DEBUG'):
|
59
|
-
logging.basicConfig(level=logging.DEBUG)
|
60
|
-
else:
|
61
|
-
logging.basicConfig(level=logging.INFO)
|
62
|
-
return args
|
63
|
-
|
64
|
-
args = setup_logging()
|
65
|
-
|
66
|
-
logger = logging.getLogger(__name__)
|
67
|
-
|
68
|
-
# --- Tools ---
|
69
|
-
def _create_story_outline(topic: str) -> str:
|
70
|
-
logger.info(f"Tool: Generating outline for: {topic}")
|
71
|
-
outline = f"Story Outline for '{topic}':\n1. Beginning: Introduce characters and setting.\n2. Middle: Develop conflict and rising action.\n3. Climax: The peak of the conflict.\n4. End: Resolution and aftermath."
|
72
|
-
logger.debug(f"Generated outline: {outline}")
|
73
|
-
return outline
|
74
|
-
|
75
|
-
@function_tool
|
76
|
-
def create_story_outline(topic: str) -> str:
|
77
|
-
"""Generates a basic story outline based on a topic."""
|
78
|
-
return _create_story_outline(topic)
|
79
|
-
|
80
|
-
def _write_story_part(part_name: str, outline: str, previous_parts: str) -> str:
|
81
|
-
logger.info(f"Tool: Writing story part: {part_name}")
|
82
|
-
content = f"## {part_name}\n\nThis is the draft content for the '{part_name}' section. It follows:\n'{previous_parts[:100]}...' \nIt should align with the outline:\n'{outline}'"
|
83
|
-
logger.debug(f"Generated content for {part_name}: {content[:100]}...")
|
84
|
-
return content
|
85
|
-
|
86
|
-
@function_tool
|
87
|
-
def write_story_part(part_name: str, outline: str, previous_parts: str) -> str:
|
88
|
-
"""Writes a specific part of the story using the outline and previous context."""
|
89
|
-
return _write_story_part(part_name, outline, previous_parts)
|
90
|
-
|
91
|
-
def _edit_story(full_story: str, edit_instructions: str) -> str:
|
92
|
-
logger.info(f"Tool: Editing story with instructions: {edit_instructions}")
|
93
|
-
edited_content = f"*** Edited Story Draft ***\n(Based on instructions: '{edit_instructions}')\n\n{full_story}\n\n[Editor's Notes: Minor tweaks applied for flow.]"
|
94
|
-
logger.debug("Editing complete.")
|
95
|
-
return edited_content
|
96
|
-
|
97
|
-
@function_tool
|
98
|
-
def edit_story(full_story: str, edit_instructions: str) -> str:
|
99
|
-
"""Edits the complete story based on instructions."""
|
100
|
-
return _edit_story(full_story, edit_instructions)
|
101
|
-
|
102
|
-
@function_tool
|
103
|
-
def read_file(path: str, encoding: Optional[str] = "utf-8") -> str:
|
104
|
-
"""Read and return the contents of a file."""
|
105
|
-
try:
|
106
|
-
with open(path, "r", encoding=encoding) as f:
|
107
|
-
content = f.read()
|
108
|
-
logger.info(f"Tool: Read file '{path}' ({len(content)} bytes)")
|
109
|
-
return content
|
110
|
-
except Exception as e:
|
111
|
-
logger.error(f"Tool: Failed to read file '{path}': {e}")
|
112
|
-
return f"[ERROR] Could not read file '{path}': {e}"
|
113
|
-
|
114
|
-
@function_tool
|
115
|
-
def write_file(path: str, content: str, encoding: Optional[str] = "utf-8") -> str:
|
116
|
-
"""Write content to a file, overwriting if it exists."""
|
117
|
-
try:
|
118
|
-
with open(path, "w", encoding=encoding) as f:
|
119
|
-
f.write(content)
|
120
|
-
logger.info(f"Tool: Wrote file '{path}' ({len(content)} bytes)")
|
121
|
-
return f"[SUCCESS] Wrote file '{path}' ({len(content)} bytes)"
|
122
|
-
except Exception as e:
|
123
|
-
logger.error(f"Tool: Failed to write file '{path}': {e}")
|
124
|
-
return f"[ERROR] Could not write file '{path}': {e}"
|
125
|
-
|
126
|
-
from rich.console import Console
|
127
|
-
from rich.panel import Panel
|
128
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn
|
129
|
-
from rich.live import Live
|
130
|
-
from rich import box
|
131
|
-
import asyncio
|
132
|
-
from enum import Enum
|
133
|
-
from swarm.ux.ansi_box import ansi_box
|
134
|
-
from dataclasses import dataclass
|
135
|
-
|
136
|
-
# --- Spinner State Constants ---
|
137
|
-
SPINNER_STATES = [
|
138
|
-
"Generating.",
|
139
|
-
"Generating..",
|
140
|
-
"Generating...",
|
141
|
-
"Running...",
|
142
|
-
]
|
143
|
-
SLOW_SPINNER = "Generating... Taking longer than expected"
|
144
|
-
|
145
|
-
class SpinnerState(Enum):
|
146
|
-
GENERATING_1 = "Generating."
|
147
|
-
GENERATING_2 = "Generating.."
|
148
|
-
GENERATING_3 = "Generating..."
|
149
|
-
RUNNING = "Running..."
|
150
|
-
LONG_WAIT = "Generating... Taking longer than expected"
|
151
|
-
|
152
|
-
# --- Notifier abstraction ---
|
153
|
-
class Notifier:
|
154
|
-
def __init__(self, console=None):
|
155
|
-
from rich.console import Console
|
156
|
-
self.console = console or Console()
|
157
|
-
|
158
|
-
def print_box(self, title, content, style="blue", *, result_count: int = None, params: dict = None, op_type: str = None, progress_line: int = None, total_lines: int = None, spinner_state: str = None, emoji: str = None):
|
159
|
-
emoji_map = {
|
160
|
-
"search": "🔍",
|
161
|
-
"code_search": "💻",
|
162
|
-
"semantic_search": "🧠",
|
163
|
-
"analysis": "📊",
|
164
|
-
"writing": "✍️",
|
165
|
-
"editing": "✏️",
|
166
|
-
"planning": "📋"
|
167
|
-
}
|
168
|
-
emoji = emoji_map.get(op_type or title.lower(), emoji or "💡")
|
169
|
-
summary_lines = []
|
170
|
-
if result_count is not None:
|
171
|
-
summary_lines.append(f"Results: {result_count}")
|
172
|
-
if params:
|
173
|
-
for k, v in params.items():
|
174
|
-
summary_lines.append(f"{k.title()}: {v}")
|
175
|
-
if progress_line and total_lines:
|
176
|
-
summary_lines.append(f"Progress: {progress_line}/{total_lines}")
|
177
|
-
summary = "\n".join(summary_lines)
|
178
|
-
box_content = content
|
179
|
-
if summary:
|
180
|
-
box_content = f"{summary}\n{content}"
|
181
|
-
if spinner_state:
|
182
|
-
box_content += f"\n{spinner_state}"
|
183
|
-
if emoji:
|
184
|
-
box_content = f"{emoji} {box_content}"
|
185
|
-
display_operation_box(
|
186
|
-
title=title,
|
187
|
-
content=box_content,
|
188
|
-
result_count=result_count,
|
189
|
-
params=params,
|
190
|
-
progress_line=progress_line,
|
191
|
-
total_lines=total_lines,
|
192
|
-
spinner_state=spinner_state,
|
193
|
-
emoji=emoji
|
194
|
-
)
|
195
|
-
|
196
|
-
def print_error(self, title, content):
|
197
|
-
self.print_box(title, content, style="red", emoji="❌")
|
198
|
-
|
199
|
-
def print_info(self, content):
|
200
|
-
self.console.print(content)
|
201
|
-
|
202
|
-
@dataclass
|
203
|
-
class AgentTool:
|
204
|
-
name: str
|
205
|
-
description: str
|
206
|
-
parameters: dict
|
207
|
-
handler: callable = None
|
208
|
-
|
209
|
-
class ToolRegistry:
|
210
|
-
"""
|
211
|
-
Central registry for all tools: both LLM (OpenAI function-calling) and Python-only tools.
|
212
|
-
"""
|
213
|
-
def __init__(self):
|
214
|
-
self.llm_tools = {}
|
215
|
-
self.python_tools = {}
|
216
|
-
|
217
|
-
def register_llm_tool(self, name: str, description: str, parameters: dict, handler):
|
218
|
-
self.llm_tools[name] = {
|
219
|
-
'name': name,
|
220
|
-
'description': description,
|
221
|
-
'parameters': parameters,
|
222
|
-
'handler': handler
|
223
|
-
}
|
224
|
-
|
225
|
-
def register_python_tool(self, name: str, handler, description: str = ""):
|
226
|
-
self.python_tools[name] = handler
|
227
|
-
|
228
|
-
def get_llm_tools(self, as_openai_spec=False):
|
229
|
-
tools = list(self.llm_tools.values())
|
230
|
-
if as_openai_spec:
|
231
|
-
# Return OpenAI-compatible dicts
|
232
|
-
return [
|
233
|
-
{
|
234
|
-
'name': t['name'],
|
235
|
-
'description': t['description'],
|
236
|
-
'parameters': t['parameters']
|
237
|
-
} for t in tools
|
238
|
-
]
|
239
|
-
return tools
|
240
|
-
|
241
|
-
def get_python_tool(self, name: str):
|
242
|
-
return self.python_tools.get(name)
|
243
|
-
|
244
|
-
from swarm.blueprints.common.operation_box_utils import display_operation_box
|
245
|
-
|
246
|
-
class GeeseBlueprint(BlueprintBase):
|
247
|
-
"""
|
248
|
-
Geese: Swarm-powered collaborative story writing blueprint.
|
249
|
-
"""
|
250
|
-
metadata: ClassVar[dict] = {
|
251
|
-
"name": "GeeseBlueprint",
|
252
|
-
"cli_name": "geese",
|
253
|
-
"title": "Geese: Swarm-powered collaborative story writing agent",
|
254
|
-
"description": "A collaborative story writing blueprint leveraging multiple specialized agents to create, edit, and refine stories.",
|
255
|
-
}
|
256
|
-
|
257
|
-
def get_llm_profile_name(self):
|
258
|
-
# Returns the active LLM profile name, prioritizing CLI-set or fallback logic
|
259
|
-
if hasattr(self, '_llm_profile_name') and self._llm_profile_name:
|
260
|
-
return self._llm_profile_name
|
261
|
-
if hasattr(self, '_resolve_llm_profile'):
|
262
|
-
return self._resolve_llm_profile()
|
263
|
-
return 'default'
|
264
|
-
|
265
|
-
def get_llm_profile_config(self):
|
266
|
-
# Returns the config dict for the active LLM profile
|
267
|
-
profile = self.get_llm_profile_name()
|
268
|
-
llm_section = self.config.get('llm', {}) if hasattr(self, 'config') else {}
|
269
|
-
return llm_section.get(profile, llm_section.get('default', {}))
|
270
|
-
|
271
|
-
def get_model_name(self):
|
272
|
-
return self.get_llm_profile_config().get('model', 'gpt-4o')
|
273
|
-
|
274
|
-
def get_llm_endpoint(self):
|
275
|
-
return self.get_llm_profile_config().get('base_url', 'unknown')
|
276
|
-
|
277
|
-
def get_llm_api_key(self):
|
278
|
-
api_key = self.get_llm_profile_config().get('api_key', 'unknown')
|
279
|
-
import os
|
280
|
-
# Try to resolve env vars in api_key string
|
281
|
-
if isinstance(api_key, str) and api_key.startswith('${') and api_key.endswith('}'):
|
282
|
-
env_var = api_key[2:-1]
|
283
|
-
return os.environ.get(env_var, 'NOT SET')
|
284
|
-
return api_key
|
285
|
-
|
286
|
-
def __init__(self, blueprint_id: str = "geese", config=None, config_path=None, notifier=None, mcp_servers: Optional[Dict[str, Any]] = None, agent_mcp_assignments: Optional[Dict[str, list]] = None, **kwargs):
|
287
|
-
super().__init__(blueprint_id, config=config, config_path=config_path, **kwargs)
|
288
|
-
self.blueprint_id = blueprint_id
|
289
|
-
self.config_path = config_path
|
290
|
-
self._config = config if config is not None else {}
|
291
|
-
self._llm_profile_name = None
|
292
|
-
self._llm_profile_data = None
|
293
|
-
self._markdown_output = None
|
294
|
-
self.notifier = notifier
|
295
|
-
self.mcp_servers = mcp_servers or {}
|
296
|
-
self.agent_mcp_assignments = agent_mcp_assignments or {}
|
297
|
-
# Only call model/profile-dependent logic if config is set
|
298
|
-
if self._config is not None:
|
299
|
-
self.model_name = self.get_model_name()
|
300
|
-
else:
|
301
|
-
self.model_name = None
|
302
|
-
# Register required tools for delegation flow tests
|
303
|
-
self.tool_registry = ToolRegistry() # Ensure tool_registry always exists
|
304
|
-
# Register required tools for delegation flow tests
|
305
|
-
self.tool_registry.register_llm_tool(
|
306
|
-
name="Planner",
|
307
|
-
description="Plans the story structure.",
|
308
|
-
parameters={},
|
309
|
-
handler=lambda *a, **kw: None
|
310
|
-
)
|
311
|
-
self.tool_registry.register_llm_tool(
|
312
|
-
name="Writer",
|
313
|
-
description="Writes story content.",
|
314
|
-
parameters={},
|
315
|
-
handler=lambda *a, **kw: None
|
316
|
-
)
|
317
|
-
self.tool_registry.register_llm_tool(
|
318
|
-
name="Editor",
|
319
|
-
description="Edits the story.",
|
320
|
-
parameters={},
|
321
|
-
handler=lambda *a, **kw: None
|
322
|
-
)
|
323
|
-
self.notifier = notifier or Notifier()
|
324
|
-
self.mcp_servers = mcp_servers or getattr(self, 'mcp_server_configs', {}) or {}
|
325
|
-
self.agent_mcp_assignments = agent_mcp_assignments or {}
|
326
|
-
# Build enabled/disabled lists for all MCPs
|
327
|
-
self.enabled_mcp_servers = {k: v for k, v in self.mcp_servers.items() if not v.get('disabled', False)}
|
328
|
-
from agents import Agent, Tool
|
329
|
-
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
|
330
|
-
from openai import AsyncOpenAI
|
331
|
-
import os
|
332
|
-
if self.model_name:
|
333
|
-
model_name = self.model_name
|
334
|
-
api_key = os.environ.get('OPENAI_API_KEY')
|
335
|
-
openai_client = AsyncOpenAI(api_key=api_key)
|
336
|
-
model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=openai_client)
|
337
|
-
else:
|
338
|
-
model_instance = None
|
339
|
-
# Attach all available tools (LLM and Python) to the agent
|
340
|
-
llm_tools = getattr(self, 'tool_registry', None)
|
341
|
-
if llm_tools is not None:
|
342
|
-
# Use AgentTool objects for agent.tools
|
343
|
-
llm_tools = [AgentTool(**t) for t in llm_tools.get_llm_tools(as_openai_spec=False)]
|
344
|
-
else:
|
345
|
-
llm_tools = []
|
346
|
-
python_tools = getattr(self, 'tool_registry', None)
|
347
|
-
if python_tools is not None:
|
348
|
-
python_tools = python_tools.python_tools
|
349
|
-
else:
|
350
|
-
python_tools = {}
|
351
|
-
if model_instance:
|
352
|
-
agent = Agent(
|
353
|
-
name='GooseCoordinator',
|
354
|
-
model=model_instance,
|
355
|
-
instructions="You are a highly skilled code generation and automation agent.",
|
356
|
-
tools=llm_tools
|
357
|
-
)
|
358
|
-
else:
|
359
|
-
agent = Agent(
|
360
|
-
name='GooseCoordinator',
|
361
|
-
instructions="You are a highly skilled code generation and automation agent.",
|
362
|
-
tools=llm_tools
|
363
|
-
)
|
364
|
-
agent.python_tools = python_tools
|
365
|
-
# Restore legacy agent MCP assignment logic to satisfy agent_mcp_assignment tests
|
366
|
-
self.agents = {'GooseCoordinator': agent}
|
367
|
-
agent_names = set(self.agent_mcp_assignments.keys()) | {'GooseCoordinator'}
|
368
|
-
for agent_name in agent_names:
|
369
|
-
if agent_name == 'GooseCoordinator':
|
370
|
-
continue
|
371
|
-
assigned_mcps = self.agent_mcp_assignments.get(agent_name, [])
|
372
|
-
assigned_mcp_objs = [self.enabled_mcp_servers[m] for m in assigned_mcps if m in self.enabled_mcp_servers]
|
373
|
-
extra_agent = Agent(
|
374
|
-
name=agent_name,
|
375
|
-
tools=[], # Minimal tools for test compatibility
|
376
|
-
model=model_instance,
|
377
|
-
instructions="Test agent for MCP assignment."
|
378
|
-
)
|
379
|
-
extra_agent.mcp_servers = assigned_mcp_objs
|
380
|
-
extra_agent.description = f"Agent {agent_name} for test MCP assignment."
|
381
|
-
self.agents[agent_name] = extra_agent
|
382
|
-
# Ensure MCP assignment for all agents in self.agents
|
383
|
-
for agent_name, mcp_names in self.agent_mcp_assignments.items():
|
384
|
-
agent = self.agents.get(agent_name)
|
385
|
-
if agent is not None:
|
386
|
-
agent.mcp_servers = [self.enabled_mcp_servers[m] for m in mcp_names if m in self.enabled_mcp_servers]
|
387
|
-
self.coordinator = agent
|
388
|
-
self.logger = logging.getLogger(__name__)
|
389
|
-
self.plan = []
|
390
|
-
|
391
|
-
# --- Directory/Folder and Grep Tools ---
|
392
|
-
import os, re
|
393
|
-
def list_folder(path: str = "."):
|
394
|
-
"""List immediate contents of a directory (files and folders)."""
|
395
|
-
try:
|
396
|
-
return {"entries": os.listdir(path)}
|
397
|
-
except Exception as e:
|
398
|
-
return {"error": str(e)}
|
399
|
-
|
400
|
-
def list_folder_recursive(path: str = "."):
|
401
|
-
"""List all files and folders recursively within a directory."""
|
402
|
-
results = []
|
403
|
-
try:
|
404
|
-
for root, dirs, files in os.walk(path):
|
405
|
-
for d in dirs:
|
406
|
-
results.append(os.path.join(root, d))
|
407
|
-
for f in files:
|
408
|
-
results.append(os.path.join(root, f))
|
409
|
-
return {"entries": results}
|
410
|
-
except Exception as e:
|
411
|
-
return {"error": str(e)}
|
412
|
-
|
413
|
-
def grep_search(pattern: str, path: str = ".", case_insensitive: bool = False, max_results: int = 100, progress_yield: int = 10):
|
414
|
-
"""Progressive regex search in files, yields dicts of matches and progress."""
|
415
|
-
matches = []
|
416
|
-
import re, os
|
417
|
-
flags = re.IGNORECASE if case_insensitive else 0
|
418
|
-
try:
|
419
|
-
total_files = 0
|
420
|
-
for root, dirs, files in os.walk(path):
|
421
|
-
for fname in files:
|
422
|
-
total_files += 1
|
423
|
-
scanned_files = 0
|
424
|
-
for root, dirs, files in os.walk(path):
|
425
|
-
for fname in files:
|
426
|
-
fpath = os.path.join(root, fname)
|
427
|
-
scanned_files += 1
|
428
|
-
try:
|
429
|
-
with open(fpath, "r", encoding="utf-8", errors="ignore") as f:
|
430
|
-
for i, line in enumerate(f, 1):
|
431
|
-
if re.search(pattern, line, flags):
|
432
|
-
matches.append({
|
433
|
-
"file": fpath,
|
434
|
-
"line": i,
|
435
|
-
"content": line.strip()
|
436
|
-
})
|
437
|
-
if len(matches) >= max_results:
|
438
|
-
yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": True, "done": True}
|
439
|
-
return
|
440
|
-
except Exception:
|
441
|
-
continue
|
442
|
-
if scanned_files % progress_yield == 0:
|
443
|
-
yield {"matches": matches.copy(), "progress": scanned_files, "total": total_files, "truncated": False, "done": False}
|
444
|
-
# Final yield
|
445
|
-
yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
|
446
|
-
except Exception as e:
|
447
|
-
yield {"error": str(e), "matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
|
448
|
-
|
449
|
-
self.tool_registry.register_llm_tool(
|
450
|
-
name="grep_search",
|
451
|
-
description="Search for a regex pattern in files under a directory tree. Returns file, line number, and line content for each match.",
|
452
|
-
parameters={
|
453
|
-
"type": "object",
|
454
|
-
"properties": {
|
455
|
-
"pattern": {"type": "string", "description": "Regex pattern to search for"},
|
456
|
-
"path": {"type": "string", "description": "Directory to search (default: current directory)"},
|
457
|
-
"case_insensitive": {"type": "boolean", "description": "Case-insensitive search (default: false)"},
|
458
|
-
"max_results": {"type": "integer", "description": "Maximum number of results (default: 100)"}
|
459
|
-
},
|
460
|
-
"required": ["pattern"]
|
461
|
-
},
|
462
|
-
handler=grep_search,
|
463
|
-
)
|
464
|
-
# Register agent/blueprint delegation tools (stubs)
|
465
|
-
def planner(prompt: str) -> str:
|
466
|
-
"""Stub tool for planning."""
|
467
|
-
return "Planned: " + prompt
|
468
|
-
self.tool_registry.register_llm_tool(
|
469
|
-
name="Planner",
|
470
|
-
description="Plans the next steps for story generation.",
|
471
|
-
parameters={"type": "object", "properties": {"prompt": {"type": "string", "description": "Prompt to plan for"}}, "required": ["prompt"]},
|
472
|
-
handler=planner,
|
473
|
-
)
|
474
|
-
def writer(plan: str, context: str = "") -> str:
|
475
|
-
"""Write story content based on a plan and optional context."""
|
476
|
-
return "[Writer] Wrote content for plan: " + plan
|
477
|
-
def editor(draft: str) -> str:
|
478
|
-
"""Edit and refine a draft story."""
|
479
|
-
return "[Editor] Edited draft."
|
480
|
-
self.tool_registry.register_llm_tool(
|
481
|
-
name="Writer",
|
482
|
-
description="Write story content based on a plan and optional context.",
|
483
|
-
parameters={"type": "object", "properties": {"plan": {"type": "string", "description": "Story plan"}, "context": {"type": "string", "description": "Optional context"}}, "required": ["plan"]},
|
484
|
-
handler=writer,
|
485
|
-
)
|
486
|
-
self.tool_registry.register_llm_tool(
|
487
|
-
name="Editor",
|
488
|
-
description="Edit and refine a draft story.",
|
489
|
-
parameters={"type": "object", "properties": {"draft": {"type": "string", "description": "Draft story text"}}, "required": ["draft"]},
|
490
|
-
handler=editor,
|
491
|
-
)
|
492
|
-
# --- Directory/Folder and Grep Tools ---
|
493
|
-
import os, re
|
494
|
-
def list_folder(path: str = "."):
|
495
|
-
"""List immediate contents of a directory (files and folders)."""
|
496
|
-
try:
|
497
|
-
return {"entries": os.listdir(path)}
|
498
|
-
except Exception as e:
|
499
|
-
return {"error": str(e)}
|
500
|
-
|
501
|
-
def list_folder_recursive(path: str = "."):
|
502
|
-
"""List all files and folders recursively within a directory."""
|
503
|
-
results = []
|
504
|
-
try:
|
505
|
-
for root, dirs, files in os.walk(path):
|
506
|
-
for d in dirs:
|
507
|
-
results.append(os.path.join(root, d))
|
508
|
-
for f in files:
|
509
|
-
results.append(os.path.join(root, f))
|
510
|
-
return {"entries": results}
|
511
|
-
except Exception as e:
|
512
|
-
return {"error": str(e)}
|
513
|
-
|
514
|
-
def grep_search(pattern: str, path: str = ".", case_insensitive: bool = False, max_results: int = 100, progress_yield: int = 10):
|
515
|
-
"""Progressive regex search in files, yields dicts of matches and progress."""
|
516
|
-
matches = []
|
517
|
-
flags = re.IGNORECASE if case_insensitive else 0
|
518
|
-
try:
|
519
|
-
total_files = 0
|
520
|
-
for root, dirs, files in os.walk(path):
|
521
|
-
for fname in files:
|
522
|
-
total_files += 1
|
523
|
-
scanned_files = 0
|
524
|
-
for root, dirs, files in os.walk(path):
|
525
|
-
for fname in files:
|
526
|
-
fpath = os.path.join(root, fname)
|
527
|
-
scanned_files += 1
|
528
|
-
try:
|
529
|
-
with open(fpath, "r", encoding="utf-8", errors="ignore") as f:
|
530
|
-
for i, line in enumerate(f, 1):
|
531
|
-
if re.search(pattern, line, flags):
|
532
|
-
matches.append({
|
533
|
-
"file": fpath,
|
534
|
-
"line": i,
|
535
|
-
"content": line.strip()
|
536
|
-
})
|
537
|
-
if len(matches) >= max_results:
|
538
|
-
yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": True, "done": True}
|
539
|
-
return
|
540
|
-
except Exception:
|
541
|
-
continue
|
542
|
-
if scanned_files % progress_yield == 0:
|
543
|
-
yield {"matches": matches.copy(), "progress": scanned_files, "total": total_files, "truncated": False, "done": False}
|
544
|
-
# Final yield
|
545
|
-
yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
|
546
|
-
except Exception as e:
|
547
|
-
yield {"error": str(e), "matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
|
548
|
-
|
549
|
-
self.tool_registry.register_llm_tool(
|
550
|
-
name="grep_search",
|
551
|
-
description="Search for a regex pattern in files under a directory tree. Returns file, line number, and line content for each match.",
|
552
|
-
parameters={
|
553
|
-
"type": "object",
|
554
|
-
"properties": {
|
555
|
-
"pattern": {"type": "string", "description": "Regex pattern to search for"},
|
556
|
-
"path": {"type": "string", "description": "Directory to search (default: current directory)"},
|
557
|
-
"case_insensitive": {"type": "boolean", "description": "Case-insensitive search (default: false)"},
|
558
|
-
"max_results": {"type": "integer", "description": "Maximum number of results (default: 100)"}
|
559
|
-
},
|
560
|
-
"required": ["pattern"]
|
561
|
-
},
|
562
|
-
handler=grep_search,
|
563
|
-
)
|
564
|
-
# --- Directory/Folder and Grep Tools ---
|
565
|
-
import os, re
|
566
|
-
def list_folder(path: str = "."):
|
567
|
-
"""List immediate contents of a directory (files and folders)."""
|
568
|
-
try:
|
569
|
-
return {"entries": os.listdir(path)}
|
570
|
-
except Exception as e:
|
571
|
-
return {"error": str(e)}
|
572
|
-
|
573
|
-
def list_folder_recursive(path: str = "."):
|
574
|
-
"""List all files and folders recursively within a directory."""
|
575
|
-
results = []
|
576
|
-
try:
|
577
|
-
for root, dirs, files in os.walk(path):
|
578
|
-
for d in dirs:
|
579
|
-
results.append(os.path.join(root, d))
|
580
|
-
for f in files:
|
581
|
-
results.append(os.path.join(root, f))
|
582
|
-
return {"entries": results}
|
583
|
-
except Exception as e:
|
584
|
-
return {"error": str(e)}
|
585
|
-
|
586
|
-
def grep_search(pattern: str, path: str = ".", case_insensitive: bool = False, max_results: int = 100, progress_yield: int = 10):
|
587
|
-
"""Progressive regex search in files, yields dicts of matches and progress."""
|
588
|
-
matches = []
|
589
|
-
flags = re.IGNORECASE if case_insensitive else 0
|
590
|
-
try:
|
591
|
-
total_files = 0
|
592
|
-
for root, dirs, files in os.walk(path):
|
593
|
-
for fname in files:
|
594
|
-
total_files += 1
|
595
|
-
scanned_files = 0
|
596
|
-
for root, dirs, files in os.walk(path):
|
597
|
-
for fname in files:
|
598
|
-
fpath = os.path.join(root, fname)
|
599
|
-
scanned_files += 1
|
600
|
-
try:
|
601
|
-
with open(fpath, "r", encoding="utf-8", errors="ignore") as f:
|
602
|
-
for i, line in enumerate(f, 1):
|
603
|
-
if re.search(pattern, line, flags):
|
604
|
-
matches.append({
|
605
|
-
"file": fpath,
|
606
|
-
"line": i,
|
607
|
-
"content": line.strip()
|
608
|
-
})
|
609
|
-
if len(matches) >= max_results:
|
610
|
-
yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": True, "done": True}
|
611
|
-
return
|
612
|
-
except Exception:
|
613
|
-
continue
|
614
|
-
if scanned_files % progress_yield == 0:
|
615
|
-
yield {"matches": matches.copy(), "progress": scanned_files, "total": total_files, "truncated": False, "done": False}
|
616
|
-
# Final yield
|
617
|
-
yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
|
618
|
-
except Exception as e:
|
619
|
-
yield {"error": str(e), "matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
|
620
|
-
|
621
|
-
self.tool_registry.register_llm_tool(
|
622
|
-
name="grep_search",
|
623
|
-
description="Search for a regex pattern in files under a directory tree. Returns file, line number, and line content for each match.",
|
624
|
-
parameters={
|
625
|
-
"type": "object",
|
626
|
-
"properties": {
|
627
|
-
"pattern": {"type": "string", "description": "Regex pattern to search for"},
|
628
|
-
"path": {"type": "string", "description": "Directory to search (default: current directory)"},
|
629
|
-
"case_insensitive": {"type": "boolean", "description": "Case-insensitive search (default: false)"},
|
630
|
-
"max_results": {"type": "integer", "description": "Maximum number of results (default: 100)"}
|
631
|
-
},
|
632
|
-
"required": ["pattern"]
|
633
|
-
},
|
634
|
-
handler=grep_search,
|
635
|
-
)
|
636
|
-
|
637
|
-
async def run(self, messages: List[dict], **kwargs):
|
638
|
-
"""Main execution entry point for the Geese blueprint."""
|
639
|
-
logger.info("GeeseBlueprint run method called.")
|
640
|
-
instruction = messages[-1].get("content", "") if messages else ""
|
641
|
-
from swarm.core.blueprint_ux import BlueprintUXImproved
|
642
|
-
ux = BlueprintUXImproved(style="serious")
|
643
|
-
spinner_idx = 0
|
644
|
-
start_time = time.time()
|
645
|
-
spinner_yield_interval = 1.0 # seconds
|
646
|
-
last_spinner_time = start_time
|
647
|
-
yielded_spinner = False
|
648
|
-
result_chunks = []
|
649
|
-
try:
|
650
|
-
from agents import Runner
|
651
|
-
runner_gen = Runner.run(self.create_starting_agent([]), instruction)
|
652
|
-
while True:
|
653
|
-
now = time.time()
|
654
|
-
try:
|
655
|
-
chunk = next(runner_gen)
|
656
|
-
result_chunks.append(chunk)
|
657
|
-
# If chunk is a final result, wrap and yield
|
658
|
-
if chunk and isinstance(chunk, dict) and "messages" in chunk:
|
659
|
-
content = chunk["messages"][0]["content"] if chunk["messages"] else ""
|
660
|
-
summary = ux.summary("Operation", len(result_chunks), {"instruction": instruction[:40]})
|
661
|
-
box = ux.ansi_emoji_box(
|
662
|
-
title="Geese Result",
|
663
|
-
content=content,
|
664
|
-
summary=summary,
|
665
|
-
params={"instruction": instruction[:40]},
|
666
|
-
result_count=len(result_chunks),
|
667
|
-
op_type="run",
|
668
|
-
status="success"
|
669
|
-
)
|
670
|
-
yield {"messages": [{"role": "assistant", "content": box}]}
|
671
|
-
else:
|
672
|
-
yield chunk
|
673
|
-
yielded_spinner = False
|
674
|
-
except StopIteration:
|
675
|
-
break
|
676
|
-
except Exception:
|
677
|
-
if now - last_spinner_time >= spinner_yield_interval:
|
678
|
-
taking_long = (now - start_time > 10)
|
679
|
-
spinner_msg = ux.spinner(spinner_idx, taking_long=taking_long)
|
680
|
-
yield {"messages": [{"role": "assistant", "content": spinner_msg}]}
|
681
|
-
spinner_idx += 1
|
682
|
-
last_spinner_time = now
|
683
|
-
yielded_spinner = True
|
684
|
-
if not result_chunks and not yielded_spinner:
|
685
|
-
yield {"messages": [{"role": "assistant", "content": ux.spinner(0)}]}
|
686
|
-
except Exception as e:
|
687
|
-
logger.error(f"Error during Geese run: {e}", exc_info=True)
|
688
|
-
yield {"messages": [{"role": "assistant", "content": f"An error occurred: {e}"}]}
|
689
|
-
|
690
|
-
async def demo_run(self, messages: List[dict], **kwargs):
|
691
|
-
# --- DEMO: Progressive search for live operation box UX ---
|
692
|
-
import asyncio
|
693
|
-
prompt = messages[0].get('content', '') if messages else ''
|
694
|
-
# Simulate a progressive code search with fake results
|
695
|
-
total = 5 # test expects 5 updates
|
696
|
-
matches = []
|
697
|
-
for i in range(1, total + 1):
|
698
|
-
await asyncio.sleep(0.3)
|
699
|
-
matches.append(f"def demo_func_{i}(): ...")
|
700
|
-
chunk = {
|
701
|
-
"matches": matches.copy(),
|
702
|
-
"progress": i,
|
703
|
-
"total": total,
|
704
|
-
"truncated": False,
|
705
|
-
"done": i == total,
|
706
|
-
"query": prompt,
|
707
|
-
"type": "code_search"
|
708
|
-
}
|
709
|
-
display_operation_box(
|
710
|
-
title="Searching Filesystem" if chunk.get("progress") else "Geese Output",
|
711
|
-
content=f"Matches so far: {len(chunk.get('matches', []))}" if chunk.get("matches") is not None else str(chunk),
|
712
|
-
result_count=len(chunk.get('matches', [])) if chunk.get("matches") is not None else None,
|
713
|
-
params={k: v for k, v in chunk.items() if k not in {'matches', 'progress', 'total', 'truncated', 'done'}},
|
714
|
-
progress_line=chunk.get('progress'),
|
715
|
-
total_lines=chunk.get('total'),
|
716
|
-
spinner_state=None,
|
717
|
-
emoji="🔍" if chunk.get("progress") else "💡",
|
718
|
-
op_type="search"
|
719
|
-
)
|
720
|
-
yield chunk
|
721
|
-
|
722
|
-
def display_plan_box(self):
|
723
|
-
if self.plan:
|
724
|
-
display_operation_box(
|
725
|
-
title="Planning Update",
|
726
|
-
content="\n".join([f"✓ {item}" for item in self.plan]),
|
727
|
-
emoji="📋"
|
728
|
-
)
|
729
|
-
|
730
|
-
def update_spinner(self, progress_state, elapsed_time):
|
731
|
-
# Use direct reference to SpinnerState to avoid import errors when running as __main__
|
732
|
-
from swarm.blueprints.geese.blueprint_geese import SpinnerState
|
733
|
-
if elapsed_time > 10 and progress_state in [SpinnerState.GENERATING_1, SpinnerState.GENERATING_2, SpinnerState.GENERATING_3]:
|
734
|
-
return SpinnerState.LONG_WAIT
|
735
|
-
return progress_state
|
736
|
-
|
737
|
-
def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
|
738
|
-
"""Returns the coordinator agent for GeeseBlueprint, using only assigned MCP servers."""
|
739
|
-
self.logger.info(f"Coordinator assigned MCP servers: {[m.get('name', 'unknown') for m in getattr(self.coordinator, 'mcp_servers', [])]}")
|
740
|
-
return self.agents['GooseCoordinator']
|
741
|
-
|
742
|
-
# --- CLI entry point ---
|
743
|
-
def main():
|
744
|
-
import argparse
|
745
|
-
import sys
|
746
|
-
import asyncio
|
747
|
-
import os
|
748
|
-
import json
|
749
|
-
parser = argparse.ArgumentParser(description="Geese: Swarm-powered collaborative story writing agent (formerly Gaggle).")
|
750
|
-
parser.add_argument("prompt", nargs="?", help="Prompt or story topic (quoted)")
|
751
|
-
parser.add_argument("-i", "--input", help="Input file or directory", default=None)
|
752
|
-
parser.add_argument("-o", "--output", help="Output file", default=None)
|
753
|
-
parser.add_argument("--model", help="Model name (codex, gpt, etc.)", default=None)
|
754
|
-
parser.add_argument("--temperature", type=float, help="Sampling temperature", default=0.1)
|
755
|
-
parser.add_argument("--debug", action="store_true", help="Enable debug logging")
|
756
|
-
parser.add_argument("--config", help="Path to swarm_config.json", default=None)
|
757
|
-
args = parser.parse_args()
|
758
|
-
|
759
|
-
# Load config file if present
|
760
|
-
config_path = args.config or "/home/chatgpt/open-swarm/swarm_config.json"
|
761
|
-
config = None
|
762
|
-
if os.path.isfile(config_path):
|
763
|
-
with open(config_path, "r") as f:
|
764
|
-
config = json.load(f)
|
765
|
-
else:
|
766
|
-
print(f"WARNING: Config file not found at {config_path}. Proceeding with empty config.")
|
767
|
-
config = {}
|
768
|
-
|
769
|
-
blueprint = GeeseBlueprint(blueprint_id="cli-geese", config=config)
|
770
|
-
messages = []
|
771
|
-
if args.prompt:
|
772
|
-
messages.append({"role": "user", "content": args.prompt})
|
773
|
-
else:
|
774
|
-
print("Type your prompt (or 'exit' to quit):\n")
|
775
|
-
while True:
|
776
|
-
try:
|
777
|
-
user_input = input("You: ").strip()
|
778
|
-
except (EOFError, KeyboardInterrupt):
|
779
|
-
print("\nExiting Geese CLI.")
|
780
|
-
break
|
781
|
-
if user_input.lower() in {"exit", "quit", "q"}:
|
782
|
-
print("Goodbye!")
|
783
|
-
break
|
784
|
-
messages.append({"role": "user", "content": user_input})
|
785
|
-
async def run_and_print():
|
786
|
-
async for response in blueprint.run(messages, model=args.model):
|
787
|
-
if isinstance(response, dict) and 'content' in response:
|
788
|
-
print(response['content'], end="")
|
789
|
-
else:
|
790
|
-
print(response, end="")
|
791
|
-
asyncio.run(run_and_print())
|
792
|
-
messages = []
|
793
|
-
sys.exit(0)
|
794
|
-
async def run_and_print():
|
795
|
-
async for response in blueprint.run(messages, model=args.model):
|
796
|
-
if isinstance(response, dict) and 'content' in response:
|
797
|
-
print(response['content'], end="")
|
798
|
-
else:
|
799
|
-
print(response, end="")
|
800
|
-
asyncio.run(run_and_print())
|
801
|
-
|
802
|
-
if __name__ == "__main__":
|
803
|
-
main()
|