Skip to content

Commit 9b3b497

Browse files
authored
Merge branch 'main' into dcr-enc-key
2 parents 044d025 + 60c5a00 commit 9b3b497

8 files changed

Lines changed: 126 additions & 38 deletions

File tree

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# Default owners for everything in the repo
2-
* @luis5tb @IlonaShishov @yuvalk
2+
* @luis5tb @IlonaShishov @yuvalk @dmartinol

src/lightspeed_agent/api/a2a/agent_card.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,53 @@ def _build_dcr_extension() -> AgentExtension:
9494
)
9595

9696

97+
def _build_access_mode_extension() -> AgentExtension:
98+
"""Build access mode extension.
99+
100+
Indicates whether the agent operates in read-only mode and lists
101+
the OAuth2 scopes reflecting this access level.
102+
"""
103+
settings = get_settings()
104+
105+
return AgentExtension(
106+
uri="urn:redhat:lightspeed:access-mode",
107+
description="Agent access mode and permitted OAuth2 scopes",
108+
params={
109+
"read_only": settings.mcp_read_only,
110+
"oauth2_scopes": settings.allowed_scopes_list,
111+
},
112+
)
113+
114+
115+
def _build_rate_limit_extension() -> AgentExtension:
116+
"""Build rate limiting metadata extension.
117+
118+
Exposes the agent's rate limits so downstream agents and clients
119+
can plan their request patterns accordingly.
120+
"""
121+
settings = get_settings()
122+
123+
return AgentExtension(
124+
uri="urn:redhat:lightspeed:rate-limiting",
125+
description="Agent rate limiting constraints",
126+
params={
127+
"requests_per_minute": settings.rate_limit_requests_per_minute,
128+
"requests_per_hour": settings.rate_limit_requests_per_hour,
129+
},
130+
)
131+
132+
97133
def _build_capabilities() -> AgentCapabilities:
98134
"""Build agent capabilities with extensions."""
99135
dcr_extension = _build_dcr_extension()
136+
access_mode_extension = _build_access_mode_extension()
137+
rate_limit_extension = _build_rate_limit_extension()
100138

101139
return AgentCapabilities(
102140
streaming=True,
103141
push_notifications=False,
104142
state_transition_history=False,
105-
extensions=[dcr_extension],
143+
extensions=[dcr_extension, access_mode_extension, rate_limit_extension],
106144
)
107145

108146

@@ -125,7 +163,7 @@ def build_agent_card() -> AgentCard:
125163
skills = _build_skills()
126164

127165
agent_card = AgentCard(
128-
name=settings.agent_name,
166+
name=settings.agent_display_name,
129167
description=settings.agent_description,
130168
version="0.1.0",
131169
url=f"{settings.agent_provider_url}/",

src/lightspeed_agent/config/settings.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,19 @@ class Settings(BaseSettings):
8484
default="lightspeed_agent",
8585
description="Agent name (must be a valid Python identifier)",
8686
)
87-
agent_description: str = Field(
87+
agent_display_name: str = Field(
8888
default="Red Hat Lightspeed Agent for Google Cloud",
89+
description="Human-readable agent name for the AgentCard",
90+
)
91+
agent_description: str = Field(
92+
default=(
93+
"Red Hat Lightspeed Agent for Google Cloud is an A2A-ready Agent "
94+
"that leverages Red Hat Lightspeed Model Context Protocol (MCP) to "
95+
"connect to Red Hat Lightspeed services, providing information about "
96+
"your Red Hat account, subscription, system configuration, and "
97+
"related details. This feature uses AI technology. Always review "
98+
"AI-generated content prior to use."
99+
),
89100
description="Agent description",
90101
)
91102
agent_host: str = Field(

src/lightspeed_agent/core/agent.py

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,37 +31,47 @@
3131
- Provide CVE information and remediation guidance
3232
- Prioritize vulnerabilities based on risk
3333
34-
## Remediations
35-
- Create and manage remediation playbooks
36-
- Guide users through issue resolution
37-
- Track remediation progress
38-
3934
## Planning
4035
- Help plan RHEL system upgrades and migrations
4136
- Provide roadmap recommendations
4237
- Assess upgrade readiness
4338
44-
## Image Builder
45-
- Assist with creating custom RHEL images
46-
- Configure image compositions
47-
- Manage image build processes
48-
4939
## Subscription Management
5040
- View activation keys for system registration
5141
- Access subscription information
5242
43+
## Access Management
44+
- View access and permissions information for Red Hat Insights applications
45+
- Understand what actions are available based on current user roles
46+
5347
## Content Sources
5448
- List available content repositories
5549
- Query repository information
5650
51+
## First Response Notice
52+
When you first interact with a user in a new conversation, begin your response with \
53+
the following notice (verbatim), followed by the accuracy disclaimer:
54+
55+
"You are interacting with the Red Hat Lightspeed Agent, which can answer questions \
56+
about your Red Hat account, subscription, system configuration, and related details. \
57+
This feature uses AI technology. Interactions may be used to improve Red Hat's \
58+
products or services.
59+
60+
Always review AI-generated content prior to use."
61+
62+
After the first response in a conversation, do not repeat this notice.
63+
5764
When responding to users:
5865
1. Always be helpful and provide clear, actionable information
5966
2. If you need more context, ask clarifying questions
60-
3. When providing remediation steps, be specific and detailed
61-
4. Respect the read-only mode when enabled - inform users if write operations are restricted
62-
5. Provide security-conscious recommendations
63-
6. When displaying lists of systems or vulnerabilities, format them clearly
64-
7. For CVEs, always include severity information when available
67+
3. Provide security-conscious recommendations
68+
4. When displaying lists of systems or vulnerabilities, format them clearly
69+
5. For CVEs, always include severity information when available
70+
6. When users ask what tools or capabilities you have, describe them based on the \
71+
capability areas listed above (Advisor, Inventory, Vulnerability, \
72+
Planning, Subscription Management, Access Management, Content Sources). Do NOT attempt \
73+
to call a "list_tools" function — it does not exist. Instead, provide a helpful \
74+
summary of your capabilities and example queries for each area
6575
"""
6676

6777

src/lightspeed_agent/tools/skills.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,5 +204,5 @@ def get_skills_for_agent_card(read_only: bool = True) -> list[dict[str, Any]]:
204204
Returns:
205205
List of skill dictionaries.
206206
"""
207-
skills = READ_ONLY_SKILLS if read_only else ALL_SKILLS
208-
return [skill.to_dict() for skill in skills]
207+
# Hardcoded to read-only skills for now
208+
return [skill.to_dict() for skill in READ_ONLY_SKILLS]

tests/test_a2a.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,47 @@ def test_agent_card_has_dcr_extension(self):
5454
"""Test AgentCard has DCR extension in capabilities."""
5555
card = build_agent_card()
5656

57-
# Extensions are now a list of AgentExtension objects
5857
assert card.capabilities.extensions is not None
59-
assert len(card.capabilities.extensions) > 0
60-
dcr_ext = card.capabilities.extensions[0]
61-
assert "dcr" in dcr_ext.uri
58+
dcr_exts = [ext for ext in card.capabilities.extensions if "dcr" in ext.uri]
59+
assert len(dcr_exts) == 1
60+
dcr_ext = dcr_exts[0]
6261
assert dcr_ext.params is not None
6362
assert "target_url" in dcr_ext.params
6463

64+
def test_agent_card_has_access_mode_extension(self):
65+
"""Test AgentCard has access mode extension with read-only metadata."""
66+
card = build_agent_card()
67+
68+
assert card.capabilities.extensions is not None
69+
exts = [ext for ext in card.capabilities.extensions if "access-mode" in ext.uri]
70+
assert len(exts) == 1
71+
ext = exts[0]
72+
assert ext.uri == "urn:redhat:lightspeed:access-mode"
73+
assert ext.params is not None
74+
assert ext.params["read_only"] is True
75+
scopes = ext.params["oauth2_scopes"]
76+
assert "api.console" in scopes
77+
assert "api.ocm" in scopes
78+
79+
def test_agent_card_has_rate_limit_extension(self):
80+
"""Test AgentCard has rate limiting extension."""
81+
card = build_agent_card()
82+
83+
assert card.capabilities.extensions is not None
84+
exts = [ext for ext in card.capabilities.extensions if "rate-limiting" in ext.uri]
85+
assert len(exts) == 1
86+
ext = exts[0]
87+
assert ext.uri == "urn:redhat:lightspeed:rate-limiting"
88+
assert ext.params is not None
89+
assert ext.params["requests_per_minute"] == 60
90+
assert ext.params["requests_per_hour"] == 1000
91+
92+
def test_agent_card_description_has_disclaimer(self):
93+
"""Test AgentCard description includes accuracy disclaimer."""
94+
card = build_agent_card()
95+
96+
assert "Always review AI-generated content prior to use" in card.description
97+
6598
def test_agent_card_url_points_to_root(self):
6699
"""Test AgentCard URL points to root endpoint."""
67100
card = build_agent_card()

tests/test_dcr.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -735,11 +735,10 @@ def test_agent_card_has_dcr_extension(self):
735735

736736
card = build_agent_card()
737737

738-
# Extensions are now a list of AgentExtension objects
739738
assert card.capabilities.extensions is not None
740-
assert len(card.capabilities.extensions) > 0
741-
dcr_ext = card.capabilities.extensions[0]
742-
assert "dcr" in dcr_ext.uri
739+
dcr_exts = [ext for ext in card.capabilities.extensions if "dcr" in ext.uri]
740+
assert len(dcr_exts) == 1
741+
dcr_ext = dcr_exts[0]
743742
assert dcr_ext.params is not None
744743
assert "target_url" in dcr_ext.params
745744
assert "/dcr" in dcr_ext.params["target_url"]

tests/test_tools.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,22 +116,19 @@ def test_read_only_skills_subset(self):
116116

117117
assert read_only_ids.issubset(all_ids)
118118

119-
def test_get_skills_for_agent_card_read_only(self):
120-
"""Test getting read-only skills for agent card."""
121-
skills = get_skills_for_agent_card(read_only=True)
119+
def test_get_skills_for_agent_card_returns_read_only(self):
120+
"""Test getting skills for agent card returns only read-only skills."""
121+
skills = get_skills_for_agent_card()
122122

123123
assert len(skills) == len(READ_ONLY_SKILLS)
124+
skill_ids = {s["id"] for s in skills}
125+
read_only_ids = {s.id for s in READ_ONLY_SKILLS}
126+
assert skill_ids == read_only_ids
124127
for skill in skills:
125128
assert "id" in skill
126129
assert "name" in skill
127130
assert "description" in skill
128131

129-
def test_get_skills_for_agent_card_all(self):
130-
"""Test getting all skills for agent card."""
131-
skills = get_skills_for_agent_card(read_only=False)
132-
133-
assert len(skills) == len(ALL_SKILLS)
134-
135132

136133
class TestToolLists:
137134
"""Tests for tool category lists."""

0 commit comments

Comments
 (0)