polyrob 0.5.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.
- agents/README.md +453 -0
- agents/__init__.py +164 -0
- agents/base_agent.py +743 -0
- agents/personality/character.py +119 -0
- agents/personality/character_manager.py +309 -0
- agents/personality/characters/rob.character.json +56 -0
- agents/personality/characters/trump.character.json +63 -0
- agents/personality/persona_render.py +78 -0
- agents/personality/persona_resolver.py +115 -0
- agents/prompt/__init__.py +58 -0
- agents/prompt/base_prompt.py +92 -0
- agents/prompt/system.py +417 -0
- agents/task/README_PATH_MANAGEMENT.md +141 -0
- agents/task/__init__.py +162 -0
- agents/task/agent/__init__.py +70 -0
- agents/task/agent/agent_state.py +408 -0
- agents/task/agent/async_delegation.py +274 -0
- agents/task/agent/autonomy_state.py +275 -0
- agents/task/agent/conversation.py +75 -0
- agents/task/agent/core/__init__.py +6 -0
- agents/task/agent/core/aux_metering.py +61 -0
- agents/task/agent/core/background_review.py +157 -0
- agents/task/agent/core/construction.py +1179 -0
- agents/task/agent/core/conversational_exit.py +49 -0
- agents/task/agent/core/correspondent_gate.py +227 -0
- agents/task/agent/core/curator.py +246 -0
- agents/task/agent/core/episodic_digest.py +103 -0
- agents/task/agent/core/error_recovery.py +480 -0
- agents/task/agent/core/history_io.py +257 -0
- agents/task/agent/core/llm_provisioning.py +287 -0
- agents/task/agent/core/llm_runner.py +547 -0
- agents/task/agent/core/logging_io.py +276 -0
- agents/task/agent/core/loop_detection.py +262 -0
- agents/task/agent/core/memory_prefetch.py +325 -0
- agents/task/agent/core/memory_writer.py +667 -0
- agents/task/agent/core/model_introspection.py +82 -0
- agents/task/agent/core/model_swap.py +177 -0
- agents/task/agent/core/next_action_internal.py +1331 -0
- agents/task/agent/core/output_validation.py +303 -0
- agents/task/agent/core/project_context.py +272 -0
- agents/task/agent/core/resources.py +93 -0
- agents/task/agent/core/result_processing.py +389 -0
- agents/task/agent/core/run_loop.py +661 -0
- agents/task/agent/core/safety_lifecycle.py +228 -0
- agents/task/agent/core/secret_guard.py +293 -0
- agents/task/agent/core/self_wake.py +280 -0
- agents/task/agent/core/session_metadata.py +104 -0
- agents/task/agent/core/step.py +781 -0
- agents/task/agent/core/step_execution.py +398 -0
- agents/task/agent/core/step_telemetry.py +222 -0
- agents/task/agent/core/test_aux_metering.py +50 -0
- agents/task/agent/core/test_billing_failover.py +68 -0
- agents/task/agent/core/test_fatal_error_classifier.py +38 -0
- agents/task/agent/core/test_judge_validation.py +275 -0
- agents/task/agent/core/test_model_swap.py +190 -0
- agents/task/agent/core/test_reflection_offload.py +54 -0
- agents/task/agent/core/turn_input.py +8 -0
- agents/task/agent/core/untrusted_wrap.py +103 -0
- agents/task/agent/core/user_ingress.py +228 -0
- agents/task/agent/hitl_manager.py +396 -0
- agents/task/agent/log_sanitize.py +36 -0
- agents/task/agent/message_manager/config.py +97 -0
- agents/task/agent/message_manager/service.py +758 -0
- agents/task/agent/message_manager/tests.py +305 -0
- agents/task/agent/message_manager/tool_call_builder.py +539 -0
- agents/task/agent/message_manager/tool_message_repair.py +364 -0
- agents/task/agent/message_manager/views.py +143 -0
- agents/task/agent/messages/__init__.py +8 -0
- agents/task/agent/messages/builders.py +309 -0
- agents/task/agent/messages/compactor.py +611 -0
- agents/task/agent/messages/context_references.py +271 -0
- agents/task/agent/messages/filters.py +333 -0
- agents/task/agent/messages/guidance.py +328 -0
- agents/task/agent/messages/persistence.py +448 -0
- agents/task/agent/messages/retrieval.py +506 -0
- agents/task/agent/messages/sqlite_persistence.py +85 -0
- agents/task/agent/messages/test_compaction_upgrade.py +349 -0
- agents/task/agent/messages/test_persistence_subdir_fix.py +28 -0
- agents/task/agent/messages/test_sqlite_persistence.py +54 -0
- agents/task/agent/messages/token_counter.py +532 -0
- agents/task/agent/orchestrator.py +776 -0
- agents/task/agent/profile_manager.py +105 -0
- agents/task/agent/profile_registry.py +173 -0
- agents/task/agent/prompts.py +1090 -0
- agents/task/agent/scenario_registry.py +226 -0
- agents/task/agent/service.py +509 -0
- agents/task/agent/session.py +984 -0
- agents/task/agent/skill_discovery.py +110 -0
- agents/task/agent/skill_frontmatter.py +94 -0
- agents/task/agent/skill_manager.py +1201 -0
- agents/task/agent/skill_store.py +627 -0
- agents/task/agent/skill_validation.py +56 -0
- agents/task/agent/skill_writer.py +492 -0
- agents/task/agent/sub_agent_manager.py +1137 -0
- agents/task/agent/test_session_disk_recovery.py +34 -0
- agents/task/agent/tests.py +513 -0
- agents/task/agent/tool_call_tracker.py +511 -0
- agents/task/agent/views.py +469 -0
- agents/task/config.py +432 -0
- agents/task/constants.py +1384 -0
- agents/task/flag_defaults.py +63 -0
- agents/task/goals/__init__.py +1 -0
- agents/task/goals/autonomy_marker.py +28 -0
- agents/task/goals/board.py +690 -0
- agents/task/goals/completion_judge.py +247 -0
- agents/task/goals/context.py +62 -0
- agents/task/goals/dispatcher.py +848 -0
- agents/task/goals/escalation.py +102 -0
- agents/task/goals/planner.py +114 -0
- agents/task/logging_config.py +155 -0
- agents/task/path.py +991 -0
- agents/task/robust_parse_config.py +400 -0
- agents/task/runtime/__init__.py +0 -0
- agents/task/runtime/run_as_session.py +130 -0
- agents/task/runtime_safety.py +308 -0
- agents/task/session/__init__.py +5 -0
- agents/task/session/browser_pool.py +130 -0
- agents/task/session/cleanup.py +612 -0
- agents/task/session/execution.py +397 -0
- agents/task/session/feed.py +200 -0
- agents/task/session/hitl_ingress.py +201 -0
- agents/task/session/hooks.py +83 -0
- agents/task/session/multi_agent.py +243 -0
- agents/task/session/workspace.py +91 -0
- agents/task/session_registry.py +91 -0
- agents/task/session_route.py +35 -0
- agents/task/sqlite_session_registry.py +261 -0
- agents/task/surface_config.py +185 -0
- agents/task/telemetry/__init__.py +58 -0
- agents/task/telemetry/event_log.py +180 -0
- agents/task/telemetry/formatters.py +642 -0
- agents/task/telemetry/manager.py +571 -0
- agents/task/telemetry/memory_events.py +66 -0
- agents/task/telemetry/self_events.py +53 -0
- agents/task/telemetry/sequence.py +160 -0
- agents/task/telemetry/service.py +1265 -0
- agents/task/telemetry/views.py +882 -0
- agents/task/templates.py +98 -0
- agents/task/test_aux_chain.py +151 -0
- agents/task/test_aux_router.py +41 -0
- agents/task/tests/test_parse_robustness.py +170 -0
- agents/task/tool_defaults.py +110 -0
- agents/task/utils.py +762 -0
- agents/task/utils_json.py +768 -0
- agents/task/utils_webview.py +86 -0
- agents/task/workspace_context.py +229 -0
- agents/task_agent_lite.py +2398 -0
- api/README.md +683 -0
- api/TASK_API_DOCS.md +151 -0
- api/__init__.py +0 -0
- api/a2a/__init__.py +37 -0
- api/a2a/agent_card.py +337 -0
- api/a2a/client.py +657 -0
- api/a2a/endpoints.py +375 -0
- api/a2a/models.py +268 -0
- api/a2a/streaming.py +419 -0
- api/a2a/task_handler.py +929 -0
- api/admin_endpoints.py +1584 -0
- api/app.py +923 -0
- api/auth_constants.py +28 -0
- api/auth_endpoints.py +402 -0
- api/auth_state.py +36 -0
- api/chat_via_task.py +42 -0
- api/conversation_manager.py +231 -0
- api/dependencies.py +186 -0
- api/eip8004_endpoints.py +452 -0
- api/hyperliquid_models.py +174 -0
- api/hyperliquid_routes.py +450 -0
- api/interfaces.py +207 -0
- api/jwt_middleware.py +83 -0
- api/kb/__init__.py +1 -0
- api/kb/endpoints.py +314 -0
- api/mcp_models.py +279 -0
- api/mcp_routes.py +438 -0
- api/middleware.py +526 -0
- api/models.py +176 -0
- api/openai_compat/__init__.py +0 -0
- api/openai_compat/model_map.py +25 -0
- api/openai_compat/models.py +59 -0
- api/openai_compat/router.py +140 -0
- api/payment_endpoints.py +444 -0
- api/payment_verification.py +138 -0
- api/polymarket_models.py +155 -0
- api/polymarket_routes.py +501 -0
- api/pricing_endpoints.py +89 -0
- api/session_routing.py +54 -0
- api/skill_endpoints.py +453 -0
- api/task_http_api.py +1856 -0
- api/webhooks.py +52 -0
- api/x402_endpoints.py +280 -0
- avatar/README.md +44 -0
- avatar/__init__.py +1 -0
- avatar/config/rob.json +7 -0
- avatar/mindprint.js +395 -0
- avatar/renders/rob.meta.json +20 -0
- avatar/renders/rob.png +0 -0
- avatar/studio.html +373 -0
- avatar/webview/avatar-live.js +44 -0
- cli/README.md +89 -0
- cli/__init__.py +0 -0
- cli/cli_surface.py +67 -0
- cli/commands/__init__.py +0 -0
- cli/commands/_bootstrap.py +49 -0
- cli/commands/_errors.py +53 -0
- cli/commands/chat.py +973 -0
- cli/commands/config.py +100 -0
- cli/commands/dashboard.py +103 -0
- cli/commands/doctor.py +222 -0
- cli/commands/email.py +174 -0
- cli/commands/gateway.py +346 -0
- cli/commands/goals.py +509 -0
- cli/commands/init.py +276 -0
- cli/commands/journey.py +30 -0
- cli/commands/kb.py +280 -0
- cli/commands/model.py +107 -0
- cli/commands/owner.py +431 -0
- cli/commands/pfp.py +232 -0
- cli/commands/run.py +414 -0
- cli/commands/serve.py +41 -0
- cli/commands/session.py +684 -0
- cli/commands/skill_install.py +786 -0
- cli/commands/skills.py +128 -0
- cli/commands/subagents.py +100 -0
- cli/commands/surface.py +81 -0
- cli/commands/telegram.py +185 -0
- cli/commands/todos.py +209 -0
- cli/commands/tools.py +102 -0
- cli/commands/update.py +316 -0
- cli/commands/whatsapp.py +175 -0
- cli/config_store.py +276 -0
- cli/gitignore.py +43 -0
- cli/inventory.py +46 -0
- cli/keys.py +101 -0
- cli/persona.py +17 -0
- cli/polyrob.py +137 -0
- cli/ui/__init__.py +64 -0
- cli/ui/activity.py +141 -0
- cli/ui/app.py +538 -0
- cli/ui/banner.py +233 -0
- cli/ui/blocks.py +534 -0
- cli/ui/bootstrap_notice.py +47 -0
- cli/ui/commands/__init__.py +41 -0
- cli/ui/commands/h_cron.py +64 -0
- cli/ui/commands/h_journey.py +164 -0
- cli/ui/commands/h_kb.py +121 -0
- cli/ui/commands/h_learn.py +100 -0
- cli/ui/commands/h_mcp.py +215 -0
- cli/ui/commands/h_self.py +113 -0
- cli/ui/commands/h_skills.py +314 -0
- cli/ui/commands/handlers.py +1672 -0
- cli/ui/commands/registry.py +426 -0
- cli/ui/dialog.py +425 -0
- cli/ui/event_registry.py +110 -0
- cli/ui/event_specs.py +45 -0
- cli/ui/events.py +433 -0
- cli/ui/identity.py +15 -0
- cli/ui/lifecycle.py +159 -0
- cli/ui/live_hooks.py +43 -0
- cli/ui/model_selector.py +532 -0
- cli/ui/persistent_loop.py +181 -0
- cli/ui/pick.py +113 -0
- cli/ui/plain_renderer.py +351 -0
- cli/ui/renderer.py +384 -0
- cli/ui/rich_renderer.py +533 -0
- cli/ui/secrets.py +115 -0
- cli/ui/state.py +376 -0
- cli/ui/statusbar.py +231 -0
- cli/ui/streaming.py +254 -0
- cli/ui/terminal_render.py +105 -0
- cli/ui/theme.py +132 -0
- cli/update/__init__.py +6 -0
- cli/update/context.py +121 -0
- cli/update/detect.py +176 -0
- cli/update/engine.py +95 -0
- cli/update/migrate_guarded.py +55 -0
- cli/update/process_guard.py +215 -0
- cli/update/runners.py +78 -0
- cli/update/snapshot.py +270 -0
- cli/update/versions.py +205 -0
- core/README.md +377 -0
- core/__init__.py +174 -0
- core/assets.py +87 -0
- core/async_bridge.py +76 -0
- core/autonomy_runtime.py +259 -0
- core/base_component.py +302 -0
- core/bootstrap.py +681 -0
- core/bot.py +310 -0
- core/config.py +833 -0
- core/constants.py +126 -0
- core/container.py +462 -0
- core/db_manifest.py +101 -0
- core/embedding.py +57 -0
- core/env.py +39 -0
- core/exceptions.py +287 -0
- core/flags.py +144 -0
- core/flags_catalog.py +345 -0
- core/home_migration.py +62 -0
- core/identity.py +118 -0
- core/initialization.py +1004 -0
- core/instance.py +537 -0
- core/interactive_gate.py +96 -0
- core/logging.py +436 -0
- core/owner_doc_writer.py +176 -0
- core/pairing.py +193 -0
- core/path_safety.py +20 -0
- core/paths.py +27 -0
- core/payment_config.py +21 -0
- core/permissions.py +322 -0
- core/runtime_config.py +96 -0
- core/runtime_paths.py +135 -0
- core/seams.py +35 -0
- core/secret_scan.py +25 -0
- core/secret_scrub.py +71 -0
- core/secrets.py +35 -0
- core/security_logging_filter.py +148 -0
- core/self_context_writer.py +333 -0
- core/self_evolution.py +294 -0
- core/session_context.py +47 -0
- core/sqlite_util.py +59 -0
- core/surfaces/__init__.py +16 -0
- core/surfaces/access.py +118 -0
- core/surfaces/binding.py +88 -0
- core/surfaces/bootstrap.py +78 -0
- core/surfaces/circuit.py +168 -0
- core/surfaces/continuity.py +77 -0
- core/surfaces/correspondents.py +228 -0
- core/surfaces/dispatcher.py +216 -0
- core/surfaces/envelopes.py +75 -0
- core/surfaces/gc.py +30 -0
- core/surfaces/idempotency.py +57 -0
- core/surfaces/inbound_webhook.py +124 -0
- core/surfaces/media.py +32 -0
- core/surfaces/message_router.py +91 -0
- core/surfaces/outbound_allowlist.py +80 -0
- core/surfaces/outbound_dispatcher.py +110 -0
- core/surfaces/outbound_mirror.py +37 -0
- core/surfaces/outbound_queue.py +107 -0
- core/surfaces/outbound_target.py +12 -0
- core/surfaces/owner_admin.py +29 -0
- core/surfaces/proactive.py +78 -0
- core/surfaces/progress.py +108 -0
- core/surfaces/rate_bucket.py +21 -0
- core/surfaces/registry.py +51 -0
- core/surfaces/rendering.py +53 -0
- core/surfaces/send_policy.py +14 -0
- core/surfaces/serialize.py +24 -0
- core/surfaces/session_chat_registry.py +112 -0
- core/surfaces/session_policy.py +60 -0
- core/surfaces/surface.py +288 -0
- core/surfaces/transcription.py +88 -0
- core/surfaces/voice_echo.py +26 -0
- core/surfaces/voice_guard.py +23 -0
- core/tickers.py +174 -0
- core/tool_catalog.py +192 -0
- core/version.py +75 -0
- core/wallet/__init__.py +5 -0
- core/wallet/agent_wallet.py +59 -0
- core/wallet/audit_sink.py +61 -0
- core/wallet/config.py +70 -0
- core/wallet/factory.py +87 -0
- core/wallet/policy.py +104 -0
- core/wallet/signer.py +51 -0
- cron/README.md +65 -0
- cron/__init__.py +0 -0
- cron/delivery.py +277 -0
- cron/digest.py +124 -0
- cron/jobs.py +207 -0
- cron/runner.py +297 -0
- cron/schedule.py +194 -0
- cron/scheduler.py +201 -0
- cron/service.py +57 -0
- cron/wake_gate.py +223 -0
- data/__init__.py +1 -0
- data/prompts/autov2_prompts.json +87 -0
- data/prompts/skills/browser-automation/SKILL.md +43 -0
- data/prompts/skills/coding-workflow/SKILL.md +36 -0
- data/prompts/skills/crypto-trading-safety/SKILL.md +53 -0
- data/prompts/skills/document-writing/SKILL.md +33 -0
- data/prompts/skills/email-comms/SKILL.md +33 -0
- data/prompts/skills/file-data-ops/SKILL.md +35 -0
- data/prompts/skills/hyperliquid-account-review/SKILL.md +48 -0
- data/prompts/skills/hyperliquid-market-data/SKILL.md +52 -0
- data/prompts/skills/hyperliquid-trading/SKILL.md +59 -0
- data/prompts/skills/lead-research/SKILL.md +86 -0
- data/prompts/skills/market-research-brief/SKILL.md +104 -0
- data/prompts/skills/person-analyzer/SKILL.md +124 -0
- data/prompts/skills/polymarket-market-research/SKILL.md +54 -0
- data/prompts/skills/polymarket-portfolio-review/SKILL.md +50 -0
- data/prompts/skills/polymarket-trading/SKILL.md +55 -0
- data/prompts/skills/presentation-creator/SKILL.md +36 -0
- data/prompts/skills/project-analyzer/SKILL.md +169 -0
- data/prompts/skills/rules.json +268 -0
- data/prompts/skills/secret-handling/SKILL.md +20 -0
- data/prompts/skills/skill-authoring/SKILL.md +31 -0
- data/prompts/skills/skill-security-review/SKILL.md +27 -0
- data/prompts/skills/social-discovery/SKILL.md +57 -0
- data/prompts/skills/task-planning/SKILL.md +33 -0
- data/prompts/skills/web-research/SKILL.md +39 -0
- data/prompts/skills/web-scraping/SKILL.md +73 -0
- data/prompts/skills/x-engagement/SKILL.md +69 -0
- data/prompts/system_prompts.json +4 -0
- data/subscription_channels.json +5 -0
- modules/README.md +503 -0
- modules/__init__.py +124 -0
- modules/auth/__init__.py +13 -0
- modules/auth/api_key_manager.py +157 -0
- modules/auth/identity_mapper.py +380 -0
- modules/auth/siwe_auth.py +273 -0
- modules/auth/tier_manager.py +173 -0
- modules/base_module.py +190 -0
- modules/credits/__init__.py +29 -0
- modules/credits/balance_manager.py +237 -0
- modules/credits/cost_utils.py +129 -0
- modules/credits/pricing.py +139 -0
- modules/credits/unified_ledger.py +148 -0
- modules/credits/usage_meter.py +310 -0
- modules/credits/usage_tracker.py +674 -0
- modules/database/__init__.py +33 -0
- modules/database/audit_log.py +322 -0
- modules/database/auth_tables.py +276 -0
- modules/database/connection.py +644 -0
- modules/database/connection_pool.py +305 -0
- modules/database/conversation_contexts.py +446 -0
- modules/database/database_manager.py +237 -0
- modules/database/hyperliquid.py +375 -0
- modules/database/polymarket.py +574 -0
- modules/database/user_mcp_servers.py +728 -0
- modules/database/user_profiles.py +562 -0
- modules/database/utils.py +107 -0
- modules/database/x402_tables.py +92 -0
- modules/eip8004/README.md +1069 -0
- modules/eip8004/__init__.py +65 -0
- modules/eip8004/contracts.py +498 -0
- modules/eip8004/models.py +271 -0
- modules/eip8004/registration.py +148 -0
- modules/eip8004/reputation.py +367 -0
- modules/eip8004/validation.py +277 -0
- modules/llm/__init__.py +97 -0
- modules/llm/adapters.py +963 -0
- modules/llm/anthropic_client.py +831 -0
- modules/llm/available_models.py +106 -0
- modules/llm/brain_scrubber.py +137 -0
- modules/llm/cache_hints.py +162 -0
- modules/llm/deepseek_client.py +562 -0
- modules/llm/gemini_client.py +1367 -0
- modules/llm/llm_client.py +1139 -0
- modules/llm/llm_client_registry.py +188 -0
- modules/llm/llm_factory.py +237 -0
- modules/llm/llm_manager.py +1036 -0
- modules/llm/messages.py +374 -0
- modules/llm/model_registry.py +1935 -0
- modules/llm/nvidia_client.py +125 -0
- modules/llm/openai_client.py +800 -0
- modules/llm/openrouter_client.py +969 -0
- modules/llm/profiles.py +178 -0
- modules/llm/test_anthropic_cache_capture.py +35 -0
- modules/llm/test_cached_pricing_derivation.py +24 -0
- modules/llm/think_scrubber.py +444 -0
- modules/llm/token_counter.py +268 -0
- modules/memory/__init__.py +35 -0
- modules/memory/backend_factory.py +83 -0
- modules/memory/cache_manager.py +186 -0
- modules/memory/episodic.py +108 -0
- modules/memory/local_vector_memory_provider.py +552 -0
- modules/memory/memory_manager.py +225 -0
- modules/memory/models.py +312 -0
- modules/memory/provider.py +210 -0
- modules/memory/registry.py +205 -0
- modules/memory/sqlite_memory_provider.py +767 -0
- modules/memory/task/__init__.py +52 -0
- modules/memory/task/compaction_manager.py +157 -0
- modules/memory/task/context_retriever.py +809 -0
- modules/memory/task/hierarchical_memory.py +1002 -0
- modules/memory/task/lexical_retriever.py +71 -0
- modules/memory/task/null_context_manager.py +213 -0
- modules/memory/task/phase_manager.py +625 -0
- modules/memory/task/reflection_service.py +157 -0
- modules/memory/task/semantic_retriever.py +218 -0
- modules/memory/task/task_context_manager.py +1165 -0
- modules/memory/task/test_forgetting_engages.py +24 -0
- modules/memory/task/test_reflection_llm.py +39 -0
- modules/memory/task/test_threat_scan.py +53 -0
- modules/memory/task/threat_scan.py +90 -0
- modules/memory/test_sqlite_memory_provider.py +106 -0
- modules/memory/user_profile_manager.py +527 -0
- modules/payments/__init__.py +11 -0
- modules/payments/deposit_monitor.py +478 -0
- modules/payments/price_oracle.py +70 -0
- modules/payments/treasury_sweeper.py +401 -0
- modules/payments/wallet_generator.py +145 -0
- modules/pfp/__init__.py +1 -0
- modules/pfp/config.py +92 -0
- modules/pfp/mesh.py +565 -0
- modules/pfp/push.py +69 -0
- modules/pfp/renderer.py +85 -0
- modules/pfp/store.py +82 -0
- modules/skills/__init__.py +1 -0
- modules/skills/skill_usage.py +248 -0
- modules/transcription/__init__.py +24 -0
- modules/transcription/base.py +20 -0
- modules/transcription/faster_whisper_transcriber.py +55 -0
- modules/x402/README.md +249 -0
- modules/x402/__init__.py +22 -0
- modules/x402/invoicing.py +416 -0
- modules/x402/middleware.py +364 -0
- modules/x402/settlement_watcher.py +145 -0
- modules/x402/x402_integration.py +344 -0
- polyrob-0.5.0.dist-info/METADATA +482 -0
- polyrob-0.5.0.dist-info/RECORD +745 -0
- polyrob-0.5.0.dist-info/WHEEL +5 -0
- polyrob-0.5.0.dist-info/entry_points.txt +2 -0
- polyrob-0.5.0.dist-info/licenses/LICENSE +21 -0
- polyrob-0.5.0.dist-info/top_level.txt +12 -0
- surfaces/README.md +70 -0
- surfaces/__init__.py +0 -0
- surfaces/email/__init__.py +0 -0
- surfaces/email/dedup.py +44 -0
- surfaces/email/harness.py +187 -0
- surfaces/email/inbound.py +159 -0
- surfaces/email/seed.py +60 -0
- surfaces/email/surface.py +105 -0
- surfaces/telegram/__init__.py +0 -0
- surfaces/telegram/dedup.py +83 -0
- surfaces/telegram/harness.py +886 -0
- surfaces/telegram/inbound.py +145 -0
- surfaces/telegram/interactive_tools.py +49 -0
- surfaces/telegram/markdown.py +347 -0
- surfaces/telegram/rate_limit.py +167 -0
- surfaces/telegram/surface.py +187 -0
- surfaces/telegram/voice.py +52 -0
- surfaces/whatsapp/__init__.py +0 -0
- surfaces/whatsapp/client.py +51 -0
- surfaces/whatsapp/harness.py +62 -0
- surfaces/whatsapp/inbound.py +132 -0
- surfaces/whatsapp/surface.py +64 -0
- surfaces/whatsapp/window.py +34 -0
- tools/README.md +578 -0
- tools/__init__.py +424 -0
- tools/alchemy/__init__.py +5 -0
- tools/alchemy/alchemy_tool.py +389 -0
- tools/anysite/__init__.py +17 -0
- tools/anysite/client.py +88 -0
- tools/anysite/tool.py +89 -0
- tools/base_tool.py +468 -0
- tools/browser/__init__.py +22 -0
- tools/browser/actions.py +118 -0
- tools/browser/browser.py +1457 -0
- tools/browser/browser_manager.py +690 -0
- tools/browser/context.py +1748 -0
- tools/browser/playwright_utils.py +137 -0
- tools/browser/views.py +56 -0
- tools/code_exec/SANDBOX_SECURITY.md +144 -0
- tools/code_exec/__init__.py +178 -0
- tools/code_exec/backend.py +73 -0
- tools/code_exec/backends/__init__.py +1 -0
- tools/code_exec/backends/docker.py +779 -0
- tools/code_exec/backends/local_subprocess.py +154 -0
- tools/code_exec/env_policy.py +45 -0
- tools/code_exec/result.py +42 -0
- tools/code_exec/sandbox_guard.py +59 -0
- tools/code_exec/tool.py +227 -0
- tools/coding/__init__.py +50 -0
- tools/coding/edit.py +107 -0
- tools/coding/search.py +113 -0
- tools/coding/tool.py +353 -0
- tools/collabland/__init__.py +5 -0
- tools/collabland/collabland_tool.py +471 -0
- tools/controller/__init__.py +0 -0
- tools/controller/_helpers.py +165 -0
- tools/controller/action_registration.py +1765 -0
- tools/controller/approval.py +244 -0
- tools/controller/approval_interactive.py +90 -0
- tools/controller/delegation.py +195 -0
- tools/controller/execution.py +797 -0
- tools/controller/execution_context.py +141 -0
- tools/controller/hooks.py +135 -0
- tools/controller/introspection.py +344 -0
- tools/controller/mcp_registrar.py +190 -0
- tools/controller/message_send.py +31 -0
- tools/controller/registry/__init__.py +4 -0
- tools/controller/registry/schema_generators.py +395 -0
- tools/controller/registry/schema_sanitizer.py +406 -0
- tools/controller/registry/service.py +1266 -0
- tools/controller/registry/views.py +151 -0
- tools/controller/service.py +409 -0
- tools/controller/tool_management.py +334 -0
- tools/controller/types.py +28 -0
- tools/controller/views.py +450 -0
- tools/cronjob_tools.py +166 -0
- tools/crypto_trade_gate.py +59 -0
- tools/descriptors.py +452 -0
- tools/dom/__init__.py +0 -0
- tools/dom/history_tree_processor/service.py +244 -0
- tools/dom/history_tree_processor/view.py +99 -0
- tools/dom/service.py +471 -0
- tools/dom/views.py +346 -0
- tools/email_tool.py +403 -0
- tools/exceptions.py +169 -0
- tools/filesystem.py +1038 -0
- tools/filesystem_docproc.py +331 -0
- tools/filesystem_pdf.py +755 -0
- tools/git/__init__.py +42 -0
- tools/git/tool.py +258 -0
- tools/github/__init__.py +33 -0
- tools/github/client.py +94 -0
- tools/github/tool.py +237 -0
- tools/goal_tools.py +309 -0
- tools/hyperliquid/__init__.py +56 -0
- tools/hyperliquid/models.py +233 -0
- tools/hyperliquid/service.py +1680 -0
- tools/knowledge_ingest.py +950 -0
- tools/mcp/README.md +535 -0
- tools/mcp/__init__.py +52 -0
- tools/mcp/catalog.py +115 -0
- tools/mcp/config.py +337 -0
- tools/mcp/mcp_tool.py +1415 -0
- tools/mcp/param_coercion.py +289 -0
- tools/mcp/protocol.py +1520 -0
- tools/mcp/rate_limit.py +50 -0
- tools/mcp/security.py +424 -0
- tools/mcp/self_install.py +111 -0
- tools/mcp/server_manager.py +829 -0
- tools/mcp/subscriptions.py +72 -0
- tools/mcp/user_mcp_service.py +899 -0
- tools/mcp/validation_tracker.py +89 -0
- tools/mcp/views.py +346 -0
- tools/oauth/__init__.py +17 -0
- tools/oauth/manager.py +75 -0
- tools/oauth/provider.py +69 -0
- tools/oauth/providers/__init__.py +1 -0
- tools/oauth/providers/generic_oauth2.py +96 -0
- tools/perplexity_tool.py +280 -0
- tools/polymarket/__init__.py +41 -0
- tools/polymarket/clob_adapter.py +76 -0
- tools/polymarket/models.py +182 -0
- tools/polymarket/service.py +2082 -0
- tools/self_env/__init__.py +61 -0
- tools/self_env/tool.py +274 -0
- tools/shell/__init__.py +80 -0
- tools/shell/backend_pool.py +118 -0
- tools/shell/discipline.py +62 -0
- tools/shell/executor.py +157 -0
- tools/shell/loopback_allow.py +87 -0
- tools/shell/process_registry.py +113 -0
- tools/shell/process_tool.py +131 -0
- tools/shell/state.py +129 -0
- tools/shell/tool.py +151 -0
- tools/task_tool.py +637 -0
- tools/twitter_tool.py +2086 -0
- tools/user_directory.py +179 -0
- tools/web_fetch/__init__.py +3 -0
- tools/web_fetch/fetcher.py +144 -0
- tools/web_fetch/render.py +44 -0
- tools/web_fetch/tool.py +60 -0
- tools/x402/__init__.py +60 -0
- tools/x402/client.py +47 -0
- tools/x402/invoice_tool.py +143 -0
- tools/x402/real_client.py +250 -0
- tools/x402/service.py +131 -0
- utils/README.md +498 -0
- utils/__init__.py +46 -0
- utils/auth_utils.py +114 -0
- utils/bounded_collections.py +162 -0
- utils/circuit_breaker.py +389 -0
- utils/gif_utils.py +435 -0
- utils/markdown_utils.py +291 -0
- utils/message_utils.py +147 -0
- utils/metrics.py +36 -0
- utils/path_validator.py +227 -0
- utils/rate_limit_manager.py +399 -0
- utils/result_size.py +296 -0
- utils/time_utils.py +206 -0
- utils/user_utils.py +188 -0
- webview/README.md +111 -0
- webview/RENAME_AND_ALIGN_HANDOFF.md +200 -0
- webview/__init__.py +8 -0
- webview/activity.py +636 -0
- webview/owner_auth.py +108 -0
- webview/pages.py +448 -0
- webview/repair_sessions.py +399 -0
- webview/server.py +4323 -0
- webview/server_launcher.py +112 -0
- webview/static/css/activity.css +89 -0
- webview/static/css/chat.css +1772 -0
- webview/static/css/components.css +1190 -0
- webview/static/css/config-panel.css +651 -0
- webview/static/css/pages/admin.css +766 -0
- webview/static/css/pages/profile.css +231 -0
- webview/static/css/pages/settings.css +297 -0
- webview/static/css/pages/signin.css +316 -0
- webview/static/css/style.css +4250 -0
- webview/static/css/variables.css +147 -0
- webview/static/css/workspace-fullwidth-fix.css +109 -0
- webview/static/img/favicon.ico +0 -0
- webview/static/js/activity.js +394 -0
- webview/static/js/admin/activity.js +186 -0
- webview/static/js/admin/dashboard.js +163 -0
- webview/static/js/admin/user_detail.js +400 -0
- webview/static/js/admin/users.js +174 -0
- webview/static/js/admin/utils.js +214 -0
- webview/static/js/chat.js +3178 -0
- webview/static/js/config-panel.js +522 -0
- webview/static/js/constants.js +20 -0
- webview/static/js/error-handler.js +226 -0
- webview/static/js/ethers.min.js +1 -0
- webview/static/js/event-filter.js +137 -0
- webview/static/js/event-store.js +351 -0
- webview/static/js/file-attachments.js +374 -0
- webview/static/js/file-loader.js +163 -0
- webview/static/js/index.js +103 -0
- webview/static/js/performance-utils.js +196 -0
- webview/static/js/profile.js +396 -0
- webview/static/js/screenshot.js +386 -0
- webview/static/js/session.js +1527 -0
- webview/static/js/settings.js +1288 -0
- webview/static/js/sidebar-data.js +766 -0
- webview/static/js/sidebar-toggle.js +296 -0
- webview/static/js/socket.io.min.js +7 -0
- webview/static/js/stats.js +355 -0
- webview/static/js/ui-utils.js +420 -0
- webview/static/js/workspace.js +966 -0
- webview/stats_service.py +613 -0
- webview/templates/__init__.py +1 -0
- webview/templates/__pycache__/__init__.cpython-311.pyc +0 -0
- webview/templates/activity.html +36 -0
- webview/templates/admin/activity.html +122 -0
- webview/templates/admin/dashboard.html +112 -0
- webview/templates/admin/user_detail.html +228 -0
- webview/templates/admin/users.html +105 -0
- webview/templates/autonomy.html +60 -0
- webview/templates/error.html +133 -0
- webview/templates/finance.html +90 -0
- webview/templates/identity.html +73 -0
- webview/templates/index.html +42 -0
- webview/templates/layout.html +125 -0
- webview/templates/memory.html +59 -0
- webview/templates/owner_login.html +15 -0
- webview/templates/profile.html +121 -0
- webview/templates/session.html +312 -0
- webview/templates/settings.html +534 -0
- webview/templates/sidebar.html +112 -0
- webview/templates/signin.html +213 -0
- webview/templates/status.html +14 -0
- webview/templates/system.html +45 -0
- webview/webgate.py +196 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""MCP → direct-action registration (Item 7H — extracted from ``controller/service.py``).
|
|
2
|
+
|
|
3
|
+
LLMs consistently fail to use nested ``mcp_execute_tool(arguments={...})``. This
|
|
4
|
+
registrar flattens each discovered MCP tool into a direct action named
|
|
5
|
+
``{server}_{tool}`` with a dynamic Pydantic param model built from the tool's JSON
|
|
6
|
+
input schema — eliminating the nesting problem.
|
|
7
|
+
|
|
8
|
+
It holds a back-reference to the owning ``Controller`` so ``registry`` and ``logger``
|
|
9
|
+
stay live (the Controller delegates ``_register_mcp_tools_as_direct_actions`` /
|
|
10
|
+
``_create_param_model_from_schema`` here). The WS-B5 typed-array behaviour (arrays
|
|
11
|
+
carry their item type; objects stay lenient ``dict``) is preserved verbatim.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Any, Dict, Type
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MCPActionRegistrar:
|
|
21
|
+
"""Registers MCP server tools as flat Controller actions + builds param models."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, controller: Any) -> None:
|
|
24
|
+
self._controller = controller
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def registry(self):
|
|
28
|
+
return self._controller.registry
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def logger(self):
|
|
32
|
+
return self._controller.logger
|
|
33
|
+
|
|
34
|
+
async def register(self, mcp_tool: Any) -> None:
|
|
35
|
+
"""Register individual MCP tools as direct actions with flat parameter schemas.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
mcp_tool: The MCPTool instance with server_manager
|
|
39
|
+
"""
|
|
40
|
+
self.logger.info("🔧 _register_mcp_tools_as_direct_actions called")
|
|
41
|
+
|
|
42
|
+
if not hasattr(mcp_tool, 'server_manager') or not mcp_tool.server_manager:
|
|
43
|
+
self.logger.warning("MCP tool has no server_manager, cannot register direct actions")
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
# Get all MCP tools from all servers
|
|
48
|
+
all_tools = mcp_tool.server_manager.get_all_tools()
|
|
49
|
+
self.logger.info(f"🔧 Found {len(all_tools)} MCP servers with tools")
|
|
50
|
+
registered_count = 0
|
|
51
|
+
|
|
52
|
+
for server_name, tools in all_tools.items():
|
|
53
|
+
for tool_meta in tools:
|
|
54
|
+
try:
|
|
55
|
+
# Create namespaced action name: server_toolname
|
|
56
|
+
action_name = f"{server_name}_{tool_meta.name}"
|
|
57
|
+
|
|
58
|
+
# Skip if already registered (avoid duplicates with mcp_* wrapper actions)
|
|
59
|
+
if action_name in self.registry.actions:
|
|
60
|
+
self.logger.debug(f"MCP action '{action_name}' already registered, skipping")
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
# Create a dynamic Pydantic model from the tool's input schema
|
|
64
|
+
param_model = self.create_param_model(
|
|
65
|
+
action_name,
|
|
66
|
+
tool_meta.input_schema or {}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Create an async executor that routes to execute_mcp_tool
|
|
70
|
+
# Use closure to capture server_name and tool_name
|
|
71
|
+
def make_executor(srv_name, t_name, mcp_ref):
|
|
72
|
+
# execution_context is a NAMED param (not **kwargs) so the
|
|
73
|
+
# registry routes the per-call context here instead of folding
|
|
74
|
+
# it into the MCP params dict — and so it reaches the per-tenant
|
|
75
|
+
# rate limiter (was falling back to a shared "global" bucket,
|
|
76
|
+
# cross-keying tenants).
|
|
77
|
+
async def executor(params_obj=None, execution_context=None, **kwargs):
|
|
78
|
+
# Convert Pydantic model to dict if needed
|
|
79
|
+
if params_obj is not None:
|
|
80
|
+
if hasattr(params_obj, 'model_dump'):
|
|
81
|
+
params = params_obj.model_dump()
|
|
82
|
+
elif isinstance(params_obj, dict):
|
|
83
|
+
params = params_obj
|
|
84
|
+
else:
|
|
85
|
+
params = dict(params_obj) if params_obj else {}
|
|
86
|
+
else:
|
|
87
|
+
params = kwargs
|
|
88
|
+
# Execute via MCP tool's execute_mcp_tool method
|
|
89
|
+
action = f"{srv_name}_{t_name}"
|
|
90
|
+
return await mcp_ref.execute_mcp_tool(
|
|
91
|
+
action, params, execution_context=execution_context
|
|
92
|
+
)
|
|
93
|
+
return executor
|
|
94
|
+
|
|
95
|
+
executor_func = make_executor(server_name, tool_meta.name, mcp_tool)
|
|
96
|
+
|
|
97
|
+
# Store metadata for routing
|
|
98
|
+
executor_func._mcp_server = server_name
|
|
99
|
+
executor_func._mcp_tool = tool_meta.name
|
|
100
|
+
|
|
101
|
+
# Register with the registry
|
|
102
|
+
self.registry.wrap_function(
|
|
103
|
+
name=action_name,
|
|
104
|
+
function=executor_func,
|
|
105
|
+
description=tool_meta.description or f"Execute {tool_meta.name} on {server_name}",
|
|
106
|
+
tool='mcp',
|
|
107
|
+
param_model=param_model
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
registered_count += 1
|
|
111
|
+
self.logger.debug(f"Registered MCP action: {action_name}")
|
|
112
|
+
|
|
113
|
+
except Exception as e:
|
|
114
|
+
self.logger.warning(f"Failed to register MCP tool {server_name}_{tool_meta.name}: {e}")
|
|
115
|
+
|
|
116
|
+
self.logger.info(f"✅ Registered {registered_count} MCP tools as direct actions")
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
self.logger.error(f"Failed to register MCP tools as direct actions: {e}", exc_info=True)
|
|
120
|
+
|
|
121
|
+
def create_param_model(self, action_name: str, schema: Dict[str, Any]) -> Type[BaseModel]:
|
|
122
|
+
"""Create a dynamic Pydantic model from a JSON schema.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
action_name: Name for the model
|
|
126
|
+
schema: JSON schema dict with properties, required, etc.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Dynamically created Pydantic BaseModel subclass
|
|
130
|
+
"""
|
|
131
|
+
from pydantic import create_model, Field
|
|
132
|
+
from typing import Optional, List, Any as TypingAny
|
|
133
|
+
|
|
134
|
+
properties = schema.get('properties', {})
|
|
135
|
+
required = set(schema.get('required', []))
|
|
136
|
+
|
|
137
|
+
# Build field definitions
|
|
138
|
+
fields = {}
|
|
139
|
+
for prop_name, prop_schema in properties.items():
|
|
140
|
+
prop_type = prop_schema.get('type', 'string')
|
|
141
|
+
description = prop_schema.get('description', '')
|
|
142
|
+
default = prop_schema.get('default', ...)
|
|
143
|
+
|
|
144
|
+
# Map JSON schema types to Python types
|
|
145
|
+
type_mapping = {
|
|
146
|
+
'string': str,
|
|
147
|
+
'integer': int,
|
|
148
|
+
'number': float,
|
|
149
|
+
'boolean': bool,
|
|
150
|
+
'array': list,
|
|
151
|
+
'object': dict,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
python_type = type_mapping.get(prop_type, TypingAny)
|
|
155
|
+
|
|
156
|
+
# WS-B5: preserve array item type (List[item]) instead of bare list.
|
|
157
|
+
# Objects stay as dict (lenient) so previously-valid payloads with
|
|
158
|
+
# unforeseen keys are never rejected.
|
|
159
|
+
if prop_type == 'array':
|
|
160
|
+
items_schema = prop_schema.get('items') or {}
|
|
161
|
+
item_type = type_mapping.get(items_schema.get('type'), TypingAny)
|
|
162
|
+
python_type = List[item_type]
|
|
163
|
+
|
|
164
|
+
# Handle nullable types (anyOf with null)
|
|
165
|
+
if 'anyOf' in prop_schema:
|
|
166
|
+
for option in prop_schema['anyOf']:
|
|
167
|
+
if option.get('type') == 'null':
|
|
168
|
+
python_type = Optional[python_type]
|
|
169
|
+
break
|
|
170
|
+
|
|
171
|
+
# Set default based on whether field is required
|
|
172
|
+
if prop_name in required:
|
|
173
|
+
field_default = ... # Required
|
|
174
|
+
elif default != ...:
|
|
175
|
+
field_default = default
|
|
176
|
+
else:
|
|
177
|
+
field_default = None
|
|
178
|
+
python_type = Optional[python_type] if python_type != Optional else python_type
|
|
179
|
+
|
|
180
|
+
# Create field with description
|
|
181
|
+
fields[prop_name] = (python_type, Field(default=field_default, description=description))
|
|
182
|
+
|
|
183
|
+
# Create dynamic model
|
|
184
|
+
model_name = f"MCP_{action_name.replace('-', '_').title().replace('_', '')}Params"
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
return create_model(model_name, **fields)
|
|
188
|
+
except Exception as e:
|
|
189
|
+
self.logger.warning(f"Failed to create param model for {action_name}: {e}, using empty model")
|
|
190
|
+
return create_model(model_name)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Pure-ish send helper for the `message` action: resolve tier, gate, route.
|
|
2
|
+
Router is any object with async send_message(chat_id, text, surface_id)."""
|
|
3
|
+
import logging
|
|
4
|
+
from core.surfaces.outbound_target import resolve_target_tier
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def perform_message_send(*, router, allowlist, owner_targets, user_id,
|
|
10
|
+
surface, target, text, action="send", reply_to=None,
|
|
11
|
+
message_id=None) -> dict:
|
|
12
|
+
tier = resolve_target_tier(surface=surface, target=target, user_id=user_id,
|
|
13
|
+
allowlist=allowlist, owner_targets=owner_targets)
|
|
14
|
+
if tier == "denied":
|
|
15
|
+
return {"success": False, "tier": "denied", "surface": surface, "target": target,
|
|
16
|
+
"error": ("target not on owner allowlist; ask the owner to run "
|
|
17
|
+
f"`/allow {surface} {target}` (or `polyrob owner allow {surface} {target}`)")}
|
|
18
|
+
if action not in ("send", "reply"):
|
|
19
|
+
# edit/delete/react are capability-gated and deferred to P2; fail cleanly.
|
|
20
|
+
return {"success": False, "tier": tier, "surface": surface, "target": target,
|
|
21
|
+
"error": f"action '{action}' not supported yet on {surface}"}
|
|
22
|
+
if router is None:
|
|
23
|
+
return {"success": False, "tier": tier, "surface": surface, "target": target,
|
|
24
|
+
"error": "no message_router available (SINGULAR_CHAT_ENABLED off?)"}
|
|
25
|
+
try:
|
|
26
|
+
ok = await router.send_message(chat_id=target, text=text, surface_id=surface)
|
|
27
|
+
except Exception as e: # fail-open: never crash the loop on a send fault
|
|
28
|
+
logger.error("message send failed: %s", e, exc_info=True)
|
|
29
|
+
return {"success": False, "tier": tier, "surface": surface, "target": target, "error": str(e)}
|
|
30
|
+
return {"success": bool(ok), "tier": tier, "surface": surface, "target": target,
|
|
31
|
+
"error": None if ok else "send returned false"}
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
"""Provider-specific tool schema generators for native tool calling"""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any, Dict, List, Optional, Type, Tuple
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from tools.controller.registry.views import RegisteredAction
|
|
11
|
+
from agents.task.utils import fix_openai_schema, fix_anthropic_schema
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SchemaValidationError(Exception):
|
|
17
|
+
"""Raised when a generated schema fails validation."""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_VALID_SCHEMA_ERROR_POLICIES = ("DROP_TOOL", "RAISE", "WARN")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_schema_error_policy() -> str:
|
|
25
|
+
"""Policy for handling a tool whose generated native schema fails validation.
|
|
26
|
+
|
|
27
|
+
Env ``TOOL_SCHEMA_ERROR_POLICY`` (default ``DROP_TOOL``):
|
|
28
|
+
- ``DROP_TOOL`` — exclude the offending tool from the emitted list (never
|
|
29
|
+
ship an invalid schema to the provider).
|
|
30
|
+
- ``RAISE`` — propagate ``SchemaValidationError``.
|
|
31
|
+
- ``WARN`` — legacy: log and ship the tool anyway.
|
|
32
|
+
"""
|
|
33
|
+
val = os.getenv("TOOL_SCHEMA_ERROR_POLICY", "DROP_TOOL").upper().strip()
|
|
34
|
+
return val if val in _VALID_SCHEMA_ERROR_POLICIES else "DROP_TOOL"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ToolSchemaGenerator(ABC):
|
|
38
|
+
"""Base class for provider-specific tool schema generation"""
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def generate_tool_schema(self, action: RegisteredAction) -> Dict[str, Any]:
|
|
42
|
+
"""Generate tool schema for specific provider"""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def generate_tools_list(self, actions: List[RegisteredAction]) -> Any:
|
|
47
|
+
"""Generate the complete tools list/object for the provider"""
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
def _get_json_schema(self, param_model: Type[BaseModel]) -> Dict[str, Any]:
|
|
51
|
+
"""Get JSON schema from Pydantic model, removing unnecessary fields"""
|
|
52
|
+
schema = param_model.model_json_schema()
|
|
53
|
+
# Remove title from properties to reduce token usage
|
|
54
|
+
if 'properties' in schema:
|
|
55
|
+
for prop in schema['properties'].values():
|
|
56
|
+
if isinstance(prop, dict) and 'title' in prop:
|
|
57
|
+
del prop['title']
|
|
58
|
+
# Remove title from root
|
|
59
|
+
if 'title' in schema:
|
|
60
|
+
del schema['title']
|
|
61
|
+
return schema
|
|
62
|
+
|
|
63
|
+
def _validate_schema(self, schema: Dict[str, Any], action_name: str) -> Tuple[bool, List[str]]:
|
|
64
|
+
"""Validate a generated schema for common issues.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Tuple of (is_valid, list of warning/error messages)
|
|
68
|
+
"""
|
|
69
|
+
issues = []
|
|
70
|
+
|
|
71
|
+
# Check for basic structure
|
|
72
|
+
if not isinstance(schema, dict):
|
|
73
|
+
issues.append(f"Schema for '{action_name}' is not a dictionary")
|
|
74
|
+
return False, issues
|
|
75
|
+
|
|
76
|
+
# Check for required fields based on schema type
|
|
77
|
+
if 'function' in schema:
|
|
78
|
+
# OpenAI format
|
|
79
|
+
func = schema.get('function', {})
|
|
80
|
+
if not func.get('name'):
|
|
81
|
+
issues.append(f"OpenAI schema for '{action_name}' missing function name")
|
|
82
|
+
if not func.get('description'):
|
|
83
|
+
issues.append(f"OpenAI schema for '{action_name}' missing description")
|
|
84
|
+
if 'parameters' not in func:
|
|
85
|
+
issues.append(f"OpenAI schema for '{action_name}' missing parameters")
|
|
86
|
+
else:
|
|
87
|
+
params = func.get('parameters', {})
|
|
88
|
+
if params.get('type') != 'object':
|
|
89
|
+
issues.append(f"OpenAI schema for '{action_name}' parameters should have type 'object'")
|
|
90
|
+
elif 'input_schema' in schema:
|
|
91
|
+
# Anthropic format
|
|
92
|
+
if not schema.get('name'):
|
|
93
|
+
issues.append(f"Anthropic schema for '{action_name}' missing name")
|
|
94
|
+
if not schema.get('description'):
|
|
95
|
+
issues.append(f"Anthropic schema for '{action_name}' missing description")
|
|
96
|
+
input_schema = schema.get('input_schema', {})
|
|
97
|
+
if input_schema.get('type') != 'object':
|
|
98
|
+
issues.append(f"Anthropic schema for '{action_name}' input_schema should have type 'object'")
|
|
99
|
+
elif 'parameters' in schema and 'name' in schema:
|
|
100
|
+
# Gemini format
|
|
101
|
+
if not schema.get('name'):
|
|
102
|
+
issues.append(f"Gemini schema for '{action_name}' missing name")
|
|
103
|
+
if not schema.get('description'):
|
|
104
|
+
issues.append(f"Gemini schema for '{action_name}' missing description")
|
|
105
|
+
|
|
106
|
+
# Check for empty properties (debug only - not an error, some actions have no params)
|
|
107
|
+
properties = None
|
|
108
|
+
if 'function' in schema:
|
|
109
|
+
properties = schema.get('function', {}).get('parameters', {}).get('properties')
|
|
110
|
+
elif 'input_schema' in schema:
|
|
111
|
+
properties = schema.get('input_schema', {}).get('properties')
|
|
112
|
+
elif 'parameters' in schema:
|
|
113
|
+
properties = schema.get('parameters', {}).get('properties')
|
|
114
|
+
|
|
115
|
+
# Note: Empty properties is not an error - some actions intentionally have no parameters
|
|
116
|
+
# (e.g., get_positions, get_portfolio_value, etc.)
|
|
117
|
+
# Only log at debug level to avoid noise
|
|
118
|
+
if properties is not None and len(properties) == 0:
|
|
119
|
+
logger.debug(f"Schema for '{action_name}' has empty properties (no parameters) - this is expected for parameterless actions")
|
|
120
|
+
|
|
121
|
+
is_valid = len([i for i in issues if 'missing' in i.lower()]) == 0
|
|
122
|
+
return is_valid, issues
|
|
123
|
+
|
|
124
|
+
def validate_and_log(self, schema: Dict[str, Any], action_name: str) -> Dict[str, Any]:
|
|
125
|
+
"""Validate schema and log any issues.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
schema: The generated schema
|
|
129
|
+
action_name: Name of the action for logging
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
The schema (unchanged)
|
|
133
|
+
|
|
134
|
+
Raises:
|
|
135
|
+
SchemaValidationError: If validation fails critically
|
|
136
|
+
"""
|
|
137
|
+
is_valid, issues = self._validate_schema(schema, action_name)
|
|
138
|
+
|
|
139
|
+
if issues:
|
|
140
|
+
for issue in issues:
|
|
141
|
+
if 'missing' in issue.lower():
|
|
142
|
+
logger.error(f"Schema validation error: {issue}")
|
|
143
|
+
else:
|
|
144
|
+
logger.warning(f"Schema validation warning: {issue}")
|
|
145
|
+
|
|
146
|
+
if not is_valid:
|
|
147
|
+
raise SchemaValidationError(f"Schema validation failed for '{action_name}': {issues}")
|
|
148
|
+
|
|
149
|
+
return schema
|
|
150
|
+
|
|
151
|
+
def _apply_schema_error_policy(self, schema: Dict[str, Any], action_name: str) -> Optional[Dict[str, Any]]:
|
|
152
|
+
"""Validate ``schema`` and apply ``TOOL_SCHEMA_ERROR_POLICY``.
|
|
153
|
+
|
|
154
|
+
Returns the schema if valid (or under WARN policy), ``None`` if the tool
|
|
155
|
+
should be dropped (DROP_TOOL), or raises ``SchemaValidationError`` (RAISE).
|
|
156
|
+
"""
|
|
157
|
+
is_valid, issues = self._validate_schema(schema, action_name)
|
|
158
|
+
if is_valid:
|
|
159
|
+
return schema
|
|
160
|
+
|
|
161
|
+
policy = get_schema_error_policy()
|
|
162
|
+
msg = f"Schema validation failed for '{action_name}': {issues}"
|
|
163
|
+
if policy == "RAISE":
|
|
164
|
+
raise SchemaValidationError(msg)
|
|
165
|
+
if policy == "WARN":
|
|
166
|
+
logger.warning(f"{msg} (WARN policy: shipping tool anyway)")
|
|
167
|
+
return schema
|
|
168
|
+
# DROP_TOOL (default): never ship an invalid schema to the provider
|
|
169
|
+
logger.warning(f"{msg} (DROP_TOOL policy: excluding this tool from the list)")
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class OpenAISchemaGenerator(ToolSchemaGenerator):
|
|
174
|
+
"""OpenAI function calling schema generator"""
|
|
175
|
+
|
|
176
|
+
def generate_tool_schema(self, action: RegisteredAction, validate: bool = True) -> Dict[str, Any]:
|
|
177
|
+
"""Generate OpenAI function schema"""
|
|
178
|
+
# Get the base schema
|
|
179
|
+
params_schema = self._get_json_schema(action.param_model)
|
|
180
|
+
|
|
181
|
+
# Apply OpenAI-specific fixes (additionalProperties: false)
|
|
182
|
+
params_schema = fix_openai_schema(params_schema)
|
|
183
|
+
|
|
184
|
+
schema = {
|
|
185
|
+
"type": "function",
|
|
186
|
+
"function": {
|
|
187
|
+
"name": action.name,
|
|
188
|
+
"description": action.description,
|
|
189
|
+
"parameters": params_schema
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# Validate if requested (log warnings but don't fail)
|
|
194
|
+
if validate:
|
|
195
|
+
try:
|
|
196
|
+
self.validate_and_log(schema, action.name)
|
|
197
|
+
except SchemaValidationError as e:
|
|
198
|
+
logger.warning(f"Schema validation issue (continuing anyway): {e}")
|
|
199
|
+
|
|
200
|
+
return schema
|
|
201
|
+
|
|
202
|
+
def generate_tools_list(self, actions: List[RegisteredAction]) -> List[Dict[str, Any]]:
|
|
203
|
+
"""Generate OpenAI tools list with deduplication by name + error policy."""
|
|
204
|
+
seen_names = set()
|
|
205
|
+
tools = []
|
|
206
|
+
for action in actions:
|
|
207
|
+
if action.name in seen_names:
|
|
208
|
+
logger.debug(f"Skipping duplicate tool name: {action.name}")
|
|
209
|
+
continue
|
|
210
|
+
seen_names.add(action.name)
|
|
211
|
+
schema = self._apply_schema_error_policy(
|
|
212
|
+
self.generate_tool_schema(action, validate=False), action.name
|
|
213
|
+
)
|
|
214
|
+
if schema is not None:
|
|
215
|
+
tools.append(schema)
|
|
216
|
+
return tools
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class AnthropicSchemaGenerator(ToolSchemaGenerator):
|
|
220
|
+
"""Anthropic tool calling schema generator"""
|
|
221
|
+
|
|
222
|
+
def generate_tool_schema(self, action: RegisteredAction, validate: bool = True) -> Dict[str, Any]:
|
|
223
|
+
"""Generate Anthropic tool schema"""
|
|
224
|
+
# Get the base schema
|
|
225
|
+
params_schema = self._get_json_schema(action.param_model)
|
|
226
|
+
|
|
227
|
+
# Apply Anthropic-specific fixes (additionalProperties: false)
|
|
228
|
+
params_schema = fix_anthropic_schema(params_schema)
|
|
229
|
+
|
|
230
|
+
schema = {
|
|
231
|
+
"name": action.name,
|
|
232
|
+
"description": action.description,
|
|
233
|
+
"input_schema": params_schema
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
# Validate if requested (log warnings but don't fail)
|
|
237
|
+
if validate:
|
|
238
|
+
try:
|
|
239
|
+
self.validate_and_log(schema, action.name)
|
|
240
|
+
except SchemaValidationError as e:
|
|
241
|
+
logger.warning(f"Schema validation issue (continuing anyway): {e}")
|
|
242
|
+
|
|
243
|
+
return schema
|
|
244
|
+
|
|
245
|
+
def generate_tools_list(self, actions: List[RegisteredAction]) -> List[Dict[str, Any]]:
|
|
246
|
+
"""Generate Anthropic tools list with deduplication by name + error policy."""
|
|
247
|
+
seen_names = set()
|
|
248
|
+
tools = []
|
|
249
|
+
for action in actions:
|
|
250
|
+
if action.name in seen_names:
|
|
251
|
+
logger.debug(f"Skipping duplicate tool name: {action.name}")
|
|
252
|
+
continue
|
|
253
|
+
seen_names.add(action.name)
|
|
254
|
+
schema = self._apply_schema_error_policy(
|
|
255
|
+
self.generate_tool_schema(action, validate=False), action.name
|
|
256
|
+
)
|
|
257
|
+
if schema is not None:
|
|
258
|
+
tools.append(schema)
|
|
259
|
+
return tools
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class GeminiSchemaGenerator(ToolSchemaGenerator):
|
|
263
|
+
"""Google Gemini function calling schema generator"""
|
|
264
|
+
|
|
265
|
+
def generate_tool_schema(self, action: RegisteredAction, validate: bool = True) -> Dict[str, Any]:
|
|
266
|
+
"""Generate Gemini function schema"""
|
|
267
|
+
base_schema = self._get_json_schema(action.param_model)
|
|
268
|
+
|
|
269
|
+
# Gemini expects 'type' and 'properties' at minimum
|
|
270
|
+
parameters = {
|
|
271
|
+
"type": "object",
|
|
272
|
+
"properties": base_schema.get("properties", {}),
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
# Only add required if it exists and is non-empty
|
|
276
|
+
if "required" in base_schema and base_schema["required"]:
|
|
277
|
+
parameters["required"] = base_schema["required"]
|
|
278
|
+
|
|
279
|
+
schema = {
|
|
280
|
+
"name": action.name,
|
|
281
|
+
"description": action.description,
|
|
282
|
+
"parameters": parameters
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
# Validate if requested (log warnings but don't fail)
|
|
286
|
+
if validate:
|
|
287
|
+
try:
|
|
288
|
+
self.validate_and_log(schema, action.name)
|
|
289
|
+
except SchemaValidationError as e:
|
|
290
|
+
logger.warning(f"Schema validation issue (continuing anyway): {e}")
|
|
291
|
+
|
|
292
|
+
return schema
|
|
293
|
+
|
|
294
|
+
def generate_tools_list(self, actions: List[RegisteredAction]) -> List[Dict[str, Any]]:
|
|
295
|
+
"""Generate Gemini tools list with function declarations + error policy."""
|
|
296
|
+
if not actions:
|
|
297
|
+
return []
|
|
298
|
+
|
|
299
|
+
declarations = []
|
|
300
|
+
for action in actions:
|
|
301
|
+
schema = self._apply_schema_error_policy(
|
|
302
|
+
self.generate_tool_schema(action, validate=False), action.name
|
|
303
|
+
)
|
|
304
|
+
if schema is not None:
|
|
305
|
+
declarations.append(schema)
|
|
306
|
+
if not declarations:
|
|
307
|
+
return []
|
|
308
|
+
return [{"function_declarations": declarations}]
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class JSONFallbackSchemaGenerator(ToolSchemaGenerator):
|
|
312
|
+
"""JSON schema generator for providers without native tool support"""
|
|
313
|
+
|
|
314
|
+
def generate_tool_schema(self, action: RegisteredAction) -> Dict[str, Any]:
|
|
315
|
+
"""Generate JSON schema for action"""
|
|
316
|
+
return {
|
|
317
|
+
"name": action.name,
|
|
318
|
+
"description": action.description,
|
|
319
|
+
"parameters": self._get_json_schema(action.param_model)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
def generate_tools_list(self, actions: List[RegisteredAction]) -> Dict[str, Any]:
|
|
323
|
+
"""Generate JSON schema for all actions"""
|
|
324
|
+
# This creates a schema that expects: {"action": [{"action_name": {...params}}]}
|
|
325
|
+
action_properties = {}
|
|
326
|
+
|
|
327
|
+
for action in actions:
|
|
328
|
+
action_properties[action.name] = {
|
|
329
|
+
"type": "object",
|
|
330
|
+
"description": action.description,
|
|
331
|
+
"properties": action.param_model.model_json_schema().get("properties", {}),
|
|
332
|
+
"required": action.param_model.model_json_schema().get("required", [])
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
"type": "object",
|
|
337
|
+
"properties": {
|
|
338
|
+
"action": {
|
|
339
|
+
"type": "array",
|
|
340
|
+
"description": "List of actions to execute",
|
|
341
|
+
"items": {
|
|
342
|
+
"type": "object",
|
|
343
|
+
"properties": action_properties,
|
|
344
|
+
"additionalProperties": False
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
"required": ["action"]
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
# Provider registry mapping
|
|
353
|
+
# NOTE: DeepSeek V3+ uses OpenAI-compatible function calling API
|
|
354
|
+
# NOTE: OpenRouter proxies to many providers and uses OpenAI-compatible format
|
|
355
|
+
SCHEMA_GENERATORS = {
|
|
356
|
+
"openai": OpenAISchemaGenerator(),
|
|
357
|
+
"anthropic": AnthropicSchemaGenerator(),
|
|
358
|
+
"gemini": GeminiSchemaGenerator(),
|
|
359
|
+
"google": GeminiSchemaGenerator(), # Alias
|
|
360
|
+
"deepseek": OpenAISchemaGenerator(), # DeepSeek V3+ is OpenAI-compatible
|
|
361
|
+
"openrouter": OpenAISchemaGenerator(), # OpenRouter uses OpenAI-compatible format
|
|
362
|
+
"nvidia": OpenAISchemaGenerator(), # NVIDIA NIM uses OpenAI-compatible format
|
|
363
|
+
"groq": JSONFallbackSchemaGenerator(),
|
|
364
|
+
"fireworks": JSONFallbackSchemaGenerator(),
|
|
365
|
+
"default": JSONFallbackSchemaGenerator(),
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
# Providers that have intentionally fallen back to the JSON generator are tracked
|
|
370
|
+
# so we warn exactly once per unrecognized provider string (avoids log spam).
|
|
371
|
+
_warned_fallback_providers: set = set()
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def get_schema_generator(provider: str) -> ToolSchemaGenerator:
|
|
375
|
+
"""Get the appropriate schema generator for a provider.
|
|
376
|
+
|
|
377
|
+
Falls back to the JSON generator for unknown providers — but warns once so a
|
|
378
|
+
misspelled/new provider silently losing native tool-calling is visible
|
|
379
|
+
(see the new-provider checklist: SCHEMA_GENERATORS is the easy-to-miss seam).
|
|
380
|
+
"""
|
|
381
|
+
provider_lower = provider.lower()
|
|
382
|
+
|
|
383
|
+
# Check for partial matches (e.g., "openai-gpt4" -> "openai")
|
|
384
|
+
for key in SCHEMA_GENERATORS:
|
|
385
|
+
if key != "default" and key in provider_lower:
|
|
386
|
+
return SCHEMA_GENERATORS[key]
|
|
387
|
+
|
|
388
|
+
if provider_lower not in _warned_fallback_providers:
|
|
389
|
+
_warned_fallback_providers.add(provider_lower)
|
|
390
|
+
logger.warning(
|
|
391
|
+
f"No native schema generator matched provider '{provider}' — using JSON "
|
|
392
|
+
f"fallback (native tool-calling disabled). If this provider supports native "
|
|
393
|
+
f"tools, add it to SCHEMA_GENERATORS."
|
|
394
|
+
)
|
|
395
|
+
return SCHEMA_GENERATORS["default"]
|