ciris-agent 1.7.7__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.
- ciris_adapters/README.md +113 -0
- ciris_adapters/__init__.py +30 -0
- ciris_adapters/ciris_covenant_metrics/README.md +144 -0
- ciris_adapters/ciris_covenant_metrics/__init__.py +36 -0
- ciris_adapters/ciris_covenant_metrics/adapter.py +249 -0
- ciris_adapters/ciris_covenant_metrics/manifest.json +152 -0
- ciris_adapters/ciris_covenant_metrics/services.py +403 -0
- ciris_adapters/ciris_hosted_tools/__init__.py +24 -0
- ciris_adapters/ciris_hosted_tools/adapter.py +169 -0
- ciris_adapters/ciris_hosted_tools/manifest.json +94 -0
- ciris_adapters/ciris_hosted_tools/services.py +744 -0
- ciris_adapters/external_data_sql/README.md +559 -0
- ciris_adapters/external_data_sql/__init__.py +43 -0
- ciris_adapters/external_data_sql/adapter.py +144 -0
- ciris_adapters/external_data_sql/configurable.py +315 -0
- ciris_adapters/external_data_sql/dialects/__init__.py +37 -0
- ciris_adapters/external_data_sql/dialects/base.py +133 -0
- ciris_adapters/external_data_sql/dialects/mysql.py +63 -0
- ciris_adapters/external_data_sql/dialects/postgresql.py +59 -0
- ciris_adapters/external_data_sql/dialects/sqlite.py +62 -0
- ciris_adapters/external_data_sql/example_config.json +88 -0
- ciris_adapters/external_data_sql/example_privacy_schema.yaml +127 -0
- ciris_adapters/external_data_sql/manifest.json +195 -0
- ciris_adapters/external_data_sql/privacy_schema_loader.py +189 -0
- ciris_adapters/external_data_sql/protocol.py +101 -0
- ciris_adapters/external_data_sql/schemas.py +146 -0
- ciris_adapters/external_data_sql/service.py +1547 -0
- ciris_adapters/external_data_sql/service_old.py +492 -0
- ciris_adapters/home_assistant/__init__.py +63 -0
- ciris_adapters/home_assistant/adapter.py +201 -0
- ciris_adapters/home_assistant/communication_service.py +347 -0
- ciris_adapters/home_assistant/configurable.py +667 -0
- ciris_adapters/home_assistant/manifest.json +203 -0
- ciris_adapters/home_assistant/schemas.py +129 -0
- ciris_adapters/home_assistant/service.py +751 -0
- ciris_adapters/home_assistant/tool_service.py +441 -0
- ciris_adapters/mcp_client/__init__.py +82 -0
- ciris_adapters/mcp_client/adapter.py +847 -0
- ciris_adapters/mcp_client/config.py +280 -0
- ciris_adapters/mcp_client/configurable.py +422 -0
- ciris_adapters/mcp_client/manifest.json +185 -0
- ciris_adapters/mcp_client/mcp_communication_service.py +393 -0
- ciris_adapters/mcp_client/mcp_tool_service.py +463 -0
- ciris_adapters/mcp_client/mcp_wise_service.py +394 -0
- ciris_adapters/mcp_client/schemas.py +149 -0
- ciris_adapters/mcp_client/security.py +592 -0
- ciris_adapters/mcp_common/__init__.py +44 -0
- ciris_adapters/mcp_common/manifest.json +25 -0
- ciris_adapters/mcp_common/protocol.py +315 -0
- ciris_adapters/mcp_common/schemas.py +225 -0
- ciris_adapters/mcp_server/__init__.py +47 -0
- ciris_adapters/mcp_server/adapter.py +581 -0
- ciris_adapters/mcp_server/config.py +260 -0
- ciris_adapters/mcp_server/configurable.py +393 -0
- ciris_adapters/mcp_server/handlers.py +663 -0
- ciris_adapters/mcp_server/manifest.json +211 -0
- ciris_adapters/mcp_server/security.py +500 -0
- ciris_adapters/mock_llm/README.md +117 -0
- ciris_adapters/mock_llm/__init__.py +21 -0
- ciris_adapters/mock_llm/adapter.py +131 -0
- ciris_adapters/mock_llm/configurable.py +237 -0
- ciris_adapters/mock_llm/manifest.json +106 -0
- ciris_adapters/mock_llm/protocol.py +37 -0
- ciris_adapters/mock_llm/responses.py +520 -0
- ciris_adapters/mock_llm/responses_action_selection.py +1041 -0
- ciris_adapters/mock_llm/responses_epistemic.py +17 -0
- ciris_adapters/mock_llm/responses_feedback.py +27 -0
- ciris_adapters/mock_llm/schemas.py +35 -0
- ciris_adapters/mock_llm/service.py +294 -0
- ciris_adapters/navigation/__init__.py +21 -0
- ciris_adapters/navigation/adapter.py +129 -0
- ciris_adapters/navigation/configurable.py +239 -0
- ciris_adapters/navigation/manifest.json +104 -0
- ciris_adapters/navigation/service.py +487 -0
- ciris_adapters/reddit/README.md +132 -0
- ciris_adapters/reddit/REDDIT_ADAPTER_ANALYSIS.md +715 -0
- ciris_adapters/reddit/REDDIT_ADAPTER_SUMMARY.txt +278 -0
- ciris_adapters/reddit/REDDIT_ANALYSIS_INDEX.md +307 -0
- ciris_adapters/reddit/REDDIT_PRODUCTION_READINESS_PLAN.md +518 -0
- ciris_adapters/reddit/__init__.py +15 -0
- ciris_adapters/reddit/adapter.py +189 -0
- ciris_adapters/reddit/configurable.py +274 -0
- ciris_adapters/reddit/error_handler.py +307 -0
- ciris_adapters/reddit/manifest.json +218 -0
- ciris_adapters/reddit/observer.py +532 -0
- ciris_adapters/reddit/protocol.py +34 -0
- ciris_adapters/reddit/schemas.py +433 -0
- ciris_adapters/reddit/service.py +1471 -0
- ciris_adapters/sample_adapter/README.md +474 -0
- ciris_adapters/sample_adapter/__init__.py +45 -0
- ciris_adapters/sample_adapter/adapter.py +208 -0
- ciris_adapters/sample_adapter/configurable.py +469 -0
- ciris_adapters/sample_adapter/manifest.json +247 -0
- ciris_adapters/sample_adapter/services.py +486 -0
- ciris_adapters/weather/__init__.py +16 -0
- ciris_adapters/weather/adapter.py +130 -0
- ciris_adapters/weather/configurable.py +240 -0
- ciris_adapters/weather/manifest.json +156 -0
- ciris_adapters/weather/service.py +600 -0
- ciris_agent-1.7.7.dist-info/METADATA +284 -0
- ciris_agent-1.7.7.dist-info/RECORD +986 -0
- ciris_agent-1.7.7.dist-info/WHEEL +5 -0
- ciris_agent-1.7.7.dist-info/entry_points.txt +15 -0
- ciris_agent-1.7.7.dist-info/licenses/LICENSE +205 -0
- ciris_agent-1.7.7.dist-info/licenses/NOTICE +82 -0
- ciris_agent-1.7.7.dist-info/top_level.txt +4 -0
- ciris_engine/__init__.py +15 -0
- ciris_engine/ciris_templates/ally.yaml +632 -0
- ciris_engine/ciris_templates/default.yaml +411 -0
- ciris_engine/ciris_templates/echo-core.yaml +629 -0
- ciris_engine/ciris_templates/echo-speculative.yaml +764 -0
- ciris_engine/ciris_templates/echo.yaml +647 -0
- ciris_engine/ciris_templates/sage.yaml +332 -0
- ciris_engine/ciris_templates/scout.yaml +338 -0
- ciris_engine/ciris_templates/test.yaml +168 -0
- ciris_engine/cli.py +42 -0
- ciris_engine/config/CIRIS_SERVICES.json +19 -0
- ciris_engine/config/MODEL_CAPABILITIES.json +419 -0
- ciris_engine/config/PRICING_DATA.json +179 -0
- ciris_engine/config/__init__.py +50 -0
- ciris_engine/config/ciris_services.py +113 -0
- ciris_engine/config/model_capabilities.py +388 -0
- ciris_engine/config/pricing_models.py +276 -0
- ciris_engine/constants.py +35 -0
- ciris_engine/data/__init__.py +1 -0
- ciris_engine/data/covenant_1.0b.txt +978 -0
- ciris_engine/gui_static/11steps.svg +107 -0
- ciris_engine/gui_static/2x-schematics.png +0 -0
- ciris_engine/gui_static/404/index.html +1 -0
- ciris_engine/gui_static/404.html +1 -0
- ciris_engine/gui_static/_next/static/0edhkwDxd5UccTsCmtaBi/_buildManifest.js +1 -0
- ciris_engine/gui_static/_next/static/0edhkwDxd5UccTsCmtaBi/_ssgManifest.js +1 -0
- ciris_engine/gui_static/_next/static/U-3xTQao7hc2wnAi-Uekm/_buildManifest.js +1 -0
- ciris_engine/gui_static/_next/static/U-3xTQao7hc2wnAi-Uekm/_ssgManifest.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/3297-60e86ba0f8a7b040.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/3835-2aad4b7f5f8e4643.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/4499-99a0bc47de0b8975.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/4534-af88cd4ba6e99bff.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/4541-84b455f9e0dc4cfe.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/4789-61412711484754bb.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/6539-c6398bc9d7018430.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/704-8e827b26cc8c2d32.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/704-fb45d630f3192c6f.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/8072-de4952a2e6d2b33f.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/8315-b91d03a3949db0af.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/8386-f93a83ccbd789bd9.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/87c73c54-781a7f35148d5433.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/8903-fefea3339a02d41b.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/9090-e66485adf8d9d990.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/_not-found/page-a67d9808462c23b1.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/api-keys/page-2d7ee1583bbbd02e.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/api-keys/page-6a3c2bae6fe92b7b.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/consent/page-2ed3a035136bc4e8.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/consent/page-b2f5c91844a32422.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/page-25b90f89af3ea58c.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/page-b65d16c94ecaf69c.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/privacy/page-675b6d05c8f9184f.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/privacy/page-cbee2e1c8ab52145.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/settings/page-0f44da06697cf9f0.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/settings/page-563420253577edbf.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/adapters/page-1854631018bc32be.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/agents/page-8353752c176a7c70.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/agents/page-f61a529f110a6040.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/api-demo/page-7f19b9d20d39be28.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/api-demo/page-d1063938f249b8bd.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/audit/page-321b6728b8fff0bb.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/audit/page-ebac35ca961a1277.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/billing/page-6f3dc3bd02924f8e.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/billing/page-fa4a469f814c821a.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/comms/page-0d4f734269addd8f.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/comms/page-79227d426050089c.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/config/page-018d21d683b6e5bc.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/config/page-2aa5a5363ca2a371.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/consent/page-198373205fd316e2.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/consent/page-f2ca39e7713b13f8.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/dashboard/page-1dd5a196f643c60d.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/dashboard/page-530a04d3abbb8cda.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/docs/page-3193b06d094ab654.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/docs/page-330e996dedb87aba.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/layout-0a70f5fc460298b1.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/layout-21f2f99dd5b336e9.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/login/page-33240e6c6034a49d.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/login/page-68ffab6d54a7fdcd.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/logs/page-8a6167aecc4a475c.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/memory/page-9ca8c5d0056de3ff.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/memory/page-e961226941c18f81.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/page-6fdb065a787a4974.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/page-89f87d431be6064a.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/runtime/page-2e728b9c43aa164d.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/runtime/page-c7dd033dc40a72f0.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/services/page-ae9f0bdf11d01a95.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/services/page-b10feb79ca5d75e5.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/sessions/page-13ebe7ef1c16ae11.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/sessions/page-e6c82b16d617f785.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/setup/page-0beb5f5b5a5c20fc.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/setup/page-2595e729eae30c0e.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/status-dashboard/page-1037c987aecc3653.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/status-dashboard/page-2ffd147f6d3162ff.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/system/page-2c5798d58cafcd91.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/system/page-505b1ba4eceb01c3.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-auth/page-b0cad31d5cb1b2fa.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-auth/page-f3ecd7a8012df230.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-login/page-f35117fdc4105801.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-login/page-fb583a7924114906.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-sdk/page-50f116fd76935563.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-sdk/page-c37d8aa5ba623a44.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/tools/page-429aec7a707777ef.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/tools/page-5f705aad60e0c04e.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/users/page-13476b8b0f3808cc.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/users/page-7e500d154ed5bba4.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/wa/page-cc4a9d8a5cb44d08.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/wa/page-ec3e429efbc79230.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/framework-9d29490f5ba089ba.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/main-1f554952e47a82c4.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/main-app-26fa8aed029082e5.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/main-app-97b0486ef6bcef25.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/pages/_app-6ce685456e616eb2.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/pages/_error-d4bce98d93fe21e7.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/webpack-fcebd240b7f8477d.js +1 -0
- ciris_engine/gui_static/_next/static/css/16b94b1fe0cc6e37.css +3 -0
- ciris_engine/gui_static/_next/static/css/77a24ceaae86deff.css +3 -0
- ciris_engine/gui_static/_next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/747892c23ea88013-s.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/8d697b304b401681-s.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/9610d9e46709d722-s.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/d8298875641ec7d4-s.p.woff2 +0 -0
- ciris_engine/gui_static/account/api-keys/index.html +1 -0
- ciris_engine/gui_static/account/api-keys/index.txt +27 -0
- ciris_engine/gui_static/account/consent/index.html +1 -0
- ciris_engine/gui_static/account/consent/index.txt +27 -0
- ciris_engine/gui_static/account/index.html +1 -0
- ciris_engine/gui_static/account/index.txt +27 -0
- ciris_engine/gui_static/account/privacy/index.html +1 -0
- ciris_engine/gui_static/account/privacy/index.txt +27 -0
- ciris_engine/gui_static/account/settings/index.html +1 -0
- ciris_engine/gui_static/account/settings/index.txt +27 -0
- ciris_engine/gui_static/adapters/index.html +1 -0
- ciris_engine/gui_static/adapters/index.txt +27 -0
- ciris_engine/gui_static/agents/index.html +1 -0
- ciris_engine/gui_static/agents/index.txt +27 -0
- ciris_engine/gui_static/andrew-roberts-euBRXcx57T4-unsplash.jpg +0 -0
- ciris_engine/gui_static/api-demo/index.html +1 -0
- ciris_engine/gui_static/api-demo/index.txt +27 -0
- ciris_engine/gui_static/audit/index.html +1 -0
- ciris_engine/gui_static/audit/index.txt +27 -0
- ciris_engine/gui_static/billing/index.html +1 -0
- ciris_engine/gui_static/billing/index.txt +27 -0
- ciris_engine/gui_static/blurryinfo.png +0 -0
- ciris_engine/gui_static/chip-vincent-PkQDwfl9Flc-unsplash.jpg +0 -0
- ciris_engine/gui_static/ciris-architecture.svg +338 -0
- ciris_engine/gui_static/comms/index.html +1 -0
- ciris_engine/gui_static/comms/index.txt +27 -0
- ciris_engine/gui_static/config/index.html +1 -0
- ciris_engine/gui_static/config/index.txt +27 -0
- ciris_engine/gui_static/consent/index.html +1 -0
- ciris_engine/gui_static/consent/index.txt +27 -0
- ciris_engine/gui_static/dashboard/index.html +1 -0
- ciris_engine/gui_static/dashboard/index.txt +27 -0
- ciris_engine/gui_static/docs/index.html +1 -0
- ciris_engine/gui_static/docs/index.txt +27 -0
- ciris_engine/gui_static/eric.png +0 -0
- ciris_engine/gui_static/file.svg +1 -0
- ciris_engine/gui_static/globe.svg +1 -0
- ciris_engine/gui_static/index.html +1 -0
- ciris_engine/gui_static/index.txt +27 -0
- ciris_engine/gui_static/infogfx-1@2x.png +0 -0
- ciris_engine/gui_static/infogfx-2.png +0 -0
- ciris_engine/gui_static/infogfx-dark-1.png +0 -0
- ciris_engine/gui_static/kelly-vohs-soSTXmIxTDU-unsplash.jpg +0 -0
- ciris_engine/gui_static/login/index.html +1 -0
- ciris_engine/gui_static/login/index.txt +27 -0
- ciris_engine/gui_static/logs/index.html +1 -0
- ciris_engine/gui_static/logs/index.txt +27 -0
- ciris_engine/gui_static/memory/index.html +1 -0
- ciris_engine/gui_static/memory/index.txt +27 -0
- ciris_engine/gui_static/nathan-farrish-ArcTfEoBgzs-unsplash.jpg +0 -0
- ciris_engine/gui_static/next.svg +1 -0
- ciris_engine/gui_static/overview.svg +512 -0
- ciris_engine/gui_static/overview1.svg +407 -0
- ciris_engine/gui_static/overview2.svg +370 -0
- ciris_engine/gui_static/pipeline-visualization.svg +278 -0
- ciris_engine/gui_static/privacy-policy.html +160 -0
- ciris_engine/gui_static/runtime/index.html +8 -0
- ciris_engine/gui_static/runtime/index.txt +27 -0
- ciris_engine/gui_static/services/index.html +1 -0
- ciris_engine/gui_static/services/index.txt +27 -0
- ciris_engine/gui_static/sessions/index.html +1 -0
- ciris_engine/gui_static/sessions/index.txt +27 -0
- ciris_engine/gui_static/setup/index.html +1 -0
- ciris_engine/gui_static/setup/index.txt +27 -0
- ciris_engine/gui_static/status-dashboard/index.html +1 -0
- ciris_engine/gui_static/status-dashboard/index.txt +27 -0
- ciris_engine/gui_static/system/index.html +1 -0
- ciris_engine/gui_static/system/index.txt +27 -0
- ciris_engine/gui_static/terms-of-service.html +174 -0
- ciris_engine/gui_static/test-auth/index.html +1 -0
- ciris_engine/gui_static/test-auth/index.txt +27 -0
- ciris_engine/gui_static/test-login/index.html +1 -0
- ciris_engine/gui_static/test-login/index.txt +27 -0
- ciris_engine/gui_static/test-sdk/index.html +1 -0
- ciris_engine/gui_static/test-sdk/index.txt +27 -0
- ciris_engine/gui_static/tools/index.html +1 -0
- ciris_engine/gui_static/tools/index.txt +27 -0
- ciris_engine/gui_static/users/index.html +1 -0
- ciris_engine/gui_static/users/index.txt +27 -0
- ciris_engine/gui_static/vercel.svg +1 -0
- ciris_engine/gui_static/videos/video1.mp4 +0 -0
- ciris_engine/gui_static/videos/video3.mp4 +0 -0
- ciris_engine/gui_static/wa/index.html +1 -0
- ciris_engine/gui_static/wa/index.txt +27 -0
- ciris_engine/gui_static/window.svg +1 -0
- ciris_engine/logic/__init__.py +8 -0
- ciris_engine/logic/adapters/__init__.py +74 -0
- ciris_engine/logic/adapters/api/__init__.py +5 -0
- ciris_engine/logic/adapters/api/adapter.py +1037 -0
- ciris_engine/logic/adapters/api/api_communication.py +370 -0
- ciris_engine/logic/adapters/api/api_document.py +330 -0
- ciris_engine/logic/adapters/api/api_observer.py +24 -0
- ciris_engine/logic/adapters/api/api_runtime_control.py +388 -0
- ciris_engine/logic/adapters/api/api_tools.py +299 -0
- ciris_engine/logic/adapters/api/api_vision.py +215 -0
- ciris_engine/logic/adapters/api/app.py +272 -0
- ciris_engine/logic/adapters/api/auth.py +159 -0
- ciris_engine/logic/adapters/api/config.py +101 -0
- ciris_engine/logic/adapters/api/constants.py +55 -0
- ciris_engine/logic/adapters/api/dependencies/__init__.py +1 -0
- ciris_engine/logic/adapters/api/dependencies/auth.py +260 -0
- ciris_engine/logic/adapters/api/endpoints/__init__.py +1 -0
- ciris_engine/logic/adapters/api/endpoints/emergency.py +86 -0
- ciris_engine/logic/adapters/api/middleware/__init__.py +1 -0
- ciris_engine/logic/adapters/api/middleware/rate_limiter.py +302 -0
- ciris_engine/logic/adapters/api/models.py +29 -0
- ciris_engine/logic/adapters/api/routes/__init__.py +52 -0
- ciris_engine/logic/adapters/api/routes/agent.py +1762 -0
- ciris_engine/logic/adapters/api/routes/audit.py +707 -0
- ciris_engine/logic/adapters/api/routes/auth.py +1745 -0
- ciris_engine/logic/adapters/api/routes/billing.py +895 -0
- ciris_engine/logic/adapters/api/routes/config.py +329 -0
- ciris_engine/logic/adapters/api/routes/connectors.py +534 -0
- ciris_engine/logic/adapters/api/routes/consent.py +637 -0
- ciris_engine/logic/adapters/api/routes/dsar.py +637 -0
- ciris_engine/logic/adapters/api/routes/dsar_multi_source.py +484 -0
- ciris_engine/logic/adapters/api/routes/emergency.py +302 -0
- ciris_engine/logic/adapters/api/routes/memory.py +733 -0
- ciris_engine/logic/adapters/api/routes/memory_filters.py +230 -0
- ciris_engine/logic/adapters/api/routes/memory_models.py +112 -0
- ciris_engine/logic/adapters/api/routes/memory_queries.py +236 -0
- ciris_engine/logic/adapters/api/routes/memory_query_helpers.py +394 -0
- ciris_engine/logic/adapters/api/routes/memory_visualization.py +359 -0
- ciris_engine/logic/adapters/api/routes/memory_visualization_helpers.py +110 -0
- ciris_engine/logic/adapters/api/routes/partnership.py +541 -0
- ciris_engine/logic/adapters/api/routes/setup.py +1374 -0
- ciris_engine/logic/adapters/api/routes/system.py +3049 -0
- ciris_engine/logic/adapters/api/routes/system_extensions.py +952 -0
- ciris_engine/logic/adapters/api/routes/telemetry.py +1987 -0
- ciris_engine/logic/adapters/api/routes/telemetry_converters.py +141 -0
- ciris_engine/logic/adapters/api/routes/telemetry_helpers.py +111 -0
- ciris_engine/logic/adapters/api/routes/telemetry_logs_reader.py +280 -0
- ciris_engine/logic/adapters/api/routes/telemetry_metrics.py +131 -0
- ciris_engine/logic/adapters/api/routes/telemetry_models.py +190 -0
- ciris_engine/logic/adapters/api/routes/telemetry_otlp.py +878 -0
- ciris_engine/logic/adapters/api/routes/telemetry_resource_helpers.py +191 -0
- ciris_engine/logic/adapters/api/routes/tickets.py +541 -0
- ciris_engine/logic/adapters/api/routes/tools.py +556 -0
- ciris_engine/logic/adapters/api/routes/transparency.py +281 -0
- ciris_engine/logic/adapters/api/routes/users.py +981 -0
- ciris_engine/logic/adapters/api/routes/verification.py +373 -0
- ciris_engine/logic/adapters/api/routes/wa.py +369 -0
- ciris_engine/logic/adapters/api/service_configuration.py +177 -0
- ciris_engine/logic/adapters/api/services/__init__.py +1 -0
- ciris_engine/logic/adapters/api/services/auth_service.py +1417 -0
- ciris_engine/logic/adapters/api/services/oauth_security.py +68 -0
- ciris_engine/logic/adapters/base.py +141 -0
- ciris_engine/logic/adapters/base_adapter.py +73 -0
- ciris_engine/logic/adapters/base_observer.py +1141 -0
- ciris_engine/logic/adapters/base_vision.py +312 -0
- ciris_engine/logic/adapters/cirisnode_client.py +307 -0
- ciris_engine/logic/adapters/cli/__init__.py +3 -0
- ciris_engine/logic/adapters/cli/adapter.py +207 -0
- ciris_engine/logic/adapters/cli/cli_adapter.py +902 -0
- ciris_engine/logic/adapters/cli/cli_observer.py +268 -0
- ciris_engine/logic/adapters/cli/cli_tools.py +427 -0
- ciris_engine/logic/adapters/cli/cli_wa_service.py +134 -0
- ciris_engine/logic/adapters/cli/config.py +73 -0
- ciris_engine/logic/adapters/discord/__init__.py +3 -0
- ciris_engine/logic/adapters/discord/adapter.py +783 -0
- ciris_engine/logic/adapters/discord/ciris_discord_client.py +159 -0
- ciris_engine/logic/adapters/discord/config.py +177 -0
- ciris_engine/logic/adapters/discord/constants.py +185 -0
- ciris_engine/logic/adapters/discord/discord-stubs.pyi +50 -0
- ciris_engine/logic/adapters/discord/discord_adapter.py +1584 -0
- ciris_engine/logic/adapters/discord/discord_audit.py +150 -0
- ciris_engine/logic/adapters/discord/discord_channel_manager.py +351 -0
- ciris_engine/logic/adapters/discord/discord_connection_manager.py +313 -0
- ciris_engine/logic/adapters/discord/discord_embed_formatter.py +369 -0
- ciris_engine/logic/adapters/discord/discord_error_classifier.py +302 -0
- ciris_engine/logic/adapters/discord/discord_error_handler.py +316 -0
- ciris_engine/logic/adapters/discord/discord_guidance_handler.py +460 -0
- ciris_engine/logic/adapters/discord/discord_message_handler.py +207 -0
- ciris_engine/logic/adapters/discord/discord_observer.py +670 -0
- ciris_engine/logic/adapters/discord/discord_rate_limiter.py +249 -0
- ciris_engine/logic/adapters/discord/discord_reaction_handler.py +278 -0
- ciris_engine/logic/adapters/discord/discord_tool_handler.py +465 -0
- ciris_engine/logic/adapters/discord/discord_tool_service.py +790 -0
- ciris_engine/logic/adapters/discord/discord_tools.py +90 -0
- ciris_engine/logic/adapters/discord/discord_vision_helper.py +148 -0
- ciris_engine/logic/adapters/discord/py.typed +0 -0
- ciris_engine/logic/adapters/document_parser.py +320 -0
- ciris_engine/logic/audit/__init__.py +10 -0
- ciris_engine/logic/audit/hash_chain.py +313 -0
- ciris_engine/logic/audit/signature_manager.py +352 -0
- ciris_engine/logic/audit/verifier.py +408 -0
- ciris_engine/logic/buses/__init__.py +21 -0
- ciris_engine/logic/buses/base_bus.py +178 -0
- ciris_engine/logic/buses/bus_manager.py +121 -0
- ciris_engine/logic/buses/communication_bus.py +387 -0
- ciris_engine/logic/buses/llm_bus.py +722 -0
- ciris_engine/logic/buses/memory_bus.py +577 -0
- ciris_engine/logic/buses/prohibitions.py +502 -0
- ciris_engine/logic/buses/runtime_control_bus.py +539 -0
- ciris_engine/logic/buses/tool_bus.py +482 -0
- ciris_engine/logic/buses/wise_bus.py +684 -0
- ciris_engine/logic/config/__init__.py +25 -0
- ciris_engine/logic/config/bootstrap.py +255 -0
- ciris_engine/logic/config/config_accessor.py +202 -0
- ciris_engine/logic/config/db_paths.py +194 -0
- ciris_engine/logic/config/env_utils.py +39 -0
- ciris_engine/logic/conscience/__init__.py +16 -0
- ciris_engine/logic/conscience/build_deferral_package.py +0 -0
- ciris_engine/logic/conscience/core.py +688 -0
- ciris_engine/logic/conscience/interface.py +33 -0
- ciris_engine/logic/conscience/registry.py +76 -0
- ciris_engine/logic/conscience/thought_depth_guardrail.py +231 -0
- ciris_engine/logic/conscience/updated_status_conscience.py +156 -0
- ciris_engine/logic/context/__init__.py +10 -0
- ciris_engine/logic/context/batch_context.py +550 -0
- ciris_engine/logic/context/builder.py +149 -0
- ciris_engine/logic/context/channel_resolution.py +136 -0
- ciris_engine/logic/context/secrets_snapshot.py +52 -0
- ciris_engine/logic/context/system_snapshot.py +116 -0
- ciris_engine/logic/context/system_snapshot_helpers.py +1651 -0
- ciris_engine/logic/covenant/__init__.py +33 -0
- ciris_engine/logic/covenant/executor.py +303 -0
- ciris_engine/logic/covenant/extractor.py +382 -0
- ciris_engine/logic/covenant/handler.py +241 -0
- ciris_engine/logic/covenant/verifier.py +383 -0
- ciris_engine/logic/dma/__init__.py +15 -0
- ciris_engine/logic/dma/action_selection/__init__.py +11 -0
- ciris_engine/logic/dma/action_selection/action_instruction_generator.py +444 -0
- ciris_engine/logic/dma/action_selection/context_builder.py +508 -0
- ciris_engine/logic/dma/action_selection/faculty_integration.py +193 -0
- ciris_engine/logic/dma/action_selection/special_cases.py +132 -0
- ciris_engine/logic/dma/action_selection_pdma.py +365 -0
- ciris_engine/logic/dma/base_dma.py +335 -0
- ciris_engine/logic/dma/csdma.py +239 -0
- ciris_engine/logic/dma/dma_executor.py +575 -0
- ciris_engine/logic/dma/dsdma_base.py +410 -0
- ciris_engine/logic/dma/exceptions.py +4 -0
- ciris_engine/logic/dma/factory.py +150 -0
- ciris_engine/logic/dma/pdma.py +120 -0
- ciris_engine/logic/dma/prompt_loader.py +189 -0
- ciris_engine/logic/dma/prompts/action_selection_pdma.yml +58 -0
- ciris_engine/logic/dma/prompts/csdma_common_sense.yml +28 -0
- ciris_engine/logic/dma/prompts/dsdma_base.yml +17 -0
- ciris_engine/logic/dma/prompts/pdma_ethical.yml +42 -0
- ciris_engine/logic/formatters/__init__.py +26 -0
- ciris_engine/logic/formatters/crisis_resources.py +80 -0
- ciris_engine/logic/formatters/escalation.py +21 -0
- ciris_engine/logic/formatters/identity.py +224 -0
- ciris_engine/logic/formatters/prompt_blocks.py +64 -0
- ciris_engine/logic/formatters/system_snapshot.py +193 -0
- ciris_engine/logic/formatters/user_profiles.py +108 -0
- ciris_engine/logic/handlers/__init__.py +1 -0
- ciris_engine/logic/handlers/control/__init__.py +1 -0
- ciris_engine/logic/handlers/control/defer_handler.py +195 -0
- ciris_engine/logic/handlers/control/ponder_handler.py +154 -0
- ciris_engine/logic/handlers/control/reject_handler.py +81 -0
- ciris_engine/logic/handlers/external/__init__.py +1 -0
- ciris_engine/logic/handlers/external/observe_handler.py +154 -0
- ciris_engine/logic/handlers/external/speak_handler.py +250 -0
- ciris_engine/logic/handlers/external/tool_handler.py +148 -0
- ciris_engine/logic/handlers/memory/__init__.py +1 -0
- ciris_engine/logic/handlers/memory/forget_handler.py +107 -0
- ciris_engine/logic/handlers/memory/memorize_handler.py +391 -0
- ciris_engine/logic/handlers/memory/recall_handler.py +213 -0
- ciris_engine/logic/handlers/terminal/__init__.py +1 -0
- ciris_engine/logic/handlers/terminal/task_complete_handler.py +299 -0
- ciris_engine/logic/infrastructure/__init__.py +1 -0
- ciris_engine/logic/infrastructure/handlers/__init__.py +8 -0
- ciris_engine/logic/infrastructure/handlers/action_dispatcher.py +382 -0
- ciris_engine/logic/infrastructure/handlers/base_handler.py +450 -0
- ciris_engine/logic/infrastructure/handlers/exceptions.py +2 -0
- ciris_engine/logic/infrastructure/handlers/handler_registry.py +59 -0
- ciris_engine/logic/infrastructure/handlers/helpers.py +55 -0
- ciris_engine/logic/infrastructure/step_streaming.py +149 -0
- ciris_engine/logic/infrastructure/sub_services/__init__.py +1 -0
- ciris_engine/logic/infrastructure/sub_services/identity_variance_monitor.py +1035 -0
- ciris_engine/logic/infrastructure/sub_services/pattern_analysis_loop.py +758 -0
- ciris_engine/logic/infrastructure/sub_services/wa_cli_bootstrap.py +229 -0
- ciris_engine/logic/infrastructure/sub_services/wa_cli_display.py +176 -0
- ciris_engine/logic/infrastructure/sub_services/wa_cli_oauth.py +404 -0
- ciris_engine/logic/infrastructure/sub_services/wa_cli_wizard.py +181 -0
- ciris_engine/logic/persistence/__init__.py +130 -0
- ciris_engine/logic/persistence/analytics.py +97 -0
- ciris_engine/logic/persistence/db/__init__.py +28 -0
- ciris_engine/logic/persistence/db/core.py +520 -0
- ciris_engine/logic/persistence/db/dialect.py +380 -0
- ciris_engine/logic/persistence/db/execution_helpers.py +216 -0
- ciris_engine/logic/persistence/db/migration_runner.py +191 -0
- ciris_engine/logic/persistence/db/operations.py +313 -0
- ciris_engine/logic/persistence/db/query_builder.py +232 -0
- ciris_engine/logic/persistence/db/retry.py +154 -0
- ciris_engine/logic/persistence/db/setup.py +18 -0
- ciris_engine/logic/persistence/migrations/postgres/001_initial_schema.sql +4 -0
- ciris_engine/logic/persistence/migrations/postgres/002_add_retry_status.sql +3 -0
- ciris_engine/logic/persistence/migrations/postgres/003_add_task_update_tracking.sql +8 -0
- ciris_engine/logic/persistence/migrations/postgres/004_add_occurrence_id.sql +54 -0
- ciris_engine/logic/persistence/migrations/postgres/005_add_consolidation_locks.sql +22 -0
- ciris_engine/logic/persistence/migrations/postgres/006_add_correlation_id_unique_index.sql +16 -0
- ciris_engine/logic/persistence/migrations/postgres/007_add_dsar_tickets.sql +39 -0
- ciris_engine/logic/persistence/migrations/postgres/008_rename_to_tickets_add_sop.sql +123 -0
- ciris_engine/logic/persistence/migrations/postgres/009_add_ticket_status_columns.sql +39 -0
- ciris_engine/logic/persistence/migrations/postgres/010_add_images_to_tasks.sql +5 -0
- ciris_engine/logic/persistence/migrations/sqlite/001_initial_schema.sql +357 -0
- ciris_engine/logic/persistence/migrations/sqlite/002_add_retry_status.sql +3 -0
- ciris_engine/logic/persistence/migrations/sqlite/003_add_task_update_tracking.sql +8 -0
- ciris_engine/logic/persistence/migrations/sqlite/004_add_occurrence_id.sql +45 -0
- ciris_engine/logic/persistence/migrations/sqlite/005_add_consolidation_locks.sql +22 -0
- ciris_engine/logic/persistence/migrations/sqlite/006_add_correlation_id_unique_index.sql +16 -0
- ciris_engine/logic/persistence/migrations/sqlite/007_add_dsar_tickets.sql +39 -0
- ciris_engine/logic/persistence/migrations/sqlite/008_rename_to_tickets_add_sop.sql +120 -0
- ciris_engine/logic/persistence/migrations/sqlite/009_add_ticket_status_columns.sql +129 -0
- ciris_engine/logic/persistence/migrations/sqlite/010_add_images_to_tasks.sql +17 -0
- ciris_engine/logic/persistence/models/__init__.py +141 -0
- ciris_engine/logic/persistence/models/correlations.py +881 -0
- ciris_engine/logic/persistence/models/deferral.py +68 -0
- ciris_engine/logic/persistence/models/dsar.py +286 -0
- ciris_engine/logic/persistence/models/graph.py +362 -0
- ciris_engine/logic/persistence/models/identity.py +264 -0
- ciris_engine/logic/persistence/models/queue_status.py +139 -0
- ciris_engine/logic/persistence/models/tasks.py +1043 -0
- ciris_engine/logic/persistence/models/thoughts.py +400 -0
- ciris_engine/logic/persistence/models/tickets.py +518 -0
- ciris_engine/logic/persistence/stores/__init__.py +13 -0
- ciris_engine/logic/persistence/stores/auth_helpers.py +117 -0
- ciris_engine/logic/persistence/stores/authentication_store.py +414 -0
- ciris_engine/logic/persistence/utils.py +212 -0
- ciris_engine/logic/processors/__init__.py +30 -0
- ciris_engine/logic/processors/core/__init__.py +1 -0
- ciris_engine/logic/processors/core/base_processor.py +280 -0
- ciris_engine/logic/processors/core/main_processor.py +1777 -0
- ciris_engine/logic/processors/core/step_decorators.py +1583 -0
- ciris_engine/logic/processors/core/thought_processor/__init__.py +20 -0
- ciris_engine/logic/processors/core/thought_processor/action_execution.py +49 -0
- ciris_engine/logic/processors/core/thought_processor/conscience_execution.py +382 -0
- ciris_engine/logic/processors/core/thought_processor/finalize_action.py +66 -0
- ciris_engine/logic/processors/core/thought_processor/gather_context.py +120 -0
- ciris_engine/logic/processors/core/thought_processor/main.py +920 -0
- ciris_engine/logic/processors/core/thought_processor/perform_aspdma.py +86 -0
- ciris_engine/logic/processors/core/thought_processor/perform_dmas.py +106 -0
- ciris_engine/logic/processors/core/thought_processor/recursive_processing.py +237 -0
- ciris_engine/logic/processors/core/thought_processor/round_complete.py +52 -0
- ciris_engine/logic/processors/core/thought_processor/start_round.py +64 -0
- ciris_engine/logic/processors/exceptions.py +59 -0
- ciris_engine/logic/processors/states/__init__.py +1 -0
- ciris_engine/logic/processors/states/dream_processor.py +1381 -0
- ciris_engine/logic/processors/states/play_processor.py +141 -0
- ciris_engine/logic/processors/states/shutdown_processor.py +623 -0
- ciris_engine/logic/processors/states/solitude_processor.py +305 -0
- ciris_engine/logic/processors/states/wakeup_processor.py +802 -0
- ciris_engine/logic/processors/states/work_processor.py +742 -0
- ciris_engine/logic/processors/support/__init__.py +1 -0
- ciris_engine/logic/processors/support/dma_orchestrator.py +336 -0
- ciris_engine/logic/processors/support/processing_queue.py +133 -0
- ciris_engine/logic/processors/support/shutdown_condition_evaluator.py +294 -0
- ciris_engine/logic/processors/support/state_manager.py +358 -0
- ciris_engine/logic/processors/support/task_manager.py +303 -0
- ciris_engine/logic/processors/support/thought_escalation.py +116 -0
- ciris_engine/logic/processors/support/thought_manager.py +328 -0
- ciris_engine/logic/processors/support/thought_manager_enhanced.py +105 -0
- ciris_engine/logic/registries/__init__.py +34 -0
- ciris_engine/logic/registries/base.py +653 -0
- ciris_engine/logic/registries/circuit_breaker.py +275 -0
- ciris_engine/logic/registries/typed_registries.py +184 -0
- ciris_engine/logic/runtime/__init__.py +7 -0
- ciris_engine/logic/runtime/adapter_loader.py +261 -0
- ciris_engine/logic/runtime/adapter_manager.py +1053 -0
- ciris_engine/logic/runtime/ciris_runtime.py +2342 -0
- ciris_engine/logic/runtime/ciris_runtime_helpers.py +923 -0
- ciris_engine/logic/runtime/component_builder.py +361 -0
- ciris_engine/logic/runtime/identity_manager.py +219 -0
- ciris_engine/logic/runtime/module_loader.py +207 -0
- ciris_engine/logic/runtime/prevent_sideeffects.py +30 -0
- ciris_engine/logic/runtime/runtime_interface.py +23 -0
- ciris_engine/logic/runtime/service_initializer.py +1623 -0
- ciris_engine/logic/secrets/__init__.py +30 -0
- ciris_engine/logic/secrets/encryption.py +175 -0
- ciris_engine/logic/secrets/filter.py +295 -0
- ciris_engine/logic/secrets/service.py +652 -0
- ciris_engine/logic/secrets/store.py +669 -0
- ciris_engine/logic/services/__init__.py +1 -0
- ciris_engine/logic/services/adaptation/__init__.py +3 -0
- ciris_engine/logic/services/base_graph_service.py +142 -0
- ciris_engine/logic/services/base_infrastructure_service.py +69 -0
- ciris_engine/logic/services/base_scheduled_service.py +136 -0
- ciris_engine/logic/services/base_service.py +247 -0
- ciris_engine/logic/services/governance/__init__.py +3 -0
- ciris_engine/logic/services/governance/adaptive_filter/__init__.py +14 -0
- ciris_engine/logic/services/governance/adaptive_filter/service.py +818 -0
- ciris_engine/logic/services/governance/consent/__init__.py +53 -0
- ciris_engine/logic/services/governance/consent/air.py +403 -0
- ciris_engine/logic/services/governance/consent/decay.py +324 -0
- ciris_engine/logic/services/governance/consent/dsar_automation.py +589 -0
- ciris_engine/logic/services/governance/consent/exceptions.py +106 -0
- ciris_engine/logic/services/governance/consent/metrics.py +270 -0
- ciris_engine/logic/services/governance/consent/partnership.py +533 -0
- ciris_engine/logic/services/governance/consent/service.py +1256 -0
- ciris_engine/logic/services/governance/dsar/__init__.py +29 -0
- ciris_engine/logic/services/governance/dsar/orchestrator.py +977 -0
- ciris_engine/logic/services/governance/dsar/schemas.py +141 -0
- ciris_engine/logic/services/governance/dsar/signature_service.py +283 -0
- ciris_engine/logic/services/governance/self_observation/__init__.py +20 -0
- ciris_engine/logic/services/governance/self_observation/service.py +1153 -0
- ciris_engine/logic/services/governance/visibility/__init__.py +17 -0
- ciris_engine/logic/services/governance/visibility/service.py +512 -0
- ciris_engine/logic/services/governance/wise_authority/__init__.py +15 -0
- ciris_engine/logic/services/governance/wise_authority/service.py +827 -0
- ciris_engine/logic/services/graph/__init__.py +5 -0
- ciris_engine/logic/services/graph/audit_service/__init__.py +5 -0
- ciris_engine/logic/services/graph/audit_service/service.py +1675 -0
- ciris_engine/logic/services/graph/base.py +208 -0
- ciris_engine/logic/services/graph/config_service/__init__.py +5 -0
- ciris_engine/logic/services/graph/config_service/service.py +372 -0
- ciris_engine/logic/services/graph/incident_service/__init__.py +5 -0
- ciris_engine/logic/services/graph/incident_service/service.py +803 -0
- ciris_engine/logic/services/graph/memory_service.py +1120 -0
- ciris_engine/logic/services/graph/telemetry_service/__init__.py +5 -0
- ciris_engine/logic/services/graph/telemetry_service/exceptions.py +104 -0
- ciris_engine/logic/services/graph/telemetry_service/helpers.py +1337 -0
- ciris_engine/logic/services/graph/telemetry_service/service.py +2429 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/__init__.py +17 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/aggregation_helpers.py +355 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/cleanup_helpers.py +438 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/compressor.py +260 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/__init__.py +27 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/audit.py +326 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/conversation.py +291 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/memory.py +197 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/metrics.py +251 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/task.py +257 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/trace.py +363 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/data_converter.py +545 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/date_calculation_helpers.py +193 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/db_query_helpers.py +296 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/edge_helpers.py +92 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/edge_manager.py +896 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/extensive_helpers.py +322 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/period_manager.py +152 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/profound_helpers.py +277 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/query_manager.py +812 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/service.py +1692 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/sql_builders.py +363 -0
- ciris_engine/logic/services/infrastructure/__init__.py +1 -0
- ciris_engine/logic/services/infrastructure/authentication/__init__.py +5 -0
- ciris_engine/logic/services/infrastructure/authentication/service.py +1634 -0
- ciris_engine/logic/services/infrastructure/database_maintenance/__init__.py +15 -0
- ciris_engine/logic/services/infrastructure/database_maintenance/service.py +764 -0
- ciris_engine/logic/services/infrastructure/resource_monitor/__init__.py +7 -0
- ciris_engine/logic/services/infrastructure/resource_monitor/ciris_billing_provider.py +755 -0
- ciris_engine/logic/services/infrastructure/resource_monitor/service.py +409 -0
- ciris_engine/logic/services/infrastructure/resource_monitor/simple_credit_provider.py +129 -0
- ciris_engine/logic/services/lifecycle/__init__.py +3 -0
- ciris_engine/logic/services/lifecycle/initialization/__init__.py +10 -0
- ciris_engine/logic/services/lifecycle/initialization/service.py +312 -0
- ciris_engine/logic/services/lifecycle/scheduler/__init__.py +5 -0
- ciris_engine/logic/services/lifecycle/scheduler/service.py +607 -0
- ciris_engine/logic/services/lifecycle/shutdown/__init__.py +9 -0
- ciris_engine/logic/services/lifecycle/shutdown/service.py +378 -0
- ciris_engine/logic/services/lifecycle/time/__init__.py +15 -0
- ciris_engine/logic/services/lifecycle/time/service.py +259 -0
- ciris_engine/logic/services/memory_service/__init__.py +8 -0
- ciris_engine/logic/services/mixins/__init__.py +13 -0
- ciris_engine/logic/services/mixins/example_usage.py +200 -0
- ciris_engine/logic/services/mixins/request_metrics.py +179 -0
- ciris_engine/logic/services/runtime/__init__.py +3 -0
- ciris_engine/logic/services/runtime/adapter_configuration/__init__.py +16 -0
- ciris_engine/logic/services/runtime/adapter_configuration/service.py +674 -0
- ciris_engine/logic/services/runtime/adapter_configuration/session.py +67 -0
- ciris_engine/logic/services/runtime/control_service/__init__.py +5 -0
- ciris_engine/logic/services/runtime/control_service/service.py +2269 -0
- ciris_engine/logic/services/runtime/llm_service/__init__.py +14 -0
- ciris_engine/logic/services/runtime/llm_service/pricing_calculator.py +279 -0
- ciris_engine/logic/services/runtime/llm_service/service.py +930 -0
- ciris_engine/logic/services/tools/__init__.py +5 -0
- ciris_engine/logic/services/tools/core_tool_service/__init__.py +8 -0
- ciris_engine/logic/services/tools/core_tool_service/service.py +852 -0
- ciris_engine/logic/setup/__init__.py +1 -0
- ciris_engine/logic/setup/first_run.py +250 -0
- ciris_engine/logic/setup/wizard.py +327 -0
- ciris_engine/logic/telemetry/__init__.py +46 -0
- ciris_engine/logic/telemetry/core.py +239 -0
- ciris_engine/logic/telemetry/hot_cold_config.py +133 -0
- ciris_engine/logic/telemetry/log_collector.py +190 -0
- ciris_engine/logic/telemetry/resource_monitor.py +7 -0
- ciris_engine/logic/telemetry/security.py +79 -0
- ciris_engine/logic/utils/__init__.py +18 -0
- ciris_engine/logic/utils/channel_utils.py +75 -0
- ciris_engine/logic/utils/consent/__init__.py +1 -0
- ciris_engine/logic/utils/consent/partnership_utils.py +172 -0
- ciris_engine/logic/utils/constants.py +92 -0
- ciris_engine/logic/utils/context_utils.py +145 -0
- ciris_engine/logic/utils/directory_setup.py +533 -0
- ciris_engine/logic/utils/graphql_context_provider.py +152 -0
- ciris_engine/logic/utils/identity_resolution.py +843 -0
- ciris_engine/logic/utils/incident_capture_handler.py +303 -0
- ciris_engine/logic/utils/initialization_manager.py +74 -0
- ciris_engine/logic/utils/jsondict_helpers.py +290 -0
- ciris_engine/logic/utils/log_sanitizer.py +97 -0
- ciris_engine/logic/utils/logging_config.py +151 -0
- ciris_engine/logic/utils/observability_decorators.py +544 -0
- ciris_engine/logic/utils/occurrence_utils.py +155 -0
- ciris_engine/logic/utils/path_resolution.py +281 -0
- ciris_engine/logic/utils/platform_detection.py +286 -0
- ciris_engine/logic/utils/privacy.py +266 -0
- ciris_engine/logic/utils/profile_loader.py +124 -0
- ciris_engine/logic/utils/profile_manager.py +16 -0
- ciris_engine/logic/utils/runtime_utils.py +69 -0
- ciris_engine/logic/utils/shutdown_manager.py +107 -0
- ciris_engine/logic/utils/task_formatters.py +60 -0
- ciris_engine/logic/utils/task_thought_factory.py +404 -0
- ciris_engine/logic/utils/thought_utils.py +54 -0
- ciris_engine/logic/utils/user_utils.py +70 -0
- ciris_engine/protocols/__init__.py +0 -0
- ciris_engine/protocols/adapters/__init__.py +35 -0
- ciris_engine/protocols/adapters/base.py +149 -0
- ciris_engine/protocols/adapters/configurable.py +265 -0
- ciris_engine/protocols/adapters/message.py +90 -0
- ciris_engine/protocols/audit/__init__.py +1 -0
- ciris_engine/protocols/buses/__init__.py +1 -0
- ciris_engine/protocols/config/__init__.py +1 -0
- ciris_engine/protocols/conscience/__init__.py +1 -0
- ciris_engine/protocols/consent.py +88 -0
- ciris_engine/protocols/context/__init__.py +1 -0
- ciris_engine/protocols/data/__init__.py +1 -0
- ciris_engine/protocols/dma/__init__.py +1 -0
- ciris_engine/protocols/dma/base.py +107 -0
- ciris_engine/protocols/faculties.py +34 -0
- ciris_engine/protocols/formatters/__init__.py +1 -0
- ciris_engine/protocols/handlers/__init__.py +1 -0
- ciris_engine/protocols/infrastructure/__init__.py +25 -0
- ciris_engine/protocols/infrastructure/base.py +377 -0
- ciris_engine/protocols/persistence/__init__.py +1 -0
- ciris_engine/protocols/pipeline_control.py +609 -0
- ciris_engine/protocols/processors/__init__.py +19 -0
- ciris_engine/protocols/processors/agent.py +299 -0
- ciris_engine/protocols/processors/base.py +130 -0
- ciris_engine/protocols/processors/orchestration.py +62 -0
- ciris_engine/protocols/registries/__init__.py +1 -0
- ciris_engine/protocols/runtime/__init__.py +1 -0
- ciris_engine/protocols/runtime/base.py +163 -0
- ciris_engine/protocols/secrets/__init__.py +1 -0
- ciris_engine/protocols/services/__init__.py +80 -0
- ciris_engine/protocols/services/adaptation/__init__.py +7 -0
- ciris_engine/protocols/services/adaptation/self_observation.py +265 -0
- ciris_engine/protocols/services/governance/__init__.py +20 -0
- ciris_engine/protocols/services/governance/communication.py +58 -0
- ciris_engine/protocols/services/governance/filter.py +56 -0
- ciris_engine/protocols/services/governance/visibility.py +32 -0
- ciris_engine/protocols/services/governance/wa_auth.py +192 -0
- ciris_engine/protocols/services/governance/wise_authority.py +75 -0
- ciris_engine/protocols/services/graph/__init__.py +19 -0
- ciris_engine/protocols/services/graph/audit.py +92 -0
- ciris_engine/protocols/services/graph/config.py +54 -0
- ciris_engine/protocols/services/graph/incident_management.py +103 -0
- ciris_engine/protocols/services/graph/memory.py +110 -0
- ciris_engine/protocols/services/graph/telemetry.py +51 -0
- ciris_engine/protocols/services/graph/tsdb_consolidation.py +87 -0
- ciris_engine/protocols/services/infrastructure/__init__.py +11 -0
- ciris_engine/protocols/services/infrastructure/authentication.py +159 -0
- ciris_engine/protocols/services/infrastructure/credit_gate.py +46 -0
- ciris_engine/protocols/services/infrastructure/database_maintenance.py +25 -0
- ciris_engine/protocols/services/infrastructure/resource_monitor.py +83 -0
- ciris_engine/protocols/services/lifecycle/__init__.py +13 -0
- ciris_engine/protocols/services/lifecycle/initialization.py +41 -0
- ciris_engine/protocols/services/lifecycle/scheduler.py +42 -0
- ciris_engine/protocols/services/lifecycle/shutdown.py +50 -0
- ciris_engine/protocols/services/lifecycle/time.py +31 -0
- ciris_engine/protocols/services/runtime/__init__.py +13 -0
- ciris_engine/protocols/services/runtime/llm.py +50 -0
- ciris_engine/protocols/services/runtime/runtime_control.py +193 -0
- ciris_engine/protocols/services/runtime/secrets.py +100 -0
- ciris_engine/protocols/services/runtime/tool.py +123 -0
- ciris_engine/protocols/telemetry/__init__.py +1 -0
- ciris_engine/protocols/utils/__init__.py +1 -0
- ciris_engine/schemas/__init__.py +112 -0
- ciris_engine/schemas/actions/__init__.py +37 -0
- ciris_engine/schemas/actions/parameters.py +137 -0
- ciris_engine/schemas/adapters/__init__.py +13 -0
- ciris_engine/schemas/adapters/cirisnode.py +135 -0
- ciris_engine/schemas/adapters/cli.py +97 -0
- ciris_engine/schemas/adapters/cli_tools.py +98 -0
- ciris_engine/schemas/adapters/discord.py +125 -0
- ciris_engine/schemas/adapters/graphql_core.py +144 -0
- ciris_engine/schemas/adapters/registration.py +47 -0
- ciris_engine/schemas/adapters/runtime_context.py +48 -0
- ciris_engine/schemas/adapters/tool_execution.py +45 -0
- ciris_engine/schemas/adapters/tools.py +96 -0
- ciris_engine/schemas/api/__init__.py +1 -0
- ciris_engine/schemas/api/agent.py +50 -0
- ciris_engine/schemas/api/audit.py +38 -0
- ciris_engine/schemas/api/auth.py +351 -0
- ciris_engine/schemas/api/config_security.py +242 -0
- ciris_engine/schemas/api/emergency.py +111 -0
- ciris_engine/schemas/api/responses.py +72 -0
- ciris_engine/schemas/api/runtime.py +26 -0
- ciris_engine/schemas/api/telemetry.py +109 -0
- ciris_engine/schemas/api/wa.py +90 -0
- ciris_engine/schemas/audit/__init__.py +13 -0
- ciris_engine/schemas/audit/core.py +139 -0
- ciris_engine/schemas/audit/hash_chain.py +58 -0
- ciris_engine/schemas/audit/verification.py +131 -0
- ciris_engine/schemas/buses/__init__.py +1 -0
- ciris_engine/schemas/config/__init__.py +41 -0
- ciris_engine/schemas/config/agent.py +279 -0
- ciris_engine/schemas/config/cognitive_state_behaviors.py +194 -0
- ciris_engine/schemas/config/default_dsar_sops.py +178 -0
- ciris_engine/schemas/config/essential.py +195 -0
- ciris_engine/schemas/config/tickets.py +86 -0
- ciris_engine/schemas/conscience/__init__.py +25 -0
- ciris_engine/schemas/conscience/context.py +34 -0
- ciris_engine/schemas/conscience/core.py +145 -0
- ciris_engine/schemas/conscience/results.py +24 -0
- ciris_engine/schemas/consent/__init__.py +5 -0
- ciris_engine/schemas/consent/core.py +404 -0
- ciris_engine/schemas/context/__init__.py +1 -0
- ciris_engine/schemas/covenant.py +382 -0
- ciris_engine/schemas/data/__init__.py +1 -0
- ciris_engine/schemas/dma/__init__.py +16 -0
- ciris_engine/schemas/dma/core.py +199 -0
- ciris_engine/schemas/dma/faculty.py +192 -0
- ciris_engine/schemas/dma/prompts.py +172 -0
- ciris_engine/schemas/dma/results.py +103 -0
- ciris_engine/schemas/formatters/__init__.py +1 -0
- ciris_engine/schemas/handlers/__init__.py +10 -0
- ciris_engine/schemas/handlers/context.py +119 -0
- ciris_engine/schemas/handlers/contexts.py +100 -0
- ciris_engine/schemas/handlers/core.py +167 -0
- ciris_engine/schemas/handlers/memory_schemas.py +67 -0
- ciris_engine/schemas/handlers/schemas.py +95 -0
- ciris_engine/schemas/identity.py +149 -0
- ciris_engine/schemas/infrastructure/__init__.py +1 -0
- ciris_engine/schemas/infrastructure/base.py +256 -0
- ciris_engine/schemas/infrastructure/behavioral_patterns.py +129 -0
- ciris_engine/schemas/infrastructure/feedback_loop.py +57 -0
- ciris_engine/schemas/infrastructure/identity_variance.py +141 -0
- ciris_engine/schemas/infrastructure/oauth.py +175 -0
- ciris_engine/schemas/infrastructure/wa_cli_wizard.py +54 -0
- ciris_engine/schemas/persistence/__init__.py +34 -0
- ciris_engine/schemas/persistence/core.py +140 -0
- ciris_engine/schemas/persistence/correlations.py +73 -0
- ciris_engine/schemas/persistence/postgres/__init__.py +1 -0
- ciris_engine/schemas/persistence/postgres/tables.py +280 -0
- ciris_engine/schemas/persistence/sqlite/__init__.py +1 -0
- ciris_engine/schemas/persistence/sqlite/tables.py +281 -0
- ciris_engine/schemas/platform.py +149 -0
- ciris_engine/schemas/processors/__init__.py +26 -0
- ciris_engine/schemas/processors/base.py +130 -0
- ciris_engine/schemas/processors/cognitive.py +77 -0
- ciris_engine/schemas/processors/context.py +35 -0
- ciris_engine/schemas/processors/core.py +152 -0
- ciris_engine/schemas/processors/dma.py +105 -0
- ciris_engine/schemas/processors/error.py +122 -0
- ciris_engine/schemas/processors/main.py +109 -0
- ciris_engine/schemas/processors/phase_results.py +21 -0
- ciris_engine/schemas/processors/results.py +99 -0
- ciris_engine/schemas/processors/solitude.py +79 -0
- ciris_engine/schemas/processors/state.py +202 -0
- ciris_engine/schemas/processors/state_example.py +177 -0
- ciris_engine/schemas/processors/states.py +21 -0
- ciris_engine/schemas/processors/status.py +34 -0
- ciris_engine/schemas/registries/__init__.py +1 -0
- ciris_engine/schemas/registries/base.py +66 -0
- ciris_engine/schemas/resources/__init__.py +15 -0
- ciris_engine/schemas/resources/crisis.py +315 -0
- ciris_engine/schemas/runtime/__init__.py +42 -0
- ciris_engine/schemas/runtime/adapter_management.py +186 -0
- ciris_engine/schemas/runtime/api.py +58 -0
- ciris_engine/schemas/runtime/audit.py +50 -0
- ciris_engine/schemas/runtime/bootstrap.py +33 -0
- ciris_engine/schemas/runtime/contexts.py +61 -0
- ciris_engine/schemas/runtime/core.py +161 -0
- ciris_engine/schemas/runtime/enums.py +167 -0
- ciris_engine/schemas/runtime/extended.py +232 -0
- ciris_engine/schemas/runtime/manifest.py +311 -0
- ciris_engine/schemas/runtime/memory.py +60 -0
- ciris_engine/schemas/runtime/messages.py +108 -0
- ciris_engine/schemas/runtime/models.py +156 -0
- ciris_engine/schemas/runtime/processing_context.py +43 -0
- ciris_engine/schemas/runtime/protocols_core.py +96 -0
- ciris_engine/schemas/runtime/resources.py +33 -0
- ciris_engine/schemas/runtime/system_context.py +417 -0
- ciris_engine/schemas/secrets/__init__.py +1 -0
- ciris_engine/schemas/secrets/core.py +267 -0
- ciris_engine/schemas/secrets/service.py +95 -0
- ciris_engine/schemas/services/__init__.py +33 -0
- ciris_engine/schemas/services/audit_summary_node.py +172 -0
- ciris_engine/schemas/services/authority/__init__.py +39 -0
- ciris_engine/schemas/services/authority/jwt.py +158 -0
- ciris_engine/schemas/services/authority/wa_updates.py +138 -0
- ciris_engine/schemas/services/authority/wise_authority.py +163 -0
- ciris_engine/schemas/services/authority_core.py +370 -0
- ciris_engine/schemas/services/capabilities.py +72 -0
- ciris_engine/schemas/services/community_core.py +95 -0
- ciris_engine/schemas/services/context.py +111 -0
- ciris_engine/schemas/services/conversation_summary_node.py +189 -0
- ciris_engine/schemas/services/core/__init__.py +153 -0
- ciris_engine/schemas/services/core/runtime.py +262 -0
- ciris_engine/schemas/services/core/runtime_config.py +117 -0
- ciris_engine/schemas/services/core/secrets.py +65 -0
- ciris_engine/schemas/services/correlation_node.py +179 -0
- ciris_engine/schemas/services/credit_gate.py +92 -0
- ciris_engine/schemas/services/discord_nodes.py +299 -0
- ciris_engine/schemas/services/feedback_core.py +131 -0
- ciris_engine/schemas/services/filters_core.py +270 -0
- ciris_engine/schemas/services/governance.py +26 -0
- ciris_engine/schemas/services/graph/__init__.py +26 -0
- ciris_engine/schemas/services/graph/attributes.py +254 -0
- ciris_engine/schemas/services/graph/audit.py +98 -0
- ciris_engine/schemas/services/graph/consolidation.py +338 -0
- ciris_engine/schemas/services/graph/edge_types.py +43 -0
- ciris_engine/schemas/services/graph/edges.py +88 -0
- ciris_engine/schemas/services/graph/incident.py +312 -0
- ciris_engine/schemas/services/graph/memory.py +84 -0
- ciris_engine/schemas/services/graph/node_data.py +174 -0
- ciris_engine/schemas/services/graph/query_results.py +82 -0
- ciris_engine/schemas/services/graph/telemetry.py +250 -0
- ciris_engine/schemas/services/graph/tsdb_consolidation.py +27 -0
- ciris_engine/schemas/services/graph/tsdb_models.py +107 -0
- ciris_engine/schemas/services/graph_core.py +196 -0
- ciris_engine/schemas/services/graph_typed_nodes.py +194 -0
- ciris_engine/schemas/services/infrastructure/__init__.py +1 -0
- ciris_engine/schemas/services/infrastructure/resource_monitor.py +20 -0
- ciris_engine/schemas/services/lifecycle/__init__.py +9 -0
- ciris_engine/schemas/services/lifecycle/initialization.py +33 -0
- ciris_engine/schemas/services/lifecycle/time.py +50 -0
- ciris_engine/schemas/services/llm.py +187 -0
- ciris_engine/schemas/services/metadata.py +43 -0
- ciris_engine/schemas/services/nodes.py +704 -0
- ciris_engine/schemas/services/operations.py +126 -0
- ciris_engine/schemas/services/requests.py +128 -0
- ciris_engine/schemas/services/resources_core.py +182 -0
- ciris_engine/schemas/services/runtime_control.py +1010 -0
- ciris_engine/schemas/services/shutdown.py +88 -0
- ciris_engine/schemas/services/special/__init__.py +0 -0
- ciris_engine/schemas/services/special/self_observation.py +396 -0
- ciris_engine/schemas/services/trace_summary_node.py +199 -0
- ciris_engine/schemas/services/visibility.py +98 -0
- ciris_engine/schemas/streaming/__init__.py +10 -0
- ciris_engine/schemas/streaming/reasoning_stream.py +95 -0
- ciris_engine/schemas/telemetry/__init__.py +0 -0
- ciris_engine/schemas/telemetry/collector.py +67 -0
- ciris_engine/schemas/telemetry/core.py +252 -0
- ciris_engine/schemas/telemetry/unified.py +59 -0
- ciris_engine/schemas/tools.py +72 -0
- ciris_engine/schemas/types.py +47 -0
- ciris_engine/schemas/utils/__init__.py +1 -0
- ciris_engine/schemas/utils/config_validator.py +54 -0
- ciris_engine/utils/__init__.py +1 -0
- ciris_engine/utils/serialization.py +35 -0
- ciris_sdk/__init__.py +124 -0
- ciris_sdk/auth_store.py +261 -0
- ciris_sdk/client.py +261 -0
- ciris_sdk/exceptions.py +73 -0
- ciris_sdk/model_types.py +258 -0
- ciris_sdk/models.py +354 -0
- ciris_sdk/pagination.py +214 -0
- ciris_sdk/rate_limiter.py +188 -0
- ciris_sdk/setup.py +17 -0
- ciris_sdk/telemetry_models.py +257 -0
- ciris_sdk/telemetry_responses.py +199 -0
- ciris_sdk/transport.py +177 -0
- ciris_sdk/websocket.py +400 -0
- main.py +766 -0
|
@@ -0,0 +1,1762 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent interaction endpoints for CIRIS API v3.0 (Simplified).
|
|
3
|
+
|
|
4
|
+
Core endpoints for natural agent interaction.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
import uuid
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
13
|
+
|
|
14
|
+
from fastapi import APIRouter, Depends, HTTPException, Query, Request, WebSocket, WebSocketDisconnect
|
|
15
|
+
from pydantic import BaseModel, Field
|
|
16
|
+
|
|
17
|
+
from ciris_engine.logic.adapters.base_observer import BillingServiceError, CreditCheckFailed, CreditDenied
|
|
18
|
+
from ciris_engine.schemas.api.agent import AgentLineage, MessageContext, ServiceAvailability
|
|
19
|
+
from ciris_engine.schemas.api.auth import ROLE_PERMISSIONS, AuthContext, Permission, UserRole
|
|
20
|
+
from ciris_engine.schemas.api.responses import SuccessResponse
|
|
21
|
+
from ciris_engine.schemas.runtime.messages import IncomingMessage
|
|
22
|
+
from ciris_engine.schemas.services.credit_gate import CreditAccount, CreditContext
|
|
23
|
+
from ciris_engine.schemas.types import JSONDict
|
|
24
|
+
|
|
25
|
+
from ..constants import DESC_CURRENT_COGNITIVE_STATE, ERROR_MEMORY_SERVICE_NOT_AVAILABLE
|
|
26
|
+
from ..dependencies.auth import require_observer
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
# Minimum uptime in seconds before defaulting task count
|
|
31
|
+
MIN_UPTIME_FOR_DEFAULT_TASKS = 60
|
|
32
|
+
|
|
33
|
+
router = APIRouter(prefix="/agent", tags=["agent"])
|
|
34
|
+
|
|
35
|
+
# Request/Response schemas
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class MessageRejectionReason(str, Enum):
|
|
39
|
+
"""Reasons why a message submission was rejected or not processed."""
|
|
40
|
+
|
|
41
|
+
AGENT_OWN_MESSAGE = "AGENT_OWN_MESSAGE" # Message from agent itself
|
|
42
|
+
FILTERED_OUT = "FILTERED_OUT" # Filtered by adaptive filter
|
|
43
|
+
CREDIT_DENIED = "CREDIT_DENIED" # Insufficient credits
|
|
44
|
+
CREDIT_CHECK_FAILED = "CREDIT_CHECK_FAILED" # Credit provider error
|
|
45
|
+
PROCESSOR_PAUSED = "PROCESSOR_PAUSED" # Agent processor paused
|
|
46
|
+
RATE_LIMITED = "RATE_LIMITED" # Rate limit exceeded
|
|
47
|
+
CHANNEL_RESTRICTED = "CHANNEL_RESTRICTED" # Channel access denied
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ImagePayload(BaseModel):
|
|
51
|
+
"""Image payload for multimodal requests."""
|
|
52
|
+
|
|
53
|
+
data: str = Field(..., description="Base64-encoded image data or URL")
|
|
54
|
+
media_type: str = Field(default="image/jpeg", description="MIME type (image/jpeg, image/png, etc)")
|
|
55
|
+
filename: Optional[str] = Field(default=None, description="Optional filename")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class DocumentPayload(BaseModel):
|
|
59
|
+
"""Document payload for text extraction from files."""
|
|
60
|
+
|
|
61
|
+
data: str = Field(..., description="Base64-encoded document data or URL")
|
|
62
|
+
media_type: str = Field(
|
|
63
|
+
default="application/pdf",
|
|
64
|
+
description="MIME type (application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document)",
|
|
65
|
+
)
|
|
66
|
+
filename: Optional[str] = Field(default=None, description="Optional filename (helps determine document type)")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class InteractRequest(BaseModel):
|
|
70
|
+
"""Request to interact with the agent."""
|
|
71
|
+
|
|
72
|
+
message: str = Field(..., description="Message to send to the agent")
|
|
73
|
+
context: Optional[MessageContext] = Field(None, description="Optional context")
|
|
74
|
+
images: Optional[List[ImagePayload]] = Field(default=None, description="Optional images for multimodal interaction")
|
|
75
|
+
documents: Optional[List[DocumentPayload]] = Field(
|
|
76
|
+
default=None, description="Optional documents (PDF, DOCX) for text extraction"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class InteractResponse(BaseModel):
|
|
81
|
+
"""Response from agent interaction."""
|
|
82
|
+
|
|
83
|
+
message_id: str = Field(..., description="Unique message ID")
|
|
84
|
+
response: str = Field(..., description="Agent's response")
|
|
85
|
+
state: str = Field(..., description="Agent's cognitive state after processing")
|
|
86
|
+
processing_time_ms: int = Field(..., description="Time taken to process")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class MessageRequest(BaseModel):
|
|
90
|
+
"""Request to send a message to the agent (async pattern)."""
|
|
91
|
+
|
|
92
|
+
message: str = Field(..., description="Message to send to the agent")
|
|
93
|
+
context: Optional[MessageContext] = Field(None, description="Optional context")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class MessageSubmissionResponse(BaseModel):
|
|
97
|
+
"""Response from message submission (returns immediately with task ID or rejection reason)."""
|
|
98
|
+
|
|
99
|
+
message_id: str = Field(..., description="Unique message ID for tracking")
|
|
100
|
+
task_id: Optional[str] = Field(None, description="Task ID created (if accepted)")
|
|
101
|
+
channel_id: str = Field(..., description="Channel where message was sent")
|
|
102
|
+
submitted_at: str = Field(..., description="ISO timestamp of submission")
|
|
103
|
+
accepted: bool = Field(..., description="Whether message was accepted for processing")
|
|
104
|
+
rejection_reason: Optional[MessageRejectionReason] = Field(None, description="Reason if rejected")
|
|
105
|
+
rejection_detail: Optional[str] = Field(None, description="Additional detail about rejection")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ConversationMessage(BaseModel):
|
|
109
|
+
"""Message in conversation history."""
|
|
110
|
+
|
|
111
|
+
id: str = Field(..., description="Message ID")
|
|
112
|
+
author: str = Field(..., description="Message author")
|
|
113
|
+
content: str = Field(..., description="Message content")
|
|
114
|
+
timestamp: datetime = Field(..., description="When sent")
|
|
115
|
+
is_agent: bool = Field(..., description="Whether this was from the agent")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class ConversationHistory(BaseModel):
|
|
119
|
+
"""Conversation history."""
|
|
120
|
+
|
|
121
|
+
messages: List[ConversationMessage] = Field(..., description="Message history")
|
|
122
|
+
total_count: int = Field(..., description="Total messages")
|
|
123
|
+
has_more: bool = Field(..., description="Whether more messages exist")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class AgentStatus(BaseModel):
|
|
127
|
+
"""Agent status and cognitive state."""
|
|
128
|
+
|
|
129
|
+
# Core identity
|
|
130
|
+
agent_id: str = Field(..., description="Agent identifier")
|
|
131
|
+
name: str = Field(..., description="Agent name")
|
|
132
|
+
|
|
133
|
+
# Version information
|
|
134
|
+
version: str = Field(..., description="CIRIS version (e.g., 1.0.4-beta)")
|
|
135
|
+
codename: str = Field(..., description="Release codename")
|
|
136
|
+
code_hash: Optional[str] = Field(None, description="Code hash for exact version")
|
|
137
|
+
|
|
138
|
+
# State information
|
|
139
|
+
cognitive_state: str = Field(..., description=DESC_CURRENT_COGNITIVE_STATE)
|
|
140
|
+
uptime_seconds: float = Field(..., description="Time since startup")
|
|
141
|
+
|
|
142
|
+
# Activity metrics
|
|
143
|
+
messages_processed: int = Field(..., description="Total messages processed")
|
|
144
|
+
last_activity: Optional[datetime] = Field(None, description="Last activity timestamp")
|
|
145
|
+
current_task: Optional[str] = Field(None, description="Current task description")
|
|
146
|
+
|
|
147
|
+
# System state
|
|
148
|
+
services_active: int = Field(..., description="Number of active services")
|
|
149
|
+
memory_usage_mb: float = Field(..., description="Current memory usage in MB")
|
|
150
|
+
multi_provider_services: Optional[JSONDict] = Field(None, description="Services with provider counts")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class AgentIdentity(BaseModel):
|
|
154
|
+
"""Agent identity and capabilities."""
|
|
155
|
+
|
|
156
|
+
# Identity
|
|
157
|
+
agent_id: str = Field(..., description="Unique agent identifier")
|
|
158
|
+
name: str = Field(..., description="Agent name")
|
|
159
|
+
purpose: str = Field(..., description="Agent's purpose")
|
|
160
|
+
created_at: datetime = Field(..., description="When agent was created")
|
|
161
|
+
lineage: AgentLineage = Field(..., description="Agent lineage information")
|
|
162
|
+
variance_threshold: float = Field(..., description="Identity variance threshold")
|
|
163
|
+
|
|
164
|
+
# Capabilities
|
|
165
|
+
tools: List[str] = Field(..., description="Available tools")
|
|
166
|
+
handlers: List[str] = Field(..., description="Active handlers")
|
|
167
|
+
services: ServiceAvailability = Field(..., description="Service availability")
|
|
168
|
+
permissions: List[str] = Field(..., description="Agent permissions")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class ChannelInfo(BaseModel):
|
|
172
|
+
"""Information about a communication channel."""
|
|
173
|
+
|
|
174
|
+
channel_id: str = Field(..., description="Unique channel identifier")
|
|
175
|
+
channel_type: str = Field(..., description="Type of channel (discord, api, cli)")
|
|
176
|
+
display_name: str = Field(..., description="Human-readable channel name")
|
|
177
|
+
is_active: bool = Field(..., description="Whether channel is currently active")
|
|
178
|
+
created_at: Optional[datetime] = Field(None, description="When channel was created")
|
|
179
|
+
last_activity: Optional[datetime] = Field(None, description="Last message in channel")
|
|
180
|
+
message_count: int = Field(0, description="Total messages in channel")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class ChannelList(BaseModel):
|
|
184
|
+
"""List of active channels."""
|
|
185
|
+
|
|
186
|
+
channels: List[ChannelInfo] = Field(..., description="List of channels")
|
|
187
|
+
total_count: int = Field(..., description="Total number of channels")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# Message tracking for interact functionality
|
|
191
|
+
_message_responses: dict[str, str] = {}
|
|
192
|
+
_response_events: dict[str, asyncio.Event] = {}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
async def store_message_response(message_id: str, response: str) -> None:
|
|
196
|
+
"""Store a response and notify waiting request."""
|
|
197
|
+
import os
|
|
198
|
+
|
|
199
|
+
occurrence_id = os.environ.get("AGENT_OCCURRENCE_ID", "default")
|
|
200
|
+
logger.info(
|
|
201
|
+
f"[STORE_RESPONSE] occurrence={occurrence_id}, message_id={message_id}, response_len={len(response)}, current_keys={list(_message_responses.keys())}"
|
|
202
|
+
)
|
|
203
|
+
_message_responses[message_id] = response
|
|
204
|
+
event = _response_events.get(message_id)
|
|
205
|
+
if event:
|
|
206
|
+
logger.info(f"[STORE_RESPONSE] Event found for {message_id}, setting it")
|
|
207
|
+
event.set()
|
|
208
|
+
else:
|
|
209
|
+
logger.warning(f"[STORE_RESPONSE] No event found for {message_id}!")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# Endpoints
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _check_send_messages_permission(auth: AuthContext, request: Request) -> None:
|
|
216
|
+
"""Check if user has SEND_MESSAGES permission and handle OAuth auto-request."""
|
|
217
|
+
if auth.has_permission(Permission.SEND_MESSAGES):
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
# Get auth service to check permission request status
|
|
221
|
+
auth_service = request.app.state.auth_service if hasattr(request.app.state, "auth_service") else None
|
|
222
|
+
user = auth_service.get_user(auth.user_id) if auth_service else None
|
|
223
|
+
|
|
224
|
+
# If user is an OAuth user without a permission request, automatically create one
|
|
225
|
+
if user and user.auth_type == "oauth" and user.permission_requested_at is None:
|
|
226
|
+
# Set permission request timestamp
|
|
227
|
+
user.permission_requested_at = datetime.now(timezone.utc)
|
|
228
|
+
# Store the updated user
|
|
229
|
+
if auth_service is not None and hasattr(auth_service, "_users"):
|
|
230
|
+
auth_service._users[user.wa_id] = user # Access private attribute for permission tracking
|
|
231
|
+
|
|
232
|
+
# Don't log potentially sensitive email addresses
|
|
233
|
+
logger.info(f"Auto-created permission request for OAuth user ID: {user.wa_id}")
|
|
234
|
+
|
|
235
|
+
# Build detailed error response
|
|
236
|
+
error_detail = {
|
|
237
|
+
"error": "insufficient_permissions",
|
|
238
|
+
"message": "You do not have permission to send messages to this agent.",
|
|
239
|
+
"discord_invite": "https://discord.gg/A3HVPMWd",
|
|
240
|
+
"can_request_permissions": user.permission_requested_at is None if user else True,
|
|
241
|
+
"permission_requested": user.permission_requested_at is not None if user else False,
|
|
242
|
+
"requested_at": user.permission_requested_at.isoformat() if user and user.permission_requested_at else None,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
raise HTTPException(status_code=403, detail=error_detail)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
async def _create_interaction_message(
|
|
249
|
+
auth: AuthContext, body: Union[InteractRequest, MessageRequest]
|
|
250
|
+
) -> Tuple[str, str, IncomingMessage]:
|
|
251
|
+
"""Create message ID, channel ID, and IncomingMessage for interaction."""
|
|
252
|
+
from ciris_engine.logic.adapters.api.api_document import get_api_document_helper
|
|
253
|
+
from ciris_engine.logic.adapters.api.api_vision import get_api_vision_helper
|
|
254
|
+
|
|
255
|
+
message_id = str(uuid.uuid4())
|
|
256
|
+
channel_id = f"api_{auth.user_id}" # User-specific channel
|
|
257
|
+
|
|
258
|
+
# Process images if provided (InteractRequest only)
|
|
259
|
+
images = []
|
|
260
|
+
if isinstance(body, InteractRequest) and body.images:
|
|
261
|
+
vision_helper = get_api_vision_helper()
|
|
262
|
+
for img_payload in body.images:
|
|
263
|
+
image_content = vision_helper.process_image_payload(
|
|
264
|
+
img_payload.data,
|
|
265
|
+
img_payload.media_type,
|
|
266
|
+
img_payload.filename,
|
|
267
|
+
)
|
|
268
|
+
if image_content:
|
|
269
|
+
images.append(image_content)
|
|
270
|
+
if images:
|
|
271
|
+
logger.info(f"Processed {len(images)} images for multimodal interaction")
|
|
272
|
+
|
|
273
|
+
# Process documents if provided (InteractRequest only)
|
|
274
|
+
additional_content = ""
|
|
275
|
+
if isinstance(body, InteractRequest) and body.documents:
|
|
276
|
+
document_helper = get_api_document_helper()
|
|
277
|
+
if document_helper.is_available():
|
|
278
|
+
doc_payloads = [
|
|
279
|
+
{"data": doc.data, "media_type": doc.media_type, "filename": doc.filename} for doc in body.documents
|
|
280
|
+
]
|
|
281
|
+
document_text = await document_helper.process_document_list(doc_payloads)
|
|
282
|
+
if document_text:
|
|
283
|
+
additional_content = "\n\n[Document Analysis]\n" + document_text
|
|
284
|
+
logger.info(f"Processed {len(body.documents)} documents for interaction")
|
|
285
|
+
else:
|
|
286
|
+
logger.warning("Document processing requested but not available (missing libraries)")
|
|
287
|
+
|
|
288
|
+
# Combine message content with any extracted document text
|
|
289
|
+
final_content = body.message + additional_content
|
|
290
|
+
|
|
291
|
+
msg = IncomingMessage(
|
|
292
|
+
message_id=message_id,
|
|
293
|
+
author_id=auth.user_id,
|
|
294
|
+
author_name=auth.user_id,
|
|
295
|
+
content=final_content,
|
|
296
|
+
channel_id=channel_id,
|
|
297
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
298
|
+
images=images,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
return message_id, channel_id, msg
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
async def _handle_consent_for_user(auth: AuthContext, channel_id: str, request: Request) -> str:
|
|
305
|
+
"""Handle consent checking and creation for user, return consent notice if applicable."""
|
|
306
|
+
try:
|
|
307
|
+
from ciris_engine.logic.services.governance.consent import ConsentNotFoundError, ConsentService
|
|
308
|
+
from ciris_engine.schemas.consent.core import ConsentRequest, ConsentStream
|
|
309
|
+
|
|
310
|
+
# Get consent manager
|
|
311
|
+
if hasattr(request.app.state, "consent_manager") and request.app.state.consent_manager:
|
|
312
|
+
consent_manager = request.app.state.consent_manager
|
|
313
|
+
else:
|
|
314
|
+
from ciris_engine.logic.services.lifecycle.time import TimeService
|
|
315
|
+
|
|
316
|
+
time_service = TimeService()
|
|
317
|
+
consent_manager = ConsentService(time_service=time_service)
|
|
318
|
+
request.app.state.consent_manager = consent_manager
|
|
319
|
+
|
|
320
|
+
# Check if user has consent
|
|
321
|
+
try:
|
|
322
|
+
consent_status = await consent_manager.get_consent(auth.user_id)
|
|
323
|
+
return "" # User already has consent
|
|
324
|
+
except ConsentNotFoundError:
|
|
325
|
+
# First interaction - create default TEMPORARY consent
|
|
326
|
+
consent_req = ConsentRequest(
|
|
327
|
+
user_id=auth.user_id,
|
|
328
|
+
stream=ConsentStream.TEMPORARY,
|
|
329
|
+
categories=[],
|
|
330
|
+
reason="Default TEMPORARY consent on first interaction",
|
|
331
|
+
)
|
|
332
|
+
consent_status = await consent_manager.grant_consent(consent_req, channel_id=channel_id)
|
|
333
|
+
|
|
334
|
+
# Return notice to add to response
|
|
335
|
+
return "\n\n📝 Privacy Notice: We forget about you in 14 days unless you say otherwise. Visit /v1/consent to manage your data preferences."
|
|
336
|
+
|
|
337
|
+
except Exception as e:
|
|
338
|
+
logger.warning(f"Could not check consent for user {auth.user_id}: {e}")
|
|
339
|
+
return ""
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
async def _track_air_interaction(
|
|
343
|
+
auth: AuthContext, channel_id: str, message_content: str, request: Request
|
|
344
|
+
) -> Optional[str]:
|
|
345
|
+
"""
|
|
346
|
+
Track interaction for AIR (Artificial Interaction Reminder) parasocial prevention.
|
|
347
|
+
|
|
348
|
+
This monitors 1:1 API interactions for:
|
|
349
|
+
- Time-based triggers (30 min continuous)
|
|
350
|
+
- Message-based triggers (20+ messages in 30 min window)
|
|
351
|
+
|
|
352
|
+
Returns reminder message if threshold exceeded, None otherwise.
|
|
353
|
+
"""
|
|
354
|
+
try:
|
|
355
|
+
# Get consent manager which hosts the AIR manager
|
|
356
|
+
consent_manager = getattr(request.app.state, "consent_manager", None)
|
|
357
|
+
if not consent_manager:
|
|
358
|
+
return None
|
|
359
|
+
|
|
360
|
+
# Track interaction and get potential reminder
|
|
361
|
+
reminder: Optional[str] = await consent_manager.track_interaction(
|
|
362
|
+
user_id=auth.user_id,
|
|
363
|
+
channel_id=channel_id,
|
|
364
|
+
channel_type="api",
|
|
365
|
+
message_content=message_content,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
if reminder:
|
|
369
|
+
logger.info(f"[AIR] Reminder triggered for user {auth.user_id}")
|
|
370
|
+
|
|
371
|
+
return reminder
|
|
372
|
+
|
|
373
|
+
except Exception as e:
|
|
374
|
+
logger.debug(f"AIR tracking error (non-fatal): {e}")
|
|
375
|
+
return None
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _derive_credit_account(
|
|
379
|
+
auth: AuthContext,
|
|
380
|
+
request: Request,
|
|
381
|
+
) -> Tuple[CreditAccount, Dict[str, str]]:
|
|
382
|
+
"""Build credit account metadata for the current request."""
|
|
383
|
+
|
|
384
|
+
auth_service = getattr(request.app.state, "auth_service", None)
|
|
385
|
+
user = None
|
|
386
|
+
if auth_service and hasattr(auth_service, "get_user"):
|
|
387
|
+
try:
|
|
388
|
+
user = auth_service.get_user(auth.user_id)
|
|
389
|
+
except Exception as exc: # pragma: no cover - defensive logging
|
|
390
|
+
logger.debug("Unable to load user for credit gating: %s", exc)
|
|
391
|
+
|
|
392
|
+
provider = "api"
|
|
393
|
+
account_id = auth.user_id
|
|
394
|
+
authority_id = None
|
|
395
|
+
tenant_id = None
|
|
396
|
+
|
|
397
|
+
if user:
|
|
398
|
+
authority_id = getattr(user, "wa_id", None) or auth.user_id
|
|
399
|
+
tenant_id = getattr(user, "wa_parent_id", None)
|
|
400
|
+
|
|
401
|
+
oauth_provider = getattr(user, "oauth_provider", None)
|
|
402
|
+
oauth_external_id = getattr(user, "oauth_external_id", None)
|
|
403
|
+
if oauth_provider and oauth_external_id:
|
|
404
|
+
provider = f"oauth:{oauth_provider}"
|
|
405
|
+
account_id = oauth_external_id
|
|
406
|
+
else:
|
|
407
|
+
provider = "wa"
|
|
408
|
+
account_id = getattr(user, "wa_id", None) or auth.user_id
|
|
409
|
+
else:
|
|
410
|
+
if auth.api_key_id:
|
|
411
|
+
provider = "api-key"
|
|
412
|
+
account_id = auth.api_key_id
|
|
413
|
+
elif auth.role == UserRole.SERVICE_ACCOUNT:
|
|
414
|
+
provider = "service-account"
|
|
415
|
+
account_id = auth.user_id
|
|
416
|
+
|
|
417
|
+
account = CreditAccount(
|
|
418
|
+
provider=provider,
|
|
419
|
+
account_id=account_id,
|
|
420
|
+
authority_id=authority_id,
|
|
421
|
+
tenant_id=tenant_id,
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
metadata: Dict[str, str] = {
|
|
425
|
+
"role": auth.role.value,
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
# Extract email address for billing backend user management
|
|
429
|
+
if user and hasattr(user, "oauth_email") and user.oauth_email:
|
|
430
|
+
metadata["email"] = user.oauth_email
|
|
431
|
+
|
|
432
|
+
# Extract marketing opt-in preference if available
|
|
433
|
+
if user and hasattr(user, "marketing_opt_in"):
|
|
434
|
+
metadata["marketing_opt_in"] = str(user.marketing_opt_in).lower()
|
|
435
|
+
|
|
436
|
+
if auth.api_key_id:
|
|
437
|
+
metadata["api_key_id"] = auth.api_key_id
|
|
438
|
+
|
|
439
|
+
return account, metadata
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _attach_credit_metadata(
|
|
443
|
+
msg: IncomingMessage,
|
|
444
|
+
request: Request,
|
|
445
|
+
auth: AuthContext,
|
|
446
|
+
channel_id: str,
|
|
447
|
+
) -> IncomingMessage:
|
|
448
|
+
"""Attach credit envelope data to the message for downstream processing."""
|
|
449
|
+
|
|
450
|
+
logger.debug(f"[CREDIT_ATTACH] Called for message {msg.message_id}")
|
|
451
|
+
|
|
452
|
+
resource_monitor = getattr(request.app.state, "resource_monitor", None)
|
|
453
|
+
logger.debug(f"[CREDIT_ATTACH] resource_monitor exists: {resource_monitor is not None}")
|
|
454
|
+
|
|
455
|
+
if not resource_monitor:
|
|
456
|
+
logger.critical(f"[CREDIT_ATTACH] NO RESOURCE MONITOR - credit metadata NOT attached to {msg.message_id}")
|
|
457
|
+
return msg
|
|
458
|
+
|
|
459
|
+
credit_provider = getattr(resource_monitor, "credit_provider", None)
|
|
460
|
+
logger.debug(
|
|
461
|
+
f"[CREDIT_ATTACH] credit_provider exists: {credit_provider is not None}, type={type(credit_provider).__name__ if credit_provider else 'None'}"
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
if not credit_provider:
|
|
465
|
+
# Try lazy initialization - token may have been written after server start
|
|
466
|
+
from ciris_engine.logic.adapters.api.routes.billing import _try_lazy_init_billing_provider
|
|
467
|
+
|
|
468
|
+
credit_provider = _try_lazy_init_billing_provider(request, resource_monitor)
|
|
469
|
+
if not credit_provider:
|
|
470
|
+
logger.critical(f"[CREDIT_ATTACH] NO CREDIT PROVIDER - credit metadata NOT attached to {msg.message_id}")
|
|
471
|
+
return msg
|
|
472
|
+
logger.info(f"[CREDIT_ATTACH] Lazily initialized billing provider for {msg.message_id}")
|
|
473
|
+
|
|
474
|
+
try:
|
|
475
|
+
account, _ = _derive_credit_account(auth, request)
|
|
476
|
+
logger.debug(f"[CREDIT_ATTACH] Derived credit account: {account.cache_key()}")
|
|
477
|
+
|
|
478
|
+
runtime = getattr(request.app.state, "runtime", None)
|
|
479
|
+
agent_identity = getattr(runtime, "agent_identity", None) if runtime else None
|
|
480
|
+
agent_id = getattr(agent_identity, "agent_id", None)
|
|
481
|
+
|
|
482
|
+
# Determine billing mode: Android uses "informational" (check only, billing via LLM usage)
|
|
483
|
+
# Hosted sites use "transactional" (check+spend per interaction)
|
|
484
|
+
from ciris_engine.logic.utils.path_resolution import is_android
|
|
485
|
+
|
|
486
|
+
billing_mode = "informational" if is_android() else "transactional"
|
|
487
|
+
|
|
488
|
+
logger.debug(
|
|
489
|
+
f"[CREDIT_ATTACH] Creating CreditContext with agent_id={agent_id}, channel_id={channel_id}, user_role={auth.role.value}, billing_mode={billing_mode}"
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
credit_context = CreditContext(
|
|
493
|
+
agent_id=agent_id,
|
|
494
|
+
channel_id=channel_id,
|
|
495
|
+
request_id=msg.message_id,
|
|
496
|
+
user_role=auth.role.value,
|
|
497
|
+
billing_mode=billing_mode,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
logger.debug("[CREDIT_ATTACH] CreditContext created successfully")
|
|
501
|
+
logger.debug(
|
|
502
|
+
f"[CREDIT_ATTACH] Attaching credit metadata to message {msg.message_id}: account={account.cache_key()}"
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
updated_msg = msg.model_copy(
|
|
506
|
+
update={
|
|
507
|
+
"credit_account": account.model_dump(),
|
|
508
|
+
"credit_context": credit_context.model_dump(),
|
|
509
|
+
}
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
logger.debug(f"[CREDIT_ATTACH] Credit metadata SUCCESSFULLY attached to message {msg.message_id}")
|
|
513
|
+
|
|
514
|
+
# CRITICAL: Also attach resource_monitor to message for observer access
|
|
515
|
+
# The observer may be initialized before resource_monitor exists on runtime,
|
|
516
|
+
# so we attach it per-message to ensure credit enforcement works
|
|
517
|
+
updated_msg._resource_monitor = resource_monitor # type: ignore[attr-defined]
|
|
518
|
+
logger.debug(f"[CREDIT_ATTACH] resource_monitor attached to message {msg.message_id}")
|
|
519
|
+
|
|
520
|
+
return updated_msg
|
|
521
|
+
|
|
522
|
+
except Exception as e:
|
|
523
|
+
logger.critical(
|
|
524
|
+
f"[CREDIT_ATTACH] EXCEPTION for message {msg.message_id}: {type(e).__name__}: {e}", exc_info=True
|
|
525
|
+
)
|
|
526
|
+
# Return original message without metadata on error
|
|
527
|
+
return msg
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def _get_runtime_processor(request: Request) -> Any:
|
|
531
|
+
"""Get runtime processor if available and valid."""
|
|
532
|
+
runtime = getattr(request.app.state, "runtime", None)
|
|
533
|
+
if not (runtime and hasattr(runtime, "agent_processor") and runtime.agent_processor):
|
|
534
|
+
return None
|
|
535
|
+
return runtime.agent_processor
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def _is_processor_paused(processor: Any) -> bool:
|
|
539
|
+
"""Check if processor is in paused state."""
|
|
540
|
+
return hasattr(processor, "_is_paused") and processor._is_paused
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
async def _handle_paused_message(request: Request, msg: IncomingMessage) -> None:
|
|
544
|
+
"""Route message to queue when processor is paused."""
|
|
545
|
+
if hasattr(request.app.state, "on_message"):
|
|
546
|
+
await request.app.state.on_message(msg)
|
|
547
|
+
else:
|
|
548
|
+
raise HTTPException(status_code=503, detail="Message handler not configured")
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _get_processor_cognitive_state(processor: Any) -> str:
|
|
552
|
+
"""Get current cognitive state from processor with fallback."""
|
|
553
|
+
try:
|
|
554
|
+
if hasattr(processor, "get_current_state"):
|
|
555
|
+
state = processor.get_current_state()
|
|
556
|
+
return str(state) if state is not None else "WORK"
|
|
557
|
+
except Exception:
|
|
558
|
+
pass
|
|
559
|
+
return "WORK" # Default
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def _create_paused_response(
|
|
563
|
+
message_id: str, cognitive_state: str, processing_time: int
|
|
564
|
+
) -> SuccessResponse[InteractResponse]:
|
|
565
|
+
"""Create response for paused processor state."""
|
|
566
|
+
return SuccessResponse(
|
|
567
|
+
data=InteractResponse(
|
|
568
|
+
message_id=message_id,
|
|
569
|
+
response="Processor paused - task added to queue. Resume processing to continue.",
|
|
570
|
+
state=cognitive_state,
|
|
571
|
+
processing_time_ms=processing_time,
|
|
572
|
+
)
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
async def _check_processor_pause_status(
|
|
577
|
+
request: Request, msg: IncomingMessage, message_id: str, start_time: datetime
|
|
578
|
+
) -> Optional[SuccessResponse[InteractResponse]]:
|
|
579
|
+
"""Check if processor is paused and handle accordingly. Returns response if paused, None if not paused."""
|
|
580
|
+
try:
|
|
581
|
+
processor = _get_runtime_processor(request)
|
|
582
|
+
if not processor or not _is_processor_paused(processor):
|
|
583
|
+
return None
|
|
584
|
+
|
|
585
|
+
# Processor is paused - route message and prepare response
|
|
586
|
+
await _handle_paused_message(request, msg)
|
|
587
|
+
|
|
588
|
+
# Clean up response tracking since we're returning immediately
|
|
589
|
+
_response_events.pop(message_id, None)
|
|
590
|
+
|
|
591
|
+
# Calculate processing time and get state
|
|
592
|
+
processing_time = int((datetime.now(timezone.utc) - start_time).total_seconds() * 1000)
|
|
593
|
+
cognitive_state = _get_processor_cognitive_state(processor)
|
|
594
|
+
|
|
595
|
+
return _create_paused_response(message_id, cognitive_state, processing_time)
|
|
596
|
+
|
|
597
|
+
except HTTPException:
|
|
598
|
+
# Re-raise HTTP exceptions (like 503 for missing message handler)
|
|
599
|
+
raise
|
|
600
|
+
except Exception as e:
|
|
601
|
+
logger.debug(f"Could not check pause state: {e}")
|
|
602
|
+
|
|
603
|
+
return None
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def _get_interaction_timeout(request: Request) -> float:
|
|
607
|
+
"""Get interaction timeout from config or return default."""
|
|
608
|
+
timeout = 55.0 # default timeout for longer processing
|
|
609
|
+
if hasattr(request.app.state, "api_config"):
|
|
610
|
+
timeout = request.app.state.api_config.interaction_timeout
|
|
611
|
+
return timeout
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def _get_current_cognitive_state(request: Request) -> str:
|
|
615
|
+
"""Get current cognitive state from request runtime."""
|
|
616
|
+
runtime = getattr(request.app.state, "runtime", None)
|
|
617
|
+
return _get_cognitive_state(runtime)
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def _cleanup_interaction_tracking(message_id: str) -> None:
|
|
621
|
+
"""Clean up interaction tracking for given message ID."""
|
|
622
|
+
_response_events.pop(message_id, None)
|
|
623
|
+
_message_responses.pop(message_id, None)
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
@router.post("/message", response_model=SuccessResponse[MessageSubmissionResponse])
|
|
627
|
+
async def submit_message(
|
|
628
|
+
request: Request, body: MessageRequest, auth: AuthContext = Depends(require_observer)
|
|
629
|
+
) -> SuccessResponse[MessageSubmissionResponse]:
|
|
630
|
+
"""
|
|
631
|
+
Submit a message to the agent (async pattern - returns immediately).
|
|
632
|
+
|
|
633
|
+
This endpoint returns immediately with a task_id for tracking or rejection reason.
|
|
634
|
+
Use GET /agent/history to poll for the agent's response.
|
|
635
|
+
|
|
636
|
+
This is the recommended way to interact with the agent via API,
|
|
637
|
+
as it doesn't block waiting for processing to complete.
|
|
638
|
+
|
|
639
|
+
Requires: SEND_MESSAGES permission (ADMIN+ by default, or OBSERVER with explicit grant)
|
|
640
|
+
"""
|
|
641
|
+
from ciris_engine.schemas.runtime.messages import MessageHandlingStatus
|
|
642
|
+
|
|
643
|
+
# Check permissions
|
|
644
|
+
_check_send_messages_permission(auth, request)
|
|
645
|
+
|
|
646
|
+
# Create message and tracking
|
|
647
|
+
message_id, channel_id, msg = await _create_interaction_message(auth, body)
|
|
648
|
+
msg = _attach_credit_metadata(msg, request, auth, channel_id)
|
|
649
|
+
|
|
650
|
+
# Handle consent for user
|
|
651
|
+
await _handle_consent_for_user(auth, channel_id, request)
|
|
652
|
+
|
|
653
|
+
# Track interaction for AIR (parasocial attachment prevention)
|
|
654
|
+
# Note: For async pattern, reminder is logged but not returned (user polls for response)
|
|
655
|
+
await _track_air_interaction(auth, channel_id, body.message, request)
|
|
656
|
+
|
|
657
|
+
# Track submission time
|
|
658
|
+
submitted_at = datetime.now(timezone.utc)
|
|
659
|
+
|
|
660
|
+
# Check if processor is paused
|
|
661
|
+
pause_response = await _check_processor_pause_status(request, msg, message_id, submitted_at)
|
|
662
|
+
if pause_response:
|
|
663
|
+
# Return rejection for paused processor
|
|
664
|
+
response = MessageSubmissionResponse(
|
|
665
|
+
message_id=message_id,
|
|
666
|
+
task_id=None,
|
|
667
|
+
channel_id=channel_id,
|
|
668
|
+
submitted_at=submitted_at.isoformat(),
|
|
669
|
+
accepted=False,
|
|
670
|
+
rejection_reason=MessageRejectionReason.PROCESSOR_PAUSED,
|
|
671
|
+
rejection_detail="Agent processor is paused",
|
|
672
|
+
)
|
|
673
|
+
return SuccessResponse(data=response)
|
|
674
|
+
|
|
675
|
+
# Submit message and get result (with credit enforcement)
|
|
676
|
+
try:
|
|
677
|
+
if hasattr(request.app.state, "on_message"):
|
|
678
|
+
result = await request.app.state.on_message(msg)
|
|
679
|
+
else:
|
|
680
|
+
raise HTTPException(status_code=503, detail="Message handler not configured")
|
|
681
|
+
except CreditDenied as exc:
|
|
682
|
+
# Return rejection for credit denial
|
|
683
|
+
response = MessageSubmissionResponse(
|
|
684
|
+
message_id=message_id,
|
|
685
|
+
task_id=None,
|
|
686
|
+
channel_id=channel_id,
|
|
687
|
+
submitted_at=submitted_at.isoformat(),
|
|
688
|
+
accepted=False,
|
|
689
|
+
rejection_reason=MessageRejectionReason.CREDIT_DENIED,
|
|
690
|
+
rejection_detail=exc.reason,
|
|
691
|
+
)
|
|
692
|
+
return SuccessResponse(data=response)
|
|
693
|
+
except CreditCheckFailed as exc:
|
|
694
|
+
# Return rejection for credit check failure
|
|
695
|
+
response = MessageSubmissionResponse(
|
|
696
|
+
message_id=message_id,
|
|
697
|
+
task_id=None,
|
|
698
|
+
channel_id=channel_id,
|
|
699
|
+
submitted_at=submitted_at.isoformat(),
|
|
700
|
+
accepted=False,
|
|
701
|
+
rejection_reason=MessageRejectionReason.CREDIT_CHECK_FAILED,
|
|
702
|
+
rejection_detail=str(exc),
|
|
703
|
+
)
|
|
704
|
+
return SuccessResponse(data=response)
|
|
705
|
+
|
|
706
|
+
# Map MessageHandlingResult to MessageSubmissionResponse
|
|
707
|
+
accepted = result.status in [MessageHandlingStatus.TASK_CREATED, MessageHandlingStatus.UPDATED_EXISTING_TASK]
|
|
708
|
+
rejection_reason = None
|
|
709
|
+
rejection_detail = None
|
|
710
|
+
|
|
711
|
+
if not accepted:
|
|
712
|
+
# Map status to rejection reason
|
|
713
|
+
status_to_reason = {
|
|
714
|
+
MessageHandlingStatus.AGENT_OWN_MESSAGE: MessageRejectionReason.AGENT_OWN_MESSAGE,
|
|
715
|
+
MessageHandlingStatus.FILTERED_OUT: MessageRejectionReason.FILTERED_OUT,
|
|
716
|
+
MessageHandlingStatus.CHANNEL_RESTRICTED: MessageRejectionReason.CHANNEL_RESTRICTED,
|
|
717
|
+
MessageHandlingStatus.RATE_LIMITED: MessageRejectionReason.RATE_LIMITED,
|
|
718
|
+
}
|
|
719
|
+
rejection_reason = status_to_reason.get(result.status)
|
|
720
|
+
rejection_detail = result.filter_reasoning if result.filtered else None
|
|
721
|
+
elif result.status == MessageHandlingStatus.UPDATED_EXISTING_TASK:
|
|
722
|
+
# Add detail that existing task was updated
|
|
723
|
+
rejection_detail = "Existing task updated with new information"
|
|
724
|
+
|
|
725
|
+
# Return result
|
|
726
|
+
response = MessageSubmissionResponse(
|
|
727
|
+
message_id=message_id,
|
|
728
|
+
task_id=result.task_id,
|
|
729
|
+
channel_id=channel_id,
|
|
730
|
+
submitted_at=submitted_at.isoformat(),
|
|
731
|
+
accepted=accepted,
|
|
732
|
+
rejection_reason=rejection_reason,
|
|
733
|
+
rejection_detail=rejection_detail,
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
return SuccessResponse(data=response)
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
@router.post("/interact", response_model=SuccessResponse[InteractResponse])
|
|
740
|
+
async def interact(
|
|
741
|
+
request: Request, body: InteractRequest, auth: AuthContext = Depends(require_observer)
|
|
742
|
+
) -> SuccessResponse[InteractResponse]:
|
|
743
|
+
"""
|
|
744
|
+
Send message and get response.
|
|
745
|
+
|
|
746
|
+
This endpoint combines the old send/ask functionality into a single interaction.
|
|
747
|
+
It sends the message and waits for the agent's response (with a reasonable timeout).
|
|
748
|
+
|
|
749
|
+
Requires: SEND_MESSAGES permission (ADMIN+ by default, or OBSERVER with explicit grant)
|
|
750
|
+
"""
|
|
751
|
+
# Check permissions
|
|
752
|
+
_check_send_messages_permission(auth, request)
|
|
753
|
+
|
|
754
|
+
# Create message and tracking
|
|
755
|
+
message_id, channel_id, msg = await _create_interaction_message(auth, body)
|
|
756
|
+
msg = _attach_credit_metadata(msg, request, auth, channel_id)
|
|
757
|
+
|
|
758
|
+
event = asyncio.Event()
|
|
759
|
+
_response_events[message_id] = event
|
|
760
|
+
|
|
761
|
+
# Handle consent for user
|
|
762
|
+
consent_notice = await _handle_consent_for_user(auth, channel_id, request)
|
|
763
|
+
|
|
764
|
+
# Track interaction for AIR (parasocial attachment prevention)
|
|
765
|
+
air_reminder = await _track_air_interaction(auth, channel_id, body.message, request)
|
|
766
|
+
|
|
767
|
+
# Track timing
|
|
768
|
+
start_time = datetime.now(timezone.utc)
|
|
769
|
+
|
|
770
|
+
# Check if processor is paused
|
|
771
|
+
pause_response = await _check_processor_pause_status(request, msg, message_id, start_time)
|
|
772
|
+
if pause_response:
|
|
773
|
+
return pause_response
|
|
774
|
+
|
|
775
|
+
try:
|
|
776
|
+
if hasattr(request.app.state, "on_message"):
|
|
777
|
+
await request.app.state.on_message(msg)
|
|
778
|
+
else:
|
|
779
|
+
raise HTTPException(status_code=503, detail="Message handler not configured")
|
|
780
|
+
except CreditDenied as exc:
|
|
781
|
+
_cleanup_interaction_tracking(message_id)
|
|
782
|
+
raise HTTPException(
|
|
783
|
+
status_code=402,
|
|
784
|
+
detail={
|
|
785
|
+
"error": "insufficient_credit",
|
|
786
|
+
"message": "Interaction blocked by credit policy.",
|
|
787
|
+
"reason": exc.reason,
|
|
788
|
+
},
|
|
789
|
+
) from exc
|
|
790
|
+
except CreditCheckFailed as exc:
|
|
791
|
+
_cleanup_interaction_tracking(message_id)
|
|
792
|
+
raise HTTPException(status_code=503, detail="Credit provider unavailable") from exc
|
|
793
|
+
except BillingServiceError as exc:
|
|
794
|
+
_cleanup_interaction_tracking(message_id)
|
|
795
|
+
raise HTTPException(
|
|
796
|
+
status_code=402,
|
|
797
|
+
detail={
|
|
798
|
+
"error": "billing_error",
|
|
799
|
+
"message": "LLM billing service error. Please check your account or try again later.",
|
|
800
|
+
"reason": exc.message,
|
|
801
|
+
},
|
|
802
|
+
) from exc
|
|
803
|
+
|
|
804
|
+
# Get timeout and wait for response
|
|
805
|
+
timeout = _get_interaction_timeout(request)
|
|
806
|
+
|
|
807
|
+
try:
|
|
808
|
+
await asyncio.wait_for(event.wait(), timeout=timeout)
|
|
809
|
+
|
|
810
|
+
# Get response
|
|
811
|
+
import os
|
|
812
|
+
|
|
813
|
+
occurrence_id = os.environ.get("AGENT_OCCURRENCE_ID", "default")
|
|
814
|
+
logger.info(
|
|
815
|
+
f"[RETRIEVE_RESPONSE] occurrence={occurrence_id}, message_id={message_id}, available_keys={list(_message_responses.keys())}"
|
|
816
|
+
)
|
|
817
|
+
response_content = _message_responses.get(message_id, "I'm processing your request. Please check back shortly.")
|
|
818
|
+
logger.info(
|
|
819
|
+
f"[RETRIEVE_RESPONSE] Retrieved content_len={len(response_content)}, content_preview={response_content[:100] if response_content else 'EMPTY'}"
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
# Add consent notice if this is first interaction
|
|
823
|
+
if consent_notice:
|
|
824
|
+
response_content += consent_notice
|
|
825
|
+
|
|
826
|
+
# Add AIR reminder if triggered (parasocial attachment prevention)
|
|
827
|
+
if air_reminder:
|
|
828
|
+
response_content += "\n\n---\n" + air_reminder
|
|
829
|
+
|
|
830
|
+
# Clean up and calculate timing
|
|
831
|
+
_cleanup_interaction_tracking(message_id)
|
|
832
|
+
processing_time_ms = int((datetime.now(timezone.utc) - start_time).total_seconds() * 1000)
|
|
833
|
+
|
|
834
|
+
# Build response
|
|
835
|
+
response = InteractResponse(
|
|
836
|
+
message_id=message_id,
|
|
837
|
+
response=response_content,
|
|
838
|
+
state=_get_current_cognitive_state(request),
|
|
839
|
+
processing_time_ms=processing_time_ms,
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
return SuccessResponse(data=response)
|
|
843
|
+
|
|
844
|
+
except asyncio.TimeoutError:
|
|
845
|
+
# Clean up
|
|
846
|
+
_cleanup_interaction_tracking(message_id)
|
|
847
|
+
|
|
848
|
+
# Return a timeout response rather than error
|
|
849
|
+
response = InteractResponse(
|
|
850
|
+
message_id=message_id,
|
|
851
|
+
response="Still processing. Check back later. Agent response is not guaranteed.",
|
|
852
|
+
state="WORK",
|
|
853
|
+
processing_time_ms=int(timeout * 1000), # Use actual timeout value
|
|
854
|
+
)
|
|
855
|
+
|
|
856
|
+
return SuccessResponse(data=response)
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
@router.get("/history", response_model=SuccessResponse[ConversationHistory])
|
|
860
|
+
async def get_history(
|
|
861
|
+
request: Request,
|
|
862
|
+
limit: int = Query(50, ge=1, le=200, description="Maximum messages to return"),
|
|
863
|
+
before: Optional[datetime] = Query(None, description="Get messages before this time"),
|
|
864
|
+
auth: AuthContext = Depends(require_observer),
|
|
865
|
+
) -> SuccessResponse[ConversationHistory]:
|
|
866
|
+
"""
|
|
867
|
+
Conversation history.
|
|
868
|
+
|
|
869
|
+
Get the conversation history for the current user.
|
|
870
|
+
"""
|
|
871
|
+
# Build channels to query based on user role
|
|
872
|
+
channels_to_query = _build_channels_to_query(auth, request)
|
|
873
|
+
channel_id = f"api_{auth.user_id}"
|
|
874
|
+
|
|
875
|
+
logger.info(f"History query for user {auth.user_id} with role {auth.role}, channels: {channels_to_query}")
|
|
876
|
+
|
|
877
|
+
# Check for mock message history first
|
|
878
|
+
message_history = getattr(request.app.state, "message_history", None)
|
|
879
|
+
if message_history is not None:
|
|
880
|
+
history = await _get_history_from_mock(message_history, channels_to_query, limit)
|
|
881
|
+
return SuccessResponse(data=history)
|
|
882
|
+
|
|
883
|
+
# Get communication service
|
|
884
|
+
comm_service = getattr(request.app.state, "communication_service", None)
|
|
885
|
+
if not comm_service:
|
|
886
|
+
# Fallback: query from memory
|
|
887
|
+
memory_service = getattr(request.app.state, "memory_service", None)
|
|
888
|
+
if memory_service:
|
|
889
|
+
history = await _get_history_from_memory(memory_service, channel_id, limit)
|
|
890
|
+
return SuccessResponse(data=history)
|
|
891
|
+
|
|
892
|
+
try:
|
|
893
|
+
history = await _get_history_from_communication_service(comm_service, channels_to_query, limit, before)
|
|
894
|
+
return SuccessResponse(data=history)
|
|
895
|
+
except Exception as e:
|
|
896
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
def _get_cognitive_state(runtime: Any) -> str:
|
|
900
|
+
"""Get the agent's cognitive state with proper None checking."""
|
|
901
|
+
if runtime is None:
|
|
902
|
+
logger.warning("Runtime is None")
|
|
903
|
+
return "UNKNOWN"
|
|
904
|
+
|
|
905
|
+
# State manager is on the agent processor, not directly on runtime
|
|
906
|
+
if hasattr(runtime, "agent_processor") and runtime.agent_processor:
|
|
907
|
+
if hasattr(runtime.agent_processor, "state_manager") and runtime.agent_processor.state_manager:
|
|
908
|
+
if hasattr(runtime.agent_processor.state_manager, "current_state"):
|
|
909
|
+
state = runtime.agent_processor.state_manager.current_state
|
|
910
|
+
# Convert AgentState enum to string if necessary
|
|
911
|
+
return str(state)
|
|
912
|
+
else:
|
|
913
|
+
logger.warning("Agent processor state_manager exists but has no current_state attribute")
|
|
914
|
+
else:
|
|
915
|
+
logger.warning("Agent processor has no state_manager or state_manager is None")
|
|
916
|
+
else:
|
|
917
|
+
logger.warning("Runtime has no agent_processor or agent_processor is None")
|
|
918
|
+
return "UNKNOWN" # Don't default to WORK - be explicit about unknown state
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
def _calculate_uptime(time_service: Any) -> float:
|
|
922
|
+
"""Calculate the agent's uptime in seconds."""
|
|
923
|
+
if not time_service:
|
|
924
|
+
return 0.0
|
|
925
|
+
|
|
926
|
+
# Try to get uptime from time service status
|
|
927
|
+
if hasattr(time_service, "get_status"):
|
|
928
|
+
time_status = time_service.get_status()
|
|
929
|
+
if hasattr(time_status, "uptime_seconds"):
|
|
930
|
+
uptime = time_status.uptime_seconds
|
|
931
|
+
return float(uptime) if uptime is not None else 0.0
|
|
932
|
+
|
|
933
|
+
# Calculate uptime manually
|
|
934
|
+
if hasattr(time_service, "_start_time") and hasattr(time_service, "now"):
|
|
935
|
+
delta = time_service.now() - time_service._start_time
|
|
936
|
+
return float(delta.total_seconds())
|
|
937
|
+
|
|
938
|
+
return 0.0
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
def _count_wakeup_tasks(uptime: float) -> int:
|
|
942
|
+
"""Count completed WAKEUP tasks."""
|
|
943
|
+
try:
|
|
944
|
+
from ciris_engine.logic import persistence
|
|
945
|
+
from ciris_engine.schemas.runtime.enums import TaskStatus
|
|
946
|
+
|
|
947
|
+
completed_tasks = persistence.get_tasks_by_status(TaskStatus.COMPLETED)
|
|
948
|
+
|
|
949
|
+
wakeup_prefixes = [
|
|
950
|
+
"VERIFY_IDENTITY",
|
|
951
|
+
"VALIDATE_INTEGRITY",
|
|
952
|
+
"EVALUATE_RESILIENCE",
|
|
953
|
+
"ACCEPT_INCOMPLETENESS",
|
|
954
|
+
"EXPRESS_GRATITUDE",
|
|
955
|
+
]
|
|
956
|
+
|
|
957
|
+
count = sum(1 for task in completed_tasks if any(task.task_id.startswith(prefix) for prefix in wakeup_prefixes))
|
|
958
|
+
|
|
959
|
+
# If no wakeup tasks found but system has been running, assume standard cycle
|
|
960
|
+
if count == 0 and uptime > MIN_UPTIME_FOR_DEFAULT_TASKS:
|
|
961
|
+
return 5 # Standard wakeup cycle completes 5 tasks
|
|
962
|
+
|
|
963
|
+
return count
|
|
964
|
+
except Exception as e:
|
|
965
|
+
logger.warning(f"Failed to count completed tasks: {e}")
|
|
966
|
+
return 0
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
def _count_active_services(service_registry: Any) -> Tuple[int, JSONDict]:
|
|
970
|
+
"""Count active services and get multi-provider service details."""
|
|
971
|
+
multi_provider_count = 0
|
|
972
|
+
multi_provider_services: JSONDict = {}
|
|
973
|
+
|
|
974
|
+
if service_registry:
|
|
975
|
+
from ciris_engine.schemas.runtime.enums import ServiceType
|
|
976
|
+
|
|
977
|
+
for service_type in list(ServiceType):
|
|
978
|
+
providers = service_registry.get_services_by_type(service_type)
|
|
979
|
+
count = len(providers)
|
|
980
|
+
if count > 0:
|
|
981
|
+
multi_provider_count += count
|
|
982
|
+
multi_provider_services[service_type.value] = {"providers": count, "type": "multi-provider"}
|
|
983
|
+
|
|
984
|
+
# CIRIS has AT LEAST 19 service types:
|
|
985
|
+
# Multi-provider services can have multiple instances + 12 singleton services
|
|
986
|
+
services_active = multi_provider_count + 12
|
|
987
|
+
|
|
988
|
+
return services_active, multi_provider_services
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
def _get_admin_channels(auth: AuthContext, request: Request) -> List[str]:
|
|
992
|
+
"""Get additional admin channels for privileged users."""
|
|
993
|
+
channels = []
|
|
994
|
+
if auth.role in ["ADMIN", "AUTHORITY", "SYSTEM_ADMIN"]:
|
|
995
|
+
# Get default API channel from config
|
|
996
|
+
api_host = getattr(request.app.state, "api_host", "127.0.0.1")
|
|
997
|
+
api_port = getattr(request.app.state, "api_port", "8080")
|
|
998
|
+
default_channel = f"api_{api_host}_{api_port}"
|
|
999
|
+
channels.append(default_channel)
|
|
1000
|
+
|
|
1001
|
+
# Add common variations of the API channel
|
|
1002
|
+
channels.extend(
|
|
1003
|
+
[
|
|
1004
|
+
f"api_0.0.0.0_{api_port}", # Bind address
|
|
1005
|
+
f"api_127.0.0.1_{api_port}", # Localhost
|
|
1006
|
+
f"api_localhost_{api_port}", # Hostname variant
|
|
1007
|
+
]
|
|
1008
|
+
)
|
|
1009
|
+
return channels
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
def _build_channels_to_query(auth: AuthContext, request: Request) -> List[str]:
|
|
1013
|
+
"""Build list of channels to query for conversation history."""
|
|
1014
|
+
channel_id = f"api_{auth.user_id}"
|
|
1015
|
+
channels_to_query = [channel_id]
|
|
1016
|
+
channels_to_query.extend(_get_admin_channels(auth, request))
|
|
1017
|
+
# Remove duplicates while preserving order
|
|
1018
|
+
seen = set()
|
|
1019
|
+
deduped = []
|
|
1020
|
+
for channel in channels_to_query:
|
|
1021
|
+
if channel not in seen:
|
|
1022
|
+
seen.add(channel)
|
|
1023
|
+
deduped.append(channel)
|
|
1024
|
+
return deduped
|
|
1025
|
+
|
|
1026
|
+
|
|
1027
|
+
def _convert_timestamp(timestamp: Any) -> datetime:
|
|
1028
|
+
"""Convert timestamp string or datetime to datetime object."""
|
|
1029
|
+
if isinstance(timestamp, str):
|
|
1030
|
+
try:
|
|
1031
|
+
return datetime.fromisoformat(timestamp)
|
|
1032
|
+
except (ValueError, TypeError):
|
|
1033
|
+
return datetime.now(timezone.utc)
|
|
1034
|
+
elif isinstance(timestamp, datetime):
|
|
1035
|
+
return timestamp
|
|
1036
|
+
else:
|
|
1037
|
+
return datetime.now(timezone.utc)
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
def _create_conversation_message_from_mock(msg: JSONDict, is_response: bool = False) -> ConversationMessage:
|
|
1041
|
+
"""Create ConversationMessage from mock message data."""
|
|
1042
|
+
if is_response:
|
|
1043
|
+
return ConversationMessage(
|
|
1044
|
+
id=f"{msg['message_id']}_response",
|
|
1045
|
+
author="Scout",
|
|
1046
|
+
content=msg["response"],
|
|
1047
|
+
timestamp=_convert_timestamp(msg["timestamp"]),
|
|
1048
|
+
is_agent=True,
|
|
1049
|
+
)
|
|
1050
|
+
else:
|
|
1051
|
+
return ConversationMessage(
|
|
1052
|
+
id=msg["message_id"],
|
|
1053
|
+
author=msg["author_id"],
|
|
1054
|
+
content=msg["content"],
|
|
1055
|
+
timestamp=_convert_timestamp(msg["timestamp"]),
|
|
1056
|
+
is_agent=False,
|
|
1057
|
+
)
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
def _expand_mock_messages(user_messages: List[JSONDict]) -> List[ConversationMessage]:
|
|
1061
|
+
"""Expand mock messages into user message + response pairs."""
|
|
1062
|
+
all_messages = []
|
|
1063
|
+
for msg in user_messages:
|
|
1064
|
+
# Add user message
|
|
1065
|
+
all_messages.append(_create_conversation_message_from_mock(msg))
|
|
1066
|
+
# Add agent response if exists
|
|
1067
|
+
if msg.get("response"):
|
|
1068
|
+
all_messages.append(_create_conversation_message_from_mock(msg, is_response=True))
|
|
1069
|
+
return all_messages
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
def _apply_message_limit(messages: List[ConversationMessage], limit: int) -> List[ConversationMessage]:
|
|
1073
|
+
"""Apply message limit, taking the last N messages."""
|
|
1074
|
+
if len(messages) > limit:
|
|
1075
|
+
return messages[-limit:]
|
|
1076
|
+
return messages
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
async def _get_history_from_mock(
|
|
1080
|
+
message_history: List[JSONDict], channels_to_query: List[str], limit: int
|
|
1081
|
+
) -> ConversationHistory:
|
|
1082
|
+
"""Process conversation history from mock data."""
|
|
1083
|
+
# Filter messages for requested channels
|
|
1084
|
+
user_messages = [m for m in message_history if m.get("channel_id") in channels_to_query]
|
|
1085
|
+
|
|
1086
|
+
# Expand all messages (user + response pairs)
|
|
1087
|
+
all_messages = _expand_mock_messages(user_messages)
|
|
1088
|
+
|
|
1089
|
+
# Apply limit
|
|
1090
|
+
limited_messages = _apply_message_limit(all_messages, limit)
|
|
1091
|
+
|
|
1092
|
+
return ConversationHistory(
|
|
1093
|
+
messages=limited_messages,
|
|
1094
|
+
total_count=len(user_messages),
|
|
1095
|
+
has_more=len(user_messages) > len(limited_messages),
|
|
1096
|
+
)
|
|
1097
|
+
|
|
1098
|
+
|
|
1099
|
+
async def _get_history_from_memory(memory_service: Any, channel_id: str, limit: int) -> ConversationHistory:
|
|
1100
|
+
"""Query conversation history from memory service."""
|
|
1101
|
+
from ciris_engine.schemas.services.graph_core import GraphScope, NodeType
|
|
1102
|
+
from ciris_engine.schemas.services.operations import MemoryQuery
|
|
1103
|
+
|
|
1104
|
+
query = MemoryQuery(
|
|
1105
|
+
node_id=f"conversation_{channel_id}",
|
|
1106
|
+
scope=GraphScope.LOCAL,
|
|
1107
|
+
type=NodeType.CONVERSATION_SUMMARY,
|
|
1108
|
+
include_edges=True,
|
|
1109
|
+
depth=1,
|
|
1110
|
+
)
|
|
1111
|
+
|
|
1112
|
+
nodes = await memory_service.recall(query)
|
|
1113
|
+
|
|
1114
|
+
# Convert to conversation messages
|
|
1115
|
+
messages = []
|
|
1116
|
+
for node in nodes:
|
|
1117
|
+
attrs = node.attributes
|
|
1118
|
+
messages.append(
|
|
1119
|
+
ConversationMessage(
|
|
1120
|
+
id=attrs.get("message_id", node.id),
|
|
1121
|
+
author=attrs.get("author", "unknown"),
|
|
1122
|
+
content=attrs.get("content", ""),
|
|
1123
|
+
timestamp=datetime.fromisoformat(attrs.get("timestamp", node.created_at)),
|
|
1124
|
+
is_agent=attrs.get("is_agent", False),
|
|
1125
|
+
)
|
|
1126
|
+
)
|
|
1127
|
+
|
|
1128
|
+
return ConversationHistory(messages=messages, total_count=len(messages), has_more=len(messages) == limit)
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
def _safe_convert_message_timestamp(msg: Any) -> datetime:
|
|
1132
|
+
"""Safely convert message timestamp with fallback."""
|
|
1133
|
+
timestamp_val = msg.timestamp
|
|
1134
|
+
if isinstance(timestamp_val, datetime):
|
|
1135
|
+
return timestamp_val
|
|
1136
|
+
elif timestamp_val:
|
|
1137
|
+
try:
|
|
1138
|
+
return datetime.fromisoformat(str(timestamp_val))
|
|
1139
|
+
except (ValueError, TypeError):
|
|
1140
|
+
pass
|
|
1141
|
+
return datetime.now(timezone.utc)
|
|
1142
|
+
|
|
1143
|
+
|
|
1144
|
+
def _convert_service_message_to_conversation(msg: Any) -> ConversationMessage:
|
|
1145
|
+
"""Convert communication service message to ConversationMessage."""
|
|
1146
|
+
return ConversationMessage(
|
|
1147
|
+
id=str(msg.message_id or ""),
|
|
1148
|
+
author=str(msg.author_name or msg.author_id or ""),
|
|
1149
|
+
content=str(msg.content or ""),
|
|
1150
|
+
timestamp=_safe_convert_message_timestamp(msg),
|
|
1151
|
+
is_agent=bool(getattr(msg, "is_agent_message", False) or getattr(msg, "is_bot", False)),
|
|
1152
|
+
)
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
async def _fetch_messages_from_channels(comm_service: Any, channels_to_query: List[str], fetch_limit: int) -> List[Any]:
|
|
1156
|
+
"""Fetch messages from all specified channels."""
|
|
1157
|
+
fetched_messages = []
|
|
1158
|
+
for channel in channels_to_query:
|
|
1159
|
+
try:
|
|
1160
|
+
logger.info(f"Fetching messages from channel: {channel}")
|
|
1161
|
+
if comm_service is None:
|
|
1162
|
+
logger.warning("Communication service is not available")
|
|
1163
|
+
continue
|
|
1164
|
+
channel_messages = await comm_service.fetch_messages(channel, limit=fetch_limit)
|
|
1165
|
+
logger.info(f"Retrieved {len(channel_messages)} messages from {channel}")
|
|
1166
|
+
fetched_messages.extend(channel_messages)
|
|
1167
|
+
except Exception as e:
|
|
1168
|
+
logger.warning(f"Failed to fetch from channel {channel}: {e}")
|
|
1169
|
+
continue
|
|
1170
|
+
return fetched_messages
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
def _sort_and_filter_messages(fetched_messages: List[Any], before: Optional[datetime]) -> List[Any]:
|
|
1174
|
+
"""Sort messages by timestamp and apply time filter."""
|
|
1175
|
+
# Sort messages by timestamp (newest first)
|
|
1176
|
+
sorted_messages = sorted(
|
|
1177
|
+
fetched_messages,
|
|
1178
|
+
key=lambda m: _safe_convert_message_timestamp(m),
|
|
1179
|
+
reverse=True,
|
|
1180
|
+
)
|
|
1181
|
+
|
|
1182
|
+
# Filter by time if specified
|
|
1183
|
+
if before:
|
|
1184
|
+
return [m for m in sorted_messages if _safe_convert_message_timestamp(m) < before]
|
|
1185
|
+
return sorted_messages
|
|
1186
|
+
|
|
1187
|
+
|
|
1188
|
+
async def _get_history_from_communication_service(
|
|
1189
|
+
comm_service: Any, channels_to_query: List[str], limit: int, before: Optional[datetime]
|
|
1190
|
+
) -> ConversationHistory:
|
|
1191
|
+
"""Get conversation history from communication service."""
|
|
1192
|
+
# Fetch more messages to allow filtering
|
|
1193
|
+
fetch_limit = limit * 2 if before else limit
|
|
1194
|
+
|
|
1195
|
+
# Fetch messages from all relevant channels
|
|
1196
|
+
fetched_messages = await _fetch_messages_from_channels(comm_service, channels_to_query, fetch_limit)
|
|
1197
|
+
|
|
1198
|
+
# Sort and filter messages
|
|
1199
|
+
filtered_messages = _sort_and_filter_messages(fetched_messages, before)
|
|
1200
|
+
|
|
1201
|
+
# Convert to conversation messages and apply final limit
|
|
1202
|
+
conv_messages = [_convert_service_message_to_conversation(msg) for msg in filtered_messages[:limit]]
|
|
1203
|
+
|
|
1204
|
+
return ConversationHistory(
|
|
1205
|
+
messages=conv_messages,
|
|
1206
|
+
total_count=len(filtered_messages),
|
|
1207
|
+
has_more=len(filtered_messages) > limit,
|
|
1208
|
+
)
|
|
1209
|
+
|
|
1210
|
+
|
|
1211
|
+
def _get_current_task_info(request: Request) -> Optional[str]:
|
|
1212
|
+
"""Get current task information from task scheduler."""
|
|
1213
|
+
import inspect
|
|
1214
|
+
|
|
1215
|
+
task_scheduler = getattr(request.app.state, "task_scheduler", None)
|
|
1216
|
+
if task_scheduler and hasattr(task_scheduler, "get_current_task"):
|
|
1217
|
+
task = task_scheduler.get_current_task()
|
|
1218
|
+
# If the result is a coroutine (from AsyncMock in tests), close it and ignore
|
|
1219
|
+
# since the real TaskSchedulerService doesn't have this method
|
|
1220
|
+
if inspect.iscoroutine(task):
|
|
1221
|
+
task.close() # Properly close the coroutine to avoid warning
|
|
1222
|
+
return None
|
|
1223
|
+
return str(task) if task is not None else None
|
|
1224
|
+
return None
|
|
1225
|
+
|
|
1226
|
+
|
|
1227
|
+
def _get_memory_usage(request: Request) -> float:
|
|
1228
|
+
"""Get current memory usage from resource monitor."""
|
|
1229
|
+
resource_monitor = getattr(request.app.state, "resource_monitor", None)
|
|
1230
|
+
if resource_monitor and hasattr(resource_monitor, "snapshot"):
|
|
1231
|
+
return float(resource_monitor.snapshot.memory_mb)
|
|
1232
|
+
return 0.0
|
|
1233
|
+
|
|
1234
|
+
|
|
1235
|
+
def _get_version_info() -> Tuple[str, str, Optional[str]]:
|
|
1236
|
+
"""Get version information including codename and code hash."""
|
|
1237
|
+
from ciris_engine.constants import CIRIS_CODENAME, CIRIS_VERSION
|
|
1238
|
+
|
|
1239
|
+
try:
|
|
1240
|
+
from version import __version__ as code_hash_val
|
|
1241
|
+
|
|
1242
|
+
code_hash: Optional[str] = code_hash_val
|
|
1243
|
+
except ImportError:
|
|
1244
|
+
code_hash = None
|
|
1245
|
+
|
|
1246
|
+
return CIRIS_VERSION, CIRIS_CODENAME, code_hash
|
|
1247
|
+
|
|
1248
|
+
|
|
1249
|
+
async def _build_agent_status(
|
|
1250
|
+
request: Request, cognitive_state: str, uptime: float, messages_processed: int, runtime: Any
|
|
1251
|
+
) -> AgentStatus:
|
|
1252
|
+
"""Build AgentStatus object with all required information."""
|
|
1253
|
+
# Get current task (synchronous call, not awaitable)
|
|
1254
|
+
current_task = _get_current_task_info(request)
|
|
1255
|
+
|
|
1256
|
+
# Get resource usage
|
|
1257
|
+
memory_usage_mb = _get_memory_usage(request)
|
|
1258
|
+
|
|
1259
|
+
# Count services
|
|
1260
|
+
service_registry = getattr(request.app.state, "service_registry", None)
|
|
1261
|
+
services_active, multi_provider_services = _count_active_services(service_registry)
|
|
1262
|
+
|
|
1263
|
+
# Get identity
|
|
1264
|
+
agent_id, agent_name = _get_agent_identity_info(runtime)
|
|
1265
|
+
|
|
1266
|
+
# Get version information
|
|
1267
|
+
version, codename, code_hash = _get_version_info()
|
|
1268
|
+
|
|
1269
|
+
return AgentStatus(
|
|
1270
|
+
agent_id=agent_id,
|
|
1271
|
+
name=agent_name,
|
|
1272
|
+
version=version,
|
|
1273
|
+
codename=codename,
|
|
1274
|
+
code_hash=code_hash,
|
|
1275
|
+
cognitive_state=cognitive_state,
|
|
1276
|
+
uptime_seconds=uptime,
|
|
1277
|
+
messages_processed=messages_processed,
|
|
1278
|
+
last_activity=datetime.now(timezone.utc),
|
|
1279
|
+
current_task=current_task,
|
|
1280
|
+
services_active=services_active,
|
|
1281
|
+
memory_usage_mb=memory_usage_mb,
|
|
1282
|
+
multi_provider_services=multi_provider_services,
|
|
1283
|
+
)
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
def _get_agent_identity_info(runtime: Any) -> Tuple[str, str]:
|
|
1287
|
+
"""Get agent ID and name."""
|
|
1288
|
+
agent_id = "ciris_agent"
|
|
1289
|
+
agent_name = "CIRIS"
|
|
1290
|
+
|
|
1291
|
+
if hasattr(runtime, "agent_identity") and runtime.agent_identity:
|
|
1292
|
+
agent_id = runtime.agent_identity.agent_id
|
|
1293
|
+
# Try to get name from various sources
|
|
1294
|
+
if hasattr(runtime.agent_identity, "name"):
|
|
1295
|
+
agent_name = runtime.agent_identity.name
|
|
1296
|
+
elif hasattr(runtime.agent_identity, "core_profile"):
|
|
1297
|
+
# Use first part of description or role as name
|
|
1298
|
+
agent_name = runtime.agent_identity.core_profile.description.split(".")[0]
|
|
1299
|
+
|
|
1300
|
+
return agent_id, agent_name
|
|
1301
|
+
|
|
1302
|
+
|
|
1303
|
+
@router.get("/status", response_model=SuccessResponse[AgentStatus])
|
|
1304
|
+
async def get_status(request: Request, auth: AuthContext = Depends(require_observer)) -> SuccessResponse[AgentStatus]:
|
|
1305
|
+
"""
|
|
1306
|
+
Agent status and cognitive state.
|
|
1307
|
+
|
|
1308
|
+
Get comprehensive agent status including state, metrics, and current activity.
|
|
1309
|
+
"""
|
|
1310
|
+
runtime = getattr(request.app.state, "runtime", None)
|
|
1311
|
+
if not runtime:
|
|
1312
|
+
raise HTTPException(status_code=503, detail="Runtime not available")
|
|
1313
|
+
|
|
1314
|
+
try:
|
|
1315
|
+
# Get basic state information
|
|
1316
|
+
cognitive_state = _get_cognitive_state(runtime)
|
|
1317
|
+
time_service = getattr(request.app.state, "time_service", None)
|
|
1318
|
+
uptime = _calculate_uptime(time_service)
|
|
1319
|
+
messages_processed = _count_wakeup_tasks(uptime)
|
|
1320
|
+
|
|
1321
|
+
# Build comprehensive status
|
|
1322
|
+
status = await _build_agent_status(request, cognitive_state, uptime, messages_processed, runtime)
|
|
1323
|
+
return SuccessResponse(data=status)
|
|
1324
|
+
|
|
1325
|
+
except Exception as e:
|
|
1326
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
1327
|
+
|
|
1328
|
+
|
|
1329
|
+
@router.get("/identity", response_model=SuccessResponse[AgentIdentity])
|
|
1330
|
+
async def get_identity(
|
|
1331
|
+
request: Request, auth: AuthContext = Depends(require_observer)
|
|
1332
|
+
) -> SuccessResponse[AgentIdentity]:
|
|
1333
|
+
"""
|
|
1334
|
+
Agent identity and capabilities.
|
|
1335
|
+
|
|
1336
|
+
Get comprehensive agent identity including capabilities, tools, and permissions.
|
|
1337
|
+
"""
|
|
1338
|
+
# Get memory service to query identity
|
|
1339
|
+
memory_service = getattr(request.app.state, "memory_service", None)
|
|
1340
|
+
if not memory_service:
|
|
1341
|
+
raise HTTPException(status_code=503, detail=ERROR_MEMORY_SERVICE_NOT_AVAILABLE)
|
|
1342
|
+
|
|
1343
|
+
try:
|
|
1344
|
+
# Query identity from graph
|
|
1345
|
+
from ciris_engine.schemas.services.graph_core import GraphScope
|
|
1346
|
+
from ciris_engine.schemas.services.operations import MemoryQuery
|
|
1347
|
+
|
|
1348
|
+
query = MemoryQuery(node_id="agent/identity", scope=GraphScope.IDENTITY, include_edges=False)
|
|
1349
|
+
|
|
1350
|
+
nodes = await memory_service.recall(query)
|
|
1351
|
+
|
|
1352
|
+
# Get identity data
|
|
1353
|
+
identity_data = {}
|
|
1354
|
+
if nodes:
|
|
1355
|
+
identity_node = nodes[0]
|
|
1356
|
+
identity_data = identity_node.attributes
|
|
1357
|
+
else:
|
|
1358
|
+
# Fallback to runtime identity
|
|
1359
|
+
runtime = getattr(request.app.state, "runtime", None)
|
|
1360
|
+
if runtime and hasattr(runtime, "agent_identity"):
|
|
1361
|
+
identity = runtime.agent_identity
|
|
1362
|
+
identity_data = {
|
|
1363
|
+
"agent_id": identity.agent_id,
|
|
1364
|
+
"name": getattr(identity, "name", identity.core_profile.description.split(".")[0]),
|
|
1365
|
+
"purpose": getattr(identity, "purpose", identity.core_profile.description),
|
|
1366
|
+
"created_at": identity.identity_metadata.created_at.isoformat(),
|
|
1367
|
+
"lineage": {
|
|
1368
|
+
"model": identity.identity_metadata.model,
|
|
1369
|
+
"version": identity.identity_metadata.version,
|
|
1370
|
+
"parent_id": getattr(identity.identity_metadata, "parent_id", None),
|
|
1371
|
+
"creation_context": getattr(identity.identity_metadata, "creation_context", "default"),
|
|
1372
|
+
"adaptations": getattr(identity.identity_metadata, "adaptations", []),
|
|
1373
|
+
},
|
|
1374
|
+
"variance_threshold": 0.2,
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
# Get capabilities
|
|
1378
|
+
|
|
1379
|
+
# Get tool service for available tools
|
|
1380
|
+
tool_service = getattr(request.app.state, "tool_service", None)
|
|
1381
|
+
tools = []
|
|
1382
|
+
if tool_service:
|
|
1383
|
+
tools = await tool_service.list_tools()
|
|
1384
|
+
|
|
1385
|
+
# Get handlers (these are the core action handlers)
|
|
1386
|
+
handlers = [
|
|
1387
|
+
"observe",
|
|
1388
|
+
"speak",
|
|
1389
|
+
"tool",
|
|
1390
|
+
"reject",
|
|
1391
|
+
"ponder",
|
|
1392
|
+
"defer",
|
|
1393
|
+
"memorize",
|
|
1394
|
+
"recall",
|
|
1395
|
+
"forget",
|
|
1396
|
+
"task_complete",
|
|
1397
|
+
]
|
|
1398
|
+
|
|
1399
|
+
# Get service availability
|
|
1400
|
+
services = ServiceAvailability()
|
|
1401
|
+
service_registry = getattr(request.app.state, "service_registry", None)
|
|
1402
|
+
if service_registry:
|
|
1403
|
+
from ciris_engine.schemas.runtime.enums import ServiceType
|
|
1404
|
+
|
|
1405
|
+
for service_type in ServiceType:
|
|
1406
|
+
providers = service_registry.get_services_by_type(service_type)
|
|
1407
|
+
count = len(providers)
|
|
1408
|
+
# Map to service categories
|
|
1409
|
+
if "graph" in service_type.value.lower() or service_type.value == "MEMORY":
|
|
1410
|
+
services.graph += count
|
|
1411
|
+
elif service_type.value in ["LLM", "SECRETS"]:
|
|
1412
|
+
services.core += count
|
|
1413
|
+
elif service_type.value in [
|
|
1414
|
+
"TIME",
|
|
1415
|
+
"SHUTDOWN",
|
|
1416
|
+
"INITIALIZATION",
|
|
1417
|
+
"VISIBILITY",
|
|
1418
|
+
"AUTHENTICATION",
|
|
1419
|
+
"RESOURCE_MONITOR",
|
|
1420
|
+
"RUNTIME_CONTROL",
|
|
1421
|
+
]:
|
|
1422
|
+
services.infrastructure += count
|
|
1423
|
+
elif service_type.value == "WISE_AUTHORITY":
|
|
1424
|
+
services.governance += count
|
|
1425
|
+
else:
|
|
1426
|
+
services.special += count
|
|
1427
|
+
|
|
1428
|
+
# Get permissions (agent's core capabilities)
|
|
1429
|
+
permissions = ["communicate", "use_tools", "access_memory", "observe_environment", "learn", "adapt"]
|
|
1430
|
+
|
|
1431
|
+
# Build response
|
|
1432
|
+
lineage_data = identity_data.get("lineage", {})
|
|
1433
|
+
lineage = AgentLineage(
|
|
1434
|
+
model=lineage_data.get("model", "unknown"),
|
|
1435
|
+
version=lineage_data.get("version", "1.0"),
|
|
1436
|
+
parent_id=lineage_data.get("parent_id"),
|
|
1437
|
+
creation_context=lineage_data.get("creation_context", "default"),
|
|
1438
|
+
adaptations=lineage_data.get("adaptations", []),
|
|
1439
|
+
)
|
|
1440
|
+
|
|
1441
|
+
response = AgentIdentity(
|
|
1442
|
+
agent_id=identity_data.get("agent_id", "ciris_agent"),
|
|
1443
|
+
name=identity_data.get("name", "CIRIS"),
|
|
1444
|
+
purpose=identity_data.get("purpose", "Autonomous AI agent"),
|
|
1445
|
+
created_at=datetime.fromisoformat(identity_data.get("created_at", datetime.now(timezone.utc).isoformat())),
|
|
1446
|
+
lineage=lineage,
|
|
1447
|
+
variance_threshold=identity_data.get("variance_threshold", 0.2),
|
|
1448
|
+
tools=tools,
|
|
1449
|
+
handlers=handlers,
|
|
1450
|
+
services=services,
|
|
1451
|
+
permissions=permissions,
|
|
1452
|
+
)
|
|
1453
|
+
|
|
1454
|
+
return SuccessResponse(data=response)
|
|
1455
|
+
|
|
1456
|
+
except HTTPException:
|
|
1457
|
+
raise
|
|
1458
|
+
except Exception as e:
|
|
1459
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
1460
|
+
|
|
1461
|
+
|
|
1462
|
+
def _convert_to_channel_info(ch: Any, adapter_type: str) -> ChannelInfo:
|
|
1463
|
+
"""Convert adapter channel data to ChannelInfo format."""
|
|
1464
|
+
if hasattr(ch, "channel_id"):
|
|
1465
|
+
# Pydantic model format
|
|
1466
|
+
return ChannelInfo(
|
|
1467
|
+
channel_id=ch.channel_id,
|
|
1468
|
+
channel_type=getattr(ch, "channel_type", adapter_type),
|
|
1469
|
+
display_name=getattr(ch, "display_name", ch.channel_id),
|
|
1470
|
+
is_active=getattr(ch, "is_active", True),
|
|
1471
|
+
created_at=getattr(ch, "created_at", None),
|
|
1472
|
+
last_activity=getattr(ch, "last_activity", None),
|
|
1473
|
+
message_count=getattr(ch, "message_count", 0),
|
|
1474
|
+
)
|
|
1475
|
+
else:
|
|
1476
|
+
# Dict format (legacy)
|
|
1477
|
+
return ChannelInfo(
|
|
1478
|
+
channel_id=ch.get("channel_id", ""),
|
|
1479
|
+
channel_type=ch.get("channel_type", adapter_type),
|
|
1480
|
+
display_name=ch.get("display_name", ch.get("channel_id", "")),
|
|
1481
|
+
is_active=ch.get("is_active", True),
|
|
1482
|
+
created_at=ch.get("created_at"),
|
|
1483
|
+
last_activity=ch.get("last_activity"),
|
|
1484
|
+
message_count=ch.get("message_count", 0),
|
|
1485
|
+
)
|
|
1486
|
+
|
|
1487
|
+
|
|
1488
|
+
async def _get_channels_from_adapter(adapter: Any, adapter_type: str) -> List[ChannelInfo]:
|
|
1489
|
+
"""Get channels from a single adapter."""
|
|
1490
|
+
channels = []
|
|
1491
|
+
if hasattr(adapter, "get_active_channels"):
|
|
1492
|
+
try:
|
|
1493
|
+
adapter_channels = await adapter.get_active_channels()
|
|
1494
|
+
for ch in adapter_channels:
|
|
1495
|
+
channels.append(_convert_to_channel_info(ch, adapter_type))
|
|
1496
|
+
except Exception as e:
|
|
1497
|
+
logger.error(f"Error getting channels from adapter {adapter_type}: {e}")
|
|
1498
|
+
return channels
|
|
1499
|
+
|
|
1500
|
+
|
|
1501
|
+
async def _get_channels_from_bootstrap_adapters(runtime: Any) -> List[ChannelInfo]:
|
|
1502
|
+
"""Get channels from bootstrap adapters."""
|
|
1503
|
+
channels = []
|
|
1504
|
+
if runtime and hasattr(runtime, "adapters"):
|
|
1505
|
+
logger.info(f"Checking {len(runtime.adapters)} bootstrap adapters for channels")
|
|
1506
|
+
for adapter in runtime.adapters:
|
|
1507
|
+
adapter_type = adapter.__class__.__name__.lower().replace("platform", "")
|
|
1508
|
+
channels.extend(await _get_channels_from_adapter(adapter, adapter_type))
|
|
1509
|
+
return channels
|
|
1510
|
+
|
|
1511
|
+
|
|
1512
|
+
def _get_control_service(runtime: Any, request: Request) -> Any:
|
|
1513
|
+
"""Get the runtime control service from app state or registry."""
|
|
1514
|
+
# Try app state first
|
|
1515
|
+
control_service = getattr(request.app.state, "main_runtime_control_service", None)
|
|
1516
|
+
if control_service:
|
|
1517
|
+
return control_service
|
|
1518
|
+
|
|
1519
|
+
# Fallback to service registry
|
|
1520
|
+
if not runtime or not hasattr(runtime, "service_registry") or not runtime.service_registry:
|
|
1521
|
+
return None
|
|
1522
|
+
|
|
1523
|
+
from ciris_engine.schemas.runtime.enums import ServiceType
|
|
1524
|
+
|
|
1525
|
+
providers = runtime.service_registry.get_services_by_type(ServiceType.RUNTIME_CONTROL)
|
|
1526
|
+
return providers[0] if providers else None
|
|
1527
|
+
|
|
1528
|
+
|
|
1529
|
+
def _get_adapter_manager(control_service: Any) -> Any:
|
|
1530
|
+
"""Get adapter manager from control service."""
|
|
1531
|
+
if not control_service:
|
|
1532
|
+
return None
|
|
1533
|
+
if not hasattr(control_service, "adapter_manager"):
|
|
1534
|
+
return None
|
|
1535
|
+
return control_service.adapter_manager
|
|
1536
|
+
|
|
1537
|
+
|
|
1538
|
+
async def _collect_unique_channels(adapter_manager: Any) -> List[ChannelInfo]:
|
|
1539
|
+
"""Collect unique channels from loaded adapters."""
|
|
1540
|
+
if not adapter_manager or not hasattr(adapter_manager, "loaded_adapters"):
|
|
1541
|
+
return []
|
|
1542
|
+
|
|
1543
|
+
channels = []
|
|
1544
|
+
seen_channel_ids = set()
|
|
1545
|
+
|
|
1546
|
+
for adapter_id, instance in adapter_manager.loaded_adapters.items():
|
|
1547
|
+
adapter_channels = await _get_channels_from_adapter(instance.adapter, instance.adapter_type)
|
|
1548
|
+
|
|
1549
|
+
# Add only unique channels
|
|
1550
|
+
for ch in adapter_channels:
|
|
1551
|
+
if ch.channel_id not in seen_channel_ids:
|
|
1552
|
+
channels.append(ch)
|
|
1553
|
+
seen_channel_ids.add(ch.channel_id)
|
|
1554
|
+
|
|
1555
|
+
return channels
|
|
1556
|
+
|
|
1557
|
+
|
|
1558
|
+
async def _get_channels_from_dynamic_adapters(runtime: Any, request: Request) -> List[ChannelInfo]:
|
|
1559
|
+
"""Get channels from dynamically loaded adapters."""
|
|
1560
|
+
control_service = _get_control_service(runtime, request)
|
|
1561
|
+
adapter_manager = _get_adapter_manager(control_service)
|
|
1562
|
+
return await _collect_unique_channels(adapter_manager)
|
|
1563
|
+
|
|
1564
|
+
|
|
1565
|
+
def _add_default_api_channels(channels: List[ChannelInfo], request: Request, auth: AuthContext) -> None:
|
|
1566
|
+
"""Add default API channels if not already present."""
|
|
1567
|
+
# Default API channel
|
|
1568
|
+
api_host = getattr(request.app.state, "api_host", "127.0.0.1")
|
|
1569
|
+
api_port = getattr(request.app.state, "api_port", "8080")
|
|
1570
|
+
api_channel_id = f"api_{api_host}_{api_port}"
|
|
1571
|
+
|
|
1572
|
+
if not any(ch.channel_id == api_channel_id for ch in channels):
|
|
1573
|
+
channels.append(
|
|
1574
|
+
ChannelInfo(
|
|
1575
|
+
channel_id=api_channel_id,
|
|
1576
|
+
channel_type="api",
|
|
1577
|
+
display_name=f"API Channel ({api_host}:{api_port})",
|
|
1578
|
+
is_active=True,
|
|
1579
|
+
created_at=None,
|
|
1580
|
+
last_activity=datetime.now(timezone.utc),
|
|
1581
|
+
message_count=0,
|
|
1582
|
+
)
|
|
1583
|
+
)
|
|
1584
|
+
|
|
1585
|
+
# User-specific API channel
|
|
1586
|
+
user_channel_id = f"api_{auth.user_id}"
|
|
1587
|
+
if not any(ch.channel_id == user_channel_id for ch in channels):
|
|
1588
|
+
channels.append(
|
|
1589
|
+
ChannelInfo(
|
|
1590
|
+
channel_id=user_channel_id,
|
|
1591
|
+
channel_type="api",
|
|
1592
|
+
display_name=f"API Channel ({auth.user_id})",
|
|
1593
|
+
is_active=True,
|
|
1594
|
+
created_at=None,
|
|
1595
|
+
last_activity=None,
|
|
1596
|
+
message_count=0,
|
|
1597
|
+
)
|
|
1598
|
+
)
|
|
1599
|
+
|
|
1600
|
+
|
|
1601
|
+
@router.get("/channels", response_model=SuccessResponse[ChannelList])
|
|
1602
|
+
async def get_channels(request: Request, auth: AuthContext = Depends(require_observer)) -> SuccessResponse[ChannelList]:
|
|
1603
|
+
"""
|
|
1604
|
+
List active communication channels.
|
|
1605
|
+
|
|
1606
|
+
Get all channels where the agent is currently active or has been active.
|
|
1607
|
+
"""
|
|
1608
|
+
try:
|
|
1609
|
+
channels = []
|
|
1610
|
+
runtime = getattr(request.app.state, "runtime", None)
|
|
1611
|
+
|
|
1612
|
+
# Get channels from bootstrap adapters
|
|
1613
|
+
channels.extend(await _get_channels_from_bootstrap_adapters(runtime))
|
|
1614
|
+
|
|
1615
|
+
# Get channels from dynamically loaded adapters
|
|
1616
|
+
dynamic_channels = await _get_channels_from_dynamic_adapters(runtime, request)
|
|
1617
|
+
channels.extend(dynamic_channels)
|
|
1618
|
+
|
|
1619
|
+
# Add default API channels
|
|
1620
|
+
_add_default_api_channels(channels, request, auth)
|
|
1621
|
+
|
|
1622
|
+
# Sort channels by type and then by id
|
|
1623
|
+
channels.sort(key=lambda x: (x.channel_type, x.channel_id))
|
|
1624
|
+
|
|
1625
|
+
channel_list = ChannelList(channels=channels, total_count=len(channels))
|
|
1626
|
+
return SuccessResponse(data=channel_list)
|
|
1627
|
+
|
|
1628
|
+
except Exception as e:
|
|
1629
|
+
logger.error(f"Failed to get channels: {e}", exc_info=True)
|
|
1630
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
1631
|
+
|
|
1632
|
+
|
|
1633
|
+
# Helper function to notify interact responses
|
|
1634
|
+
async def notify_interact_response(message_id: str, content: str) -> None:
|
|
1635
|
+
"""Notify waiting interact requests of responses."""
|
|
1636
|
+
if message_id in _response_events:
|
|
1637
|
+
_message_responses[message_id] = content
|
|
1638
|
+
_response_events[message_id].set()
|
|
1639
|
+
|
|
1640
|
+
|
|
1641
|
+
def _validate_websocket_authorization(websocket: WebSocket) -> Optional[str]:
|
|
1642
|
+
"""Validate websocket authorization header and return API key."""
|
|
1643
|
+
authorization = websocket.headers.get("authorization")
|
|
1644
|
+
if not authorization:
|
|
1645
|
+
return None
|
|
1646
|
+
if not authorization.startswith("Bearer "):
|
|
1647
|
+
return None
|
|
1648
|
+
return authorization[7:] # Remove "Bearer " prefix
|
|
1649
|
+
|
|
1650
|
+
|
|
1651
|
+
async def _authenticate_websocket_user(websocket: WebSocket, api_key: str) -> Optional[AuthContext]:
|
|
1652
|
+
"""Authenticate websocket user and return auth context."""
|
|
1653
|
+
auth_service = getattr(websocket.app.state, "auth_service", None)
|
|
1654
|
+
if not auth_service:
|
|
1655
|
+
return None
|
|
1656
|
+
|
|
1657
|
+
key_info = auth_service.validate_api_key(api_key)
|
|
1658
|
+
if not key_info:
|
|
1659
|
+
return None
|
|
1660
|
+
|
|
1661
|
+
return AuthContext(
|
|
1662
|
+
user_id=key_info.user_id,
|
|
1663
|
+
role=key_info.role,
|
|
1664
|
+
permissions=ROLE_PERMISSIONS.get(key_info.role, set()),
|
|
1665
|
+
api_key_id=auth_service._get_key_id(api_key),
|
|
1666
|
+
authenticated_at=datetime.now(timezone.utc),
|
|
1667
|
+
)
|
|
1668
|
+
|
|
1669
|
+
|
|
1670
|
+
async def _handle_websocket_subscription_action(
|
|
1671
|
+
websocket: WebSocket, data: JSONDict, subscribed_channels: set[str]
|
|
1672
|
+
) -> None:
|
|
1673
|
+
"""Handle websocket subscribe/unsubscribe actions."""
|
|
1674
|
+
action = data.get("action")
|
|
1675
|
+
channels_raw = data.get("channels", [])
|
|
1676
|
+
# Type narrow to list for set operations
|
|
1677
|
+
channels = channels_raw if isinstance(channels_raw, list) else []
|
|
1678
|
+
|
|
1679
|
+
if action == "subscribe":
|
|
1680
|
+
subscribed_channels.update(channels)
|
|
1681
|
+
elif action == "unsubscribe":
|
|
1682
|
+
subscribed_channels.difference_update(channels)
|
|
1683
|
+
elif action == "ping":
|
|
1684
|
+
await websocket.send_json({"type": "pong", "timestamp": datetime.now(timezone.utc).isoformat()})
|
|
1685
|
+
return
|
|
1686
|
+
|
|
1687
|
+
# Send subscription update for subscribe/unsubscribe
|
|
1688
|
+
if action in ["subscribe", "unsubscribe"]:
|
|
1689
|
+
await websocket.send_json(
|
|
1690
|
+
{
|
|
1691
|
+
"type": "subscription_update",
|
|
1692
|
+
"channels": list(subscribed_channels),
|
|
1693
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
1694
|
+
}
|
|
1695
|
+
)
|
|
1696
|
+
|
|
1697
|
+
|
|
1698
|
+
def _register_websocket_client(websocket: WebSocket, client_id: str) -> None:
|
|
1699
|
+
"""Register websocket client with communication service."""
|
|
1700
|
+
comm_service = getattr(websocket.app.state, "communication_service", None)
|
|
1701
|
+
if comm_service and hasattr(comm_service, "register_websocket"):
|
|
1702
|
+
comm_service.register_websocket(client_id, websocket)
|
|
1703
|
+
|
|
1704
|
+
|
|
1705
|
+
def _unregister_websocket_client(websocket: WebSocket, client_id: str) -> None:
|
|
1706
|
+
"""Unregister websocket client from communication service."""
|
|
1707
|
+
comm_service = getattr(websocket.app.state, "communication_service", None)
|
|
1708
|
+
if comm_service and hasattr(comm_service, "unregister_websocket"):
|
|
1709
|
+
comm_service.unregister_websocket(client_id)
|
|
1710
|
+
|
|
1711
|
+
|
|
1712
|
+
# WebSocket endpoint for streaming
|
|
1713
|
+
|
|
1714
|
+
|
|
1715
|
+
@router.websocket("/stream")
|
|
1716
|
+
async def websocket_stream(
|
|
1717
|
+
websocket: WebSocket,
|
|
1718
|
+
) -> None:
|
|
1719
|
+
"""
|
|
1720
|
+
WebSocket endpoint for real-time updates.
|
|
1721
|
+
|
|
1722
|
+
Clients can subscribe to different channels:
|
|
1723
|
+
- messages: Agent messages and responses
|
|
1724
|
+
- telemetry: Real-time metrics
|
|
1725
|
+
- reasoning: Reasoning traces
|
|
1726
|
+
- logs: System logs
|
|
1727
|
+
"""
|
|
1728
|
+
# Validate authorization
|
|
1729
|
+
api_key = _validate_websocket_authorization(websocket)
|
|
1730
|
+
if not api_key:
|
|
1731
|
+
await websocket.close(code=1008, reason="Missing or invalid authorization header")
|
|
1732
|
+
return
|
|
1733
|
+
|
|
1734
|
+
# Authenticate user
|
|
1735
|
+
auth_context = await _authenticate_websocket_user(websocket, api_key)
|
|
1736
|
+
if not auth_context:
|
|
1737
|
+
await websocket.close(code=1008, reason="Authentication failed")
|
|
1738
|
+
return
|
|
1739
|
+
|
|
1740
|
+
# Check minimum role requirement (OBSERVER)
|
|
1741
|
+
if not auth_context.role.has_permission(UserRole.OBSERVER):
|
|
1742
|
+
await websocket.close(code=1008, reason="Insufficient permissions")
|
|
1743
|
+
return
|
|
1744
|
+
|
|
1745
|
+
await websocket.accept()
|
|
1746
|
+
client_id = f"ws_{id(websocket)}"
|
|
1747
|
+
|
|
1748
|
+
# Register websocket client
|
|
1749
|
+
_register_websocket_client(websocket, client_id)
|
|
1750
|
+
|
|
1751
|
+
subscribed_channels = set(["messages"]) # Default subscription
|
|
1752
|
+
|
|
1753
|
+
try:
|
|
1754
|
+
while True:
|
|
1755
|
+
# Receive and process client messages
|
|
1756
|
+
data = await websocket.receive_json()
|
|
1757
|
+
await _handle_websocket_subscription_action(websocket, data, subscribed_channels)
|
|
1758
|
+
|
|
1759
|
+
except WebSocketDisconnect:
|
|
1760
|
+
# Clean up on disconnect
|
|
1761
|
+
_unregister_websocket_client(websocket, client_id)
|
|
1762
|
+
logger.info(f"WebSocket client {client_id} disconnected")
|