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,96 @@
|
|
|
1
|
+
"""Generic OAuth2 auth-code + refresh-token provider (Item 4).
|
|
2
|
+
|
|
3
|
+
Driven by a plain config dict (client_id/secret/auth_url/token_url/scopes/
|
|
4
|
+
redirect_uri). The HTTP token POST is injectable (``http_post``) so the provider is
|
|
5
|
+
unit-testable WITHOUT hitting any live endpoint; the default uses ``httpx``.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import time
|
|
10
|
+
from typing import Any, Awaitable, Callable, Dict, Optional
|
|
11
|
+
from urllib.parse import urlencode
|
|
12
|
+
|
|
13
|
+
from tools.oauth.provider import OAuthError, OAuthProvider, OAuthToken
|
|
14
|
+
|
|
15
|
+
# (url, form_data) -> token-endpoint JSON dict
|
|
16
|
+
HttpPost = Callable[[str, Dict[str, Any]], Awaitable[Dict[str, Any]]]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class GenericOAuth2Provider(OAuthProvider):
|
|
20
|
+
def __init__(self, name: str, config: Dict[str, Any], *, http_post: Optional[HttpPost] = None) -> None:
|
|
21
|
+
self.name = name
|
|
22
|
+
self.client_id = config["client_id"]
|
|
23
|
+
self.client_secret = config.get("client_secret", "")
|
|
24
|
+
self.auth_url = config["auth_url"]
|
|
25
|
+
self.token_url = config["token_url"]
|
|
26
|
+
self.scopes = config.get("scopes") or []
|
|
27
|
+
self.redirect_uri = config.get("redirect_uri")
|
|
28
|
+
self._http_post = http_post
|
|
29
|
+
|
|
30
|
+
def authorize_url(self, *, state: Optional[str] = None, redirect_uri: Optional[str] = None) -> str:
|
|
31
|
+
params = {
|
|
32
|
+
"response_type": "code",
|
|
33
|
+
"client_id": self.client_id,
|
|
34
|
+
"redirect_uri": redirect_uri or self.redirect_uri or "",
|
|
35
|
+
}
|
|
36
|
+
if self.scopes:
|
|
37
|
+
params["scope"] = " ".join(self.scopes)
|
|
38
|
+
if state:
|
|
39
|
+
params["state"] = state
|
|
40
|
+
sep = "&" if "?" in self.auth_url else "?"
|
|
41
|
+
return f"{self.auth_url}{sep}{urlencode(params)}"
|
|
42
|
+
|
|
43
|
+
async def exchange_code(self, code: str, *, redirect_uri: Optional[str] = None) -> OAuthToken:
|
|
44
|
+
data = {
|
|
45
|
+
"grant_type": "authorization_code",
|
|
46
|
+
"code": code,
|
|
47
|
+
"client_id": self.client_id,
|
|
48
|
+
"client_secret": self.client_secret,
|
|
49
|
+
"redirect_uri": redirect_uri or self.redirect_uri or "",
|
|
50
|
+
}
|
|
51
|
+
return self._token_from_response(await self._post_token(data))
|
|
52
|
+
|
|
53
|
+
async def refresh(self, token: OAuthToken) -> OAuthToken:
|
|
54
|
+
if not token.refresh_token:
|
|
55
|
+
raise OAuthError(f"no refresh_token to refresh provider '{self.name}'")
|
|
56
|
+
data = {
|
|
57
|
+
"grant_type": "refresh_token",
|
|
58
|
+
"refresh_token": token.refresh_token,
|
|
59
|
+
"client_id": self.client_id,
|
|
60
|
+
"client_secret": self.client_secret,
|
|
61
|
+
}
|
|
62
|
+
new = self._token_from_response(await self._post_token(data))
|
|
63
|
+
# Many providers don't rotate the refresh token — keep the old one if absent.
|
|
64
|
+
if not new.refresh_token:
|
|
65
|
+
new.refresh_token = token.refresh_token
|
|
66
|
+
return new
|
|
67
|
+
|
|
68
|
+
# -- internals ------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
async def _post_token(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
71
|
+
if self._http_post is not None:
|
|
72
|
+
return await self._http_post(self.token_url, data)
|
|
73
|
+
import httpx # lazy: only needed for the live path
|
|
74
|
+
|
|
75
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
76
|
+
resp = await client.post(self.token_url, data=data)
|
|
77
|
+
resp.raise_for_status()
|
|
78
|
+
return resp.json()
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def _token_from_response(resp: Dict[str, Any]) -> OAuthToken:
|
|
82
|
+
if not resp or "access_token" not in resp:
|
|
83
|
+
raise OAuthError(f"token endpoint returned no access_token: {resp}")
|
|
84
|
+
expires_at = None
|
|
85
|
+
if "expires_in" in resp:
|
|
86
|
+
try:
|
|
87
|
+
expires_at = time.time() + float(resp["expires_in"])
|
|
88
|
+
except (TypeError, ValueError):
|
|
89
|
+
expires_at = None
|
|
90
|
+
return OAuthToken(
|
|
91
|
+
access_token=resp["access_token"],
|
|
92
|
+
refresh_token=resp.get("refresh_token"),
|
|
93
|
+
expires_at=expires_at,
|
|
94
|
+
token_type=resp.get("token_type", "Bearer"),
|
|
95
|
+
scope=resp.get("scope"),
|
|
96
|
+
)
|
tools/perplexity_tool.py
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import List, Optional, Dict, Any
|
|
3
|
+
import json
|
|
4
|
+
import aiohttp
|
|
5
|
+
import ssl
|
|
6
|
+
import certifi
|
|
7
|
+
from core.exceptions import ConfigurationError, APIError, AuthenticationError, ToolError
|
|
8
|
+
from core.config import BotConfig
|
|
9
|
+
from .base_tool import BaseTool
|
|
10
|
+
from tools.controller.views import PerplexitySearchAction, PerplexityAnalyzeAction, PerplexitySourcesAction
|
|
11
|
+
|
|
12
|
+
class PerplexityTool(BaseTool):
|
|
13
|
+
"""Service for interacting with Perplexity API."""
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def required_services(self) -> Dict[str, str]:
|
|
17
|
+
"""Get required services."""
|
|
18
|
+
return {
|
|
19
|
+
'rate_limit_manager': 'Rate limit management'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def optional_services(self) -> Dict[str, str]:
|
|
24
|
+
"""Get optional services."""
|
|
25
|
+
return {
|
|
26
|
+
'cache_manager': 'Cache for API responses' # Only need caching
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
def __init__(self, name: str, config: BotConfig, container: Optional[Any] = None):
|
|
30
|
+
"""Initialize Perplexity service."""
|
|
31
|
+
super().__init__(name=name, config=config, container=container)
|
|
32
|
+
|
|
33
|
+
# Initialize SSL context during construction
|
|
34
|
+
self.ssl_context = ssl.create_default_context(cafile=certifi.where())
|
|
35
|
+
self.base_url = "https://api.perplexity.ai/chat/completions"
|
|
36
|
+
|
|
37
|
+
# Enable if API key is present
|
|
38
|
+
self._enabled = bool(getattr(config, 'perplexity_api_key', None))
|
|
39
|
+
if not self._enabled:
|
|
40
|
+
self.logger.warning("Perplexity API key not found, service will be disabled")
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def required_config(self) -> Dict[str, str]:
|
|
44
|
+
"""Get required configuration keys."""
|
|
45
|
+
return {
|
|
46
|
+
'perplexity_api_key': 'Perplexity API key'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async def _initialize(self) -> None:
|
|
50
|
+
"""Initialize Perplexity service."""
|
|
51
|
+
try:
|
|
52
|
+
# Services are automatically injected by BaseService
|
|
53
|
+
# Just validate API key BEFORE registering actions
|
|
54
|
+
if not self.config.perplexity_api_key or self.config.perplexity_api_key == "placeholder":
|
|
55
|
+
self.logger.warning("Perplexity API key not configured")
|
|
56
|
+
self._enabled = False
|
|
57
|
+
# Mark service as unavailable but don't raise exception
|
|
58
|
+
from tools.base_tool import ToolStatus
|
|
59
|
+
self._status = ToolStatus.FAILED
|
|
60
|
+
# Don't register actions for disabled service
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
# Only register actions if we have a valid API key
|
|
64
|
+
# Call parent's _initialize to register decorated actions
|
|
65
|
+
await super()._initialize()
|
|
66
|
+
|
|
67
|
+
# Test API connection
|
|
68
|
+
try:
|
|
69
|
+
await self._test_api_connection()
|
|
70
|
+
except Exception as e:
|
|
71
|
+
self.logger.error(f"Failed to connect to Perplexity API: {e}")
|
|
72
|
+
self._enabled = False
|
|
73
|
+
from tools.base_tool import ToolStatus
|
|
74
|
+
self._status = ToolStatus.FAILED
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
self.logger.error(f"Failed to initialize Perplexity service: {e}")
|
|
79
|
+
# Don't raise for optional services - mark as unavailable
|
|
80
|
+
from tools.base_tool import ToolStatus
|
|
81
|
+
self._status = ToolStatus.FAILED
|
|
82
|
+
self._enabled = False
|
|
83
|
+
|
|
84
|
+
async def _test_api_connection(self) -> None:
|
|
85
|
+
"""Test API connection."""
|
|
86
|
+
try:
|
|
87
|
+
# Make a minimal test request
|
|
88
|
+
messages = [
|
|
89
|
+
{"role": "user", "content": "test"}
|
|
90
|
+
]
|
|
91
|
+
result = await self._make_request(messages)
|
|
92
|
+
if "error" in result:
|
|
93
|
+
raise APIError(f"API test failed: {result['error']}")
|
|
94
|
+
self.logger.info("Perplexity API connection test successful")
|
|
95
|
+
except AuthenticationError as e:
|
|
96
|
+
self.logger.error(f"Perplexity API authentication failed: {e}")
|
|
97
|
+
self.logger.error("The API key appears to be invalid. Please update PERPLEXITY_API_KEY in your .env file")
|
|
98
|
+
self.logger.error("You can generate a new API key at: https://www.perplexity.ai/settings/api")
|
|
99
|
+
raise
|
|
100
|
+
except Exception as e:
|
|
101
|
+
self.logger.error(f"Perplexity API connection test failed: {e}")
|
|
102
|
+
raise
|
|
103
|
+
|
|
104
|
+
async def _cleanup(self) -> None:
|
|
105
|
+
"""Cleanup Perplexity service resources."""
|
|
106
|
+
try:
|
|
107
|
+
# Close any open connections
|
|
108
|
+
if hasattr(self, 'session') and self.session:
|
|
109
|
+
await self.session.close()
|
|
110
|
+
|
|
111
|
+
self.logger.info("Perplexity service cleaned up successfully")
|
|
112
|
+
|
|
113
|
+
except Exception as e:
|
|
114
|
+
self.logger.error(f"Error during Perplexity service cleanup: {e}")
|
|
115
|
+
raise ToolError(f"Failed to cleanup Perplexity service: {e}")
|
|
116
|
+
|
|
117
|
+
@BaseTool.action(
|
|
118
|
+
'Search for information using Perplexity AI',
|
|
119
|
+
param_model=PerplexitySearchAction
|
|
120
|
+
)
|
|
121
|
+
async def perplexity_search(self, params: PerplexitySearchAction):
|
|
122
|
+
"""Search using Perplexity API."""
|
|
123
|
+
await self.ensure_initialized()
|
|
124
|
+
|
|
125
|
+
if not self._enabled:
|
|
126
|
+
raise ConfigurationError("Perplexity service is not enabled")
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
query = params.query.strip()
|
|
130
|
+
if not query:
|
|
131
|
+
return "Please provide a search query."
|
|
132
|
+
|
|
133
|
+
messages = [
|
|
134
|
+
{"role": "system", "content": "You are a helpful AI assistant that provides accurate and concise information."},
|
|
135
|
+
{"role": "user", "content": query}
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
result = await self._make_request(messages)
|
|
139
|
+
if "error" in result:
|
|
140
|
+
return f"Error: {result['error']}"
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
content = result["choices"][0]["message"]["content"]
|
|
144
|
+
# Limit response size to prevent overwhelming the agent
|
|
145
|
+
MAX_RESPONSE_LENGTH = 50000 # 50k chars (reasonable for search responses)
|
|
146
|
+
if len(content) > MAX_RESPONSE_LENGTH:
|
|
147
|
+
self.logger.warning(f"Perplexity response truncated from {len(content)} to {MAX_RESPONSE_LENGTH} characters")
|
|
148
|
+
content = content[:MAX_RESPONSE_LENGTH] + "\n\n[Response truncated due to size...]"
|
|
149
|
+
return content
|
|
150
|
+
except (KeyError, IndexError) as e:
|
|
151
|
+
return f"Error parsing response: {str(e)}"
|
|
152
|
+
|
|
153
|
+
except AuthenticationError as e:
|
|
154
|
+
self.logger.error(f"Perplexity authentication error: {str(e)}")
|
|
155
|
+
self.logger.error("The API key is invalid. Please update PERPLEXITY_API_KEY in config/.env.development")
|
|
156
|
+
self.logger.error("Generate a new key at: https://www.perplexity.ai/settings/api")
|
|
157
|
+
raise APIError(f"Search failed: Invalid API key. Please update PERPLEXITY_API_KEY in the configuration.")
|
|
158
|
+
except Exception as e:
|
|
159
|
+
self.logger.error(f"Perplexity search error: {str(e)}")
|
|
160
|
+
raise APIError(f"Search failed: {str(e)}")
|
|
161
|
+
|
|
162
|
+
@BaseTool.action(
|
|
163
|
+
'Get detailed analysis on a topic using Perplexity AI',
|
|
164
|
+
param_model=PerplexityAnalyzeAction
|
|
165
|
+
)
|
|
166
|
+
async def perplexity_analyze(self, params: PerplexityAnalyzeAction):
|
|
167
|
+
"""Get detailed analysis from Perplexity."""
|
|
168
|
+
await self.ensure_initialized()
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
topic = params.topic.strip()
|
|
172
|
+
if not topic:
|
|
173
|
+
return "Please provide a topic to analyze."
|
|
174
|
+
|
|
175
|
+
messages = [
|
|
176
|
+
{"role": "system", "content": "You are an analytical AI assistant. Provide a detailed analysis of the given topic."},
|
|
177
|
+
{"role": "user", "content": f"Please provide a detailed analysis of: {topic}"}
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
result = await self._make_request(messages)
|
|
181
|
+
if "error" in result:
|
|
182
|
+
return result["error"]
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
content = result["choices"][0]["message"]["content"]
|
|
186
|
+
# Limit response size to prevent overwhelming the agent
|
|
187
|
+
MAX_RESPONSE_LENGTH = 50000 # 50k chars (reasonable for analysis responses)
|
|
188
|
+
if len(content) > MAX_RESPONSE_LENGTH:
|
|
189
|
+
self.logger.warning(f"Perplexity response truncated from {len(content)} to {MAX_RESPONSE_LENGTH} characters")
|
|
190
|
+
content = content[:MAX_RESPONSE_LENGTH] + "\n\n[Response truncated due to size...]"
|
|
191
|
+
return content
|
|
192
|
+
except (KeyError, IndexError) as e:
|
|
193
|
+
return f"Error parsing response: {str(e)}"
|
|
194
|
+
except Exception as e:
|
|
195
|
+
self.logger.error(f"Perplexity analyze error: {str(e)}")
|
|
196
|
+
raise
|
|
197
|
+
|
|
198
|
+
@BaseTool.action(
|
|
199
|
+
'Get trusted sources and references on a topic using Perplexity AI',
|
|
200
|
+
param_model=PerplexitySourcesAction
|
|
201
|
+
)
|
|
202
|
+
async def perplexity_sources(self, params: PerplexitySourcesAction):
|
|
203
|
+
"""Get sources for a query."""
|
|
204
|
+
await self.ensure_initialized()
|
|
205
|
+
|
|
206
|
+
query = params.topic.strip()
|
|
207
|
+
if not query:
|
|
208
|
+
return "Please provide a query to get sources for."
|
|
209
|
+
|
|
210
|
+
messages = [
|
|
211
|
+
{"role": "system", "content": "You are a research assistant. Provide relevant sources and references for the query."},
|
|
212
|
+
{"role": "user", "content": f"Please provide sources and references for: {query}"}
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
result = await self._make_request(messages)
|
|
216
|
+
if "error" in result:
|
|
217
|
+
return result["error"]
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
return result["choices"][0]["message"]["content"]
|
|
221
|
+
except (KeyError, IndexError) as e:
|
|
222
|
+
return f"Error parsing response: {str(e)}"
|
|
223
|
+
|
|
224
|
+
async def ensure_initialized(self) -> None:
|
|
225
|
+
"""Ensure service is initialized."""
|
|
226
|
+
if not self._initialized:
|
|
227
|
+
await self.initialize()
|
|
228
|
+
|
|
229
|
+
async def test_connection(self) -> bool:
|
|
230
|
+
"""Test if the Perplexity API is accessible and properly configured"""
|
|
231
|
+
if not self.config.perplexity_api_key:
|
|
232
|
+
return False
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
# Make a minimal test request
|
|
236
|
+
messages = [
|
|
237
|
+
{"role": "user", "content": "test"}
|
|
238
|
+
]
|
|
239
|
+
result = await self._make_request(messages)
|
|
240
|
+
return "error" not in result
|
|
241
|
+
except Exception as e:
|
|
242
|
+
self.logger.warning(f"Perplexity API connection test failed: {str(e)}")
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
async def _make_request(self, messages: List[Dict[str, str]]) -> Dict[str, Any]:
|
|
246
|
+
"""Make a request to the Perplexity API."""
|
|
247
|
+
if not self.config.perplexity_api_key:
|
|
248
|
+
raise ConfigurationError("Perplexity API key not configured")
|
|
249
|
+
|
|
250
|
+
headers = {
|
|
251
|
+
"Authorization": f"Bearer {self.config.perplexity_api_key}",
|
|
252
|
+
"Content-Type": "application/json"
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
data = {
|
|
256
|
+
"model": "sonar",
|
|
257
|
+
"messages": messages
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
# Use SSL context in ClientSession
|
|
262
|
+
conn = aiohttp.TCPConnector(ssl=self.ssl_context)
|
|
263
|
+
async with aiohttp.ClientSession(connector=conn) as session:
|
|
264
|
+
async with session.post(
|
|
265
|
+
self.base_url,
|
|
266
|
+
headers=headers,
|
|
267
|
+
json=data,
|
|
268
|
+
ssl=self.ssl_context
|
|
269
|
+
) as response:
|
|
270
|
+
if response.status == 200:
|
|
271
|
+
return await response.json()
|
|
272
|
+
elif response.status == 401:
|
|
273
|
+
raise AuthenticationError("Invalid Perplexity API key")
|
|
274
|
+
else:
|
|
275
|
+
error_text = await response.text()
|
|
276
|
+
self.logger.error(f"Perplexity API error: {error_text}")
|
|
277
|
+
raise APIError(f"API error: {response.status} - {error_text}")
|
|
278
|
+
except aiohttp.ClientError as e:
|
|
279
|
+
self.logger.error(f"Request error: {str(e)}")
|
|
280
|
+
raise APIError(f"Request failed: {str(e)}")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Polymarket Integration Package.
|
|
3
|
+
|
|
4
|
+
Provides access to Polymarket prediction markets through PolymarketTool,
|
|
5
|
+
which uses direct API integration with Gamma + CLOB APIs.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
polymarket.search_markets(query="...")
|
|
9
|
+
polymarket.get_trending_markets(limit=10)
|
|
10
|
+
polymarket.get_market_details(market_id="...")
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from tools.polymarket.service import PolymarketTool, PolymarketDataTool
|
|
14
|
+
|
|
15
|
+
from tools.polymarket.models import (
|
|
16
|
+
PolymarketCredentials,
|
|
17
|
+
TradingLimits,
|
|
18
|
+
ExecutionResult,
|
|
19
|
+
ApiCredentials,
|
|
20
|
+
POLYGON_MAINNET,
|
|
21
|
+
POLYGON_AMOY_TESTNET,
|
|
22
|
+
SIGNATURE_TYPE_EOA,
|
|
23
|
+
SIGNATURE_TYPE_MAGIC,
|
|
24
|
+
SIGNATURE_TYPE_PROXY,
|
|
25
|
+
MIN_ORDER_VALUE_USD,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"PolymarketTool",
|
|
30
|
+
"PolymarketDataTool",
|
|
31
|
+
"PolymarketCredentials",
|
|
32
|
+
"TradingLimits",
|
|
33
|
+
"ExecutionResult",
|
|
34
|
+
"ApiCredentials",
|
|
35
|
+
"POLYGON_MAINNET",
|
|
36
|
+
"POLYGON_AMOY_TESTNET",
|
|
37
|
+
"SIGNATURE_TYPE_EOA",
|
|
38
|
+
"SIGNATURE_TYPE_MAGIC",
|
|
39
|
+
"SIGNATURE_TYPE_PROXY",
|
|
40
|
+
"MIN_ORDER_VALUE_USD",
|
|
41
|
+
]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Single seam for the Polymarket CLOB trading client.
|
|
2
|
+
|
|
3
|
+
Polymarket archived `py-clob-client` in 2026 ("no longer functional"). The current
|
|
4
|
+
maintained low-level client is `py-clob-client-v2`. This module is the ONE place that
|
|
5
|
+
import happens, so a missing/incompatible client is a LOUD, actionable failure instead
|
|
6
|
+
of silently dropping every trading action (the prior behavior, which degraded the tool
|
|
7
|
+
to read-only with no signal).
|
|
8
|
+
|
|
9
|
+
Contract:
|
|
10
|
+
- The READ surface never imports this module.
|
|
11
|
+
- The TRADE surface calls ``require_clob()`` before any order action; absence yields a
|
|
12
|
+
typed ``PolymarketDependencyError`` with an install hint, surfaced to the operator.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any, Dict, Optional
|
|
17
|
+
|
|
18
|
+
from core.exceptions import ToolError
|
|
19
|
+
|
|
20
|
+
INSTALL_HINT = "pip install py-clob-client-v2"
|
|
21
|
+
|
|
22
|
+
# The archived ``py_clob_client`` is intentionally NOT imported here — it is no longer
|
|
23
|
+
# functional. Only the maintained v2 client is supported.
|
|
24
|
+
try:
|
|
25
|
+
from py_clob_client_v2.client import ClobClient # type: ignore
|
|
26
|
+
from py_clob_client_v2.clob_types import ( # type: ignore
|
|
27
|
+
ApiCreds,
|
|
28
|
+
OrderArgs,
|
|
29
|
+
OrderType,
|
|
30
|
+
BalanceAllowanceParams,
|
|
31
|
+
AssetType,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
CLOB_AVAILABLE = True
|
|
35
|
+
CLOB_IMPORT_ERROR: Optional[str] = None
|
|
36
|
+
try: # version is best-effort, never load-bearing
|
|
37
|
+
from importlib.metadata import version as _pkg_version
|
|
38
|
+
|
|
39
|
+
CLOB_VERSION: Optional[str] = _pkg_version("py-clob-client-v2")
|
|
40
|
+
except Exception: # pragma: no cover - metadata absent
|
|
41
|
+
CLOB_VERSION = None
|
|
42
|
+
except Exception as _exc: # ImportError or any transitive failure
|
|
43
|
+
ClobClient = None # type: ignore
|
|
44
|
+
ApiCreds = None # type: ignore
|
|
45
|
+
OrderArgs = None # type: ignore
|
|
46
|
+
OrderType = None # type: ignore
|
|
47
|
+
BalanceAllowanceParams = None # type: ignore
|
|
48
|
+
AssetType = None # type: ignore
|
|
49
|
+
CLOB_AVAILABLE = False
|
|
50
|
+
CLOB_IMPORT_ERROR = f"{type(_exc).__name__}: {_exc}"
|
|
51
|
+
CLOB_VERSION = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class PolymarketDependencyError(ToolError):
|
|
55
|
+
"""Raised when a Polymarket trade action needs the CLOB client but it is absent."""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def trade_capability() -> Dict[str, Any]:
|
|
59
|
+
"""Report whether the trade surface can function, with an actionable hint."""
|
|
60
|
+
if CLOB_AVAILABLE:
|
|
61
|
+
return {"available": True, "reason": "py-clob-client-v2 importable", "install_hint": INSTALL_HINT, "version": CLOB_VERSION}
|
|
62
|
+
return {
|
|
63
|
+
"available": False,
|
|
64
|
+
"reason": f"Polymarket trading client unavailable ({CLOB_IMPORT_ERROR})",
|
|
65
|
+
"install_hint": INSTALL_HINT,
|
|
66
|
+
"version": None,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def require_clob() -> None:
|
|
71
|
+
"""Raise a typed, actionable error if the CLOB trading client is missing."""
|
|
72
|
+
if not CLOB_AVAILABLE:
|
|
73
|
+
raise PolymarketDependencyError(
|
|
74
|
+
f"Polymarket trading requires py-clob-client-v2 ({INSTALL_HINT}); "
|
|
75
|
+
f"import failed: {CLOB_IMPORT_ERROR}"
|
|
76
|
+
)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Polymarket data models.
|
|
3
|
+
|
|
4
|
+
Defines credentials, trading limits, and execution results.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Chain IDs
|
|
12
|
+
POLYGON_MAINNET = 137
|
|
13
|
+
POLYGON_AMOY_TESTNET = 80002
|
|
14
|
+
|
|
15
|
+
# Signature Types for CLOB client authentication
|
|
16
|
+
SIGNATURE_TYPE_EOA = 0 # Standard EOA (MetaMask, hardware wallets)
|
|
17
|
+
SIGNATURE_TYPE_MAGIC = 1 # Email/Magic wallet (delegated signing)
|
|
18
|
+
SIGNATURE_TYPE_PROXY = 2 # Browser wallet proxy (Polymarket website users)
|
|
19
|
+
|
|
20
|
+
# Trading Constants
|
|
21
|
+
MIN_ORDER_VALUE_USD = 1.00
|
|
22
|
+
|
|
23
|
+
# Polymarket Contract Addresses (Polygon)
|
|
24
|
+
USDC_ADDRESS = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
|
|
25
|
+
CTF_ADDRESS = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045"
|
|
26
|
+
EXCHANGE_ADDRESS = "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E"
|
|
27
|
+
NEG_RISK_EXCHANGE = "0xC5d563A36AE78145C45a50134d48A1215220f80a"
|
|
28
|
+
NEG_RISK_ADAPTER = "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class TradingLimits:
|
|
33
|
+
"""
|
|
34
|
+
Trading safety limits for Polymarket operations.
|
|
35
|
+
|
|
36
|
+
All limits are enforced at the tool level before
|
|
37
|
+
making API calls.
|
|
38
|
+
"""
|
|
39
|
+
max_order_size_usd: int = 1000
|
|
40
|
+
max_total_exposure_usd: int = 5000
|
|
41
|
+
max_position_per_market_usd: int = 2000
|
|
42
|
+
min_liquidity_required: int = 10000
|
|
43
|
+
max_spread_tolerance: float = 0.05
|
|
44
|
+
require_confirmation_above_usd: int = 500
|
|
45
|
+
enable_autonomous_trading: bool = False
|
|
46
|
+
allowed_categories: List[str] = field(default_factory=lambda: ["*"])
|
|
47
|
+
blocked_markets: List[str] = field(default_factory=list)
|
|
48
|
+
|
|
49
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
50
|
+
"""Convert to dictionary for storage."""
|
|
51
|
+
return {
|
|
52
|
+
"max_order_size_usd": self.max_order_size_usd,
|
|
53
|
+
"max_total_exposure_usd": self.max_total_exposure_usd,
|
|
54
|
+
"max_position_per_market_usd": self.max_position_per_market_usd,
|
|
55
|
+
"min_liquidity_required": self.min_liquidity_required,
|
|
56
|
+
"max_spread_tolerance": self.max_spread_tolerance,
|
|
57
|
+
"require_confirmation_above_usd": self.require_confirmation_above_usd,
|
|
58
|
+
"enable_autonomous_trading": self.enable_autonomous_trading,
|
|
59
|
+
"allowed_categories": self.allowed_categories,
|
|
60
|
+
"blocked_markets": self.blocked_markets,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def from_dict(cls, data: Dict[str, Any]) -> "TradingLimits":
|
|
65
|
+
"""Create from dictionary."""
|
|
66
|
+
if not data:
|
|
67
|
+
return cls()
|
|
68
|
+
return cls(
|
|
69
|
+
max_order_size_usd=data.get("max_order_size_usd", 1000),
|
|
70
|
+
max_total_exposure_usd=data.get("max_total_exposure_usd", 5000),
|
|
71
|
+
max_position_per_market_usd=data.get("max_position_per_market_usd", 2000),
|
|
72
|
+
min_liquidity_required=data.get("min_liquidity_required", 10000),
|
|
73
|
+
max_spread_tolerance=data.get("max_spread_tolerance", 0.05),
|
|
74
|
+
require_confirmation_above_usd=data.get("require_confirmation_above_usd", 500),
|
|
75
|
+
enable_autonomous_trading=data.get("enable_autonomous_trading", False),
|
|
76
|
+
allowed_categories=data.get("allowed_categories", ["*"]),
|
|
77
|
+
blocked_markets=data.get("blocked_markets", []),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class ApiCredentials:
|
|
83
|
+
"""
|
|
84
|
+
L2 API credentials for authenticated CLOB operations.
|
|
85
|
+
|
|
86
|
+
These are created via the CLOB API using the private key,
|
|
87
|
+
and stored encrypted for reuse.
|
|
88
|
+
"""
|
|
89
|
+
api_key: str
|
|
90
|
+
api_secret: str # Same as passphrase in some contexts
|
|
91
|
+
api_passphrase: str
|
|
92
|
+
created_at: Optional[str] = None
|
|
93
|
+
|
|
94
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
95
|
+
"""Convert to dictionary (secrets included for encrypted storage)."""
|
|
96
|
+
return {
|
|
97
|
+
"api_key": self.api_key,
|
|
98
|
+
"api_secret": self.api_secret,
|
|
99
|
+
"api_passphrase": self.api_passphrase,
|
|
100
|
+
"created_at": self.created_at,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def from_dict(cls, data: Dict[str, Any]) -> Optional["ApiCredentials"]:
|
|
105
|
+
"""Create from dictionary."""
|
|
106
|
+
if not data or not data.get("api_key"):
|
|
107
|
+
return None
|
|
108
|
+
return cls(
|
|
109
|
+
api_key=data.get("api_key", ""),
|
|
110
|
+
api_secret=data.get("api_secret", ""),
|
|
111
|
+
api_passphrase=data.get("api_passphrase", ""),
|
|
112
|
+
created_at=data.get("created_at"),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass
|
|
117
|
+
class PolymarketCredentials:
|
|
118
|
+
"""
|
|
119
|
+
User's Polymarket credentials with proxy wallet support.
|
|
120
|
+
|
|
121
|
+
The private_key is stored encrypted in the database and only
|
|
122
|
+
decrypted in memory when needed for API authentication.
|
|
123
|
+
|
|
124
|
+
L2 API credentials (api_credentials) are created via CLOB API
|
|
125
|
+
and stored for reuse to avoid repeated key creation.
|
|
126
|
+
|
|
127
|
+
Polymarket uses a proxy wallet system:
|
|
128
|
+
- wallet_address: EOA address (derived from private key)
|
|
129
|
+
- proxy_wallet_address: Smart contract wallet (shown on Polymarket profile)
|
|
130
|
+
|
|
131
|
+
Most Polymarket website users need signature_type=2 (PROXY).
|
|
132
|
+
"""
|
|
133
|
+
user_id: str
|
|
134
|
+
wallet_address: Optional[str] = None # EOA address (derived from private key)
|
|
135
|
+
proxy_wallet_address: Optional[str] = None # Smart contract wallet (from Polymarket profile)
|
|
136
|
+
private_key: Optional[str] = field(default=None, repr=False) # Never log this
|
|
137
|
+
signature_type: int = SIGNATURE_TYPE_PROXY # Default to proxy (2) - most common
|
|
138
|
+
demo_mode: bool = True
|
|
139
|
+
enabled: bool = True
|
|
140
|
+
chain_id: int = POLYGON_MAINNET
|
|
141
|
+
trading_limits: TradingLimits = field(default_factory=TradingLimits)
|
|
142
|
+
api_credentials: Optional[ApiCredentials] = field(default=None, repr=False)
|
|
143
|
+
allowances_verified: bool = False # Track if allowances are configured
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def funder_address(self) -> Optional[str]:
|
|
147
|
+
"""
|
|
148
|
+
Get the address that holds funds (proxy wallet if set, else EOA).
|
|
149
|
+
|
|
150
|
+
This is the address used for balance checks and as the 'funder'
|
|
151
|
+
parameter in CLOB client for proxy wallet users.
|
|
152
|
+
"""
|
|
153
|
+
return self.proxy_wallet_address or self.wallet_address
|
|
154
|
+
|
|
155
|
+
def is_configured(self) -> bool:
|
|
156
|
+
"""Check if credentials are properly configured."""
|
|
157
|
+
if self.demo_mode:
|
|
158
|
+
return True
|
|
159
|
+
return bool(self.wallet_address and self.private_key)
|
|
160
|
+
|
|
161
|
+
def has_api_credentials(self) -> bool:
|
|
162
|
+
"""Check if L2 API credentials are available."""
|
|
163
|
+
return self.api_credentials is not None
|
|
164
|
+
|
|
165
|
+
def can_trade(self) -> bool:
|
|
166
|
+
"""Check if user can perform trading operations."""
|
|
167
|
+
return (
|
|
168
|
+
not self.demo_mode
|
|
169
|
+
and self.enabled
|
|
170
|
+
and self.is_configured()
|
|
171
|
+
and self.trading_limits.enable_autonomous_trading
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@dataclass
|
|
176
|
+
class ExecutionResult:
|
|
177
|
+
"""Result from executing a Polymarket action."""
|
|
178
|
+
success: bool
|
|
179
|
+
data: Optional[Any] = None
|
|
180
|
+
error: Optional[str] = None
|
|
181
|
+
tool_name: Optional[str] = None
|
|
182
|
+
execution_time_ms: float = 0.0
|