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,237 @@
|
|
|
1
|
+
"""Credit balance manager for managing user credit balances."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CreditBalanceManager:
|
|
10
|
+
"""Manage user credit balances."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, db, tier_manager=None):
|
|
13
|
+
"""
|
|
14
|
+
Initialize credit balance manager.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
db: Database manager instance
|
|
18
|
+
tier_manager: TierManager instance (optional)
|
|
19
|
+
"""
|
|
20
|
+
self.db = db
|
|
21
|
+
self.tier_manager = tier_manager
|
|
22
|
+
self.logger = logging.getLogger('credits.balance_manager')
|
|
23
|
+
|
|
24
|
+
async def get_balance(self, user_id: str) -> dict:
|
|
25
|
+
"""Get user's credit balance."""
|
|
26
|
+
|
|
27
|
+
result = await self.db.fetch_one("""
|
|
28
|
+
SELECT balance, lifetime_earned, lifetime_spent
|
|
29
|
+
FROM user_credits
|
|
30
|
+
WHERE user_id = ?
|
|
31
|
+
""", (user_id,))
|
|
32
|
+
|
|
33
|
+
if not result:
|
|
34
|
+
# User must exist (payment_endpoints ensures this now)
|
|
35
|
+
# Initialize balance
|
|
36
|
+
await self.db.execute("""
|
|
37
|
+
INSERT INTO user_credits (user_id, balance)
|
|
38
|
+
VALUES (?, 0)
|
|
39
|
+
""", (user_id,))
|
|
40
|
+
return {"balance": 0, "lifetime_earned": 0, "lifetime_spent": 0}
|
|
41
|
+
|
|
42
|
+
return dict(result)
|
|
43
|
+
|
|
44
|
+
async def has_sufficient_balance(self, user_id: str, amount: int) -> bool:
|
|
45
|
+
"""Check if user has enough credits."""
|
|
46
|
+
|
|
47
|
+
balance = await self.get_balance(user_id)
|
|
48
|
+
return balance['balance'] >= amount
|
|
49
|
+
|
|
50
|
+
async def deduct_credits(self, user_id: str, amount: int,
|
|
51
|
+
reason: str, session_id: str = None) -> bool:
|
|
52
|
+
"""
|
|
53
|
+
Deduct credits from user balance.
|
|
54
|
+
|
|
55
|
+
Uses atomic UPDATE with WHERE clause to prevent race conditions.
|
|
56
|
+
Wrapped in transaction for consistency between balance update and transaction log.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
user_id: User ID
|
|
60
|
+
amount: Amount to deduct
|
|
61
|
+
reason: Reason for deduction
|
|
62
|
+
session_id: Optional session ID
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
True if successful, False if insufficient balance
|
|
66
|
+
"""
|
|
67
|
+
# Ensure user exists (creates with 0 balance if not)
|
|
68
|
+
await self.get_balance(user_id)
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
# Start transaction for atomic operation
|
|
72
|
+
await self.db.connection.begin_transaction()
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
# SECURITY FIX: Atomic UPDATE with balance check in WHERE clause
|
|
76
|
+
# This prevents TOCTOU race condition where two concurrent requests
|
|
77
|
+
# could both read the same balance before either deducts
|
|
78
|
+
cursor = await self.db.execute("""
|
|
79
|
+
UPDATE user_credits
|
|
80
|
+
SET balance = balance - ?,
|
|
81
|
+
lifetime_spent = lifetime_spent + ?,
|
|
82
|
+
updated_at = CURRENT_TIMESTAMP
|
|
83
|
+
WHERE user_id = ? AND balance >= ?
|
|
84
|
+
""", (amount, amount, user_id, amount))
|
|
85
|
+
|
|
86
|
+
# Check if update succeeded (rowcount > 0 means balance was sufficient)
|
|
87
|
+
if cursor.rowcount == 0:
|
|
88
|
+
# No rows updated - insufficient balance
|
|
89
|
+
await self.db.connection.rollback()
|
|
90
|
+
|
|
91
|
+
# Get current balance for logging
|
|
92
|
+
balance_info = await self.get_balance(user_id)
|
|
93
|
+
self.logger.warning(
|
|
94
|
+
f"Insufficient credits for {user_id}: need {amount}, have {balance_info['balance']}"
|
|
95
|
+
)
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
# Get new balance for transaction log
|
|
99
|
+
balance_info = await self.get_balance(user_id)
|
|
100
|
+
new_balance = balance_info['balance']
|
|
101
|
+
# Calculate what balance was before deduction
|
|
102
|
+
balance_before = new_balance + amount
|
|
103
|
+
|
|
104
|
+
# Record transaction (within same transaction)
|
|
105
|
+
await self.db.execute("""
|
|
106
|
+
INSERT INTO credit_transactions (
|
|
107
|
+
user_id, amount, transaction_type, reason,
|
|
108
|
+
session_id, balance_before, balance_after, timestamp
|
|
109
|
+
) VALUES (?, ?, 'usage', ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
110
|
+
""", (user_id, -amount, reason, session_id, balance_before, new_balance))
|
|
111
|
+
|
|
112
|
+
# Commit transaction
|
|
113
|
+
await self.db.connection.commit()
|
|
114
|
+
|
|
115
|
+
self.logger.info(f"Deducted {amount} credits from {user_id}: {reason}")
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
# Rollback on any error
|
|
120
|
+
await self.db.connection.rollback()
|
|
121
|
+
raise
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
self.logger.error(f"Error deducting credits for {user_id}: {e}")
|
|
125
|
+
raise
|
|
126
|
+
|
|
127
|
+
async def add_credits(self, user_id: str, amount: int,
|
|
128
|
+
reason: str, transaction_type: str = 'purchase') -> bool:
|
|
129
|
+
"""
|
|
130
|
+
Add credits to user balance.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
user_id: User ID
|
|
134
|
+
amount: Amount to add
|
|
135
|
+
reason: Reason for addition
|
|
136
|
+
transaction_type: Type ('purchase', 'allowance', 'refund', 'admin_grant')
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
True if successful
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
# Ensure the row exists (creates with 0 balance if not) so the atomic
|
|
143
|
+
# UPDATE below actually matches a row.
|
|
144
|
+
await self.get_balance(user_id)
|
|
145
|
+
|
|
146
|
+
# Join an already-open transaction instead of nesting: a caller like
|
|
147
|
+
# DepositMonitor._process_deposit wraps credit + dedup-row in ONE outer
|
|
148
|
+
# transaction, and a nested BEGIN raises (while an inner COMMIT would
|
|
149
|
+
# end the outer transaction early, breaking its rollback guarantee).
|
|
150
|
+
owns_tx = not await self.db.connection.in_transaction()
|
|
151
|
+
try:
|
|
152
|
+
if owns_tx:
|
|
153
|
+
await self.db.connection.begin_transaction()
|
|
154
|
+
try:
|
|
155
|
+
# SECURITY FIX (C2): atomic relative UPDATE, mirroring deduct_credits.
|
|
156
|
+
# The previous read-modify-write (get_balance -> SET balance=<abs>)
|
|
157
|
+
# let two concurrent grants both read the same stale balance and the
|
|
158
|
+
# later absolute write clobber the earlier one — a silently lost grant.
|
|
159
|
+
await self.db.execute("""
|
|
160
|
+
UPDATE user_credits
|
|
161
|
+
SET balance = balance + ?,
|
|
162
|
+
lifetime_earned = lifetime_earned + ?,
|
|
163
|
+
updated_at = CURRENT_TIMESTAMP
|
|
164
|
+
WHERE user_id = ?
|
|
165
|
+
""", (amount, amount, user_id))
|
|
166
|
+
|
|
167
|
+
# Read the authoritative post-update balance for the ledger row.
|
|
168
|
+
balance_info = await self.get_balance(user_id)
|
|
169
|
+
new_balance = balance_info['balance']
|
|
170
|
+
balance_before = new_balance - amount
|
|
171
|
+
|
|
172
|
+
await self.db.execute("""
|
|
173
|
+
INSERT INTO credit_transactions (
|
|
174
|
+
user_id, amount, transaction_type, reason,
|
|
175
|
+
balance_before, balance_after, timestamp
|
|
176
|
+
) VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
177
|
+
""", (user_id, amount, transaction_type, reason, balance_before, new_balance))
|
|
178
|
+
|
|
179
|
+
if owns_tx:
|
|
180
|
+
await self.db.connection.commit()
|
|
181
|
+
except Exception:
|
|
182
|
+
if owns_tx:
|
|
183
|
+
await self.db.connection.rollback()
|
|
184
|
+
raise
|
|
185
|
+
except Exception as e:
|
|
186
|
+
self.logger.error(f"Error adding credits for {user_id}: {e}")
|
|
187
|
+
raise
|
|
188
|
+
|
|
189
|
+
self.logger.info(f"Added {amount} credits to {user_id}: {reason} ({transaction_type})")
|
|
190
|
+
|
|
191
|
+
return True
|
|
192
|
+
|
|
193
|
+
async def get_transaction_history(self, user_id: str, limit: int = 50) -> list:
|
|
194
|
+
"""Get user's credit transaction history."""
|
|
195
|
+
|
|
196
|
+
results = await self.db.fetch_all("""
|
|
197
|
+
SELECT
|
|
198
|
+
amount,
|
|
199
|
+
transaction_type,
|
|
200
|
+
reason,
|
|
201
|
+
session_id,
|
|
202
|
+
balance_before,
|
|
203
|
+
balance_after,
|
|
204
|
+
timestamp
|
|
205
|
+
FROM credit_transactions
|
|
206
|
+
WHERE user_id = ?
|
|
207
|
+
ORDER BY timestamp DESC
|
|
208
|
+
LIMIT ?
|
|
209
|
+
""", (user_id, limit))
|
|
210
|
+
|
|
211
|
+
return [dict(row) for row in results]
|
|
212
|
+
|
|
213
|
+
async def get_monthly_stats(self, user_id: str) -> dict:
|
|
214
|
+
"""Get monthly usage statistics."""
|
|
215
|
+
|
|
216
|
+
# Get this month's usage
|
|
217
|
+
month_usage = await self.db.fetch_one("""
|
|
218
|
+
SELECT COALESCE(SUM(ABS(amount)), 0) as total
|
|
219
|
+
FROM credit_transactions
|
|
220
|
+
WHERE user_id = ?
|
|
221
|
+
AND transaction_type = 'usage'
|
|
222
|
+
AND timestamp >= date('now', 'start of month')
|
|
223
|
+
""", (user_id,))
|
|
224
|
+
|
|
225
|
+
# Get this month's purchases
|
|
226
|
+
month_purchased = await self.db.fetch_one("""
|
|
227
|
+
SELECT COALESCE(SUM(amount), 0) as total
|
|
228
|
+
FROM credit_transactions
|
|
229
|
+
WHERE user_id = ?
|
|
230
|
+
AND transaction_type IN ('purchase', 'allowance')
|
|
231
|
+
AND timestamp >= date('now', 'start of month')
|
|
232
|
+
""", (user_id,))
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
"month_spent": month_usage['total'] if month_usage else 0,
|
|
236
|
+
"month_earned": month_purchased['total'] if month_purchased else 0
|
|
237
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared cost calculation utilities.
|
|
3
|
+
|
|
4
|
+
SINGLE SOURCE OF TRUTH for cost estimation from token counts.
|
|
5
|
+
All modules should import from here, not duplicate logic.
|
|
6
|
+
|
|
7
|
+
This module consolidates:
|
|
8
|
+
- webview/stats_service.py::_calculate_cost_from_registry (REMOVED)
|
|
9
|
+
- agents/task/telemetry/service.py::_calculate_cost_from_registry (REMOVED)
|
|
10
|
+
- modules/llm/token_counter.py::estimate_cost (delegates here)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Optional, Tuple, Dict, Any
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def calculate_cost_from_tokens(
|
|
20
|
+
model_name: str,
|
|
21
|
+
input_tokens: Optional[int] = None,
|
|
22
|
+
output_tokens: Optional[int] = None,
|
|
23
|
+
total_tokens: Optional[int] = None,
|
|
24
|
+
cached_tokens: int = 0
|
|
25
|
+
) -> float:
|
|
26
|
+
"""
|
|
27
|
+
Calculate API cost from token counts using model registry.
|
|
28
|
+
|
|
29
|
+
This is the SINGLE implementation - all other files should import this.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
model_name: Name of the model
|
|
33
|
+
input_tokens: Number of input/prompt tokens
|
|
34
|
+
output_tokens: Number of output/completion tokens
|
|
35
|
+
total_tokens: Total tokens (fallback if split not available)
|
|
36
|
+
cached_tokens: Number of cached tokens
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Estimated API cost in USD
|
|
40
|
+
"""
|
|
41
|
+
from modules.llm.model_registry import calculate_cost
|
|
42
|
+
|
|
43
|
+
if not model_name or model_name == "unknown":
|
|
44
|
+
logger.debug("Cannot calculate cost: invalid model name")
|
|
45
|
+
return 0.0
|
|
46
|
+
|
|
47
|
+
# Use split tokens if available (most accurate)
|
|
48
|
+
if input_tokens is not None and output_tokens is not None:
|
|
49
|
+
try:
|
|
50
|
+
return calculate_cost(
|
|
51
|
+
model_name=model_name,
|
|
52
|
+
input_tokens=input_tokens,
|
|
53
|
+
output_tokens=output_tokens,
|
|
54
|
+
cached_tokens=cached_tokens
|
|
55
|
+
)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logger.warning(f"Cost calculation failed for {model_name}: {e}")
|
|
58
|
+
# Fall through to estimation
|
|
59
|
+
|
|
60
|
+
# Fallback: estimate split from total (65% input, 35% output)
|
|
61
|
+
if total_tokens and total_tokens > 0:
|
|
62
|
+
estimated_input = int(total_tokens * 0.65)
|
|
63
|
+
estimated_output = int(total_tokens * 0.35)
|
|
64
|
+
|
|
65
|
+
logger.debug(
|
|
66
|
+
f"Estimating token split for {model_name}: "
|
|
67
|
+
f"{estimated_input} input + {estimated_output} output = {total_tokens} total"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
return calculate_cost(
|
|
72
|
+
model_name=model_name,
|
|
73
|
+
input_tokens=estimated_input,
|
|
74
|
+
output_tokens=estimated_output,
|
|
75
|
+
cached_tokens=0 # Can't estimate cached from total
|
|
76
|
+
)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.warning(f"Cost estimation failed for {model_name}: {e}")
|
|
79
|
+
|
|
80
|
+
logger.debug(f"Cannot calculate cost for {model_name}: no token data")
|
|
81
|
+
return 0.0
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def calculate_user_cost(api_cost_usd: float) -> Tuple[int, float]:
|
|
85
|
+
"""
|
|
86
|
+
Calculate what user pays from API cost.
|
|
87
|
+
|
|
88
|
+
SINGLE SOURCE - uses pricing.py configuration.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
api_cost_usd: What we pay the API provider
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Tuple of (credits_charged, user_cost_usd)
|
|
95
|
+
"""
|
|
96
|
+
from modules.credits.pricing import pricing
|
|
97
|
+
return pricing.calculate_credits_from_api_cost(api_cost_usd)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_cost_breakdown(api_cost_usd: float) -> Dict[str, Any]:
|
|
101
|
+
"""
|
|
102
|
+
Get complete cost breakdown for transparency.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
api_cost_usd: What we pay the API provider
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Dict with complete breakdown including markup
|
|
109
|
+
"""
|
|
110
|
+
from modules.credits.pricing import pricing
|
|
111
|
+
|
|
112
|
+
credits_charged, user_cost_usd = pricing.calculate_credits_from_api_cost(api_cost_usd)
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
"api_cost_usd": api_cost_usd,
|
|
116
|
+
"user_cost_usd": user_cost_usd,
|
|
117
|
+
"credits_charged": credits_charged,
|
|
118
|
+
"markup": pricing.MARKUP,
|
|
119
|
+
"markup_usd": user_cost_usd - api_cost_usd
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# Re-export for convenience
|
|
124
|
+
__all__ = [
|
|
125
|
+
'calculate_cost_from_tokens',
|
|
126
|
+
'calculate_user_cost',
|
|
127
|
+
'get_cost_breakdown'
|
|
128
|
+
]
|
|
129
|
+
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Credits Module - Pricing Configuration
|
|
3
|
+
|
|
4
|
+
SINGLE SOURCE OF TRUTH for all credit pricing and cost calculation.
|
|
5
|
+
|
|
6
|
+
This config is used by:
|
|
7
|
+
- modules/credits/usage_tracker.py (calculates and charges)
|
|
8
|
+
- modules/credits/usage_meter.py (metering)
|
|
9
|
+
- modules/credits/balance_manager.py (balance operations)
|
|
10
|
+
- modules/auth/identity_mapper.py (welcome bonus, DEN allowance)
|
|
11
|
+
- modules/auth/tier_manager.py (tier limits)
|
|
12
|
+
- api/payment_endpoints.py (public pricing API)
|
|
13
|
+
- webview/stats_service.py (display fallback only)
|
|
14
|
+
|
|
15
|
+
Location: modules/credits/pricing.py
|
|
16
|
+
Why here? Pricing is OWNED by the credits module - it's business logic, not bot config.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import math
|
|
20
|
+
import os
|
|
21
|
+
from typing import Dict
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ============================================================================
|
|
25
|
+
# CREDIT BONUSES - Single source of truth for all bonus amounts
|
|
26
|
+
# ============================================================================
|
|
27
|
+
|
|
28
|
+
# Welcome bonus given to ALL new users on sign up
|
|
29
|
+
WELCOME_BONUS: int = int(os.environ.get("WELCOME_BONUS", "100"))
|
|
30
|
+
|
|
31
|
+
# One-time DEN Sign Up Allowance per token ID (prevents transfer abuse)
|
|
32
|
+
DEN_SIGNUP_ALLOWANCE: int = int(os.environ.get("DEN_SIGNUP_ALLOWANCE", "2000"))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PricingConfig:
|
|
36
|
+
"""Centralized pricing configuration for the entire application."""
|
|
37
|
+
|
|
38
|
+
# ============================================================================
|
|
39
|
+
# MARKUP CONFIGURATION (Core business logic)
|
|
40
|
+
# ============================================================================
|
|
41
|
+
|
|
42
|
+
# MARKUP: Users pay API cost × this multiplier
|
|
43
|
+
# Examples:
|
|
44
|
+
# 1.00 = 0% markup (users pay exactly what API costs)
|
|
45
|
+
# 1.20 = 20% markup (users pay API cost + 20%)
|
|
46
|
+
# 1.50 = 50% markup
|
|
47
|
+
MARKUP: float = float(os.environ.get("PRICING_MARKUP", "1.00"))
|
|
48
|
+
|
|
49
|
+
# ============================================================================
|
|
50
|
+
# CREDIT SYSTEM (Currency conversion)
|
|
51
|
+
# ============================================================================
|
|
52
|
+
|
|
53
|
+
# How much USD one credit is worth
|
|
54
|
+
CREDIT_VALUE_USD: float = float(os.environ.get("CREDIT_VALUE_USD", "0.01")) # 1 credit = $0.01
|
|
55
|
+
|
|
56
|
+
# Minimum charge per LLM call (prevents free calls)
|
|
57
|
+
MIN_CREDIT_CHARGE: int = int(os.environ.get("MIN_CREDIT_CHARGE", "1")) # Minimum 1 credit
|
|
58
|
+
|
|
59
|
+
# ============================================================================
|
|
60
|
+
# FIXED COSTS (Non-LLM resources)
|
|
61
|
+
# ============================================================================
|
|
62
|
+
|
|
63
|
+
# Session creation cost (infrastructure setup)
|
|
64
|
+
SESSION_CREATION_COST: int = int(os.environ.get("SESSION_CREATION_COST", "1"))
|
|
65
|
+
|
|
66
|
+
# Browser action costs
|
|
67
|
+
BROWSER_PAGE_LOAD_COST: float = float(os.environ.get("BROWSER_PAGE_LOAD_COST", "0.5"))
|
|
68
|
+
BROWSER_SCREENSHOT_COST: float = float(os.environ.get("BROWSER_SCREENSHOT_COST", "0.2"))
|
|
69
|
+
BROWSER_INTERACTION_COST: float = float(os.environ.get("BROWSER_INTERACTION_COST", "0.1"))
|
|
70
|
+
BROWSER_DEFAULT_COST: float = float(os.environ.get("BROWSER_DEFAULT_COST", "0.5"))
|
|
71
|
+
|
|
72
|
+
# Special action costs
|
|
73
|
+
VISION_CALL_COST: int = int(os.environ.get("VISION_CALL_COST", "2"))
|
|
74
|
+
TOOL_CALL_COST: float = float(os.environ.get("TOOL_CALL_COST", "0.5"))
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def get_markup_info(cls) -> Dict[str, any]:
|
|
78
|
+
"""Get markup information for display/reporting.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Dict with markup details and formatted description
|
|
82
|
+
"""
|
|
83
|
+
markup_percentage = (cls.MARKUP - 1.0) * 100
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
"markup": cls.MARKUP,
|
|
87
|
+
"markup_percentage": markup_percentage,
|
|
88
|
+
"description": f"{markup_percentage:.0f}% markup" if markup_percentage > 0 else "No markup (API cost only)",
|
|
89
|
+
"credit_value_usd": cls.CREDIT_VALUE_USD,
|
|
90
|
+
"min_charge": cls.MIN_CREDIT_CHARGE
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def calculate_credits_from_api_cost(cls, api_cost_usd: float) -> tuple[int, float]:
|
|
95
|
+
"""Calculate credits to charge user from API cost.
|
|
96
|
+
|
|
97
|
+
This is the CORE pricing logic used throughout the application.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
api_cost_usd: Cost charged by API provider in USD
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Tuple of (credits_to_charge, user_cost_usd)
|
|
104
|
+
"""
|
|
105
|
+
# Convert to credits with markup
|
|
106
|
+
credits_raw = (api_cost_usd / cls.CREDIT_VALUE_USD) * cls.MARKUP
|
|
107
|
+
|
|
108
|
+
# ALWAYS round UP to ensure we never charge less than API cost
|
|
109
|
+
# Using math.ceil() instead of int(x + 0.5) which was incorrectly rounding DOWN
|
|
110
|
+
# for values like 2.42 → int(2.92) = 2, losing money!
|
|
111
|
+
credits_charged = max(cls.MIN_CREDIT_CHARGE, math.ceil(credits_raw))
|
|
112
|
+
|
|
113
|
+
# Calculate what user actually pays
|
|
114
|
+
user_cost_usd = credits_charged * cls.CREDIT_VALUE_USD
|
|
115
|
+
|
|
116
|
+
return credits_charged, user_cost_usd
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def get_browser_action_cost(cls, action_type: str) -> float:
|
|
120
|
+
"""Get cost for browser action.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
action_type: Type of browser action (page_load, screenshot, interaction, etc.)
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Cost in credits
|
|
127
|
+
"""
|
|
128
|
+
costs = {
|
|
129
|
+
"page_load": cls.BROWSER_PAGE_LOAD_COST,
|
|
130
|
+
"screenshot": cls.BROWSER_SCREENSHOT_COST,
|
|
131
|
+
"interaction": cls.BROWSER_INTERACTION_COST,
|
|
132
|
+
}
|
|
133
|
+
return costs.get(action_type, cls.BROWSER_DEFAULT_COST)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# Create singleton instance for easy importing
|
|
137
|
+
# Usage: from modules.credits.pricing import pricing
|
|
138
|
+
# price = pricing.MARKUP * api_cost
|
|
139
|
+
pricing = PricingConfig()
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Unified agent ledger: one read model over the
|
|
2
|
+
three money organs, so "what did I earn and spend this week, on what" is
|
|
3
|
+
answerable with evidence.
|
|
4
|
+
|
|
5
|
+
Legs (each fail-open to zeros — the stores live in different backends and any
|
|
6
|
+
may be absent in a given deployment):
|
|
7
|
+
|
|
8
|
+
- **costs** — LLM/tool self-cost from ``usage_records`` (api_cost_usd is the
|
|
9
|
+
provider truth; credits are the platform-billing view) in the async
|
|
10
|
+
``database_manager`` store;
|
|
11
|
+
- **outbound spend** — wallet payments from the durable telemetry event log's
|
|
12
|
+
``wallet_spend`` events (``core/wallet/factory._emit_spend_to_event_log``);
|
|
13
|
+
- **inbound** — x402 receipts from ``x402_payment_requests`` (settled rows),
|
|
14
|
+
plus the open pipeline (pending agent invoices).
|
|
15
|
+
|
|
16
|
+
Deliberately read-only and deliberately NOT a new table: `modules/credits`
|
|
17
|
+
stays the platform-billing concern; this is the agent's own balance-sheet VIEW
|
|
18
|
+
joining what already exists (application-level union — the legs share no DB).
|
|
19
|
+
"""
|
|
20
|
+
import logging
|
|
21
|
+
import time
|
|
22
|
+
from typing import Any, Dict, Optional
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def _resolve_db(db=None):
|
|
28
|
+
if db is not None:
|
|
29
|
+
return db
|
|
30
|
+
from core.container import DependencyContainer
|
|
31
|
+
return DependencyContainer.get_instance().get_service("database_manager")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def _costs_leg(database, user_id: str, days: int) -> Dict[str, Any]:
|
|
35
|
+
try:
|
|
36
|
+
row = await database.fetch_one(
|
|
37
|
+
"""SELECT COALESCE(SUM(api_cost_usd), 0) AS api_usd,
|
|
38
|
+
COALESCE(SUM(cost), 0) AS credits,
|
|
39
|
+
COUNT(*) AS calls
|
|
40
|
+
FROM usage_records
|
|
41
|
+
WHERE user_id = ? AND timestamp >= datetime('now', ?)""",
|
|
42
|
+
(user_id, f"-{int(days)} day"),
|
|
43
|
+
)
|
|
44
|
+
return {
|
|
45
|
+
"llm_api_cost_usd": round(float(row.get("api_usd") or 0), 6) if row else 0.0,
|
|
46
|
+
"credits_spent": round(float(row.get("credits") or 0), 4) if row else 0.0,
|
|
47
|
+
"llm_calls": int(row.get("calls") or 0) if row else 0,
|
|
48
|
+
}
|
|
49
|
+
except Exception:
|
|
50
|
+
logger.debug("ledger: usage_records leg unavailable", exc_info=True)
|
|
51
|
+
return {"llm_api_cost_usd": 0.0, "credits_spent": 0.0, "llm_calls": 0}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _wallet_leg(user_id: str, days: int) -> Dict[str, Any]:
|
|
55
|
+
if not user_id:
|
|
56
|
+
# query(user_id=None) means "no filter" — an empty tenant must NEVER
|
|
57
|
+
# widen into the platform-wide spend aggregate (cross-tenant leak)
|
|
58
|
+
return {"wallet_spend_usd": 0.0, "wallet_payments": 0}
|
|
59
|
+
try:
|
|
60
|
+
from agents.task.telemetry.event_log import get_event_log, event_log_enabled
|
|
61
|
+
if not event_log_enabled():
|
|
62
|
+
return {"wallet_spend_usd": 0.0, "wallet_payments": 0}
|
|
63
|
+
events = get_event_log().query(
|
|
64
|
+
kind="wallet_spend", user_id=user_id,
|
|
65
|
+
since_ts=time.time() - days * 86400, limit=1000,
|
|
66
|
+
)
|
|
67
|
+
total = 0.0
|
|
68
|
+
for ev in events or []:
|
|
69
|
+
attrs = ev.get("attrs") or {}
|
|
70
|
+
try:
|
|
71
|
+
total += float(attrs.get("amount_usd") or 0)
|
|
72
|
+
except (TypeError, ValueError):
|
|
73
|
+
continue
|
|
74
|
+
return {"wallet_spend_usd": round(total, 6), "wallet_payments": len(events or [])}
|
|
75
|
+
except Exception:
|
|
76
|
+
logger.debug("ledger: wallet_spend leg unavailable", exc_info=True)
|
|
77
|
+
return {"wallet_spend_usd": 0.0, "wallet_payments": 0}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def _inbound_leg(database, user_id: str, days: int) -> Dict[str, Any]:
|
|
81
|
+
try:
|
|
82
|
+
tenant_like = f'%"tenant_id": "{user_id}"%'
|
|
83
|
+
settled = await database.fetch_one(
|
|
84
|
+
"""SELECT COALESCE(SUM(amount_usd), 0) AS usd, COUNT(*) AS n
|
|
85
|
+
FROM x402_payment_requests
|
|
86
|
+
WHERE (user_id = ? OR metadata LIKE ?)
|
|
87
|
+
AND status IN ('completed', 'settled_no_tx')
|
|
88
|
+
AND created_at >= datetime('now', ?)""",
|
|
89
|
+
(user_id, tenant_like, f"-{int(days)} day"),
|
|
90
|
+
)
|
|
91
|
+
pending = await database.fetch_one(
|
|
92
|
+
"""SELECT COALESCE(SUM(amount_usd), 0) AS usd, COUNT(*) AS n
|
|
93
|
+
FROM x402_payment_requests
|
|
94
|
+
WHERE (user_id = ? OR metadata LIKE ?) AND status = 'pending'""",
|
|
95
|
+
(user_id, tenant_like),
|
|
96
|
+
)
|
|
97
|
+
return {
|
|
98
|
+
"earned_usd": round(float(settled.get("usd") or 0), 6) if settled else 0.0,
|
|
99
|
+
"settled_payments": int(settled.get("n") or 0) if settled else 0,
|
|
100
|
+
"pending_invoices_usd": round(float(pending.get("usd") or 0), 6) if pending else 0.0,
|
|
101
|
+
"pending_invoices": int(pending.get("n") or 0) if pending else 0,
|
|
102
|
+
}
|
|
103
|
+
except Exception:
|
|
104
|
+
logger.debug("ledger: x402 inbound leg unavailable", exc_info=True)
|
|
105
|
+
return {"earned_usd": 0.0, "settled_payments": 0,
|
|
106
|
+
"pending_invoices_usd": 0.0, "pending_invoices": 0}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
async def build_ledger(user_id: str, *, days: int = 7, db=None) -> Dict[str, Any]:
|
|
110
|
+
"""The unified balance-sheet view for one tenant over a trailing window.
|
|
111
|
+
|
|
112
|
+
Tenant-scoped by contract: an empty user_id is refused (mirrors
|
|
113
|
+
MEMORY_REQUIRE_USER_ID) — anonymity must never widen into an all-tenants view.
|
|
114
|
+
"""
|
|
115
|
+
if not user_id:
|
|
116
|
+
raise ValueError("accounting requires an authenticated tenant (empty user_id refused)")
|
|
117
|
+
days = max(1, int(days))
|
|
118
|
+
database = await _resolve_db(db)
|
|
119
|
+
costs = (await _costs_leg(database, user_id, days)) if database is not None else {
|
|
120
|
+
"llm_api_cost_usd": 0.0, "credits_spent": 0.0, "llm_calls": 0}
|
|
121
|
+
inbound = (await _inbound_leg(database, user_id, days)) if database is not None else {
|
|
122
|
+
"earned_usd": 0.0, "settled_payments": 0,
|
|
123
|
+
"pending_invoices_usd": 0.0, "pending_invoices": 0}
|
|
124
|
+
wallet = _wallet_leg(user_id, days)
|
|
125
|
+
total_out = costs["llm_api_cost_usd"] + wallet["wallet_spend_usd"]
|
|
126
|
+
return {
|
|
127
|
+
"user_id": user_id,
|
|
128
|
+
"window_days": days,
|
|
129
|
+
**costs,
|
|
130
|
+
**wallet,
|
|
131
|
+
**inbound,
|
|
132
|
+
"total_spend_usd": round(total_out, 6),
|
|
133
|
+
"net_usd": round(inbound["earned_usd"] - total_out, 6),
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def format_ledger(ledger: Dict[str, Any]) -> str:
|
|
138
|
+
"""Agent/owner-readable rendering of :func:`build_ledger` (pure)."""
|
|
139
|
+
return (
|
|
140
|
+
f"Ledger — last {ledger['window_days']}d (tenant {ledger['user_id'] or '(all)'})\n"
|
|
141
|
+
f" earned: ${ledger['earned_usd']:.4f} across {ledger['settled_payments']} settled payment(s)\n"
|
|
142
|
+
f" pending: ${ledger['pending_invoices_usd']:.4f} across {ledger['pending_invoices']} open invoice(s)\n"
|
|
143
|
+
f" spent: ${ledger['total_spend_usd']:.4f} total — "
|
|
144
|
+
f"${ledger['llm_api_cost_usd']:.4f} LLM/API ({ledger['llm_calls']} calls), "
|
|
145
|
+
f"${ledger['wallet_spend_usd']:.4f} wallet ({ledger['wallet_payments']} payment(s))\n"
|
|
146
|
+
f" credits: {ledger['credits_spent']:.2f} platform credits consumed\n"
|
|
147
|
+
f" net: ${ledger['net_usd']:+.4f}"
|
|
148
|
+
)
|