solace-agent-mesh 1.5.1__py3-none-any.whl → 1.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of solace-agent-mesh might be problematic. Click here for more details.
- solace_agent_mesh/agent/adk/callbacks.py +0 -5
- solace_agent_mesh/agent/adk/models/lite_llm.py +123 -8
- solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +245 -0
- solace_agent_mesh/agent/protocol/event_handlers.py +40 -1
- solace_agent_mesh/agent/proxies/__init__.py +0 -0
- solace_agent_mesh/agent/proxies/a2a/__init__.py +3 -0
- solace_agent_mesh/agent/proxies/a2a/app.py +55 -0
- solace_agent_mesh/agent/proxies/a2a/component.py +1115 -0
- solace_agent_mesh/agent/proxies/a2a/config.py +140 -0
- solace_agent_mesh/agent/proxies/a2a/oauth_token_cache.py +104 -0
- solace_agent_mesh/agent/proxies/base/__init__.py +3 -0
- solace_agent_mesh/agent/proxies/base/app.py +99 -0
- solace_agent_mesh/agent/proxies/base/component.py +619 -0
- solace_agent_mesh/agent/proxies/base/config.py +85 -0
- solace_agent_mesh/agent/proxies/base/proxy_task_context.py +17 -0
- solace_agent_mesh/agent/sac/app.py +9 -3
- solace_agent_mesh/agent/sac/component.py +160 -8
- solace_agent_mesh/agent/tools/audio_tools.py +125 -8
- solace_agent_mesh/agent/tools/web_tools.py +10 -5
- solace_agent_mesh/agent/utils/artifact_helpers.py +141 -3
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.eda4bcb2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.f4b15f3b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/71da7b71.38583438.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/77cf947d.48cb18a2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/924ffdeb.8095e148.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9e9d0a82.570c057b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{ad71b5ed.60668e9e.js → ad71b5ed.af3ecfd1.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/ceb2a7a6.5d92d7d0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{da0b5bad.9d369087.js → da0b5bad.d08a9466.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/db924877.e98d12a1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/de915948.27d6b065.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e6f9706b.e74a984d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.42f59cdd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ff4d71f2.15b02f97.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js → main.20feee82.js} +2 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.0d198646.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +15 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +262 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +31 -3
- solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +135 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +6 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +6 -5
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +100 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +3 -3
- solace_agent_mesh/assets/docs/lunr-index-1761165361160.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1761165361160.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/assets/docs/sitemap.xml +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +2 -69
- solace_agent_mesh/cli/commands/eval_cmd.py +11 -49
- solace_agent_mesh/cli/commands/init_cmd/__init__.py +0 -5
- solace_agent_mesh/cli/commands/init_cmd/env_step.py +10 -12
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +9 -61
- solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +9 -49
- solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +1 -2
- solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-DwrxZE0E.js → authCallback-BTf6dqwp.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{client-DarGQzyw.js → client-CaY59VuC.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-BGTaW0uv.js +342 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-DHJKSW1S.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/{vendor-BKIeiHj_.js → vendor-BEmvJSYz.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
- solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
- solace_agent_mesh/common/a2a/__init__.py +24 -0
- solace_agent_mesh/common/a2a/artifact.py +39 -0
- solace_agent_mesh/common/a2a/events.py +29 -0
- solace_agent_mesh/common/a2a/message.py +68 -0
- solace_agent_mesh/common/a2a/protocol.py +73 -1
- solace_agent_mesh/common/agent_registry.py +83 -3
- solace_agent_mesh/common/constants.py +3 -1
- solace_agent_mesh/common/utils/pydantic_utils.py +12 -0
- solace_agent_mesh/config_portal/backend/common.py +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-ByU1X1HD.js +98 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-44d62be6.js → manifest-61038fc6.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
- solace_agent_mesh/evaluation/evaluator.py +128 -104
- solace_agent_mesh/evaluation/message_organizer.py +116 -110
- solace_agent_mesh/evaluation/report_data_processor.py +84 -86
- solace_agent_mesh/evaluation/report_generator.py +73 -79
- solace_agent_mesh/evaluation/run.py +421 -235
- solace_agent_mesh/evaluation/shared/__init__.py +92 -0
- solace_agent_mesh/evaluation/shared/constants.py +47 -0
- solace_agent_mesh/evaluation/shared/exceptions.py +50 -0
- solace_agent_mesh/evaluation/shared/helpers.py +35 -0
- solace_agent_mesh/evaluation/shared/test_case_loader.py +167 -0
- solace_agent_mesh/evaluation/shared/test_suite_loader.py +280 -0
- solace_agent_mesh/evaluation/subscriber.py +111 -232
- solace_agent_mesh/evaluation/summary_builder.py +227 -117
- solace_agent_mesh/gateway/base/app.py +1 -1
- solace_agent_mesh/gateway/base/component.py +8 -1
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251015_add_session_performance_indexes.py +70 -0
- solace_agent_mesh/gateway/http_sse/component.py +98 -2
- solace_agent_mesh/gateway/http_sse/dependencies.py +4 -4
- solace_agent_mesh/gateway/http_sse/main.py +2 -1
- solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +12 -13
- solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +15 -18
- solace_agent_mesh/gateway/http_sse/repository/interfaces.py +25 -18
- solace_agent_mesh/gateway/http_sse/repository/session_repository.py +30 -26
- solace_agent_mesh/gateway/http_sse/repository/task_repository.py +35 -44
- solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +4 -3
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +95 -203
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +4 -3
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +2 -2
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +33 -41
- solace_agent_mesh/gateway/http_sse/routers/visualization.py +17 -11
- solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +4 -4
- solace_agent_mesh/gateway/http_sse/services/feedback_service.py +51 -43
- solace_agent_mesh/gateway/http_sse/services/session_service.py +20 -20
- solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +8 -8
- solace_agent_mesh/gateway/http_sse/shared/base_repository.py +45 -71
- solace_agent_mesh/gateway/http_sse/shared/types.py +0 -18
- solace_agent_mesh/templates/gateway_config_template.yaml +0 -5
- solace_agent_mesh/templates/logging_config_template.ini +10 -6
- solace_agent_mesh/templates/plugin_gateway_config_template.yaml +0 -3
- solace_agent_mesh/templates/shared_config.yaml +40 -0
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/METADATA +47 -21
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/RECORD +162 -141
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.e49689dd.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.39d5851d.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/71da7b71.804d6567.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/77cf947d.64c9bd6c.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/9e9d0a82.dd810042.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/db924877.cbc66f02.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/de915948.139b4b9c.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/e6f9706b.582a78ca.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/f284c35a.5766a13d.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/ff4d71f2.9c0297a6.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/runtime~main.18dc45dd.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1760121512891.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1760121512891.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-2nd1gbaH.js +0 -339
- solace_agent_mesh/client/webui/frontend/static/assets/main-DoKXctCM.css +0 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-BNuqpWDc.js +0 -98
- solace_agent_mesh/evaluation/config_loader.py +0 -657
- solace_agent_mesh/evaluation/test_case_loader.py +0 -714
- /solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js.LICENSE.txt → main.20feee82.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pydantic configuration models for A2A proxy applications.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List, Literal, Optional
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
from pydantic import Field, model_validator
|
|
9
|
+
|
|
10
|
+
from ..base.config import BaseProxyAppConfig, ProxiedAgentConfig
|
|
11
|
+
from ....common.utils.pydantic_utils import SamConfigBase
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AuthenticationConfig(SamConfigBase):
|
|
15
|
+
"""Authentication configuration for downstream A2A agents."""
|
|
16
|
+
|
|
17
|
+
type: Optional[
|
|
18
|
+
Literal["static_bearer", "static_apikey", "oauth2_client_credentials"]
|
|
19
|
+
] = Field(
|
|
20
|
+
default=None,
|
|
21
|
+
description="Authentication type. If not specified, inferred from 'scheme' for backward compatibility.",
|
|
22
|
+
)
|
|
23
|
+
scheme: Optional[str] = Field(
|
|
24
|
+
default=None,
|
|
25
|
+
description="(Legacy) The authentication scheme (e.g., 'bearer', 'apikey'). Use 'type' field instead.",
|
|
26
|
+
)
|
|
27
|
+
token: Optional[str] = Field(
|
|
28
|
+
default=None,
|
|
29
|
+
description="The authentication token or API key (for static_bearer and static_apikey types).",
|
|
30
|
+
)
|
|
31
|
+
token_url: Optional[str] = Field(
|
|
32
|
+
default=None,
|
|
33
|
+
description="OAuth 2.0 token endpoint URL (required for oauth2_client_credentials type).",
|
|
34
|
+
)
|
|
35
|
+
client_id: Optional[str] = Field(
|
|
36
|
+
default=None,
|
|
37
|
+
description="OAuth 2.0 client identifier (required for oauth2_client_credentials type).",
|
|
38
|
+
)
|
|
39
|
+
client_secret: Optional[str] = Field(
|
|
40
|
+
default=None,
|
|
41
|
+
description="OAuth 2.0 client secret (required for oauth2_client_credentials type).",
|
|
42
|
+
)
|
|
43
|
+
scope: Optional[str] = Field(
|
|
44
|
+
default=None,
|
|
45
|
+
description="OAuth 2.0 scope as a space-separated string (optional for oauth2_client_credentials type).",
|
|
46
|
+
)
|
|
47
|
+
token_cache_duration_seconds: int = Field(
|
|
48
|
+
default=3300,
|
|
49
|
+
gt=0,
|
|
50
|
+
description="How long to cache OAuth 2.0 tokens before refresh, in seconds (default: 3300 = 55 minutes).",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
@model_validator(mode="after")
|
|
54
|
+
def validate_auth_config(self) -> "AuthenticationConfig":
|
|
55
|
+
"""Validates authentication configuration based on type."""
|
|
56
|
+
# Determine effective auth type (with backward compatibility)
|
|
57
|
+
auth_type = self.type
|
|
58
|
+
if not auth_type and self.scheme:
|
|
59
|
+
# Legacy config: infer type from scheme
|
|
60
|
+
if self.scheme == "bearer":
|
|
61
|
+
auth_type = "static_bearer"
|
|
62
|
+
elif self.scheme == "apikey":
|
|
63
|
+
auth_type = "static_apikey"
|
|
64
|
+
else:
|
|
65
|
+
raise ValueError(
|
|
66
|
+
f"Unknown legacy authentication scheme '{self.scheme}'. "
|
|
67
|
+
f"Supported schemes: 'bearer', 'apikey'."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if not auth_type:
|
|
71
|
+
# No authentication configured
|
|
72
|
+
return self
|
|
73
|
+
|
|
74
|
+
# Validate based on auth type
|
|
75
|
+
if auth_type in ["static_bearer", "static_apikey"]:
|
|
76
|
+
if not self.token:
|
|
77
|
+
raise ValueError(
|
|
78
|
+
f"Authentication type '{auth_type}' requires 'token' field."
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
elif auth_type == "oauth2_client_credentials":
|
|
82
|
+
# Validate token_url
|
|
83
|
+
if not self.token_url:
|
|
84
|
+
raise ValueError(
|
|
85
|
+
"OAuth 2.0 client credentials flow requires 'token_url'."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Validate token_url is HTTPS
|
|
89
|
+
try:
|
|
90
|
+
parsed_url = urlparse(self.token_url)
|
|
91
|
+
if parsed_url.scheme != "https":
|
|
92
|
+
raise ValueError(
|
|
93
|
+
f"OAuth 2.0 'token_url' must use HTTPS for security. "
|
|
94
|
+
f"Got scheme: '{parsed_url.scheme}'"
|
|
95
|
+
)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
raise ValueError(f"Failed to parse 'token_url': {e}")
|
|
98
|
+
|
|
99
|
+
# Validate client_id
|
|
100
|
+
if not self.client_id:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
"OAuth 2.0 client credentials flow requires 'client_id'."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Validate client_secret
|
|
106
|
+
if not self.client_secret:
|
|
107
|
+
raise ValueError(
|
|
108
|
+
"OAuth 2.0 client credentials flow requires 'client_secret'."
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
else:
|
|
112
|
+
raise ValueError(
|
|
113
|
+
f"Unsupported authentication type '{auth_type}'. "
|
|
114
|
+
f"Supported types: static_bearer, static_apikey, oauth2_client_credentials."
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return self
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class A2AProxiedAgentConfig(ProxiedAgentConfig):
|
|
121
|
+
"""Configuration for an A2A-over-HTTPS proxied agent."""
|
|
122
|
+
|
|
123
|
+
url: str = Field(
|
|
124
|
+
...,
|
|
125
|
+
description="The base URL of the downstream A2A agent's HTTP endpoint.",
|
|
126
|
+
)
|
|
127
|
+
authentication: Optional[AuthenticationConfig] = Field(
|
|
128
|
+
default=None,
|
|
129
|
+
description="Authentication details for the downstream agent.",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class A2AProxyAppConfig(BaseProxyAppConfig):
|
|
134
|
+
"""Complete configuration for an A2A proxy application."""
|
|
135
|
+
|
|
136
|
+
proxied_agents: List[A2AProxiedAgentConfig] = Field(
|
|
137
|
+
...,
|
|
138
|
+
min_length=1,
|
|
139
|
+
description="A list of downstream A2A agents to be proxied.",
|
|
140
|
+
)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OAuth 2.0 token caching for A2A proxy authentication.
|
|
3
|
+
|
|
4
|
+
This module provides an in-memory cache for OAuth 2.0 access tokens
|
|
5
|
+
with automatic expiration. Tokens are cached per agent to minimize
|
|
6
|
+
token acquisition overhead and reduce load on authorization servers.
|
|
7
|
+
|
|
8
|
+
The cache is thread-safe using asyncio.Lock and implements lazy
|
|
9
|
+
expiration (tokens are checked for expiration on retrieval).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import time
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Dict, Optional
|
|
16
|
+
|
|
17
|
+
from solace_ai_connector.common.log import log
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class CachedToken:
|
|
22
|
+
"""Represents a cached OAuth token with expiration."""
|
|
23
|
+
|
|
24
|
+
access_token: str
|
|
25
|
+
expires_at: float # Unix timestamp when token expires (time.time() + cache_duration)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class OAuth2TokenCache:
|
|
29
|
+
"""
|
|
30
|
+
Thread-safe in-memory cache for OAuth 2.0 access tokens.
|
|
31
|
+
|
|
32
|
+
Tokens are cached per agent and automatically expire based on
|
|
33
|
+
the configured cache duration.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
"""Initialize the token cache with an empty dictionary and lock."""
|
|
38
|
+
self._cache: Dict[str, CachedToken] = {}
|
|
39
|
+
self._lock = asyncio.Lock()
|
|
40
|
+
|
|
41
|
+
async def get(self, agent_name: str) -> Optional[str]:
|
|
42
|
+
"""
|
|
43
|
+
Retrieves a cached token if it exists and hasn't expired.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
agent_name: The name of the agent to get the token for.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
The access token if cached and valid, None otherwise.
|
|
50
|
+
"""
|
|
51
|
+
async with self._lock:
|
|
52
|
+
cached = self._cache.get(agent_name)
|
|
53
|
+
if not cached:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
# Check if token has expired
|
|
57
|
+
if time.time() >= cached.expires_at:
|
|
58
|
+
log.debug(
|
|
59
|
+
"Cached token for '%s' has expired. Removing from cache.",
|
|
60
|
+
agent_name,
|
|
61
|
+
)
|
|
62
|
+
del self._cache[agent_name]
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
log.debug(
|
|
66
|
+
"Using cached OAuth token for '%s' (expires in %.0fs)",
|
|
67
|
+
agent_name,
|
|
68
|
+
cached.expires_at - time.time(),
|
|
69
|
+
)
|
|
70
|
+
return cached.access_token
|
|
71
|
+
|
|
72
|
+
async def set(
|
|
73
|
+
self, agent_name: str, access_token: str, cache_duration_seconds: int
|
|
74
|
+
):
|
|
75
|
+
"""
|
|
76
|
+
Caches a token with an expiration time.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
agent_name: The name of the agent.
|
|
80
|
+
access_token: The OAuth 2.0 access token.
|
|
81
|
+
cache_duration_seconds: How long the token should be cached.
|
|
82
|
+
"""
|
|
83
|
+
async with self._lock:
|
|
84
|
+
expires_at = time.time() + cache_duration_seconds
|
|
85
|
+
self._cache[agent_name] = CachedToken(
|
|
86
|
+
access_token=access_token, expires_at=expires_at
|
|
87
|
+
)
|
|
88
|
+
log.debug(
|
|
89
|
+
"Cached token for '%s' (expires in %ds)",
|
|
90
|
+
agent_name,
|
|
91
|
+
cache_duration_seconds,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
async def invalidate(self, agent_name: str):
|
|
95
|
+
"""
|
|
96
|
+
Removes a token from the cache.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
agent_name: The name of the agent.
|
|
100
|
+
"""
|
|
101
|
+
async with self._lock:
|
|
102
|
+
if agent_name in self._cache:
|
|
103
|
+
del self._cache[agent_name]
|
|
104
|
+
log.info("Invalidated cached token for '%s'", agent_name)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Abstract base class for proxy apps.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from typing import Any, Dict, Type
|
|
9
|
+
|
|
10
|
+
from pydantic import ValidationError
|
|
11
|
+
from solace_ai_connector.common.log import log
|
|
12
|
+
from solace_ai_connector.flow.app import App
|
|
13
|
+
|
|
14
|
+
from ....common.a2a import get_agent_request_topic
|
|
15
|
+
from .component import BaseProxyComponent
|
|
16
|
+
from .config import BaseProxyAppConfig
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
info = {
|
|
20
|
+
"class_name": "BaseProxyApp",
|
|
21
|
+
"description": "Abstract base class for proxy apps. Handles common configuration and subscription generation.",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BaseProxyApp(App, ABC):
|
|
26
|
+
"""
|
|
27
|
+
Abstract base class for proxy apps.
|
|
28
|
+
|
|
29
|
+
Handles common configuration schema, generates Solace topic subscriptions for all
|
|
30
|
+
proxied agents, and programmatically defines the single proxy component instance.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
app_schema = {}
|
|
34
|
+
|
|
35
|
+
def __init__(self, app_info: Dict[str, Any], **kwargs):
|
|
36
|
+
log.debug("Initializing BaseProxyApp...")
|
|
37
|
+
|
|
38
|
+
app_config_dict = app_info.get("app_config", {})
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
# Validate the raw dict, cleaning None values to allow defaults to apply
|
|
42
|
+
app_config = BaseProxyAppConfig.model_validate_and_clean(app_config_dict)
|
|
43
|
+
# Overwrite the raw dict with the validated object for downstream use
|
|
44
|
+
app_info["app_config"] = app_config
|
|
45
|
+
except ValidationError as e:
|
|
46
|
+
log.error("Proxy configuration validation failed:\n%s", e)
|
|
47
|
+
raise ValueError(f"Invalid proxy configuration: {e}") from e
|
|
48
|
+
|
|
49
|
+
namespace = app_config.get("namespace")
|
|
50
|
+
proxied_agents = app_config.get("proxied_agents", [])
|
|
51
|
+
|
|
52
|
+
# Generate subscriptions for each proxied agent
|
|
53
|
+
required_topics = [
|
|
54
|
+
get_agent_request_topic(namespace, agent["name"])
|
|
55
|
+
for agent in proxied_agents
|
|
56
|
+
if "name" in agent
|
|
57
|
+
]
|
|
58
|
+
generated_subs = [{"topic": topic} for topic in required_topics]
|
|
59
|
+
log.info(
|
|
60
|
+
"Automatically generated subscriptions for proxy: %s",
|
|
61
|
+
generated_subs,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Programmatically define the component
|
|
65
|
+
component_class = self._get_component_class()
|
|
66
|
+
component_definition = {
|
|
67
|
+
"name": f"{app_info.get('name', 'proxy')}_component",
|
|
68
|
+
"component_class": component_class,
|
|
69
|
+
"component_config": {}, # Component will get config from app_config
|
|
70
|
+
"subscriptions": generated_subs,
|
|
71
|
+
}
|
|
72
|
+
app_info["components"] = [component_definition]
|
|
73
|
+
log.debug("Replaced 'components' in app_info with programmatic definition.")
|
|
74
|
+
|
|
75
|
+
# Ensure broker is configured for input/output
|
|
76
|
+
broker_config = app_info.setdefault("broker", {})
|
|
77
|
+
broker_config["input_enabled"] = True
|
|
78
|
+
broker_config["output_enabled"] = True
|
|
79
|
+
log.debug("Injected broker.input_enabled=True and broker.output_enabled=True")
|
|
80
|
+
|
|
81
|
+
# Generate a unique queue name
|
|
82
|
+
app_name = app_info.get("name", "proxy-app")
|
|
83
|
+
generated_queue_name = f"{namespace.strip('/')}/q/proxy/{app_name}"
|
|
84
|
+
broker_config["queue_name"] = generated_queue_name
|
|
85
|
+
log.debug("Injected generated broker.queue_name: %s", generated_queue_name)
|
|
86
|
+
|
|
87
|
+
broker_config["temporary_queue"] = True
|
|
88
|
+
log.debug("Set broker_config.temporary_queue = True")
|
|
89
|
+
|
|
90
|
+
super().__init__(app_info, **kwargs)
|
|
91
|
+
log.debug("BaseProxyApp initialization complete.")
|
|
92
|
+
|
|
93
|
+
@abstractmethod
|
|
94
|
+
def _get_component_class(self) -> Type[BaseProxyComponent]:
|
|
95
|
+
"""
|
|
96
|
+
Abstract method to be implemented by concrete proxy apps.
|
|
97
|
+
Must return the specific proxy component class to be instantiated.
|
|
98
|
+
"""
|
|
99
|
+
raise NotImplementedError
|