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,1987 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Telemetry & Observability endpoints for CIRIS API v1.
|
|
3
|
+
|
|
4
|
+
Consolidated metrics, traces, logs, and insights from all system components.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import sys
|
|
9
|
+
import uuid
|
|
10
|
+
from collections import defaultdict
|
|
11
|
+
from datetime import datetime, timedelta, timezone
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from fastapi import APIRouter, Depends, HTTPException, Query, Request, Response
|
|
15
|
+
from pydantic import BaseModel, Field, field_serializer
|
|
16
|
+
|
|
17
|
+
from ciris_engine.schemas.api.auth import AuthContext
|
|
18
|
+
from ciris_engine.schemas.api.responses import ResponseMetadata, SuccessResponse
|
|
19
|
+
from ciris_engine.schemas.api.telemetry import (
|
|
20
|
+
APIResponseThoughtStep,
|
|
21
|
+
LogContext,
|
|
22
|
+
MetricTags,
|
|
23
|
+
QueryResult,
|
|
24
|
+
ServiceMetricValue,
|
|
25
|
+
TelemetryQueryFilters,
|
|
26
|
+
)
|
|
27
|
+
from ciris_engine.schemas.types import JSONDict
|
|
28
|
+
|
|
29
|
+
from ..constants import (
|
|
30
|
+
DESC_CURRENT_COGNITIVE_STATE,
|
|
31
|
+
DESC_END_TIME,
|
|
32
|
+
DESC_START_TIME,
|
|
33
|
+
ERROR_TELEMETRY_SERVICE_NOT_AVAILABLE,
|
|
34
|
+
)
|
|
35
|
+
from ..dependencies.auth import require_admin, require_observer
|
|
36
|
+
|
|
37
|
+
# Error message constants to avoid duplication
|
|
38
|
+
ERROR_TELEMETRY_NOT_INITIALIZED = "Critical system failure: Telemetry service not initialized"
|
|
39
|
+
ERROR_AUDIT_NOT_INITIALIZED = "Critical system failure: Audit service not initialized"
|
|
40
|
+
|
|
41
|
+
# Import extracted modules
|
|
42
|
+
from .telemetry_converters import convert_to_graphite, convert_to_prometheus
|
|
43
|
+
from .telemetry_helpers import get_telemetry_from_service
|
|
44
|
+
from .telemetry_models import (
|
|
45
|
+
LogEntry,
|
|
46
|
+
MetricData,
|
|
47
|
+
ResourceDataPoint,
|
|
48
|
+
ResourceHistoryResponse,
|
|
49
|
+
ResourceMetricData,
|
|
50
|
+
ResourceMetricStats,
|
|
51
|
+
ResourceUsage,
|
|
52
|
+
ServiceHealth,
|
|
53
|
+
SystemOverview,
|
|
54
|
+
TimePeriod,
|
|
55
|
+
)
|
|
56
|
+
from .telemetry_otlp import convert_logs_to_otlp_json, convert_to_otlp_json, convert_traces_to_otlp_json
|
|
57
|
+
from .telemetry_resource_helpers import (
|
|
58
|
+
MetricValueExtractor,
|
|
59
|
+
ResourceDataPointBuilder,
|
|
60
|
+
ResourceMetricBuilder,
|
|
61
|
+
ResourceMetricsCollector,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
logger = logging.getLogger(__name__)
|
|
65
|
+
|
|
66
|
+
router = APIRouter(prefix="/telemetry", tags=["telemetry"])
|
|
67
|
+
|
|
68
|
+
# Additional request/response schemas not in telemetry_models.py
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class DetailedMetric(BaseModel):
|
|
72
|
+
"""Detailed metric information."""
|
|
73
|
+
|
|
74
|
+
name: str = Field(..., description="Metric name")
|
|
75
|
+
current_value: float = Field(..., description="Current value")
|
|
76
|
+
unit: Optional[str] = Field(None, description="Metric unit")
|
|
77
|
+
trend: str = Field("stable", description="Trend: up|down|stable")
|
|
78
|
+
hourly_average: float = Field(0.0, description="Average over last hour")
|
|
79
|
+
daily_average: float = Field(0.0, description="Average over last day")
|
|
80
|
+
by_service: List[ServiceMetricValue] = Field(default_factory=list, description="Values by service")
|
|
81
|
+
recent_data: List[MetricData] = Field(default_factory=list, description="Recent data points")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class MetricAggregate(BaseModel):
|
|
85
|
+
"""Aggregated metric statistics."""
|
|
86
|
+
|
|
87
|
+
min: float = Field(0.0, description="Minimum value")
|
|
88
|
+
max: float = Field(0.0, description="Maximum value")
|
|
89
|
+
avg: float = Field(0.0, description="Average value")
|
|
90
|
+
sum: float = Field(0.0, description="Sum of values")
|
|
91
|
+
count: int = Field(0, description="Number of data points")
|
|
92
|
+
p50: Optional[float] = Field(None, description="50th percentile")
|
|
93
|
+
p95: Optional[float] = Field(None, description="95th percentile")
|
|
94
|
+
p99: Optional[float] = Field(None, description="99th percentile")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class MetricsResponse(BaseModel):
|
|
98
|
+
"""Detailed metrics response."""
|
|
99
|
+
|
|
100
|
+
metrics: List[DetailedMetric] = Field(..., description="Detailed metrics")
|
|
101
|
+
summary: MetricAggregate = Field(..., description="Summary statistics")
|
|
102
|
+
period: str = Field(..., description="Time period")
|
|
103
|
+
timestamp: datetime = Field(..., description="Response timestamp")
|
|
104
|
+
|
|
105
|
+
@field_serializer("timestamp")
|
|
106
|
+
def serialize_timestamp(self, timestamp: datetime, _info: Any) -> Optional[str]:
|
|
107
|
+
return timestamp.isoformat() if timestamp else None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ReasoningTraceData(BaseModel):
|
|
111
|
+
"""Reasoning trace information."""
|
|
112
|
+
|
|
113
|
+
trace_id: str = Field(..., description="Unique trace ID")
|
|
114
|
+
task_id: Optional[str] = Field(None, description="Associated task ID")
|
|
115
|
+
task_description: Optional[str] = Field(None, description="Task description")
|
|
116
|
+
start_time: datetime = Field(..., description="Trace start time")
|
|
117
|
+
duration_ms: float = Field(..., description="Total duration")
|
|
118
|
+
thought_count: int = Field(0, description="Number of thoughts")
|
|
119
|
+
decision_count: int = Field(0, description="Number of decisions")
|
|
120
|
+
reasoning_depth: int = Field(0, description="Maximum reasoning depth")
|
|
121
|
+
thoughts: List[APIResponseThoughtStep] = Field(default_factory=list, description="Thought steps")
|
|
122
|
+
outcome: Optional[str] = Field(None, description="Final outcome")
|
|
123
|
+
|
|
124
|
+
@field_serializer("start_time")
|
|
125
|
+
def serialize_timestamp(self, timestamp: datetime, _info: Any) -> Optional[str]:
|
|
126
|
+
return timestamp.isoformat() if timestamp else None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class TracesResponse(BaseModel):
|
|
130
|
+
"""Reasoning traces response."""
|
|
131
|
+
|
|
132
|
+
traces: List[ReasoningTraceData] = Field(..., description="Recent reasoning traces")
|
|
133
|
+
total: int = Field(..., description="Total trace count")
|
|
134
|
+
has_more: bool = Field(False, description="More traces available")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class LogEntryResponse(BaseModel):
|
|
138
|
+
"""System log entry."""
|
|
139
|
+
|
|
140
|
+
timestamp: datetime = Field(..., description="Log timestamp")
|
|
141
|
+
level: str = Field(..., description="Log level: DEBUG|INFO|WARNING|ERROR|CRITICAL")
|
|
142
|
+
service: str = Field(..., description="Source service")
|
|
143
|
+
message: str = Field(..., description="Log message")
|
|
144
|
+
context: LogContext = Field(default_factory=lambda: LogContext.model_validate({}), description="Additional context")
|
|
145
|
+
trace_id: Optional[str] = Field(None, description="Associated trace ID")
|
|
146
|
+
|
|
147
|
+
@field_serializer("timestamp")
|
|
148
|
+
def serialize_timestamp(self, timestamp: datetime, _info: Any) -> Optional[str]:
|
|
149
|
+
return timestamp.isoformat() if timestamp else None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class LogsResponse(BaseModel):
|
|
153
|
+
"""System logs response."""
|
|
154
|
+
|
|
155
|
+
logs: List[LogEntryResponse] = Field(..., description="Log entries")
|
|
156
|
+
total: int = Field(..., description="Total matching logs")
|
|
157
|
+
has_more: bool = Field(False, description="More logs available")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class TelemetryQuery(BaseModel):
|
|
161
|
+
"""Custom telemetry query."""
|
|
162
|
+
|
|
163
|
+
query_type: str = Field(..., description="Query type: metrics|traces|logs|incidents|insights")
|
|
164
|
+
filters: TelemetryQueryFilters = Field(
|
|
165
|
+
default_factory=lambda: TelemetryQueryFilters.model_validate({}), description="Query filters"
|
|
166
|
+
)
|
|
167
|
+
aggregations: Optional[List[str]] = Field(None, description="Aggregations to apply")
|
|
168
|
+
start_time: Optional[datetime] = Field(None, description="Query start time")
|
|
169
|
+
end_time: Optional[datetime] = Field(None, description="Query end time")
|
|
170
|
+
limit: int = Field(100, ge=1, le=1000, description="Result limit")
|
|
171
|
+
|
|
172
|
+
@field_serializer("start_time", "end_time")
|
|
173
|
+
def serialize_times(self, dt: Optional[datetime], _info: Any) -> Optional[str]:
|
|
174
|
+
return dt.isoformat() if dt else None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class QueryResponse(BaseModel):
|
|
178
|
+
"""Custom query response."""
|
|
179
|
+
|
|
180
|
+
query_type: str = Field(..., description="Query type executed")
|
|
181
|
+
results: List[QueryResult] = Field(..., description="Query results")
|
|
182
|
+
total: int = Field(..., description="Total results found")
|
|
183
|
+
execution_time_ms: float = Field(..., description="Query execution time")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# Helper functions
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
async def _update_telemetry_summary(overview: SystemOverview, telemetry_service: Any) -> None:
|
|
190
|
+
"""Update overview with telemetry summary metrics."""
|
|
191
|
+
if telemetry_service and hasattr(telemetry_service, "get_telemetry_summary"):
|
|
192
|
+
try:
|
|
193
|
+
summary = await telemetry_service.get_telemetry_summary()
|
|
194
|
+
overview.messages_processed_24h = summary.messages_processed_24h
|
|
195
|
+
overview.thoughts_processed_24h = summary.thoughts_processed_24h
|
|
196
|
+
overview.tasks_completed_24h = summary.tasks_completed_24h
|
|
197
|
+
overview.errors_24h = summary.errors_24h
|
|
198
|
+
overview.tokens_last_hour = summary.tokens_last_hour
|
|
199
|
+
overview.cost_last_hour_cents = summary.cost_last_hour_cents
|
|
200
|
+
overview.carbon_last_hour_grams = summary.carbon_last_hour_grams
|
|
201
|
+
overview.energy_last_hour_kwh = summary.energy_last_hour_kwh
|
|
202
|
+
overview.tokens_24h = summary.tokens_24h
|
|
203
|
+
overview.cost_24h_cents = summary.cost_24h_cents
|
|
204
|
+
overview.carbon_24h_grams = summary.carbon_24h_grams
|
|
205
|
+
overview.energy_24h_kwh = summary.energy_24h_kwh
|
|
206
|
+
overview.error_rate_percent = summary.error_rate_percent
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.warning(
|
|
209
|
+
f"Telemetry metric retrieval failed for telemetry summary: {type(e).__name__}: {str(e)} - Returning default/empty value"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
async def _update_visibility_state(overview: SystemOverview, visibility_service: Any) -> None:
|
|
214
|
+
"""Update overview with visibility state information."""
|
|
215
|
+
if visibility_service:
|
|
216
|
+
try:
|
|
217
|
+
snapshot = await visibility_service.get_current_state()
|
|
218
|
+
if snapshot:
|
|
219
|
+
overview.reasoning_depth = snapshot.reasoning_depth
|
|
220
|
+
if snapshot.current_task:
|
|
221
|
+
overview.current_task = snapshot.current_task.description
|
|
222
|
+
except Exception as e:
|
|
223
|
+
logger.warning(
|
|
224
|
+
f"Telemetry metric retrieval failed for visibility state: {type(e).__name__}: {str(e)} - Returning default/empty value"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _create_empty_system_overview() -> SystemOverview:
|
|
229
|
+
"""Create a SystemOverview with default values."""
|
|
230
|
+
return SystemOverview(
|
|
231
|
+
uptime_seconds=0.0,
|
|
232
|
+
cognitive_state="UNKNOWN",
|
|
233
|
+
messages_processed_24h=0,
|
|
234
|
+
thoughts_processed_24h=0,
|
|
235
|
+
tasks_completed_24h=0,
|
|
236
|
+
errors_24h=0,
|
|
237
|
+
tokens_last_hour=0.0,
|
|
238
|
+
cost_last_hour_cents=0.0,
|
|
239
|
+
carbon_last_hour_grams=0.0,
|
|
240
|
+
energy_last_hour_kwh=0.0,
|
|
241
|
+
tokens_24h=0.0,
|
|
242
|
+
cost_24h_cents=0.0,
|
|
243
|
+
carbon_24h_grams=0.0,
|
|
244
|
+
energy_24h_kwh=0.0,
|
|
245
|
+
memory_mb=0.0,
|
|
246
|
+
cpu_percent=0.0,
|
|
247
|
+
healthy_services=0,
|
|
248
|
+
degraded_services=0,
|
|
249
|
+
error_rate_percent=0.0,
|
|
250
|
+
current_task=None,
|
|
251
|
+
reasoning_depth=0,
|
|
252
|
+
active_deferrals=0,
|
|
253
|
+
recent_incidents=0,
|
|
254
|
+
total_metrics=0,
|
|
255
|
+
active_services=0,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _validate_critical_services(telemetry_service: Any, time_service: Any) -> None:
|
|
260
|
+
"""Validate that critical services are available."""
|
|
261
|
+
if not telemetry_service:
|
|
262
|
+
raise HTTPException(status_code=503, detail=ERROR_TELEMETRY_NOT_INITIALIZED)
|
|
263
|
+
if not time_service:
|
|
264
|
+
raise HTTPException(status_code=503, detail="Critical system failure: Time service not initialized")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _update_uptime(overview: SystemOverview, time_service: Any) -> None:
|
|
268
|
+
"""Update overview with system uptime."""
|
|
269
|
+
if not time_service:
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
uptime = time_service.get_uptime()
|
|
274
|
+
overview.uptime_seconds = uptime
|
|
275
|
+
except Exception as e:
|
|
276
|
+
logger.warning(
|
|
277
|
+
f"Telemetry metric retrieval failed for uptime: {type(e).__name__}: {str(e)} - Returning default/empty value"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _update_cognitive_state(overview: SystemOverview, request: Request) -> None:
|
|
282
|
+
"""Update overview with cognitive state from runtime."""
|
|
283
|
+
runtime = getattr(request.app.state, "runtime", None)
|
|
284
|
+
if runtime and hasattr(runtime, "state_manager"):
|
|
285
|
+
overview.cognitive_state = runtime.state_manager.current_state
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _update_resource_usage(overview: SystemOverview, resource_monitor: Any) -> None:
|
|
289
|
+
"""Update overview with resource usage metrics."""
|
|
290
|
+
if not resource_monitor:
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
# Access the snapshot directly
|
|
295
|
+
if hasattr(resource_monitor, "snapshot"):
|
|
296
|
+
overview.memory_mb = float(resource_monitor.snapshot.memory_mb)
|
|
297
|
+
overview.cpu_percent = float(resource_monitor.snapshot.cpu_percent)
|
|
298
|
+
except Exception as e:
|
|
299
|
+
logger.warning(
|
|
300
|
+
f"Telemetry metric retrieval failed for resource usage: {type(e).__name__}: {str(e)} - Returning default/empty value"
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _get_service_health_counts(request: Request) -> tuple[int, int]:
|
|
305
|
+
"""Count healthy and degraded services (22 core services + API consent_manager)."""
|
|
306
|
+
services = [
|
|
307
|
+
# Graph Services (6)
|
|
308
|
+
"memory_service",
|
|
309
|
+
"config_service",
|
|
310
|
+
"telemetry_service",
|
|
311
|
+
"audit_service",
|
|
312
|
+
"incident_management_service",
|
|
313
|
+
"tsdb_consolidation_service",
|
|
314
|
+
# Infrastructure Services (8)
|
|
315
|
+
"time_service",
|
|
316
|
+
"shutdown_service",
|
|
317
|
+
"initialization_service",
|
|
318
|
+
"authentication_service",
|
|
319
|
+
"resource_monitor",
|
|
320
|
+
"database_maintenance_service",
|
|
321
|
+
"secrets_service",
|
|
322
|
+
"consent_manager", # API-specific consent service
|
|
323
|
+
# Governance Services (4)
|
|
324
|
+
"wise_authority_service",
|
|
325
|
+
"adaptive_filter_service",
|
|
326
|
+
"visibility_service",
|
|
327
|
+
"self_observation_service",
|
|
328
|
+
# Runtime Services (3)
|
|
329
|
+
"llm_service",
|
|
330
|
+
"runtime_control_service",
|
|
331
|
+
"task_scheduler",
|
|
332
|
+
# Tool Services (1)
|
|
333
|
+
"secrets_tool_service",
|
|
334
|
+
]
|
|
335
|
+
|
|
336
|
+
healthy = 0
|
|
337
|
+
degraded = 0
|
|
338
|
+
for service_attr in services:
|
|
339
|
+
if getattr(request.app.state, service_attr, None):
|
|
340
|
+
healthy += 1
|
|
341
|
+
else:
|
|
342
|
+
degraded += 1
|
|
343
|
+
|
|
344
|
+
return healthy, degraded
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
async def _update_incident_count(overview: SystemOverview, incident_service: Any) -> None:
|
|
348
|
+
"""Update overview with recent incident count."""
|
|
349
|
+
if not incident_service:
|
|
350
|
+
return
|
|
351
|
+
|
|
352
|
+
try:
|
|
353
|
+
# Get count of incidents from the last hour
|
|
354
|
+
overview.recent_incidents = await incident_service.get_incident_count(hours=1)
|
|
355
|
+
except Exception as e:
|
|
356
|
+
logger.warning(
|
|
357
|
+
f"Telemetry metric retrieval failed for incident count: {type(e).__name__}: {str(e)} - Returning default/empty value"
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
async def _update_deferral_count(overview: SystemOverview, wise_authority: Any) -> None:
|
|
362
|
+
"""Update overview with active deferral count."""
|
|
363
|
+
if not wise_authority:
|
|
364
|
+
return
|
|
365
|
+
|
|
366
|
+
try:
|
|
367
|
+
deferrals = await wise_authority.get_pending_deferrals()
|
|
368
|
+
overview.active_deferrals = len(deferrals) if deferrals else 0
|
|
369
|
+
except Exception as e:
|
|
370
|
+
logger.warning(
|
|
371
|
+
f"Telemetry metric retrieval failed for deferral count: {type(e).__name__}: {str(e)} - Returning default/empty value"
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
async def _update_metrics_count(overview: SystemOverview, telemetry_service: Any, healthy_services: int) -> None:
|
|
376
|
+
"""Update overview with telemetry metrics count."""
|
|
377
|
+
if not telemetry_service:
|
|
378
|
+
return
|
|
379
|
+
|
|
380
|
+
try:
|
|
381
|
+
# Count total metrics collected
|
|
382
|
+
if hasattr(telemetry_service, "get_metric_count"):
|
|
383
|
+
overview.total_metrics = await telemetry_service.get_metric_count()
|
|
384
|
+
elif hasattr(telemetry_service, "query_metrics"):
|
|
385
|
+
overview.total_metrics = await _estimate_metrics_count(telemetry_service)
|
|
386
|
+
|
|
387
|
+
# Count active services (services that have reported metrics)
|
|
388
|
+
overview.active_services = healthy_services # Use healthy services count as proxy
|
|
389
|
+
|
|
390
|
+
except Exception as e:
|
|
391
|
+
logger.warning(
|
|
392
|
+
f"Telemetry metric retrieval failed for metrics count: {type(e).__name__}: {str(e)} - Returning default/empty value"
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
async def _estimate_metrics_count(telemetry_service: Any) -> int:
|
|
397
|
+
"""Estimate total metrics count from common metric queries."""
|
|
398
|
+
metric_names = [
|
|
399
|
+
"llm.tokens.total", # Total tokens used
|
|
400
|
+
"llm_tokens_used", # Legacy token metric
|
|
401
|
+
"thought_processing_completed", # Thoughts completed
|
|
402
|
+
"action_selected_task_complete", # Tasks completed
|
|
403
|
+
"handler_invoked_total", # Total handler invocations
|
|
404
|
+
"action_selected_memorize", # Memory operations
|
|
405
|
+
]
|
|
406
|
+
|
|
407
|
+
total = 0
|
|
408
|
+
now = datetime.now(timezone.utc)
|
|
409
|
+
day_ago = now - timedelta(hours=24)
|
|
410
|
+
|
|
411
|
+
for metric in metric_names:
|
|
412
|
+
try:
|
|
413
|
+
data = await telemetry_service.query_metrics(metric_name=metric, start_time=day_ago, end_time=now)
|
|
414
|
+
if data:
|
|
415
|
+
total += len(data)
|
|
416
|
+
except (AttributeError, TypeError, ValueError, RuntimeError) as e:
|
|
417
|
+
logger.debug(f"Failed to query metric '{metric}': {type(e).__name__}: {str(e)}")
|
|
418
|
+
|
|
419
|
+
return total
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
async def _get_system_overview(request: Request) -> SystemOverview:
|
|
423
|
+
"""Build comprehensive system overview from all services."""
|
|
424
|
+
# Get core services - THESE MUST EXIST
|
|
425
|
+
telemetry_service = request.app.state.telemetry_service
|
|
426
|
+
visibility_service = request.app.state.visibility_service
|
|
427
|
+
time_service = request.app.state.time_service
|
|
428
|
+
resource_monitor = request.app.state.resource_monitor
|
|
429
|
+
incident_service = request.app.state.incident_management_service
|
|
430
|
+
wise_authority = request.app.state.wise_authority_service
|
|
431
|
+
|
|
432
|
+
# Validate critical services
|
|
433
|
+
_validate_critical_services(telemetry_service, time_service)
|
|
434
|
+
|
|
435
|
+
# Initialize overview with default values
|
|
436
|
+
overview = _create_empty_system_overview()
|
|
437
|
+
|
|
438
|
+
# Update overview with data from various services
|
|
439
|
+
_update_uptime(overview, time_service)
|
|
440
|
+
await _update_telemetry_summary(overview, telemetry_service)
|
|
441
|
+
_update_cognitive_state(overview, request)
|
|
442
|
+
await _update_visibility_state(overview, visibility_service)
|
|
443
|
+
_update_resource_usage(overview, resource_monitor)
|
|
444
|
+
|
|
445
|
+
# Update service health counts
|
|
446
|
+
healthy, degraded = _get_service_health_counts(request)
|
|
447
|
+
overview.healthy_services = healthy
|
|
448
|
+
overview.degraded_services = degraded
|
|
449
|
+
|
|
450
|
+
# Update incident and deferral counts
|
|
451
|
+
await _update_incident_count(overview, incident_service)
|
|
452
|
+
await _update_deferral_count(overview, wise_authority)
|
|
453
|
+
await _update_metrics_count(overview, telemetry_service, healthy)
|
|
454
|
+
|
|
455
|
+
return overview
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
# Endpoints
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
async def _export_otlp_metrics(telemetry_service: Any) -> JSONDict:
|
|
462
|
+
"""Export metrics in OTLP format."""
|
|
463
|
+
if not telemetry_service:
|
|
464
|
+
raise HTTPException(status_code=503, detail=ERROR_TELEMETRY_SERVICE_NOT_AVAILABLE)
|
|
465
|
+
|
|
466
|
+
# Get aggregated telemetry
|
|
467
|
+
aggregated = await telemetry_service.get_aggregated_telemetry()
|
|
468
|
+
|
|
469
|
+
# Convert to dict for OTLP conversion
|
|
470
|
+
telemetry_dict = {
|
|
471
|
+
"system_healthy": aggregated.system_healthy,
|
|
472
|
+
"services_online": aggregated.services_online,
|
|
473
|
+
"services_total": aggregated.services_total,
|
|
474
|
+
"overall_error_rate": aggregated.overall_error_rate,
|
|
475
|
+
"overall_uptime_seconds": aggregated.overall_uptime_seconds,
|
|
476
|
+
"total_errors": aggregated.total_errors,
|
|
477
|
+
"total_requests": aggregated.total_requests,
|
|
478
|
+
"services": aggregated.services,
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
# Add covenant metrics if available
|
|
482
|
+
if hasattr(aggregated, "covenant_metrics"):
|
|
483
|
+
telemetry_dict["covenant_metrics"] = aggregated.covenant_metrics
|
|
484
|
+
|
|
485
|
+
return convert_to_otlp_json(telemetry_dict)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _extract_basic_trace_fields(correlation: Any) -> JSONDict:
|
|
489
|
+
"""Extract basic trace fields from correlation object."""
|
|
490
|
+
return {
|
|
491
|
+
"trace_id": (correlation.trace_context.trace_id if correlation.trace_context else correlation.correlation_id),
|
|
492
|
+
"span_id": (correlation.trace_context.span_id if correlation.trace_context else str(uuid.uuid4())),
|
|
493
|
+
"parent_span_id": (correlation.trace_context.parent_span_id if correlation.trace_context else None),
|
|
494
|
+
"timestamp": (
|
|
495
|
+
correlation.timestamp.isoformat() if correlation.timestamp else datetime.now(timezone.utc).isoformat()
|
|
496
|
+
),
|
|
497
|
+
"operation": correlation.action_type or "unknown",
|
|
498
|
+
"service": correlation.service_type,
|
|
499
|
+
"handler": correlation.handler_name,
|
|
500
|
+
"status": (correlation.status.value if hasattr(correlation.status, "value") else str(correlation.status)),
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def _extract_request_data_fields(correlation: Any, trace_data: JSONDict) -> None:
|
|
505
|
+
"""Extract task/thought linkage from request data."""
|
|
506
|
+
if not correlation.request_data:
|
|
507
|
+
return
|
|
508
|
+
|
|
509
|
+
if hasattr(correlation.request_data, "task_id") and correlation.request_data.task_id:
|
|
510
|
+
trace_data["task_id"] = correlation.request_data.task_id
|
|
511
|
+
if hasattr(correlation.request_data, "thought_id") and correlation.request_data.thought_id:
|
|
512
|
+
trace_data["thought_id"] = correlation.request_data.thought_id
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def _extract_response_data_fields(correlation: Any, trace_data: JSONDict) -> None:
|
|
516
|
+
"""Extract performance data from response data."""
|
|
517
|
+
if not correlation.response_data:
|
|
518
|
+
return
|
|
519
|
+
|
|
520
|
+
if hasattr(correlation.response_data, "execution_time_ms"):
|
|
521
|
+
trace_data["duration_ms"] = correlation.response_data.execution_time_ms
|
|
522
|
+
if hasattr(correlation.response_data, "success"):
|
|
523
|
+
trace_data["success"] = correlation.response_data.success
|
|
524
|
+
if hasattr(correlation.response_data, "error_message"):
|
|
525
|
+
trace_data["error"] = correlation.response_data.error_message
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def _extract_span_attributes(correlation: Any, trace_data: JSONDict) -> None:
|
|
529
|
+
"""Extract span attributes from trace context."""
|
|
530
|
+
if not correlation.trace_context:
|
|
531
|
+
return
|
|
532
|
+
|
|
533
|
+
trace_data["span_name"] = (
|
|
534
|
+
correlation.trace_context.span_name
|
|
535
|
+
if hasattr(correlation.trace_context, "span_name")
|
|
536
|
+
else correlation.action_type
|
|
537
|
+
)
|
|
538
|
+
trace_data["span_kind"] = (
|
|
539
|
+
correlation.trace_context.span_kind if hasattr(correlation.trace_context, "span_kind") else "internal"
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def _build_trace_data_from_correlation(correlation: Any) -> JSONDict:
|
|
544
|
+
"""Build trace data dictionary from correlation object."""
|
|
545
|
+
trace_data = _extract_basic_trace_fields(correlation)
|
|
546
|
+
_extract_request_data_fields(correlation, trace_data)
|
|
547
|
+
_extract_response_data_fields(correlation, trace_data)
|
|
548
|
+
_extract_span_attributes(correlation, trace_data)
|
|
549
|
+
return trace_data
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
async def _export_otlp_traces(visibility_service: Any, limit: int) -> JSONDict:
|
|
553
|
+
"""Export traces in OTLP format."""
|
|
554
|
+
if not visibility_service:
|
|
555
|
+
raise HTTPException(status_code=503, detail="Visibility service not available")
|
|
556
|
+
|
|
557
|
+
traces = []
|
|
558
|
+
try:
|
|
559
|
+
# Get service correlations (trace spans)
|
|
560
|
+
correlations = await visibility_service.get_recent_traces(limit=limit)
|
|
561
|
+
|
|
562
|
+
for correlation in correlations:
|
|
563
|
+
trace_data = _build_trace_data_from_correlation(correlation)
|
|
564
|
+
traces.append(trace_data)
|
|
565
|
+
|
|
566
|
+
except Exception as e:
|
|
567
|
+
print(f"ERROR in get_otlp_telemetry traces: {e}", file=sys.stderr)
|
|
568
|
+
import traceback
|
|
569
|
+
|
|
570
|
+
traceback.print_exc()
|
|
571
|
+
|
|
572
|
+
return convert_traces_to_otlp_json(traces)
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def _build_log_data_from_entry(log_entry: Any) -> JSONDict:
|
|
576
|
+
"""Build log data dictionary from log entry."""
|
|
577
|
+
log_data = {
|
|
578
|
+
"timestamp": log_entry.timestamp.isoformat(),
|
|
579
|
+
"level": log_entry.level,
|
|
580
|
+
"message": log_entry.message,
|
|
581
|
+
"service": log_entry.service,
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
# Add context data if available - handle both LogEntry formats
|
|
585
|
+
# LogEntryResponse has nested context object, telemetry_models.LogEntry has top-level fields
|
|
586
|
+
if hasattr(log_entry, "context") and log_entry.context:
|
|
587
|
+
if hasattr(log_entry.context, "correlation_id") and log_entry.context.correlation_id:
|
|
588
|
+
log_data["correlation_id"] = log_entry.context.correlation_id
|
|
589
|
+
if hasattr(log_entry.context, "trace_id") and log_entry.context.trace_id:
|
|
590
|
+
log_data["trace_id"] = log_entry.context.trace_id
|
|
591
|
+
if hasattr(log_entry.context, "user_id") and log_entry.context.user_id:
|
|
592
|
+
log_data["user_id"] = log_entry.context.user_id
|
|
593
|
+
if hasattr(log_entry.context, "entity_id") and log_entry.context.entity_id:
|
|
594
|
+
log_data["entity_id"] = log_entry.context.entity_id
|
|
595
|
+
else:
|
|
596
|
+
# Handle flat LogEntry format from telemetry_models
|
|
597
|
+
if hasattr(log_entry, "correlation_id") and log_entry.correlation_id:
|
|
598
|
+
log_data["correlation_id"] = log_entry.correlation_id
|
|
599
|
+
if hasattr(log_entry, "user_id") and log_entry.user_id:
|
|
600
|
+
log_data["user_id"] = log_entry.user_id
|
|
601
|
+
|
|
602
|
+
# Add trace ID at top level if available
|
|
603
|
+
if hasattr(log_entry, "trace_id") and log_entry.trace_id:
|
|
604
|
+
log_data["trace_id"] = log_entry.trace_id
|
|
605
|
+
|
|
606
|
+
return log_data
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
async def _export_otlp_logs(limit: int, start_time: Optional[datetime], end_time: Optional[datetime]) -> JSONDict:
|
|
610
|
+
"""Export logs in OTLP format."""
|
|
611
|
+
logs = []
|
|
612
|
+
try:
|
|
613
|
+
from .telemetry_logs_reader import log_reader
|
|
614
|
+
|
|
615
|
+
# Read actual log files including incidents
|
|
616
|
+
file_logs = log_reader.read_logs(
|
|
617
|
+
level=None, # Get all levels
|
|
618
|
+
service=None, # Get all services
|
|
619
|
+
limit=limit,
|
|
620
|
+
start_time=start_time if start_time else None, # Don't default to 1 hour ago
|
|
621
|
+
end_time=end_time if end_time else None, # Don't default to now
|
|
622
|
+
include_incidents=True, # Include incident logs (WARNING/ERROR/CRITICAL)
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
# Convert LogEntry objects to dict format for OTLP
|
|
626
|
+
for log_entry in file_logs:
|
|
627
|
+
log_data = _build_log_data_from_entry(log_entry)
|
|
628
|
+
logs.append(log_data)
|
|
629
|
+
|
|
630
|
+
except ImportError:
|
|
631
|
+
# Log reader module not available
|
|
632
|
+
logger.warning("Log reader not available for OTLP logs export")
|
|
633
|
+
except Exception as e:
|
|
634
|
+
logger.warning(f"Failed to read log files for OTLP: {e}")
|
|
635
|
+
|
|
636
|
+
return convert_logs_to_otlp_json(logs)
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
@router.get("/otlp/{signal}", response_model=None)
|
|
640
|
+
async def get_otlp_telemetry(
|
|
641
|
+
signal: str,
|
|
642
|
+
request: Request,
|
|
643
|
+
auth: AuthContext = Depends(require_observer),
|
|
644
|
+
limit: int = Query(100, ge=1, le=1000, description="Maximum items to return"),
|
|
645
|
+
start_time: Optional[datetime] = Query(None, description=DESC_START_TIME),
|
|
646
|
+
end_time: Optional[datetime] = Query(None, description=DESC_END_TIME),
|
|
647
|
+
) -> JSONDict:
|
|
648
|
+
"""
|
|
649
|
+
OpenTelemetry Protocol (OTLP) JSON export.
|
|
650
|
+
|
|
651
|
+
Export telemetry data in OTLP JSON format for OpenTelemetry collectors.
|
|
652
|
+
|
|
653
|
+
Supported signals:
|
|
654
|
+
- metrics: System and service metrics
|
|
655
|
+
- traces: Distributed traces with spans
|
|
656
|
+
- logs: Structured log records
|
|
657
|
+
|
|
658
|
+
Returns OTLP JSON formatted data compatible with OpenTelemetry v1.7.0 specification.
|
|
659
|
+
"""
|
|
660
|
+
|
|
661
|
+
if signal not in ["metrics", "traces", "logs"]:
|
|
662
|
+
raise HTTPException(
|
|
663
|
+
status_code=400, detail=f"Invalid signal type: {signal}. Must be one of: metrics, traces, logs"
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
try:
|
|
667
|
+
if signal == "metrics":
|
|
668
|
+
return await _export_otlp_metrics(request.app.state.telemetry_service)
|
|
669
|
+
elif signal == "traces":
|
|
670
|
+
return await _export_otlp_traces(request.app.state.visibility_service, limit)
|
|
671
|
+
else: # signal == "logs" - already validated above
|
|
672
|
+
return await _export_otlp_logs(limit, start_time, end_time)
|
|
673
|
+
|
|
674
|
+
except HTTPException:
|
|
675
|
+
raise
|
|
676
|
+
except Exception as e:
|
|
677
|
+
logger.error(f"OTLP export failed for {signal}: {e}")
|
|
678
|
+
raise HTTPException(status_code=500, detail=f"Failed to export {signal} in OTLP format")
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
@router.get("/overview", response_model=SuccessResponse[SystemOverview])
|
|
682
|
+
async def get_telemetry_overview(
|
|
683
|
+
request: Request, auth: AuthContext = Depends(require_observer)
|
|
684
|
+
) -> SuccessResponse[SystemOverview]:
|
|
685
|
+
"""
|
|
686
|
+
System metrics summary.
|
|
687
|
+
|
|
688
|
+
Comprehensive overview combining telemetry, visibility, incidents, and resource usage.
|
|
689
|
+
"""
|
|
690
|
+
try:
|
|
691
|
+
overview = await _get_system_overview(request)
|
|
692
|
+
return SuccessResponse(
|
|
693
|
+
data=overview,
|
|
694
|
+
metadata=ResponseMetadata(
|
|
695
|
+
timestamp=datetime.now(timezone.utc), request_id=str(uuid.uuid4()), duration_ms=0
|
|
696
|
+
),
|
|
697
|
+
)
|
|
698
|
+
except HTTPException:
|
|
699
|
+
# Re-raise HTTPException as-is to preserve status code
|
|
700
|
+
raise
|
|
701
|
+
except Exception as e:
|
|
702
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
class ResourceUsageData(BaseModel):
|
|
706
|
+
"""Current resource usage data."""
|
|
707
|
+
|
|
708
|
+
cpu_percent: float = Field(..., description="CPU usage percentage")
|
|
709
|
+
memory_mb: float = Field(..., description="Memory usage in MB")
|
|
710
|
+
memory_percent: float = Field(..., description="Memory usage percentage")
|
|
711
|
+
disk_usage_bytes: int = Field(0, description="Disk usage in bytes")
|
|
712
|
+
disk_usage_gb: float = Field(0.0, description="Disk usage in GB")
|
|
713
|
+
active_threads: int = Field(0, description="Number of active threads")
|
|
714
|
+
open_files: int = Field(0, description="Number of open files")
|
|
715
|
+
timestamp: str = Field(..., description="Timestamp of measurement")
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
class ResourceLimits(BaseModel):
|
|
719
|
+
"""Resource usage limits."""
|
|
720
|
+
|
|
721
|
+
max_memory_mb: float = Field(..., description="Maximum memory in MB")
|
|
722
|
+
max_cpu_percent: float = Field(100.0, description="Maximum CPU percentage")
|
|
723
|
+
max_disk_bytes: int = Field(0, description="Maximum disk usage in bytes")
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
class ResourceHistoryPoint(BaseModel):
|
|
727
|
+
"""Historical resource usage point."""
|
|
728
|
+
|
|
729
|
+
timestamp: datetime = Field(..., description="Timestamp of measurement")
|
|
730
|
+
cpu_percent: float = Field(..., description="CPU usage percentage")
|
|
731
|
+
memory_mb: float = Field(..., description="Memory usage in MB")
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
class ResourceHealthStatus(BaseModel):
|
|
735
|
+
"""Resource health status."""
|
|
736
|
+
|
|
737
|
+
status: str = Field(..., description="Health status: healthy|warning|critical")
|
|
738
|
+
warnings: List[str] = Field(default_factory=list, description="Warning messages")
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
class ResourceTelemetryResponse(BaseModel):
|
|
742
|
+
"""Complete resource telemetry response."""
|
|
743
|
+
|
|
744
|
+
current: ResourceUsageData = Field(..., description="Current resource usage")
|
|
745
|
+
limits: ResourceLimits = Field(..., description="Resource limits")
|
|
746
|
+
history: List[ResourceHistoryPoint] = Field(default_factory=list, description="Historical data")
|
|
747
|
+
health: ResourceHealthStatus = Field(..., description="Health status")
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
@router.get("/resources", response_model=SuccessResponse[ResourceTelemetryResponse])
|
|
751
|
+
async def get_resource_telemetry(
|
|
752
|
+
request: Request, auth: AuthContext = Depends(require_observer)
|
|
753
|
+
) -> SuccessResponse[ResourceTelemetryResponse]:
|
|
754
|
+
"""
|
|
755
|
+
Get current resource usage telemetry.
|
|
756
|
+
|
|
757
|
+
Returns CPU, memory, disk, and other resource metrics.
|
|
758
|
+
"""
|
|
759
|
+
# These services MUST exist - if they don't, we have a critical failure
|
|
760
|
+
resource_monitor = request.app.state.resource_monitor
|
|
761
|
+
telemetry_service = request.app.state.telemetry_service
|
|
762
|
+
|
|
763
|
+
if not resource_monitor:
|
|
764
|
+
raise HTTPException(status_code=503, detail="Critical system failure: Resource monitor service not initialized")
|
|
765
|
+
if not telemetry_service:
|
|
766
|
+
raise HTTPException(status_code=503, detail=ERROR_TELEMETRY_NOT_INITIALIZED)
|
|
767
|
+
|
|
768
|
+
try:
|
|
769
|
+
# Get current resource usage
|
|
770
|
+
current_usage = resource_monitor.snapshot
|
|
771
|
+
|
|
772
|
+
# Get resource limits
|
|
773
|
+
limits = resource_monitor.budget
|
|
774
|
+
|
|
775
|
+
# Get historical data if available
|
|
776
|
+
history_points = []
|
|
777
|
+
if telemetry_service and hasattr(telemetry_service, "query_metrics"):
|
|
778
|
+
now = datetime.now(timezone.utc)
|
|
779
|
+
hour_ago = now - timedelta(hours=1)
|
|
780
|
+
|
|
781
|
+
# Query CPU history
|
|
782
|
+
cpu_history = await telemetry_service.query_metrics(
|
|
783
|
+
metric_name="cpu_percent", start_time=hour_ago, end_time=now
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
# Query memory history
|
|
787
|
+
memory_history = await telemetry_service.query_metrics(
|
|
788
|
+
metric_name="memory_mb", start_time=hour_ago, end_time=now
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
# Build history points
|
|
792
|
+
for i in range(min(len(cpu_history), len(memory_history))):
|
|
793
|
+
history_points.append(
|
|
794
|
+
ResourceHistoryPoint(
|
|
795
|
+
timestamp=cpu_history[i].timestamp,
|
|
796
|
+
cpu_percent=float(cpu_history[i].value),
|
|
797
|
+
memory_mb=float(memory_history[i].value),
|
|
798
|
+
)
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
# Convert disk bytes to GB for the response
|
|
802
|
+
disk_bytes = getattr(current_usage, "disk_usage_bytes", 0)
|
|
803
|
+
disk_gb = disk_bytes / (1024 * 1024 * 1024) if disk_bytes > 0 else 0.0
|
|
804
|
+
|
|
805
|
+
response = ResourceTelemetryResponse(
|
|
806
|
+
current=ResourceUsageData(
|
|
807
|
+
cpu_percent=current_usage.cpu_percent,
|
|
808
|
+
memory_mb=current_usage.memory_mb,
|
|
809
|
+
memory_percent=current_usage.memory_percent,
|
|
810
|
+
disk_usage_bytes=disk_bytes,
|
|
811
|
+
disk_usage_gb=disk_gb,
|
|
812
|
+
active_threads=getattr(current_usage, "active_threads", 0),
|
|
813
|
+
open_files=getattr(current_usage, "open_files", 0),
|
|
814
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
815
|
+
),
|
|
816
|
+
limits=ResourceLimits(
|
|
817
|
+
max_memory_mb=getattr(limits, "max_memory_mb", 2048.0),
|
|
818
|
+
max_cpu_percent=getattr(limits, "max_cpu_percent", 100.0),
|
|
819
|
+
max_disk_bytes=getattr(limits, "max_disk_bytes", 0),
|
|
820
|
+
),
|
|
821
|
+
history=history_points[-60:], # Last hour of data
|
|
822
|
+
health=ResourceHealthStatus(
|
|
823
|
+
status="healthy" if current_usage.memory_percent < 80 and current_usage.cpu_percent < 80 else "warning",
|
|
824
|
+
warnings=getattr(current_usage, "warnings", []),
|
|
825
|
+
),
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
return SuccessResponse(
|
|
829
|
+
data=response,
|
|
830
|
+
metadata=ResponseMetadata(
|
|
831
|
+
timestamp=datetime.now(timezone.utc), request_id=str(uuid.uuid4()), duration_ms=0
|
|
832
|
+
),
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
except HTTPException:
|
|
836
|
+
# Re-raise HTTPException as-is to preserve status code
|
|
837
|
+
raise
|
|
838
|
+
except Exception as e:
|
|
839
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
def _get_metric_unit(metric_name: str) -> Optional[str]:
|
|
843
|
+
"""Determine unit from metric name."""
|
|
844
|
+
if "tokens" in metric_name:
|
|
845
|
+
return "tokens"
|
|
846
|
+
elif "time" in metric_name or "latency" in metric_name or "ms" in metric_name:
|
|
847
|
+
return "ms"
|
|
848
|
+
elif "percent" in metric_name or "rate" in metric_name or "cpu" in metric_name:
|
|
849
|
+
return "%"
|
|
850
|
+
elif "mb" in metric_name or "memory" in metric_name:
|
|
851
|
+
return "MB"
|
|
852
|
+
elif "cents" in metric_name:
|
|
853
|
+
return "cents"
|
|
854
|
+
elif "grams" in metric_name:
|
|
855
|
+
return "g"
|
|
856
|
+
elif "kwh" in metric_name:
|
|
857
|
+
return "kWh"
|
|
858
|
+
return "count"
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def _calculate_trend(values: List[float]) -> str:
|
|
862
|
+
"""Calculate trend from a list of values."""
|
|
863
|
+
if len(values) <= 1:
|
|
864
|
+
return "stable"
|
|
865
|
+
|
|
866
|
+
recent_avg = sum(values[-5:]) / len(values[-5:])
|
|
867
|
+
older_avg = sum(values[:-5]) / len(values[:-5]) if len(values) > 5 else values[0]
|
|
868
|
+
|
|
869
|
+
if recent_avg > older_avg * 1.1:
|
|
870
|
+
return "up"
|
|
871
|
+
elif recent_avg < older_avg * 0.9:
|
|
872
|
+
return "down"
|
|
873
|
+
return "stable"
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
async def _process_metric_data(telemetry_service: Any, metric_name: str, now: datetime) -> Optional[DetailedMetric]:
|
|
877
|
+
"""Process metric data for a single metric name."""
|
|
878
|
+
if not hasattr(telemetry_service, "query_metrics"):
|
|
879
|
+
return None
|
|
880
|
+
|
|
881
|
+
day_ago = now - timedelta(hours=24)
|
|
882
|
+
hour_ago = now - timedelta(hours=1)
|
|
883
|
+
|
|
884
|
+
# Get hourly data
|
|
885
|
+
hourly_data = await telemetry_service.query_metrics(metric_name=metric_name, start_time=hour_ago, end_time=now)
|
|
886
|
+
|
|
887
|
+
# Get daily data
|
|
888
|
+
daily_data = await telemetry_service.query_metrics(metric_name=metric_name, start_time=day_ago, end_time=now)
|
|
889
|
+
|
|
890
|
+
if not (hourly_data or daily_data):
|
|
891
|
+
return None
|
|
892
|
+
|
|
893
|
+
# Calculate averages and trends
|
|
894
|
+
hourly_values = [float(dp.value) for dp in hourly_data] if hourly_data else [0.0]
|
|
895
|
+
daily_values = [float(dp.value) for dp in daily_data] if daily_data else [0.0]
|
|
896
|
+
|
|
897
|
+
hourly_avg = sum(hourly_values) / len(hourly_values) if hourly_values else 0.0
|
|
898
|
+
daily_avg = sum(daily_values) / len(daily_values) if daily_values else 0.0
|
|
899
|
+
current_value = hourly_values[-1] if hourly_values else 0.0
|
|
900
|
+
|
|
901
|
+
# Determine trend
|
|
902
|
+
trend = _calculate_trend(hourly_values)
|
|
903
|
+
unit = _get_metric_unit(metric_name)
|
|
904
|
+
|
|
905
|
+
return DetailedMetric(
|
|
906
|
+
name=metric_name,
|
|
907
|
+
current_value=current_value,
|
|
908
|
+
unit=unit,
|
|
909
|
+
trend=trend,
|
|
910
|
+
hourly_average=hourly_avg,
|
|
911
|
+
daily_average=daily_avg,
|
|
912
|
+
by_service=[], # Could aggregate by service if tags available
|
|
913
|
+
recent_data=[
|
|
914
|
+
MetricData(
|
|
915
|
+
timestamp=dp.timestamp,
|
|
916
|
+
value=float(dp.value),
|
|
917
|
+
tags=MetricTags(**dp.tags) if dp.tags else MetricTags(),
|
|
918
|
+
)
|
|
919
|
+
for dp in (hourly_data[-10:] if hourly_data else [])
|
|
920
|
+
],
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
async def _get_legacy_metrics(telemetry_service: Any) -> List[DetailedMetric]:
|
|
925
|
+
"""Get metrics from legacy get_metrics method."""
|
|
926
|
+
metrics: List[DetailedMetric] = []
|
|
927
|
+
if not (hasattr(telemetry_service, "get_metrics") and not hasattr(telemetry_service, "query_metrics")):
|
|
928
|
+
return metrics
|
|
929
|
+
|
|
930
|
+
legacy_metrics = await telemetry_service.get_metrics()
|
|
931
|
+
if not legacy_metrics:
|
|
932
|
+
return metrics
|
|
933
|
+
|
|
934
|
+
for metric_name, value in legacy_metrics.items():
|
|
935
|
+
unit = _get_metric_unit(metric_name)
|
|
936
|
+
metrics.append(
|
|
937
|
+
DetailedMetric(
|
|
938
|
+
name=metric_name,
|
|
939
|
+
current_value=float(value),
|
|
940
|
+
unit=unit,
|
|
941
|
+
trend="stable", # Default trend when no history
|
|
942
|
+
hourly_average=float(value),
|
|
943
|
+
daily_average=float(value),
|
|
944
|
+
by_service=[],
|
|
945
|
+
recent_data=[],
|
|
946
|
+
)
|
|
947
|
+
)
|
|
948
|
+
return metrics
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
def _calculate_metrics_summary(metrics: List[DetailedMetric]) -> MetricAggregate:
|
|
952
|
+
"""Calculate summary statistics across all metrics."""
|
|
953
|
+
all_values = []
|
|
954
|
+
for metric in metrics:
|
|
955
|
+
if metric.recent_data:
|
|
956
|
+
all_values.extend([dp.value for dp in metric.recent_data])
|
|
957
|
+
|
|
958
|
+
if all_values:
|
|
959
|
+
return MetricAggregate(
|
|
960
|
+
min=min(all_values),
|
|
961
|
+
max=max(all_values),
|
|
962
|
+
avg=sum(all_values) / len(all_values),
|
|
963
|
+
sum=sum(all_values),
|
|
964
|
+
count=len(all_values),
|
|
965
|
+
)
|
|
966
|
+
else:
|
|
967
|
+
return MetricAggregate(min=0.0, max=0.0, avg=0.0, sum=0.0, count=0)
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
@router.get("/metrics", response_model=SuccessResponse[MetricsResponse])
|
|
971
|
+
async def get_detailed_metrics(
|
|
972
|
+
request: Request, auth: AuthContext = Depends(require_observer)
|
|
973
|
+
) -> SuccessResponse[MetricsResponse]:
|
|
974
|
+
"""
|
|
975
|
+
Detailed metrics.
|
|
976
|
+
|
|
977
|
+
Get detailed metrics with trends and breakdowns by service.
|
|
978
|
+
"""
|
|
979
|
+
# Telemetry service MUST exist - if it doesn't, we have a critical failure
|
|
980
|
+
telemetry_service = request.app.state.telemetry_service
|
|
981
|
+
if not telemetry_service:
|
|
982
|
+
raise HTTPException(status_code=503, detail=ERROR_TELEMETRY_NOT_INITIALIZED)
|
|
983
|
+
|
|
984
|
+
try:
|
|
985
|
+
# Common metrics to query - use actual metric names that exist in TSDB nodes
|
|
986
|
+
metric_names = [
|
|
987
|
+
"llm_tokens_used", # Legacy LLM token usage
|
|
988
|
+
"llm_api_call_structured", # Legacy LLM API calls
|
|
989
|
+
"llm.tokens.total", # New format: total tokens
|
|
990
|
+
"llm.tokens.input", # New format: input tokens
|
|
991
|
+
"llm.tokens.output", # New format: output tokens
|
|
992
|
+
"llm.cost.cents", # Cost tracking
|
|
993
|
+
"llm.environmental.carbon_grams", # Carbon footprint
|
|
994
|
+
"llm.environmental.energy_kwh", # Energy usage
|
|
995
|
+
"handler_completed_total", # Handler completions
|
|
996
|
+
"handler_invoked_total", # Handler invocations
|
|
997
|
+
"thought_processing_completed", # Thought completion
|
|
998
|
+
"thought_processing_started", # Thought starts
|
|
999
|
+
"action_selected_task_complete", # Task completions
|
|
1000
|
+
"action_selected_memorize", # Memory operations
|
|
1001
|
+
]
|
|
1002
|
+
|
|
1003
|
+
metrics = []
|
|
1004
|
+
now = datetime.now(timezone.utc)
|
|
1005
|
+
|
|
1006
|
+
# Process metrics with query_metrics method
|
|
1007
|
+
for metric_name in metric_names:
|
|
1008
|
+
metric = await _process_metric_data(telemetry_service, metric_name, now)
|
|
1009
|
+
if metric:
|
|
1010
|
+
metrics.append(metric)
|
|
1011
|
+
|
|
1012
|
+
# Fallback to legacy get_metrics if needed
|
|
1013
|
+
if not metrics:
|
|
1014
|
+
metrics = await _get_legacy_metrics(telemetry_service)
|
|
1015
|
+
|
|
1016
|
+
# Calculate summary statistics
|
|
1017
|
+
summary = _calculate_metrics_summary(metrics)
|
|
1018
|
+
|
|
1019
|
+
response = MetricsResponse(metrics=metrics, summary=summary, period="24h", timestamp=now)
|
|
1020
|
+
|
|
1021
|
+
return SuccessResponse(
|
|
1022
|
+
data=response,
|
|
1023
|
+
metadata=ResponseMetadata(
|
|
1024
|
+
timestamp=datetime.now(timezone.utc), request_id=str(uuid.uuid4()), duration_ms=0
|
|
1025
|
+
),
|
|
1026
|
+
)
|
|
1027
|
+
|
|
1028
|
+
except HTTPException:
|
|
1029
|
+
# Re-raise HTTPException as-is to preserve status code
|
|
1030
|
+
raise
|
|
1031
|
+
except Exception as e:
|
|
1032
|
+
import traceback
|
|
1033
|
+
|
|
1034
|
+
logger.error(f"Error in get_detailed_metrics: {str(e)}\n{traceback.format_exc()}")
|
|
1035
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
async def _get_trace_from_task(task: Any, visibility_service: Any) -> Optional[ReasoningTraceData]:
|
|
1039
|
+
"""Extract a reasoning trace from a task via visibility service."""
|
|
1040
|
+
if not hasattr(visibility_service, "get_reasoning_trace"):
|
|
1041
|
+
return None
|
|
1042
|
+
|
|
1043
|
+
trace = await visibility_service.get_reasoning_trace(task.task_id)
|
|
1044
|
+
if not trace:
|
|
1045
|
+
return None
|
|
1046
|
+
|
|
1047
|
+
return ReasoningTraceData(
|
|
1048
|
+
trace_id=f"trace_{task.task_id}",
|
|
1049
|
+
task_id=task.task_id,
|
|
1050
|
+
task_description=task.description,
|
|
1051
|
+
start_time=(datetime.fromisoformat(task.created_at) if isinstance(task.created_at, str) else task.created_at),
|
|
1052
|
+
duration_ms=0, # TaskOutcome doesn't have completion timestamp
|
|
1053
|
+
thought_count=len(trace.thought_steps),
|
|
1054
|
+
decision_count=len(trace.decisions) if hasattr(trace, "decisions") else 0,
|
|
1055
|
+
reasoning_depth=len(trace.thought_steps) if hasattr(trace, "thought_steps") else 0,
|
|
1056
|
+
thoughts=[
|
|
1057
|
+
APIResponseThoughtStep(
|
|
1058
|
+
step=i,
|
|
1059
|
+
content=getattr(thought, "content", str(thought)),
|
|
1060
|
+
timestamp=getattr(thought, "timestamp", datetime.now(timezone.utc)),
|
|
1061
|
+
depth=getattr(thought, "depth", 0),
|
|
1062
|
+
action=getattr(thought, "action", None),
|
|
1063
|
+
confidence=getattr(thought, "confidence", None),
|
|
1064
|
+
)
|
|
1065
|
+
for i, thought in enumerate(trace.thought_steps)
|
|
1066
|
+
],
|
|
1067
|
+
outcome=trace.outcome if hasattr(trace, "outcome") else None,
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
async def _get_traces_from_task_history(visibility_service: Any, limit: int) -> List[ReasoningTraceData]:
|
|
1072
|
+
"""Get traces from task history via visibility service."""
|
|
1073
|
+
traces: List[ReasoningTraceData] = []
|
|
1074
|
+
if not hasattr(visibility_service, "get_task_history"):
|
|
1075
|
+
return traces
|
|
1076
|
+
|
|
1077
|
+
task_history = await visibility_service.get_task_history(limit=limit)
|
|
1078
|
+
|
|
1079
|
+
for task in task_history:
|
|
1080
|
+
trace_data = await _get_trace_from_task(task, visibility_service)
|
|
1081
|
+
if trace_data:
|
|
1082
|
+
traces.append(trace_data)
|
|
1083
|
+
|
|
1084
|
+
return traces
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
async def _get_current_reasoning_trace(visibility_service: Any) -> Optional[ReasoningTraceData]:
|
|
1088
|
+
"""Get current reasoning trace via visibility service."""
|
|
1089
|
+
if not hasattr(visibility_service, "get_current_reasoning"):
|
|
1090
|
+
return None
|
|
1091
|
+
|
|
1092
|
+
current = await visibility_service.get_current_reasoning()
|
|
1093
|
+
if not current:
|
|
1094
|
+
return None
|
|
1095
|
+
|
|
1096
|
+
return ReasoningTraceData(
|
|
1097
|
+
trace_id="trace_current",
|
|
1098
|
+
task_id=current.get("task_id"),
|
|
1099
|
+
task_description=current.get("task_description"),
|
|
1100
|
+
start_time=datetime.now(timezone.utc),
|
|
1101
|
+
duration_ms=0,
|
|
1102
|
+
thought_count=len(current.get("thoughts", [])),
|
|
1103
|
+
decision_count=0,
|
|
1104
|
+
reasoning_depth=current.get("depth", 0),
|
|
1105
|
+
thoughts=[_convert_thought_to_api_response(i, t) for i, t in enumerate(current.get("thoughts", []))],
|
|
1106
|
+
outcome=None,
|
|
1107
|
+
)
|
|
1108
|
+
|
|
1109
|
+
|
|
1110
|
+
def _extract_content_from_thought_data(thought_data: Any) -> str:
|
|
1111
|
+
"""Extract content from thought data with fallbacks."""
|
|
1112
|
+
if hasattr(thought_data, "thought") and hasattr(thought_data.thought, "content"):
|
|
1113
|
+
return str(thought_data.thought.content)
|
|
1114
|
+
if isinstance(thought_data, dict):
|
|
1115
|
+
return str(thought_data.get("content", ""))
|
|
1116
|
+
return str(thought_data)
|
|
1117
|
+
|
|
1118
|
+
|
|
1119
|
+
def _extract_timestamp_from_thought_data(thought_data: Any) -> datetime:
|
|
1120
|
+
"""Extract timestamp from thought data with fallbacks."""
|
|
1121
|
+
if hasattr(thought_data, "thought") and hasattr(thought_data.thought, "timestamp"):
|
|
1122
|
+
ts = thought_data.thought.timestamp
|
|
1123
|
+
if isinstance(ts, datetime):
|
|
1124
|
+
return ts
|
|
1125
|
+
return datetime.now(timezone.utc)
|
|
1126
|
+
if isinstance(thought_data, dict):
|
|
1127
|
+
ts_str = thought_data.get("timestamp", datetime.now(timezone.utc).isoformat())
|
|
1128
|
+
if isinstance(ts_str, str):
|
|
1129
|
+
return datetime.fromisoformat(ts_str)
|
|
1130
|
+
return datetime.now(timezone.utc)
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
def _extract_depth_from_thought_data(thought_data: Any) -> int:
|
|
1134
|
+
"""Extract depth from thought data with fallbacks."""
|
|
1135
|
+
if hasattr(thought_data, "thought") and hasattr(thought_data.thought, "depth"):
|
|
1136
|
+
depth = thought_data.thought.depth
|
|
1137
|
+
return int(depth) if depth is not None else 0
|
|
1138
|
+
if isinstance(thought_data, dict):
|
|
1139
|
+
depth = thought_data.get("depth", 0)
|
|
1140
|
+
return int(depth) if depth is not None else 0
|
|
1141
|
+
return 0
|
|
1142
|
+
|
|
1143
|
+
|
|
1144
|
+
def _extract_action_from_thought_data(thought_data: Any) -> Optional[str]:
|
|
1145
|
+
"""Extract action from thought data with fallbacks."""
|
|
1146
|
+
if hasattr(thought_data, "thought") and hasattr(thought_data.thought, "action"):
|
|
1147
|
+
action = thought_data.thought.action
|
|
1148
|
+
return str(action) if action is not None else None
|
|
1149
|
+
if isinstance(thought_data, dict):
|
|
1150
|
+
action = thought_data.get("action")
|
|
1151
|
+
return str(action) if action is not None else None
|
|
1152
|
+
return None
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
def _extract_confidence_from_thought_data(thought_data: Any) -> Optional[float]:
|
|
1156
|
+
"""Extract confidence from thought data with fallbacks."""
|
|
1157
|
+
if hasattr(thought_data, "thought") and hasattr(thought_data.thought, "confidence"):
|
|
1158
|
+
conf = thought_data.thought.confidence
|
|
1159
|
+
return float(conf) if conf is not None else None
|
|
1160
|
+
if isinstance(thought_data, dict):
|
|
1161
|
+
conf = thought_data.get("confidence")
|
|
1162
|
+
return float(conf) if conf is not None else None
|
|
1163
|
+
return None
|
|
1164
|
+
|
|
1165
|
+
|
|
1166
|
+
def _convert_thought_to_api_response(step_index: int, thought_data: Any) -> APIResponseThoughtStep:
|
|
1167
|
+
"""Convert thought data to APIResponseThoughtStep."""
|
|
1168
|
+
return APIResponseThoughtStep(
|
|
1169
|
+
step=step_index,
|
|
1170
|
+
content=_extract_content_from_thought_data(thought_data),
|
|
1171
|
+
timestamp=_extract_timestamp_from_thought_data(thought_data),
|
|
1172
|
+
depth=_extract_depth_from_thought_data(thought_data),
|
|
1173
|
+
action=_extract_action_from_thought_data(thought_data),
|
|
1174
|
+
confidence=_extract_confidence_from_thought_data(thought_data),
|
|
1175
|
+
)
|
|
1176
|
+
|
|
1177
|
+
|
|
1178
|
+
async def _get_traces_from_visibility_service(visibility_service: Any, limit: int) -> List[ReasoningTraceData]:
|
|
1179
|
+
"""Get reasoning traces from visibility service."""
|
|
1180
|
+
traces = await _get_traces_from_task_history(visibility_service, limit)
|
|
1181
|
+
|
|
1182
|
+
# If no task history, try current reasoning
|
|
1183
|
+
if not traces:
|
|
1184
|
+
current_trace = await _get_current_reasoning_trace(visibility_service)
|
|
1185
|
+
if current_trace:
|
|
1186
|
+
traces.append(current_trace)
|
|
1187
|
+
|
|
1188
|
+
return traces
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
def _parse_timestamp(timestamp_value: Any) -> datetime:
|
|
1192
|
+
"""Parse timestamp from various formats."""
|
|
1193
|
+
if isinstance(timestamp_value, str):
|
|
1194
|
+
return datetime.fromisoformat(timestamp_value)
|
|
1195
|
+
elif isinstance(timestamp_value, datetime):
|
|
1196
|
+
return timestamp_value
|
|
1197
|
+
else:
|
|
1198
|
+
return datetime.now(timezone.utc)
|
|
1199
|
+
|
|
1200
|
+
|
|
1201
|
+
async def _get_traces_from_audit_service(
|
|
1202
|
+
audit_service: Any, start_time: Optional[datetime], end_time: Optional[datetime], limit: int
|
|
1203
|
+
) -> List[ReasoningTraceData]:
|
|
1204
|
+
"""Get reasoning traces from audit service as fallback."""
|
|
1205
|
+
# Query audit entries related to reasoning using the actual audit service method
|
|
1206
|
+
from ciris_engine.schemas.services.graph.audit import AuditQuery
|
|
1207
|
+
|
|
1208
|
+
query = AuditQuery(
|
|
1209
|
+
start_time=start_time,
|
|
1210
|
+
end_time=end_time,
|
|
1211
|
+
event_type="handler_action_ponder",
|
|
1212
|
+
limit=limit * 10, # Get more to group
|
|
1213
|
+
)
|
|
1214
|
+
entries = await audit_service.query_audit_trail(query)
|
|
1215
|
+
|
|
1216
|
+
# Group by correlation ID or time window
|
|
1217
|
+
trace_groups = defaultdict(list)
|
|
1218
|
+
for entry in entries:
|
|
1219
|
+
# AuditEntry objects have .context attribute which is AuditEntryContext
|
|
1220
|
+
context_data = entry.context.additional_data or {}
|
|
1221
|
+
timestamp = entry.timestamp
|
|
1222
|
+
trace_key = context_data.get("task_id", timestamp.strftime("%Y%m%d%H%M"))
|
|
1223
|
+
trace_groups[trace_key].append(entry)
|
|
1224
|
+
|
|
1225
|
+
traces = []
|
|
1226
|
+
for trace_id, entries in list(trace_groups.items())[:limit]:
|
|
1227
|
+
if entries:
|
|
1228
|
+
trace_data = _build_trace_from_audit_entries(trace_id, entries)
|
|
1229
|
+
traces.append(trace_data)
|
|
1230
|
+
|
|
1231
|
+
return traces
|
|
1232
|
+
|
|
1233
|
+
|
|
1234
|
+
def _build_trace_from_audit_entries(trace_id: str, entries: List[Any]) -> ReasoningTraceData:
|
|
1235
|
+
"""Build a ReasoningTraceData from audit entries."""
|
|
1236
|
+
# Sort entries by timestamp - AuditEntry objects have .timestamp attribute
|
|
1237
|
+
entries.sort(key=lambda e: e.timestamp)
|
|
1238
|
+
|
|
1239
|
+
start_timestamp = entries[0].timestamp
|
|
1240
|
+
end_timestamp = entries[-1].timestamp
|
|
1241
|
+
|
|
1242
|
+
return ReasoningTraceData(
|
|
1243
|
+
trace_id=f"trace_{trace_id}",
|
|
1244
|
+
task_id=trace_id if trace_id != start_timestamp.strftime("%Y%m%d%H%M") else None,
|
|
1245
|
+
task_description=None,
|
|
1246
|
+
start_time=start_timestamp,
|
|
1247
|
+
duration_ms=(end_timestamp - start_timestamp).total_seconds() * 1000,
|
|
1248
|
+
thought_count=len(entries),
|
|
1249
|
+
decision_count=sum(1 for e in entries if "decision" in e.action.lower()),
|
|
1250
|
+
reasoning_depth=(max((e.context.additional_data or {}).get("depth", 0) for e in entries) if entries else 0),
|
|
1251
|
+
thoughts=[
|
|
1252
|
+
APIResponseThoughtStep(
|
|
1253
|
+
step=i,
|
|
1254
|
+
content=(e.context.additional_data or {}).get("thought", e.action),
|
|
1255
|
+
timestamp=e.timestamp,
|
|
1256
|
+
depth=(e.context.additional_data or {}).get("depth", 0),
|
|
1257
|
+
action=(e.context.additional_data or {}).get("action"),
|
|
1258
|
+
confidence=(e.context.additional_data or {}).get("confidence"),
|
|
1259
|
+
)
|
|
1260
|
+
for i, e in enumerate(entries)
|
|
1261
|
+
],
|
|
1262
|
+
outcome=None,
|
|
1263
|
+
)
|
|
1264
|
+
|
|
1265
|
+
|
|
1266
|
+
@router.get("/traces", response_model=SuccessResponse[TracesResponse])
|
|
1267
|
+
async def get_reasoning_traces(
|
|
1268
|
+
request: Request,
|
|
1269
|
+
auth: AuthContext = Depends(require_observer),
|
|
1270
|
+
limit: int = Query(10, ge=1, le=100, description="Maximum traces to return"),
|
|
1271
|
+
start_time: Optional[datetime] = Query(None, description=DESC_START_TIME),
|
|
1272
|
+
end_time: Optional[datetime] = Query(None, description=DESC_END_TIME),
|
|
1273
|
+
) -> SuccessResponse[TracesResponse]:
|
|
1274
|
+
"""
|
|
1275
|
+
Reasoning traces.
|
|
1276
|
+
|
|
1277
|
+
Get reasoning traces showing agent thought processes and decision-making.
|
|
1278
|
+
"""
|
|
1279
|
+
# These services MUST exist
|
|
1280
|
+
visibility_service = request.app.state.visibility_service
|
|
1281
|
+
audit_service = request.app.state.audit_service
|
|
1282
|
+
|
|
1283
|
+
if not visibility_service:
|
|
1284
|
+
raise HTTPException(status_code=503, detail="Critical system failure: Visibility service not initialized")
|
|
1285
|
+
if not audit_service:
|
|
1286
|
+
raise HTTPException(status_code=503, detail=ERROR_AUDIT_NOT_INITIALIZED)
|
|
1287
|
+
|
|
1288
|
+
traces = []
|
|
1289
|
+
|
|
1290
|
+
# Try to get from visibility service first
|
|
1291
|
+
try:
|
|
1292
|
+
traces = await _get_traces_from_visibility_service(visibility_service, limit)
|
|
1293
|
+
except Exception as e:
|
|
1294
|
+
logger.warning(
|
|
1295
|
+
f"Telemetry metric retrieval failed for reasoning traces from visibility service: {type(e).__name__}: {str(e)} - Returning default/empty value"
|
|
1296
|
+
)
|
|
1297
|
+
|
|
1298
|
+
# Fallback to audit-based traces
|
|
1299
|
+
if not traces:
|
|
1300
|
+
try:
|
|
1301
|
+
traces = await _get_traces_from_audit_service(audit_service, start_time, end_time, limit)
|
|
1302
|
+
except Exception as e:
|
|
1303
|
+
logger.warning(
|
|
1304
|
+
f"Telemetry metric retrieval failed for reasoning traces from audit service: {type(e).__name__}: {str(e)} - Returning default/empty value"
|
|
1305
|
+
)
|
|
1306
|
+
|
|
1307
|
+
response = TracesResponse(traces=traces, total=len(traces), has_more=len(traces) == limit)
|
|
1308
|
+
|
|
1309
|
+
return SuccessResponse(
|
|
1310
|
+
data=response,
|
|
1311
|
+
metadata=ResponseMetadata(timestamp=datetime.now(timezone.utc), request_id=str(uuid.uuid4()), duration_ms=0),
|
|
1312
|
+
)
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
def _validate_audit_service(audit_service: Any) -> None:
|
|
1316
|
+
"""Validate that audit service is available."""
|
|
1317
|
+
if not audit_service:
|
|
1318
|
+
raise HTTPException(status_code=503, detail=ERROR_AUDIT_NOT_INITIALIZED)
|
|
1319
|
+
|
|
1320
|
+
|
|
1321
|
+
def _determine_log_level(action: str) -> str:
|
|
1322
|
+
"""Determine log level from audit action."""
|
|
1323
|
+
action_lower = action.lower()
|
|
1324
|
+
if "critical" in action_lower or "fatal" in action_lower:
|
|
1325
|
+
return "CRITICAL"
|
|
1326
|
+
elif "error" in action_lower or "fail" in action_lower:
|
|
1327
|
+
return "ERROR"
|
|
1328
|
+
elif "warning" in action_lower or "warn" in action_lower:
|
|
1329
|
+
return "WARNING"
|
|
1330
|
+
elif "debug" in action_lower:
|
|
1331
|
+
return "DEBUG"
|
|
1332
|
+
return "INFO"
|
|
1333
|
+
|
|
1334
|
+
|
|
1335
|
+
def _extract_service_name(actor: str) -> str:
|
|
1336
|
+
"""Extract service name from actor string."""
|
|
1337
|
+
return actor.split(".")[0] if "." in actor else actor
|
|
1338
|
+
|
|
1339
|
+
|
|
1340
|
+
def _should_include_log(
|
|
1341
|
+
log_level: str, log_service: str, level_filter: Optional[str], service_filter: Optional[str]
|
|
1342
|
+
) -> bool:
|
|
1343
|
+
"""Check if log entry should be included based on filters."""
|
|
1344
|
+
if level_filter and log_level != level_filter.upper():
|
|
1345
|
+
return False
|
|
1346
|
+
if service_filter and log_service.lower() != service_filter.lower():
|
|
1347
|
+
return False
|
|
1348
|
+
return True
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
def _build_log_entry(entry: Any, log_level: str, log_service: str) -> LogEntryResponse:
|
|
1352
|
+
"""Build LogEntry from audit entry."""
|
|
1353
|
+
# AuditEntry.context is AuditEntryContext with .additional_data dict
|
|
1354
|
+
context_data = entry.context.additional_data or {}
|
|
1355
|
+
|
|
1356
|
+
return LogEntryResponse(
|
|
1357
|
+
timestamp=entry.timestamp,
|
|
1358
|
+
level=log_level,
|
|
1359
|
+
service=log_service,
|
|
1360
|
+
message=f"{entry.action}: {context_data.get('description', '')}".strip(": "),
|
|
1361
|
+
context=LogContext(
|
|
1362
|
+
trace_id=entry.context.correlation_id,
|
|
1363
|
+
correlation_id=entry.context.correlation_id,
|
|
1364
|
+
user_id=entry.context.user_id,
|
|
1365
|
+
entity_id=context_data.get("entity_id"),
|
|
1366
|
+
error_details=context_data.get("error_details", {}) if "error" in log_level.lower() else None,
|
|
1367
|
+
metadata=context_data,
|
|
1368
|
+
),
|
|
1369
|
+
trace_id=entry.context.correlation_id,
|
|
1370
|
+
)
|
|
1371
|
+
|
|
1372
|
+
|
|
1373
|
+
async def _get_logs_from_audit_service(
|
|
1374
|
+
audit_service: Any,
|
|
1375
|
+
start_time: Optional[datetime],
|
|
1376
|
+
end_time: Optional[datetime],
|
|
1377
|
+
level: Optional[str],
|
|
1378
|
+
service: Optional[str],
|
|
1379
|
+
limit: int,
|
|
1380
|
+
) -> List[LogEntryResponse]:
|
|
1381
|
+
"""Get logs from audit service with filtering."""
|
|
1382
|
+
logs = []
|
|
1383
|
+
try:
|
|
1384
|
+
from ciris_engine.schemas.services.graph.audit import AuditQuery
|
|
1385
|
+
|
|
1386
|
+
audit_query = AuditQuery(start_time=start_time, end_time=end_time, limit=limit * 2) # Get extra for filtering
|
|
1387
|
+
entries = await audit_service.query_audit_trail(audit_query)
|
|
1388
|
+
|
|
1389
|
+
for entry in entries:
|
|
1390
|
+
log_level = _determine_log_level(entry.action)
|
|
1391
|
+
log_service = _extract_service_name(entry.actor)
|
|
1392
|
+
|
|
1393
|
+
if not _should_include_log(log_level, log_service, level, service):
|
|
1394
|
+
continue
|
|
1395
|
+
|
|
1396
|
+
log = _build_log_entry(entry, log_level, log_service)
|
|
1397
|
+
logs.append(log)
|
|
1398
|
+
|
|
1399
|
+
if len(logs) >= limit:
|
|
1400
|
+
break
|
|
1401
|
+
except Exception as e:
|
|
1402
|
+
logger.warning(f"Failed to get logs from audit service: {e}")
|
|
1403
|
+
|
|
1404
|
+
return logs
|
|
1405
|
+
|
|
1406
|
+
|
|
1407
|
+
async def _get_logs_from_file_reader(
|
|
1408
|
+
level: Optional[str],
|
|
1409
|
+
service: Optional[str],
|
|
1410
|
+
limit: int,
|
|
1411
|
+
start_time: Optional[datetime],
|
|
1412
|
+
end_time: Optional[datetime],
|
|
1413
|
+
) -> List[LogEntryResponse]:
|
|
1414
|
+
"""Get logs from file reader if available."""
|
|
1415
|
+
try:
|
|
1416
|
+
from .telemetry_logs_reader import log_reader
|
|
1417
|
+
|
|
1418
|
+
# The log_reader returns List[LogEntry] from telemetry_models, need to convert
|
|
1419
|
+
logs = log_reader.read_logs(
|
|
1420
|
+
level=level,
|
|
1421
|
+
service=service,
|
|
1422
|
+
limit=limit,
|
|
1423
|
+
start_time=start_time,
|
|
1424
|
+
end_time=end_time,
|
|
1425
|
+
include_incidents=True,
|
|
1426
|
+
)
|
|
1427
|
+
# Convert LogEntry to LogEntryResponse (they are the same structure now)
|
|
1428
|
+
return [LogEntryResponse(**log.model_dump()) for log in logs]
|
|
1429
|
+
except ImportError:
|
|
1430
|
+
logger.debug("Log reader not available, using audit entries only")
|
|
1431
|
+
return []
|
|
1432
|
+
except Exception as e:
|
|
1433
|
+
logger.warning(f"Failed to read log files: {e}, using audit entries only")
|
|
1434
|
+
return []
|
|
1435
|
+
|
|
1436
|
+
|
|
1437
|
+
@router.get("/logs", response_model=SuccessResponse[LogsResponse])
|
|
1438
|
+
async def get_system_logs(
|
|
1439
|
+
request: Request,
|
|
1440
|
+
auth: AuthContext = Depends(require_observer),
|
|
1441
|
+
start_time: Optional[datetime] = Query(None, description=DESC_START_TIME),
|
|
1442
|
+
end_time: Optional[datetime] = Query(None, description=DESC_END_TIME),
|
|
1443
|
+
level: Optional[str] = Query(None, description="Log level filter"),
|
|
1444
|
+
service: Optional[str] = Query(None, description="Service filter"),
|
|
1445
|
+
limit: int = Query(100, ge=1, le=1000, description="Maximum logs to return"),
|
|
1446
|
+
) -> SuccessResponse[LogsResponse]:
|
|
1447
|
+
"""
|
|
1448
|
+
System logs.
|
|
1449
|
+
|
|
1450
|
+
Get system logs from all services with filtering capabilities.
|
|
1451
|
+
"""
|
|
1452
|
+
audit_service = request.app.state.audit_service
|
|
1453
|
+
_validate_audit_service(audit_service)
|
|
1454
|
+
|
|
1455
|
+
# Get logs from audit service
|
|
1456
|
+
logs = await _get_logs_from_audit_service(audit_service, start_time, end_time, level, service, limit)
|
|
1457
|
+
|
|
1458
|
+
# Add file logs if we haven't reached the limit
|
|
1459
|
+
if len(logs) < limit:
|
|
1460
|
+
file_logs = await _get_logs_from_file_reader(level, service, limit - len(logs), start_time, end_time)
|
|
1461
|
+
logs.extend(file_logs)
|
|
1462
|
+
|
|
1463
|
+
response = LogsResponse(logs=logs[:limit], total=len(logs), has_more=len(logs) > limit)
|
|
1464
|
+
|
|
1465
|
+
return SuccessResponse(
|
|
1466
|
+
data=response,
|
|
1467
|
+
metadata=ResponseMetadata(timestamp=datetime.now(timezone.utc), request_id=str(uuid.uuid4()), duration_ms=0),
|
|
1468
|
+
)
|
|
1469
|
+
|
|
1470
|
+
|
|
1471
|
+
async def _query_metrics(telemetry_service: Any, query: TelemetryQuery) -> List[QueryResult]:
|
|
1472
|
+
"""Query metrics data."""
|
|
1473
|
+
results: List[QueryResult] = []
|
|
1474
|
+
if not (telemetry_service and hasattr(telemetry_service, "query_metrics")):
|
|
1475
|
+
return results
|
|
1476
|
+
|
|
1477
|
+
metric_names = query.filters.metric_names or []
|
|
1478
|
+
for metric_name in metric_names:
|
|
1479
|
+
data_points = await telemetry_service.query_metrics(
|
|
1480
|
+
metric_name=metric_name, start_time=query.start_time, end_time=query.end_time
|
|
1481
|
+
)
|
|
1482
|
+
if data_points:
|
|
1483
|
+
results.append(
|
|
1484
|
+
QueryResult(
|
|
1485
|
+
id=f"metric_{metric_name}",
|
|
1486
|
+
type="metric",
|
|
1487
|
+
timestamp=datetime.now(timezone.utc),
|
|
1488
|
+
data={
|
|
1489
|
+
"metric_name": metric_name,
|
|
1490
|
+
"data_points": data_points,
|
|
1491
|
+
"count": len(data_points),
|
|
1492
|
+
},
|
|
1493
|
+
)
|
|
1494
|
+
)
|
|
1495
|
+
return results
|
|
1496
|
+
|
|
1497
|
+
|
|
1498
|
+
async def _query_traces(visibility_service: Any, query: TelemetryQuery) -> List[QueryResult]:
|
|
1499
|
+
"""Query reasoning traces."""
|
|
1500
|
+
results: List[QueryResult] = []
|
|
1501
|
+
if not visibility_service:
|
|
1502
|
+
return results
|
|
1503
|
+
|
|
1504
|
+
trace_limit = query.filters.limit or query.limit
|
|
1505
|
+
traces = []
|
|
1506
|
+
|
|
1507
|
+
if hasattr(visibility_service, "query_traces"):
|
|
1508
|
+
traces = await visibility_service.query_traces(
|
|
1509
|
+
start_time=query.start_time, end_time=query.end_time, limit=trace_limit
|
|
1510
|
+
)
|
|
1511
|
+
|
|
1512
|
+
for trace in traces:
|
|
1513
|
+
results.append(
|
|
1514
|
+
QueryResult(
|
|
1515
|
+
id=trace.trace_id,
|
|
1516
|
+
type="trace",
|
|
1517
|
+
timestamp=trace.start_time,
|
|
1518
|
+
data={
|
|
1519
|
+
"trace_id": trace.trace_id,
|
|
1520
|
+
"task_id": trace.task_id,
|
|
1521
|
+
"duration_ms": trace.duration_ms,
|
|
1522
|
+
"thought_count": trace.thought_count,
|
|
1523
|
+
},
|
|
1524
|
+
)
|
|
1525
|
+
)
|
|
1526
|
+
return results
|
|
1527
|
+
|
|
1528
|
+
|
|
1529
|
+
def _should_include_log_entry(entry: Any, filters: Any) -> bool:
|
|
1530
|
+
"""Check if log entry should be included based on filters."""
|
|
1531
|
+
if not filters:
|
|
1532
|
+
return True
|
|
1533
|
+
|
|
1534
|
+
if filters.services and entry.actor not in filters.services:
|
|
1535
|
+
return False
|
|
1536
|
+
|
|
1537
|
+
if filters.severity:
|
|
1538
|
+
# Infer level from action
|
|
1539
|
+
if "error" in entry.action.lower() and filters.severity.upper() != "ERROR":
|
|
1540
|
+
return False
|
|
1541
|
+
|
|
1542
|
+
return True
|
|
1543
|
+
|
|
1544
|
+
|
|
1545
|
+
async def _query_logs(audit_service: Any, query: TelemetryQuery) -> List[QueryResult]:
|
|
1546
|
+
"""Query logs data."""
|
|
1547
|
+
results: List[QueryResult] = []
|
|
1548
|
+
if not audit_service:
|
|
1549
|
+
return results
|
|
1550
|
+
|
|
1551
|
+
from ciris_engine.schemas.services.graph.audit import AuditQuery
|
|
1552
|
+
|
|
1553
|
+
audit_query = AuditQuery(start_time=query.start_time, end_time=query.end_time, limit=query.limit)
|
|
1554
|
+
log_entries = await audit_service.query_audit_trail(audit_query)
|
|
1555
|
+
|
|
1556
|
+
for entry in log_entries:
|
|
1557
|
+
if not _should_include_log_entry(entry, query.filters):
|
|
1558
|
+
continue
|
|
1559
|
+
|
|
1560
|
+
results.append(
|
|
1561
|
+
QueryResult(
|
|
1562
|
+
id=f"log_{entry.timestamp.timestamp()}_{entry.actor}",
|
|
1563
|
+
type="log",
|
|
1564
|
+
timestamp=entry.timestamp,
|
|
1565
|
+
data={
|
|
1566
|
+
"timestamp": entry.timestamp.isoformat(),
|
|
1567
|
+
"service": entry.actor,
|
|
1568
|
+
"action": entry.action,
|
|
1569
|
+
"context": entry.context.model_dump() if hasattr(entry.context, "model_dump") else entry.context,
|
|
1570
|
+
},
|
|
1571
|
+
)
|
|
1572
|
+
)
|
|
1573
|
+
return results
|
|
1574
|
+
|
|
1575
|
+
|
|
1576
|
+
async def _query_incidents(incident_service: Any, query: TelemetryQuery) -> List[QueryResult]:
|
|
1577
|
+
"""Query incidents data."""
|
|
1578
|
+
results: List[QueryResult] = []
|
|
1579
|
+
if not incident_service:
|
|
1580
|
+
return results
|
|
1581
|
+
|
|
1582
|
+
incidents = await incident_service.query_incidents(
|
|
1583
|
+
start_time=query.start_time,
|
|
1584
|
+
end_time=query.end_time,
|
|
1585
|
+
severity=query.filters.severity,
|
|
1586
|
+
status=getattr(query.filters, "status", None),
|
|
1587
|
+
)
|
|
1588
|
+
|
|
1589
|
+
for incident in incidents:
|
|
1590
|
+
results.append(
|
|
1591
|
+
QueryResult(
|
|
1592
|
+
id=incident.id,
|
|
1593
|
+
type="incident",
|
|
1594
|
+
timestamp=getattr(incident, "created_at", incident.detected_at),
|
|
1595
|
+
data={
|
|
1596
|
+
"incident_id": incident.id,
|
|
1597
|
+
"severity": incident.severity,
|
|
1598
|
+
"status": incident.status,
|
|
1599
|
+
"description": incident.description,
|
|
1600
|
+
"created_at": getattr(incident, "created_at", incident.detected_at).isoformat(),
|
|
1601
|
+
},
|
|
1602
|
+
)
|
|
1603
|
+
)
|
|
1604
|
+
return results
|
|
1605
|
+
|
|
1606
|
+
|
|
1607
|
+
async def _query_insights(incident_service: Any, query: TelemetryQuery) -> List[QueryResult]:
|
|
1608
|
+
"""Query adaptation insights."""
|
|
1609
|
+
results: List[QueryResult] = []
|
|
1610
|
+
if not (incident_service and hasattr(incident_service, "get_insights")):
|
|
1611
|
+
return results
|
|
1612
|
+
|
|
1613
|
+
insights = await incident_service.get_insights(
|
|
1614
|
+
start_time=query.start_time, end_time=query.end_time, limit=query.limit
|
|
1615
|
+
)
|
|
1616
|
+
|
|
1617
|
+
for insight in insights:
|
|
1618
|
+
results.append(
|
|
1619
|
+
QueryResult(
|
|
1620
|
+
id=insight.id,
|
|
1621
|
+
type="insight",
|
|
1622
|
+
timestamp=getattr(insight, "created_at", insight.analysis_timestamp),
|
|
1623
|
+
data={
|
|
1624
|
+
"insight_id": insight.id,
|
|
1625
|
+
"insight_type": insight.insight_type,
|
|
1626
|
+
"summary": insight.summary,
|
|
1627
|
+
"details": insight.details,
|
|
1628
|
+
"created_at": getattr(insight, "created_at", insight.analysis_timestamp).isoformat(),
|
|
1629
|
+
},
|
|
1630
|
+
)
|
|
1631
|
+
)
|
|
1632
|
+
return results
|
|
1633
|
+
|
|
1634
|
+
|
|
1635
|
+
def _apply_aggregations(
|
|
1636
|
+
results: List[QueryResult], aggregations: Optional[List[str]], query_type: str
|
|
1637
|
+
) -> List[QueryResult]:
|
|
1638
|
+
"""Apply aggregations to query results."""
|
|
1639
|
+
if not aggregations:
|
|
1640
|
+
return results
|
|
1641
|
+
|
|
1642
|
+
for agg in aggregations:
|
|
1643
|
+
if agg == "count":
|
|
1644
|
+
# Return count as a QueryResult
|
|
1645
|
+
return [
|
|
1646
|
+
QueryResult(
|
|
1647
|
+
id="aggregation_count",
|
|
1648
|
+
type="aggregation",
|
|
1649
|
+
timestamp=datetime.now(timezone.utc),
|
|
1650
|
+
data={"aggregation": "count", "value": len(results)},
|
|
1651
|
+
)
|
|
1652
|
+
]
|
|
1653
|
+
elif agg == "group_by_service" and query_type == "logs":
|
|
1654
|
+
# Group logs by service
|
|
1655
|
+
grouped: Dict[str, int] = defaultdict(int)
|
|
1656
|
+
for r in results:
|
|
1657
|
+
# Access service from the data field
|
|
1658
|
+
service_val = r.data.get("service", "unknown")
|
|
1659
|
+
service = str(service_val) if service_val is not None else "unknown"
|
|
1660
|
+
grouped[service] += 1
|
|
1661
|
+
|
|
1662
|
+
# Convert grouped results to QueryResult objects
|
|
1663
|
+
return [
|
|
1664
|
+
QueryResult(
|
|
1665
|
+
id=f"aggregation_service_{k}",
|
|
1666
|
+
type="aggregation",
|
|
1667
|
+
timestamp=datetime.now(timezone.utc),
|
|
1668
|
+
data={"service": k, "count": v},
|
|
1669
|
+
)
|
|
1670
|
+
for k, v in grouped.items()
|
|
1671
|
+
]
|
|
1672
|
+
return results
|
|
1673
|
+
|
|
1674
|
+
|
|
1675
|
+
def _validate_query_services(
|
|
1676
|
+
telemetry_service: Any, visibility_service: Any, audit_service: Any, incident_service: Any
|
|
1677
|
+
) -> None:
|
|
1678
|
+
"""Validate that all required services are available for queries."""
|
|
1679
|
+
if not telemetry_service:
|
|
1680
|
+
raise HTTPException(status_code=503, detail=ERROR_TELEMETRY_NOT_INITIALIZED)
|
|
1681
|
+
if not visibility_service:
|
|
1682
|
+
raise HTTPException(status_code=503, detail="Critical system failure: Visibility service not initialized")
|
|
1683
|
+
if not audit_service:
|
|
1684
|
+
raise HTTPException(status_code=503, detail=ERROR_AUDIT_NOT_INITIALIZED)
|
|
1685
|
+
if not incident_service:
|
|
1686
|
+
raise HTTPException(status_code=503, detail="Critical system failure: Incident service not initialized")
|
|
1687
|
+
|
|
1688
|
+
|
|
1689
|
+
async def _route_query_to_handler(
|
|
1690
|
+
query: TelemetryQuery, telemetry_service: Any, visibility_service: Any, audit_service: Any, incident_service: Any
|
|
1691
|
+
) -> List[QueryResult]:
|
|
1692
|
+
"""Route query to appropriate handler based on query type."""
|
|
1693
|
+
if query.query_type == "metrics":
|
|
1694
|
+
return await _query_metrics(telemetry_service, query)
|
|
1695
|
+
elif query.query_type == "traces":
|
|
1696
|
+
return await _query_traces(visibility_service, query)
|
|
1697
|
+
elif query.query_type == "logs":
|
|
1698
|
+
return await _query_logs(audit_service, query)
|
|
1699
|
+
elif query.query_type == "incidents":
|
|
1700
|
+
return await _query_incidents(incident_service, query)
|
|
1701
|
+
elif query.query_type == "insights":
|
|
1702
|
+
return await _query_insights(incident_service, query)
|
|
1703
|
+
return []
|
|
1704
|
+
|
|
1705
|
+
|
|
1706
|
+
def _build_query_response(
|
|
1707
|
+
query: TelemetryQuery, results: List[QueryResult], execution_time_ms: float
|
|
1708
|
+
) -> SuccessResponse[QueryResponse]:
|
|
1709
|
+
"""Build the final query response."""
|
|
1710
|
+
response = QueryResponse(
|
|
1711
|
+
query_type=query.query_type,
|
|
1712
|
+
results=results[: query.limit],
|
|
1713
|
+
total=len(results),
|
|
1714
|
+
execution_time_ms=execution_time_ms,
|
|
1715
|
+
)
|
|
1716
|
+
|
|
1717
|
+
return SuccessResponse(
|
|
1718
|
+
data=response,
|
|
1719
|
+
metadata=ResponseMetadata(timestamp=datetime.now(timezone.utc), request_id=str(uuid.uuid4()), duration_ms=0),
|
|
1720
|
+
)
|
|
1721
|
+
|
|
1722
|
+
|
|
1723
|
+
@router.post("/query", response_model=SuccessResponse[QueryResponse])
|
|
1724
|
+
async def query_telemetry(
|
|
1725
|
+
request: Request, query: TelemetryQuery, auth: AuthContext = Depends(require_admin)
|
|
1726
|
+
) -> SuccessResponse[QueryResponse]:
|
|
1727
|
+
"""
|
|
1728
|
+
Custom telemetry queries.
|
|
1729
|
+
|
|
1730
|
+
Execute custom queries against telemetry data including metrics, traces, logs, incidents, and insights.
|
|
1731
|
+
Requires ADMIN role.
|
|
1732
|
+
"""
|
|
1733
|
+
start_time = datetime.now(timezone.utc)
|
|
1734
|
+
|
|
1735
|
+
# Get and validate services
|
|
1736
|
+
telemetry_service = request.app.state.telemetry_service
|
|
1737
|
+
visibility_service = request.app.state.visibility_service
|
|
1738
|
+
audit_service = request.app.state.audit_service
|
|
1739
|
+
incident_service = request.app.state.incident_management_service
|
|
1740
|
+
|
|
1741
|
+
_validate_query_services(telemetry_service, visibility_service, audit_service, incident_service)
|
|
1742
|
+
|
|
1743
|
+
try:
|
|
1744
|
+
# Route query to appropriate handler
|
|
1745
|
+
results = await _route_query_to_handler(
|
|
1746
|
+
query, telemetry_service, visibility_service, audit_service, incident_service
|
|
1747
|
+
)
|
|
1748
|
+
|
|
1749
|
+
# Apply aggregations if specified
|
|
1750
|
+
results = _apply_aggregations(results, query.aggregations, query.query_type)
|
|
1751
|
+
|
|
1752
|
+
# Calculate execution time and build response
|
|
1753
|
+
execution_time = (datetime.now(timezone.utc) - start_time).total_seconds() * 1000
|
|
1754
|
+
return _build_query_response(query, results, execution_time)
|
|
1755
|
+
|
|
1756
|
+
except HTTPException:
|
|
1757
|
+
raise
|
|
1758
|
+
except Exception as e:
|
|
1759
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
1760
|
+
|
|
1761
|
+
|
|
1762
|
+
@router.get("/metrics/{metric_name}", response_model=SuccessResponse[DetailedMetric])
|
|
1763
|
+
async def get_detailed_metric(
|
|
1764
|
+
request: Request,
|
|
1765
|
+
metric_name: str,
|
|
1766
|
+
auth: AuthContext = Depends(require_observer),
|
|
1767
|
+
hours: int = Query(24, ge=1, le=168, description="Hours of history to include"),
|
|
1768
|
+
) -> SuccessResponse[DetailedMetric]:
|
|
1769
|
+
"""
|
|
1770
|
+
Get detailed information about a specific metric.
|
|
1771
|
+
|
|
1772
|
+
Returns current value, trends, and historical data for the specified metric.
|
|
1773
|
+
"""
|
|
1774
|
+
# Telemetry service MUST exist - if it doesn't, we have a critical failure
|
|
1775
|
+
telemetry_service = request.app.state.telemetry_service
|
|
1776
|
+
if not telemetry_service:
|
|
1777
|
+
raise HTTPException(status_code=503, detail=ERROR_TELEMETRY_NOT_INITIALIZED)
|
|
1778
|
+
|
|
1779
|
+
try:
|
|
1780
|
+
now = datetime.now(timezone.utc)
|
|
1781
|
+
start_time = now - timedelta(hours=hours)
|
|
1782
|
+
|
|
1783
|
+
# Get metric data
|
|
1784
|
+
data_points = []
|
|
1785
|
+
if hasattr(telemetry_service, "query_metrics"):
|
|
1786
|
+
data_points = await telemetry_service.query_metrics(
|
|
1787
|
+
metric_name=metric_name, start_time=start_time, end_time=now
|
|
1788
|
+
)
|
|
1789
|
+
|
|
1790
|
+
if not data_points:
|
|
1791
|
+
raise HTTPException(status_code=404, detail=f"Metric '{metric_name}' not found")
|
|
1792
|
+
|
|
1793
|
+
# Calculate statistics
|
|
1794
|
+
values = [float(dp.value) for dp in data_points]
|
|
1795
|
+
current_value = values[-1] if values else 0.0
|
|
1796
|
+
hourly_avg = sum(values[-60:]) / len(values[-60:]) if len(values) > 60 else sum(values) / len(values)
|
|
1797
|
+
daily_avg = sum(values) / len(values)
|
|
1798
|
+
|
|
1799
|
+
# Determine trend
|
|
1800
|
+
trend = "stable"
|
|
1801
|
+
if len(values) > 10:
|
|
1802
|
+
recent = sum(values[-10:]) / 10
|
|
1803
|
+
older = sum(values[-20:-10]) / 10
|
|
1804
|
+
if recent > older * 1.1:
|
|
1805
|
+
trend = "up"
|
|
1806
|
+
elif recent < older * 0.9:
|
|
1807
|
+
trend = "down"
|
|
1808
|
+
|
|
1809
|
+
# Determine unit
|
|
1810
|
+
unit = None
|
|
1811
|
+
if "tokens" in metric_name:
|
|
1812
|
+
unit = "tokens"
|
|
1813
|
+
elif "time" in metric_name or "latency" in metric_name:
|
|
1814
|
+
unit = "ms"
|
|
1815
|
+
elif "percent" in metric_name or "rate" in metric_name:
|
|
1816
|
+
unit = "%"
|
|
1817
|
+
elif "bytes" in metric_name or "memory" in metric_name:
|
|
1818
|
+
unit = "bytes"
|
|
1819
|
+
elif "count" in metric_name or "total" in metric_name:
|
|
1820
|
+
unit = "count"
|
|
1821
|
+
|
|
1822
|
+
metric = DetailedMetric(
|
|
1823
|
+
name=metric_name,
|
|
1824
|
+
current_value=current_value,
|
|
1825
|
+
unit=unit,
|
|
1826
|
+
trend=trend,
|
|
1827
|
+
hourly_average=hourly_avg,
|
|
1828
|
+
daily_average=daily_avg,
|
|
1829
|
+
by_service=[], # Could be populated if service tags are available
|
|
1830
|
+
recent_data=[
|
|
1831
|
+
MetricData(
|
|
1832
|
+
timestamp=dp.timestamp,
|
|
1833
|
+
value=float(dp.value),
|
|
1834
|
+
tags=MetricTags(**dp.tags) if dp.tags else MetricTags(),
|
|
1835
|
+
)
|
|
1836
|
+
for dp in data_points[-100:] # Last 100 data points
|
|
1837
|
+
],
|
|
1838
|
+
)
|
|
1839
|
+
|
|
1840
|
+
return SuccessResponse(
|
|
1841
|
+
data=metric,
|
|
1842
|
+
metadata=ResponseMetadata(
|
|
1843
|
+
timestamp=datetime.now(timezone.utc), request_id=str(uuid.uuid4()), duration_ms=0
|
|
1844
|
+
),
|
|
1845
|
+
)
|
|
1846
|
+
|
|
1847
|
+
except HTTPException:
|
|
1848
|
+
# Re-raise HTTPException as-is to preserve status code
|
|
1849
|
+
raise
|
|
1850
|
+
except Exception as e:
|
|
1851
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
1852
|
+
|
|
1853
|
+
|
|
1854
|
+
# All resource metric models are now in telemetry_models.py with proper type safety
|
|
1855
|
+
|
|
1856
|
+
|
|
1857
|
+
# Helper functions moved to telemetry_helpers.py
|
|
1858
|
+
|
|
1859
|
+
|
|
1860
|
+
@router.get("/unified", response_model=None)
|
|
1861
|
+
async def get_unified_telemetry(
|
|
1862
|
+
request: Request,
|
|
1863
|
+
auth: AuthContext = Depends(require_observer),
|
|
1864
|
+
view: str = Query("summary", description="View type: summary|health|operational|detailed|performance|reliability"),
|
|
1865
|
+
category: Optional[str] = Query(
|
|
1866
|
+
None, description="Filter by category: buses|graph|infrastructure|governance|runtime|adapters|components|all"
|
|
1867
|
+
),
|
|
1868
|
+
format: str = Query("json", description="Output format: json|prometheus|graphite"),
|
|
1869
|
+
live: bool = Query(False, description="Force live collection (bypass cache)"),
|
|
1870
|
+
) -> JSONDict | Response:
|
|
1871
|
+
"""
|
|
1872
|
+
Unified enterprise telemetry endpoint.
|
|
1873
|
+
|
|
1874
|
+
This single endpoint replaces 78+ individual telemetry routes by intelligently
|
|
1875
|
+
aggregating metrics from all 22 required services using parallel collection.
|
|
1876
|
+
|
|
1877
|
+
Features:
|
|
1878
|
+
- Parallel collection from all services (10x faster than sequential)
|
|
1879
|
+
- Smart caching with 30-second TTL
|
|
1880
|
+
- Multiple views for different stakeholders
|
|
1881
|
+
- System health and reliability scoring
|
|
1882
|
+
- Export formats for monitoring tools
|
|
1883
|
+
|
|
1884
|
+
Examples:
|
|
1885
|
+
- /telemetry/unified?view=summary - Executive dashboard
|
|
1886
|
+
- /telemetry/unified?view=health - Quick health check
|
|
1887
|
+
- /telemetry/unified?view=operational&live=true - Live ops data
|
|
1888
|
+
- /telemetry/unified?view=reliability - System reliability metrics
|
|
1889
|
+
- /telemetry/unified?category=buses - Just bus metrics
|
|
1890
|
+
- /telemetry/unified?format=prometheus - Prometheus export
|
|
1891
|
+
"""
|
|
1892
|
+
try:
|
|
1893
|
+
# Get the telemetry service
|
|
1894
|
+
telemetry_service = getattr(request.app.state, "telemetry_service", None)
|
|
1895
|
+
if not telemetry_service:
|
|
1896
|
+
raise HTTPException(status_code=503, detail=ERROR_TELEMETRY_SERVICE_NOT_AVAILABLE)
|
|
1897
|
+
|
|
1898
|
+
# Get telemetry data - NO FALLBACKS, fail FAST and LOUD per CIRIS philosophy
|
|
1899
|
+
if not hasattr(telemetry_service, "get_aggregated_telemetry"):
|
|
1900
|
+
raise HTTPException(
|
|
1901
|
+
status_code=503,
|
|
1902
|
+
detail="CRITICAL: Telemetry service does not have get_aggregated_telemetry method - NO FALLBACKS!",
|
|
1903
|
+
)
|
|
1904
|
+
result = await get_telemetry_from_service(telemetry_service, view, category, format, live)
|
|
1905
|
+
|
|
1906
|
+
# Handle export formats
|
|
1907
|
+
if format == "prometheus":
|
|
1908
|
+
content = convert_to_prometheus(result)
|
|
1909
|
+
return Response(content=content, media_type="text/plain; version=0.0.4; charset=utf-8")
|
|
1910
|
+
elif format == "graphite":
|
|
1911
|
+
content = convert_to_graphite(result)
|
|
1912
|
+
return Response(content=content, media_type="text/plain; charset=utf-8")
|
|
1913
|
+
|
|
1914
|
+
return result
|
|
1915
|
+
|
|
1916
|
+
except HTTPException:
|
|
1917
|
+
# Re-raise HTTPException as-is to preserve status code
|
|
1918
|
+
raise
|
|
1919
|
+
except Exception as e:
|
|
1920
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
1921
|
+
|
|
1922
|
+
|
|
1923
|
+
@router.get("/resources/history", response_model=SuccessResponse)
|
|
1924
|
+
async def get_resource_history(
|
|
1925
|
+
request: Request,
|
|
1926
|
+
auth: AuthContext = Depends(require_observer),
|
|
1927
|
+
hours: int = Query(24, ge=1, le=168, description="Hours of history"),
|
|
1928
|
+
) -> SuccessResponse[ResourceHistoryResponse]:
|
|
1929
|
+
"""
|
|
1930
|
+
Get historical resource usage data.
|
|
1931
|
+
|
|
1932
|
+
Returns time-series data for resource usage over the specified period.
|
|
1933
|
+
"""
|
|
1934
|
+
# Telemetry service MUST exist - if it doesn't, we have a critical failure
|
|
1935
|
+
telemetry_service = request.app.state.telemetry_service
|
|
1936
|
+
if not telemetry_service:
|
|
1937
|
+
raise HTTPException(status_code=503, detail=ERROR_TELEMETRY_NOT_INITIALIZED)
|
|
1938
|
+
|
|
1939
|
+
try:
|
|
1940
|
+
now = datetime.now(timezone.utc)
|
|
1941
|
+
start_time = now - timedelta(hours=hours)
|
|
1942
|
+
|
|
1943
|
+
# Use helper classes for clean separation of concerns
|
|
1944
|
+
collector = ResourceMetricsCollector()
|
|
1945
|
+
extractor = MetricValueExtractor()
|
|
1946
|
+
builder = ResourceMetricBuilder()
|
|
1947
|
+
|
|
1948
|
+
# Fetch all metrics concurrently
|
|
1949
|
+
cpu_data, memory_data, disk_data = await collector.fetch_all_resource_metrics(
|
|
1950
|
+
telemetry_service, start_time, now
|
|
1951
|
+
)
|
|
1952
|
+
|
|
1953
|
+
# Extract values for statistics
|
|
1954
|
+
cpu_values, memory_values, disk_values = extractor.extract_all_values(cpu_data, memory_data, disk_data)
|
|
1955
|
+
|
|
1956
|
+
# Build data points
|
|
1957
|
+
default_timestamp = now.isoformat()
|
|
1958
|
+
point_builder = ResourceDataPointBuilder()
|
|
1959
|
+
cpu_points, memory_points, disk_points = point_builder.build_all_data_points(
|
|
1960
|
+
cpu_data, memory_data, disk_data, default_timestamp
|
|
1961
|
+
)
|
|
1962
|
+
|
|
1963
|
+
# Build complete metrics with stats
|
|
1964
|
+
cpu_metric, memory_metric, disk_metric = builder.build_all_metrics(
|
|
1965
|
+
cpu_points, cpu_values, memory_points, memory_values, disk_points, disk_values
|
|
1966
|
+
)
|
|
1967
|
+
|
|
1968
|
+
# Create properly typed response using Pydantic models
|
|
1969
|
+
response = ResourceHistoryResponse(
|
|
1970
|
+
period=TimePeriod(start=start_time.isoformat(), end=now.isoformat(), hours=hours),
|
|
1971
|
+
cpu=cpu_metric,
|
|
1972
|
+
memory=memory_metric,
|
|
1973
|
+
disk=disk_metric,
|
|
1974
|
+
)
|
|
1975
|
+
|
|
1976
|
+
return SuccessResponse(
|
|
1977
|
+
data=response,
|
|
1978
|
+
metadata=ResponseMetadata(
|
|
1979
|
+
timestamp=datetime.now(timezone.utc), request_id=str(uuid.uuid4()), duration_ms=0
|
|
1980
|
+
),
|
|
1981
|
+
)
|
|
1982
|
+
|
|
1983
|
+
except HTTPException:
|
|
1984
|
+
# Re-raise HTTPException as-is to preserve status code
|
|
1985
|
+
raise
|
|
1986
|
+
except Exception as e:
|
|
1987
|
+
raise HTTPException(status_code=500, detail=str(e))
|