Skip to content

Commit dacaa32

Browse files
committed
fix: Final address of all remaining review comments by swapnil@, 1/ changed agent_id --> client_id everywhere, and 2/ changed final uri: https://github.com/a2aproject/a2a-samples/tree/main/samples/python/extensions/secure-passport
1 parent cde46af commit dacaa32

File tree

5 files changed

+75
-61
lines changed

5 files changed

+75
-61
lines changed

‎extensions/secure-passport/v1/samples/python/README.md‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ The core of this extension is the **`CallerContext`** data model, which is attac
99
### Extension URI
1010

1111
The unique identifier for this extension is:
12-
`https://a2aprotocol.ai/ext/secure-passport/v1`
12+
`https://github.com/a2aproject/a2a-samples/tree/main/samples/python/extensions/secure-passport`
1313

1414
---
1515

@@ -29,22 +29,22 @@ from secure_passport_ext import (
2929
get_secure_passport
3030
)
3131

32-
def demonstrate_use_case(title: str, agent_id: str, state: dict, signature: str | None = None, session_id: str | None = None):
32+
def demonstrate_use_case(title: str, client_id: str, state: dict, signature: str | None = None, session_id: str | None = None):
3333
print(f"\n--- Demonstrating: {title} ---")
3434

3535
passport = CallerContext(
36-
agent_id=agent_id,
36+
client_id=client_id,
3737
session_id=session_id,
3838
signature=signature,
3939
state=state
4040
)
4141

42-
message = MockA2AMessage()
42+
message = A2AMessage()
4343
add_secure_passport(message, passport)
4444
retrieved = get_secure_passport(message)
4545

4646
if retrieved:
47-
print(f" Source: {retrieved.agent_id}")
47+
print(f" Source: {retrieved.client_id}")
4848
print(f" Verified: {retrieved.is_verified}")
4949
print(f" Context: {retrieved.state}")
5050
else:
@@ -54,7 +54,7 @@ def demonstrate_use_case(title: str, agent_id: str, state: dict, signature: str
5454

5555
demonstrate_use_case(
5656
title="1. Currency Conversion (GBP)",
57-
agent_id="a2a://travel-orchestrator.com",
57+
client_id="a2a://travel-orchestrator.com",
5858
state={"user_preferred_currency": "GBP", "user_id": "U001"},
5959
signature="sig-currency-1"
6060
)
@@ -63,7 +63,7 @@ demonstrate_use_case(
6363

6464
demonstrate_use_case(
6565
title="2. Personalized Travel (Platinum Tier)",
66-
agent_id="a2a://travel-portal.com",
66+
client_id="a2a://travel-portal.com",
6767
session_id="travel-session-999",
6868
state={
6969
"destination": "Bali, Indonesia",
@@ -76,7 +76,7 @@ demonstrate_use_case(
7676

7777
demonstrate_use_case(
7878
title="3. Retail Assistance (Unverified)",
79-
agent_id="a2a://ecommerce-front.com",
79+
client_id="a2a://ecommerce-front.com",
8080
state={"product_sku": "Nikon-Z-50mm-f1.8", "user_intent": "seeking_reviews"},
8181
signature=None
8282
)
@@ -85,7 +85,7 @@ demonstrate_use_case(
8585

8686
demonstrate_use_case(
8787
title="4. Secured DB Access (Finance)",
88-
agent_id="a2a://marketing-agent.com",
88+
client_id="a2a://marketing-agent.com",
8989
state={
9090
"query_type": "quarterly_revenue",
9191
"access_scope": ["read:finance_db", "user:Gulli"]

‎extensions/secure-passport/v1/samples/python/run.py‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def mock_agent_core_handler(message: A2AMessage, passport: CallerContext | None)
3030
print(" [Agent Core] Executing task with no external context.")
3131

3232

33-
def create_and_run_passport_test(agent_id: str, session_id: str | None, state: dict, signature: str | None, use_case_title: str):
33+
def create_and_run_passport_test(client_id: str, session_id: str | None, state: dict, signature: str | None, use_case_title: str):
3434
"""
3535
Demonstrates a full communication cycle using the conceptual middleware.
3636
"""
@@ -39,14 +39,14 @@ def create_and_run_passport_test(agent_id: str, session_id: str | None, state: d
3939

4040
# 1. Orchestrator (Client) creates the Passport
4141
client_passport = CallerContext(
42-
agent_id=agent_id,
42+
client_id=client_id,
4343
session_id=session_id,
4444
signature=signature,
4545
state=state
4646
)
4747

4848
# Mock A2A Message Container
49-
client_message = A2AMessage() # CORRECTED: Instantiating A2AMessage
49+
client_message = A2AMessage()
5050

5151
# --- CLIENT-SIDE PIPELINE ---
5252
print(" [PIPELINE] Client Side: Middleware -> Transport")
@@ -74,7 +74,7 @@ def run_all_samples():
7474

7575
# --- Use Case 1: Efficient Currency Conversion (High Trust Example) ---
7676
create_and_run_passport_test(
77-
agent_id="a2a://travel-orchestrator.com",
77+
client_id="a2a://travel-orchestrator.com",
7878
session_id=None,
7979
state={"user_preferred_currency": "GBP", "loyalty_tier": "Silver"},
8080
signature="sig-currency-1",
@@ -83,7 +83,7 @@ def run_all_samples():
8383

8484
# --- Use Case 2: Personalized Travel Booking (High Context Example) ---
8585
create_and_run_passport_test(
86-
agent_id="a2a://travel-portal.com",
86+
client_id="a2a://travel-portal.com",
8787
session_id="travel-booking-session-999",
8888
state={
8989
"destination": "Bali, Indonesia",
@@ -95,7 +95,7 @@ def run_all_samples():
9595

9696
# --- Use Case 3: Proactive Retail Assistance (Unsigned/Low Trust Example) ---
9797
create_and_run_passport_test(
98-
agent_id="a2a://ecommerce-front.com",
98+
client_id="a2a://ecommerce-front.com",
9999
session_id="cart-session-404",
100100
state={
101101
"product_sku": "Nikon-Z-50mm-f1.8",
@@ -107,7 +107,7 @@ def run_all_samples():
107107

108108
# --- Use Case 4: Marketing Agent seek insights (Secured Scope Example) ---
109109
create_and_run_passport_test(
110-
agent_id="a2a://marketing-agent.com",
110+
client_id="a2a://marketing-agent.com",
111111
session_id=None,
112112
state={
113113
"query_type": "quarterly_revenue",
Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,23 @@
11
from typing import Optional, Dict, Any, List, Callable
22
from pydantic import BaseModel, Field, ValidationError, ConfigDict
33
from copy import deepcopy
4-
import logging
54

6-
# --- Import official A2A types ---
7-
# Assuming the official A2A message type is available here:
8-
try:
9-
from a2a.types import A2AMessage
10-
except ImportError:
11-
# Fallback/Placeholder if official package is not fully installed locally
12-
class A2AMessage(BaseModel):
13-
metadata: Dict[str, Any] = Field(default_factory=dict)
14-
155
# --- Extension Definition ---
166

17-
SECURE_PASSPORT_URI = "https://a2a-protocol.org/ext/secure-passport/v1"
7+
SECURE_PASSPORT_URI = "https://github.com/a2aproject/a2a-samples/tree/main/samples/python/extensions/secure-passport"
188

199
class CallerContext(BaseModel):
2010
"""
2111
The Secure Passport payload containing contextual state shared by the calling agent.
2212
"""
23-
# PEP 8 Compliant (snake_case) with JSON mapping (camelCase alias)
24-
agent_id: str = Field(..., alias='agentId', description="The verifiable unique identifier of the calling agent.")
13+
# *** CORE CHANGE: agent_id renamed to client_id ***
14+
client_id: str = Field(..., alias='clientId', description="The verifiable unique identifier of the calling client.")
2515
signature: Optional[str] = Field(None, alias='signature', description="A cryptographic signature of the 'state' payload.")
2616
session_id: Optional[str] = Field(None, alias='sessionId', description="A session or conversation identifier for continuity.")
2717
state: Dict[str, Any] = Field(..., description="A free-form JSON object containing the contextual data.")
2818

2919
# Use ConfigDict for Pydantic V2 compatibility and configuration
3020
model_config = ConfigDict(
31-
# Allows instantiation using either the JSON alias or the Python field name
3221
populate_by_name=True,
3322
extra='forbid'
3423
)
@@ -41,11 +30,18 @@ def is_verified(self) -> bool:
4130
return self.signature is not None
4231

4332
# --- Helper Functions (Core Protocol Interaction) ---
33+
34+
class BaseA2AMessage(BaseModel):
35+
metadata: Dict[str, Any] = Field(default_factory=dict)
36+
37+
try:
38+
from a2a.types import A2AMessage
39+
except ImportError:
40+
A2AMessage = BaseA2AMessage
4441

4542
def add_secure_passport(message: A2AMessage, context: CallerContext) -> None:
4643
"""Adds the Secure Passport (CallerContext) to the message's metadata."""
4744

48-
# by_alias=True ensures the output JSON uses the correct camelCase names
4945
message.metadata[SECURE_PASSPORT_URI] = context.model_dump(by_alias=True, exclude_none=True)
5046

5147
def get_secure_passport(message: A2AMessage) -> Optional[CallerContext]:
@@ -55,10 +51,9 @@ def get_secure_passport(message: A2AMessage) -> Optional[CallerContext]:
5551
return None
5652

5753
try:
58-
# validate uses aliases implicitly for input conversion
5954
return CallerContext.model_validate(deepcopy(passport_data))
6055
except ValidationError as e:
61-
# logging is already used here
56+
import logging
6257
logging.warning(f"ERROR: Received malformed Secure Passport data. Ignoring payload: {e}")
6358
return None
6459

@@ -81,36 +76,33 @@ def get_agent_card_declaration(supported_state_keys: Optional[List[str]] = None)
8176
"uri": SECURE_PASSPORT_URI,
8277
"params": {}
8378
}
84-
8579
if supported_state_keys:
8680
declaration["params"]["supportedStateKeys"] = supported_state_keys
87-
81+
8882
return declaration
8983

9084
@staticmethod
9185
def client_middleware(next_handler: Callable[[A2AMessage], Any], message: A2AMessage, context: CallerContext):
9286
"""
9387
[Conceptual Middleware Layer: Client/Calling Agent]
94-
95-
Type Hint: next_handler takes one A2AMessage and returns Any.
9688
"""
97-
logging.info(f"[Middleware: Client] Attaching Secure Passport for {context.agent_id}")
89+
# ACCESS UPDATED: Use context.client_id
90+
print(f"[Middleware: Client] Attaching Secure Passport for {context.client_id}")
9891
add_secure_passport(message, context)
99-
return next_handler(message) # Passes the augmented message to the transport layer
92+
return next_handler(message)
10093

10194
@staticmethod
10295
def server_middleware(next_handler: Callable[[A2AMessage, Optional[CallerContext]], Any], message: A2AMessage):
10396
"""
10497
[Conceptual Middleware Layer: Server/Receiving Agent]
105-
106-
Type Hint: next_handler takes A2AMessage and Optional[CallerContext] and returns Any.
10798
"""
10899
passport = get_secure_passport(message)
109100

110101
if passport:
111-
logging.info(f"[Middleware: Server] Extracted Secure Passport. Verified: {passport.is_verified}")
102+
print(f"[Middleware: Server] Extracted Secure Passport. Verified: {passport.is_verified}")
112103
else:
113-
logging.debug("[Middleware: Server] No Secure Passport found or validation failed.")
104+
print("[Middleware: Server] No Secure Passport found or validation failed.")
114105

115-
# next_handler is the agent's core task logic. We pass the message and the extracted passport.
116106
return next_handler(message, passport)
107+
108+

‎extensions/secure-passport/v1/samples/python/tests/test_secure_passport.py‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
def valid_passport_data():
1616
"""
1717
Returns a dictionary for creating a valid CallerContext.
18-
Uses snake_case keys to align with the CallerContext model attributes.
18+
Keys are snake_case to align with the final CallerContext model attributes.
1919
"""
2020
return {
21-
"agent_id": "a2a://orchestrator.com",
21+
"client_id": "a2a://orchestrator.com", # CORRECTED: Changed agent_id to client_id
2222
"session_id": "session-123",
2323
"state": {"currency": "USD", "tier": "silver"},
2424
"signature": "mock-signature-xyz"
@@ -37,7 +37,7 @@ def test_add_and_get_passport_success(valid_passport_data):
3737
retrieved = get_secure_passport(message)
3838

3939
assert retrieved is not None
40-
assert retrieved.agent_id == "a2a://orchestrator.com"
40+
assert retrieved.client_id == "a2a://orchestrator.com" # CORRECTED: Access via client_id
4141
assert retrieved.state == {"currency": "USD", "tier": "silver"}
4242

4343
def test_get_passport_when_missing():
@@ -47,9 +47,9 @@ def test_get_passport_when_missing():
4747
assert retrieved is None
4848

4949
def test_passport_validation_failure_missing_required_field(valid_passport_data):
50-
"""Tests validation fails when a required field (agent_id) is missing."""
50+
"""Tests validation fails when a required field (client_id) is missing."""
5151
invalid_data = valid_passport_data.copy()
52-
del invalid_data['agent_id']
52+
del invalid_data['client_id'] # CORRECTED: Deleting client_id key
5353

5454
message = A2AMessage()
5555
message.metadata[SECURE_PASSPORT_URI] = invalid_data
@@ -107,7 +107,7 @@ def test_use_case_1_currency_conversion():
107107
}
108108

109109
passport = CallerContext(
110-
agent_id="a2a://travel-orchestrator.com",
110+
client_id="a2a://travel-orchestrator.com", # CORRECTED: Using client_id keyword
111111
state=state_data,
112112
signature="sig-currency-1"
113113
)
@@ -128,8 +128,8 @@ def test_use_case_2_personalized_travel_booking():
128128
}
129129

130130
passport = CallerContext(
131-
agent_id="a2a://travel-portal.com",
132-
session_id="travel-booking-session-999",
131+
client_id="a2a://travel-portal.com", # CORRECTED: Using client_id keyword
132+
session_id="travel-booking-session-999",
133133
state=state_data,
134134
signature="sig-travel-2"
135135
)
@@ -151,7 +151,7 @@ def test_use_case_3_proactive_retail_assistance():
151151
}
152152

153153
passport = CallerContext(
154-
agent_id="a2a://ecommerce-front.com",
154+
client_id="a2a://ecommerce-front.com", # CORRECTED: Using client_id keyword
155155
state=state_data,
156156
)
157157

@@ -172,7 +172,7 @@ def test_use_case_4_secured_db_insights():
172172
}
173173

174174
passport = CallerContext(
175-
agent_id="a2a://marketing-agent.com",
175+
client_id="a2a://marketing-agent.com", # CORRECTED: Using client_id keyword
176176
state=state_data,
177177
signature="sig-finance-4"
178178
)

0 commit comments

Comments
 (0)