Skip to content

Commit 28a557e

Browse files
authored
Merge pull request #640 from vivche/servicenow-integration
ServiceNow Integration + Group Agent/Plugin fixes
2 parents 1e4b524 + 8a9ad98 commit 28a557e

27 files changed

Lines changed: 7701 additions & 279 deletions

application/single_app/config.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,7 @@
8888
EXECUTOR_TYPE = 'thread'
8989
EXECUTOR_MAX_WORKERS = 30
9090
SESSION_TYPE = 'filesystem'
91-
VERSION = "0.237.007"
92-
91+
VERSION = "0.237.008"
9392

9493
SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production')
9594

application/single_app/route_backend_plugins.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,12 @@ def create_group_action_route():
458458
for key in ('group_id', 'last_updated', 'user_id', 'is_global', 'is_group', 'scope'):
459459
payload.pop(key, None)
460460

461+
# Merge with schema to ensure all required fields are present (same as global actions)
462+
schema_dir = os.path.join(current_app.root_path, 'static', 'json', 'schemas')
463+
merged = get_merged_plugin_settings(payload.get('type'), payload, schema_dir)
464+
payload['metadata'] = merged.get('metadata', payload.get('metadata', {}))
465+
payload['additionalFields'] = merged.get('additionalFields', payload.get('additionalFields', {}))
466+
461467
try:
462468
saved = save_group_action(active_group, payload)
463469
except Exception as exc:
@@ -511,6 +517,12 @@ def update_group_action_route(action_id):
511517
except ValueError as exc:
512518
return jsonify({'error': str(exc)}), 400
513519

520+
# Merge with schema to ensure all required fields are present (same as global actions)
521+
schema_dir = os.path.join(current_app.root_path, 'static', 'json', 'schemas')
522+
schema_merged = get_merged_plugin_settings(merged.get('type'), merged, schema_dir)
523+
merged['metadata'] = schema_merged.get('metadata', merged.get('metadata', {}))
524+
merged['additionalFields'] = schema_merged.get('additionalFields', merged.get('additionalFields', {}))
525+
514526
try:
515527
saved = save_group_action(active_group, merged)
516528
except Exception as exc:

application/single_app/semantic_kernel_loader.py

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# semantic_kernel_loader.py
1+
# semantic_kernel_loader.py
22
"""
33
Loader for Semantic Kernel plugins/actions from app settings.
44
- Loads plugin/action manifests from settings (CosmosDB)
@@ -1186,26 +1186,60 @@ def load_user_semantic_kernel(kernel: Kernel, settings, user_id: str, redis_clie
11861186
for agent in agents_cfg:
11871187
agent['is_global'] = False
11881188

1189+
# Load group agents for user's active group (if any)
1190+
try:
1191+
active_group_id = require_active_group(user_id)
1192+
group_agents = get_group_agents(active_group_id)
1193+
if group_agents:
1194+
print(f"[SK Loader] Found {len(group_agents)} group agents for active group '{active_group_id}'")
1195+
# Badge group agents with group metadata
1196+
for group_agent in group_agents:
1197+
group_agent['is_global'] = False
1198+
group_agent['is_group'] = True
1199+
agents_cfg.extend(group_agents)
1200+
print(f"[SK Loader] After merging group agents: {len(agents_cfg)} total agents")
1201+
else:
1202+
print(f"[SK Loader] No group agents found for active group '{active_group_id}'")
1203+
except ValueError:
1204+
# No active group set - this is fine, just means no group agents available
1205+
print(f"[SK Loader] User '{user_id}' has no active group - skipping group agent loading")
1206+
11891207
# Append selected group agent (if any) to the candidate list so downstream selection logic can resolve it
11901208
selected_agent_data = selected_agent if isinstance(selected_agent, dict) else {}
11911209
selected_agent_is_group = selected_agent_data.get('is_group', False)
11921210
if selected_agent_is_group:
11931211
resolved_group_id = selected_agent_data.get('group_id')
1212+
active_group_id = None
1213+
1214+
# Group agent MUST have a group_id
1215+
if not resolved_group_id:
1216+
log_event(
1217+
"[SK Loader] Group agent selected but no group_id provided in selection data.",
1218+
level=logging.ERROR
1219+
)
1220+
load_core_plugins_only(kernel, settings)
1221+
return kernel, None
1222+
11941223
try:
11951224
active_group_id = require_active_group(user_id)
1196-
if not resolved_group_id:
1197-
resolved_group_id = active_group_id
1198-
elif resolved_group_id != active_group_id:
1225+
if resolved_group_id != active_group_id:
11991226
debug_print(
12001227
f"[SK Loader] Selected group agent references group {resolved_group_id}, active group is {active_group_id}."
12011228
)
1202-
except ValueError as err:
1203-
debug_print(f"[SK Loader] No active group available while loading group agent: {err}")
1204-
if not resolved_group_id:
12051229
log_event(
1206-
"[SK Loader] Group agent selected but no active group in settings.",
1207-
level=logging.WARNING
1230+
"[SK Loader] Group agent selected from the non-active group.",
1231+
level=logging.ERROR
12081232
)
1233+
load_core_plugins_only(kernel, settings)
1234+
return kernel, None
1235+
except ValueError as err:
1236+
debug_print(f"[SK Loader] No active group available while loading group agent: {err}")
1237+
log_event(
1238+
"[SK Loader] Group agent selected but no active group in settings.",
1239+
level=logging.ERROR
1240+
)
1241+
load_core_plugins_only(kernel, settings)
1242+
return kernel, None
12091243

12101244
if resolved_group_id:
12111245
agent_identifier = selected_agent_data.get('id') or selected_agent_data.get('name')
@@ -1234,11 +1268,6 @@ def load_user_semantic_kernel(kernel: Kernel, settings, user_id: str, redis_clie
12341268
f"[SK Loader] Selected group agent '{selected_agent_data.get('name')}' not found for group {resolved_group_id}.",
12351269
level=logging.WARNING
12361270
)
1237-
else:
1238-
log_event(
1239-
"[SK Loader] Unable to resolve group ID for selected group agent; skipping group agent load.",
1240-
level=logging.WARNING
1241-
)
12421271

12431272
# PATCH: Merge global agents if enabled
12441273
merge_global = settings.get('merge_global_semantic_kernel_with_workspace', False)

application/single_app/semantic_kernel_plugins/openapi_plugin_factory.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import tempfile
1313
from typing import Dict, Any, Optional
1414
from .openapi_plugin import OpenApiPlugin
15+
from functions_debug import debug_print
1516

1617

1718
class OpenApiPluginFactory:
@@ -130,10 +131,48 @@ def _extract_auth_config(cls, config: Dict[str, Any]) -> Dict[str, Any]:
130131
return {}
131132

132133
auth_type = auth_config.get('type', 'none')
134+
debug_print(f"[Factory] auth_type: {auth_type}")
133135

134136
if auth_type == 'none':
135137
return {}
136138

137-
# Return the auth config as-is since the OpenApiPlugin already handles
138-
# the different auth types
139+
# Check if this is basic auth stored in the 'key' field format
140+
# Simple Chat stores basic auth as: auth.type='key', auth.key='username:password', additionalFields.auth_method='basic'
141+
additional_fields = config.get('additionalFields', {})
142+
auth_method = additional_fields.get('auth_method', '')
143+
debug_print(f"[Factory] additionalFields.auth_method: {auth_method}")
144+
145+
if auth_type == 'key' and auth_method == 'basic':
146+
# Extract username and password from the combined key
147+
key = auth_config.get('key', '')
148+
debug_print(f"[Factory] Applying basic auth transformation")
149+
if ':' in key:
150+
username, password = key.split(':', 1)
151+
return {
152+
'type': 'basic',
153+
'username': username,
154+
'password': password
155+
}
156+
else:
157+
# Malformed basic auth key
158+
return {}
159+
160+
# For bearer tokens stored as 'key' type
161+
if auth_type == 'key' and auth_method == 'bearer':
162+
token = auth_config.get('key', '')
163+
debug_print(f"[Factory] Applying bearer auth transformation")
164+
return {
165+
'type': 'bearer',
166+
'token': token
167+
}
168+
169+
# For OAuth2 stored as 'key' type
170+
if auth_type == 'key' and auth_method == 'oauth2':
171+
debug_print(f"[Factory] Applying OAuth2 auth transformation")
172+
return {
173+
'type': 'bearer', # OAuth2 tokens are typically bearer tokens
174+
'token': auth_config.get('key', '')
175+
}
176+
177+
# Return the auth config as-is for other auth types
139178
return auth_config

0 commit comments

Comments
 (0)