solace-agent-mesh 1.7.1__py3-none-any.whl → 1.13.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.
Potentially problematic release.
This version of solace-agent-mesh might be problematic. Click here for more details.
- solace_agent_mesh/agent/adk/alembic/README +74 -0
- solace_agent_mesh/agent/adk/alembic/env.py +77 -0
- solace_agent_mesh/agent/adk/alembic/script.py.mako +28 -0
- solace_agent_mesh/agent/adk/alembic/versions/e2902798564d_adk_session_db_upgrade.py +52 -0
- solace_agent_mesh/agent/adk/alembic.ini +112 -0
- solace_agent_mesh/agent/adk/artifacts/filesystem_artifact_service.py +164 -0
- solace_agent_mesh/agent/adk/artifacts/s3_artifact_service.py +163 -0
- solace_agent_mesh/agent/adk/callbacks.py +752 -127
- solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +99 -7
- solace_agent_mesh/agent/adk/intelligent_mcp_callbacks.py +52 -5
- solace_agent_mesh/agent/adk/mcp_content_processor.py +1 -1
- solace_agent_mesh/agent/adk/models/lite_llm.py +34 -16
- solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +24 -137
- solace_agent_mesh/agent/adk/runner.py +66 -8
- solace_agent_mesh/agent/adk/schema_migration.py +88 -0
- solace_agent_mesh/agent/adk/services.py +41 -1
- solace_agent_mesh/agent/adk/setup.py +220 -32
- solace_agent_mesh/agent/adk/stream_parser.py +229 -40
- solace_agent_mesh/agent/protocol/event_handlers.py +219 -33
- solace_agent_mesh/agent/proxies/a2a/component.py +572 -75
- solace_agent_mesh/agent/proxies/a2a/config.py +80 -4
- solace_agent_mesh/agent/proxies/base/component.py +188 -22
- solace_agent_mesh/agent/proxies/base/proxy_task_context.py +3 -1
- solace_agent_mesh/agent/sac/app.py +37 -12
- solace_agent_mesh/agent/sac/component.py +322 -52
- solace_agent_mesh/agent/sac/patch_adk.py +8 -16
- solace_agent_mesh/agent/sac/task_execution_context.py +90 -0
- solace_agent_mesh/agent/tools/__init__.py +3 -0
- solace_agent_mesh/agent/tools/audio_tools.py +3 -3
- solace_agent_mesh/agent/tools/builtin_artifact_tools.py +698 -24
- solace_agent_mesh/agent/tools/deep_research_tools.py +2161 -0
- solace_agent_mesh/agent/tools/peer_agent_tool.py +82 -15
- solace_agent_mesh/agent/tools/time_tools.py +126 -0
- solace_agent_mesh/agent/tools/tool_config_types.py +54 -2
- solace_agent_mesh/agent/tools/web_search_tools.py +279 -0
- solace_agent_mesh/agent/tools/web_tools.py +125 -17
- solace_agent_mesh/agent/utils/artifact_helpers.py +243 -5
- solace_agent_mesh/agent/utils/context_helpers.py +17 -0
- solace_agent_mesh/assets/docs/404.html +6 -6
- solace_agent_mesh/assets/docs/assets/css/{styles.906a1503.css → styles.8162edfb.css} +1 -1
- solace_agent_mesh/assets/docs/assets/js/05749d90.19ac4f35.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/15ba94aa.e186750d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/15e40e79.434bb30f.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/17896441.e612dfb4.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2279.550aa580.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/{17896441.a5e82f9b.js.LICENSE.txt → 2279.550aa580.js.LICENSE.txt} +6 -0
- solace_agent_mesh/assets/docs/assets/js/240a0364.83e37aa8.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2e32b5e0.2f0db237.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3a6c6137.7e61915d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3ac1795d.7f7ab1c1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3ff0015d.e53c9b78.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/41adc471.0e95b87c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4667dc50.bf2ad456.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/49eed117.493d6f99.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{509e993c.4c7a1a6d.js → 509e993c.a1fbf45a.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/547e15cc.8e6da617.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/55b7b518.29d6e75d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5b8d9c11.d4eb37b8.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.1ee87753.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/60702c0e.a8bdd79b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/64195356.09dbd087.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/66d4869e.30340bd3.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6aaedf65.7253541d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6d84eae0.fd23ba4a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/729898df.7249e9fd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7e294c01.7c5f6906.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8024126c.e3467286.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/81a99df0.7ed65d45.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/82fbfb93.161823a5.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/924ffdeb.975e428a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/94e8668d.16083b3f.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9bb13469.4523ae20.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a7d42657.a956689d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a94703ab.3e5fbcb3.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ab9708a8.3e563275.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/c93cbaa0.0e0d8baf.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cab03b5b.6a073091.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.07e170dd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e04b235d.06d23db6.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e1b6eeb4.deb2b62e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e3d9abda.1476f570.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e6f9706b.acc800d3.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e92d0134.c147a429.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ee0c2fe7.94d0a351.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.cc97854c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/main.d634009f.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.27bb82a7.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +68 -68
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +50 -50
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +42 -42
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +55 -55
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +75 -75
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/image-tools/index.html +81 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +67 -50
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/research-tools/index.html +136 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +178 -144
- solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +43 -42
- solace_agent_mesh/assets/docs/docs/documentation/components/index.html +20 -18
- solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +23 -23
- solace_agent_mesh/assets/docs/docs/documentation/components/platform-service/index.html +33 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +45 -45
- solace_agent_mesh/assets/docs/docs/documentation/components/projects/index.html +98 -112
- solace_agent_mesh/assets/docs/docs/documentation/components/prompts/index.html +147 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +208 -125
- solace_agent_mesh/assets/docs/docs/documentation/components/speech/index.html +52 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +28 -28
- solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +29 -29
- solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +14 -14
- solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes/index.html +47 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes/kubernetes-deployment-guide/index.html +197 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/logging/index.html +67 -53
- solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +17 -17
- solace_agent_mesh/assets/docs/docs/documentation/deploying/proxy_configuration/index.html +49 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +38 -38
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +87 -87
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +67 -49
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +17 -17
- solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +51 -51
- solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +22 -22
- solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +27 -27
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +135 -135
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +66 -66
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +51 -51
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +50 -38
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +86 -86
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +51 -51
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +24 -24
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +30 -30
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +44 -44
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/teams-integration/index.html +115 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/agent-builder/index.html +50 -23
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/connectors/index.html +29 -24
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +21 -21
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +40 -37
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/openapi-tools/index.html +324 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +96 -66
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/secure-user-delegated-access/index.html +181 -181
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +75 -75
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/wheel-installation/index.html +27 -27
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +44 -44
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +39 -38
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +30 -30
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +18 -18
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/vibe_coding/index.html +62 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/artifact-storage/index.html +135 -114
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +37 -37
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +14 -14
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +27 -25
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +69 -69
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +72 -72
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/session-storage/index.html +112 -112
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/user-feedback/index.html +28 -28
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +42 -42
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +20 -20
- solace_agent_mesh/assets/docs/docs/documentation/migrations/platform-service-split/index.html +85 -0
- solace_agent_mesh/assets/docs/lunr-index-1768329217460.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1768329217460.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/assets/docs/sitemap.xml +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/cli/commands/add_cmd/__init__.py +3 -1
- solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +6 -1
- solace_agent_mesh/cli/commands/add_cmd/proxy_cmd.py +100 -0
- solace_agent_mesh/cli/commands/eval_cmd.py +1 -1
- solace_agent_mesh/cli/commands/init_cmd/__init__.py +15 -0
- solace_agent_mesh/cli/commands/init_cmd/directory_step.py +1 -1
- solace_agent_mesh/cli/commands/init_cmd/env_step.py +30 -3
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +3 -4
- solace_agent_mesh/cli/commands/init_cmd/platform_service_step.py +85 -0
- solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +16 -3
- solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +2 -1
- solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +1 -0
- solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +3 -3
- solace_agent_mesh/cli/commands/run_cmd.py +64 -49
- solace_agent_mesh/cli/commands/tools_cmd.py +315 -0
- solace_agent_mesh/cli/main.py +15 -0
- solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-tcIFZLis.js → authCallback-KnKMP_vb.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/client-DpBL2stg.js +25 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-Cd498TV2.js +435 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-rSf8Vu29.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/{vendor-CINwxvwV.js → vendor-CGk8Suyh.js} +189 -94
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
- solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
- solace_agent_mesh/client/webui/frontend/static/mockServiceWorker.js +336 -0
- solace_agent_mesh/client/webui/frontend/static/ui-version.json +6 -0
- solace_agent_mesh/common/a2a/types.py +1 -1
- solace_agent_mesh/common/agent_registry.py +38 -11
- solace_agent_mesh/common/data_parts.py +124 -0
- solace_agent_mesh/common/error_handlers.py +83 -0
- solace_agent_mesh/common/exceptions.py +24 -0
- solace_agent_mesh/common/oauth/__init__.py +17 -0
- solace_agent_mesh/common/oauth/oauth_client.py +408 -0
- solace_agent_mesh/common/oauth/utils.py +50 -0
- solace_agent_mesh/common/rag_dto.py +156 -0
- solace_agent_mesh/common/sac/sam_component_base.py +73 -1
- solace_agent_mesh/common/sam_events/event_service.py +2 -2
- solace_agent_mesh/common/utils/embeds/converter.py +1 -8
- solace_agent_mesh/common/utils/embeds/modifiers.py +2 -27
- solace_agent_mesh/common/utils/embeds/resolver.py +94 -25
- solace_agent_mesh/common/utils/embeds/types.py +1 -0
- solace_agent_mesh/common/utils/log_formatters.py +20 -0
- solace_agent_mesh/common/utils/mime_helpers.py +12 -5
- solace_agent_mesh/common/utils/rbac_utils.py +69 -0
- solace_agent_mesh/common/utils/templates/__init__.py +8 -0
- solace_agent_mesh/common/utils/templates/liquid_renderer.py +210 -0
- solace_agent_mesh/common/utils/templates/template_resolver.py +161 -0
- solace_agent_mesh/config_portal/backend/common.py +12 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-CljP4_mv.js +103 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{components-Rk0n-9cK.js → components-CaC6hG8d.js} +22 -22
- solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-mvZjNKiz.js → entry.client-H_TM0YBt.js} +3 -3
- solace_agent_mesh/config_portal/frontend/static/client/assets/{index-DzNKzXrc.js → index-CnFykb2v.js} +16 -16
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-f8439d40.js +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-BIMqslJB.css +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-mJmTIdIk.js +10 -0
- solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
- solace_agent_mesh/core_a2a/service.py +3 -2
- solace_agent_mesh/gateway/adapter/base.py +28 -1
- solace_agent_mesh/gateway/adapter/types.py +9 -0
- solace_agent_mesh/gateway/base/app.py +10 -0
- solace_agent_mesh/gateway/base/auth_interface.py +103 -0
- solace_agent_mesh/gateway/base/component.py +451 -10
- solace_agent_mesh/gateway/generic/component.py +274 -30
- solace_agent_mesh/gateway/http_sse/alembic/env.py +0 -7
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_soft_delete_and_search.py +2 -43
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_default_agent_to_projects.py +2 -2
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251108_create_prompt_tables_with_sharing.py +154 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251115_add_parent_task_id.py +32 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251126_add_background_task_fields.py +47 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251202_add_versioned_fields_to_prompts.py +52 -0
- solace_agent_mesh/gateway/http_sse/alembic.ini +0 -36
- solace_agent_mesh/gateway/http_sse/app.py +23 -6
- solace_agent_mesh/gateway/http_sse/component.py +158 -73
- solace_agent_mesh/gateway/http_sse/dependencies.py +50 -57
- solace_agent_mesh/gateway/http_sse/main.py +58 -482
- solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +2 -2
- solace_agent_mesh/gateway/http_sse/repository/entities/project.py +1 -1
- solace_agent_mesh/gateway/http_sse/repository/entities/project_user.py +1 -1
- solace_agent_mesh/gateway/http_sse/repository/entities/session.py +3 -2
- solace_agent_mesh/gateway/http_sse/repository/entities/task.py +7 -0
- solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +2 -2
- solace_agent_mesh/gateway/http_sse/repository/interfaces.py +2 -2
- solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +5 -0
- solace_agent_mesh/gateway/http_sse/repository/models/prompt_model.py +159 -0
- solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +1 -1
- solace_agent_mesh/gateway/http_sse/repository/models/task_model.py +8 -1
- solace_agent_mesh/gateway/http_sse/repository/project_repository.py +1 -1
- solace_agent_mesh/gateway/http_sse/repository/project_user_repository.py +1 -1
- solace_agent_mesh/gateway/http_sse/repository/session_repository.py +12 -107
- solace_agent_mesh/gateway/http_sse/repository/task_repository.py +86 -2
- solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +38 -7
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +113 -7
- solace_agent_mesh/gateway/http_sse/routers/auth.py +69 -132
- solace_agent_mesh/gateway/http_sse/routers/config.py +235 -10
- solace_agent_mesh/gateway/http_sse/routers/dto/project_dto.py +69 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/prompt_dto.py +255 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +1 -1
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/base_responses.py +1 -1
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/project_responses.py +1 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +3 -2
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/version_responses.py +31 -0
- solace_agent_mesh/gateway/http_sse/routers/feedback.py +2 -2
- solace_agent_mesh/gateway/http_sse/routers/people.py +2 -2
- solace_agent_mesh/gateway/http_sse/routers/projects.py +250 -24
- solace_agent_mesh/gateway/http_sse/routers/prompts.py +1416 -0
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +14 -5
- solace_agent_mesh/gateway/http_sse/routers/speech.py +355 -0
- solace_agent_mesh/gateway/http_sse/routers/sse.py +117 -4
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +509 -149
- solace_agent_mesh/gateway/http_sse/routers/users.py +1 -1
- solace_agent_mesh/gateway/http_sse/routers/version.py +343 -0
- solace_agent_mesh/gateway/http_sse/routers/visualization.py +2 -1
- solace_agent_mesh/gateway/http_sse/services/audio_service.py +1227 -0
- solace_agent_mesh/gateway/http_sse/services/background_task_monitor.py +186 -0
- solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +1 -1
- solace_agent_mesh/gateway/http_sse/services/feedback_service.py +1 -1
- solace_agent_mesh/gateway/http_sse/services/project_service.py +539 -12
- solace_agent_mesh/gateway/http_sse/services/prompt_builder_assistant.py +303 -0
- solace_agent_mesh/gateway/http_sse/services/session_service.py +198 -21
- solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +354 -4
- solace_agent_mesh/gateway/http_sse/sse_manager.py +280 -169
- solace_agent_mesh/gateway/http_sse/utils/artifact_copy_utils.py +370 -0
- solace_agent_mesh/gateway/http_sse/utils/stim_utils.py +41 -1
- solace_agent_mesh/services/__init__.py +0 -0
- solace_agent_mesh/services/platform/__init__.py +29 -0
- solace_agent_mesh/services/platform/alembic/env.py +85 -0
- solace_agent_mesh/services/platform/alembic/script.py.mako +28 -0
- solace_agent_mesh/services/platform/alembic.ini +109 -0
- solace_agent_mesh/services/platform/api/__init__.py +3 -0
- solace_agent_mesh/services/platform/api/dependencies.py +154 -0
- solace_agent_mesh/services/platform/api/main.py +314 -0
- solace_agent_mesh/services/platform/api/middleware.py +51 -0
- solace_agent_mesh/services/platform/api/routers/__init__.py +33 -0
- solace_agent_mesh/services/platform/api/routers/health_router.py +31 -0
- solace_agent_mesh/services/platform/app.py +215 -0
- solace_agent_mesh/services/platform/component.py +777 -0
- solace_agent_mesh/shared/__init__.py +14 -0
- solace_agent_mesh/shared/api/__init__.py +42 -0
- solace_agent_mesh/shared/auth/__init__.py +26 -0
- solace_agent_mesh/shared/auth/dependencies.py +204 -0
- solace_agent_mesh/shared/auth/middleware.py +347 -0
- solace_agent_mesh/shared/database/__init__.py +20 -0
- solace_agent_mesh/{gateway/http_sse/shared → shared/database}/base_repository.py +1 -1
- solace_agent_mesh/{gateway/http_sse/shared → shared/database}/database_exceptions.py +1 -1
- solace_agent_mesh/{gateway/http_sse/shared → shared/database}/database_helpers.py +1 -1
- solace_agent_mesh/shared/exceptions/__init__.py +36 -0
- solace_agent_mesh/{gateway/http_sse/shared → shared/exceptions}/exception_handlers.py +1 -1
- solace_agent_mesh/shared/utils/__init__.py +21 -0
- solace_agent_mesh/templates/logging_config_template.yaml +48 -0
- solace_agent_mesh/templates/main_orchestrator.yaml +12 -1
- solace_agent_mesh/templates/platform.yaml +49 -0
- solace_agent_mesh/templates/plugin_readme_template.md +3 -25
- solace_agent_mesh/templates/plugin_tool_config_template.yaml +109 -0
- solace_agent_mesh/templates/proxy_template.yaml +62 -0
- solace_agent_mesh/templates/webui.yaml +148 -6
- solace_agent_mesh/tools/web_search/__init__.py +18 -0
- solace_agent_mesh/tools/web_search/base.py +84 -0
- solace_agent_mesh/tools/web_search/google_search.py +247 -0
- solace_agent_mesh/tools/web_search/models.py +99 -0
- {solace_agent_mesh-1.7.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/METADATA +29 -8
- {solace_agent_mesh-1.7.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/RECORD +334 -313
- {solace_agent_mesh-1.7.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/WHEEL +1 -1
- solace_agent_mesh/agent/adk/adk_llm.txt +0 -226
- solace_agent_mesh/agent/adk/adk_llm_detail.txt +0 -566
- solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +0 -171
- solace_agent_mesh/agent/adk/models/models_llm.txt +0 -189
- solace_agent_mesh/agent/agent_llm.txt +0 -369
- solace_agent_mesh/agent/agent_llm_detail.txt +0 -1702
- solace_agent_mesh/agent/protocol/protocol_llm.txt +0 -81
- solace_agent_mesh/agent/protocol/protocol_llm_detail.txt +0 -92
- solace_agent_mesh/agent/proxies/a2a/a2a_llm.txt +0 -190
- solace_agent_mesh/agent/proxies/base/base_llm.txt +0 -148
- solace_agent_mesh/agent/proxies/proxies_llm.txt +0 -283
- solace_agent_mesh/agent/sac/sac_llm.txt +0 -189
- solace_agent_mesh/agent/sac/sac_llm_detail.txt +0 -200
- solace_agent_mesh/agent/testing/testing_llm.txt +0 -58
- solace_agent_mesh/agent/testing/testing_llm_detail.txt +0 -68
- solace_agent_mesh/agent/tools/tools_llm.txt +0 -276
- solace_agent_mesh/agent/tools/tools_llm_detail.txt +0 -275
- solace_agent_mesh/agent/utils/utils_llm.txt +0 -152
- solace_agent_mesh/agent/utils/utils_llm_detail.txt +0 -149
- solace_agent_mesh/assets/docs/assets/js/05749d90.c70b2be9.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/15ba94aa.92fea363.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/15e40e79.36003774.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js +0 -2
- solace_agent_mesh/assets/docs/assets/js/240a0364.c39f8388.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/2e32b5e0.33f5d75b.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/3a6c6137.f5940cfa.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/3ac1795d.e4870a49.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/3ff0015d.b63ee53a.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/547e15cc.2f7790c1.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/55b7b518.f2b1d1ba.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.45b32c2b.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/64195356.c498c4d0.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/66d4869e.830d443f.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/6d84eae0.4a5fbf39.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/8024126c.fa0e7186.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/81a99df0.07034dd9.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/82fbfb93.139a1a1f.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/924ffdeb.8095e148.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/94e8668d.09ed9234.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/9bb13469.dd1c9b54.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/a94703ab.0438dbc2.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/ab9708a8.245ae0ef.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/c93cbaa0.eaff365e.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.f902fad8.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/db5d6442.3daf1696.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/e04b235d.c9c50c7b.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/e3d9abda.d11c67a7.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/e6f9706b.045d0fa1.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/e92d0134.3bda61dd.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/f284c35a.5099c51e.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/main.f213fe0c.js +0 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.d9606d6a.js +0 -1
- solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes-deployment/index.html +0 -47
- solace_agent_mesh/assets/docs/lunr-index-1762283454666.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1762283454666.json +0 -1
- solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +0 -250
- solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +0 -365
- solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +0 -305
- solace_agent_mesh/client/webui/frontend/static/assets/client-CRYdKo2Q.js +0 -25
- solace_agent_mesh/client/webui/frontend/static/assets/main-CojeY_1w.css +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-ILja9MCG.js +0 -353
- solace_agent_mesh/common/a2a/a2a_llm.txt +0 -175
- solace_agent_mesh/common/a2a/a2a_llm_detail.txt +0 -193
- solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +0 -445
- solace_agent_mesh/common/a2a_spec/a2a_spec_llm_detail.txt +0 -736
- solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +0 -330
- solace_agent_mesh/common/common_llm.txt +0 -230
- solace_agent_mesh/common/common_llm_detail.txt +0 -2562
- solace_agent_mesh/common/middleware/middleware_llm.txt +0 -174
- solace_agent_mesh/common/middleware/middleware_llm_detail.txt +0 -185
- solace_agent_mesh/common/sac/sac_llm.txt +0 -71
- solace_agent_mesh/common/sac/sac_llm_detail.txt +0 -82
- solace_agent_mesh/common/sam_events/sam_events_llm.txt +0 -104
- solace_agent_mesh/common/sam_events/sam_events_llm_detail.txt +0 -115
- solace_agent_mesh/common/services/providers/providers_llm.txt +0 -81
- solace_agent_mesh/common/services/services_llm.txt +0 -368
- solace_agent_mesh/common/services/services_llm_detail.txt +0 -459
- solace_agent_mesh/common/utils/embeds/embeds_llm.txt +0 -220
- solace_agent_mesh/common/utils/utils_llm.txt +0 -335
- solace_agent_mesh/common/utils/utils_llm_detail.txt +0 -572
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-ByU1X1HD.js +0 -98
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-61038fc6.js +0 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-BWvk5-gF.js +0 -10
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-DxRwaWiE.css +0 -1
- solace_agent_mesh/core_a2a/core_a2a_llm.txt +0 -90
- solace_agent_mesh/core_a2a/core_a2a_llm_detail.txt +0 -101
- solace_agent_mesh/gateway/base/base_llm.txt +0 -226
- solace_agent_mesh/gateway/base/base_llm_detail.txt +0 -235
- solace_agent_mesh/gateway/gateway_llm.txt +0 -369
- solace_agent_mesh/gateway/gateway_llm_detail.txt +0 -3885
- solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +0 -345
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_fulltext_search_indexes.py +0 -92
- solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +0 -161
- solace_agent_mesh/gateway/http_sse/components/components_llm.txt +0 -105
- solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +0 -299
- solace_agent_mesh/gateway/http_sse/http_sse_llm_detail.txt +0 -3278
- solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +0 -221
- solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +0 -257
- solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +0 -308
- solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +0 -450
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +0 -133
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +0 -123
- solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +0 -312
- solace_agent_mesh/gateway/http_sse/services/services_llm.txt +0 -303
- solace_agent_mesh/gateway/http_sse/shared/__init__.py +0 -146
- solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +0 -319
- solace_agent_mesh/gateway/http_sse/utils/utils_llm.txt +0 -47
- solace_agent_mesh/llm.txt +0 -228
- solace_agent_mesh/llm_detail.txt +0 -2835
- solace_agent_mesh/solace_agent_mesh_llm.txt +0 -362
- solace_agent_mesh/solace_agent_mesh_llm_detail.txt +0 -8599
- solace_agent_mesh/templates/logging_config_template.ini +0 -45
- solace_agent_mesh/templates/templates_llm.txt +0 -147
- /solace_agent_mesh/assets/docs/assets/js/{main.f213fe0c.js.LICENSE.txt → main.d634009f.js.LICENSE.txt} +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/api}/auth_utils.py +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/api}/pagination.py +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/api}/response_utils.py +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/exceptions}/error_dto.py +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/exceptions}/exceptions.py +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/enums.py +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/timestamp_utils.py +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/types.py +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/utils.py +0 -0
- {solace_agent_mesh-1.7.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.7.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,10 +4,18 @@ Business service for project-related operations.
|
|
|
4
4
|
|
|
5
5
|
from typing import List, Optional, TYPE_CHECKING
|
|
6
6
|
import logging
|
|
7
|
+
import json
|
|
8
|
+
import zipfile
|
|
9
|
+
from io import BytesIO
|
|
7
10
|
from fastapi import UploadFile
|
|
8
11
|
from datetime import datetime, timezone
|
|
9
12
|
|
|
10
|
-
from ....agent.utils.artifact_helpers import get_artifact_info_list, save_artifact_with_metadata
|
|
13
|
+
from ....agent.utils.artifact_helpers import get_artifact_info_list, save_artifact_with_metadata, get_artifact_counts_batch
|
|
14
|
+
|
|
15
|
+
# Default max upload size (50MB) - matches gateway_max_upload_size_bytes default
|
|
16
|
+
DEFAULT_MAX_UPLOAD_SIZE_BYTES = 52428800
|
|
17
|
+
# Default max ZIP upload size (100MB) - for project import ZIP files
|
|
18
|
+
DEFAULT_MAX_ZIP_UPLOAD_SIZE_BYTES = 104857600
|
|
11
19
|
|
|
12
20
|
try:
|
|
13
21
|
from google.adk.artifacts import BaseArtifactService
|
|
@@ -36,6 +44,29 @@ class ProjectService:
|
|
|
36
44
|
self.artifact_service = component.get_shared_artifact_service() if component else None
|
|
37
45
|
self.app_name = component.get_config("name", "WebUIBackendApp") if component else "WebUIBackendApp"
|
|
38
46
|
self.logger = logging.getLogger(__name__)
|
|
47
|
+
# Get max upload size from component config, with fallback to default
|
|
48
|
+
# Ensure values are integers for proper formatting
|
|
49
|
+
max_upload_config = (
|
|
50
|
+
component.get_config("gateway_max_upload_size_bytes", DEFAULT_MAX_UPLOAD_SIZE_BYTES)
|
|
51
|
+
if component else DEFAULT_MAX_UPLOAD_SIZE_BYTES
|
|
52
|
+
)
|
|
53
|
+
self.max_upload_size_bytes = int(max_upload_config) if isinstance(max_upload_config, (int, float)) else DEFAULT_MAX_UPLOAD_SIZE_BYTES
|
|
54
|
+
|
|
55
|
+
# Get max ZIP upload size from component config, with fallback to default (100MB)
|
|
56
|
+
max_zip_config = (
|
|
57
|
+
component.get_config("gateway_max_zip_upload_size_bytes", DEFAULT_MAX_ZIP_UPLOAD_SIZE_BYTES)
|
|
58
|
+
if component else DEFAULT_MAX_ZIP_UPLOAD_SIZE_BYTES
|
|
59
|
+
)
|
|
60
|
+
self.max_zip_upload_size_bytes = int(max_zip_config) if isinstance(max_zip_config, (int, float)) else DEFAULT_MAX_ZIP_UPLOAD_SIZE_BYTES
|
|
61
|
+
|
|
62
|
+
self.logger.info(
|
|
63
|
+
"[ProjectService] Initialized with max_upload_size_bytes=%d (%.2f MB), "
|
|
64
|
+
"max_zip_upload_size_bytes=%d (%.2f MB)",
|
|
65
|
+
self.max_upload_size_bytes,
|
|
66
|
+
self.max_upload_size_bytes / (1024*1024),
|
|
67
|
+
self.max_zip_upload_size_bytes,
|
|
68
|
+
self.max_zip_upload_size_bytes / (1024*1024)
|
|
69
|
+
)
|
|
39
70
|
|
|
40
71
|
def _get_repositories(self, db):
|
|
41
72
|
"""Create project repository for the given database session."""
|
|
@@ -46,6 +77,75 @@ class ProjectService:
|
|
|
46
77
|
"""Checks if the service is configured with a persistent backend."""
|
|
47
78
|
return self.component and self.component.database_url is not None
|
|
48
79
|
|
|
80
|
+
async def _validate_file_size(self, file: UploadFile, log_prefix: str = "") -> bytes:
|
|
81
|
+
"""
|
|
82
|
+
Validate file size and read content with size checking.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
file: The uploaded file to validate
|
|
86
|
+
log_prefix: Prefix for log messages
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
bytes: The file content if validation passes
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
ValueError: If file exceeds maximum allowed size
|
|
93
|
+
"""
|
|
94
|
+
# Read file content in chunks to validate size
|
|
95
|
+
chunk_size = 1024 * 1024 # 1MB chunks
|
|
96
|
+
content_bytes = bytearray()
|
|
97
|
+
total_bytes_read = 0
|
|
98
|
+
|
|
99
|
+
while True:
|
|
100
|
+
chunk = await file.read(chunk_size)
|
|
101
|
+
if not chunk:
|
|
102
|
+
break
|
|
103
|
+
|
|
104
|
+
chunk_len = len(chunk)
|
|
105
|
+
total_bytes_read += chunk_len
|
|
106
|
+
|
|
107
|
+
# Validate size during reading (fail fast)
|
|
108
|
+
if total_bytes_read > self.max_upload_size_bytes:
|
|
109
|
+
error_msg = (
|
|
110
|
+
f"File '{file.filename}' rejected: size exceeds maximum "
|
|
111
|
+
f"{self.max_upload_size_bytes:,} bytes "
|
|
112
|
+
f"({self.max_upload_size_bytes / (1024*1024):.2f} MB). "
|
|
113
|
+
f"Read {total_bytes_read:,} bytes so far."
|
|
114
|
+
)
|
|
115
|
+
self.logger.warning(f"{log_prefix} {error_msg}")
|
|
116
|
+
raise ValueError(error_msg)
|
|
117
|
+
|
|
118
|
+
content_bytes.extend(chunk)
|
|
119
|
+
|
|
120
|
+
return bytes(content_bytes)
|
|
121
|
+
|
|
122
|
+
async def _validate_files(
|
|
123
|
+
self,
|
|
124
|
+
files: List[UploadFile],
|
|
125
|
+
log_prefix: str = ""
|
|
126
|
+
) -> List[tuple]:
|
|
127
|
+
"""
|
|
128
|
+
Validate multiple files and return their content.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
files: List of uploaded files to validate
|
|
132
|
+
log_prefix: Prefix for log messages
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
List of tuples: [(file, content_bytes), ...]
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
ValueError: If any file exceeds maximum allowed size
|
|
139
|
+
"""
|
|
140
|
+
validated_files = []
|
|
141
|
+
for file in files:
|
|
142
|
+
content_bytes = await self._validate_file_size(file, log_prefix)
|
|
143
|
+
validated_files.append((file, content_bytes))
|
|
144
|
+
self.logger.debug(
|
|
145
|
+
f"{log_prefix} Validated file '{file.filename}': {len(content_bytes):,} bytes"
|
|
146
|
+
)
|
|
147
|
+
return validated_files
|
|
148
|
+
|
|
49
149
|
async def create_project(
|
|
50
150
|
self,
|
|
51
151
|
db,
|
|
@@ -73,8 +173,9 @@ class ProjectService:
|
|
|
73
173
|
DomainProject: The created project
|
|
74
174
|
|
|
75
175
|
Raises:
|
|
76
|
-
ValueError: If project name is invalid
|
|
176
|
+
ValueError: If project name is invalid, user_id is missing, or file size exceeds limit
|
|
77
177
|
"""
|
|
178
|
+
log_prefix = f"[ProjectService:create_project] User {user_id}:"
|
|
78
179
|
self.logger.info(f"Creating new project '{name}' for user {user_id}")
|
|
79
180
|
|
|
80
181
|
# Business validation
|
|
@@ -84,6 +185,13 @@ class ProjectService:
|
|
|
84
185
|
if not user_id:
|
|
85
186
|
raise ValueError("User ID is required to create a project")
|
|
86
187
|
|
|
188
|
+
# Validate file sizes before creating project
|
|
189
|
+
validated_files = []
|
|
190
|
+
if files:
|
|
191
|
+
self.logger.info(f"{log_prefix} Validating {len(files)} files before project creation")
|
|
192
|
+
validated_files = await self._validate_files(files, log_prefix)
|
|
193
|
+
self.logger.info(f"{log_prefix} All {len(files)} files passed size validation")
|
|
194
|
+
|
|
87
195
|
project_repository = self._get_repositories(db)
|
|
88
196
|
|
|
89
197
|
# Check for duplicate project name for this user
|
|
@@ -100,13 +208,12 @@ class ProjectService:
|
|
|
100
208
|
default_agent_id=default_agent_id,
|
|
101
209
|
)
|
|
102
210
|
|
|
103
|
-
if
|
|
211
|
+
if validated_files and self.artifact_service:
|
|
104
212
|
self.logger.info(
|
|
105
|
-
f"Project {project_domain.id} created, now saving {len(
|
|
213
|
+
f"Project {project_domain.id} created, now saving {len(validated_files)} artifacts."
|
|
106
214
|
)
|
|
107
215
|
project_session_id = f"project-{project_domain.id}"
|
|
108
|
-
for file in
|
|
109
|
-
content_bytes = await file.read()
|
|
216
|
+
for file, content_bytes in validated_files:
|
|
110
217
|
metadata = {"source": "project"}
|
|
111
218
|
if file_metadata and file.filename in file_metadata:
|
|
112
219
|
desc = file_metadata[file.filename]
|
|
@@ -124,7 +231,7 @@ class ProjectService:
|
|
|
124
231
|
metadata_dict=metadata,
|
|
125
232
|
timestamp=datetime.now(timezone.utc),
|
|
126
233
|
)
|
|
127
|
-
self.logger.info(f"Saved {len(
|
|
234
|
+
self.logger.info(f"Saved {len(validated_files)} artifacts for project {project_domain.id}")
|
|
128
235
|
|
|
129
236
|
self.logger.info(
|
|
130
237
|
f"Successfully created project {project_domain.id} for user {user_id}"
|
|
@@ -162,6 +269,52 @@ class ProjectService:
|
|
|
162
269
|
db_projects = project_repository.get_user_projects(user_id)
|
|
163
270
|
return db_projects
|
|
164
271
|
|
|
272
|
+
async def get_user_projects_with_counts(self, db, user_id: str) -> List[tuple[Project, int]]:
|
|
273
|
+
"""
|
|
274
|
+
Get all projects owned by a specific user with artifact counts.
|
|
275
|
+
Uses batch counting for efficiency.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
db: Database session
|
|
279
|
+
user_id: The user ID
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
List[tuple[Project, int]]: List of tuples (project, artifact_count)
|
|
283
|
+
"""
|
|
284
|
+
self.logger.debug(f"Retrieving projects with artifact counts for user {user_id}")
|
|
285
|
+
projects = self.get_user_projects(db, user_id)
|
|
286
|
+
|
|
287
|
+
if not self.artifact_service or not projects:
|
|
288
|
+
# If no artifact service or no projects, return projects with 0 counts
|
|
289
|
+
return [(project, 0) for project in projects]
|
|
290
|
+
|
|
291
|
+
# Build list of session IDs for batch counting
|
|
292
|
+
session_ids = [f"project-{project.id}" for project in projects]
|
|
293
|
+
|
|
294
|
+
try:
|
|
295
|
+
# Get all counts in a single batch operation
|
|
296
|
+
counts_by_session = await get_artifact_counts_batch(
|
|
297
|
+
artifact_service=self.artifact_service,
|
|
298
|
+
app_name=self.app_name,
|
|
299
|
+
user_id=user_id,
|
|
300
|
+
session_ids=session_ids,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Map counts back to projects
|
|
304
|
+
projects_with_counts = []
|
|
305
|
+
for project in projects:
|
|
306
|
+
storage_session_id = f"project-{project.id}"
|
|
307
|
+
artifact_count = counts_by_session.get(storage_session_id, 0)
|
|
308
|
+
projects_with_counts.append((project, artifact_count))
|
|
309
|
+
|
|
310
|
+
self.logger.debug(f"Retrieved artifact counts for {len(projects)} projects in batch")
|
|
311
|
+
return projects_with_counts
|
|
312
|
+
|
|
313
|
+
except Exception as e:
|
|
314
|
+
self.logger.error(f"Failed to get artifact counts in batch: {e}")
|
|
315
|
+
# Fallback to 0 counts on error
|
|
316
|
+
return [(project, 0) for project in projects]
|
|
317
|
+
|
|
165
318
|
async def get_project_artifacts(self, db, project_id: str, user_id: str) -> List[ArtifactInfo]:
|
|
166
319
|
"""
|
|
167
320
|
Get a list of artifacts for a given project.
|
|
@@ -220,8 +373,10 @@ class ProjectService:
|
|
|
220
373
|
List[dict]: A list of results from the save operations
|
|
221
374
|
|
|
222
375
|
Raises:
|
|
223
|
-
ValueError: If project not found or
|
|
376
|
+
ValueError: If project not found, access denied, or file size exceeds limit
|
|
224
377
|
"""
|
|
378
|
+
log_prefix = f"[ProjectService:add_artifacts] Project {project_id}, User {user_id}:"
|
|
379
|
+
|
|
225
380
|
project = self.get_project(db, project_id, user_id)
|
|
226
381
|
if not project:
|
|
227
382
|
raise ValueError("Project not found or access denied")
|
|
@@ -233,12 +388,16 @@ class ProjectService:
|
|
|
233
388
|
if not files:
|
|
234
389
|
return []
|
|
235
390
|
|
|
236
|
-
|
|
391
|
+
# Validate file sizes before saving any artifacts
|
|
392
|
+
self.logger.info(f"{log_prefix} Validating {len(files)} files before adding to project")
|
|
393
|
+
validated_files = await self._validate_files(files, log_prefix)
|
|
394
|
+
self.logger.info(f"{log_prefix} All {len(files)} files passed size validation")
|
|
395
|
+
|
|
396
|
+
self.logger.info(f"Adding {len(validated_files)} artifacts to project {project_id} for user {user_id}")
|
|
237
397
|
storage_session_id = f"project-{project.id}"
|
|
238
398
|
results = []
|
|
239
399
|
|
|
240
|
-
for file in
|
|
241
|
-
content_bytes = await file.read()
|
|
400
|
+
for file, content_bytes in validated_files:
|
|
242
401
|
metadata = {"source": "project"}
|
|
243
402
|
if file_metadata and file.filename in file_metadata:
|
|
244
403
|
desc = file_metadata[file.filename]
|
|
@@ -258,9 +417,83 @@ class ProjectService:
|
|
|
258
417
|
)
|
|
259
418
|
results.append(result)
|
|
260
419
|
|
|
261
|
-
self.logger.info(f"Finished adding {len(
|
|
420
|
+
self.logger.info(f"Finished adding {len(validated_files)} artifacts to project {project_id}")
|
|
262
421
|
return results
|
|
263
422
|
|
|
423
|
+
async def update_artifact_metadata(
|
|
424
|
+
self,
|
|
425
|
+
db,
|
|
426
|
+
project_id: str,
|
|
427
|
+
user_id: str,
|
|
428
|
+
filename: str,
|
|
429
|
+
description: Optional[str] = None
|
|
430
|
+
) -> bool:
|
|
431
|
+
"""
|
|
432
|
+
Update metadata (description) for a project artifact.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
db: The database session
|
|
436
|
+
project_id: The project ID
|
|
437
|
+
user_id: The requesting user ID
|
|
438
|
+
filename: The filename of the artifact to update
|
|
439
|
+
description: New description for the artifact
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
bool: True if update was successful, False if project not found
|
|
443
|
+
|
|
444
|
+
Raises:
|
|
445
|
+
ValueError: If user cannot modify the project or artifact service is missing
|
|
446
|
+
"""
|
|
447
|
+
project = self.get_project(db, project_id, user_id)
|
|
448
|
+
if not project:
|
|
449
|
+
return False
|
|
450
|
+
|
|
451
|
+
if not self.artifact_service:
|
|
452
|
+
self.logger.warning(f"Attempted to update artifact metadata in project {project_id} but no artifact service is configured.")
|
|
453
|
+
raise ValueError("Artifact service is not configured")
|
|
454
|
+
|
|
455
|
+
storage_session_id = f"project-{project.id}"
|
|
456
|
+
|
|
457
|
+
self.logger.info(f"Updating metadata for artifact '{filename}' in project {project_id} for user {user_id}")
|
|
458
|
+
|
|
459
|
+
# Load the current artifact to get its content and existing metadata
|
|
460
|
+
try:
|
|
461
|
+
artifact_part = await self.artifact_service.load_artifact(
|
|
462
|
+
app_name=self.app_name,
|
|
463
|
+
user_id=project.user_id,
|
|
464
|
+
session_id=storage_session_id,
|
|
465
|
+
filename=filename,
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
if not artifact_part or not artifact_part.inline_data:
|
|
469
|
+
self.logger.warning(f"Artifact '{filename}' not found in project {project_id}")
|
|
470
|
+
return False
|
|
471
|
+
|
|
472
|
+
# Prepare updated metadata
|
|
473
|
+
metadata = {"source": "project"}
|
|
474
|
+
if description is not None:
|
|
475
|
+
metadata["description"] = description
|
|
476
|
+
|
|
477
|
+
# Save the artifact with updated metadata
|
|
478
|
+
await save_artifact_with_metadata(
|
|
479
|
+
artifact_service=self.artifact_service,
|
|
480
|
+
app_name=self.app_name,
|
|
481
|
+
user_id=project.user_id,
|
|
482
|
+
session_id=storage_session_id,
|
|
483
|
+
filename=filename,
|
|
484
|
+
content_bytes=artifact_part.inline_data.data,
|
|
485
|
+
mime_type=artifact_part.inline_data.mime_type,
|
|
486
|
+
metadata_dict=metadata,
|
|
487
|
+
timestamp=datetime.now(timezone.utc),
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
self.logger.info(f"Successfully updated metadata for artifact '{filename}' in project {project_id}")
|
|
491
|
+
return True
|
|
492
|
+
|
|
493
|
+
except Exception as e:
|
|
494
|
+
self.logger.error(f"Error updating artifact metadata: {e}")
|
|
495
|
+
raise
|
|
496
|
+
|
|
264
497
|
async def delete_artifact_from_project(self, db, project_id: str, user_id: str, filename: str) -> bool:
|
|
265
498
|
"""
|
|
266
499
|
Deletes an artifact from a project.
|
|
@@ -401,3 +634,297 @@ class ProjectService:
|
|
|
401
634
|
self.logger.info(f"Successfully soft deleted project {project_id} and {deleted_count} associated sessions")
|
|
402
635
|
|
|
403
636
|
return success
|
|
637
|
+
|
|
638
|
+
async def export_project_as_zip(
|
|
639
|
+
self, db, project_id: str, user_id: str
|
|
640
|
+
) -> BytesIO:
|
|
641
|
+
"""
|
|
642
|
+
Create ZIP file with project data and artifacts.
|
|
643
|
+
Returns in-memory ZIP file.
|
|
644
|
+
|
|
645
|
+
Args:
|
|
646
|
+
db: Database session
|
|
647
|
+
project_id: The project ID
|
|
648
|
+
user_id: The requesting user ID
|
|
649
|
+
|
|
650
|
+
Returns:
|
|
651
|
+
BytesIO: In-memory ZIP file
|
|
652
|
+
|
|
653
|
+
Raises:
|
|
654
|
+
ValueError: If project not found or access denied
|
|
655
|
+
"""
|
|
656
|
+
# Get project
|
|
657
|
+
project = self.get_project(db, project_id, user_id)
|
|
658
|
+
if not project:
|
|
659
|
+
raise ValueError("Project not found or access denied")
|
|
660
|
+
|
|
661
|
+
# Get artifacts
|
|
662
|
+
artifacts = await self.get_project_artifacts(db, project_id, user_id)
|
|
663
|
+
|
|
664
|
+
# Calculate total size
|
|
665
|
+
total_size = sum(artifact.size for artifact in artifacts)
|
|
666
|
+
|
|
667
|
+
# Create export metadata
|
|
668
|
+
from ..routers.dto.project_dto import (
|
|
669
|
+
ProjectExportFormat,
|
|
670
|
+
ProjectExportData,
|
|
671
|
+
ProjectExportMetadata,
|
|
672
|
+
ArtifactMetadata,
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
export_data = ProjectExportFormat(
|
|
676
|
+
version="1.0",
|
|
677
|
+
exported_at=int(datetime.now(timezone.utc).timestamp() * 1000),
|
|
678
|
+
project=ProjectExportData(
|
|
679
|
+
name=project.name,
|
|
680
|
+
description=project.description,
|
|
681
|
+
system_prompt=project.system_prompt,
|
|
682
|
+
default_agent_id=project.default_agent_id,
|
|
683
|
+
metadata=ProjectExportMetadata(
|
|
684
|
+
original_created_at=project.created_at,
|
|
685
|
+
artifact_count=len(artifacts),
|
|
686
|
+
total_size_bytes=total_size,
|
|
687
|
+
),
|
|
688
|
+
),
|
|
689
|
+
artifacts=[
|
|
690
|
+
ArtifactMetadata(
|
|
691
|
+
filename=artifact.filename,
|
|
692
|
+
mime_type=artifact.mime_type or "application/octet-stream",
|
|
693
|
+
size=artifact.size,
|
|
694
|
+
metadata={
|
|
695
|
+
"description": artifact.description,
|
|
696
|
+
"source": artifact.source,
|
|
697
|
+
} if artifact.description or artifact.source else {},
|
|
698
|
+
)
|
|
699
|
+
for artifact in artifacts
|
|
700
|
+
],
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
# Create ZIP in memory
|
|
704
|
+
zip_buffer = BytesIO()
|
|
705
|
+
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
|
706
|
+
# Add project.json
|
|
707
|
+
project_json = export_data.model_dump(by_alias=True, mode='json')
|
|
708
|
+
zip_file.writestr('project.json', json.dumps(project_json, indent=2))
|
|
709
|
+
|
|
710
|
+
# Add artifacts
|
|
711
|
+
if self.artifact_service and artifacts:
|
|
712
|
+
storage_session_id = f"project-{project.id}"
|
|
713
|
+
for artifact in artifacts:
|
|
714
|
+
try:
|
|
715
|
+
# Load artifact content
|
|
716
|
+
artifact_part = await self.artifact_service.load_artifact(
|
|
717
|
+
app_name=self.app_name,
|
|
718
|
+
user_id=project.user_id,
|
|
719
|
+
session_id=storage_session_id,
|
|
720
|
+
filename=artifact.filename,
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
if artifact_part and artifact_part.inline_data:
|
|
724
|
+
# Add to ZIP under artifacts/ directory
|
|
725
|
+
zip_file.writestr(
|
|
726
|
+
f'artifacts/{artifact.filename}',
|
|
727
|
+
artifact_part.inline_data.data
|
|
728
|
+
)
|
|
729
|
+
except Exception as e:
|
|
730
|
+
self.logger.warning(
|
|
731
|
+
f"Failed to add artifact {artifact.filename} to export: {e}"
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
zip_buffer.seek(0)
|
|
735
|
+
return zip_buffer
|
|
736
|
+
|
|
737
|
+
async def import_project_from_zip(
|
|
738
|
+
self, db, zip_file: UploadFile, user_id: str,
|
|
739
|
+
preserve_name: bool = False, custom_name: Optional[str] = None
|
|
740
|
+
) -> tuple[Project, int, List[str]]:
|
|
741
|
+
"""
|
|
742
|
+
Import project from ZIP file.
|
|
743
|
+
|
|
744
|
+
Args:
|
|
745
|
+
db: Database session
|
|
746
|
+
zip_file: Uploaded ZIP file
|
|
747
|
+
user_id: The importing user ID
|
|
748
|
+
preserve_name: Whether to preserve original name
|
|
749
|
+
custom_name: Custom name to use (overrides preserve_name)
|
|
750
|
+
|
|
751
|
+
Returns:
|
|
752
|
+
tuple: (created_project, artifacts_count, warnings)
|
|
753
|
+
|
|
754
|
+
Raises:
|
|
755
|
+
ValueError: If ZIP is invalid, import fails, or file size exceeds limit
|
|
756
|
+
"""
|
|
757
|
+
log_prefix = f"[ProjectService:import_project] User {user_id}:"
|
|
758
|
+
warnings = []
|
|
759
|
+
|
|
760
|
+
# Read ZIP file content with size validation
|
|
761
|
+
self.logger.info(f"{log_prefix} Reading ZIP file")
|
|
762
|
+
zip_content = await zip_file.read()
|
|
763
|
+
zip_size = len(zip_content)
|
|
764
|
+
self.logger.info(f"{log_prefix} ZIP file read: {zip_size:,} bytes")
|
|
765
|
+
|
|
766
|
+
# Validate ZIP file size (separate, larger limit than individual artifacts)
|
|
767
|
+
if zip_size > self.max_zip_upload_size_bytes:
|
|
768
|
+
max_size_mb = self.max_zip_upload_size_bytes / (1024 * 1024)
|
|
769
|
+
file_size_mb = zip_size / (1024 * 1024)
|
|
770
|
+
error_msg = (
|
|
771
|
+
f"ZIP file '{zip_file.filename}' rejected: size ({file_size_mb:.2f} MB) "
|
|
772
|
+
f"exceeds maximum allowed ({max_size_mb:.2f} MB)"
|
|
773
|
+
)
|
|
774
|
+
self.logger.warning(f"{log_prefix} {error_msg}")
|
|
775
|
+
raise ValueError(error_msg)
|
|
776
|
+
|
|
777
|
+
zip_buffer = BytesIO(zip_content)
|
|
778
|
+
|
|
779
|
+
try:
|
|
780
|
+
with zipfile.ZipFile(zip_buffer, 'r') as zip_ref:
|
|
781
|
+
# Validate ZIP structure
|
|
782
|
+
if 'project.json' not in zip_ref.namelist():
|
|
783
|
+
raise ValueError("Invalid project export: missing project.json")
|
|
784
|
+
|
|
785
|
+
# Parse project.json
|
|
786
|
+
project_json_content = zip_ref.read('project.json').decode('utf-8')
|
|
787
|
+
project_data = json.loads(project_json_content)
|
|
788
|
+
|
|
789
|
+
# Validate version
|
|
790
|
+
if project_data.get('version') != '1.0':
|
|
791
|
+
raise ValueError(
|
|
792
|
+
f"Unsupported export version: {project_data.get('version')}"
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
# Determine project name
|
|
796
|
+
original_name = project_data['project']['name']
|
|
797
|
+
if custom_name:
|
|
798
|
+
desired_name = custom_name
|
|
799
|
+
elif preserve_name:
|
|
800
|
+
desired_name = original_name
|
|
801
|
+
else:
|
|
802
|
+
desired_name = original_name
|
|
803
|
+
|
|
804
|
+
# Resolve name conflicts
|
|
805
|
+
final_name = self._resolve_project_name_conflict(db, desired_name, user_id)
|
|
806
|
+
if final_name != desired_name:
|
|
807
|
+
warnings.append(
|
|
808
|
+
f"Name conflict resolved: '{desired_name}' → '{final_name}'"
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
# Get default agent ID, but set to None if not provided
|
|
812
|
+
# The agent may not exist in the target environment
|
|
813
|
+
imported_agent_id = project_data['project'].get('defaultAgentId')
|
|
814
|
+
|
|
815
|
+
# Create project (agent validation happens in create_project if needed)
|
|
816
|
+
project = await self.create_project(
|
|
817
|
+
db=db,
|
|
818
|
+
name=final_name,
|
|
819
|
+
user_id=user_id,
|
|
820
|
+
description=project_data['project'].get('description'),
|
|
821
|
+
system_prompt=project_data['project'].get('systemPrompt'),
|
|
822
|
+
default_agent_id=imported_agent_id,
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
# Add warning if agent was specified but may not exist
|
|
826
|
+
if imported_agent_id:
|
|
827
|
+
warnings.append(
|
|
828
|
+
f"Default agent '{imported_agent_id}' was imported. "
|
|
829
|
+
"Verify it exists in your environment."
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
# Import artifacts
|
|
833
|
+
artifacts_imported = 0
|
|
834
|
+
if self.artifact_service:
|
|
835
|
+
storage_session_id = f"project-{project.id}"
|
|
836
|
+
artifact_files = [
|
|
837
|
+
name for name in zip_ref.namelist()
|
|
838
|
+
if name.startswith('artifacts/') and name != 'artifacts/'
|
|
839
|
+
]
|
|
840
|
+
|
|
841
|
+
for artifact_path in artifact_files:
|
|
842
|
+
try:
|
|
843
|
+
filename = artifact_path.replace('artifacts/', '')
|
|
844
|
+
content_bytes = zip_ref.read(artifact_path)
|
|
845
|
+
|
|
846
|
+
# Skip oversized artifacts with a warning (don't fail the entire import)
|
|
847
|
+
if len(content_bytes) > self.max_upload_size_bytes:
|
|
848
|
+
max_size_mb = self.max_upload_size_bytes / (1024 * 1024)
|
|
849
|
+
file_size_mb = len(content_bytes) / (1024 * 1024)
|
|
850
|
+
skip_msg = (
|
|
851
|
+
f"Skipped '{filename}': size ({file_size_mb:.2f} MB) "
|
|
852
|
+
f"exceeds maximum allowed ({max_size_mb:.2f} MB)"
|
|
853
|
+
)
|
|
854
|
+
self.logger.warning(f"{log_prefix} {skip_msg}")
|
|
855
|
+
warnings.append(skip_msg)
|
|
856
|
+
continue # Skip this artifact, continue with others
|
|
857
|
+
|
|
858
|
+
# Find metadata from project.json
|
|
859
|
+
artifact_meta = next(
|
|
860
|
+
(a for a in project_data.get('artifacts', [])
|
|
861
|
+
if a['filename'] == filename),
|
|
862
|
+
None
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
metadata = artifact_meta.get('metadata', {}) if artifact_meta else {}
|
|
866
|
+
mime_type = artifact_meta.get('mimeType', 'application/octet-stream') if artifact_meta else 'application/octet-stream'
|
|
867
|
+
|
|
868
|
+
# Save artifact
|
|
869
|
+
from ....agent.utils.artifact_helpers import save_artifact_with_metadata
|
|
870
|
+
await save_artifact_with_metadata(
|
|
871
|
+
artifact_service=self.artifact_service,
|
|
872
|
+
app_name=self.app_name,
|
|
873
|
+
user_id=project.user_id,
|
|
874
|
+
session_id=storage_session_id,
|
|
875
|
+
filename=filename,
|
|
876
|
+
content_bytes=content_bytes,
|
|
877
|
+
mime_type=mime_type,
|
|
878
|
+
metadata_dict=metadata,
|
|
879
|
+
timestamp=datetime.now(timezone.utc),
|
|
880
|
+
)
|
|
881
|
+
artifacts_imported += 1
|
|
882
|
+
except Exception as e:
|
|
883
|
+
self.logger.warning(
|
|
884
|
+
f"Failed to import artifact {artifact_path}: {e}"
|
|
885
|
+
)
|
|
886
|
+
warnings.append(f"Failed to import artifact: {filename}")
|
|
887
|
+
|
|
888
|
+
self.logger.info(
|
|
889
|
+
f"Successfully imported project {project.id} with {artifacts_imported} artifacts"
|
|
890
|
+
)
|
|
891
|
+
return project, artifacts_imported, warnings
|
|
892
|
+
|
|
893
|
+
except zipfile.BadZipFile:
|
|
894
|
+
raise ValueError("Invalid ZIP file")
|
|
895
|
+
except json.JSONDecodeError:
|
|
896
|
+
raise ValueError("Invalid project.json format")
|
|
897
|
+
except KeyError as e:
|
|
898
|
+
raise ValueError(f"Missing required field in project.json: {e}")
|
|
899
|
+
|
|
900
|
+
def _resolve_project_name_conflict(
|
|
901
|
+
self, db, desired_name: str, user_id: str
|
|
902
|
+
) -> str:
|
|
903
|
+
"""
|
|
904
|
+
Resolve project name conflicts by appending (2), (3), etc.
|
|
905
|
+
Similar to prompt import conflict resolution.
|
|
906
|
+
|
|
907
|
+
Args:
|
|
908
|
+
db: Database session
|
|
909
|
+
desired_name: The desired project name
|
|
910
|
+
user_id: The user ID
|
|
911
|
+
|
|
912
|
+
Returns:
|
|
913
|
+
str: A unique project name
|
|
914
|
+
"""
|
|
915
|
+
project_repository = self._get_repositories(db)
|
|
916
|
+
existing_projects = project_repository.get_user_projects(user_id)
|
|
917
|
+
existing_names = {p.name.lower() for p in existing_projects}
|
|
918
|
+
|
|
919
|
+
if desired_name.lower() not in existing_names:
|
|
920
|
+
return desired_name
|
|
921
|
+
|
|
922
|
+
# Try appending (2), (3), etc.
|
|
923
|
+
counter = 2
|
|
924
|
+
while True:
|
|
925
|
+
candidate = f"{desired_name} ({counter})"
|
|
926
|
+
if candidate.lower() not in existing_names:
|
|
927
|
+
return candidate
|
|
928
|
+
counter += 1
|
|
929
|
+
if counter > 100: # Safety limit
|
|
930
|
+
raise ValueError("Unable to resolve name conflict")
|