apowerb 0.1.2__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.
- apowerb/__init__.py +18 -0
- apowerb/agent_store/__init__.py +0 -0
- apowerb/agent_store/agent_manager.py +159 -0
- apowerb/agent_store/api_key_store.py +112 -0
- apowerb/agent_store/hub_manager.py +121 -0
- apowerb/auth/__init__.py +0 -0
- apowerb/auth/dependencies.py +181 -0
- apowerb/auth/exceptions.py +7 -0
- apowerb/auth/middleware.py +110 -0
- apowerb/auth/router.py +142 -0
- apowerb/auth/schemas.py +66 -0
- apowerb/auth/service.py +283 -0
- apowerb/bi/__init__.py +1 -0
- apowerb/bi/chart_refresh_router.py +153 -0
- apowerb/bi/charts/__init__.py +1 -0
- apowerb/bi/charts/core.py +290 -0
- apowerb/bi/charts/router.py +279 -0
- apowerb/bi/charts/schemas.py +220 -0
- apowerb/bi/charts/service.py +301 -0
- apowerb/bi/dashboards/__init__.py +1 -0
- apowerb/bi/dashboards/core.py +460 -0
- apowerb/bi/dashboards/router.py +470 -0
- apowerb/bi/dashboards/schema.py +371 -0
- apowerb/bi/dashboards/service.py +304 -0
- apowerb/bi/data/__init__.py +1 -0
- apowerb/bi/data/_bi_storage.py +107 -0
- apowerb/bi/data/agent_executor.py +165 -0
- apowerb/bi/data/csv_executor.py +107 -0
- apowerb/bi/data/dataset_router.py +636 -0
- apowerb/bi/data/db_executor.py +477 -0
- apowerb/bi/data/google_drive_executor.py +163 -0
- apowerb/bi/data/onedrive_excel_executor.py +243 -0
- apowerb/bi/data/router.py +151 -0
- apowerb/bi/data/schema.py +117 -0
- apowerb/bi/data/service.py +376 -0
- apowerb/bi/data/upload_router.py +239 -0
- apowerb/bi/db_stores.py +437 -0
- apowerb/bi/dependencies.py +51 -0
- apowerb/bi/refresh_router.py +338 -0
- apowerb/bi/stats_router.py +40 -0
- apowerb/cli/agents.py +202 -0
- apowerb/cli/main.py +36 -0
- apowerb/cli/migrate_integrations.py +68 -0
- apowerb/cli/runs.py +83 -0
- apowerb/cli/tools.py +23 -0
- apowerb/configs/__init__.py +0 -0
- apowerb/configs/model_pricing.yaml +82 -0
- apowerb/configs/models.py +164 -0
- apowerb/configs/paths.py +99 -0
- apowerb/configs/settings.py +410 -0
- apowerb/configs/th2logger.py +194 -0
- apowerb/core/__init__.py +0 -0
- apowerb/core/adk_agent_builder.py +143 -0
- apowerb/core/adk_runner.py +989 -0
- apowerb/core/agent_flow.py +0 -0
- apowerb/core/agent_helpers/__init__.py +113 -0
- apowerb/core/agent_helpers/agent_utils.py +906 -0
- apowerb/core/agent_helpers/callback_chain.py +37 -0
- apowerb/core/agent_helpers/callbacks.py +805 -0
- apowerb/core/agent_helpers/chat_action_tools.py +433 -0
- apowerb/core/agent_helpers/default_llm.py +127 -0
- apowerb/core/agent_helpers/extras_loader.py +144 -0
- apowerb/core/agent_helpers/file_readers.py +230 -0
- apowerb/core/agent_helpers/ingest_file_tool.py +199 -0
- apowerb/core/agent_helpers/llm_model_builder.py +180 -0
- apowerb/core/agent_helpers/mcp_loader.py +349 -0
- apowerb/core/agent_helpers/pdf_to_images_tool.py +377 -0
- apowerb/core/agent_helpers/pdf_writer.py +67 -0
- apowerb/core/agent_helpers/read_file_tool.py +315 -0
- apowerb/core/agent_helpers/run_config_patch.py +132 -0
- apowerb/core/agent_helpers/text_utils.py +70 -0
- apowerb/core/agent_helpers/tool_factories.py +256 -0
- apowerb/core/agent_helpers/tools_binder.py +346 -0
- apowerb/core/agent_main.py +764 -0
- apowerb/core/agent_seeds.py +529 -0
- apowerb/core/api_key_main.py +115 -0
- apowerb/core/artifact_executor.py +140 -0
- apowerb/core/audio_streaming.py +636 -0
- apowerb/core/db/__init__.py +0 -0
- apowerb/core/db/write_config.py +73 -0
- apowerb/core/extensions/__init__.py +0 -0
- apowerb/core/extensions/loader.py +77 -0
- apowerb/core/extensions/registry.py +245 -0
- apowerb/core/flow_scheduler.py +0 -0
- apowerb/core/guardrails.py +279 -0
- apowerb/core/history_compaction.py +85 -0
- apowerb/core/hub_main.py +527 -0
- apowerb/core/invocation_context.py +65 -0
- apowerb/core/knowledge_map.py +148 -0
- apowerb/core/rag_streaming.py +167 -0
- apowerb/core/run_gate.py +80 -0
- apowerb/core/superagents/__init__.py +230 -0
- apowerb/core/superagents/schemas/__init__.py +0 -0
- apowerb/core/superagents/templates/__init__.py +75 -0
- apowerb/core/superagents/templates/audio.py +236 -0
- apowerb/core/superagents/templates/dashboard.py +189 -0
- apowerb/core/superagents/templates/data.py +407 -0
- apowerb/core/superagents/templates/image.py +217 -0
- apowerb/core/superagents/templates/marketing.py +97 -0
- apowerb/core/superagents/templates/rag.py +213 -0
- apowerb/core/validation/__init__.py +0 -0
- apowerb/core/validation/prompt_safety.py +207 -0
- apowerb/helpers/__init__.py +0 -0
- apowerb/helpers/audit_log.py +56 -0
- apowerb/helpers/business_intelligence_migration.py +76 -0
- apowerb/helpers/data_lake.py +101 -0
- apowerb/helpers/database.py +84 -0
- apowerb/helpers/database_connection.py +26 -0
- apowerb/helpers/email_sender.py +93 -0
- apowerb/helpers/emails.py +16 -0
- apowerb/helpers/encryptor.py +72 -0
- apowerb/helpers/env_scope.py +88 -0
- apowerb/helpers/google_oidc.py +106 -0
- apowerb/helpers/integrations_migration.py +63 -0
- apowerb/helpers/jsonify.py +81 -0
- apowerb/helpers/litellm_config.py +619 -0
- apowerb/helpers/metrics.py +223 -0
- apowerb/helpers/metrics_middleware.py +49 -0
- apowerb/helpers/model_pricing.py +102 -0
- apowerb/helpers/notification_bus.py +45 -0
- apowerb/helpers/notification_migration.py +65 -0
- apowerb/helpers/notifier_health.py +67 -0
- apowerb/helpers/notify_etl.py +97 -0
- apowerb/helpers/oauth_state_store.py +137 -0
- apowerb/helpers/oauth_states_migration.py +77 -0
- apowerb/helpers/ownership.py +72 -0
- apowerb/helpers/pagination.py +71 -0
- apowerb/helpers/request_id_middleware.py +84 -0
- apowerb/helpers/security.py +277 -0
- apowerb/helpers/security_headers.py +64 -0
- apowerb/helpers/share_migration.py +98 -0
- apowerb/helpers/store_migrations.py +38 -0
- apowerb/helpers/system_mailer.py +210 -0
- apowerb/helpers/title_generator.py +133 -0
- apowerb/helpers/user_migration.py +163 -0
- apowerb/helpers/webhook_log_migration.py +71 -0
- apowerb/helpers/webhook_migration.py +127 -0
- apowerb/integrations/__init__.py +0 -0
- apowerb/integrations/github.py +215 -0
- apowerb/integrations/gmail_webhook.py +400 -0
- apowerb/integrations/google.py +243 -0
- apowerb/integrations/helpers.py +516 -0
- apowerb/integrations/microsoft.py +440 -0
- apowerb/integrations/odoo.py +218 -0
- apowerb/integrations/outlook_webhook.py +525 -0
- apowerb/integrations/slack.py +0 -0
- apowerb/main.py +646 -0
- apowerb/middleware/__init__.py +1 -0
- apowerb/middleware/auth.py +31 -0
- apowerb/models.py +631 -0
- apowerb/routers/__init__.py +0 -0
- apowerb/routers/adk_runner.py +808 -0
- apowerb/routers/agent_reload.py +109 -0
- apowerb/routers/agents.py +150 -0
- apowerb/routers/api_keys.py +48 -0
- apowerb/routers/artifacts.py +186 -0
- apowerb/routers/audio_stream.py +569 -0
- apowerb/routers/config.py +25 -0
- apowerb/routers/data_lake.py +102 -0
- apowerb/routers/emailing.py +258 -0
- apowerb/routers/files.py +272 -0
- apowerb/routers/google_drive_browser.py +249 -0
- apowerb/routers/health.py +113 -0
- apowerb/routers/hub.py +63 -0
- apowerb/routers/integrations.py +894 -0
- apowerb/routers/models.py +59 -0
- apowerb/routers/notifications.py +221 -0
- apowerb/routers/onedrive_browser.py +397 -0
- apowerb/routers/rag/__init__.py +55 -0
- apowerb/routers/rag/index_db.py +271 -0
- apowerb/routers/rag/index_files.py +104 -0
- apowerb/routers/rag/index_s3.py +120 -0
- apowerb/routers/rag/index_url.py +98 -0
- apowerb/routers/rag/schemas.py +57 -0
- apowerb/routers/rag/status.py +97 -0
- apowerb/routers/rag/stream.py +187 -0
- apowerb/routers/rag/validators.py +92 -0
- apowerb/routers/rag/webhook.py +117 -0
- apowerb/routers/runs.py +62 -0
- apowerb/routers/scheduler.py +236 -0
- apowerb/routers/share.py +109 -0
- apowerb/routers/skills.py +472 -0
- apowerb/routers/superagents.py +38 -0
- apowerb/routers/tools.py +670 -0
- apowerb/routers/webhook_handlers/__init__.py +28 -0
- apowerb/routers/webhook_handlers/_common.py +311 -0
- apowerb/routers/webhook_handlers/gmail.py +501 -0
- apowerb/routers/webhook_handlers/outlook.py +891 -0
- apowerb/routers/webhooks.py +1042 -0
- apowerb/routers/workflows.py +215 -0
- apowerb/scheduler/backlog_migrations.py +142 -0
- apowerb/scheduler/backlog_status_tool.py +197 -0
- apowerb/scheduler/backlog_worker.py +386 -0
- apowerb/scheduler/events_retention.py +115 -0
- apowerb/scheduler/mage.py +939 -0
- apowerb/scheduler/notifier_watch.py +82 -0
- apowerb/scheduler/run_agent_background/__init__.py +73 -0
- apowerb/scheduler/run_agent_background/agent_runner.py +411 -0
- apowerb/scheduler/run_agent_background/schedule_helpers.py +322 -0
- apowerb/scheduler/run_agent_background/scheduler_api.py +469 -0
- apowerb/scheduler/run_agent_background/token_issuer.py +62 -0
- apowerb/scheduler/th2etl_client.py +331 -0
- apowerb/scheduler/webhook_renewal.py +259 -0
- apowerb/schema/__init__.py +0 -0
- apowerb/schema/adk_runner_schema.py +56 -0
- apowerb/schema/agent_schema.py +82 -0
- apowerb/schema/api_key_schema.py +33 -0
- apowerb/schema/data_lake_schema.py +71 -0
- apowerb/schema/hub_schema.py +18 -0
- apowerb/schema/notification_schema.py +35 -0
- apowerb/schema/share_schema.py +28 -0
- apowerb/schema/skill_schema.py +66 -0
- apowerb/schema/tool_config_schema.py +16 -0
- apowerb/schema/usage_schema.py +185 -0
- apowerb/schema/webhook_schema.py +87 -0
- apowerb/skills_store/__init__.py +0 -0
- apowerb/skills_store/portfolio/dashboard-builder/SKILL.md +77 -0
- apowerb/skills_store/portfolio/data-visualization/SKILL.md +93 -0
- apowerb/skills_store/portfolio/data-visualization/references/chart-guide.md +141 -0
- apowerb/skills_store/portfolio/email-campaign/SKILL.md +183 -0
- apowerb/skills_store/portfolio/email-campaign/references/templates_en.md +82 -0
- apowerb/skills_store/portfolio/email-campaign/references/templates_fr.md +82 -0
- apowerb/skills_store/portfolio/error-recovery/SKILL.md +104 -0
- apowerb/skills_store/portfolio/error-recovery/references/common-errors.md +70 -0
- apowerb/skills_store/portfolio/onedrive-navigation/SKILL.md +116 -0
- apowerb/skills_store/portfolio/onedrive-navigation/references/path-patterns.md +123 -0
- apowerb/skills_store/portfolio/rag-search/SKILL.md +81 -0
- apowerb/skills_store/portfolio/rag-search/references/rag-workflow.md +101 -0
- apowerb/skills_store/portfolio/report-generation/SKILL.md +112 -0
- apowerb/skills_store/portfolio/text-to-sql/SKILL.md +100 -0
- apowerb/skills_store/portfolio/text-to-sql/references/sql-patterns.md +202 -0
- apowerb/skills_store/skill_manager.py +105 -0
- apowerb/skills_store/skills_loader.py +170 -0
- apowerb/sqlgen/__init__.py +44 -0
- apowerb/sqlgen/analyst.py +164 -0
- apowerb/sqlgen/analyst_prompts.py +186 -0
- apowerb/sqlgen/generator.py +29 -0
- apowerb/sqlgen/safety.py +22 -0
- apowerb/sqlgen/schema_format.py +97 -0
- apowerb/sqlgen/table_selection.py +53 -0
- apowerb/storage/__init__.py +0 -0
- apowerb/storage/filename.py +53 -0
- apowerb/storage/s3.py +75 -0
- apowerb/storage/storage_service.py +59 -0
- apowerb/storage/test.py +16 -0
- apowerb/storage/test.txt +531 -0
- apowerb/storage/webhook_attachments.py +160 -0
- apowerb/tools_store/__init__.py +0 -0
- apowerb/tools_store/portfolio/api_call.py +59 -0
- apowerb/tools_store/portfolio/artifacts/__init__.py +0 -0
- apowerb/tools_store/portfolio/artifacts/save_code_artifact.py +39 -0
- apowerb/tools_store/portfolio/audio.py +732 -0
- apowerb/tools_store/portfolio/basic.py +486 -0
- apowerb/tools_store/portfolio/business_intelligence.py +1862 -0
- apowerb/tools_store/portfolio/data_handler.py +249 -0
- apowerb/tools_store/portfolio/database.py +570 -0
- apowerb/tools_store/portfolio/database_mcp.py +26 -0
- apowerb/tools_store/portfolio/db_to_rag.py +244 -0
- apowerb/tools_store/portfolio/google_auth.py +205 -0
- apowerb/tools_store/portfolio/google_calendar.py +187 -0
- apowerb/tools_store/portfolio/google_docs.py +179 -0
- apowerb/tools_store/portfolio/google_drive.py +568 -0
- apowerb/tools_store/portfolio/google_gmail.py +478 -0
- apowerb/tools_store/portfolio/google_sheets.py +156 -0
- apowerb/tools_store/portfolio/image_generation.py +305 -0
- apowerb/tools_store/portfolio/integration_status.py +95 -0
- apowerb/tools_store/portfolio/internal/data_handler.py +207 -0
- apowerb/tools_store/portfolio/marketing.py +99 -0
- apowerb/tools_store/portfolio/memory.py +280 -0
- apowerb/tools_store/portfolio/microsoft_auth.py +342 -0
- apowerb/tools_store/portfolio/odoo.py +235 -0
- apowerb/tools_store/portfolio/onedrive_core.py +674 -0
- apowerb/tools_store/portfolio/onedrive_read.py +669 -0
- apowerb/tools_store/portfolio/onedrive_write.py +565 -0
- apowerb/tools_store/portfolio/outlook_mail.py +731 -0
- apowerb/tools_store/portfolio/rag.py +407 -0
- apowerb/tools_store/portfolio/s3_tools.py +315 -0
- apowerb/tools_store/portfolio/teams.py +1140 -0
- apowerb/tools_store/portfolio/text_to_sql.py +1056 -0
- apowerb/tools_store/portfolio/thaink2.py +406 -0
- apowerb/tools_store/portfolio/visualization.py +292 -0
- apowerb/tools_store/portfolio/web_search_mcp.py +54 -0
- apowerb/tools_store/schema/__init__.py +0 -0
- apowerb/tools_store/schema/basic.py +13 -0
- apowerb/tools_store/schema/text_to_sql.py +22 -0
- apowerb/tools_store/tool_config.py +106 -0
- apowerb/tools_store/tool_manager.py +228 -0
- apowerb/tools_store/tools_helpers.py +497 -0
- apowerb/users/dependencies.py +45 -0
- apowerb/users/exceptions.py +6 -0
- apowerb/users/router.py +108 -0
- apowerb/users/schemas.py +77 -0
- apowerb/users/service.py +86 -0
- apowerb-0.1.2.dist-info/METADATA +1117 -0
- apowerb-0.1.2.dist-info/RECORD +298 -0
- apowerb-0.1.2.dist-info/WHEEL +4 -0
- apowerb-0.1.2.dist-info/entry_points.txt +3 -0
- apowerb-0.1.2.dist-info/licenses/LICENSE +21 -0
apowerb/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Docstring for th2agent
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Pydantic hack for Google ADK - must be applied before any google.adk imports
|
|
7
|
+
try:
|
|
8
|
+
import mcp.client.session
|
|
9
|
+
from pydantic_core import core_schema
|
|
10
|
+
|
|
11
|
+
def __get_pydantic_core_schema__(cls, source_type, handler):
|
|
12
|
+
return core_schema.is_instance_schema(cls)
|
|
13
|
+
|
|
14
|
+
mcp.client.session.ClientSession.__get_pydantic_core_schema__ = classmethod(
|
|
15
|
+
__get_pydantic_core_schema__
|
|
16
|
+
)
|
|
17
|
+
except ImportError:
|
|
18
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from apowerb.configs.th2logger import setup_logging
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
from apowerb.helpers.database_connection import DBConfig
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import (
|
|
8
|
+
create_engine,
|
|
9
|
+
Table,
|
|
10
|
+
Column,
|
|
11
|
+
Integer,
|
|
12
|
+
String,
|
|
13
|
+
MetaData,
|
|
14
|
+
inspect,
|
|
15
|
+
UniqueConstraint,
|
|
16
|
+
)
|
|
17
|
+
from apowerb.configs.settings import get_settings
|
|
18
|
+
|
|
19
|
+
logger = setup_logging(__name__)
|
|
20
|
+
|
|
21
|
+
settings = get_settings()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AgentStoreConfig(DBConfig):
|
|
25
|
+
"""Configuration for the agent store."""
|
|
26
|
+
|
|
27
|
+
table_name: str = settings.db_agent_store_table_name
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AgentStore(BaseModel):
|
|
31
|
+
"""Class to manage the agent store in a database.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
db_host: str - The database host.
|
|
35
|
+
db_name: str - The database name.
|
|
36
|
+
db_url: str - The database URL.
|
|
37
|
+
table_name: str - The name of the agent table.
|
|
38
|
+
db_user: str - The database user.
|
|
39
|
+
db_password: str - The database password.
|
|
40
|
+
db_type: str - The type of the database.
|
|
41
|
+
db_schema: str - The database schema.
|
|
42
|
+
engine: Any - The SQLAlchemy engine instance.
|
|
43
|
+
metadata: Any - The SQLAlchemy metadata instance.
|
|
44
|
+
agent_table: Any - The SQLAlchemy table instance for agents.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
agent_config: AgentStoreConfig = AgentStoreConfig()
|
|
48
|
+
db_host: str = agent_config.db_host
|
|
49
|
+
db_name: str = settings.db_name
|
|
50
|
+
db_url: str = f"{agent_config.db_host}:{agent_config.db_port}/{db_name}"
|
|
51
|
+
table_name: str = agent_config.table_name
|
|
52
|
+
db_user: str = agent_config.db_user
|
|
53
|
+
db_password: str = agent_config.db_password
|
|
54
|
+
db_type: str = agent_config.db_type
|
|
55
|
+
db_schema: str = agent_config.db_schema
|
|
56
|
+
engine: Any = None
|
|
57
|
+
metadata: Any = None
|
|
58
|
+
agent_table: Any = None
|
|
59
|
+
|
|
60
|
+
def __init__(self, **data: Any):
|
|
61
|
+
super().__init__(**data)
|
|
62
|
+
self.engine = create_engine(
|
|
63
|
+
f"{self.db_type}://{self.db_user}:{self.db_password}@{self.db_url}"
|
|
64
|
+
)
|
|
65
|
+
self.metadata = MetaData(schema=self.db_schema)
|
|
66
|
+
self.agent_table = Table(
|
|
67
|
+
self.table_name,
|
|
68
|
+
self.metadata,
|
|
69
|
+
Column("agent_id", Integer, primary_key=True, autoincrement=True),
|
|
70
|
+
Column("agent_name", String, nullable=False),
|
|
71
|
+
Column("agent_model", String, nullable=False),
|
|
72
|
+
Column("agent_model_params", String),
|
|
73
|
+
Column("agent_description", String),
|
|
74
|
+
Column("agent_instruction", String),
|
|
75
|
+
Column("agent_tools", String),
|
|
76
|
+
Column("agent_type", String),
|
|
77
|
+
Column("sub_agents", String),
|
|
78
|
+
Column("output_key", String),
|
|
79
|
+
Column("output_schema_name", String),
|
|
80
|
+
Column("skip_when_upstream", String),
|
|
81
|
+
Column("input_schema", String),
|
|
82
|
+
Column("output_schema", String),
|
|
83
|
+
Column("code_executor", String),
|
|
84
|
+
Column("created_at", String),
|
|
85
|
+
Column("updated_at", String),
|
|
86
|
+
Column("status", String),
|
|
87
|
+
Column("organization_id", String, nullable=False),
|
|
88
|
+
Column("project_id", String, nullable=False),
|
|
89
|
+
Column("owner_id", String),
|
|
90
|
+
Column("tags", String),
|
|
91
|
+
Column("guardrails_config", String),
|
|
92
|
+
Column("memory_enabled", String),
|
|
93
|
+
Column("artifacts_enabled", String),
|
|
94
|
+
Column("superagent_template_id", String),
|
|
95
|
+
Column("superagent_template_version_hash", String),
|
|
96
|
+
Column("loop_max_iterations", String),
|
|
97
|
+
Column("loop_exit_instruction", String),
|
|
98
|
+
Column("mcp_servers", String),
|
|
99
|
+
Column("agent_skills", String),
|
|
100
|
+
Column("hub_origin_id", String),
|
|
101
|
+
UniqueConstraint(
|
|
102
|
+
"agent_name",
|
|
103
|
+
"organization_id",
|
|
104
|
+
"project_id",
|
|
105
|
+
name="unique_agent_name_per_org_proj",
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def create_table(self):
|
|
110
|
+
"""Create the agent metadata table in the database."""
|
|
111
|
+
if not inspect(self.engine).has_table(self.table_name):
|
|
112
|
+
self.metadata.create_all(self.engine)
|
|
113
|
+
logger.info(f"Agent store table '{self.table_name}' created successfully.")
|
|
114
|
+
else:
|
|
115
|
+
logger.info(f"Agent store table '{self.table_name}' already exists.")
|
|
116
|
+
self.ensure_columns()
|
|
117
|
+
|
|
118
|
+
def ensure_columns(self):
|
|
119
|
+
"""Add new columns to existing tables if they don't exist."""
|
|
120
|
+
from sqlalchemy import text, inspect as sa_inspect
|
|
121
|
+
|
|
122
|
+
inspector = sa_inspect(self.engine)
|
|
123
|
+
existing = [
|
|
124
|
+
c["name"]
|
|
125
|
+
for c in inspector.get_columns(self.table_name, schema=self.db_schema)
|
|
126
|
+
]
|
|
127
|
+
new_cols = {
|
|
128
|
+
"output_key": "VARCHAR",
|
|
129
|
+
"output_schema_name": "VARCHAR",
|
|
130
|
+
"skip_when_upstream": "VARCHAR",
|
|
131
|
+
"guardrails_config": "VARCHAR",
|
|
132
|
+
"memory_enabled": "VARCHAR",
|
|
133
|
+
"artifacts_enabled": "VARCHAR",
|
|
134
|
+
"superagent_template_id": "VARCHAR",
|
|
135
|
+
"loop_max_iterations": "VARCHAR",
|
|
136
|
+
"loop_exit_instruction": "VARCHAR",
|
|
137
|
+
"mcp_servers": "VARCHAR",
|
|
138
|
+
"agent_skills": "VARCHAR",
|
|
139
|
+
"hub_origin_id": "VARCHAR",
|
|
140
|
+
# Snapshot of the source template's hash-relevant fields at
|
|
141
|
+
# creation time. The UI compares it to the live template hash
|
|
142
|
+
# to surface "template updated, click to sync" banners.
|
|
143
|
+
"superagent_template_version_hash": "VARCHAR",
|
|
144
|
+
}
|
|
145
|
+
with self.engine.begin() as conn:
|
|
146
|
+
for col, typ in new_cols.items():
|
|
147
|
+
if col not in existing:
|
|
148
|
+
schema_prefix = f'"{self.db_schema}".' if self.db_schema else ""
|
|
149
|
+
conn.execute(
|
|
150
|
+
text(
|
|
151
|
+
f'ALTER TABLE {schema_prefix}"{self.table_name}" ADD COLUMN "{col}" {typ}'
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def get_list_agents(self, agents_query: Any) -> Any:
|
|
156
|
+
"""Get a list of all agents from the database."""
|
|
157
|
+
with self.engine.connect() as conn:
|
|
158
|
+
result = conn.execute(agents_query)
|
|
159
|
+
return result.fetchall()
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from apowerb.configs.th2logger import setup_logging
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from apowerb.helpers.database_connection import DBConfig
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import (
|
|
7
|
+
create_engine,
|
|
8
|
+
Table,
|
|
9
|
+
Column,
|
|
10
|
+
Integer,
|
|
11
|
+
String,
|
|
12
|
+
MetaData,
|
|
13
|
+
inspect,
|
|
14
|
+
)
|
|
15
|
+
from apowerb.configs.settings import get_settings
|
|
16
|
+
|
|
17
|
+
logger = setup_logging(__name__)
|
|
18
|
+
|
|
19
|
+
settings = get_settings()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ApiKeyStoreConfig(DBConfig):
|
|
23
|
+
"""Configuration for the API key store."""
|
|
24
|
+
|
|
25
|
+
table_name: str = "saved_api_keys"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ApiKeyStore(BaseModel):
|
|
29
|
+
"""Class to manage saved API keys in a database."""
|
|
30
|
+
|
|
31
|
+
api_key_config: ApiKeyStoreConfig = ApiKeyStoreConfig()
|
|
32
|
+
db_host: str = api_key_config.db_host
|
|
33
|
+
db_name: str = settings.db_name
|
|
34
|
+
db_url: str = f"{api_key_config.db_host}:{api_key_config.db_port}/{db_name}"
|
|
35
|
+
table_name: str = api_key_config.table_name
|
|
36
|
+
db_user: str = api_key_config.db_user
|
|
37
|
+
db_password: str = api_key_config.db_password
|
|
38
|
+
db_type: str = api_key_config.db_type
|
|
39
|
+
db_schema: str = api_key_config.db_schema
|
|
40
|
+
engine: Any = None
|
|
41
|
+
metadata: Any = None
|
|
42
|
+
api_key_table: Any = None
|
|
43
|
+
|
|
44
|
+
def __init__(self, **data: Any):
|
|
45
|
+
super().__init__(**data)
|
|
46
|
+
self.engine = create_engine(
|
|
47
|
+
f"{self.db_type}://{self.db_user}:{self.db_password}@{self.db_url}"
|
|
48
|
+
)
|
|
49
|
+
self.metadata = MetaData(schema=self.db_schema)
|
|
50
|
+
self.api_key_table = Table(
|
|
51
|
+
self.table_name,
|
|
52
|
+
self.metadata,
|
|
53
|
+
Column("api_key_id", Integer, primary_key=True, autoincrement=True),
|
|
54
|
+
Column("key_name", String),
|
|
55
|
+
Column("provider", String),
|
|
56
|
+
Column("api_key_value", String),
|
|
57
|
+
Column("model", String, nullable=True),
|
|
58
|
+
Column("model_api_base", String, nullable=True),
|
|
59
|
+
Column("owner_id", String),
|
|
60
|
+
Column("organization_id", String),
|
|
61
|
+
Column("created_at", String),
|
|
62
|
+
Column("updated_at", String),
|
|
63
|
+
Column("status", String),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def create_table(self):
|
|
67
|
+
"""Create the saved_api_keys table in the database."""
|
|
68
|
+
if not inspect(self.engine).has_table(self.table_name):
|
|
69
|
+
self.metadata.create_all(self.engine)
|
|
70
|
+
logger.info(
|
|
71
|
+
f"API key store table '{self.table_name}' created successfully."
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
logger.info(f"API key store table '{self.table_name}' already exists.")
|
|
75
|
+
self.ensure_columns()
|
|
76
|
+
|
|
77
|
+
def ensure_columns(self):
|
|
78
|
+
"""Add new columns to existing tables if they don't exist."""
|
|
79
|
+
from sqlalchemy import text, inspect as sa_inspect
|
|
80
|
+
|
|
81
|
+
inspector = sa_inspect(self.engine)
|
|
82
|
+
existing = [
|
|
83
|
+
c["name"]
|
|
84
|
+
for c in inspector.get_columns(self.table_name, schema=self.db_schema)
|
|
85
|
+
]
|
|
86
|
+
new_cols: dict[str, str] = {
|
|
87
|
+
"model": "VARCHAR",
|
|
88
|
+
"model_api_base": "VARCHAR",
|
|
89
|
+
}
|
|
90
|
+
with self.engine.begin() as conn:
|
|
91
|
+
for col, typ in new_cols.items():
|
|
92
|
+
if col not in existing:
|
|
93
|
+
schema_prefix = f'"{self.db_schema}".' if self.db_schema else ""
|
|
94
|
+
conn.execute(
|
|
95
|
+
text(
|
|
96
|
+
f'ALTER TABLE {schema_prefix}"{self.table_name}" ADD COLUMN "{col}" {typ}'
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def get_list(self, query: Any) -> Any:
|
|
101
|
+
"""Execute a query and return all rows."""
|
|
102
|
+
with self.engine.connect() as conn:
|
|
103
|
+
result = conn.execute(query)
|
|
104
|
+
return result.fetchall()
|
|
105
|
+
|
|
106
|
+
def delete_api_key(self, api_key_id: int) -> None:
|
|
107
|
+
"""Delete an API key by its numeric ID."""
|
|
108
|
+
delete_q = self.api_key_table.delete().where(
|
|
109
|
+
self.api_key_table.c.api_key_id == api_key_id
|
|
110
|
+
)
|
|
111
|
+
with self.engine.begin() as conn:
|
|
112
|
+
conn.execute(delete_q)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from apowerb.configs.th2logger import setup_logging
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from apowerb.helpers.database_connection import DBConfig
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import (
|
|
7
|
+
create_engine,
|
|
8
|
+
Table,
|
|
9
|
+
Column,
|
|
10
|
+
Integer,
|
|
11
|
+
String,
|
|
12
|
+
MetaData,
|
|
13
|
+
inspect,
|
|
14
|
+
)
|
|
15
|
+
from apowerb.configs.settings import get_settings
|
|
16
|
+
|
|
17
|
+
logger = setup_logging(__name__)
|
|
18
|
+
|
|
19
|
+
settings = get_settings()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class HubStoreConfig(DBConfig):
|
|
23
|
+
"""Configuration for the hub store."""
|
|
24
|
+
|
|
25
|
+
table_name: str = "hub_agents"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class HubStore(BaseModel):
|
|
29
|
+
"""Class to manage the hub agent store in a database."""
|
|
30
|
+
|
|
31
|
+
hub_config: HubStoreConfig = HubStoreConfig()
|
|
32
|
+
db_host: str = hub_config.db_host
|
|
33
|
+
db_name: str = settings.db_name
|
|
34
|
+
db_url: str = f"{hub_config.db_host}:{hub_config.db_port}/{db_name}"
|
|
35
|
+
table_name: str = hub_config.table_name
|
|
36
|
+
db_user: str = hub_config.db_user
|
|
37
|
+
db_password: str = hub_config.db_password
|
|
38
|
+
db_type: str = hub_config.db_type
|
|
39
|
+
db_schema: str = hub_config.db_schema
|
|
40
|
+
engine: Any = None
|
|
41
|
+
metadata: Any = None
|
|
42
|
+
hub_table: Any = None
|
|
43
|
+
|
|
44
|
+
def __init__(self, **data: Any):
|
|
45
|
+
super().__init__(**data)
|
|
46
|
+
self.engine = create_engine(
|
|
47
|
+
f"{self.db_type}://{self.db_user}:{self.db_password}@{self.db_url}"
|
|
48
|
+
)
|
|
49
|
+
self.metadata = MetaData(schema=self.db_schema)
|
|
50
|
+
self.hub_table = Table(
|
|
51
|
+
self.table_name,
|
|
52
|
+
self.metadata,
|
|
53
|
+
Column("hub_id", Integer, primary_key=True, autoincrement=True),
|
|
54
|
+
Column("hub_name", String, nullable=False),
|
|
55
|
+
Column("hub_description", String),
|
|
56
|
+
Column("hub_category", String),
|
|
57
|
+
Column("hub_tags", String),
|
|
58
|
+
# Snapshot of the original agent config (no API keys)
|
|
59
|
+
Column("agent_name", String),
|
|
60
|
+
Column("agent_model", String),
|
|
61
|
+
Column("agent_description", String),
|
|
62
|
+
Column("agent_instruction", String),
|
|
63
|
+
Column("agent_tools", String),
|
|
64
|
+
Column("agent_type", String),
|
|
65
|
+
Column("sub_agents", String),
|
|
66
|
+
Column("sub_agents_snapshot", String),
|
|
67
|
+
Column("loop_max_iterations", String),
|
|
68
|
+
Column("loop_exit_instruction", String),
|
|
69
|
+
Column("agent_skills", String),
|
|
70
|
+
Column("memory_enabled", String),
|
|
71
|
+
Column("artifacts_enabled", String),
|
|
72
|
+
Column("guardrails_config", String),
|
|
73
|
+
# Metadata
|
|
74
|
+
Column("publisher_id", String),
|
|
75
|
+
Column("publisher_org", String),
|
|
76
|
+
Column("source_agent_id", String),
|
|
77
|
+
Column("clone_count", Integer, default=0),
|
|
78
|
+
Column("published_at", String),
|
|
79
|
+
Column("updated_at", String),
|
|
80
|
+
Column("status", String),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def create_table(self):
|
|
84
|
+
"""Create the hub_agents table in the database."""
|
|
85
|
+
if not inspect(self.engine).has_table(self.table_name):
|
|
86
|
+
self.metadata.create_all(self.engine)
|
|
87
|
+
logger.info(f"Hub store table '{self.table_name}' created successfully.")
|
|
88
|
+
else:
|
|
89
|
+
logger.info(f"Hub store table '{self.table_name}' already exists.")
|
|
90
|
+
self.ensure_columns()
|
|
91
|
+
|
|
92
|
+
def ensure_columns(self):
|
|
93
|
+
"""Add new columns to existing tables if they don't exist."""
|
|
94
|
+
from sqlalchemy import text, inspect as sa_inspect
|
|
95
|
+
|
|
96
|
+
inspector = sa_inspect(self.engine)
|
|
97
|
+
existing = [
|
|
98
|
+
c["name"]
|
|
99
|
+
for c in inspector.get_columns(self.table_name, schema=self.db_schema)
|
|
100
|
+
]
|
|
101
|
+
new_cols: dict[str, str] = {
|
|
102
|
+
"sub_agents_snapshot": "VARCHAR",
|
|
103
|
+
"loop_max_iterations": "VARCHAR",
|
|
104
|
+
"loop_exit_instruction": "VARCHAR",
|
|
105
|
+
"agent_skills": "VARCHAR",
|
|
106
|
+
}
|
|
107
|
+
with self.engine.begin() as conn:
|
|
108
|
+
for col, typ in new_cols.items():
|
|
109
|
+
if col not in existing:
|
|
110
|
+
schema_prefix = f'"{self.db_schema}".' if self.db_schema else ""
|
|
111
|
+
conn.execute(
|
|
112
|
+
text(
|
|
113
|
+
f'ALTER TABLE {schema_prefix}"{self.table_name}" ADD COLUMN "{col}" {typ}'
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def get_list(self, query: Any) -> Any:
|
|
118
|
+
"""Execute a query and return all rows."""
|
|
119
|
+
with self.engine.connect() as conn:
|
|
120
|
+
result = conn.execute(query)
|
|
121
|
+
return result.fetchall()
|
apowerb/auth/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
from apowerb.configs.th2logger import setup_logging
|
|
2
|
+
import os
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
from fastapi import Depends, HTTPException, status
|
|
7
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
8
|
+
|
|
9
|
+
from jose import JWTError, jwt
|
|
10
|
+
from sqlalchemy import select
|
|
11
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
12
|
+
|
|
13
|
+
from apowerb.configs.settings import get_settings
|
|
14
|
+
from apowerb.helpers.database import get_db
|
|
15
|
+
from apowerb.helpers.security import get_algorithm, get_secret_key
|
|
16
|
+
from apowerb.models import User as UserModel
|
|
17
|
+
from apowerb.users import schemas as user_schemas
|
|
18
|
+
|
|
19
|
+
logger = setup_logging(__name__)
|
|
20
|
+
|
|
21
|
+
BYPASS_AUTH = os.environ.get("BYPASS_AUTH", "").lower() == "true"
|
|
22
|
+
|
|
23
|
+
settings = get_settings()
|
|
24
|
+
# Make authentication optional
|
|
25
|
+
security = HTTPBearer(auto_error=False)
|
|
26
|
+
DBSessionDep = Annotated[AsyncSession, Depends(get_db)]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def get_current_user(
|
|
30
|
+
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
|
31
|
+
db: AsyncSession = Depends(get_db),
|
|
32
|
+
) -> user_schemas.User:
|
|
33
|
+
if BYPASS_AUTH:
|
|
34
|
+
logger.warning("AUTH BYPASS ACTIVE - Using fake user")
|
|
35
|
+
|
|
36
|
+
# Return fake user with ALL required fields
|
|
37
|
+
return user_schemas.User(
|
|
38
|
+
user_id=1,
|
|
39
|
+
email="test@example.com",
|
|
40
|
+
role="USER",
|
|
41
|
+
first_name="Test",
|
|
42
|
+
last_name="User",
|
|
43
|
+
onboarding_completed=True,
|
|
44
|
+
created_at=datetime.now(),
|
|
45
|
+
updated_at=datetime.now(),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if credentials is None:
|
|
49
|
+
logger.debug("No credentials provided for request")
|
|
50
|
+
raise HTTPException(
|
|
51
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
52
|
+
detail="Not authenticated",
|
|
53
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
token = credentials.credentials
|
|
57
|
+
# La clé est résolue AVANT le try : une configuration manquante doit
|
|
58
|
+
# remonter telle quelle, pas se faire convertir en « identifiants
|
|
59
|
+
# invalides » par le except ci-dessous.
|
|
60
|
+
secret = get_secret_key()
|
|
61
|
+
try:
|
|
62
|
+
payload = jwt.decode(
|
|
63
|
+
token,
|
|
64
|
+
secret,
|
|
65
|
+
algorithms=[get_algorithm()],
|
|
66
|
+
)
|
|
67
|
+
# H1 — Reject any token that isn't explicitly an access token.
|
|
68
|
+
# Refresh tokens (30-day cookie) and long-lived agent_refresh tokens
|
|
69
|
+
# (90-day Mage schedule tokens) MUST NOT unlock user-scope endpoints.
|
|
70
|
+
token_type = payload.get("type")
|
|
71
|
+
if token_type != "access":
|
|
72
|
+
logger.warning(
|
|
73
|
+
"Rejected non-access token on protected endpoint (type=%s)",
|
|
74
|
+
token_type,
|
|
75
|
+
)
|
|
76
|
+
raise HTTPException(
|
|
77
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
78
|
+
detail="Invalid token type. Access token required.",
|
|
79
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
80
|
+
)
|
|
81
|
+
email: str = payload.get("sub")
|
|
82
|
+
if email is None:
|
|
83
|
+
raise HTTPException(
|
|
84
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
85
|
+
detail="Could not validate credentials",
|
|
86
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
87
|
+
)
|
|
88
|
+
except JWTError as e:
|
|
89
|
+
logger.warning("JWT decode failed: %s", type(e).__name__)
|
|
90
|
+
raise HTTPException(
|
|
91
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
92
|
+
detail="Could not validate credentials",
|
|
93
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
result = await db.execute(select(UserModel).where(UserModel.email == email))
|
|
97
|
+
user = result.scalar_one_or_none()
|
|
98
|
+
|
|
99
|
+
if user is None:
|
|
100
|
+
raise HTTPException(
|
|
101
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
102
|
+
detail="User not found",
|
|
103
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return user_schemas.User(
|
|
107
|
+
user_id=user.user_id,
|
|
108
|
+
email=user.email,
|
|
109
|
+
role=user.role.value if hasattr(user.role, "value") else str(user.role),
|
|
110
|
+
first_name=user.first_name,
|
|
111
|
+
last_name=user.last_name,
|
|
112
|
+
username=user.username,
|
|
113
|
+
full_name=user.full_name,
|
|
114
|
+
avatar_url=user.avatar_url,
|
|
115
|
+
plan=user.plan,
|
|
116
|
+
stripe_customer_id=user.stripe_customer_id,
|
|
117
|
+
mfa_enabled=getattr(user, "mfa_enabled", False) or False,
|
|
118
|
+
onboarding_completed=getattr(user, "onboarding_completed", False) or False,
|
|
119
|
+
created_at=user.created_at,
|
|
120
|
+
updated_at=user.updated_at,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
async def get_optional_user(
|
|
125
|
+
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
|
126
|
+
db: AsyncSession = Depends(get_db),
|
|
127
|
+
) -> user_schemas.User | None:
|
|
128
|
+
"""Like get_current_user but returns None instead of 401 for unauthenticated requests."""
|
|
129
|
+
if credentials is None:
|
|
130
|
+
return None
|
|
131
|
+
token = credentials.credentials
|
|
132
|
+
secret = get_secret_key()
|
|
133
|
+
try:
|
|
134
|
+
payload = jwt.decode(
|
|
135
|
+
token,
|
|
136
|
+
secret,
|
|
137
|
+
algorithms=[get_algorithm()],
|
|
138
|
+
)
|
|
139
|
+
# H1 — Only accept explicit access tokens in optional-auth paths too.
|
|
140
|
+
if payload.get("type") != "access":
|
|
141
|
+
return None
|
|
142
|
+
email: str | None = payload.get("sub")
|
|
143
|
+
if email is None:
|
|
144
|
+
return None
|
|
145
|
+
except JWTError:
|
|
146
|
+
return None
|
|
147
|
+
result = await db.execute(select(UserModel).where(UserModel.email == email))
|
|
148
|
+
user = result.scalar_one_or_none()
|
|
149
|
+
if user is None:
|
|
150
|
+
return None
|
|
151
|
+
return user_schemas.User(
|
|
152
|
+
user_id=user.user_id,
|
|
153
|
+
email=user.email,
|
|
154
|
+
role=user.role.value if hasattr(user.role, "value") else str(user.role),
|
|
155
|
+
first_name=user.first_name,
|
|
156
|
+
last_name=user.last_name,
|
|
157
|
+
username=user.username,
|
|
158
|
+
full_name=user.full_name,
|
|
159
|
+
avatar_url=user.avatar_url,
|
|
160
|
+
plan=user.plan,
|
|
161
|
+
stripe_customer_id=user.stripe_customer_id,
|
|
162
|
+
mfa_enabled=getattr(user, "mfa_enabled", False) or False,
|
|
163
|
+
onboarding_completed=getattr(user, "onboarding_completed", False) or False,
|
|
164
|
+
created_at=user.created_at,
|
|
165
|
+
updated_at=user.updated_at,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
async def get_admin_user(
|
|
170
|
+
current_user: user_schemas.User = Depends(get_current_user),
|
|
171
|
+
) -> user_schemas.User:
|
|
172
|
+
if BYPASS_AUTH:
|
|
173
|
+
logger.warning("ADMIN BYPASS ACTIVE")
|
|
174
|
+
current_user.role = "ADMIN"
|
|
175
|
+
return current_user
|
|
176
|
+
|
|
177
|
+
if getattr(current_user, "role", None) != "ADMIN":
|
|
178
|
+
raise HTTPException(
|
|
179
|
+
status_code=status.HTTP_403_FORBIDDEN, detail="Not enough permissions"
|
|
180
|
+
)
|
|
181
|
+
return current_user
|