Skip to content

Commit 6402138

Browse files
authored
feat(DATAGO-131375): Implement EventMeshService for request-response communication via Solace broker (#157)
1 parent 838e88d commit 6402138

21 files changed

Lines changed: 2078 additions & 0 deletions

.github/pr_labeler.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,8 @@ sam-sql-database-tool:
4545
sam-mcp-server-gateway-adapter:
4646
- changed-files:
4747
- any-glob-to-any-file: sam-mcp-server-gateway-adapter/**
48+
49+
sam-event-mesh-identity-provider:
50+
- changed-files:
51+
- any-glob-to-any-file: sam-event-mesh-identity-provider/**
52+

.github/workflows/build-plugin.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ on:
4141
- sam-slack-gateway-adapter
4242
- sam-sql-database-tool
4343
- sam-webhook-gateway
44+
- sam-event-mesh-identity-provider
4445
ref:
4546
description: "Git ref to checkout (branch or SHA)"
4647
required: false

.github/workflows/deprecate-plugins.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ on:
1414
required: false
1515
type: boolean
1616
default: false
17+
sam-event-mesh-identity-provider:
18+
description: "Deprecate sam-event-mesh-identity-provider"
19+
required: false
20+
type: boolean
21+
default: false
1722
sam-event-mesh-tool:
1823
description: "Deprecate sam-event-mesh-tool"
1924
required: false

.github/workflows/sync-plugin-configs.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ on:
3939
- "!sam-sql-database/**"
4040
- "!sam-sql-database-tool/**"
4141
- "!sam-webhook-gateway/**"
42+
- "!sam-event-mesh-identity-provider/**"
4243

4344
permissions:
4445
contents: write

.release-please-manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"sam-bedrock-agent": "0.1.1",
33
"sam-event-mesh-gateway": "1.1.0",
4+
"sam-event-mesh-identity-provider": "0.1.0",
45
"sam-event-mesh-tool": "0.1.1",
56
"sam-mcp-server-gateway-adapter": "0.1.1",
67
"sam-mongodb": "0.1.1",

release-please-config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@
7474
"package-name": "sam_event_mesh_gateway",
7575
"changelog-path": "CHANGELOG.md"
7676
},
77+
"sam-event-mesh-identity-provider": {
78+
"package-name": "solace_agent_mesh_event_mesh_identity_provider",
79+
"changelog-path": "CHANGELOG.md"
80+
},
7781
"sam-event-mesh-tool": {
7882
"package-name": "sam_event_mesh_tool",
7983
"changelog-path": "CHANGELOG.md"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# Distribution / packaging
7+
dist/
8+
build/
9+
*.egg-info/
10+
*.egg
11+
12+
# Virtual environments
13+
.venv/
14+
venv/
15+
16+
# IDE
17+
.idea/
18+
.vscode/
19+
*.swp
20+
*.swo
21+
22+
# Testing
23+
.pytest_cache/
24+
htmlcov/
25+
.coverage
26+
coverage.xml
27+
28+
# Hatch
29+
.hatch/
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
# Event Mesh Identity Provider Plugin
2+
3+
A generic, vendor-agnostic identity and employee service provider for [Solace Agent Mesh](https://solacelabs.github.io/solace-agent-mesh/) that communicates with any backend system via Solace Event Mesh request-response messaging.
4+
5+
## About Solace Agent Mesh
6+
7+
[Solace Agent Mesh](https://solacelabs.github.io/solace-agent-mesh/) is a framework for building AI agent systems that communicate over Solace event brokers. It enables multi-agent collaboration, tool integration, and gateway connectivity for AI-powered applications.
8+
9+
## When to Use This Plugin
10+
11+
Use this plugin when:
12+
13+
- **You have an HR or identity system** (e.g., SAP SuccessFactors, Workday, BambooHR, custom LDAP) that is accessible via a service connected to a Solace event broker.
14+
- **You need identity enrichment** in your gateway — enrich user auth claims with profile data (title, department, manager) from your HR system.
15+
- **You need employee directory access** in your agents — let agents look up employees, search users, fetch org structure, or retrieve profile pictures.
16+
- **You want a single plugin** that serves both identity and employee service roles without writing custom code.
17+
18+
The plugin sends requests to configurable topics on your Solace broker and expects a backend service (your integration layer) to respond. This makes it compatible with **any** backend system — you just need a message-driven integration that responds on the event mesh.
19+
20+
## Key Features
21+
22+
- **Vendor-agnostic**: Works with any backend connected to Solace Event Mesh
23+
- **Configurable field mapping**: Map source fields to canonical schema via YAML (no code changes)
24+
- **Computed fields**: Derive values from multiple source fields (e.g., displayName from first + last name)
25+
- **Field exclusion and renaming**: Fine-grained control over output fields
26+
- **Flexible topic configuration**: Single topic string for all operations, or per-operation dict
27+
- **Built-in caching**: Configurable TTL caching for all operations
28+
- **Dual-purpose**: Use as identity service (gateways) and/or employee service (agents)
29+
30+
## Installation
31+
32+
```bash
33+
sam plugin install sam-event-mesh-identity-provider
34+
```
35+
36+
Or install directly:
37+
38+
```bash
39+
pip install sam-event-mesh-identity-provider
40+
```
41+
42+
## Configuration
43+
44+
### As Identity Service (Gateway)
45+
46+
Add to your gateway configuration to enrich user identity on each request:
47+
48+
```yaml
49+
identity_service:
50+
type: "event-mesh-identity-provider"
51+
52+
broker_url: "${IDENTITY_BROKER_URL}"
53+
broker_vpn: "${IDENTITY_BROKER_VPN}"
54+
broker_username: "${IDENTITY_BROKER_USERNAME}"
55+
broker_password: "${IDENTITY_BROKER_PASSWORD}"
56+
57+
lookup_key: "email"
58+
cache_ttl_seconds: 3600
59+
request_expiry_ms: 120000
60+
response_topic_prefix: "mycompany/identity/response"
61+
62+
# Single topic for all operations:
63+
# request_topic: "mycompany/identity/request/v1/{request_id}"
64+
65+
# Or per-operation topics:
66+
request_topic:
67+
user_profile: "mycompany/identity/user-profile/request/v1/{request_id}"
68+
search_users: "mycompany/identity/search-users/request/v1/{request_id}"
69+
70+
field_mapping_config:
71+
field_mapping:
72+
emp_email: "workEmail"
73+
title: "jobTitle"
74+
dept_name: "department"
75+
computed_fields:
76+
- target: "displayName"
77+
source_fields: ["first_name", "last_name"]
78+
separator: " "
79+
pass_through_unmapped: true
80+
```
81+
82+
### As People/Employee Service (Agent)
83+
84+
Add to your agent configuration for employee directory access:
85+
86+
```yaml
87+
people_service:
88+
type: "event-mesh-identity-provider"
89+
90+
broker_url: "${IDENTITY_BROKER_URL}"
91+
broker_vpn: "${IDENTITY_BROKER_VPN}"
92+
broker_username: "${IDENTITY_BROKER_USERNAME}"
93+
broker_password: "${IDENTITY_BROKER_PASSWORD}"
94+
95+
lookup_key: "email"
96+
cache_ttl_seconds: 3600
97+
response_topic_prefix: "mycompany/identity/response"
98+
99+
request_topic:
100+
user_profile: "mycompany/identity/user-profile/request/v1/{request_id}"
101+
search_users: "mycompany/identity/search-users/request/v1/{request_id}"
102+
employee_data: "mycompany/identity/employee-data/request/v1/{request_id}"
103+
employee_profile: "mycompany/identity/employee-profile/request/v1/{request_id}"
104+
time_off: "mycompany/identity/time-off/request/v1/{request_id}"
105+
profile_picture: "mycompany/identity/profile-picture/request/v1/{request_id}"
106+
107+
field_mapping_config:
108+
field_mapping: {}
109+
pass_through_unmapped: true
110+
```
111+
112+
## Configuration Reference
113+
114+
| Option | Description | Default |
115+
|--------|-------------|---------|
116+
| `type` | Must be `"event-mesh-identity-provider"` | Required |
117+
| `broker_url` | Solace broker URL | Required |
118+
| `broker_vpn` | Solace message VPN name | Required |
119+
| `broker_username` | Broker authentication username | Required |
120+
| `broker_password` | Broker authentication password | Required |
121+
| `dev_mode` | Enable development mode (no TLS verification) | `false` |
122+
| `lookup_key` | Key in auth_claims used to extract the lookup value | `"email"` |
123+
| `payload_key` | Key name used in the request payload sent to the backend | Value of `lookup_key` |
124+
| `cache_ttl_seconds` | Cache TTL in seconds (0 to disable) | `3600` |
125+
| `request_expiry_ms` | Request timeout in milliseconds | `120000` |
126+
| `response_topic_prefix` | Prefix for the response correlation topic | `"sam/identity-provider/response"` |
127+
| `user_properties_reply_topic_key` | Key in message user properties where the reply topic is stored | `"replyTopic"` |
128+
| `response_topic_insertion_expression` | Expression used by the broker to insert the response topic into the request | `"replyTopic"` |
129+
| `request_topic` | Topic template (string) or per-operation map (dict) | Required |
130+
| `field_mapping_config` | Field transformation configuration | `{}` (passthrough) |
131+
132+
### `request_topic` Formats
133+
134+
**String** — one topic for every operation:
135+
```yaml
136+
request_topic: "mycompany/identity/request/v1/{request_id}"
137+
```
138+
139+
**Dict** — per-operation topics (operations not listed return `None` with a warning):
140+
```yaml
141+
request_topic:
142+
user_profile: "mycompany/identity/user-profile/request/v1/{request_id}"
143+
search_users: "mycompany/identity/search-users/request/v1/{request_id}"
144+
employee_data: "mycompany/identity/employee-data/request/v1/{request_id}"
145+
```
146+
147+
Available operation keys: `user_profile`, `search_users`, `employee_data`, `employee_profile`, `time_off`, `profile_picture`.
148+
149+
## Field Mapping Guide
150+
151+
The field mapping engine transforms source data through a three-phase pipeline:
152+
153+
```
154+
Source Data -> [1. Field Mapping] -> [2. Exclusion] -> [3. Renaming] -> Output
155+
^
156+
[Computed Fields]
157+
```
158+
159+
### Phase 1: field_mapping
160+
161+
Rename source fields to canonical names:
162+
163+
```yaml
164+
field_mapping:
165+
emp_email: "workEmail" # "emp_email" in source -> "workEmail" in output
166+
position: "jobTitle" # "position" in source -> "jobTitle" in output
167+
```
168+
169+
### Computed Fields
170+
171+
Derive values from multiple source fields:
172+
173+
```yaml
174+
computed_fields:
175+
# Concatenation (skips empty parts)
176+
- target: "displayName"
177+
source_fields: ["firstName", "middleName", "lastName"]
178+
separator: " "
179+
180+
# Template-based (with fallback to concatenation if template fails)
181+
- target: "fullAddress"
182+
source_fields: ["city", "country"]
183+
template: "{city}, {country}"
184+
```
185+
186+
### Phase 2: exclusion_list
187+
188+
Remove fields from output:
189+
190+
```yaml
191+
exclusion_list:
192+
- "mobilePhone" # Don't expose phone numbers
193+
- "salaryStructure" # Don't expose salary data
194+
```
195+
196+
### Phase 3: rename_mapping
197+
198+
Rename fields in the final output:
199+
200+
```yaml
201+
rename_mapping:
202+
supervisorId: "managerId" # Rename for downstream consumers
203+
workEmail: "email" # Simplify field name
204+
```
205+
206+
### Zero-Configuration
207+
208+
If your backend already returns data using the canonical field names (`id`, `displayName`, `workEmail`, `jobTitle`, `department`, `location`, `supervisorId`, `hireDate`, `mobilePhone`), you don't need any field mapping configuration. Just leave `field_mapping_config` empty or omit it entirely.
209+
210+
## Backend Integration Guide
211+
212+
Your backend service needs to listen on the configured topics and respond. Here's what each operation expects:
213+
214+
### user_profile
215+
216+
**Request payload:**
217+
```json
218+
{"email": "jane@company.com"}
219+
```
220+
221+
**Expected response:** A single employee record (dict).
222+
223+
### search_users
224+
225+
**Request payload:**
226+
```json
227+
{"query": "jan", "limit": 10}
228+
```
229+
230+
**Expected response:** A list of user dicts, or `{"results": [...]}`.
231+
232+
### employee_data
233+
234+
**Request payload:**
235+
```json
236+
{}
237+
```
238+
239+
**Expected response:** A list of all employee dicts, or `{"employees": [...]}`.
240+
241+
### employee_profile
242+
243+
**Request payload:**
244+
```json
245+
{"employee_id": "jane@company.com"}
246+
```
247+
248+
**Expected response:** A single employee record (dict).
249+
250+
### time_off
251+
252+
**Request payload:**
253+
```json
254+
{"employee_id": "jane@company.com", "start_date": "2025-01-01", "end_date": "2025-12-31"}
255+
```
256+
257+
**Expected response:** A list of time-off entry dicts, or `{"entries": [...]}`.
258+
Each entry must contain: `start` (YYYY-MM-DD), `end` (YYYY-MM-DD), `type` (string), `amount` ("full_day" or "half_day").
259+
260+
### profile_picture
261+
262+
**Request payload:**
263+
```json
264+
{"employee_id": "jane@company.com"}
265+
```
266+
267+
**Expected response:** A data URI string (e.g., `"data:image/jpeg;base64,..."`) or `{"data_uri": "..."}`.
268+
269+
## Canonical Employee Schema
270+
271+
| Field | Type | Description |
272+
|-------|------|-------------|
273+
| `id` | string | Unique, stable, lowercase identifier |
274+
| `displayName` | string | Full name for display |
275+
| `workEmail` | string | Primary work email |
276+
| `jobTitle` | string | Official job title |
277+
| `department` | string | Department name |
278+
| `location` | string | Physical/regional location |
279+
| `supervisorId` | string | Manager's unique id |
280+
| `hireDate` | string | ISO 8601 date (YYYY-MM-DD) |
281+
| `mobilePhone` | string | Mobile phone number |
282+
283+
Additional fields from your backend are passed through by default (when `pass_through_unmapped: true`).
284+
285+
## Development
286+
287+
```bash
288+
# Clone the repository
289+
git clone https://github.com/SolaceLabs/solace-agent-mesh-core-plugins.git
290+
cd solace-agent-mesh-core-plugins/sam-event-mesh-identity-provider
291+
292+
# Install in development mode
293+
pip install -e .
294+
295+
# Install test dependencies
296+
pip install pytest pytest-asyncio pytest-mock pytest-cov
297+
298+
# Run tests
299+
pytest tests/ -v
300+
301+
# Run with coverage
302+
pytest tests/ --cov=sam_event_mesh_identity_provider --cov-report=term-missing
303+
```
304+
305+
## License
306+
307+
Apache License 2.0

0 commit comments

Comments
 (0)