snippbot 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_gateway/__init__.py +16 -0
- claude_gateway/__main__.py +11 -0
- claude_gateway/claude/__init__.py +14 -0
- claude_gateway/claude/cli.py +174 -0
- claude_gateway/claude/parser.py +295 -0
- claude_gateway/config.py +428 -0
- claude_gateway/data/__init__.py +15 -0
- claude_gateway/data/stores.py +82 -0
- claude_gateway/handlers/__init__.py +5 -0
- claude_gateway/handlers/agents.py +1383 -0
- claude_gateway/handlers/anthropic.py +93 -0
- claude_gateway/handlers/artifacts.py +246 -0
- claude_gateway/handlers/awakening.py +357 -0
- claude_gateway/handlers/base.py +111 -0
- claude_gateway/handlers/embeddings.py +103 -0
- claude_gateway/handlers/health.py +104 -0
- claude_gateway/handlers/openai.py +393 -0
- claude_gateway/handlers/subagent.py +84 -0
- claude_gateway/logging.py +37 -0
- claude_gateway/models.py +143 -0
- claude_gateway/pipe_read.py +61 -0
- claude_gateway/security.py +79 -0
- claude_gateway/server.py +256 -0
- claude_gateway/ssh/__init__.py +21 -0
- claude_gateway/ssh/command.py +31 -0
- claude_gateway/ssh/file_access.py +488 -0
- claude_gateway/streaming/__init__.py +12 -0
- claude_gateway/streaming/sse.py +56 -0
- claude_gateway/streaming/state_machine.py +174 -0
- snippbot/__init__.py +18 -0
- snippbot/__main__.py +6 -0
- snippbot/api/__init__.py +16 -0
- snippbot/api/agents.py +603 -0
- snippbot/api/api_handler.py +2611 -0
- snippbot/api/approval.py +111 -0
- snippbot/api/assets.py +629 -0
- snippbot/api/audio.py +276 -0
- snippbot/api/auth_helpers.py +79 -0
- snippbot/api/browser.py +507 -0
- snippbot/api/browser_ws.py +214 -0
- snippbot/api/channels.py +1097 -0
- snippbot/api/chat.py +3815 -0
- snippbot/api/chat_files.py +366 -0
- snippbot/api/custom_providers.py +518 -0
- snippbot/api/device_auth.py +480 -0
- snippbot/api/device_ws.py +43 -0
- snippbot/api/devices.py +1537 -0
- snippbot/api/dispatcher.py +369 -0
- snippbot/api/email.py +114 -0
- snippbot/api/equipment.py +177 -0
- snippbot/api/events_ws.py +98 -0
- snippbot/api/execution.py +2761 -0
- snippbot/api/files.py +351 -0
- snippbot/api/game_engine.py +438 -0
- snippbot/api/game_saves.py +166 -0
- snippbot/api/health.py +117 -0
- snippbot/api/hooks.py +825 -0
- snippbot/api/image_gen.py +307 -0
- snippbot/api/insights.py +580 -0
- snippbot/api/internal_token.py +74 -0
- snippbot/api/issues.py +711 -0
- snippbot/api/license.py +96 -0
- snippbot/api/marketplace.py +3753 -0
- snippbot/api/marketplace_auth.py +171 -0
- snippbot/api/marketplace_oauth.py +315 -0
- snippbot/api/mcp_refresh.py +264 -0
- snippbot/api/memory_capture.py +402 -0
- snippbot/api/memory_maintenance.py +349 -0
- snippbot/api/monitor.py +241 -0
- snippbot/api/nodes.py +263 -0
- snippbot/api/package_builder.py +33 -0
- snippbot/api/package_credentials.py +254 -0
- snippbot/api/recall_feedback_task.py +99 -0
- snippbot/api/reflection_capture.py +819 -0
- snippbot/api/remote_sessions.py +796 -0
- snippbot/api/router_settings.py +80 -0
- snippbot/api/routes.py +13161 -0
- snippbot/api/sandbox.py +706 -0
- snippbot/api/scenarios.py +129 -0
- snippbot/api/scheduler.py +981 -0
- snippbot/api/security.py +318 -0
- snippbot/api/setup.py +1396 -0
- snippbot/api/skill_builder.py +1751 -0
- snippbot/api/sub_agents.py +793 -0
- snippbot/api/sync.py +108 -0
- snippbot/api/thinking.py +730 -0
- snippbot/api/tool_metadata_cache.py +145 -0
- snippbot/api/wallet.py +846 -0
- snippbot/api/workflows.py +843 -0
- snippbot/api/workspaces.py +435 -0
- snippbot/api/world.py +334 -0
- snippbot/api/world_map.py +211 -0
- snippbot/channel_adapter/__init__.py +5 -0
- snippbot/channel_adapter/__main__.py +92 -0
- snippbot/channel_adapter/adapters/__init__.py +47 -0
- snippbot/channel_adapter/adapters/discord.py +376 -0
- snippbot/channel_adapter/adapters/email.py +503 -0
- snippbot/channel_adapter/adapters/google_chat.py +504 -0
- snippbot/channel_adapter/adapters/slack.py +311 -0
- snippbot/channel_adapter/adapters/teams.py +312 -0
- snippbot/channel_adapter/adapters/telegram.py +377 -0
- snippbot/channel_adapter/adapters/whatsapp.py +286 -0
- snippbot/channel_adapter/app.py +500 -0
- snippbot/channel_adapter/attachments.py +92 -0
- snippbot/channel_adapter/base.py +146 -0
- snippbot/channel_adapter/conversations.py +256 -0
- snippbot/channel_adapter/dispatch.py +332 -0
- snippbot/channel_adapter/loader.py +190 -0
- snippbot/channel_adapter/manager.py +120 -0
- snippbot/channel_adapter/models.py +85 -0
- snippbot/channel_adapter/rate_limiter.py +101 -0
- snippbot/channel_adapter/router.py +161 -0
- snippbot/channel_adapter/space_history.py +182 -0
- snippbot/channel_adapter/tunnel.py +134 -0
- snippbot/cli.py +11 -0
- snippbot/daemon.py +435 -0
- snippbot/discovery.py +117 -0
- snippbot/server.py +2636 -0
- snippbot/ui/_x9b404ae321ef/snake.html +283 -0
- snippbot/ui/_x9b404ae321ef/space-platformer.html +1132 -0
- snippbot/ui/_x9b404ae321ef/space_jumper.html +904 -0
- snippbot/ui/_x9b404ae321ef/tower-defense.html +928 -0
- snippbot/ui/assets/BrowserPage-B134jSc9.js +14 -0
- snippbot/ui/assets/BrowserPage-B134jSc9.js.map +1 -0
- snippbot/ui/assets/DeviceDetailPage-B1CdI77u.js +2 -0
- snippbot/ui/assets/DeviceDetailPage-B1CdI77u.js.map +1 -0
- snippbot/ui/assets/DevicesPage-CfhY-0tL.js +6 -0
- snippbot/ui/assets/DevicesPage-CfhY-0tL.js.map +1 -0
- snippbot/ui/assets/SandboxPage-B9h2NGJP.js +27 -0
- snippbot/ui/assets/SandboxPage-B9h2NGJP.js.map +1 -0
- snippbot/ui/assets/SecurityDashboardPage-DOjO-MVc.js +2 -0
- snippbot/ui/assets/SecurityDashboardPage-DOjO-MVc.js.map +1 -0
- snippbot/ui/assets/StepOutputPreview-B5gWdezJ.js +13 -0
- snippbot/ui/assets/StepOutputPreview-B5gWdezJ.js.map +1 -0
- snippbot/ui/assets/StepOutputPreview-BZV40eAE.css +1 -0
- snippbot/ui/assets/WorkflowBuilderPage-Da_pwRhO.js +367 -0
- snippbot/ui/assets/WorkflowBuilderPage-Da_pwRhO.js.map +1 -0
- snippbot/ui/assets/WorkflowRunPage-CgcCiDo_.js +2 -0
- snippbot/ui/assets/WorkflowRunPage-CgcCiDo_.js.map +1 -0
- snippbot/ui/assets/WorkflowsPage-Q0cIvHFm.js +2 -0
- snippbot/ui/assets/WorkflowsPage-Q0cIvHFm.js.map +1 -0
- snippbot/ui/assets/index-C54biiMe.js +2 -0
- snippbot/ui/assets/index-C54biiMe.js.map +1 -0
- snippbot/ui/assets/index-D7ZcuCt6.css +1 -0
- snippbot/ui/assets/index-FRfh1_if.js +1234 -0
- snippbot/ui/assets/index-FRfh1_if.js.map +1 -0
- snippbot/ui/assets/logo-alt-D_oF903h.png +0 -0
- snippbot/ui/assets/logo-alt-icon-Dt-XLW7h.png +0 -0
- snippbot/ui/assets/logo-d_fNF9Hk.png +0 -0
- snippbot/ui/assets/logo-icon-C0v6NQ9j.png +0 -0
- snippbot/ui/assets/state-BOtVR23t.js +26 -0
- snippbot/ui/assets/state-BOtVR23t.js.map +1 -0
- snippbot/ui/assets/stepIcons-BL48dla-.js +72 -0
- snippbot/ui/assets/stepIcons-BL48dla-.js.map +1 -0
- snippbot/ui/assets/ui-BDp1HqTB.js +10 -0
- snippbot/ui/assets/ui-BDp1HqTB.js.map +1 -0
- snippbot/ui/assets/vendor-C1G_MATc.js +60 -0
- snippbot/ui/assets/vendor-C1G_MATc.js.map +1 -0
- snippbot/ui/audio/ambient-setup.mp3 +0 -0
- snippbot/ui/favicon.png +0 -0
- snippbot/ui/frames/common.png +0 -0
- snippbot/ui/frames/epic.png +0 -0
- snippbot/ui/frames/legendary.png +0 -0
- snippbot/ui/frames/rare.png +0 -0
- snippbot/ui/frames/uncommon.png +0 -0
- snippbot/ui/game/title-bg.png +0 -0
- snippbot/ui/index.html +28 -0
- snippbot/ui/manifest.json +48 -0
- snippbot/ui/old_frames/common.png +0 -0
- snippbot/ui/old_frames/epic.png +0 -0
- snippbot/ui/old_frames/legendary.png +0 -0
- snippbot/ui/old_frames/rare.png +0 -0
- snippbot/ui/old_frames/uncommon.png +0 -0
- snippbot/ui/sw.js +137 -0
- snippbot/wallet/__init__.py +34 -0
- snippbot/wallet/manager.py +656 -0
- snippbot/websocket/__init__.py +1 -0
- snippbot/websocket/websocket_handler.py +640 -0
- snippbot-0.1.0.dist-info/METADATA +191 -0
- snippbot-0.1.0.dist-info/RECORD +740 -0
- snippbot-0.1.0.dist-info/WHEEL +4 -0
- snippbot-0.1.0.dist-info/entry_points.txt +2 -0
- snippbot_cli/__init__.py +7 -0
- snippbot_cli/checks.py +696 -0
- snippbot_cli/commands/__init__.py +1 -0
- snippbot_cli/commands/agents.py +200 -0
- snippbot_cli/commands/auth.py +361 -0
- snippbot_cli/commands/channel.py +147 -0
- snippbot_cli/commands/completions.py +102 -0
- snippbot_cli/commands/config.py +168 -0
- snippbot_cli/commands/device.py +241 -0
- snippbot_cli/commands/dispatcher.py +547 -0
- snippbot_cli/commands/doctor.py +148 -0
- snippbot_cli/commands/export.py +133 -0
- snippbot_cli/commands/marketplace.py +2686 -0
- snippbot_cli/commands/project.py +195 -0
- snippbot_cli/commands/reset.py +660 -0
- snippbot_cli/commands/secrets.py +263 -0
- snippbot_cli/commands/security.py +432 -0
- snippbot_cli/commands/service.py +312 -0
- snippbot_cli/commands/setup.py +366 -0
- snippbot_cli/commands/start.py +138 -0
- snippbot_cli/commands/status.py +92 -0
- snippbot_cli/commands/stop.py +167 -0
- snippbot_cli/commands/uninstall.py +196 -0
- snippbot_cli/daemon_client.py +101 -0
- snippbot_cli/main.py +111 -0
- snippbot_cli/user_config.py +437 -0
- snippbot_core/__init__.py +22 -0
- snippbot_core/agent_settings.py +188 -0
- snippbot_core/approvals.py +513 -0
- snippbot_core/assets/__init__.py +10 -0
- snippbot_core/assets/folder_store.py +403 -0
- snippbot_core/assets/store.py +383 -0
- snippbot_core/audio/__init__.py +15 -0
- snippbot_core/audio/models.py +98 -0
- snippbot_core/audio/providers/__init__.py +4 -0
- snippbot_core/audio/providers/base.py +55 -0
- snippbot_core/audio/providers/elevenlabs_stt.py +88 -0
- snippbot_core/audio/providers/elevenlabs_tts.py +153 -0
- snippbot_core/audio/providers/hume_tts.py +172 -0
- snippbot_core/audio/providers/local_stt.py +106 -0
- snippbot_core/audio/providers/local_tts.py +164 -0
- snippbot_core/audio/providers/openai_stt.py +76 -0
- snippbot_core/audio/providers/openai_tts.py +110 -0
- snippbot_core/audio/service.py +191 -0
- snippbot_core/audio/settings.py +96 -0
- snippbot_core/audit_service.py +367 -0
- snippbot_core/autonomy/__init__.py +22 -0
- snippbot_core/autonomy/bridge.py +140 -0
- snippbot_core/browser_settings.py +306 -0
- snippbot_core/capability_inference.py +131 -0
- snippbot_core/channel_store.py +1054 -0
- snippbot_core/chat/__init__.py +78 -0
- snippbot_core/chat/agent_store.py +588 -0
- snippbot_core/chat/agent_sync.py +244 -0
- snippbot_core/chat/chat_store.py +1438 -0
- snippbot_core/chat/command_processor.py +684 -0
- snippbot_core/chat/complexity_classifier.py +126 -0
- snippbot_core/chat/context_builder.py +721 -0
- snippbot_core/chat/context_refs.py +626 -0
- snippbot_core/chat/context_window.py +1500 -0
- snippbot_core/chat/gateway_client.py +626 -0
- snippbot_core/chat/model_router.py +224 -0
- snippbot_core/chat/models.py +190 -0
- snippbot_core/chat/prepare.py +1124 -0
- snippbot_core/chat/providers/__init__.py +33 -0
- snippbot_core/chat/providers/anthropic.py +213 -0
- snippbot_core/chat/providers/anthropic_api.py +846 -0
- snippbot_core/chat/providers/auth_checks.py +57 -0
- snippbot_core/chat/providers/base.py +120 -0
- snippbot_core/chat/providers/claude_native.py +2073 -0
- snippbot_core/chat/providers/custom.py +391 -0
- snippbot_core/chat/providers/deepseek.py +64 -0
- snippbot_core/chat/providers/gemini.py +577 -0
- snippbot_core/chat/providers/grok.py +25 -0
- snippbot_core/chat/providers/groq.py +23 -0
- snippbot_core/chat/providers/mistral.py +19 -0
- snippbot_core/chat/providers/openai.py +19 -0
- snippbot_core/chat/providers/openai_compat.py +522 -0
- snippbot_core/chat/providers/openrouter.py +36 -0
- snippbot_core/chat/providers/process_manager.py +538 -0
- snippbot_core/chat/providers/registry.py +473 -0
- snippbot_core/chat/roster.py +503 -0
- snippbot_core/chat/router_settings.py +277 -0
- snippbot_core/chat/routing.py +226 -0
- snippbot_core/chat/runtime_context.py +230 -0
- snippbot_core/chat/session.py +1001 -0
- snippbot_core/chat/session_context.py +90 -0
- snippbot_core/chat/sse.py +74 -0
- snippbot_core/chat/token_counter.py +426 -0
- snippbot_core/chat/tool_dispatch.py +364 -0
- snippbot_core/chat/tool_profile_merge.py +42 -0
- snippbot_core/chat/tool_result_aging.py +334 -0
- snippbot_core/chat/turn.py +1004 -0
- snippbot_core/chat/turn_orchestrator.py +113 -0
- snippbot_core/claude_cli.py +140 -0
- snippbot_core/compat.py +26 -0
- snippbot_core/config.py +772 -0
- snippbot_core/dag_builder.py +796 -0
- snippbot_core/db/__init__.py +17 -0
- snippbot_core/db/migrations.py +239 -0
- snippbot_core/device/__init__.py +25 -0
- snippbot_core/device/auth.py +1114 -0
- snippbot_core/device/capabilities.py +320 -0
- snippbot_core/device/execution.py +274 -0
- snippbot_core/device/file_transfer.py +507 -0
- snippbot_core/device/groups.py +251 -0
- snippbot_core/device/health.py +230 -0
- snippbot_core/device/manager.py +232 -0
- snippbot_core/device/pairing.py +380 -0
- snippbot_core/device/protocol.py +1499 -0
- snippbot_core/device/queue.py +193 -0
- snippbot_core/device/registry.py +1633 -0
- snippbot_core/device/router.py +278 -0
- snippbot_core/digest/__init__.py +18 -0
- snippbot_core/digest/composer.py +158 -0
- snippbot_core/digest/scheduler.py +187 -0
- snippbot_core/digest/store.py +189 -0
- snippbot_core/dispatch/__init__.py +131 -0
- snippbot_core/dispatch/bridge.py +347 -0
- snippbot_core/dispatch/decision_log.py +230 -0
- snippbot_core/dispatch/dispatcher.py +376 -0
- snippbot_core/dispatch/executors.py +447 -0
- snippbot_core/dispatch/idempotency.py +95 -0
- snippbot_core/dispatch/inject.py +244 -0
- snippbot_core/dispatch/policy_gate.py +82 -0
- snippbot_core/dispatch/proactive.py +379 -0
- snippbot_core/dispatch/proactivity_gate.py +92 -0
- snippbot_core/dispatch/settings.py +459 -0
- snippbot_core/dispatch/shadow.py +246 -0
- snippbot_core/dispatch/tier1_deterministic.py +100 -0
- snippbot_core/dispatch/tier2_policy.py +65 -0
- snippbot_core/dispatch/tier3_classifier.py +359 -0
- snippbot_core/dispatch/tier3_provider.py +386 -0
- snippbot_core/dispatch/tier4_fallback.py +67 -0
- snippbot_core/dispatch/types.py +218 -0
- snippbot_core/email/__init__.py +32 -0
- snippbot_core/email/config.py +29 -0
- snippbot_core/email/imap_client.py +317 -0
- snippbot_core/email/insight_email_scheduler.py +169 -0
- snippbot_core/email/service.py +156 -0
- snippbot_core/email/store.py +187 -0
- snippbot_core/email/subscriber.py +147 -0
- snippbot_core/email/templates.py +195 -0
- snippbot_core/event_bus.py +342 -0
- snippbot_core/events.py +1271 -0
- snippbot_core/execution_orchestrator.py +1401 -0
- snippbot_core/execution_store.py +1225 -0
- snippbot_core/executor.py +682 -0
- snippbot_core/files/__init__.py +18 -0
- snippbot_core/files/models.py +81 -0
- snippbot_core/files/pdf_extractor.py +77 -0
- snippbot_core/files/storage.py +201 -0
- snippbot_core/files/store.py +373 -0
- snippbot_core/game/__init__.py +10 -0
- snippbot_core/game/combat.py +624 -0
- snippbot_core/game/engine.py +358 -0
- snippbot_core/game/game_save_store.py +258 -0
- snippbot_core/game/gm_prompts.py +365 -0
- snippbot_core/game/world_store.py +1033 -0
- snippbot_core/history.py +797 -0
- snippbot_core/hooks/__init__.py +46 -0
- snippbot_core/hooks/analytics.py +346 -0
- snippbot_core/hooks/bridge.py +105 -0
- snippbot_core/hooks/bundled/__init__.py +122 -0
- snippbot_core/hooks/bundled/audit_logger.py +111 -0
- snippbot_core/hooks/bundled/boot_md.py +149 -0
- snippbot_core/hooks/bundled/context_files.py +144 -0
- snippbot_core/hooks/bundled/dispatcher_audit.py +93 -0
- snippbot_core/hooks/bundled/session_memory.py +110 -0
- snippbot_core/hooks/chains.py +208 -0
- snippbot_core/hooks/dispatcher.py +314 -0
- snippbot_core/hooks/engine.py +531 -0
- snippbot_core/hooks/executor.py +530 -0
- snippbot_core/hooks/filters.py +314 -0
- snippbot_core/hooks/models.py +451 -0
- snippbot_core/hooks/sandbox.py +222 -0
- snippbot_core/hooks/sdk.py +280 -0
- snippbot_core/hooks/store.py +1528 -0
- snippbot_core/hooks/webhook_auth.py +237 -0
- snippbot_core/idle_messages.py +59 -0
- snippbot_core/image/__init__.py +0 -0
- snippbot_core/image/generate.py +149 -0
- snippbot_core/image/ocr.py +113 -0
- snippbot_core/image/scene_prompts.py +129 -0
- snippbot_core/image/vision.py +324 -0
- snippbot_core/insight_generator.py +2176 -0
- snippbot_core/insight_queue.py +1278 -0
- snippbot_core/interface_settings.py +263 -0
- snippbot_core/issues/__init__.py +50 -0
- snippbot_core/issues/investigator.py +471 -0
- snippbot_core/issues/magic_link.py +86 -0
- snippbot_core/issues/models.py +314 -0
- snippbot_core/issues/notifier.py +175 -0
- snippbot_core/issues/security_audit.py +295 -0
- snippbot_core/issues/snapshot.py +67 -0
- snippbot_core/issues/store.py +1056 -0
- snippbot_core/legacy_data_migration.py +290 -0
- snippbot_core/license/__init__.py +14 -0
- snippbot_core/license/models.py +103 -0
- snippbot_core/license/store.py +136 -0
- snippbot_core/license/validator.py +155 -0
- snippbot_core/marketplace/__init__.py +64 -0
- snippbot_core/marketplace/agent_config_support.py +428 -0
- snippbot_core/marketplace/auto_updater.py +636 -0
- snippbot_core/marketplace/bundled.py +236 -0
- snippbot_core/marketplace/channel_support.py +201 -0
- snippbot_core/marketplace/essentials.py +45 -0
- snippbot_core/marketplace/essentials.toml +29 -0
- snippbot_core/marketplace/hook_support.py +302 -0
- snippbot_core/marketplace/ignore.py +203 -0
- snippbot_core/marketplace/installer.py +2015 -0
- snippbot_core/marketplace/job_support.py +251 -0
- snippbot_core/marketplace/manifest.py +953 -0
- snippbot_core/marketplace/manifest_facts.py +333 -0
- snippbot_core/marketplace/mcp_support.py +195 -0
- snippbot_core/marketplace/oauth_manager.py +534 -0
- snippbot_core/marketplace/oauth_providers.py +83 -0
- snippbot_core/marketplace/packaging.py +522 -0
- snippbot_core/marketplace/permission_grants.py +377 -0
- snippbot_core/marketplace/registry_store.py +428 -0
- snippbot_core/marketplace/sandbox_support.py +359 -0
- snippbot_core/marketplace/tool_support.py +997 -0
- snippbot_core/marketplace/workflow_support.py +828 -0
- snippbot_core/mcp/__init__.py +27 -0
- snippbot_core/mcp/bridge_registry.py +140 -0
- snippbot_core/mcp/catalog.py +202 -0
- snippbot_core/mcp/catalog_data.json +492 -0
- snippbot_core/mcp/client.py +651 -0
- snippbot_core/mcp/manager.py +368 -0
- snippbot_core/mcp/oauth.py +195 -0
- snippbot_core/mcp/sandbox.py +408 -0
- snippbot_core/mcp/tool_bridge_server.py +786 -0
- snippbot_core/memory/__init__.py +97 -0
- snippbot_core/memory/audit_log.py +338 -0
- snippbot_core/memory/clustering.py +349 -0
- snippbot_core/memory/consolidation.py +822 -0
- snippbot_core/memory/episodic.py +1100 -0
- snippbot_core/memory/forgetting.py +644 -0
- snippbot_core/memory/hybrid_search.py +697 -0
- snippbot_core/memory/keyword_search.py +622 -0
- snippbot_core/memory/knowledge_graph.py +1550 -0
- snippbot_core/memory/llm_extraction.py +718 -0
- snippbot_core/memory/recall_feedback.py +203 -0
- snippbot_core/memory/sensory_buffer.py +214 -0
- snippbot_core/memory/session.py +143 -0
- snippbot_core/memory/vector_index.py +743 -0
- snippbot_core/memory/write_pipeline.py +628 -0
- snippbot_core/memory_search.py +341 -0
- snippbot_core/memory_settings.py +482 -0
- snippbot_core/models_dev_catalog.py +387 -0
- snippbot_core/multimodal/__init__.py +44 -0
- snippbot_core/multimodal/image_blocks.py +230 -0
- snippbot_core/node_registry.py +399 -0
- snippbot_core/package_builder/__init__.py +70 -0
- snippbot_core/package_builder/engine.py +2116 -0
- snippbot_core/package_builder/fix_verifier.py +510 -0
- snippbot_core/package_builder/packager.py +100 -0
- snippbot_core/package_builder/plan.py +680 -0
- snippbot_core/package_builder/plugins/__init__.py +190 -0
- snippbot_core/package_builder/plugins/hook_plugin.py +389 -0
- snippbot_core/package_builder/plugins/mcp_server_plugin.py +497 -0
- snippbot_core/package_builder/plugins/tool_plugin.py +535 -0
- snippbot_core/package_builder/plugins/workflow_plugin.py +732 -0
- snippbot_core/package_builder/quality_audit.py +547 -0
- snippbot_core/package_builder/rate_limiter.py +242 -0
- snippbot_core/package_builder/sandbox.py +399 -0
- snippbot_core/package_builder/session.py +424 -0
- snippbot_core/package_builder/spec.py +246 -0
- snippbot_core/package_builder/store.py +582 -0
- snippbot_core/package_builder/system_prompt.py +561 -0
- snippbot_core/package_builder/telemetry.py +234 -0
- snippbot_core/payments/__init__.py +26 -0
- snippbot_core/payments/spend_authorization.py +231 -0
- snippbot_core/permissions.py +1099 -0
- snippbot_core/proactivity.py +975 -0
- snippbot_core/proactivity_settings.py +142 -0
- snippbot_core/profile_settings.py +299 -0
- snippbot_core/project_store.py +1349 -0
- snippbot_core/projects/__init__.py +7 -0
- snippbot_core/projects/plan_buffer.py +177 -0
- snippbot_core/projects/refine_agent.py +597 -0
- snippbot_core/projects/refine_lock.py +82 -0
- snippbot_core/projects/refine_session_registry.py +99 -0
- snippbot_core/projects/refine_tools.py +177 -0
- snippbot_core/provider_settings.py +555 -0
- snippbot_core/provider_sync.py +1082 -0
- snippbot_core/provider_sync_task.py +150 -0
- snippbot_core/providers.py +141 -0
- snippbot_core/push/__init__.py +30 -0
- snippbot_core/push/models.py +147 -0
- snippbot_core/push/service.py +393 -0
- snippbot_core/push/store.py +451 -0
- snippbot_core/py.typed +0 -0
- snippbot_core/remote_session/__init__.py +111 -0
- snippbot_core/remote_session/fan_out.py +287 -0
- snippbot_core/remote_session/manager.py +1010 -0
- snippbot_core/remote_session/models.py +361 -0
- snippbot_core/remote_session/security_gate.py +753 -0
- snippbot_core/remote_session/security_store.py +1393 -0
- snippbot_core/remote_session/settings.py +306 -0
- snippbot_core/remote_session/store.py +1041 -0
- snippbot_core/risk_scorer.py +512 -0
- snippbot_core/sandbox/__init__.py +66 -0
- snippbot_core/sandbox/audit.py +630 -0
- snippbot_core/sandbox/config.py +400 -0
- snippbot_core/sandbox/dockerfiles/Dockerfile.base +7 -0
- snippbot_core/sandbox/dockerfiles/Dockerfile.datascience +11 -0
- snippbot_core/sandbox/dockerfiles/Dockerfile.node +9 -0
- snippbot_core/sandbox/dockerfiles/Dockerfile.python +9 -0
- snippbot_core/sandbox/dockerfiles/Dockerfile.rust +9 -0
- snippbot_core/sandbox/gpu.py +311 -0
- snippbot_core/sandbox/manager.py +1317 -0
- snippbot_core/sandbox/network.py +863 -0
- snippbot_core/sandbox/pool.py +242 -0
- snippbot_core/sandbox/runtime/__init__.py +20 -0
- snippbot_core/sandbox/runtime/base.py +390 -0
- snippbot_core/sandbox/runtime/docker.py +1137 -0
- snippbot_core/sandbox/runtime/podman.py +1207 -0
- snippbot_core/sandbox/runtime/process.py +985 -0
- snippbot_core/sandbox/smart.py +205 -0
- snippbot_core/sandbox/snapshot.py +520 -0
- snippbot_core/sandbox/templates.py +894 -0
- snippbot_core/scheduler/__init__.py +31 -0
- snippbot_core/scheduler/chains.py +372 -0
- snippbot_core/scheduler/chat_commands.py +247 -0
- snippbot_core/scheduler/conditions.py +521 -0
- snippbot_core/scheduler/delivery.py +1058 -0
- snippbot_core/scheduler/detector_settings.py +158 -0
- snippbot_core/scheduler/engine.py +652 -0
- snippbot_core/scheduler/executor.py +953 -0
- snippbot_core/scheduler/models.py +493 -0
- snippbot_core/scheduler/nl_parser.py +716 -0
- snippbot_core/scheduler/nl_patterns.py +220 -0
- snippbot_core/scheduler/retry.py +102 -0
- snippbot_core/scheduler/schedule_types.py +431 -0
- snippbot_core/scheduler/sessions.py +174 -0
- snippbot_core/scheduler/skills_loader.py +366 -0
- snippbot_core/scheduler/smart.py +203 -0
- snippbot_core/scheduler/store.py +1382 -0
- snippbot_core/security/__init__.py +219 -0
- snippbot_core/security/csrf.py +323 -0
- snippbot_core/security/db_encryption.py +420 -0
- snippbot_core/security/dep_scanner.py +233 -0
- snippbot_core/security/dlp.py +374 -0
- snippbot_core/security/egress.py +312 -0
- snippbot_core/security/env_filter.py +157 -0
- snippbot_core/security/error_sanitizer.py +57 -0
- snippbot_core/security/file_permissions.py +267 -0
- snippbot_core/security/input_validator.py +155 -0
- snippbot_core/security/master_key.py +303 -0
- snippbot_core/security/mcp_validation.py +178 -0
- snippbot_core/security/package_audit.py +1176 -0
- snippbot_core/security/package_signing.py +631 -0
- snippbot_core/security/prompt_injection.py +527 -0
- snippbot_core/security/rate_limiter.py +348 -0
- snippbot_core/security/registry_security.py +281 -0
- snippbot_core/security/sandbox_profile.py +314 -0
- snippbot_core/security/scanner.py +1027 -0
- snippbot_core/security/secret_store.py +578 -0
- snippbot_core/security/skill_vetting.py +551 -0
- snippbot_core/security/tls.py +192 -0
- snippbot_core/security/url_validation.py +259 -0
- snippbot_core/security/workflow_audit.py +406 -0
- snippbot_core/security_settings.py +339 -0
- snippbot_core/settings_manager.py +279 -0
- snippbot_core/settings_store.py +79 -0
- snippbot_core/skill_builder/__init__.py +53 -0
- snippbot_core/skill_builder/engine.py +13 -0
- snippbot_core/skill_builder/packager.py +7 -0
- snippbot_core/skill_builder/session.py +7 -0
- snippbot_core/skill_builder/spec.py +7 -0
- snippbot_core/skill_builder/system_prompt.py +7 -0
- snippbot_core/skills_store.py +950 -0
- snippbot_core/snipp/__init__.py +110 -0
- snippbot_core/snipp/balance.py +403 -0
- snippbot_core/snipp/encrypted_storage.py +482 -0
- snippbot_core/snipp/recovery_phrase.py +459 -0
- snippbot_core/snipp/signing.py +478 -0
- snippbot_core/snipp/wallet.py +340 -0
- snippbot_core/strategy.py +231 -0
- snippbot_core/sub_agent/__init__.py +57 -0
- snippbot_core/sub_agent/aggregator.py +376 -0
- snippbot_core/sub_agent/concurrency.py +200 -0
- snippbot_core/sub_agent/event_digest.py +176 -0
- snippbot_core/sub_agent/lifecycle.py +534 -0
- snippbot_core/sub_agent/messaging.py +336 -0
- snippbot_core/sub_agent/models.py +545 -0
- snippbot_core/sub_agent/orchestrator.py +639 -0
- snippbot_core/sub_agent/resource_manager.py +227 -0
- snippbot_core/sub_agent/role_prompts.py +242 -0
- snippbot_core/sub_agent/session.py +1340 -0
- snippbot_core/sub_agent/shared_context.py +238 -0
- snippbot_core/sub_agent/spawner.py +264 -0
- snippbot_core/sub_agent/store.py +987 -0
- snippbot_core/sub_agent/team_models.py +123 -0
- snippbot_core/sub_agent/team_prompts.py +188 -0
- snippbot_core/subprocess_utils.py +80 -0
- snippbot_core/sync.py +222 -0
- snippbot_core/task_executor.py +1703 -0
- snippbot_core/thinking/__init__.py +25 -0
- snippbot_core/thinking/daemon.py +955 -0
- snippbot_core/thinking/engagement.py +261 -0
- snippbot_core/thinking/engine.py +632 -0
- snippbot_core/thinking/models.py +251 -0
- snippbot_core/thinking/store.py +626 -0
- snippbot_core/thinking/triggers.py +193 -0
- snippbot_core/tier_inference.py +187 -0
- snippbot_core/tools/__init__.py +22 -0
- snippbot_core/tools/_python_dispatcher.py +226 -0
- snippbot_core/tools/browser/__init__.py +14 -0
- snippbot_core/tools/browser/actions.py +438 -0
- snippbot_core/tools/browser/auth_manager.py +439 -0
- snippbot_core/tools/browser/device_emulation.py +378 -0
- snippbot_core/tools/browser/dom_snapshot.py +722 -0
- snippbot_core/tools/browser/exceptions.py +126 -0
- snippbot_core/tools/browser/file_handler.py +261 -0
- snippbot_core/tools/browser/live_stream.py +293 -0
- snippbot_core/tools/browser/manager.py +884 -0
- snippbot_core/tools/browser/network_manager.py +511 -0
- snippbot_core/tools/browser/profiles.py +438 -0
- snippbot_core/tools/browser/recorder.py +514 -0
- snippbot_core/tools/browser/screen_recorder.py +995 -0
- snippbot_core/tools/browser/ssrf_guard.py +331 -0
- snippbot_core/tools/browser/stealth.py +141 -0
- snippbot_core/tools/browser/tab_manager.py +277 -0
- snippbot_core/tools/browser_manager.py +9 -0
- snippbot_core/tools/channel_sender.py +406 -0
- snippbot_core/tools/definitions.py +1681 -0
- snippbot_core/tools/execution_mode.py +104 -0
- snippbot_core/tools/executor.py +4917 -0
- snippbot_core/tools/invocation_log.py +182 -0
- snippbot_core/tools/marketplace_types.py +39 -0
- snippbot_core/tools/registry.py +584 -0
- snippbot_core/tools/search_providers.py +349 -0
- snippbot_core/tools/summarization.py +255 -0
- snippbot_core/tools/workflow_dispatch.py +296 -0
- snippbot_core/user_model.py +1709 -0
- snippbot_core/worker_pool.py +118 -0
- snippbot_core/workflows/__init__.py +51 -0
- snippbot_core/workflows/dry_run.py +211 -0
- snippbot_core/workflows/dsl.py +365 -0
- snippbot_core/workflows/executor.py +825 -0
- snippbot_core/workflows/models.py +675 -0
- snippbot_core/workflows/parser.py +144 -0
- snippbot_core/workflows/run_store.py +556 -0
- snippbot_core/workflows/scheduler.py +455 -0
- snippbot_core/workflows/schema.py +616 -0
- snippbot_core/workflows/state_machine.py +277 -0
- snippbot_core/workflows/step_executors/__init__.py +502 -0
- snippbot_core/workflows/step_executors/approval_gate.py +197 -0
- snippbot_core/workflows/step_executors/conditional.py +103 -0
- snippbot_core/workflows/step_executors/llm_step.py +405 -0
- snippbot_core/workflows/step_executors/loop_step.py +157 -0
- snippbot_core/workflows/step_executors/parallel_step.py +197 -0
- snippbot_core/workflows/step_executors/subworkflow.py +116 -0
- snippbot_core/workflows/step_executors/tool_step.py +138 -0
- snippbot_core/workflows/store.py +568 -0
- snippbot_core/workflows/template_store.py +590 -0
- snippbot_core/workflows/version_control.py +149 -0
- snippbot_core/workspace/__init__.py +67 -0
- snippbot_core/workspace/evolution.py +278 -0
- snippbot_core/workspace/identity_proposals.py +362 -0
- snippbot_core/workspace/pending_writes.py +256 -0
- snippbot_core/workspace/tools.py +877 -0
- snippbot_core/workspace_manager.py +493 -0
- vps/__init__.py +7 -0
- vps/config/strategy-presets.json +34 -0
- vps/data/.gitkeep +0 -0
- vps/data/memory_schema.sql +80 -0
- vps/data/schema.sql +268 -0
- vps/data/user_model_schema.sql +125 -0
- vps/lib/__init__.py +101 -0
- vps/lib/adaptive_throttle.py +978 -0
- vps/lib/agent_workspace.py +731 -0
- vps/lib/anticipation.py +1266 -0
- vps/lib/api_handler.py +2586 -0
- vps/lib/approvals.py +507 -0
- vps/lib/auth.py +968 -0
- vps/lib/awakening_api.py +639 -0
- vps/lib/benchmark.py +351 -0
- vps/lib/chaos.py +959 -0
- vps/lib/checkpoint.py +497 -0
- vps/lib/config.py +227 -0
- vps/lib/connection_pool.py +658 -0
- vps/lib/credentials.py +841 -0
- vps/lib/daemon.py +437 -0
- vps/lib/dag_builder.py +871 -0
- vps/lib/delight_tracker.py +707 -0
- vps/lib/error_handler.py +880 -0
- vps/lib/event_bus.py +343 -0
- vps/lib/events.py +354 -0
- vps/lib/executor.py +681 -0
- vps/lib/expertise_detector.py +716 -0
- vps/lib/failure_handler.py +946 -0
- vps/lib/failure_learning.py +1196 -0
- vps/lib/feedback_learner.py +1035 -0
- vps/lib/focus_mode.py +1049 -0
- vps/lib/frustration_detector.py +713 -0
- vps/lib/history.py +837 -0
- vps/lib/idle_daemon.py +640 -0
- vps/lib/insight_delivery.py +863 -0
- vps/lib/insight_queue.py +1072 -0
- vps/lib/interest_detector.py +750 -0
- vps/lib/memory/__init__.py +49 -0
- vps/lib/memory/consolidation.py +643 -0
- vps/lib/memory/episodic.py +956 -0
- vps/lib/memory/forgetting.py +644 -0
- vps/lib/memory/hybrid_search.py +697 -0
- vps/lib/memory/keyword_search.py +565 -0
- vps/lib/memory/knowledge_graph.py +1244 -0
- vps/lib/memory/vector_index.py +736 -0
- vps/lib/memory/write_pipeline.py +622 -0
- vps/lib/memory_search.py +328 -0
- vps/lib/metrics.py +515 -0
- vps/lib/parallel_executor.py +871 -0
- vps/lib/permissions.py +1046 -0
- vps/lib/preference_learner.py +714 -0
- vps/lib/proactivity.py +780 -0
- vps/lib/profiling.py +659 -0
- vps/lib/project_store.py +928 -0
- vps/lib/prometheus.py +596 -0
- vps/lib/query_optimizer.py +700 -0
- vps/lib/rate_limiter.py +416 -0
- vps/lib/recovery.py +813 -0
- vps/lib/replay.py +935 -0
- vps/lib/request_validator.py +665 -0
- vps/lib/resource_limits.py +458 -0
- vps/lib/risk_scorer.py +512 -0
- vps/lib/room_reader.py +690 -0
- vps/lib/sandbox.py +1161 -0
- vps/lib/snipp/__init__.py +497 -0
- vps/lib/snipp/aggregation.py +449 -0
- vps/lib/snipp/api_interceptor.py +494 -0
- vps/lib/snipp/audit.py +459 -0
- vps/lib/snipp/balance.py +379 -0
- vps/lib/snipp/encrypted_storage.py +481 -0
- vps/lib/snipp/escrow.py +932 -0
- vps/lib/snipp/rating.py +855 -0
- vps/lib/snipp/receipt.py +400 -0
- vps/lib/snipp/receipt_store.py +556 -0
- vps/lib/snipp/recovery_phrase.py +459 -0
- vps/lib/snipp/reputation_sync.py +544 -0
- vps/lib/snipp/request_router.py +961 -0
- vps/lib/snipp/revision_handler.py +550 -0
- vps/lib/snipp/reward_callback.py +549 -0
- vps/lib/snipp/service_registry.py +812 -0
- vps/lib/snipp/signing.py +368 -0
- vps/lib/snipp/snipp_client.py +567 -0
- vps/lib/snipp/utils.py +152 -0
- vps/lib/snipp/wallet.py +243 -0
- vps/lib/snipp/work_claim.py +550 -0
- vps/lib/speculative.py +804 -0
- vps/lib/strategy.py +231 -0
- vps/lib/style_adapter.py +661 -0
- vps/lib/tool_selector.py +1123 -0
- vps/lib/user_goals_api.py +824 -0
- vps/lib/user_model.py +1565 -0
- vps/lib/websocket_handler.py +545 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Claude Gateway - HTTP Gateway for Claude CLI with Multi-Agent Support
|
|
3
|
+
|
|
4
|
+
A unified gateway server that provides:
|
|
5
|
+
- OpenAI Chat Completions API compatibility (/v1/chat/completions)
|
|
6
|
+
- Anthropic Messages API compatibility (/v1/messages)
|
|
7
|
+
- Multi-agent support with SSH-based file access
|
|
8
|
+
- Real-time streaming via Server-Sent Events (SSE)
|
|
9
|
+
- Tool/function call parsing
|
|
10
|
+
- Embeddings API (with optional sentence-transformers)
|
|
11
|
+
- Artifacts management
|
|
12
|
+
- Awakening experience for onboarding
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
__version__ = "5.1.0"
|
|
16
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Claude CLI integration - subprocess handling and message parsing."""
|
|
2
|
+
|
|
3
|
+
from claude_gateway.claude.cli import call_claude, call_claude_streaming, find_claude_path, get_claude_path
|
|
4
|
+
from claude_gateway.claude.parser import parse_function_calls, messages_to_prompt, tools_to_prompt
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"call_claude",
|
|
8
|
+
"call_claude_streaming",
|
|
9
|
+
"find_claude_path",
|
|
10
|
+
"get_claude_path",
|
|
11
|
+
"parse_function_calls",
|
|
12
|
+
"messages_to_prompt",
|
|
13
|
+
"tools_to_prompt",
|
|
14
|
+
]
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Claude CLI subprocess integration.
|
|
3
|
+
|
|
4
|
+
Handles:
|
|
5
|
+
- Finding the Claude CLI executable
|
|
6
|
+
- Executing non-streaming requests
|
|
7
|
+
- Executing streaming requests with subprocess.Popen
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import shutil
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
import time
|
|
17
|
+
|
|
18
|
+
from claude_gateway.logging import log
|
|
19
|
+
from claude_gateway.models import map_model_name, DEFAULT_SYSTEM_PROMPT
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def find_claude_path() -> str | None:
|
|
23
|
+
"""Find claude CLI, checking env var first, then common locations.
|
|
24
|
+
|
|
25
|
+
Returns the path to the Claude CLI binary, or None if not found.
|
|
26
|
+
"""
|
|
27
|
+
env_path = os.environ.get("CLAUDE_PATH")
|
|
28
|
+
if env_path and os.path.exists(env_path):
|
|
29
|
+
return env_path
|
|
30
|
+
|
|
31
|
+
# Common locations to check
|
|
32
|
+
candidates = [
|
|
33
|
+
os.path.expanduser("~/.local/bin/claude"),
|
|
34
|
+
os.path.expanduser("~/.npm-global/bin/claude"),
|
|
35
|
+
os.path.expanduser("~/node_modules/.bin/claude"),
|
|
36
|
+
os.path.expanduser("~/.npm/bin/claude"),
|
|
37
|
+
"/usr/local/bin/claude",
|
|
38
|
+
os.path.expanduser("~/bin/claude"),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
# On Windows, also check AppData
|
|
42
|
+
if sys.platform == "win32":
|
|
43
|
+
candidates.extend([
|
|
44
|
+
os.path.expanduser("~/AppData/Local/Programs/claude/claude.exe"),
|
|
45
|
+
os.path.expanduser("~/.claude/claude.exe"),
|
|
46
|
+
])
|
|
47
|
+
|
|
48
|
+
for path in candidates:
|
|
49
|
+
if os.path.exists(path) and os.access(path, os.X_OK):
|
|
50
|
+
return path
|
|
51
|
+
|
|
52
|
+
# Try system PATH as last resort
|
|
53
|
+
return shutil.which("claude")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_claude_path() -> str:
|
|
57
|
+
"""Get the Claude CLI path, raising FileNotFoundError if not found."""
|
|
58
|
+
path = find_claude_path()
|
|
59
|
+
if path is None:
|
|
60
|
+
raise FileNotFoundError(
|
|
61
|
+
"Claude CLI not found. Install with: npm install -g @anthropic-ai/claude-code"
|
|
62
|
+
)
|
|
63
|
+
return path
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def call_claude(
|
|
67
|
+
prompt: str,
|
|
68
|
+
model: str,
|
|
69
|
+
system_prompt: str | None = None,
|
|
70
|
+
disable_tools: bool = True,
|
|
71
|
+
image_temp_dir: str | None = None,
|
|
72
|
+
) -> tuple[subprocess.CompletedProcess, float, str]:
|
|
73
|
+
"""Call Claude CLI and return result (non-streaming).
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
prompt: The prompt to send
|
|
77
|
+
model: Model name
|
|
78
|
+
system_prompt: Optional system prompt
|
|
79
|
+
disable_tools: If True, disables internal tool execution so we get raw tool calls
|
|
80
|
+
image_temp_dir: If set, enables Read tool and grants access to this directory
|
|
81
|
+
so Claude can read saved image files.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Tuple of (CompletedProcess, elapsed_seconds, cli_model_name)
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
FileNotFoundError: If Claude CLI is not installed
|
|
88
|
+
"""
|
|
89
|
+
claude_path = get_claude_path()
|
|
90
|
+
cli_model = map_model_name(model)
|
|
91
|
+
cmd = [claude_path, '-p', prompt, '--model', cli_model, '--output-format', 'json']
|
|
92
|
+
|
|
93
|
+
if image_temp_dir:
|
|
94
|
+
# Enable only the Read tool so Claude can view saved image files
|
|
95
|
+
cmd.extend(['--tools', 'Read'])
|
|
96
|
+
cmd.extend(['--add-dir', image_temp_dir])
|
|
97
|
+
elif disable_tools:
|
|
98
|
+
# Prevent CLI from executing tools so we get raw tool_calls in output.
|
|
99
|
+
# NOTE: --tools '' breaks when MCP tools are configured (disables entire
|
|
100
|
+
# tool_use mechanism, causing hangs). Use --max-turns 1 instead — it lets
|
|
101
|
+
# the model produce tool calls normally but stops the CLI from executing them.
|
|
102
|
+
cmd.extend(['--max-turns', '1'])
|
|
103
|
+
|
|
104
|
+
# Use --system-prompt to override Claude Code's default identity
|
|
105
|
+
effective_system = system_prompt if system_prompt else DEFAULT_SYSTEM_PROMPT
|
|
106
|
+
cmd.extend(['--system-prompt', effective_system])
|
|
107
|
+
|
|
108
|
+
start_time = time.time()
|
|
109
|
+
result = subprocess.run(
|
|
110
|
+
cmd,
|
|
111
|
+
capture_output=True,
|
|
112
|
+
text=True,
|
|
113
|
+
encoding="utf-8",
|
|
114
|
+
timeout=300 # 5 minute timeout
|
|
115
|
+
)
|
|
116
|
+
elapsed = time.time() - start_time
|
|
117
|
+
|
|
118
|
+
return result, elapsed, cli_model
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def call_claude_streaming(
|
|
122
|
+
prompt: str,
|
|
123
|
+
model: str,
|
|
124
|
+
system_prompt: str | None = None,
|
|
125
|
+
disable_tools: bool = True,
|
|
126
|
+
image_temp_dir: str | None = None,
|
|
127
|
+
) -> tuple[subprocess.Popen, str]:
|
|
128
|
+
"""Call Claude CLI and return a subprocess with streaming stdout.
|
|
129
|
+
|
|
130
|
+
Returns (process, cli_model). Read process.stdout line by line for
|
|
131
|
+
streaming JSON events. Caller must handle process cleanup.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
prompt: The prompt to send
|
|
135
|
+
model: Model name
|
|
136
|
+
system_prompt: Optional system prompt
|
|
137
|
+
disable_tools: If True, disables internal tool execution so we get raw tool calls
|
|
138
|
+
image_temp_dir: If set, enables Read tool and grants access to this directory
|
|
139
|
+
so Claude can read saved image files.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
FileNotFoundError: If Claude CLI is not installed
|
|
143
|
+
"""
|
|
144
|
+
claude_path = get_claude_path()
|
|
145
|
+
cli_model = map_model_name(model)
|
|
146
|
+
log(f"[CLI] Model mapping: '{model}' -> '{cli_model}'")
|
|
147
|
+
cmd = [
|
|
148
|
+
claude_path, '-p', prompt, '--model', cli_model,
|
|
149
|
+
'--output-format', 'stream-json', '--verbose',
|
|
150
|
+
'--include-partial-messages'
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
if image_temp_dir:
|
|
154
|
+
# Enable only the Read tool so Claude can view saved image files
|
|
155
|
+
cmd.extend(['--tools', 'Read'])
|
|
156
|
+
cmd.extend(['--add-dir', image_temp_dir])
|
|
157
|
+
log(f"[CLI] Image temp dir: {image_temp_dir} — Read tool enabled")
|
|
158
|
+
elif disable_tools:
|
|
159
|
+
# Prevent CLI from executing tools — see call_claude() for rationale
|
|
160
|
+
cmd.extend(['--max-turns', '1'])
|
|
161
|
+
|
|
162
|
+
effective_system = system_prompt if system_prompt else DEFAULT_SYSTEM_PROMPT
|
|
163
|
+
cmd.extend(['--system-prompt', effective_system])
|
|
164
|
+
|
|
165
|
+
process = subprocess.Popen(
|
|
166
|
+
cmd,
|
|
167
|
+
stdout=subprocess.PIPE,
|
|
168
|
+
stderr=subprocess.PIPE,
|
|
169
|
+
text=True,
|
|
170
|
+
encoding="utf-8",
|
|
171
|
+
bufsize=1 # line-buffered
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return process, cli_model
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Message parsing and tool/function call conversion.
|
|
3
|
+
|
|
4
|
+
Handles:
|
|
5
|
+
- Parsing XML function_calls from Claude's response text
|
|
6
|
+
- Converting OpenAI tools array to text description
|
|
7
|
+
- Converting messages array to prompt string
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import base64
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import tempfile
|
|
17
|
+
import uuid
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def parse_function_calls(text: str) -> tuple[str, list[dict]]:
|
|
21
|
+
"""Parse XML function_calls from Claude's response text.
|
|
22
|
+
|
|
23
|
+
Returns tuple: (clean_text, tool_calls_list)
|
|
24
|
+
- clean_text: response with function_calls XML removed
|
|
25
|
+
- tool_calls_list: list of OpenAI-format tool_calls
|
|
26
|
+
"""
|
|
27
|
+
tool_calls = []
|
|
28
|
+
|
|
29
|
+
# Find all <invoke> blocks (can be inside <function_calls> or standalone)
|
|
30
|
+
invoke_pattern = r'<invoke\s+name="([^"]+)">\s*(.*?)\s*</invoke>'
|
|
31
|
+
matches = re.findall(invoke_pattern, text, re.DOTALL)
|
|
32
|
+
|
|
33
|
+
print(f"[PARSER] Found {len(matches)} invoke blocks in text ({len(text)} chars)", flush=True)
|
|
34
|
+
|
|
35
|
+
for i, (func_name, params_block) in enumerate(matches):
|
|
36
|
+
print(f"[PARSER] Invoke {i}: func_name='{func_name}', params_block_len={len(params_block)}", flush=True)
|
|
37
|
+
# Parse parameters - use non-greedy match to handle content with < characters
|
|
38
|
+
# Old pattern [^<]* would truncate at first < in content (breaks heredocs, etc.)
|
|
39
|
+
param_pattern = r'<parameter\s+name="([^"]+)">(.*?)</parameter>'
|
|
40
|
+
params = dict(re.findall(param_pattern, params_block, re.DOTALL))
|
|
41
|
+
print(f"[PARSER] Invoke {i}: parsed {len(params)} params: {list(params.keys())}", flush=True)
|
|
42
|
+
for pname, pval in params.items():
|
|
43
|
+
print(f"[PARSER] param '{pname}': {len(pval)} chars, preview='{pval[:100]}...'", flush=True)
|
|
44
|
+
|
|
45
|
+
tool_call = {
|
|
46
|
+
"id": f"call_{uuid.uuid4().hex[:24]}",
|
|
47
|
+
"type": "function",
|
|
48
|
+
"function": {
|
|
49
|
+
"name": func_name,
|
|
50
|
+
"arguments": json.dumps(params)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
tool_calls.append(tool_call)
|
|
54
|
+
|
|
55
|
+
# Remove all function_calls blocks and invoke blocks from text
|
|
56
|
+
clean_text = text
|
|
57
|
+
# Remove complete function_calls blocks
|
|
58
|
+
clean_text = re.sub(r'<function_calls>.*?</function_calls>', '', clean_text, flags=re.DOTALL)
|
|
59
|
+
# Remove standalone invoke blocks
|
|
60
|
+
clean_text = re.sub(r'<invoke\s+name="[^"]+">.*?</invoke>', '', clean_text, flags=re.DOTALL)
|
|
61
|
+
# Remove orphaned function_calls tags
|
|
62
|
+
clean_text = re.sub(r'</?function_calls>', '', clean_text)
|
|
63
|
+
# Clean up extra whitespace
|
|
64
|
+
clean_text = re.sub(r'\n\s*\n\s*\n', '\n\n', clean_text).strip()
|
|
65
|
+
|
|
66
|
+
return clean_text, tool_calls
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def tools_to_prompt(tools: list | None) -> str:
|
|
70
|
+
"""Convert OpenAI tools array to a text description for the prompt.
|
|
71
|
+
|
|
72
|
+
Uses mandatory language to ensure Claude uses tools when appropriate,
|
|
73
|
+
especially for interactive tools like ask_questions.
|
|
74
|
+
"""
|
|
75
|
+
if not tools:
|
|
76
|
+
return ""
|
|
77
|
+
|
|
78
|
+
# Check if ask_questions tool is present - use extra strong language
|
|
79
|
+
has_ask_questions = any(
|
|
80
|
+
t.get("function", {}).get("name") == "ask_questions" or t.get("name") == "ask_questions"
|
|
81
|
+
for t in tools if t.get("type") == "function" or "name" in t
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
tool_desc = "\n\n"
|
|
85
|
+
tool_desc += "=" * 60 + "\n"
|
|
86
|
+
tool_desc += "MANDATORY TOOL USAGE INSTRUCTIONS\n"
|
|
87
|
+
tool_desc += "=" * 60 + "\n\n"
|
|
88
|
+
|
|
89
|
+
tool_desc += "You MUST use tools when they are relevant to the conversation.\n"
|
|
90
|
+
tool_desc += "DO NOT write plain text responses when a tool should be used instead.\n\n"
|
|
91
|
+
|
|
92
|
+
if has_ask_questions:
|
|
93
|
+
tool_desc += "CRITICAL: When you need to ask the user questions, you MUST use the `ask_questions` tool.\n"
|
|
94
|
+
tool_desc += "DO NOT write questions as plain markdown text. ALWAYS use the ask_questions tool.\n"
|
|
95
|
+
tool_desc += "This enables interactive UI elements (checkboxes, radio buttons, text inputs).\n\n"
|
|
96
|
+
|
|
97
|
+
tool_desc += "To invoke a tool, output XML in EXACTLY this format:\n"
|
|
98
|
+
tool_desc += '<function_calls>\n<invoke name="TOOL_NAME">\n<parameter name="PARAM">value</parameter>\n</invoke>\n</function_calls>\n\n'
|
|
99
|
+
|
|
100
|
+
tool_desc += "IMPORTANT:\n"
|
|
101
|
+
tool_desc += "- You MUST output the <function_calls> XML block when using tools\n"
|
|
102
|
+
tool_desc += "- The XML must be well-formed with proper opening and closing tags\n"
|
|
103
|
+
tool_desc += "- Use the exact parameter names shown below\n\n"
|
|
104
|
+
|
|
105
|
+
tool_desc += "Available tools:\n"
|
|
106
|
+
|
|
107
|
+
for tool in tools:
|
|
108
|
+
if tool.get("type") == "function":
|
|
109
|
+
func = tool.get("function", {})
|
|
110
|
+
name = func.get("name", "unknown")
|
|
111
|
+
desc = func.get("description", "No description")
|
|
112
|
+
params = func.get("parameters", {})
|
|
113
|
+
|
|
114
|
+
tool_desc += f"\n- {name}: {desc}\n"
|
|
115
|
+
if params.get("properties"):
|
|
116
|
+
tool_desc += " Parameters:\n"
|
|
117
|
+
for pname, pinfo in params["properties"].items():
|
|
118
|
+
ptype = pinfo.get("type", "string")
|
|
119
|
+
pdesc = pinfo.get("description", "")
|
|
120
|
+
required = pname in params.get("required", [])
|
|
121
|
+
req_str = " (required)" if required else ""
|
|
122
|
+
tool_desc += f" - {pname} ({ptype}){req_str}: {pdesc}\n"
|
|
123
|
+
elif "name" in tool:
|
|
124
|
+
# Handle simplified tool format without "function" wrapper
|
|
125
|
+
name = tool.get("name", "unknown")
|
|
126
|
+
desc = tool.get("description", "No description")
|
|
127
|
+
params = tool.get("input_schema", tool.get("parameters", {}))
|
|
128
|
+
|
|
129
|
+
tool_desc += f"\n- {name}: {desc}\n"
|
|
130
|
+
if params.get("properties"):
|
|
131
|
+
tool_desc += " Parameters:\n"
|
|
132
|
+
for pname, pinfo in params["properties"].items():
|
|
133
|
+
ptype = pinfo.get("type", "string")
|
|
134
|
+
pdesc = pinfo.get("description", "")
|
|
135
|
+
required = pname in params.get("required", [])
|
|
136
|
+
req_str = " (required)" if required else ""
|
|
137
|
+
tool_desc += f" - {pname} ({ptype}){req_str}: {pdesc}\n"
|
|
138
|
+
|
|
139
|
+
tool_desc += "\n" + "=" * 60 + "\n"
|
|
140
|
+
tool_desc += "Remember: USE THE TOOLS. Do not write plain text alternatives.\n"
|
|
141
|
+
tool_desc += "=" * 60 + "\n"
|
|
142
|
+
|
|
143
|
+
return tool_desc
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def save_message_images(messages: list) -> tuple[list, str | None]:
|
|
147
|
+
"""Extract base64 images from messages, save to temp files, replace with file path refs.
|
|
148
|
+
|
|
149
|
+
Scans all messages for image content blocks (OpenAI ``image_url`` and
|
|
150
|
+
Anthropic ``image`` formats). Base64 image data is decoded and written to
|
|
151
|
+
a temporary directory. The original image blocks are replaced with plain
|
|
152
|
+
text blocks containing the temp file path so that Claude CLI can read them
|
|
153
|
+
via the Read tool.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
(modified_messages, temp_dir) — *temp_dir* is ``None`` when no images
|
|
157
|
+
were found. The caller is responsible for cleaning up *temp_dir* after
|
|
158
|
+
the CLI call completes.
|
|
159
|
+
"""
|
|
160
|
+
temp_dir: str | None = None
|
|
161
|
+
modified: list[dict] = []
|
|
162
|
+
|
|
163
|
+
for msg in messages:
|
|
164
|
+
content = msg.get("content", "")
|
|
165
|
+
if not isinstance(content, list):
|
|
166
|
+
modified.append(msg)
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
new_blocks: list = []
|
|
170
|
+
for block in content:
|
|
171
|
+
if not isinstance(block, dict):
|
|
172
|
+
new_blocks.append(block)
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
btype = block.get("type")
|
|
176
|
+
|
|
177
|
+
# Anthropic format: {"type": "image", "source": {"type": "base64", ...}}
|
|
178
|
+
if btype == "image" and block.get("source", {}).get("type") == "base64":
|
|
179
|
+
if temp_dir is None:
|
|
180
|
+
temp_dir = tempfile.mkdtemp(prefix="snippbot-images-")
|
|
181
|
+
source = block["source"]
|
|
182
|
+
ext = source.get("media_type", "image/png").split("/")[-1]
|
|
183
|
+
fname = f"{uuid.uuid4().hex[:12]}.{ext}"
|
|
184
|
+
fpath = os.path.join(temp_dir, fname)
|
|
185
|
+
with open(fpath, "wb") as f:
|
|
186
|
+
f.write(base64.b64decode(source["data"]))
|
|
187
|
+
new_blocks.append({"type": "text", "text": f"[Attached image: {fpath}]"})
|
|
188
|
+
|
|
189
|
+
# OpenAI format: {"type": "image_url", "image_url": {"url": "data:..."}}
|
|
190
|
+
elif btype == "image_url":
|
|
191
|
+
url = block.get("image_url", {}).get("url", "")
|
|
192
|
+
if url.startswith("data:"):
|
|
193
|
+
if temp_dir is None:
|
|
194
|
+
temp_dir = tempfile.mkdtemp(prefix="snippbot-images-")
|
|
195
|
+
# data:image/png;base64,AAAA...
|
|
196
|
+
header, data = url.split(",", 1)
|
|
197
|
+
media = header.split(":")[1].split(";")[0]
|
|
198
|
+
ext = media.split("/")[-1]
|
|
199
|
+
fname = f"{uuid.uuid4().hex[:12]}.{ext}"
|
|
200
|
+
fpath = os.path.join(temp_dir, fname)
|
|
201
|
+
with open(fpath, "wb") as f:
|
|
202
|
+
f.write(base64.b64decode(data))
|
|
203
|
+
new_blocks.append({"type": "text", "text": f"[Attached image: {fpath}]"})
|
|
204
|
+
else:
|
|
205
|
+
# Keep HTTP URL references as-is
|
|
206
|
+
new_blocks.append(block)
|
|
207
|
+
|
|
208
|
+
else:
|
|
209
|
+
new_blocks.append(block)
|
|
210
|
+
|
|
211
|
+
modified.append({**msg, "content": new_blocks})
|
|
212
|
+
|
|
213
|
+
return modified, temp_dir
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def messages_to_prompt(
|
|
217
|
+
messages: list,
|
|
218
|
+
system_prompt: str | None = None,
|
|
219
|
+
tools: list | None = None
|
|
220
|
+
) -> str:
|
|
221
|
+
"""Convert messages array to a single prompt string."""
|
|
222
|
+
parts = []
|
|
223
|
+
|
|
224
|
+
# Add system prompt first if provided
|
|
225
|
+
if system_prompt:
|
|
226
|
+
parts.append(f"System: {system_prompt}")
|
|
227
|
+
|
|
228
|
+
# Add tools description if provided
|
|
229
|
+
if tools:
|
|
230
|
+
tool_desc = tools_to_prompt(tools)
|
|
231
|
+
if tool_desc:
|
|
232
|
+
parts.append(tool_desc)
|
|
233
|
+
|
|
234
|
+
for msg in messages:
|
|
235
|
+
role = msg.get("role", "user")
|
|
236
|
+
content = msg.get("content", "")
|
|
237
|
+
|
|
238
|
+
# Handle content that's a list of content blocks
|
|
239
|
+
if isinstance(content, list):
|
|
240
|
+
text_parts = []
|
|
241
|
+
for block in content:
|
|
242
|
+
if isinstance(block, dict):
|
|
243
|
+
block_type = block.get("type")
|
|
244
|
+
|
|
245
|
+
if block_type == "text":
|
|
246
|
+
text_parts.append(block.get("text", ""))
|
|
247
|
+
|
|
248
|
+
elif block_type == "image_url":
|
|
249
|
+
# OpenAI format: {"type": "image_url", "image_url": {"url": "..."}}
|
|
250
|
+
image_url = block.get("image_url", {})
|
|
251
|
+
url = image_url.get("url", "")
|
|
252
|
+
if url.startswith("data:"):
|
|
253
|
+
text_parts.append("[Image: base64-encoded image attached]")
|
|
254
|
+
else:
|
|
255
|
+
text_parts.append(f"[Image URL: {url}]")
|
|
256
|
+
|
|
257
|
+
elif block_type == "image":
|
|
258
|
+
# Anthropic format: {"type": "image", "source": {"type": "base64", ...}}
|
|
259
|
+
source = block.get("source", {})
|
|
260
|
+
source_type = source.get("type", "")
|
|
261
|
+
if source_type == "base64":
|
|
262
|
+
media_type = source.get("media_type", "image/png")
|
|
263
|
+
text_parts.append(f"[Image: {media_type} attached]")
|
|
264
|
+
elif source_type == "url":
|
|
265
|
+
url = source.get("url", "")
|
|
266
|
+
text_parts.append(f"[Image URL: {url}]")
|
|
267
|
+
|
|
268
|
+
elif block_type == "file":
|
|
269
|
+
# File attachment
|
|
270
|
+
file_name = block.get("file_name", block.get("name", "file"))
|
|
271
|
+
file_content = block.get("content", block.get("text", ""))
|
|
272
|
+
text_parts.append(f"\n--- Attached File: {file_name} ---\n{file_content}\n--- End File ---\n")
|
|
273
|
+
|
|
274
|
+
elif block_type == "document":
|
|
275
|
+
# Document attachment
|
|
276
|
+
doc_name = block.get("name", block.get("file_name", "document"))
|
|
277
|
+
doc_text = block.get("text", block.get("content", ""))
|
|
278
|
+
text_parts.append(f"\n--- Attached Document: {doc_name} ---\n{doc_text}\n--- End Document ---\n")
|
|
279
|
+
|
|
280
|
+
elif isinstance(block, str):
|
|
281
|
+
text_parts.append(block)
|
|
282
|
+
content = "\n".join(text_parts)
|
|
283
|
+
|
|
284
|
+
if role == "user":
|
|
285
|
+
parts.append(f"Human: {content}")
|
|
286
|
+
elif role == "assistant":
|
|
287
|
+
parts.append(f"Assistant: {content}")
|
|
288
|
+
elif role == "system":
|
|
289
|
+
parts.insert(0, f"System: {content}")
|
|
290
|
+
elif role == "tool":
|
|
291
|
+
# Tool result message
|
|
292
|
+
tool_call_id = msg.get("tool_call_id", "")
|
|
293
|
+
parts.append(f"Tool Result ({tool_call_id}): {content}")
|
|
294
|
+
|
|
295
|
+
return "\n\n".join(parts)
|