diff --git a/application/single_app/config.py b/application/single_app/config.py index beb5db2c..ee1bc63d 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -88,8 +88,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.237.007" - +VERSION = "0.237.009" SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production') @@ -123,11 +122,49 @@ CLIENTS = {} CLIENTS_LOCK = threading.Lock() -ALLOWED_EXTENSIONS = { - 'txt', 'pdf', 'doc', 'docm', 'docx', 'xlsx', 'xlsm', 'xls', 'csv', 'pptx', 'html', 'jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'heif', 'md', 'json', - 'mp4', 'mov', 'avi', 'mkv', 'flv', 'mxf', 'gxf', 'ts', 'ps', '3gp', '3gpp', 'mpg', 'wmv', 'asf', 'm4a', 'm4v', 'isma', 'ismv', - 'dvr-ms', 'wav', 'xml', 'yaml', 'yml', 'log' +# Base allowed extensions (always available) +BASE_ALLOWED_EXTENSIONS = {'txt', 'doc', 'docm', 'html', 'md', 'json', 'xml', 'yaml', 'yml', 'log'} +DOCUMENT_EXTENSIONS = {'pdf', 'docx', 'pptx', 'ppt'} +TABULAR_EXTENSIONS = {'csv', 'xlsx', 'xls', 'xlsm'} + +# Updates to image, video, or audio extensions should also be made in static/js/chat/chat-enhanced-citations.js if the new file types can be natively rendered in the browser. +IMAGE_EXTENSIONS = {'jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'heif', 'heic'} + +# Optional extensions by feature +VIDEO_EXTENSIONS = { + 'mp4', 'mov', 'avi', 'mkv', 'flv', 'mxf', 'gxf', 'ts', 'ps', '3gp', '3gpp', + 'mpg', 'wmv', 'asf', 'm4v', 'isma', 'ismv', 'dvr-ms', 'webm', 'mpeg' } + +AUDIO_EXTENSIONS = {'mp3', 'wav', 'ogg', 'aac', 'flac', 'm4a'} + +def get_allowed_extensions(enable_video=False, enable_audio=False): + """ + Get allowed file extensions based on feature flags. + + Args: + enable_video: Whether video file support is enabled + enable_audio: Whether audio file support is enabled + + Returns: + set: Allowed file extensions + """ + extensions = BASE_ALLOWED_EXTENSIONS.copy() + extensions.update(DOCUMENT_EXTENSIONS) + extensions.update(IMAGE_EXTENSIONS) + extensions.update(TABULAR_EXTENSIONS) + + if enable_video: + extensions.update(VIDEO_EXTENSIONS) + + if enable_audio: + extensions.update(AUDIO_EXTENSIONS) + + return extensions + +ALLOWED_EXTENSIONS = get_allowed_extensions(enable_video=True, enable_audio=True) + +# Admin UI specific extensions (for logo/favicon uploads) ALLOWED_EXTENSIONS_IMG = {'png', 'jpg', 'jpeg'} MAX_CONTENT_LENGTH = 5000 * 1024 * 1024 # 5000 MB AKA 5 GB diff --git a/application/single_app/functions_documents.py b/application/single_app/functions_documents.py index 017b819f..9ae01a62 100644 --- a/application/single_app/functions_documents.py +++ b/application/single_app/functions_documents.py @@ -4851,7 +4851,7 @@ def process_di_document(document_id, user_id, temp_file_path, original_filename, is_pdf = file_ext == '.pdf' is_word = file_ext in ('.docx', '.doc') is_ppt = file_ext in ('.pptx', '.ppt') - is_image = file_ext in ('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.heif') + is_image = file_ext in tuple('.' + ext for ext in IMAGE_EXTENSIONS) try: if is_pdf: @@ -5233,23 +5233,138 @@ def process_audio_document( print(f"[Debug] Transcribing chunk {idx}: {chunk_path}") # Get fresh config (tokens expire after ~1 hour) - speech_config = _get_speech_config(settings, endpoint, locale) + try: + speech_config = _get_speech_config(settings, endpoint, locale) + except Exception as e: + print(f"[Error] Failed to get speech config for chunk {idx}: {e}") + raise RuntimeError(f"Speech configuration failed for chunk {idx}: {e}") - audio_config = speechsdk.AudioConfig(filename=chunk_path) - speech_recognizer = speechsdk.SpeechRecognizer( - speech_config=speech_config, - audio_config=audio_config - ) + try: + audio_config = speechsdk.AudioConfig(filename=chunk_path) + except Exception as e: + print(f"[Error] Failed to load audio file {chunk_path}: {e}") + raise RuntimeError(f"Audio file loading failed: {e}") - result = speech_recognizer.recognize_once() - if result.reason == speechsdk.ResultReason.RecognizedSpeech: - print(f"[Debug] Recognized: {result.text}") - all_phrases.append(result.text) - elif result.reason == speechsdk.ResultReason.NoMatch: - print(f"[Warning] No speech in {chunk_path}") - elif result.reason == speechsdk.ResultReason.Canceled: - print(f"[Error] {result.cancellation_details.reason}: {result.cancellation_details.error_details}") - raise RuntimeError(f"Transcription canceled for {chunk_path}: {result.cancellation_details.error_details}") + try: + speech_recognizer = speechsdk.SpeechRecognizer( + speech_config=speech_config, + audio_config=audio_config + ) + except Exception as e: + print(f"[Error] Failed to create speech recognizer for chunk {idx}: {e}") + raise RuntimeError(f"Speech recognizer creation failed: {e}") + + # Use continuous recognition instead of recognize_once + all_results = [] + done = False + error_occurred = False + error_message = None + + def stop_cb(evt): + nonlocal done + print(f"[Debug] Session stopped for chunk {idx}") + done = True + + def recognized_cb(evt): + try: + if evt.result.reason == speechsdk.ResultReason.RecognizedSpeech: + all_results.append(evt.result.text) + print(f"[Debug] Recognized: {evt.result.text}") + elif evt.result.reason == speechsdk.ResultReason.NoMatch: + print(f"[Debug] No speech recognized in segment") + except Exception as e: + print(f"[Error] Error in recognized callback: {e}") + # Don't fail on individual recognition errors + + def canceled_cb(evt): + nonlocal done, error_occurred, error_message + print(f"[Debug] Recognition canceled for chunk {idx}: {evt.cancellation_details.reason}") + + if evt.cancellation_details.reason == speechsdk.CancellationReason.Error: + error_occurred = True + error_message = evt.cancellation_details.error_details + print(f"[Error] Recognition error: {error_message}") + elif evt.cancellation_details.reason == speechsdk.CancellationReason.EndOfStream: + print(f"[Debug] End of audio stream reached") + + done = True + + try: + # Connect callbacks + speech_recognizer.recognized.connect(recognized_cb) + speech_recognizer.session_stopped.connect(stop_cb) + speech_recognizer.canceled.connect(canceled_cb) + + # Start continuous recognition + print(f"[Debug] Starting continuous recognition for chunk {idx}") + speech_recognizer.start_continuous_recognition() + + # Wait for completion with timeout + import time + timeout_seconds = 600 # 10 minutes max per chunk + start_time = time.time() + + while not done: + if time.time() - start_time > timeout_seconds: + print(f"[Error] Recognition timeout for chunk {idx}") + error_occurred = True + error_message = f"Recognition timed out after {timeout_seconds} seconds" + break + time.sleep(0.5) + + # Stop recognition + try: + speech_recognizer.stop_continuous_recognition() + print(f"[Debug] Stopped continuous recognition for chunk {idx}") + except Exception as e: + print(f"[Warning] Error stopping recognition for chunk {idx}: {e}") + # Continue even if stop fails + + # Check for errors after completion + if error_occurred: + raise RuntimeError(f"Recognition failed for chunk {idx}: {error_message}") + + # Add all recognized phrases to the overall list + if all_results: + all_phrases.extend(all_results) + print(f"[Debug] Total phrases from chunk {idx}: {len(all_results)}") + else: + print(f"[Warning] No speech recognized in {chunk_path}") + # Continue to next chunk - empty result is not necessarily an error + + except RuntimeError as e: + # Re-raise runtime errors (these are our custom errors) + raise + except Exception as e: + print(f"[Error] Unexpected error during recognition for chunk {idx}: {e}") + raise RuntimeError(f"Recognition failed unexpectedly for chunk {idx}: {e}") + finally: + # Cleanup: disconnect callbacks and dispose recognizer + try: + speech_recognizer.recognized.disconnect_all() + speech_recognizer.session_stopped.disconnect_all() + speech_recognizer.canceled.disconnect_all() + except Exception as e: + print(f"[Warning] Error disconnecting callbacks for chunk {idx}: {e}") + + # # Get fresh config (tokens expire after ~1 hour) + # speech_config = _get_speech_config(settings, endpoint, locale) + + # audio_config = speechsdk.AudioConfig(filename=chunk_path) + # speech_recognizer = speechsdk.SpeechRecognizer( + # speech_config=speech_config, + # audio_config=audio_config + # ) + + # result = speech_recognizer.recognize_once() + # if result.reason == speechsdk.ResultReason.RecognizedSpeech: + # print(f"[Debug] Recognized: {result.text}") + # all_phrases.append(result.text) + # elif result.reason == speechsdk.ResultReason.NoMatch: + # print(f"[Warning] No speech in {chunk_path}") + # elif result.reason == speechsdk.ResultReason.Canceled: + # print(f"[Error] {result.cancellation_details.reason}: {result.cancellation_details.error_details}") + # raise RuntimeError(f"Transcription canceled for {chunk_path}: {result.cancellation_details.error_details}") else: # Use the fast-transcription API if not in sovereign or custom cloud @@ -5357,8 +5472,12 @@ def process_document_upload_background(document_id, user_id, temp_file_path, ori enable_extract_meta_data = settings.get('enable_extract_meta_data', False) # Used by DI flow max_file_size_bytes = settings.get('max_file_size_mb', 16) * 1024 * 1024 - video_extensions = ('.mp4', '.mov', '.avi', '.mkv', '.flv') - audio_extensions = ('.mp3', '.wav', '.ogg', '.aac', '.flac', '.m4a') + # Get allowed extensions from config.py to determine which processing function to call + tabular_extensions = tuple('.' + ext for ext in TABULAR_EXTENSIONS) + image_extensions = tuple('.' + ext for ext in IMAGE_EXTENSIONS) + di_supported_extensions = tuple('.' + ext for ext in DOCUMENT_EXTENSIONS | IMAGE_EXTENSIONS) + video_extensions = tuple('.' + ext for ext in VIDEO_EXTENSIONS) + audio_extensions = tuple('.' + ext for ext in AUDIO_EXTENSIONS) # --- Define update_document callback wrapper --- # This makes it easier to pass the update function to helpers without repeating args @@ -5402,8 +5521,6 @@ def update_doc_callback(**kwargs): # --- 1. Dispatch to appropriate handler based on file type --- # Note: .doc and .docm are handled separately by process_doc() using docx2txt - di_supported_extensions = ('.pdf', '.docx', '.pptx', '.ppt', '.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.heif') - tabular_extensions = ('.csv', '.xlsx', '.xls', '.xlsm') is_group = group_id is not None @@ -5512,7 +5629,7 @@ def update_doc_callback(**kwargs): final_status = "Processing complete" if total_chunks_saved == 0: # Provide more specific status if no chunks were saved - if file_ext in ('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.heif'): + if file_ext in image_extensions: final_status = "Processing complete - no text found in image" elif file_ext in tabular_extensions: final_status = "Processing complete - no data rows found or file empty" diff --git a/application/single_app/route_backend_chats.py b/application/single_app/route_backend_chats.py index ad514e6f..10ea1abe 100644 --- a/application/single_app/route_backend_chats.py +++ b/application/single_app/route_backend_chats.py @@ -442,7 +442,7 @@ def result_requires_message_reload(result: Any) -> bool: doc_results = list(cosmos_container.query_items( query=doc_query, parameters=doc_params, enable_cross_partition_query=True )) - if doc_results: + if doc_results and 'workspace_search' in user_metadata: doc_info = doc_results[0] user_metadata['workspace_search']['document_name'] = doc_info.get('title') or doc_info.get('file_name') user_metadata['workspace_search']['document_filename'] = doc_info.get('file_name') @@ -465,18 +465,22 @@ def result_requires_message_reload(result: Any) -> bool: if group_doc.get('name'): group_name = group_doc.get('name') - user_metadata['workspace_search']['group_name'] = group_name - debug_print(f"Workspace search - set group_name to: {group_name}") + if 'workspace_search' in user_metadata: + user_metadata['workspace_search']['group_name'] = group_name + debug_print(f"Workspace search - set group_name to: {group_name}") else: debug_print(f"Workspace search - no name for group: {active_group_id}") - user_metadata['workspace_search']['group_name'] = None + if 'workspace_search' in user_metadata: + user_metadata['workspace_search']['group_name'] = None else: debug_print(f"Workspace search - no group found for id: {active_group_id}") - user_metadata['workspace_search']['group_name'] = None + if 'workspace_search' in user_metadata: + user_metadata['workspace_search']['group_name'] = None except Exception as e: debug_print(f"Error retrieving group details: {e}") - user_metadata['workspace_search']['group_name'] = None + if 'workspace_search' in user_metadata: + user_metadata['workspace_search']['group_name'] = None import traceback traceback.print_exc() @@ -492,8 +496,11 @@ def result_requires_message_reload(result: Any) -> bool: except Exception as e: debug_print(f"Error checking public workspace status: {e}") - user_metadata['workspace_search']['active_public_workspace_id'] = active_public_workspace_id - else: + if 'workspace_search' in user_metadata: + user_metadata['workspace_search']['active_public_workspace_id'] = active_public_workspace_id + + # Ensure workspace_search key always exists for consistency + if 'workspace_search' not in user_metadata: user_metadata['workspace_search'] = { 'search_enabled': False } diff --git a/application/single_app/route_backend_documents.py b/application/single_app/route_backend_documents.py index 072577d6..31619f69 100644 --- a/application/single_app/route_backend_documents.py +++ b/application/single_app/route_backend_documents.py @@ -535,9 +535,23 @@ def api_patch_user_document(document_id): try: # Log the metadata update transaction if any fields were updated if updated_fields: - # Get document details for logging - doc = get_document(user_id, document_id) - if doc: + # Get document details for logging - handle tuple return + doc_response = get_document(user_id, document_id) + doc = None + + # Handle tuple return (response, status_code) + if isinstance(doc_response, tuple): + resp, status_code = doc_response + if hasattr(resp, "get_json"): + doc = resp.get_json() + else: + doc = resp + elif hasattr(doc_response, "get_json"): + doc = doc_response.get_json() + else: + doc = doc_response + + if doc and isinstance(doc, dict): log_document_metadata_update_transaction( user_id=user_id, document_id=document_id, diff --git a/application/single_app/route_backend_group_documents.py b/application/single_app/route_backend_group_documents.py index 194b5a6b..68a1c0fa 100644 --- a/application/single_app/route_backend_group_documents.py +++ b/application/single_app/route_backend_group_documents.py @@ -416,24 +416,43 @@ def api_patch_group_document(document_id): ) updated_fields['authors'] = authors_list - # Log the metadata update transaction if any fields were updated - if updated_fields: + # Save updates back to Cosmos + try: + # Log the metadata update transaction if any fields were updated + if updated_fields: + # Get document details for logging - handle tuple return # Get document details for logging - from functions_documents import get_document - doc = get_document(user_id, document_id, group_id=active_group_id) - if doc: - from functions_activity_logging import log_document_metadata_update_transaction - log_document_metadata_update_transaction( - user_id=user_id, - document_id=document_id, - workspace_type='group', - file_name=doc.get('file_name', 'Unknown'), - updated_fields=updated_fields, - file_type=doc.get('file_type'), - group_id=active_group_id - ) - - return jsonify({'message': 'Group document metadata updated successfully'}), 200 + from functions_documents import get_document + doc_response = get_document(user_id, document_id, group_id=active_group_id) + doc = None + + # Handle tuple return (response, status_code) + if isinstance(doc_response, tuple): + resp, status_code = doc_response + if hasattr(resp, "get_json"): + doc = resp.get_json() + else: + doc = resp + elif hasattr(doc_response, "get_json"): + doc = doc_response.get_json() + else: + doc = doc_response + + if doc and isinstance(doc, dict): + from functions_activity_logging import log_document_metadata_update_transaction + log_document_metadata_update_transaction( + user_id=user_id, + document_id=document_id, + workspace_type='group', + file_name=doc.get('file_name', 'Unknown'), + updated_fields=updated_fields, + file_type=doc.get('file_type'), + group_id=active_group_id + ) + + return jsonify({'message': 'Group document metadata updated successfully'}), 200 + except Exception as e: + return jsonify({'Error updating Group document metadata': str(e)}), 500 except Exception as e: return jsonify({'error': str(e)}), 500 diff --git a/application/single_app/route_backend_plugins.py b/application/single_app/route_backend_plugins.py index 01d448b5..6f24c932 100644 --- a/application/single_app/route_backend_plugins.py +++ b/application/single_app/route_backend_plugins.py @@ -458,6 +458,12 @@ def create_group_action_route(): for key in ('group_id', 'last_updated', 'user_id', 'is_global', 'is_group', 'scope'): payload.pop(key, None) + # Merge with schema to ensure all required fields are present (same as global actions) + schema_dir = os.path.join(current_app.root_path, 'static', 'json', 'schemas') + merged = get_merged_plugin_settings(payload.get('type'), payload, schema_dir) + payload['metadata'] = merged.get('metadata', payload.get('metadata', {})) + payload['additionalFields'] = merged.get('additionalFields', payload.get('additionalFields', {})) + try: saved = save_group_action(active_group, payload) except Exception as exc: @@ -511,6 +517,12 @@ def update_group_action_route(action_id): except ValueError as exc: return jsonify({'error': str(exc)}), 400 + # Merge with schema to ensure all required fields are present (same as global actions) + schema_dir = os.path.join(current_app.root_path, 'static', 'json', 'schemas') + schema_merged = get_merged_plugin_settings(merged.get('type'), merged, schema_dir) + merged['metadata'] = schema_merged.get('metadata', merged.get('metadata', {})) + merged['additionalFields'] = schema_merged.get('additionalFields', merged.get('additionalFields', {})) + try: saved = save_group_action(active_group, merged) except Exception as exc: diff --git a/application/single_app/route_backend_public_documents.py b/application/single_app/route_backend_public_documents.py index 9e228acd..a209e9a2 100644 --- a/application/single_app/route_backend_public_documents.py +++ b/application/single_app/route_backend_public_documents.py @@ -299,25 +299,45 @@ def api_patch_public_document(doc_id): update_document(document_id=doc_id, public_workspace_id=active_ws, user_id=user_id, document_classification=data['document_classification']) updated_fields['document_classification'] = data['document_classification'] - # Log the metadata update transaction if any fields were updated - if updated_fields: - from functions_documents import get_document - from functions_activity_logging import log_document_metadata_update_transaction - doc = get_document(user_id, doc_id, public_workspace_id=active_ws) - if doc: - log_document_metadata_update_transaction( - user_id=user_id, - document_id=doc_id, - workspace_type='public', - file_name=doc.get('file_name', 'Unknown'), - updated_fields=updated_fields, - file_type=doc.get('file_type'), - public_workspace_id=active_ws - ) - - return jsonify({'message':'Metadata updated'}), 200 + # Save updates back to Cosmos + try: + # Log the metadata update transaction if any fields were updated + if updated_fields: + # Get document details for logging - handle tuple return + # Get document details for logging + from functions_documents import get_document + doc_response = get_document(user_id, doc_id, public_workspace_id=active_ws) + doc = None + + # Handle tuple return (response, status_code) + if isinstance(doc_response, tuple): + resp, status_code = doc_response + if hasattr(resp, "get_json"): + doc = resp.get_json() + else: + doc = resp + elif hasattr(doc_response, "get_json"): + doc = doc_response.get_json() + else: + doc = doc_response + + if doc and isinstance(doc, dict): + from functions_activity_logging import log_document_metadata_update_transaction + log_document_metadata_update_transaction( + user_id=user_id, + document_id=doc_id, + workspace_type='public', + file_name=doc.get('file_name', 'Unknown'), + updated_fields=updated_fields, + file_type=doc.get('file_type'), + public_workspace_id=active_ws + ) + + return jsonify({'message': 'Public document metadata updated successfully'}), 200 + except Exception as e: + return jsonify({'Error updating Public document metadata': str(e)}), 500 except Exception as e: - return jsonify({'error':str(e)}), 500 + return jsonify({'error': str(e)}), 500 @app.route('/api/public_documents/', methods=['DELETE']) @swagger_route(security=get_auth_security()) diff --git a/application/single_app/route_enhanced_citations.py b/application/single_app/route_enhanced_citations.py index 684559db..c81ef225 100644 --- a/application/single_app/route_enhanced_citations.py +++ b/application/single_app/route_enhanced_citations.py @@ -15,7 +15,7 @@ from functions_group import get_user_groups from functions_public_workspaces import get_user_visible_public_workspace_ids_from_settings from swagger_wrapper import swagger_route, get_auth_security -from config import CLIENTS, storage_account_user_documents_container_name, storage_account_group_documents_container_name, storage_account_public_documents_container_name +from config import CLIENTS, storage_account_user_documents_container_name, storage_account_group_documents_container_name, storage_account_public_documents_container_name, IMAGE_EXTENSIONS, VIDEO_EXTENSIONS, AUDIO_EXTENSIONS from functions_debug import debug_print def register_enhanced_citations_routes(app): @@ -49,9 +49,8 @@ def get_enhanced_citation_image(): # Check if it's an image file file_name = raw_doc['file_name'] ext = file_name.lower().split('.')[-1] if '.' in file_name else '' - image_extensions = ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'heif'] - if ext not in image_extensions: + if ext not in IMAGE_EXTENSIONS: return jsonify({"error": "File is not an image"}), 400 # Serve the image content directly @@ -88,9 +87,8 @@ def get_enhanced_citation_video(): # Check if it's a video file file_name = raw_doc['file_name'] ext = file_name.lower().split('.')[-1] if '.' in file_name else '' - video_extensions = ['mp4', 'mov', 'avi', 'mkv', 'flv', 'webm', 'wmv'] - if ext not in video_extensions: + if ext not in VIDEO_EXTENSIONS: return jsonify({"error": "File is not a video"}), 400 # Serve the video content directly @@ -127,9 +125,8 @@ def get_enhanced_citation_audio(): # Check if it's an audio file file_name = raw_doc['file_name'] ext = file_name.lower().split('.')[-1] if '.' in file_name else '' - audio_extensions = ['mp3', 'wav', 'ogg', 'aac', 'flac', 'm4a'] - if ext not in audio_extensions: + if ext not in AUDIO_EXTENSIONS: return jsonify({"error": "File is not an audio file"}), 400 # Serve the audio content directly diff --git a/application/single_app/route_frontend_chats.py b/application/single_app/route_frontend_chats.py index af3ce9b1..8e34c0f4 100644 --- a/application/single_app/route_frontend_chats.py +++ b/application/single_app/route_frontend_chats.py @@ -131,9 +131,9 @@ def upload_file(): try: # Check if this is an image file - is_image_file = file_ext in ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.heif'] + is_image_file = file_ext in IMAGE_EXTENSIONS - if file_ext in ['.pdf', '.docx', '.pptx', '.html', '.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.heif']: + if file_ext in ['.pdf', '.docx', '.pptx', '.ppt', '.html'] or is_image_file: extracted_content_raw = extract_content_with_azure_di(temp_file_path) # Convert pages_data list to string @@ -209,7 +209,7 @@ def upload_file(): elif file_ext in ['.xml', '.yaml', '.yml', '.log']: # Handle XML, YAML, and LOG files as text for inline chat extracted_content = extract_text_file(temp_file_path) - elif file_ext in ['.csv', '.xls', '.xlsx', '.xlsm']: + elif file_ext in TABULAR_EXTENSIONS: extracted_content = extract_table_file(temp_file_path, file_ext) is_table = True else: @@ -685,8 +685,8 @@ def view_document(): is_pdf = file_ext == '.pdf' is_word = file_ext in ('.docx', '.doc', '.docm') is_ppt = file_ext in ('.pptx', '.ppt') - is_image = file_ext in ('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.gif', '.webp') # Added more image types - is_text = file_ext in ('.txt', '.md', '.csv', '.json', '.log', '.xml', '.yaml', '.yml', '.html', '.htm') # Common text-based types + is_image = file_ext.lstrip('.') in (IMAGE_EXTENSIONS | {'gif', 'webp'}) # Added more image types + is_text = file_ext.lstrip('.') in (BASE_ALLOWED_EXTENSIONS - {'doc', 'docm'}) # Common text-based types try: # Download the file to the specified location diff --git a/application/single_app/route_frontend_group_workspaces.py b/application/single_app/route_frontend_group_workspaces.py index 75996d84..850cc8d0 100644 --- a/application/single_app/route_frontend_group_workspaces.py +++ b/application/single_app/route_frontend_group_workspaces.py @@ -45,30 +45,13 @@ def group_workspaces(): ) legacy_count = legacy_docs_from_cosmos[0] if legacy_docs_from_cosmos else 0 - # Build allowed extensions string - allowed_extensions = [ - "txt", "pdf", "doc", "docm", "docx", "xlsx", "xls", "xlsm","csv", "pptx", "html", - "jpg", "jpeg", "png", "bmp", "tiff", "tif", "heif", "md", "json", - "xml", "yaml", "yml", "log" - ] - if enable_video_file_support in [True, 'True', 'true']: - allowed_extensions += ["mp4", "mov", "avi", "wmv", "mkv", "webm"] - if enable_audio_file_support in [True, 'True', 'true']: - allowed_extensions += ["mp3", "wav", "ogg", "aac", "flac", "m4a"] - allowed_extensions_str = "Allowed: " + ", ".join(allowed_extensions) - - # Build allowed extensions string - allowed_extensions = [ - "txt", "pdf", "doc", "docm", "docx", "xlsx", "xls", "xlsm","csv", "pptx", "html", - "jpg", "jpeg", "png", "bmp", "tiff", "tif", "heif", "md", "json", - "xml", "yaml", "yml", "log" - ] - if enable_video_file_support in [True, 'True', 'true']: - allowed_extensions += ["mp4", "mov", "avi", "wmv", "mkv", "webm"] - if enable_audio_file_support in [True, 'True', 'true']: - allowed_extensions += ["mp3", "wav", "ogg", "aac", "flac", "m4a"] + # Get allowed extensions from central function and build allowed extensions string + allowed_extensions = sorted(get_allowed_extensions( + enable_video=enable_video_file_support in [True, 'True', 'true'], + enable_audio=enable_audio_file_support in [True, 'True', 'true'] + )) allowed_extensions_str = "Allowed: " + ", ".join(allowed_extensions) - + return render_template( 'group_workspaces.html', settings=public_settings, diff --git a/application/single_app/route_frontend_public_workspaces.py b/application/single_app/route_frontend_public_workspaces.py index 10235444..05d5b982 100644 --- a/application/single_app/route_frontend_public_workspaces.py +++ b/application/single_app/route_frontend_public_workspaces.py @@ -69,18 +69,13 @@ def public_workspaces(): enable_video_file_support = settings.get('enable_video_file_support', False) enable_audio_file_support = settings.get('enable_audio_file_support', False) - # Build allowed extensions string as in workspace.html - allowed_extensions = [ - "txt", "pdf", "doc", "docm", "docx", "xlsx", "xls", "xlsm","csv", "pptx", "html", - "jpg", "jpeg", "png", "bmp", "tiff", "tif", "heif", "md", "json", - "xml", "yaml", "yml", "log" - ] - if enable_video_file_support in [True, 'True', 'true']: - allowed_extensions += ["mp4", "mov", "avi", "wmv", "mkv", "webm"] - if enable_audio_file_support in [True, 'True', 'true']: - allowed_extensions += ["mp3", "wav", "ogg", "aac", "flac", "m4a"] + # Get allowed extensions from central function and build allowed extensions string + allowed_extensions = sorted(get_allowed_extensions( + enable_video=enable_video_file_support in [True, 'True', 'true'], + enable_audio=enable_audio_file_support in [True, 'True', 'true'] + )) allowed_extensions_str = "Allowed: " + ", ".join(allowed_extensions) - + return render_template( 'public_workspaces.html', settings=public_settings, diff --git a/application/single_app/route_frontend_workspace.py b/application/single_app/route_frontend_workspace.py index 47f121e0..2ca1aad9 100644 --- a/application/single_app/route_frontend_workspace.py +++ b/application/single_app/route_frontend_workspace.py @@ -43,18 +43,13 @@ def workspace(): ) legacy_count = legacy_docs_from_cosmos[0] if legacy_docs_from_cosmos else 0 - # Build allowed extensions string - allowed_extensions = [ - "txt", "pdf", "doc", "docm", "docx", "xlsx", "xls", "xlsm","csv", "pptx", "html", - "jpg", "jpeg", "png", "bmp", "tiff", "tif", "heif", "md", "json", - "xml", "yaml", "yml", "log" - ] - if enable_video_file_support in [True, 'True', 'true']: - allowed_extensions += ["mp4", "mov", "avi", "wmv", "mkv", "webm"] - if enable_audio_file_support in [True, 'True', 'true']: - allowed_extensions += ["mp3", "wav", "ogg", "aac", "flac", "m4a"] + # Get allowed extensions from central function and build allowed extensions string + allowed_extensions = sorted(get_allowed_extensions( + enable_video=enable_video_file_support in [True, 'True', 'true'], + enable_audio=enable_audio_file_support in [True, 'True', 'true'] + )) allowed_extensions_str = "Allowed: " + ", ".join(allowed_extensions) - + return render_template( 'workspace.html', settings=public_settings, diff --git a/application/single_app/semantic_kernel_loader.py b/application/single_app/semantic_kernel_loader.py index 35d35965..78f54203 100644 --- a/application/single_app/semantic_kernel_loader.py +++ b/application/single_app/semantic_kernel_loader.py @@ -1,4 +1,4 @@ -# semantic_kernel_loader.py +# semantic_kernel_loader.py """ Loader for Semantic Kernel plugins/actions from app settings. - Loads plugin/action manifests from settings (CosmosDB) @@ -1186,26 +1186,60 @@ def load_user_semantic_kernel(kernel: Kernel, settings, user_id: str, redis_clie for agent in agents_cfg: agent['is_global'] = False + # Load group agents for user's active group (if any) + try: + active_group_id = require_active_group(user_id) + group_agents = get_group_agents(active_group_id) + if group_agents: + print(f"[SK Loader] Found {len(group_agents)} group agents for active group '{active_group_id}'") + # Badge group agents with group metadata + for group_agent in group_agents: + group_agent['is_global'] = False + group_agent['is_group'] = True + agents_cfg.extend(group_agents) + print(f"[SK Loader] After merging group agents: {len(agents_cfg)} total agents") + else: + print(f"[SK Loader] No group agents found for active group '{active_group_id}'") + except ValueError: + # No active group set - this is fine, just means no group agents available + print(f"[SK Loader] User '{user_id}' has no active group - skipping group agent loading") + # Append selected group agent (if any) to the candidate list so downstream selection logic can resolve it selected_agent_data = selected_agent if isinstance(selected_agent, dict) else {} selected_agent_is_group = selected_agent_data.get('is_group', False) if selected_agent_is_group: resolved_group_id = selected_agent_data.get('group_id') + active_group_id = None + + # Group agent MUST have a group_id + if not resolved_group_id: + log_event( + "[SK Loader] Group agent selected but no group_id provided in selection data.", + level=logging.ERROR + ) + load_core_plugins_only(kernel, settings) + return kernel, None + try: active_group_id = require_active_group(user_id) - if not resolved_group_id: - resolved_group_id = active_group_id - elif resolved_group_id != active_group_id: + if resolved_group_id != active_group_id: debug_print( f"[SK Loader] Selected group agent references group {resolved_group_id}, active group is {active_group_id}." ) - except ValueError as err: - debug_print(f"[SK Loader] No active group available while loading group agent: {err}") - if not resolved_group_id: log_event( - "[SK Loader] Group agent selected but no active group in settings.", - level=logging.WARNING + "[SK Loader] Group agent selected from the non-active group.", + level=logging.ERROR ) + load_core_plugins_only(kernel, settings) + return kernel, None + except ValueError as err: + debug_print(f"[SK Loader] No active group available while loading group agent: {err}") + log_event( + "[SK Loader] Group agent selected but no active group in settings.", + level=logging.ERROR + ) + load_core_plugins_only(kernel, settings) + return kernel, None if resolved_group_id: 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 f"[SK Loader] Selected group agent '{selected_agent_data.get('name')}' not found for group {resolved_group_id}.", level=logging.WARNING ) - else: - log_event( - "[SK Loader] Unable to resolve group ID for selected group agent; skipping group agent load.", - level=logging.WARNING - ) # PATCH: Merge global agents if enabled merge_global = settings.get('merge_global_semantic_kernel_with_workspace', False) diff --git a/application/single_app/semantic_kernel_plugins/openapi_plugin_factory.py b/application/single_app/semantic_kernel_plugins/openapi_plugin_factory.py index 3380c208..d2a91477 100644 --- a/application/single_app/semantic_kernel_plugins/openapi_plugin_factory.py +++ b/application/single_app/semantic_kernel_plugins/openapi_plugin_factory.py @@ -12,6 +12,7 @@ import tempfile from typing import Dict, Any, Optional from .openapi_plugin import OpenApiPlugin +from functions_debug import debug_print class OpenApiPluginFactory: @@ -130,10 +131,48 @@ def _extract_auth_config(cls, config: Dict[str, Any]) -> Dict[str, Any]: return {} auth_type = auth_config.get('type', 'none') + debug_print(f"[Factory] auth_type: {auth_type}") if auth_type == 'none': return {} - # Return the auth config as-is since the OpenApiPlugin already handles - # the different auth types + # Check if this is basic auth stored in the 'key' field format + # Simple Chat stores basic auth as: auth.type='key', auth.key='username:password', additionalFields.auth_method='basic' + additional_fields = config.get('additionalFields', {}) + auth_method = additional_fields.get('auth_method', '') + debug_print(f"[Factory] additionalFields.auth_method: {auth_method}") + + if auth_type == 'key' and auth_method == 'basic': + # Extract username and password from the combined key + key = auth_config.get('key', '') + debug_print(f"[Factory] Applying basic auth transformation") + if ':' in key: + username, password = key.split(':', 1) + return { + 'type': 'basic', + 'username': username, + 'password': password + } + else: + # Malformed basic auth key + return {} + + # For bearer tokens stored as 'key' type + if auth_type == 'key' and auth_method == 'bearer': + token = auth_config.get('key', '') + debug_print(f"[Factory] Applying bearer auth transformation") + return { + 'type': 'bearer', + 'token': token + } + + # For OAuth2 stored as 'key' type + if auth_type == 'key' and auth_method == 'oauth2': + debug_print(f"[Factory] Applying OAuth2 auth transformation") + return { + 'type': 'bearer', # OAuth2 tokens are typically bearer tokens + 'token': auth_config.get('key', '') + } + + # Return the auth config as-is for other auth types return auth_config diff --git a/application/single_app/static/js/chat/chat-enhanced-citations.js b/application/single_app/static/js/chat/chat-enhanced-citations.js index 18c75229..dcda708b 100644 --- a/application/single_app/static/js/chat/chat-enhanced-citations.js +++ b/application/single_app/static/js/chat/chat-enhanced-citations.js @@ -15,8 +15,8 @@ export function getFileType(fileName) { const ext = fileName.toLowerCase().split('.').pop(); - const imageExtensions = ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'heif']; - const videoExtensions = ['mp4', 'mov', 'avi', 'mkv', 'flv', 'webm', 'wmv']; + const imageExtensions = ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif']; + const videoExtensions = ['mp4', 'mov', 'avi', 'mkv', 'flv', 'webm', 'wmv', 'm4v', '3gp']; const audioExtensions = ['mp3', 'wav', 'ogg', 'aac', 'flac', 'm4a']; if (imageExtensions.includes(ext)) return 'image'; diff --git a/application/single_app/static/js/group/manage_group.js b/application/single_app/static/js/group/manage_group.js index 2de03e43..a6b00cc4 100644 --- a/application/single_app/static/js/group/manage_group.js +++ b/application/single_app/static/js/group/manage_group.js @@ -473,8 +473,6 @@ function renderMemberActions(member) { } else { return ` `; @@ -546,10 +541,6 @@ function loadPendingRequests() { data-request-id="${u.userId}">Approve - - `; @@ -644,22 +635,15 @@ function searchUsers() { // Render user-search results in add-member modal function renderUserSearchResults(users) { let html = ""; - if (!users || !users.length) { - html = `No results.`; if (!users || !users.length) { html = `No results.`; } else { - users.forEach(u => { users.forEach(u => { html += ` ${u.displayName || "(no name)"} ${u.email || ""} - + + + `; + }); + } + $("#userSearchResultsTable tbody").html(html); +} + +// Populate manual-add fields from search result +function selectUserForAdd(id, name, email) { + $("#newUserId").val(id); + $("#newUserDisplayName").val(name); +// Populate manual-add fields from search result // ❌ DUPLICATE +function selectUserForAdd(id, name, email) { + $("#newUserId").val(id); + $("#newUserDisplayName").val(name); + $("#newUserEmail").val(email); +} +``` + +## Solution + +Removed all duplicate code blocks and restored proper function structure: + +```javascript +function renderUserSearchResults(users) { + let html = ""; + if (!users || !users.length) { + html = `No results.`; + } else { + users.forEach(u => { + html += ` + + ${u.displayName || "(no name)"} + ${u.email || ""} + + + + + `; + }); + } + $("#userSearchResultsTable tbody").html(html); +} + +// Populate manual-add fields from search result +function selectUserForAdd(id, name, email) { + $("#newUserId").val(id); + $("#newUserDisplayName").val(name); + $("#newUserEmail").val(email); +} +``` + +## Impact + +### Before Fix +- ❌ Manage group page completely failed to load +- ❌ "Loading..." screen displayed indefinitely +- ❌ Console error blocked all JavaScript execution +- ❌ No access to group management features +- ❌ Unable to add/remove group members +- ❌ Unable to modify group settings + +### After Fix +- ✅ Manage group page loads successfully +- ✅ User search functionality works correctly +- ✅ Member management operations available +- ✅ All group management features accessible +- ✅ Clean JavaScript execution with no syntax errors + +## Testing + +### Manual Verification +1. Navigate to any group management page +2. Verify page loads without "Loading..." indefinitely +3. Check browser console for absence of syntax errors +4. Test user search functionality +5. Verify member addition/removal operations + +### Browser Console Validation +```javascript +// Before: +// ❌ Uncaught SyntaxError: missing ) after argument list at manage_group.js:673 + +// After: +// ✅ No syntax errors +// ✅ navigation.js:11 Top navigation initialized +// ✅ user-agreement.js:54 [UserAgreement] Manager initialized +``` + +## Files Modified + +- `application/single_app/static/js/group/manage_group.js` (lines 645-680) + +## Related Components + +- Group management UI +- User search functionality +- Member addition workflow +- Group settings interface + +## Prevention + +This type of error typically occurs from: +- Copy-paste mistakes during development +- Incomplete conflict resolution during merge +- Missing code review for duplicated blocks + +**Recommendations:** +1. Use a JavaScript linter (ESLint) to catch syntax errors before deployment +2. Enable pre-commit hooks to validate JavaScript syntax +3. Add automated functional tests for critical page loads +4. Review all merge conflicts carefully for duplicate code blocks + +## References + +- **Fix Documentation:** `MANAGE_GROUP_SYNTAX_ERROR_FIX.md` +- **Component:** Group Management +- **Browser Impact:** All modern browsers (Chrome, Firefox, Edge, Safari) diff --git a/docs/explanation/fixes/v0.237.008/OPENAPI_BASIC_AUTH_FIX.md b/docs/explanation/fixes/v0.237.008/OPENAPI_BASIC_AUTH_FIX.md new file mode 100644 index 00000000..6c7ab4bd --- /dev/null +++ b/docs/explanation/fixes/v0.237.008/OPENAPI_BASIC_AUTH_FIX.md @@ -0,0 +1,206 @@ +# OpenAPI Basic Authentication Fix + +**Fixed/Implemented in version:** **0.237.005** (matches `config.py` `app.config['VERSION']`) + +**Issue:** OpenAPI actions with Basic Authentication fail with "session not authenticated" error +**Root Cause:** Mismatch between authentication format stored by UI and format expected by OpenAPI plugin +**Status:** ✅ Fixed + +--- + +## Problem Description + +When configuring an OpenAPI action with Basic Authentication in the Simple Chat admin interface: + +1. User uploads OpenAPI spec with `securitySchemes.basicAuth` defined +2. User selects "Basic Auth" authentication type +3. User enters username and password in the configuration wizard +4. Action is saved successfully +5. **BUT**: When agent attempts to use the action, authentication fails with error: + ``` + "I'm unable to access your ServiceNow incidents because your session + is not authenticated. Please log in to your ServiceNow instance or + check your authentication credentials." + ``` + +### Symptoms +- ❌ OpenAPI actions with Basic Auth fail despite correct credentials +- ✅ Direct API calls with same credentials work correctly +- ✅ Other Simple Chat features authenticate successfully +- ❌ Error occurs even when Base URL is correctly configured + +--- + +## Root Cause Analysis + +### Authentication Storage Format (Frontend) + +The Simple Chat admin UI (`plugin_modal_stepper.js`, lines 1539-1543) stores Basic Auth credentials as: + +```javascript +auth.type = 'key'; // Basic auth is also 'key' type in the schema +const username = document.getElementById('plugin-auth-basic-username').value.trim(); +const password = document.getElementById('plugin-auth-basic-password').value.trim(); +auth.key = `${username}:${password}`; // Store as combined string +additionalFields.auth_method = 'basic'; +``` + +**Stored format:** +```json +{ + "auth": { + "type": "key", + "key": "username:password" + }, + "additionalFields": { + "auth_method": "basic" + } +} +``` + +### Authentication Expected Format (Backend) + +The OpenAPI plugin (`openapi_plugin.py`, lines 952-955) expects Basic Auth as: + +```python +elif auth_type == "basic": + import base64 + username = self.auth.get("username", "") + password = self.auth.get("password", "") + credentials = base64.b64encode(f"{username}:{password}".encode()).decode() + headers["Authorization"] = f"Basic {credentials}" +``` + +**Expected format:** +```json +{ + "auth": { + "type": "basic", + "username": "actual_username", + "password": "actual_password" + } +} +``` + +### The Mismatch + +❌ **Frontend stores:** `auth.type='key'`, `auth.key='username:password'` +❌ **Backend expects:** `auth.type='basic'`, `auth.username`, `auth.password` +❌ **Result:** Plugin code path for Basic Auth (`elif auth_type == "basic"`) never executes +❌ **Consequence:** No `Authorization` header added, API returns authentication error + +--- + +## Solution Implementation + +### Fix Location +**File:** `application/single_app/semantic_kernel_plugins/openapi_plugin_factory.py` +**Function:** `_extract_auth_config()` +**Lines:** 129-166 + +### Code Changes + +Added authentication format transformation logic to detect and convert Simple Chat's storage format into OpenAPI plugin's expected format: + +```python +@classmethod +def _extract_auth_config(cls, config: Dict[str, Any]) -> Dict[str, Any]: + """Extract authentication configuration from plugin config.""" + auth_config = config.get('auth', {}) + if not auth_config: + return {} + + auth_type = auth_config.get('type', 'none') + + if auth_type == 'none': + return {} + + # Check if this is basic auth stored in the 'key' field format + # Simple Chat stores basic auth as: auth.type='key', auth.key='username:password', + # additionalFields.auth_method='basic' + additional_fields = config.get('additionalFields', {}) + auth_method = additional_fields.get('auth_method', '') + + if auth_type == 'key' and auth_method == 'basic': + # Extract username and password from the combined key + key = auth_config.get('key', '') + if ':' in key: + username, password = key.split(':', 1) + return { + 'type': 'basic', + 'username': username, + 'password': password + } + else: + # Malformed basic auth key + return {} + + # For bearer tokens stored as 'key' type + if auth_type == 'key' and auth_method == 'bearer': + return { + 'type': 'bearer', + 'token': auth_config.get('key', '') + } + + # For OAuth2 stored as 'key' type + if auth_type == 'key' and auth_method == 'oauth2': + return { + 'type': 'bearer', # OAuth2 tokens are typically bearer tokens + 'token': auth_config.get('key', '') + } + + # Return the auth config as-is for other auth types + return auth_config +``` + +### How It Works + +1. **Detection:** Check if `auth.type == 'key'` AND `additionalFields.auth_method == 'basic'` +2. **Extraction:** Split `auth.key` on first `:` to get username and password +3. **Transformation:** Return new dict with `type='basic'`, `username`, and `password` +4. **Pass-through:** OpenAPI plugin receives correct format and adds Authorization header + +### Additional Auth Method Support + +The fix also handles other authentication methods stored in the same format: +- **Bearer tokens:** `auth_method='bearer'` → transforms to `{type: 'bearer', token: ...}` +- **OAuth2:** `auth_method='oauth2'` → transforms to `{type: 'bearer', token: ...}` + +--- + +## Testing + +### Before Fix +```bash +# Test action: servicenow_query_incidents +User: "Show me all incidents in ServiceNow" +Agent: "I'm unable to access your ServiceNow incidents because your + session is not authenticated..." + +# HTTP request (no Authorization header sent): +GET https://YOUR-INSTANCE.service-now.com/api/now/table/incident +# Response: 401 Unauthorized or session expired error +``` + +### After Fix +```bash +# Test action: servicenow_query_incidents +User: "Show me all incidents in ServiceNow" +Agent: "Here are your ServiceNow incidents: ..." + +# HTTP request (Authorization header correctly added): +GET https://YOUR-INSTANCE.service-now.com/api/now/table/incident +Authorization: Basic + +# Response: 200 OK with incident data +``` + +### Validation Steps +1. ✅ Create OpenAPI action with Basic Auth +2. ✅ Enter username and password in admin wizard +3. ✅ Save action successfully +4. ✅ Attach action to agent +5. ✅ Test agent with prompt requiring action +6. ✅ Verify Authorization header is sent +7. ✅ Verify API returns 200 OK with data +8. ✅ Verify agent processes response correctly \ No newline at end of file diff --git a/docs/explanation/release_notes.md b/docs/explanation/release_notes.md index ea5eaa1b..384d44bc 100644 --- a/docs/explanation/release_notes.md +++ b/docs/explanation/release_notes.md @@ -1,6 +1,78 @@ + # Feature Release +### **(v0.237.009)** + +#### New Features + +* **ServiceNow Integration Documentation** + * Comprehensive documentation for integrating ServiceNow with Simple Chat, including step-by-step guides for both Basic Authentication and OAuth 2.0. + * **OAuth 2.0 Setup**: Detailed guide for Resource Owner Password Credential grant type with production security considerations. + * **OpenAPI Specifications**: 7 OpenAPI YAML files for ServiceNow Incident Management and Knowledge Base APIs (both bearer token and basic auth versions). + * **Agent Instructions**: Behavioral instructions optimized for ServiceNow operations (263 lines). + * **Key Features**: Integration user creation, role assignment guidance, token management strategies, troubleshooting guide, and production deployment considerations. + * **Documentation Files**: `SERVICENOW_INTEGRATION.md` (760 lines), `SERVICENOW_OAUTH_SETUP.md` (480+ lines), `servicenow_agent_instructions.txt`, and 7 OpenAPI specs in `docs/how-to/agents/ServiceNow/`. + * (Ref: ServiceNow integration, OAuth 2.0, OpenAPI specifications, enterprise integrations) + +#### Bug Fixes + +* **Workspace Search Deselection KeyError Fix** + * Fixed HTTP 500 error when deselecting the workspace search button after having a document selected. Users would get "Could not get a response. HTTP error! status: 500" in the chat interface. + * **Root Cause**: When workspace search was deselected (`hybrid_search_enabled = False`), the `user_metadata['workspace_search']` dictionary was never initialized. However, subsequent code for handling group scope or public workspace context attempted to access `user_metadata['workspace_search']['group_name']` or other properties, causing a KeyError. + * **Error**: `KeyError: 'workspace_search'` at lines 468, 479 in `route_backend_chats.py` when trying to set group_name or active_public_workspace_id. + * **Solution**: Added defensive checks before accessing `user_metadata['workspace_search']`. If the key doesn't exist, initialize it with `{'search_enabled': False}` before attempting to set additional properties like group_name or workspace IDs. + * **Workaround**: Clicking Home and then back to Chat worked because it triggered a page reload that reset the state properly. + * (Ref: `route_backend_chats.py`, workspace search, metadata initialization, KeyError handling) + +* **OpenAPI Basic Authentication Fix** + * Fixed "session not authenticated" errors when using Basic Authentication with OpenAPI actions, even when credentials were correct. + * **Root Cause**: Mismatch between how the UI stored Basic Auth credentials (as `username:password` string in `auth.key`) and how the OpenAPI plugin factory expected them (as separate `username` and `password` properties in `additionalFields`). + * **Solution**: Modified `OpenApiPluginFactory` to detect and parse `username:password` format from `auth.key`, splitting credentials into separate properties that the authentication middleware expects. + * **Files Modified**: `semantic_kernel_plugins/openapi_plugin_factory.py`. + * (Ref: OpenAPI actions, Basic Authentication, credential parsing, `OPENAPI_BASIC_AUTH_FIX.md`) + +* **Group Action OAuth Schema Merging Fix** + * Fixed HTTP 401 Unauthorized errors when using OAuth bearer token authentication with group actions. When editing group actions, `additionalFields` was empty, missing all authentication configuration. + * **Root Cause**: Group action backend routes did not call `get_merged_plugin_settings()` to merge UI form data with OpenAPI schema defaults, while global action routes did. This caused group actions to be saved without authentication configuration fields like `auth_method`, `base_url`, and authentication credentials. + * **Solution**: Updated group action save/update routes in `route_backend_plugins.py` to call `get_merged_plugin_settings()`, ensuring authentication configuration is properly merged and persisted. + * **Files Modified**: `route_backend_plugins.py`. + * (Ref: Group actions, OAuth authentication, schema merging, `GROUP_ACTION_OAUTH_SCHEMA_MERGING_FIX.md`) + +* **Group Agent Loading Fix** + * Fixed issue where group agents were not appearing in the agent list when per-user semantic kernel mode was enabled. Users selecting group agents would fall back to the global "researcher" agent with zero plugins/actions available. + * **Root Cause**: The `load_user_semantic_kernel()` function only loaded personal agents and global agents (when merge enabled), but completely omitted group agents from groups the user is a member of. + * **Solution**: Updated `load_user_semantic_kernel()` to fetch and load group agents for all groups the user is a member of, ensuring proper agent availability in per-user kernel mode. + * **Files Modified**: `semantic_kernel_loader.py`. + * (Ref: Group agents, per-user semantic kernel, agent loading, `GROUP_AGENT_LOADING_FIX.md`) + +* **Manage Group Page Syntax Error Fix** + * Fixed critical JavaScript syntax error preventing the manage group page from loading. Removed duplicate code blocks including duplicate conditional checks, forEach loops, button tags, and function definitions. + * The page was stuck on "Loading..." indefinitely with console error "Uncaught SyntaxError: missing ) after argument list" at line 673. + * (Ref: `manage_group.js`, duplicate code removal, syntax error resolution) + +* **File Extension Handling Improvements** + * Fixed multiple issues related to file extension handling and audio transcription across the application. + * **Missing MP3 Extension**: Fixed issue where .mp3 files were missing from the list of allowed extensions. Users attempting to upload mp3 files to workspaces saw "Uploaded 0/1, Failed: 1" with no error logging to activity_logs or documents containers. + * **Centralized Extension Definitions**: Resolved file extension variable duplications throughout codebase by centralizing all allowed file extension definitions in `config.py` and importing them in downstream function and route files. This prevents extension lists from going out of sync during updates. + * **Additional Supported Extensions**: Added missing file types supported by Document Intelligence and Video Indexer services: .heic (image), .mpg, .mpeg, .webm (video). + * **Browser-Compatible Extensions**: Adjusted file extensions in `chat-enhanced-citations.js` for proper browser rendering. Removed incompatible formats like .heif and added compatible formats like .3gp after thorough testing. + * (Ref: `config.py`, file extension centralization, enhanced citations rendering) + +* **Audio Transcription Continuous Recognition Fix (MAG)** + * Fixed incomplete audio transcriptions in Azure Government (MAG) environments where transcription stopped at first silence or after 30 seconds of audio. + * **Root Cause**: Previous implementation used `recognize_once()` method which stops transcription at the first silence (end of sentence, speaker pauses) and has a maximum 30-second transcription limit. + * **Solution**: Implemented continuous recognition using `start_continuous_recognition()` method instead of `recognize_once()`, enabling full-length audio file transcription without interruption at natural speech pauses. + * **Impact**: Audio files now transcribe completely regardless of length or natural pauses in speech, improving transcription quality and completeness in MAG regions where Fast Transcription API is unavailable. + * (Ref: Azure Speech Service, continuous recognition, MAG support, audio transcription) + +* **Workspace File Metadata Edit Error Fix** + * Fixed "'tuple' object has no attribute 'get'" error when clicking Save after editing workspace file metadata in personal, group, or public workspaces. + * **Root Cause**: Missing checks and error handling in route backend documents code when processing metadata updates. + * **Solution**: Added additional validation checks and proper handling to `route_backend_documents.py` for all workspace types (personal, group, public). + * **Impact**: Users can now successfully edit and save file metadata without encountering errors. + * (Ref: `route_backend_documents.py`, metadata updates, error handling) + ### **(v0.237.007)** #### Bug Fixes @@ -59,6 +131,10 @@ #### Bug Fixes +* **Azure AI Search Test Connection Fix** + * Fixed test connection functionality for Azure AI Search configuration validation. + * (Ref: Azure AI Search, connection testing, admin configuration, `AZURE_AI_SEARCH_TEST_CONNECTION_FIX.md`) + * **Retention Policy Field Name Fix** * Fixed retention policy to use the correct field name `last_updated` instead of the non-existent `last_activity_at` field. * **Root Cause**: The retention policy query was looking for `last_activity_at` field, but all conversation schemas (legacy and current) use `last_updated` to track the conversation's last modification time. @@ -176,7 +252,7 @@ * **Frontend Integration**: UI can query allowed auth types to display only valid options. * **Files Modified**: `route_backend_plugins.py`. * (Ref: plugin authentication, auth type constraints, OpenAPI plugins, security) - + #### Bug Fixes * **Control Center Chart Date Labels Fix** @@ -735,9 +811,11 @@ * (Ref: `functions_authentication.py`, `functions_documents.py`, Video Indexer workflow logging) ### **(v0.229.014)** + #### Bug Fixes ##### Public Workspace Management Fixes + * **Public Workspace Management Permission Fix** * Fixed incorrect permission checking for public workspace management operations when "Require Membership to Create Public Workspaces" setting was enabled. * **Issue**: Users with legitimate access to manage workspaces (Owner/Admin/DocumentManager) were incorrectly shown "Forbidden" errors when accessing management functionality. @@ -756,7 +834,9 @@ * (Ref: `chat-documents.js`, scope label updates, dynamic workspace display) ======= + ##### User Interface and Content Rendering Fixes + * **Unicode Table Rendering Fix** * Fixed issue where AI-generated tables using Unicode box-drawing characters were not rendering as proper HTML tables in the chat interface. * **Problem**: AI agents (particularly ESAM Agent) generated Unicode tables that appeared as plain text instead of formatted tables. @@ -1088,7 +1168,7 @@ * (Ref: `artifacts/architecture.vsdx`) * **Health Check** * Provide admins ability to enable a healthcheck api. - * (Ref: `route_external_health.py`) + * (Ref: `route_external_health.py`) #### Bug Fixes @@ -1564,9 +1644,9 @@ We introduced a robust user feedback system, expanded content-safety features fo 5. **Inline File Previews in Chat** - Files attached to a conversation can be previewed directly from the chat, with text or data displayed in a pop-up. -7. **Optional Image Generation** +6. **Optional Image Generation** - Users can toggle an “Image” button to create images via Azure OpenAI (e.g., DALL·E) when configured in Admin Settings. -8. **App Roles & Enterprise Application** +7. **App Roles & Enterprise Application** - Provides a robust way to control user access at scale. - Admins can assign roles to new users or entire Azure AD groups. \ No newline at end of file diff --git a/docs/how-to/agents/ServiceNow/SERVICENOW_ASSET_MANAGEMENT_SETUP.md b/docs/how-to/agents/ServiceNow/SERVICENOW_ASSET_MANAGEMENT_SETUP.md new file mode 100644 index 00000000..e99f67f7 --- /dev/null +++ b/docs/how-to/agents/ServiceNow/SERVICENOW_ASSET_MANAGEMENT_SETUP.md @@ -0,0 +1,337 @@ +# ServiceNow Asset Management Agent Setup Guide + +This guide walks through setting up a complete ServiceNow Asset Management agent with separate actions for querying, creating, updating, and deleting assets. + +## Overview + +The Asset Management agent uses **four separate actions** for different operations: +1. **Asset Query and Details** - Search and retrieve asset information +2. **Asset Creation** - Add new assets to ServiceNow +3. **Asset Update** - Modify existing asset records +4. **Asset Deletion** - Remove assets from the system + +## Architecture + +``` +ServiceNow Asset Management Agent +├── Action 1: Query and Get Assets +├── Action 2: Create Assets +├── Action 3: Update Assets +└── Action 4: Delete Assets +``` + +### Scope: Core Asset Records (`alm_asset` table) + +These actions manage **hardware asset records** directly in the `alm_asset` table - the core table containing actual assets (laptops, monitors, phones, servers, etc.). + +**Your actions WILL handle:** +- ✅ Querying existing assets (by tag, model, location, status, assigned user) +- ✅ Creating new asset records directly +- ✅ Updating asset details (assignment, location, status, warranty) +- ✅ Deleting assets from the system + +**Your actions will NOT handle (different tables/workflows):** +- ❌ Asset requests workflow (`ast_request` table) - Formal request/approval process for new assets +- ❌ Transfer orders (`alm_transfer_order` table) - Asset transfers between locations +- ❌ Stock orders/stockroom operations (`alm_stockroom_transfer` table) - Inventory replenishment + +**Note:** To enable asset requests, transfer orders, or stock management capabilities, create additional actions targeting those specific ServiceNow tables. The current setup focuses on direct asset record management, which covers most asset management needs. + +--- + +## Prerequisites: ServiceNow User and Authentication Setup + +### ServiceNow Integration User + +Create a dedicated ServiceNow service account for asset management operations. + +**Username:** `servicenow_asset_manager` +**Required ServiceNow Roles:** +- `itil` - Standard ITIL user access (includes basic asset read/write) +- `asset` - Asset management permissions (create, update, delete assets) +- `rest_api_explorer` - REST API access + +**Optional Enhanced Permissions:** +- `admin` - Full administrative access (only if your organization requires elevated permissions for asset operations) + +**Permissions:** +- Read/Write: `alm_asset` table (asset records) +- Read: `cmdb_model` table (asset models) +- Read: `cmn_location` table (locations) +- Read: `sys_user` table (user assignments) + +### User Creation Steps + +``` +1. Log into ServiceNow as admin +2. Navigate to: User Administration > Users +3. Click "New" +4. Fill in: + - User ID: servicenow_asset_manager + - First name: ServiceNow Asset + - Last name: Manager Service Account + - Email: servicenow-assets@your-domain.com + - Active: ✓ + - Password needs reset: ☐ UNCHECK THIS (important for API access) +5. Click "Submit" +6. Set Password: + - Right-click the header bar > "Set Password" + - Enter a secure password + - Save the password for OAuth token generation +7. Open the user record +8. Go to "Roles" tab +9. Add roles: itil, asset, rest_api_explorer +10. Save +``` + +### Generate OAuth Bearer Token + +> **📘 For complete OAuth token generation instructions, see: [SERVICENOW_OAUTH_SETUP.md](SERVICENOW_OAUTH_SETUP.md)** +> +> The OAuth setup guide provides: +> - OAuth application configuration in ServiceNow +> - Token generation using cURL, PowerShell, and Python +> - Token refresh procedures +> - Troubleshooting common OAuth issues + +**Generate token for user:** `servicenow_asset_manager` + +**Save the access token** - You'll need it when configuring each action in Step 1. + +### Authentication Configuration for Actions + +When creating each action (query, create, update, delete), use the same authentication: + +**Authentication Type:** `key` (Bearer Token) +**Key:** `Bearer YOUR_ACCESS_TOKEN` + +**Important:** All four actions use the **same bearer token** from the `servicenow_asset_manager` user. + +--- + +## Step 1: Create ServiceNow Actions + +### Action 1: Query and Get Assets + +**Action Name:** `servicenow_query_assets` +**Display Name:** `ServiceNow - Query Assets` +**Description:** `Query assets and retrieve asset details from ServiceNow` +**Type:** `openapi` + +**OpenAPI Specification:** See [servicenow_query_assets_openapi.json](servicenow_query_assets_openapi.json) + +**Key Operations:** +- `queryAssets` - Search and filter assets with query parameters +- `getAssetDetails` - Retrieve full details for a specific asset by sys_id + +**Endpoint:** `https://YOUR-INSTANCE.service-now.com/api/now` + +**Authentication:** +- Type: `key` +- Key: `YOUR_BEARER_TOKEN` + +--- + +### Action 2: Create Assets + +**Action Name:** `servicenow_create_asset` +**Display Name:** `ServiceNow - Create Asset` +**Description:** `Create new assets in ServiceNow` +**Type:** `openapi` + +**OpenAPI Specification:** See [servicenow_create_asset_openapi.json](servicenow_create_asset_openapi.json) + +**Key Operation:** +- `createAsset` - Create new asset with required fields (asset_tag, display_name) + +**Required Fields:** +- `asset_tag` - Unique asset identifier +- `display_name` - Display name for the asset + +**Optional Fields:** model, serial_number, assigned_to, location, install_status, purchase_date, warranty_expiration, cost, department, managed_by, owned_by, comments + +**Endpoint:** `https://YOUR-INSTANCE.service-now.com/api/now` + +--- + +### Action 3: Update Assets + +**Action Name:** `servicenow_update_asset` +**Display Name:** `ServiceNow - Update Asset` +**Description:** `Update existing assets in ServiceNow` +**Type:** `openapi` + +**OpenAPI Specification:** See [servicenow_update_asset_openapi.json](servicenow_update_asset_openapi.json) + +**Key Operation:** +- `updateAsset` - Update asset fields using PATCH method + +**⚠️ CRITICAL:** Always query for sys_id first using asset_tag, then update + +**Updatable Fields:** display_name, assigned_to, assignment_group, location, install_status, substatus, serial_number, warranty_expiration, cost, department, managed_by, owned_by, comments + +**Endpoint:** `https://YOUR-INSTANCE.service-now.com/api/now` + +--- + +### Action 4: Delete Assets + +**Action Name:** `servicenow_delete_asset` +**Display Name:** `ServiceNow - Delete Asset` +**Description:** `Delete assets from ServiceNow` +**Type:** `openapi` + +**OpenAPI Specification:** See [servicenow_delete_asset_openapi.json](servicenow_delete_asset_openapi.json) + +**Key Operation:** +- `deleteAsset` - Permanently delete an asset from ServiceNow + +**⚠️ WARNING:** This is a destructive operation. Always confirm with user before deleting. Consider retiring assets (install_status=7) instead of deletion. + +**⚠️ CRITICAL:** Query for sys_id first using asset_tag, then delete + +**Endpoint:** `https://YOUR-INSTANCE.service-now.com/api/now` + +--- + +## Step 2: Create Asset Management Agent + +### Agent Configuration + +**Agent Name:** `servicenow_asset_management` +**Display Name:** `ServiceNow Asset Manager` +**Description:** `AI agent for ServiceNow asset management - query, create, update, and delete assets` +**Model:** `gpt-4o` or `gpt-4.1` + +**Actions to Load:** +- `servicenow_query_assets` +- `servicenow_create_asset` +- `servicenow_update_asset` +- `servicenow_delete_asset` + +### Agent Instructions File + +Upload the agent instructions file: [servicenow_asset_management_agent_instructions.txt](servicenow_asset_management_agent_instructions.txt) + +**Key Instruction Highlights:** +- No-narration execution pattern (execute silently, show results only) +- Critical two-call pattern for updates/deletes (query for sys_id first, then operate) +- Install status mappings (1=In use, 6=In stock, 7=Retired) +- Progressive search for large result sets +- Delete confirmation workflow (show details, wait for confirmation, then delete) +- Required field validation for asset creation +- Formatted response templates (markdown tables, success messages) + +--- + +## Step 3: Testing the Setup + +### Test Queries + +1. **Query assets:** + ``` + Show me all active assets + ``` + +2. **Get asset details:** + ``` + Get details for asset P1000234 + ``` + +3. **Create asset:** + ``` + Create a new laptop asset: + - Tag: P1000500 + - Display Name: Jane's Laptop + - Model: Dell Latitude 5420 + - Assigned to: Jane Smith + - Status: In stock + ``` + +4. **Update asset:** + ``` + Update asset P1000234 to assign it to Bob Johnson + ``` + +5. **Delete asset:** + ``` + Delete asset P1000999 + ``` + +--- + +## Common Issues and Solutions + +### Issue: "Asset not found" when updating + +**Solution:** Always query for sys_id first: +``` +1. queryAssets(sysparm_query="asset_tag=P1000234") +2. Extract sys_id from result +3. updateAsset(sys_id="...", ...) +``` + +### Issue: Create fails with missing fields + +**Solution:** Check required fields: +- `asset_tag` (required) +- `display_name` (required) + +### Issue: Agent narrates instead of executing + +**Solution:** Review agent instructions - ensure "DO NOT narrate" section is prominent. + +--- + +## Advanced Configuration + +### Custom Asset Fields + +If your ServiceNow instance has custom fields, add them to the OpenAPI schema: + +```json +"custom_field_name": { + "type": "string", + "description": "Custom field description" +} +``` + +### Asset Statistics + +Add a statistics operation similar to incident stats: + +```json +"/stats/alm_asset": { + "get": { + "operationId": "getAssetStats", + "description": "Get asset statistics grouped by category, status, etc." + } +} +``` + +--- + +## Files to Create + +1. ✅ Four action manifests in Admin → Actions +2. ✅ Agent manifest in Admin → Agents +3. ✅ Agent instructions file: `servicenow_asset_management_agent_instructions.txt` +4. ✅ This README for reference + +--- + +## Next Steps + +1. Create the four actions in the admin interface +2. Create the asset management agent +3. Link the four actions to the agent +4. Upload the instructions file +5. Test with sample queries +6. Expand instructions based on your specific asset management workflows + +--- + +**Questions or Issues?** + +Refer to the ServiceNow API documentation for additional field definitions and query patterns: +https://developer.servicenow.com/dev.do#!/reference/api/vancouver/rest/c_TableAPI diff --git a/docs/how-to/agents/ServiceNow/SERVICENOW_INTEGRATION.md b/docs/how-to/agents/ServiceNow/SERVICENOW_INTEGRATION.md new file mode 100644 index 00000000..91764088 --- /dev/null +++ b/docs/how-to/agents/ServiceNow/SERVICENOW_INTEGRATION.md @@ -0,0 +1,794 @@ +# ServiceNow Integration Guide + +**Version:** 0.237.005 +**Implemented in version:**0.237.005 + +## Overview + +This guide documents the **single-agent integration** between Simple Chat and ServiceNow, enabling AI-powered incident management and knowledge base search through natural language prompts. + +**This guide covers:** +- ✅ Incident management (create, update, query, statistics) +- ✅ Knowledge base search (read-only access) +- ✅ Single ServiceNow agent for standard support operations + +**For advanced KB management (create/publish articles), see:** +- 📘 [TWO_AGENT_SETUP.md](TWO_AGENT_SETUP.md) - Recommended approach with separate KB Management agent +- 📘 [KB_MULTI_ACTION_SETUP.md](KB_MULTI_ACTION_SETUP.md) - Alternative multi-action approach + +> **⚠️ Important - Work in Progress:** +> This integration is under active development. **Check back regularly for updates** to the OpenAPI specifications and agent instructions. Unit testing of prompts is still in progress, so further changes to the spec files and agent instruction file are expected. + +--- + +## Integration Architecture + +**Approach:** Single-Agent Hybrid Integration +- **ServiceNow OpenAPI Actions** - Modular API integration for CRUD operations +- **ServiceNow Support Agent** - Specialized AI agent for incidents + read-only KB search + +--- + +## Prerequisites + +### Simple Chat Requirements +- ✅ Agents enabled (`enable_semantic_kernel: True`) +- ✅ Workspace Mode enabled (`per_user_semantic_kernel: True`) +- ✅ Global Actions enabled +- ✅ Application restarted after enabling Workspace Mode + +### ServiceNow Requirements +- [ ] ServiceNow Developer Instance (Zurich - Latest release recommended) +- [ ] Integration user with API access +- [ ] API credentials (Basic Auth or OAuth 2.0) + +--- + +## Phase 1: ServiceNow Instance Setup + +### Step 1: Request Developer Instance + +1. Navigate to: https://developer.servicenow.com/ +2. Click "Request an Instance" +3. Select **Zurich (Latest release)** +4. Click "Request" +5. Wait for instance provisioning (typically 2-5 minutes) + +**You'll receive:** +``` +Instance URL: https://devXXXXX.service-now.com +Admin Username: admin +Admin Password: [provided by ServiceNow] +``` + +### Step 2: Create Integration User + +> **Note:** This step demonstrates basic authentication setup for initial testing. For production deployments using Bearer Token authentication, refer to "SERVICENOW_OAUTH_SETUP.md". + +1. Log into your ServiceNow instance as admin +2. Navigate to: **User Administration** → **Users** +3. Click **New** to create integration user: + ``` + Username: simplechat6_servicenow_support_service + First Name: Simple + Last Name: Chat Integration + Email: [your email] + Time Zone: [your timezone] + ``` + +4. Assign Roles: + - Navigate to **Roles** tab + - Add roles: + - `rest_api_explorer` - For REST API access + - `itil` - For incident management (basic create/read/update) + - `knowledge` - For knowledge base read access + + **Optional Role for Enhanced Permissions:** + - `incident_manager` - Add only if you need to: + - Assign incidents to any user/group organization-wide + - Close and resolve any incident (not just your own) + - View all incidents across the organization + - Escalate incidents and modify any incident assignments + - Access incident analytics and reporting + + **Note:** For most AI agent use cases, `itil` + `knowledge` + `rest_api_explorer` is sufficient. + +5. Set Password: + - Click **Set Password** + - Create secure password + - **Password needs reset: ☐ UNCHECK THIS** (important for API access) + - Save for later use + +### Step 3: Test REST API Access + +1. Navigate to: **System Web Services** → **REST** → **REST API Explorer** +2. URL: `https://devXXXXX.service-now.com/$restapi.do` +3. Select API: **Table API** +4. Select Table: **incident** +5. Click **Send** to test query +6. Verify you get JSON response with incident data + +**Example successful response:** +```json +{ + "result": [ + { + "number": "INC0000001", + "short_description": "Test incident", + "state": "1" + } + ] +} +``` + +--- + +## Phase 2: OpenAPI Specification + +### ServiceNow API Endpoints + +The integration uses two OpenAPI specification files that define all ServiceNow REST API operations: + +#### 1. Incident Management API +**Files:** +- **Bearer Token Auth:** `sample_servicenow_incident_api.yaml` (Recommended for production) +- **Basic Auth:** `sample_servicenow_incident_api_basicauth.yaml` (For testing only) + +**Base URL:** `https://devXXXXX.service-now.com/api/now` + +**Endpoints:** +- `GET /table/incident` - Query incidents with filters +- `POST /table/incident` - Create new incident +- `GET /table/incident/{sys_id}` - Get specific incident details +- `PATCH /table/incident/{sys_id}` - Update incident +- `GET /stats/incident` - Get incident statistics and aggregations + +**Operations:** +- `queryIncidents` - Query incidents based on filters (state, priority, date range, etc.) +- `createIncident` - Create new incident with short_description, description, priority, etc. +- `getIncidentDetails` - Retrieve full details of specific incident by sys_id +- `updateIncident` - Update incident fields (state, work_notes, priority, assigned_to, etc.) +- `getIncidentStats` - Get aggregated statistics (count, averages, grouping by fields) + +#### 2. Knowledge Base API +**Files:** +- **Bearer Token Auth:** `sample_now_knowledge_search_spec.yaml` (Recommended for production) +- **Basic Auth:** `sample_now_knowledge_search_spec_basicauth.yaml` (For testing only) + +**Base URL:** `https://devXXXXX.service-now.com` + +**Endpoints:** +- `GET /api/now/table/kb_knowledge` - Search knowledge base articles +- `GET /api/now/table/kb_knowledge/{sys_id}` - Get specific article details + +**Operations:** +- `searchKnowledgeFacets` - Search knowledge articles with progressive fallback strategy +- `getKnowledgeArticle` - Retrieve full content of specific knowledge article + +### OpenAPI Specification Files + +**Locations:** `docs/how-to/agents/ServiceNow/open_api_specs/` + +**Available Authentication Options:** + +#### Bearer Token Authentication (Production) +- `sample_servicenow_incident_api.yaml` - Incident management with OAuth 2.0 bearer token +- `sample_now_knowledge_search_spec.yaml` - Knowledge base search (read-only) with OAuth 2.0 bearer token +- **Use these for:** Production deployments, secure enterprise environments +- **Setup guide:** See `SERVICENOW_OAUTH_SETUP.md` for OAuth configuration + +#### Basic Authentication (Testing Only) +- `sample_servicenow_incident_api_basicauth.yaml` - Incident management with username:password +- `sample_now_knowledge_search_spec_basicauth.yaml` - Knowledge base search (read-only) with username:password +- **Use these for:** Initial testing, development instances, proof of concept +- **Security note:** Not recommended for production use + +**Status:** ✅ Created and configured + +**Key Features:** +- ✅ Both authentication methods supported (bearer token and basic auth) +- ✅ Comprehensive parameter documentation with detailed descriptions +- ✅ Critical usage patterns documented: + - Progressive search strategy (fallback from exact phrase to broad keyword) + - sys_id requirements and query-first patterns for updates + - Field mapping for create/update operations + - Work notes timing considerations (updates may take a few moments to appear) +- ✅ Query examples and common use case patterns +- ✅ Field descriptions, constraints, and validation rules +- ✅ State/priority/urgency enumerations documented +- ✅ Error handling guidance and status codes +- ✅ Pagination and filtering parameter examples +- ✅ ServiceNow-specific query syntax (encoded queries, operators) + +> **⚠️ Important:** These OpenAPI specifications are continuously tested and refined based on real-world use cases, agent behavior analysis, and production feedback. Regular updates ensure optimal AI agent understanding and reliable API interactions. + +--- + +## Phase 3: Simple Chat Configuration + +> **📌 Important - Scope Options:** +> ServiceNow actions and agents can be configured at different levels based on your organization's needs: +> +> - **Global Actions/Agents**: Available to all users across the entire Simple Chat instance +> - **Group Actions/Agents**: Available only to members of specific workspaces/groups +> - **Personal Actions/Agents**: Available only to individual users +> +> Choose the appropriate scope based on your security, governance, and access control requirements. For enterprise deployments, group-level configuration is recommended to control access by department or team. + +### Step 1: Add ServiceNow Actions + +> **Note:** This integration uses **two separate actions** because ServiceNow has distinct API endpoints for incident management and knowledge base operations, each with its own OpenAPI specification file. + +1. Navigate to: **Admin Settings** → **Actions Configuration** +2. Click **"Add Action"** +3. **Select Action Type: OpenAPI** + - ServiceNow REST APIs use OpenAPI/Swagger specifications + - OpenAPI type supports: External API integration, HTTP/HTTPS requests, authentication, JSON payloads + - Click **"Next"** after selecting OpenAPI + +#### Action 1: Incident Management +``` +Name: servicenow_manage_incident +Display Name: ServiceNow - Manage Incidents +Type: OpenAPI +Description: Complete incident management - query, create, update, retrieve details, and get statistics +OpenAPI Spec: [Upload sample_servicenow_incident_api.yaml or sample_servicenow_incident_api_basicauth.yaml] +Base URL: https://devXXXXX.service-now.com + +Operations Included: + - queryIncidents: Query/filter incidents with advanced search + - createIncident: Create new incidents with all fields + - getIncidentDetails: Retrieve full incident details by sys_id + - updateIncident: Update incident state, assignments, work notes, etc. + - getIncidentStats: Get aggregated statistics and metrics + +Authentication Options: + +Option A - Basic Auth (Testing Only): + Auth Type: key + Key: username:password (or use Key Vault reference) + OpenAPI Spec File: sample_servicenow_incident_api_basicauth.yaml + +Option B - OAuth Bearer Token (Recommended for Production): + Auth Type: key + Key: (or use Key Vault reference) + OpenAPI Spec File: sample_servicenow_incident_api.yaml + See: SERVICENOW_OAUTH_SETUP.md for OAuth setup + +Scope: Global or Group +``` + +**Repeat Step 1 for Knowledge Base Action:** + +#### Action 2: Knowledge Base Search (Optional) +``` +Name: servicenow_search_knowledge_base +Display Name: ServiceNow - Search Knowledge Base +Type: OpenAPI +Description: Search knowledge articles with progressive fallback and retrieve full article content +OpenAPI Spec: [Upload sample_now_knowledge_search_spec.yaml or sample_now_knowledge_search_spec_basicauth.yaml] +Base URL: https://devXXXXX.service-now.com + +Operations Included: + - searchKnowledgeFacets: Search KB articles with progressive search strategy + - getKnowledgeArticle: Retrieve complete article content by sys_id + +[Same auth config as above] + +Scope: Global or Group +``` + +> **💡 Tip:** If you only need incident management without knowledge base search, you can skip Action 2 and configure your agent with only the `servicenow_manage_incident` action. + +--- + +## Phase 4: Configure ServiceNow Agent + +### Step 1: Create Agent + +1. Navigate to: **Admin Settings** → **Agents Configuration** +2. Click **"Add Agent"** +3. Configure agent: + +``` +Name: servicenow_support_agent +Display Name: ServiceNow Support Agent +Description: AI agent for ServiceNow incident management and knowledge base operations + +Instructions: [Copy from agent_instructions/servicenow_agent_instructions.txt] + +Model: gpt-4o (or your preferred model) +Scope: Global or Group +``` + +> **📄 Agent Instructions File:** +> - **Location:** `docs/how-to/agents/ServiceNow/agent_instructions/servicenow_agent_instructions.txt` +> - **Purpose:** Comprehensive behavioral instructions for the ServiceNow support agent +> - **Usage:** Copy the entire content from this file into the "Instructions" field when creating the agent +> +> **⚠️ Important:** These instructions are continuously refined and tuned based on real-world use cases, agent behavior analysis, and production feedback. The file serves as a living reference that should be updated as new patterns emerge or edge cases are discovered. Regular review and updates ensure optimal agent performance and reliable ServiceNow interactions. + +### Step 2: Attach Actions to Agent + +1. Edit the ServiceNow Support Agent +2. Navigate to **Actions** tab +3. Select and attach: + - ✅ servicenow_manage_incident + - ✅ servicenow_search_knowledge_base +4. Save agent configuration + +--- + +## Testing the Integration + +### Test 1: Query Incidents +``` +Prompt: "Show me open critical incidents created in the last 7 days" +``` + +Expected: Agent queries incidents with appropriate filters and displays results in table format. + +### Test 2: Create Incident +``` +Prompt: "Create an incident: Email server down for Finance team, priority High, assigned to John Doe" +``` + +Expected: Agent creates incident and returns incident number. + +### Test 3: Update Incident +``` +Prompt: "Update INC0010095 - add work note: Investigating email server logs" +``` + +Expected: Agent queries for sys_id, then updates with work note. + +### Test 4: Search Knowledge Base +``` +Prompt: "Find KB articles about email troubleshooting" +``` + +Expected: Agent searches KB and returns relevant articles with links. + +--- + +## Phase 5: Testing + +### Test Scenarios + +#### Test 1: Query Recent Tickets +**Prompt:** +``` +Show me all incidents created in the last 7 days +``` + +**Expected Behavior:** +- Agent uses servicenow_query_incidents action +- Filters by created_date >= 7 days ago +- Returns formatted table with results + +**Status:** [ ] Tested + +--- + +#### Test 2: Create New Ticket +**Prompt:** +``` +Create a new incident: +- Description: Email server not responding for Finance department +- Urgency: High +- Priority: 2 +- Category: Email +``` + +**Expected Behavior:** +- Agent confirms parameters +- Uses servicenow_create_incident action +- Returns new incident number (e.g., INC0010001) + +**Status:** [ ] Tested + +--- + +#### Test 3: Trend Analysis +**Prompt:** +``` +What are the top 5 trending issues over the last 30 days? +Show incident counts for each category. +``` + +**Expected Behavior:** +- Agent queries incidents from last 30 days +- Groups by category +- Counts incidents per category +- Returns top 5 in table format + +**Status:** [ ] Tested + +--- + +#### Test 4: Support Team Analytics +**Prompt:** +``` +Who is the most active support person in the last 30 days? +Show number of tickets resolved and average resolution time. +``` + +**Expected Behavior:** +- Agent queries incidents with resolved status +- Groups by assigned_to +- Calculates counts and averages +- Returns ranked list + +**Status:** [ ] Tested + +--- + +#### Test 5: Predictive Analysis +**Prompt:** +``` +Analyze resolution entries over the last year and identify patterns. +What are the most common types of outages? +``` + +**Expected Behavior:** +- Agent queries historical data (1 year) +- Analyzes resolution notes and categories +- Identifies recurring patterns +- Provides recommendations + +**Status:** [ ] Tested + +--- + +## Security Best Practices + +### Credential Management + +**Option 1: Direct Password Entry (Quick Setup)** +- Enter ServiceNow password directly in action configuration +- Stored encrypted in Cosmos DB +- ⚠️ Less secure for production use + +**Option 2: Azure Key Vault (Recommended)** +1. Store ServiceNow credentials in Azure Key Vault +2. Create secret: `servicenow-integration-password` +3. Reference in action config: `@keyvault:servicenow-integration-password` +4. Simple Chat automatically retrieves from Key Vault + +**Status:** [ ] Credentials secured + +### API User Permissions + +**Least Privilege Principle:** +- [ ] Integration user has only required roles +- [ ] Read-only access for query actions +- [ ] Write access only for create/update actions +- [ ] No admin privileges + +### Audit Logging + +**Enable in ServiceNow:** +1. Navigate to **System Logs** → **System Log** → **REST Messages** +2. Enable logging for API calls +3. Monitor for unusual activity + +**Status:** [ ] Audit logging configured + +--- + +## Troubleshooting + +### Common Issues + +#### Issue: "Session not authenticated" or "Session expired" error + +**Status:** ✅ **FIXED in version 0.235.026** + +**Symptoms:** +- Agent responds: "I'm unable to access your ServiceNow incidents because your session is not authenticated" +- Direct API calls with same credentials work correctly +- Base URL is configured correctly + +**Root Cause:** +This issue was caused by a mismatch between how the Simple Chat UI stores Basic Auth credentials (as `username:password` in a single field) and how the OpenAPI plugin expected them (as separate `username` and `password` fields). + +**Solution:** +The fix is included in Simple Chat v0.235.026+. The OpenAPI plugin factory now automatically transforms the authentication format when loading actions, so no user action is required. + +**For detailed technical information, see:** `docs/explanation/fixes/OPENAPI_BASIC_AUTH_FIX.md` + +#### Issue: "Authentication failed" error +**Solution:** +- Verify username (simplechat6_servicenow_support_service) and password are correct +- Check integration user is active +- Confirm user has `rest_api_explorer` role +- Test credentials in REST API Explorer first +- Ensure Base URL is correct: `https://devXXXXX.service-now.com/api/now` + +#### Issue: "No results returned" for queries +**Solution:** +- Check date filters are correct +- Verify table name is correct (incident, not incidents) +- Test query in ServiceNow REST API Explorer +- Check sysparm_query encoding + +#### Issue: Agent not using ServiceNow actions +**Solution:** +- Verify actions are attached to agent +- Check actions are saved as "Global" scope +- Restart application after configuration changes +- Review agent instructions for clarity + +#### Issue: "Rate limit exceeded" error +**Solution:** +- ServiceNow limits API calls per hour +- Developer instances: ~10,000 calls/hour +- Add delays between bulk operations +- Implement retry logic with exponential backoff + +--- + +## Next Steps + +### Completed +- [x] Understand integration approach +- [x] Choose ServiceNow instance (Zurich) + +### In Progress +- [ ] Request ServiceNow developer instance +- [ ] Create integration user +- [ ] Test REST API access + +### To Do +- [ ] Create OpenAPI specification +- [ ] Add ServiceNow actions in Simple Chat +- [ ] Create ServiceNow support agent +- [ ] Test all use cases +- [ ] Secure credentials with Key Vault +- [ ] Deploy to production + +--- + +## Resources + +### ServiceNow Documentation +- REST API Reference: https://developer.servicenow.com/dev.do#!/reference/api/latest/rest +- Table API Guide: https://docs.servicenow.com/bundle/latest/page/integrate/inbound-rest/concept/c_TableAPI.html +- Developer Portal: https://developer.servicenow.com/ + +### Simple Chat Documentation +- Actions Configuration: `docs/admin_configuration.md` +- Agent Creation: `docs/features.md` +- API Integration: `docs/explanation/features/` + +--- + +## Appendix + +### ServiceNow Query Syntax Examples + +**Last 7 days:** +``` +sysparm_query=sys_created_onONLast 7 days@javascript:gs.daysAgoStart(7) +``` + +**By priority:** +``` +sysparm_query=priority=1 +``` + +**By state (resolved):** +``` +sysparm_query=state=6 +``` + +**Combined filters:** +``` +sysparm_query=priority=1^state=1^sys_created_onONLast 30 days@javascript:gs.daysAgoStart(30) +``` + +### Sample Prompts for ServiceNow Actions + +Use these prompts with the ServiceNow Support Agent to test and demonstrate functionality: + +#### Query Incidents (servicenow_query_incidents) + +**Basic queries:** +- "Show me all open incidents" +- "List incidents created in the last 7 days" +- "What incidents are currently in progress?" +- "Show me all critical priority incidents" +- "Find all incidents assigned to the Finance department" + +**Advanced queries:** +- "Show me high priority incidents from last month that are still unresolved" +- "List all email-related incidents created in the last 2 weeks" +- "What incidents were opened yesterday with priority 1 or 2?" +- "Find all network incidents assigned to IT Support team" +- "Show me the most recent 20 incidents sorted by creation date" + +**Analytics and trends:** +- "What are the top 10 most common incident categories this month?" +- "How many incidents were created each day last week?" +- "Show me incident volume by priority for the last 30 days" +- "What's the average resolution time for critical incidents?" +- "Which category has the most unresolved incidents?" + +#### Create Incident (servicenow_create_incident) + +**Simple creation:** +- "Create a new incident: Email server is down for Marketing team" +- "Log a ticket: Users can't access the VPN, high urgency" +- "Open an incident for printer not working in conference room A" + +**Detailed creation:** +- "Create a critical incident: Database server crashed, all users affected, need immediate attention" +- "Log a new ticket with the following details: + - Description: Password reset portal showing error 500 + - Priority: High + - Category: Security + - Urgency: High + - Impact: Medium" + +**Template-based:** +- "Create an email server outage incident with high priority" +- "Open a standard network connectivity ticket for Building 2, Floor 3" +- "Log a hardware failure incident for laptop replacement" + +#### Get Incident Details (servicenow_get_incident) + +**By incident number:** +- "Show me details for incident INC0010001" +- "What's the status of ticket INC0010025?" +- "Get full details for incident INC0000157" +- "Show me the complete information for INC0010010" + +**Follow-up queries:** +- "What's the current status of the email server incident we created earlier?" +- "Show me all the work notes for incident INC0010005" +- "Has incident INC0010015 been assigned to anyone yet?" +- "When was INC0010020 last updated?" + +#### Update Incident (servicenow_update_incident) + +**Status updates:** +- "Mark incident INC0010001 as resolved" +- "Update INC0010025 status to In Progress" +- "Close incident INC0010005 with resolution: Issue resolved by restarting service" +- "Put incident INC0010010 on hold" + +**Assignment updates:** +- "Assign incident INC0010001 to John Smith" +- "Reassign INC0010025 to the Network Support team" +- "Change the assigned user for INC0010005" + +**Work notes:** +- "Add work note to INC0010001: Investigating email server logs, found connection timeout" +- "Update INC0010025 with note: Contacted vendor for support" +- "Add comment to INC0010010: Waiting for user response" + +**Priority changes:** +- "Increase priority of INC0010001 to Critical" +- "Lower the urgency of INC0010025 to Medium" +- "Change INC0010005 priority to 2" + +#### Get Statistics (servicenow_get_stats) + +**Volume metrics:** +- "How many incidents were created last month?" +- "What's the total incident count by category for this year?" +- "Show me incident volume trends for the last 6 months" +- "How many critical incidents were opened this week?" + +**Performance metrics:** +- "What's the average resolution time for incidents last month?" +- "Show me the mean time to resolve by category" +- "What percentage of incidents are resolved within SLA?" +- "Calculate the average time to first response" + +**Team analytics:** +- "Show me incident counts by assigned user for last 30 days" +- "Which support team resolved the most incidents this quarter?" +- "What's the workload distribution across support groups?" +- "Who has the fastest average resolution time?" + +**Categorical analysis:** +- "Break down incident counts by priority for last month" +- "Show me the distribution of incidents by state" +- "What categories have the highest incident volume?" +- "Compare email vs network incident counts for Q4" + +#### Search Knowledge Base (servicenow_search_kb) + +**Solution searches:** +- "Search the knowledge base for email configuration guides" +- "Find articles about VPN connection troubleshooting" +- "Look up password reset procedures in the KB" +- "Search for solutions to 'server not responding' errors" + +**Category searches:** +- "Show me all knowledge articles in the Email category" +- "Find network troubleshooting guides" +- "List all hardware setup articles" +- "Show me security-related KB articles" + +**Problem-specific:** +- "Find KB articles about printer connectivity issues" +- "Search for documentation on how to reset user passwords" +- "Look up articles about 'Error 500' messages" +- "Find guides for setting up mobile email access" + +**Recent/popular:** +- "What are the most viewed knowledge articles this month?" +- "Show me recently updated KB articles" +- "Find the top 10 most helpful articles" +- "List new knowledge articles from the last 30 days" + +#### Complex Multi-Action Workflows + +**Incident creation with KB lookup:** +- "Users are reporting email server issues. Search the knowledge base for solutions and if none exist, create a new incident." + +**Trend analysis with knowledge suggestions:** +- "What are the top 5 recurring issues this month? For each, suggest relevant knowledge articles." + +**Incident lifecycle:** +- "Show me all unresolved incidents from last week. For those older than 5 days, add a work note asking for status update." + +**Support quality check:** +- "Find all incidents closed yesterday. Check if resolution notes reference knowledge articles. Report which ones are missing KB references." + +**Proactive support:** +- "Analyze incidents from the last 90 days. Identify the top 3 issues that don't have knowledge articles, and suggest creating documentation for them." + +#### Natural Language Queries (Advanced Agent Capabilities) + +- "I need help with the laptop that won't connect to WiFi" + - Agent creates incident with user's details + - Searches KB for WiFi troubleshooting + - Provides step-by-step guide + - Tracks incident until resolved + +- "Show me everything related to the email outage last Tuesday" + - Agent queries incidents from that date + - Filters by email category + - Shows timeline of events + - Provides resolution summary + +- "Create a monthly support report for my manager" + - Agent gathers statistics for last month + - Calculates key metrics (volume, resolution time, SLA) + - Identifies trends and patterns + - Formats professional summary + +- "What's our biggest support challenge right now?" + - Agent analyzes recent incident data + - Identifies high-volume categories + - Calculates resolution times + - Highlights recurring problems + - Suggests improvements + +--- + +**Tip:** Start with simple queries to verify actions are working, then progress to more complex multi-action workflows. The ServiceNow Support Agent can combine multiple actions intelligently based on your natural language requests. + +### Useful ServiceNow Fields + +**Incident Table Fields:** +- `number` - Incident number (INC0000001) +- `short_description` - Brief title +- `description` - Detailed description +- `priority` - 1-5 (1=Critical, 5=Planning) +- `urgency` - 1-3 (1=High, 3=Low) +- `state` - 1=New, 2=In Progress, 6=Resolved, 7=Closed +- `assigned_to` - Assigned user +- `category` - Incident category +- `sys_created_on` - Created timestamp +- `sys_updated_on` - Updated timestamp +- `resolved_at` - Resolution timestamp +- `sys_id` - Unique identifier + +--- + +**Last Updated:** January 21, 2026 +**Status:** Initial Draft - In Progress diff --git a/docs/how-to/agents/ServiceNow/SERVICENOW_OAUTH_SETUP.md b/docs/how-to/agents/ServiceNow/SERVICENOW_OAUTH_SETUP.md new file mode 100644 index 00000000..923788b8 --- /dev/null +++ b/docs/how-to/agents/ServiceNow/SERVICENOW_OAUTH_SETUP.md @@ -0,0 +1,522 @@ +# ServiceNow OAuth 2.0 Setup for Simple Chat + +**Version:** 0.237.005 +**Implemented in version:** 0.237.005 + +## Overview +This guide shows you how to configure OAuth 2.0 bearer token authentication for ServiceNow integration with Simple Chat using the **modern "New Inbound Integration Experience"** method. This is more secure than Basic Auth and recommended for production environments. + +> **Note:** This guide uses the current ServiceNow OAuth configuration method. The deprecated "Create an OAuth API endpoint for external clients" method is no longer recommended. + +## Prerequisites +- ServiceNow instance (Developer or Production) +- Admin access to ServiceNow +- **ServiceNow integration user** with appropriate roles (e.g., `itil`, `incident_manager`) + - **Best Practice**: Create a dedicated user (e.g., `simplechat_integration`) instead of using a personal account + - This user's permissions determine what the OAuth token can access +- Existing Simple Chat ServiceNow action (or create new one) + +--- + +## Part 1: Configure OAuth in ServiceNow + +### Step 1: Create ServiceNow Integration User (Recommended) + +Before creating the OAuth application, create a dedicated integration user: + +1. **Navigate to User Administration:** + - In ServiceNow, search for: **"Users"** in the left navigation filter + - Click: **System Security > Users** + +2. **Create New User:** + - Click **"New"** + - Fill in the form: + ``` + User ID: simplechat_integration + First name: Simple Chat + Last name: Integration + Email: your-email@example.com + Password: [Set a strong password - save this!] + ``` + +3. **Assign Roles:** + - Click on the **Roles** tab + - Add appropriate roles based on your Simple Chat use case: + - `itil` - Read access to ITIL tables (incidents, problems, changes) + - `incident_manager` - Create and update incidents + - `knowledge` - Read knowledge base articles + - **Security Best Practice**: Grant **only the minimum roles** needed for Simple Chat operations + +4. **Activate and Save:** + - Check the **"Active"** checkbox + - Click **"Submit"** + +5. **Save These Credentials (you'll need them in Step 3):** + ``` + ServiceNow Integration User + Username: simplechat_integration + Password: [the password you set] + ``` + +> **Why Create a Dedicated User?** +> - ✅ **Security**: Limit blast radius if credentials are compromised +> - ✅ **Audit Trail**: Clear visibility in ServiceNow logs (shows "simplechat_integration" performed actions) +> - ✅ **Permission Control**: Grant only the specific roles needed, not your full admin rights +> - ✅ **Lifecycle Management**: Can deactivate or rotate credentials without affecting personal accounts + +--- + +### Step 2: Create OAuth Application + +1. **Log in to your ServiceNow instance** as an admin + - URL: `https://devnnnnnn.service-now.com` + +2. **Navigate to OAuth Application Registry:** + ``` + System OAuth > Application Registry + ``` + Or search for "OAuth" in the navigation filter + +3. **Create New OAuth Integration:** + - Click **New** + - Select **"New Inbound Integration Experience"** (recommended for external clients) + - ⚠️ **Do NOT use** the deprecated "Create an OAuth API endpoint for external clients" + +4. **Select OAuth Grant Type:** + + ServiceNow will present you with several OAuth grant type options: + + **For this POC, select: "OAuth - Resource owner password credential grant"** + + > **📋 Why This Grant Type for POC:** + > - ✅ **Trusted application scenario**: Simple Chat is a trusted first-party application on your Azure infrastructure + > - ✅ **User context preserved**: Actions execute with the **integration user's permissions** and audit trail + > - Token request requires: **OAuth app credentials** (Client ID/Secret) + **ServiceNow user credentials** (Username/Password) + > - ServiceNow issues token **on behalf of that specific user** + > - All API calls execute with that user's roles, ACLs, and permissions + > - Audit logs show the integration user's name, not just "OAuth app" + > - ✅ **Simple token management**: Easy to obtain and refresh tokens programmatically + > - ✅ **Development/testing friendly**: Works well for POC without complex OAuth flows + > - ✅ **Server-to-server integration**: Simple Chat backend directly requests tokens using credentials + + > **⚠️ IMPORTANT - Review Grant Type for Production:** + > + > The OAuth grant type should be **revisited based on your customer's security requirements** and deployment scenario: + > + > | Grant Type | Best For | Use When | + > |------------|----------|----------| + > | **Resource Owner Password** | Trusted apps, POC/Dev | App is first-party, trusted infrastructure, need user context | + > | **Client Credentials** | Machine-to-machine | No user context needed, service account only | + > | **Authorization Code** | Third-party apps | Interactive user consent required, multi-tenant scenarios | + > | **JWT Bearer** | Advanced scenarios | Token exchange, federated identity, microservices | + > + > **Production Considerations:** + > - If customer requires **no password storage**, use Authorization Code grant with PKCE + > - If customer requires **service account only**, use Client Credentials grant + > - If customer has **strict OAuth compliance**, avoid Resource Owner Password grant (considered legacy by some standards) + > - If integrating with **external identity providers**, use JWT Bearer or Authorization Code grant + > + > Always align the grant type choice with your customer's security policies and compliance requirements. + +5. **Configure the Integration Form:** + + ServiceNow presents a "New record" form with several sections. Configure as follows: + + **Details Section:** + ``` + Name: Simple Chat Integration + Provider name: Azure app service (auto-filled) + Client ID: (auto-generated - COPY THIS!) + Client secret: (auto-generated - COPY THIS IMMEDIATELY!) + Comments: OAuth integration for Simple Chat AI assistant + Active: ☑ Checked + ``` + + **Auth Scope Section:** + ``` + Auth scope: useraccount (default) + Limit authorization to following APIs: (leave empty for POC) + ``` + > ⚠️ The "useraccount" scope grants access to all resources available to the signed-in user. This is acceptable for POC with a dedicated integration user account. For production, consider creating custom scopes to limit access to only required APIs. + + **Advanced Options (optional):** + ``` + Enforce token restriction: ☐ Unchecked (for POC) + Token Format: Opaque (default) + Access token lifespan (seconds): 3600 (1 hour - recommended for POC) + Refresh token lifespan (seconds): 86400 (24 hours - recommended for POC) + ``` + > **Note:** ServiceNow defaults to 1800 seconds (30 min) for access tokens, which is too short for testing. Change to longer based on your needs or the dev/testing duration. + +6. **⚠️ CRITICAL - Copy Credentials BEFORE Saving:** + + **Before clicking "Save", you MUST copy these values:** + + 1. **Client ID:** Visible in plain text (e.g., `565d53a80dfe4cb89b8869fd1d977308`) + - Select and copy the entire value + + 2. **Client Secret:** Hidden behind dots + - Click the 👁️ (eye icon) to reveal, OR + - Click the 📋 (copy icon) to copy directly + - **This may only be shown once - copy it now!** + + **Save these values securely** - paste them into a text file or password manager immediately. + + Example format to save: + ``` + ServiceNow OAuth Credentials + Instance: https://devnnnnnn.service-now.com + Client ID: 565d53a... + Client Secret: [paste the revealed secret here] + Token Endpoint: https://devnnnnnn.service-now.com/oauth_token.do + Username: + ``` + +7. **Click "Save"** + +8. **Note the token endpoint:** + - Token endpoint: `https://devnnnnnn.service-now.com/oauth_token.do` + +--- + +### Step 3: Obtain Access Token + +You have two options to get an access token. + +> **Important:** The token request requires **BOTH**: +> - **OAuth App Credentials**: `client_id` and `client_secret` (from Step 1) +> - **ServiceNow User Credentials**: `username` and `password` (integration user you created) +> +> The resulting token will execute API calls **as that integration user** with their specific roles and permissions. + +#### **Option A: Using REST Client (Postman/cURL/PowerShell)** + +**Using cURL:** +```bash +curl -X POST https://devnnnnnn.service-now.com/oauth_token.do \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=password" \ + -d "client_id=YOUR_CLIENT_ID" \ # OAuth app Client ID + -d "client_secret=YOUR_CLIENT_SECRET" \ # OAuth app Client Secret + -d "username=YOUR_USERNAME" \ # ServiceNow integration user + -d "password=YOUR_PASSWORD" # Integration user's password +``` + +**Using PowerShell (recommended for Windows):** +```powershell +$response = Invoke-RestMethod -Uri "https://devnnnnnn.service-now.com/oauth_token.do" ` + -Method Post ` + -ContentType "application/x-www-form-urlencoded" ` + -Body @{ + grant_type="password" + client_id="YOUR_CLIENT_ID" # OAuth app Client ID + client_secret="YOUR_CLIENT_SECRET" # OAuth app Client Secret + username="YOUR_USERNAME" # ServiceNow integration user + password="YOUR_PASSWORD" # Integration user's password + } + +# Display the response +$response | ConvertTo-Json -Depth 10 +``` + +**Response:** +```json +{ + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...", + "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGc...", + "scope": "useraccount", + "token_type": "Bearer", + "expires_in": 31536000 +} +``` + +#### **Option B: Using Python Script** + +Create `get_servicenow_token.py`: +```python +#!/usr/bin/env python3 +""" +Get ServiceNow OAuth access token for Simple Chat integration. + +Requires BOTH: +- OAuth App credentials (Client ID/Secret from ServiceNow OAuth registry) +- ServiceNow integration user credentials (Username/Password) + +The token will execute API calls as the integration user with their permissions. +""" + +import requests +import json + +# ServiceNow OAuth App credentials (from Step 1 - OAuth registry) +SERVICENOW_INSTANCE = "https://devnnnnnn.service-now.com" +CLIENT_ID = "YOUR_CLIENT_ID" # From OAuth Application Registry +CLIENT_SECRET = "YOUR_CLIENT_SECRET" # From OAuth Application Registry + +# ServiceNow integration user credentials (dedicated user with specific roles) +USERNAME = "YOUR_USERNAME" # e.g., simplechat_integration +PASSWORD = "YOUR_PASSWORD" # Integration user's password + +def get_access_token(): + """Get OAuth access token from ServiceNow.""" + url = f"{SERVICENOW_INSTANCE}/oauth_token.do" + + data = { + 'grant_type': 'password', + 'client_id': CLIENT_ID, + 'client_secret': CLIENT_SECRET, + 'username': USERNAME, + 'password': PASSWORD + } + + response = requests.post(url, data=data) + + if response.status_code == 200: + token_data = response.json() + print("✅ Access Token obtained successfully!") + print(f"\nAccess Token: {token_data['access_token']}") + print(f"Expires in: {token_data['expires_in']} seconds") + print(f"Token Type: {token_data['token_type']}") + + # Save to file + with open('servicenow_token.json', 'w') as f: + json.dump(token_data, f, indent=2) + print("\n📁 Token saved to servicenow_token.json") + + return token_data['access_token'] + else: + print(f"❌ Failed to get token: {response.status_code}") + print(response.text) + return None + +if __name__ == "__main__": + get_access_token() +``` + +Run the script: +```bash +python get_servicenow_token.py +``` + +--- + +## Part 2: Configure Action in Simple Chat + +### Step 1: Navigate to Actions Configuration + +1. **Navigate to Actions page:** + - Go to **Settings** > **Actions** (Global) or **Group Settings** > **Actions** (Group-specific) + +2. **Edit your ServiceNow action** or click **"Create New Action"** + +### Step 2: Upload OpenAPI Specification + +1. **Action Details:** + ``` + Name: ServiceNow Query Incidents (or your preferred name) + Display Name: ServiceNow - Query Incident + Description: Query ServiceNow incidents with filters + ``` + +2. **OpenAPI Specification:** + - Upload your `servicenow_incident_api.yaml` file + - Or paste the OpenAPI spec content directly + +3. **Base URL:** + ``` + https://devnnnnnn.service-now.com/api/now + ``` + *(Replace devnnnnnn with your actual ServiceNow instance)* + +### Step 3: Configure Bearer Token Authentication + +In the **Authentication Configuration** section: + +1. **Select Authentication Type:** + - From the **"Type"** dropdown, select: **Bearer Token** + +2. **Enter Token:** + - Paste your access token in the **"Token"** field + ``` + YOUR_ACCESS_TOKEN_FROM_PART_1_STEP_2 + ``` + *(Use the actual token obtained from Part 1, Step 2)* + +3. **Save the Action** + +**That's it!** Simple Chat will automatically: +- Add `Authorization: Bearer YOUR_TOKEN` header to all requests +- Handle the token properly for ServiceNow API authentication + +> **Production Considerations:** +> +> For production deployments, consider the following: +> +> 1. **Secure Token Storage**: Store the OAuth token in Azure Key Vault rather than directly in the action configuration +> - Enables centralized secret management and rotation +> - Provides audit logging for secret access +> - Allows token updates without modifying Simple Chat configuration +> +> 2. **Token Expiration Management**: OAuth tokens have limited lifespans (typically 1-8 hours) +> - **Monitor token expiration**: Set up alerts before tokens expire +> - **Implement token refresh**: Use the refresh token to obtain new access tokens automatically +> - **Automated renewal**: Consider creating an Azure Function or scheduled task to refresh tokens periodically +> - See the "Token Refresh Strategy" section below for implementation options +> +> 3. **Graceful Failure Handling**: Implement monitoring to detect authentication failures due to expired tokens + +--- + +## Part 3: Testing + +### Test with Simple Chat Agent + +1. **Open Simple Chat** and select your ServiceNow agent +2. **Test query:** + ``` + Show me recent incidents + ``` + +3. **Check logs** for successful authentication: + ``` + Added bearer auth: eyJ0eXAi... + Authorization: Bearer eyJ0eXAi... + ``` + +### Test with Curl + +```bash +curl -X GET \ + "https://devnnnnnn.service-now.com/api/now/table/incident?sysparm_limit=5" \ + -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ + -H "Accept: application/json" +``` + +--- + +## Token Refresh Strategy + +OAuth tokens expire. Here are your options: + +### Option 1: Manual Refresh (Simple) +- Set calendar reminder before token expires +- Run `get_servicenow_token.py` script +- Update Key Vault secret +- Simple Chat will use new token automatically + +### Option 2: Automatic Refresh (Advanced) +Create a scheduled task/Azure Function to refresh tokens: + +```python +# refresh_servicenow_token.py +import requests +from azure.keyvault.secrets import SecretClient +from azure.identity import DefaultAzureCredential + +def refresh_token(): + # Get refresh token from Key Vault + credential = DefaultAzureCredential() + client = SecretClient(vault_url="https://your-vault.vault.azure.net", credential=credential) + refresh_token = client.get_secret("servicenow-refresh-token").value + + # Request new access token + response = requests.post( + "https://devnnnnnn.service-now.com/oauth_token.do", + data={ + 'grant_type': 'refresh_token', + 'refresh_token': refresh_token, + 'client_id': CLIENT_ID, + 'client_secret': CLIENT_SECRET + } + ) + + if response.status_code == 200: + new_token = response.json()['access_token'] + + # Update Key Vault + client.set_secret("servicenow-oauth-token", new_token) + print("✅ Token refreshed successfully!") + else: + print(f"❌ Refresh failed: {response.text}") + +if __name__ == "__main__": + refresh_token() +``` + +Schedule with Azure Function (Timer Trigger): +``` +Trigger: Every 50 minutes (before 1-hour expiration) +``` + +### Option 3: Long-Lived Tokens +Configure longer token lifespans in ServiceNow: +``` +Access Token Lifespan: 28800 (8 hours) +Refresh Token Lifespan: 604800 (7 days) +``` + +--- + +## Security Best Practices + +### ✅ DO: +- Store tokens in Azure Key Vault +- Use HTTPS for all requests +- Set appropriate token expiration times +- Rotate tokens regularly +- Use refresh tokens to avoid storing passwords +- Monitor token usage in ServiceNow + +### ❌ DON'T: +- Hardcode tokens in code +- Share tokens between environments +- Use overly long token lifespans +- Commit tokens to source control +- Use the same credentials for dev and prod + +--- + +## Comparison: Basic Auth vs OAuth Bearer + +| Aspect | Basic Auth | OAuth Bearer Token | +|--------|------------|-------------------| +| **Security** | Lower (credentials in every request) | Higher (token-based, expirable) | +| **Setup** | Simple | Moderate complexity | +| **Token Expiration** | None | Configurable (1-8 hours) | +| **Rotation** | Manual password change | Automatic with refresh tokens | +| **Audit Trail** | Username-based | Token-based (better tracking) | +| **Revocation** | Change password (affects all) | Revoke individual tokens | +| **Best For** | Development/Testing | Production environments | + +--- + +## Troubleshooting + +### Error: "invalid_client" +- Verify Client ID and Client Secret are correct +- Check OAuth application is active in ServiceNow + +### Error: "invalid_grant" +- Check username and password are correct +- Verify user has necessary roles in ServiceNow + +### Error: 401 Unauthorized with Bearer Token +- Token may have expired - refresh it +- Verify token is being sent correctly: `Authorization: Bearer TOKEN` +- Check token wasn't truncated when copying + +--- + +## Next Steps + +1. **Complete OAuth setup** in ServiceNow +2. **Get initial access token** using script or Postman +3. **Store token in Key Vault** (recommended) +4. **Update action configuration** in Simple Chat +5. **Test with agent** to verify authentication works +6. **Set up token refresh** strategy (manual or automated) + +## Related Documentation +- [ServiceNow OAuth Documentation](https://docs.servicenow.com/bundle/xanadu-platform-security/page/administer/security/concept/c_OAuthApplications.html) +- [Simple Chat OpenAPI Basic Auth Fix](./explanation/fixes/OPENAPI_BASIC_AUTH_FIX.md) +- [ServiceNow Integration Guide](./SERVICENOW_INTEGRATION.md) diff --git a/docs/how-to/agents/ServiceNow/TWO_AGENT_SETUP.md b/docs/how-to/agents/ServiceNow/TWO_AGENT_SETUP.md new file mode 100644 index 00000000..96ce09e3 --- /dev/null +++ b/docs/how-to/agents/ServiceNow/TWO_AGENT_SETUP.md @@ -0,0 +1,603 @@ +# ServiceNow Two-Agent Setup Guide (Advanced KB Management) + +**Version:** 0.237.005 +**Implemented in version:** 0.237.005 + +## Overview + +This guide describes the **advanced two-agent architecture** for organizations that need full knowledge base management capabilities in addition to standard incident management. + +**Use this approach when you need:** +- ✅ Separate permissions for KB creation and publishing +- ✅ Different ServiceNow roles for support staff vs. KB managers +- ✅ Ability to import external articles to ServiceNow KB +- ✅ Workflow-based KB article approval (draft → review → published) + +**For simpler single-agent setup (incidents + KB search only), see:** +- 📘 [SERVICENOW_INTEGRATION.md](SERVICENOW_INTEGRATION.md) - Recommended for most users + +--- + +## Architecture + +This setup uses **two ServiceNow agents** with different permission levels: + +1. **ServiceNow Support Agent** - Incident management + KB search (read-only) +2. **ServiceNow KB Management Agent** - Full KB operations (search, create, publish) + +Each agent uses a **separate ServiceNow integration user** with appropriate role assignments and **separate OAuth bearer tokens**. + +--- + +## Agent 1: ServiceNow Support Agent + +### Purpose +Primary agent for incident management and KB article searches. + +### Capabilities +- ✅ Create, update, query incidents +- ✅ Get incident statistics +- ✅ Search KB articles (read-only) +- ✅ View KB article details +- ❌ Cannot create KB articles +- ❌ Cannot publish KB articles + +### ServiceNow Integration User + +**Username:** `servicenow_support_service` +**Required ServiceNow Roles:** +- `itil` - Standard ITIL user access (basic incident read/write) +- `knowledge` - KB article read access +- `rest_api_explorer` - REST API access + +**Optional Role for Enhanced Permissions:** +- `incident_manager` - Adds ability to: + - Assign incidents to any user/group + - Close and resolve any incident + - View all incidents across the organization + - Escalate incidents + - Modify incident assignments and ownership + - Access incident analytics and reporting + +**Note:** For API-based incident operations, `itil` role is usually sufficient. Add `incident_manager` only if the agent needs to manage incidents assigned to other users. + +**Permissions:** +- Read/Write: `incident` table +- Read: `kb_knowledge` table (published articles only) +- Read: `kb_category` table +- Read: `kb_knowledge_base` table + +### Authentication +- **Type:** Bearer Token (OAuth 2.0) +- **Token Endpoint:** `https://YOUR-INSTANCE.service-now.com/oauth_token.do` +- **Grant Type:** Resource Owner Password Credentials + +### Actions Configuration + +**Action 1: Manage Incidents** +- OpenAPI Spec: `sample_servicenow_incident_api.yaml` +- Operations: queryIncidents, createIncident, getIncidentDetails, updateIncident, getIncidentStats +- Uses: `servicenow_support_service` credentials + +**Action 2: Search Knowledge Base** +- OpenAPI Spec: `sample_now_knowledge_search_spec.yaml` +- Operations: searchKnowledgeFacets, getKnowledgeArticle +- Uses: `servicenow_support_service` credentials + +### Agent Instructions +- File: `servicenow_agent_instructions.txt` +- Location: `docs/how-to/agents/ServiceNow/` + +### Available To +- All users (standard support staff) +- Primary agent for incident handling + +--- + +## Agent 2: ServiceNow KB Management Agent + +### Purpose +Specialized agent for knowledge base article management. + +### Capabilities +- ✅ Search KB articles +- ✅ View KB article details +- ✅ Create new KB articles from external URLs +- ✅ Publish draft articles +- ✅ Update existing articles +- ✅ Retire outdated articles +- ❌ Cannot manage incidents (use Support Agent for that) + +### ServiceNow Integration User + +**Username:** `simplechat6_servicenow_kb_manager` +**Required ServiceNow Roles:** +- `itil` - Standard ITIL user access +- `knowledge` - KB article contributor access +- `knowledge_manager` - Full KB management permissions (create, publish, retire) +- `knowledge_admin` - Elevated permissions to bypass ACL constraints for approval workflow +- `rest_api_explorer` - REST API access + +**Why Both knowledge_manager AND knowledge_admin?** +- `knowledge_manager` alone allows KB management but may be blocked by ACL (separation of duties) +- `knowledge_admin` provides elevated permissions for KB operations +- **Note:** When approval workflow is enabled, publishing still requires manual portal interaction (see troubleshooting section) + +**Permissions:** +- Read: `kb_knowledge` table (all states: draft, review, published, retired) +- Create: `kb_knowledge` table +- Update: `kb_knowledge` table (including workflow_state changes) +- Read: `kb_category` table +- Read: `kb_knowledge_base` table + +### Authentication +- **Type:** Bearer Token (OAuth 2.0) - **SEPARATE TOKEN** +- **Token Endpoint:** `https://YOUR-INSTANCE.service-now.com/oauth_token.do` +- **Grant Type:** Resource Owner Password Credentials + +### Actions Configuration + +**Action 1: Search Knowledge Base** +- OpenAPI Spec: `sample_now_knowledge_search_spec.yaml` +- Operations: searchKnowledgeFacets, getKnowledgeArticle +- Uses: `simplechat6_servicenow_kb_manager` credentials + +**Action 2: Create Knowledge Articles** +- OpenAPI Spec: `sample_now_knowledge_create_spec.yaml` +- Operations: createKnowledgeArticle +- Uses: `simplechat6_servicenow_kb_manager` credentials + +**Action 3: Publish Knowledge Articles** +- OpenAPI Spec: `sample_now_knowledge_publish_spec.yaml` +- Operations: updateKnowledgeArticle +- Uses: `simplechat6_servicenow_kb_manager` credentials + +**Plugin: SmartHttpPlugin** (globally enabled by default) +- Enabled via: Admin Settings → "Enable HTTP Action" (enabled by default) +- Operation: get_web_content +- Used to fetch content from external URLs before creating KB articles +- No separate action configuration needed - available to all agents when globally enabled + +### Agent Instructions +- File: `servicenow_kb_management_agent_instructions.txt` +- Location: `docs/how-to/agents/ServiceNow/` + +### Available To +- Knowledge managers only +- Users who need to create and publish KB articles + +--- + +## Setup Steps + +### Step 1: Create ServiceNow Integration Users + +#### User 1: Support Agent Service Account + +``` +1. Log into ServiceNow as admin +2. Navigate to: User Administration > Users +3. Click "New" +4. Fill in: + - User ID: servicenow_support_service + - First name: ServiceNow Support + - Last name: Service Account + - Email: servicenow-support@your-domain.com + - Active: ✓ +5. Click "Submit" +6. Open the user record +7. Go to "Roles" tab +8. Add roles: itil, incident_manager +9. Save +``` + +#### User 2: KB Manager Service Account + +``` +1. Log into ServiceNow as admin +2. Navigate to: User Administration > Users +3. Click "New" +4. Fill in: + - User ID: simplechat6_servicenow_kb_manager + - First name: ServiceNow KB Manager + - Last name: Service Account + - Email: servicenow-kb@your-domain.com + - Active: ✓ + - Password needs reset: ☐ UNCHECK THIS (important for API access) +5. Click "Submit" +6. Set Password: + - Right-click the header bar > "Set Password" + - Enter a secure password + - Save the password for OAuth token generation +7. Open the user record +8. Go to "Roles" tab +9. Add roles: knowledge_manager, knowledge_admin, knowledge, itil, rest_api_explorer +10. Save + +**Note:** The `knowledge_admin` role is required in addition to `knowledge_manager` to bypass ACL constraints and enable fully automated approval workflow. +``` + +--- + +### Step 2: Generate OAuth Tokens + +> **📘 For detailed OAuth token generation instructions, see: [SERVICENOW_OAUTH_SETUP.md](SERVICENOW_OAUTH_SETUP.md)** +> +> The OAuth setup guide provides: +> - Complete OAuth application configuration in ServiceNow +> - Token generation using cURL, PowerShell, and Python +> - Token refresh procedures +> - Troubleshooting common OAuth issues + +#### Token 1: Support Agent + +Generate OAuth token for `servicenow_support_service` user: +- **Username:** servicenow_support_service +- **Client ID:** Your OAuth application client ID +- **Client Secret:** Your OAuth application client secret +- **Password:** Service account password + +**Save the access token** - You'll need it for the Support Agent action configuration. + +#### Token 2: KB Manager + +Generate OAuth token for `simplechat6_servicenow_kb_manager` user: +- **Username:** simplechat6_servicenow_kb_manager +- **Client ID:** Your OAuth application client ID +- **Client Secret:** Your OAuth application client secret +- **Password:** Service account password + +**Save the access token** - You'll need it for the KB Manager Agent action configuration. + +--- + +### Step 3: Configure Agent 1 (ServiceNow Support Agent) + +#### In Simple Chat Admin: + +``` +1. Navigate to: Admin > Personal Agents +2. Click "Create New Agent" +3. Fill in: + - Name: ServiceNow Support Agent + - Display Name: ServiceNow Support + - Description: Incident management and KB article searches + - Instructions: [Upload servicenow_agent_instructions.txt] + +4. Add Action 1: + - Name: Manage Incidents + - Type: OpenAPI Plugin + - Upload Spec: sample_servicenow_incident_api.yaml + - Authentication Type: Bearer Token + - Token: TOKEN_FOR_SUPPORT_AGENT + +5. Add Action 2: + - Name: Search Knowledge Base + - Type: OpenAPI Plugin + - Upload Spec: sample_now_knowledge_search_spec.yaml + - Authentication Type: Bearer Token + - Token: TOKEN_FOR_SUPPORT_AGENT + +6. Model: gpt-4o (recommended) +7. Availability: All Users +8. Save +``` + +--- + +### Step 4: Configure Agent 2 (ServiceNow KB Management Agent) + +#### In Simple Chat Admin: + +``` +1. Navigate to: Admin > Personal Agents +2. Click "Create New Agent" +3. Fill in: + - Name: ServiceNow KB Management Agent + - Display Name: ServiceNow KB Manager + - Description: Create and publish knowledge base articles from external URLs or direct content, search KB articles, and manage article lifecycle + - Instructions: [Upload servicenow_kb_management_agent_instructions.txt] + +4. Add Action 1 - Search Knowledge Base: + - Name: Search Knowledge Base + - Description: Search and retrieve knowledge base articles + - Type: OpenAPI Plugin + - Upload Spec: sample_now_knowledge_search_spec.yaml + - Authentication Type: Bearer Token + - Token: TOKEN_FOR_KB_MANAGER + +5. Add Action 2 - Create Knowledge Articles: + - Name: Create Knowledge Articles + - Description: Create new draft knowledge base articles from external content + - Type: OpenAPI Plugin + - Upload Spec: sample_now_knowledge_create_spec.yaml + - Authentication Type: Bearer Token + - Token: TOKEN_FOR_KB_MANAGER + +6. Add Action 3 - Publish Knowledge Articles: + - Name: Publish Knowledge Articles + - Description: Update and publish knowledge articles to make them visible to end users + - Type: OpenAPI Plugin + - Upload Spec: sample_now_knowledge_publish_spec.yaml + - Authentication Type: Bearer Token + - Token: TOKEN_FOR_KB_MANAGER + +7. Verify SmartHttpPlugin is enabled: + - Navigate to: Admin Settings → Plugins + - Ensure "Enable HTTP Action" is checked (enabled by default) + - This provides the get_web_content() function for fetching external URLs + +8. Model: gpt-4o (recommended) +9. Availability: Knowledge Managers Only (configure user groups) +10. Save +``` + +--- + +## Usage Examples + +### Example 1: Support Agent - Search KB and Create Incident + +**User to Support Agent:** +``` +"Search for email configuration KB articles and create an incident +for user John Doe who can't configure Outlook" +``` + +**Agent Executes:** +1. searchKnowledgeFacets(query="email configuration") +2. createIncident(description="Email configuration issue", assigned_to="John Doe") + +**Agent Response:** +``` +Found 3 KB articles about email configuration: + +KB0011 - Configure email settings in Microsoft 365 +https://YOUR-INSTANCE.service-now.com/now/knowledge-center/kb_view/kb_knowledge/abc123 + +✅ Incident INC0012345 created successfully. + +Priority: 3 - Moderate +Assigned to: John Doe +Category: Email +Status: New + +Related KB articles have been added to the incident notes. +``` + +--- + +### Example 2: KB Manager - Create and Publish Article + +**User to KB Manager:** +``` +"Add this Microsoft article to ServiceNow KB: +https://support.microsoft.com/en-us/office/configure-email-settings" +``` + +**Agent Executes:** +1. SmartHttpPlugin.get_web_content(url="https://support.microsoft.com...") +2. createKnowledgeArticle(content=extracted, workflow_state="draft") + +**Agent Response:** +``` +✅ Knowledge article KB0001234 created successfully in draft state. + +**Article Details:** +- Title: Configure email settings in Microsoft 365 +- Knowledge Base: IT +- Category: Operating Systems +- Type: How-to guide +- Source: https://support.microsoft.com/en-us/office/configure-email-settings +- Content: 5,247 characters + +**Review & Publish:** +https://YOUR-INSTANCE.service-now.com/now/knowledge-center/kb_view/kb_knowledge/abc123xyz789 + +📝 The article is in draft state. To publish: +1. Click the link above to open the article +2. Click the "Publish" button in ServiceNow portal +3. An approval request will be created for the designated approver +4. Once approved, the article will be published automatically +``` + +--- + +### Example 3: KB Manager - Publish Existing Draft + +**User to KB Manager:** +``` +"Publish KB article KB0001234" +``` + +**Agent Executes:** +1. searchKnowledgeFacets(query="number=KB0001234") +2. getKnowledgeArticle(sys_id="abc123") - verify current state + +**Agent Response:** +``` +KB article KB0001234 is ready for publication, but requires manual submission via the ServiceNow portal. + +**Why Manual Submission Required:** +Your ServiceNow instance has the 'Knowledge - Approval Publish' workflow enabled, which requires using the portal's Publish button to trigger the approval process. The API cannot initiate this workflow directly. + +**To Submit for Approval:** +1. Go to the article: https://YOUR-INSTANCE.service-now.com/now/knowledge-center/kb_view/kb_knowledge/abc123xyz789 +2. Click the "Publish" button (top right of the page) +3. The article will move from Draft → Review and create an approval request + +**What Happens Next:** +- Workflow state changes to "Review" +- An approval request is created for a designated approver +- The article waits for manual approval before publication + +Once you've clicked Publish, let me know and I can verify the article is in review state. +``` + +--- + +## Security Model + +### Permission Separation + +| Operation | Support Agent | KB Manager | +|-----------|---------------|------------| +| Search KB articles | ✅ Yes | ✅ Yes | +| View KB article details | ✅ Yes | ✅ Yes | +| Create incidents | ✅ Yes | ❌ No | +| Update incidents | ✅ Yes | ❌ No | +| Create KB articles | ❌ No | ✅ Yes | +| Publish KB articles | ❌ No | ✅ Yes | +| Update KB articles | ❌ No | ✅ Yes | +| Retire KB articles | ❌ No | ✅ Yes | + +### Why Two Separate Integration Users? + +1. **Least Privilege:** Each agent has only the permissions it needs +2. **Audit Trail:** Different service accounts show who did what in ServiceNow audit logs +3. **Security:** Support Agent cannot accidentally publish unapproved KB articles +4. **Compliance:** Enforces approval workflow (create draft → manager publishes) +5. **Token Isolation:** If one token is compromised, other agent remains secure + +--- + +## Troubleshooting + +### Problem: Support Agent Cannot Search KB Articles + +**Symptoms:** 401 Unauthorized or 403 Forbidden when searching KB + +**Solution:** +1. Verify `servicenow_support_service` has `itil` role in ServiceNow +2. Check bearer token is valid and not expired +3. Verify `sample_now_knowledge_search_spec.yaml` base URL matches your instance + +--- + +### Problem: KB Manager Cannot Publish Articles + +**Symptoms:** 403 Forbidden when calling updateKnowledgeArticle + +**Solution:** +1. Verify `simplechat6_servicenow_kb_manager` has `knowledge_manager` role +2. Check that separate bearer token is configured (not using same token as Support Agent) +3. Verify article exists and is in valid state for publishing + +--- + +### Problem: KB Article Shows Draft After Agent Reports Published + +**Symptoms:** +- Agent reports "✅ Knowledge article KB0010004 published successfully" +- ServiceNow portal still shows workflow_state = "Draft" +- Article not visible to end users + +**Root Cause:** ServiceNow Knowledge Base has approval workflows enabled + +**Solution:** + +Your ServiceNow instance requires approval workflow: **Draft → Review → Published** + +**Check if Approval Workflow is Enabled:** +1. Go to: Knowledge → Administration → Knowledge Bases +2. Open your KB (e.g., "IT") +3. Check fields: + - **Publish flow** (e.g., "Knowledge - Approval Publish") + - **Retire flow** (e.g., "Knowledge - Approval Retire") +4. If these are populated, approval workflow is required + +**Fix Options:** + +**Option 1: Use Portal-Based Publishing (Required when workflow is enabled)** +- The agent instructions (v0.237.005+) detect approval workflow and guide users accordingly +- Agent creates article in draft state via API ✓ +- User clicks "Publish" button in ServiceNow portal (workflow requirement) +- Article moves to "Review" state and creates approval request +- Designated approver approves in portal +- Article automatically published once approved +- This is the ONLY workflow that works when "Knowledge - Approval Publish" workflow is enabled + +**Option 2: Disable approval workflow (Non-production only)** +- Go to: Knowledge → Administration → Knowledge Bases +- Clear the "Publish flow" and "Retire flow" fields +- Allows direct API publishing (draft → published) +- Only recommended for development/testing environments + +**Related Files:** +- Fix documentation: `docs/explanation/fixes/SERVICENOW_KB_APPROVAL_WORKFLOW_FIX.md` +- Agent instructions: `servicenow_kb_management_agent_instructions.txt` + +--- + +### Problem: External URL Content Not Fetching + +**Symptoms:** "Unable to fetch content from URL" error + +**Solution:** +1. Verify SmartHttpPlugin action is configured for KB Manager agent +2. Check URL is accessible from Simple Chat server +3. Try simpler URL first (e.g., public Microsoft docs) +4. Check for firewall or proxy blocking external requests + +--- + +## Maintenance + +### Token Refresh + +Bearer tokens expire after a period (typically 1 hour). + +> **📘 For token refresh procedures, see: [SERVICENOW_OAUTH_SETUP.md](SERVICENOW_OAUTH_SETUP.md)** + +After obtaining a new token, update it in the agent action configuration in Simple Chat. + +### Role Updates + +If ServiceNow roles change: +1. Update integration user roles in ServiceNow +2. Regenerate OAuth token +3. Update token in Simple Chat agent configuration +4. Test agent operations + +--- + +## Files Reference + +### Agent Instructions +- `servicenow_agent_instructions.txt` - Support Agent (incidents + KB search) +- `servicenow_kb_management_agent_instructions.txt` - KB Manager (full KB operations) + +### OpenAPI Specs +- `sample_servicenow_incident_api.yaml` - Incident management operations +- `sample_now_knowledge_search_spec.yaml` - KB search (read-only) +- `sample_now_knowledge_search_spec.yaml` - KB search operations +- `sample_now_knowledge_create_spec.yaml` - KB creation operations +- `sample_now_knowledge_publish_spec.yaml` - KB publish/update operations + +### Documentation +- `KB_MULTI_ACTION_SETUP.md` - Original multi-action approach (reference) +- `TWO_AGENT_SETUP.md` - This file (recommended approach) + +--- + +## Benefits of Two-Agent Approach + +1. ✅ **Clear Separation:** Each agent has distinct purpose +2. ✅ **Better UX:** Users choose appropriate agent for their task +3. ✅ **Security:** Permission enforcement at ServiceNow API level +4. ✅ **Audit Trail:** Different service accounts for different operations +5. ✅ **Flexibility:** Easy to add more agents with different permission levels +6. ✅ **Scalability:** Can create specialized agents for other ServiceNow tables +7. ✅ **Token Security:** Separate tokens limit blast radius if one is compromised + +--- + +## Next Steps + +1. ✅ Create two ServiceNow integration users +2. ✅ Generate separate OAuth tokens +3. ✅ Configure Support Agent with incident + KB search actions +4. ✅ Configure KB Manager Agent with full KB management actions +5. ✅ Test both agents with sample scenarios +6. ✅ Train users on which agent to use for which tasks +7. ✅ Monitor ServiceNow audit logs for service account activity diff --git a/docs/how-to/agents/ServiceNow/agent_instructions/servicenow_agent_instructions.txt b/docs/how-to/agents/ServiceNow/agent_instructions/servicenow_agent_instructions.txt new file mode 100644 index 00000000..fca6ba58 --- /dev/null +++ b/docs/how-to/agents/ServiceNow/agent_instructions/servicenow_agent_instructions.txt @@ -0,0 +1,295 @@ +You are a ServiceNow support specialist with direct API access. Execute actions and show results immediately without narration. + +**CRITICAL: DO NOT narrate what you're doing. Just execute and show data.** + +❌ NEVER say: +- "I will query..." +- "Generating report..." +- "Gathering statistics..." +- "Proceeding with..." +- "Let me check..." +- "I'll analyze..." + +✅ INSTEAD: Execute the action silently and return only the results. + +**You have these operations - use them immediately:** + +From the ServiceNow incident management action (Manage Incidents): +- queryIncidents - Query/filter incidents with advanced search +- createIncident - Create new incidents with all fields +- getIncidentDetails - Retrieve full incident details by sys_id +- updateIncident - Update incident state, assignments, work notes, etc. +- getIncidentStats - Get aggregated statistics and metrics + +From the ServiceNow knowledge base search action (Search Knowledge Base): +- searchKnowledgeFacets - Search KB articles with progressive search +- getKnowledgeArticle - Retrieve complete article content by sys_id +- READ-ONLY access - cannot create or publish articles +- For KB creation/publishing, use the dedicated KB Management agent + +**Error Handling:** +- If an action fails, report the error clearly without technical jargon +- For KB search failures: Continue with the main task and note KB articles are unavailable +- Do NOT let one failed action block the entire response +- Example: If KB search fails while analyzing incidents, still show the incident analysis +- Example good error message: "Knowledge articles unavailable at this time. Contact your ServiceNow admin if this persists." + +**Execution Pattern:** + +For READ operations (queries, stats, KB search): +1. Execute action immediately (no announcement) +2. Return formatted results +3. Add brief analysis if requested + +For WRITE operations (create, update): +1. Confirm required parameters if missing +2. Execute after confirmation +3. Return success message with details + +**CRITICAL - Field Mapping for Create/Update Operations:** +When creating or updating incidents, **ALWAYS include ALL fields specified by the user** in the API payload: + +**CRITICAL - Category Field:** +- If user says "Category: Email", you MUST pass EXACTLY: `"category": "Email"` in the JSON request body +- If user says "Category: Network", you MUST pass EXACTLY: `"category": "Network"` in the JSON request body +- NEVER omit the category field when user specifies it +- NEVER substitute a different category value +- ServiceNow will default to "Inquiry / Help" if category field is missing or empty + +**Field Mapping Rules:** +- If user says "Priority: 2", MUST pass `priority: "2"` in the request body +- If user says "assigned to Vivien Chen", MUST pass `assigned_to: "Vivien Chen"` in the request body +- DO NOT skip optional fields that user explicitly requested + +**Required ServiceNow API Field Names:** +- Category → `"category"` (string, e.g., "Email", "Network", "Hardware") +- Priority → `"priority"` (string: "1" to "5") +- State → `"state"` (string: "1" to "8") +- Urgency → `"urgency"` (string: "1" to "3") +- Assigned To → `"assigned_to"` (string: user name or sys_id) +- Assignment Group → `"assignment_group"` (string: group name or sys_id) +- Short Description → `"short_description"` (string, required) +- Description → `"description"` (string: detailed notes) + +**Example Create Incident Payload:** +```json +{ + "short_description": "Email server not responding", + "description": "Users cannot access Outlook", + "category": "Email", + "priority": "2", + "urgency": "1", + "assigned_to": "Vivien Chen" +} +``` + +**Response Format Examples:** + +Query request: "Show open critical incidents" +❌ Bad: "I'll query critical incidents with state=1 and priority=1..." +✅ Good: [Execute query] "Here are 5 open critical incidents: + +| Number | Priority | Description | Created | +|--------|----------|-------------|---------| +| INC001 | Critical | Email down | Jan 22 | +..." + +Statistics request: "How many incidents last month?" +❌ Bad: "Let me gather statistics for December 2025..." +✅ Good: [Execute stats] "December 2025 Incident Statistics: + +- Total incidents: 127 +- Critical: 15 (12%) +- High: 42 (33%) +- Medium: 70 (55%) + +Top categories: +1. Email (45 incidents) +2. Network (32 incidents) +3. Hardware (28 incidents)" + +Monthly report request: "Create monthly support report" +❌ Bad: "Generating monthly report... Gathering statistics... Proceeding with analysis..." +✅ Good: [Execute query for last month] "December 2025 Support Report: + +**Incident Volume:** +- Total: 127 incidents +- Critical: 15 | High: 42 | Medium: 70 + +**Top Issues:** +| Category | Count | % of Total | +|----------|-------|------------| +| Email | 45 | 35% | +| Network | 32 | 25% | +| Hardware | 28 | 22% | + +**Resolution Metrics:** +- Avg resolution time: 4.2 hours +- Same-day resolution: 78% + +**Recommendations:** +- Email issues increasing 15% vs November +- Consider additional email server monitoring +- Network incidents clustering Tue/Thu - investigate" + +**Formatting Rules:** +- Use markdown tables for lists of incidents +- Include: incident number, priority, state, description, dates +- Show metrics as bullet points or small tables +- Add brief recommendations when doing analysis + +**CRITICAL - Display ALL Records User Requests:** +- When user says "show 100 records", display ALL 100 rows in the table (not just a preview) +- When user says "top 200", display ALL 200 rows in the table +- DO NOT truncate tables to "first 10" or add "..." for more +- If user doesn't specify a number, default to showing 10 records only +- For very large requests (500+), you may summarize instead of full table - ask user first + +**CRITICAL - API Parameter Requirements:** +- **ALWAYS include sysparm_limit parameter** in queryIncidents action +- ServiceNow API default is 100 results if sysparm_limit is not specified +- When user says "return 100 records", you MUST pass sysparm_limit=100 +- When user says "top 200", you MUST pass sysparm_limit=200 +- When user says "top 10", you MUST pass sysparm_limit=10 +- When user doesn't specify, use sysparm_limit=10 (NOT the API default of 100) +- Maximum allowed: sysparm_limit=10000 + +**CRITICAL - Incident Updates with sys_id:** +⚠️ YOU MUST QUERY FOR sys_id FIRST - DO NOT USE CACHED VALUES ⚠️ + +When user asks to update incident INC0010095: + +CALL 1 - Query to Get sys_id: +1. Call queryIncidents(sysparm_query="number=INC0010095", sysparm_fields="sys_id,number") +2. Extract sys_id from result: e.g., "32ac0eaec326361067d91a2ed40131a7" +3. Verify you got exactly 1 result + +CALL 2 - Update with Retrieved sys_id: +1. Call updateIncident(sys_id="32ac0eaec326361067d91a2ed40131a7", work_notes="...") +2. This will succeed because you have the correct sys_id + +WHY THIS MATTERS: +- Wrong sys_id = HTTP 404 "Record doesn't exist" error +- You cannot remember or cache sys_id from previous queries +- Each incident update MUST start with fresh queryIncidents call + +DO NOT: +❌ Use sys_id from memory or previous conversation +❌ Assume sys_id stays the same across conversations +❌ Skip the queryIncidents call - always query first +❌ Use sys_id from a different incident + +**Date/Time Understanding:** + +CRITICAL: Understand the difference between incident STATE and incident DATE: + +❌ WRONG Interpretation: +- "Show open incidents today" → Query for state=New AND created_on=today + - This is WRONG because "open" refers to STATE, not creation date + +✅ CORRECT Interpretation: +- "Show open incidents today" → Query for state=1 (New) OR state=2 (In Progress) + - Shows all incidents currently in open states, regardless of creation date + +- "Show incidents created today" → Query for sys_created_on=today + - Shows all incidents created today, regardless of current state + +**Common date/time queries:** +- "open incidents" = state=1 or state=2 (New or In Progress) +- "closed incidents" = state=7 (Closed) or state=6 (Resolved) +- "incidents created today" = sys_created_on >= start of today +- "incidents created last 7 days" = sys_created_on >= 7 days ago +- "resolved incidents today" = state=6 AND resolved_at >= start of today +- "open incidents created yesterday" = (state=1 OR state=2) AND sys_created_on between yesterday start/end + +**State values:** +- 1 = New +- 2 = In Progress +- 3 = On Hold +- 6 = Resolved +- 7 = Closed +- 8 = Canceled + +**Query examples:** +``` +"Show me top 200 open incidents" +→ Execute with: state IN (1,2), sysparm_limit=200 +→ Return all 200 in table, no commentary about "more available" + +"Show open incidents created today" +→ Execute with: (state=1 OR state=2) AND sys_created_on >= today +→ This shows NEW/IN PROGRESS incidents that were CREATED today + +"Show incidents resolved today" +→ Execute with: state=6 AND resolved_at >= today +→ This shows incidents that reached RESOLVED state today +``` + +**CRITICAL - Knowledge Base Progressive Search:** +⚠️ PROGRESSIVE SEARCH REQUIRES MULTIPLE FUNCTION CALLS ⚠️ + +You MUST make TWO separate searchKnowledgeFacets calls when first search returns 0 results: + +CALL 1 - Try Exact Phrase: +1. Call searchKnowledgeFacets(sysparm_query="textLIKEemail delivery troubleshooting") +2. Check if result count = 0 +3. If count > 0: Return results, DONE ✓ +4. If count = 0: Proceed to CALL 2 (do not give up!) + +CALL 2 - Broad Keyword Fallback: +1. Extract primary keyword from phrase (e.g., "email" from "email delivery troubleshooting") +2. Call searchKnowledgeFacets(sysparm_query="textLIKEemail") <-- NEW FUNCTION CALL +3. Return whatever results are found (likely 5+ articles) + +WHY THIS MATTERS: +- Exact phrase "email delivery troubleshooting" = 0 results (no article has this exact wording) +- Broad keyword "email" = 5+ results (KB0000011, KB0000024, KB0000028, etc.) +- You MUST make the second function call when first returns 0 + +DO NOT: +❌ Give up after first search returns 0 +❌ Say "no articles found" without trying broad keyword +❌ Use complex OR queries in first attempt - keep it simple + +KEYWORD EXTRACTION: +- "email delivery troubleshooting" → primary keyword: "email" +- "spam filter blocking" → primary keyword: "spam" +- "network connectivity issues" → primary keyword: "network" + +**Work Notes Timing:** +- Work notes updates may take a few moments to appear in subsequent queries +- Do not be alarmed if work_notes field appears empty immediately after update +- The update was successful - ServiceNow processes journal entries asynchronously + +**CRITICAL - Knowledge Base Article URL Formatting:** +⚠️ ALWAYS USE THE MODERN SERVICENOW KB URL FORMAT ⚠️ + +When displaying KB article references in incident details, comments, or responses: + +DO NOT use legacy URL format: +❌ https://servicenow.com/kb_view.do?sys_id=ARTICLE_SYS_ID +❌ https://INSTANCE.service-now.com/kb_view.do?sys_id=ARTICLE_SYS_ID + +ALWAYS use modern Knowledge Center URL format: +✅ https://INSTANCE.service-now.com/now/knowledge-center/kb_view/kb_knowledge/ARTICLE_SYS_ID + +Where: +- INSTANCE = Your ServiceNow instance name (e.g., devnnnnnn, yourcompany, prod123) +- ARTICLE_SYS_ID = The sys_id of the KB article (e.g., 3b0785718703210deddb882a2e3ec20) + +**Example:** +If incident INC0010119 references KB article KB0000017 with sys_id 3b0785718703210deddb882a2e3ec20 on instance devnnnnnn: + +CORRECT URL: +https://devnnnnnn.service-now.com/now/knowledge-center/kb_view/kb_knowledge/3b0785718703210deddb882a2e3ec20 + +DISPLAY FORMAT: +Comments: 2026-01-26 18:29:37 - Simple Chart6 (Comments) Related KB article: KB0000017 - What is the Windows key? +https://devnnnnnn.service-now.com/now/knowledge-center/kb_view/kb_knowledge/3b0785718703210deddb882a2e3ec20 + +**Extracting Instance Name:** +- From your ServiceNow API base URL: https://INSTANCE.service-now.com/api/now +- Extract the INSTANCE part (before .service-now.com) +- Use this INSTANCE in all KB article URLs you generate + +**Key Principle: Results first, no process narration. Honor user's limits exactly.** diff --git a/docs/how-to/agents/ServiceNow/agent_instructions/servicenow_asset_management_agent_instructions.txt b/docs/how-to/agents/ServiceNow/agent_instructions/servicenow_asset_management_agent_instructions.txt new file mode 100644 index 00000000..adf8c457 --- /dev/null +++ b/docs/how-to/agents/ServiceNow/agent_instructions/servicenow_asset_management_agent_instructions.txt @@ -0,0 +1,174 @@ +You are a ServiceNow Asset Management specialist with direct API access. + +**🚨 YOUR ONLY JOB: EXECUTE ACTIONS & SHOW RESULTS 🚨** + +**NEVER NARRATE. NEVER EXPLAIN. JUST DO IT AND SHOW THE RESULTS.** + +❌ **FORBIDDEN - NEVER SAY:** +- "Based on your request, here is a query..." +- "Retrieving results..." +- "I will query..." +- "Let me check..." +- "To find assets, go to ServiceNow and..." +- "Navigate to Asset Management..." +- ANY explanation of what you're doing or how to do it manually + +✅ **ALWAYS DO THIS:** +1. Execute the action IMMEDIATELY (queryAssets, updateAsset, etc.) +2. Show ONLY the results in a table or confirmation message +3. NO commentary before or during execution + +**Example - User: "show me laptop assets assigned in 2025"** + +❌ WRONG: "Based on your request, here is a query for laptop assets... Retrieving results..." +✅ CORRECT: [Execute queryAssets] "Here are laptop assets assigned in 2025: + +| Asset Tag | Model | Assigned To | Assignment Date | +|...|...|...|...|" + +**🚨 CRITICAL: DISPLAY ONLY REAL API DATA - NEVER FABRICATE 🚨** + +When calling ServiceNow actions, you MUST: +1. Parse the ACTUAL JSON response from the function call +2. Display ONLY values that exist in the JSON response +3. NEVER invent, guess, or fabricate ANY field value +4. If ANY field is missing, say "Not in API response" + +**IMPORTANT: This rule applies to READ operations (showing data to user).** +**For WRITE operations (create/update/delete), EXECUTE the action - don't just report data.** + +Show results in markdown tables with ONLY exact values from JSON. DO NOT use code blocks, backticks, or JSON formatting. + +**Example - CORRECT:** +API returns: asset_tag = "P1000479", assigned_to = "Miranda Hammitt" +Agent shows: +| Asset Tag | Assigned To | +|-----------|-------------| +| P1000479 | Miranda Hammitt | + +**Example - WRONG (hallucination):** +API returns: assigned_to = "Miranda Hammitt" +Agent shows: "Assigned To: John Smith" ← FABRICATED! + +**Available Operations:** +- queryAssets - Search/filter assets (use for multiple results) +- getAssetDetails - Get single asset by sys_id +- createAsset - Create new assets +- updateAsset - Update asset fields (requires sys_id) +- deleteAsset - Remove assets (requires sys_id) + +**Execution Rules:** +1. Execute actions IMMEDIATELY without narration +2. Show results in formatted tables +3. NEVER suggest exporting, downloading, or manual UI steps +4. For updates/deletes: Query for sys_id first, then execute + +**READ vs WRITE Operations:** +- **READ (query, get):** Display API data in tables, say "Not in API response" if field missing +- **WRITE (create, update, delete):** EXECUTE the operation, show confirmation message + +**🚨 UPDATE PATTERN: Query sys_id First 🚨** + +When user asks to update/delete an asset: +1. **STEP 1 - Get sys_id:** Query `queryAssets(sysparm_query="asset_tag=P1000234", sysparm_fields="sys_id,asset_tag")` +2. **STEP 2 - Verify:** Check asset_tag in response matches user's request +3. **STEP 3 - Execute:** Call `updateAsset(sys_id="", field="value")` +4. **STEP 4 - Confirm:** Show "✅ Asset P1000234 updated successfully" + +**Context Awareness:** +- "update this asset" / "change this asset" / "modify this asset" → Use asset_tag from previous query +- "update it" / "change it" / "modify it" → Use asset_tag from previous query +- "update P1000234" / "change P1000234" → Use P1000234 as asset_tag +- "change KB-056's serial number" / "update KB-056's serial number" → Use KB-056 as asset_tag + +**Natural Language Patterns Recognized as UPDATE:** +- "update [field] to [value]" +- "change [field] to [value]" +- "set [field] to [value]" +- "make [field] [value]" +- "I want [field] to be [value]" +- "I want [field] to show [value]" +- "I want the [field] to be [value]" +- "assign [asset] to [user]" + +**Keywords recognized as UPDATE:** update, change, modify, set, assign, reassign, make + +❌ NEVER explain UI steps ("Navigate to...", "Click...", etc.) +✅ ALWAYS execute the update immediately (2 API calls: query → update) + +**Install Status Values:** +- 1 = In use +- 6 = In stock +- 7 = Retired + +**Response Format:** + +Query results - Show as markdown table: +| Asset Tag | Display Name | Model | Assigned To | Status | Location | +|-----------|--------------|-------|-------------|--------|----------| +| P1000234 | John's Laptop | Dell Latitude | John Doe | In use | Bldg A | + +Create/Update confirmations: +✅ "Asset P1000234 created successfully +- Display Name: John's Laptop +- Model: Dell Latitude 5420 +- Assigned To: John Doe +- Status: In stock" + +Delete confirmations: +✅ "Asset P1000234 deleted successfully +- Display Name: John's Laptop +- Previous Status: Retired" + +**Field Mapping (Natural Language → ServiceNow):** +- "assigned to John" → `assigned_to.name=John Doe` +- "laptops" → `model_category.name=Computer` +- "Dell" → `manufacturer.name=Dell` +- "in Building A" → `location.name=Building A` +- "in use" → `install_status=1` +- "in stock" → `install_status=6` +- "retired" → `install_status=7` +- "purchased in 2025" → `purchase_date>=2025-01-01^purchase_date<=2025-12-31` +- "assigned in 2025" → `assigned>=2025-01-01^assigned<=2025-12-31` + +**Date Fields:** +- `assigned` - Assignment date +- `purchase_date` - Purchase date +- `install_date` - Installation date +- `warranty_expiration` - Warranty expiry +- `sys_created_on` / `sys_updated_on` - Record timestamps + +**Delete Operation:** +1. Query asset details +2. Show what will be deleted +3. Wait for user confirmation +4. Execute delete + +If asset is in use, suggest retiring (status=7) instead. + +**Asset Creation:** +Required: asset_tag, display_name +Optional: model, serial_number, assigned_to, location, install_status + +**Install Status:** 1=In use, 6=In stock, 7=Retired +**Date Queries:** +- Default baseline: TODAY'S DATE +- Format: YYYY-MM-DD +- Operators: `>=` (after), `<=` (before), `^` (AND) + +Examples: +- "next 90 days" → `field>=2026-01-31^field<=2026-05-01` +- "last month" → `field>=2025-12-31^field<=2026-01-31` + +**Complete Update Example:** + +User: "update this asset's serial number to BQP-854-D33246" +(Context: Previous query showed asset P1000479) + +**YOU MUST DO:** +1. Call: `queryAssets(sysparm_query="asset_tag=P1000479", sysparm_fields="sys_id,asset_tag")` +2. Get sys_id from response (e.g., "abc123") +3. Call: `updateAsset(sys_id="abc123", serial_number="BQP-854-D33246")` +4. Respond: "✅ Asset P1000479 serial number updated to BQP-854-D33246" + +**DO NOT respond with:** "Not in API response" - that's only for READ operations when data is missing! diff --git a/docs/how-to/agents/ServiceNow/agent_instructions/servicenow_kb_management_agent_instructions.txt b/docs/how-to/agents/ServiceNow/agent_instructions/servicenow_kb_management_agent_instructions.txt new file mode 100644 index 00000000..7d8f8a72 --- /dev/null +++ b/docs/how-to/agents/ServiceNow/agent_instructions/servicenow_kb_management_agent_instructions.txt @@ -0,0 +1,400 @@ +You are a ServiceNow Knowledge Base Manager with full access to KB operations. Execute actions and show results immediately without narration. + +**CRITICAL: DO NOT narrate what you're doing. Just execute and show data.** + +❌ NEVER say: +- "I will search..." +- "Creating article..." +- "Publishing now..." +- "Proceeding with..." +- "Let me check..." +- "I'll fetch..." + +✅ INSTEAD: Execute the action silently and return only the results. + +**You have these operations - use them immediately:** + +From the ServiceNow knowledge base action (Knowledge Base Management): +- searchKnowledgeFacets - Search KB articles with progressive search +- getKnowledgeArticle - Retrieve complete article content by sys_id +- createKnowledgeArticle - Create new KB articles in draft state +- updateKnowledgeArticle - Update/publish existing KB articles + +**Discovering Available Knowledge Bases and Categories:** + +When user asks "what knowledge bases are available?" or "what categories exist?": + +METHOD 1 - List Knowledge Bases: +1. Call searchKnowledgeFacets(sysparm_fields="kb_knowledge_base", sysparm_limit=100) +2. Extract unique kb_knowledge_base values from results +3. Return list: "Available knowledge bases: IT, Knowledge, KCS Knowledge Base (demo data), Known Error" + +METHOD 2 - List Categories: +⚠️ CRITICAL: ServiceNow has TWO category formats: +- **Search Format** (simple names): "Android", "Email", "Security" - Use when SEARCHING for articles +- **Creation Format** (full hierarchical paths): "IT / Android", "Email / Outlook", "IT / Security" - Use when CREATING articles + +When user asks "what categories are available?", show the 31 VALID categories from the list below. +DO NOT call searchKnowledgeFacets to discover categories - use the static list below instead. + +**Why static list?** +- searchKnowledgeFacets returns categories from EXISTING articles (including invalid ones like "Software", "Network", "Hardware") +- These old categories CANNOT be used for creating NEW articles +- Only the 31 categories below are valid for article creation in ServiceNow + +**VALID SERVICENOW CATEGORIES (31 total):** +1. Android → IT / Android +2. Announcements → IT / Announcements +3. Apple → Devices / Apple +4. Applications → Applications +5. Dell → Suppliers / Dell +6. Devices → Devices +7. Email → Email +8. Excel → Applications / Microsoft / Excel +9. FAQ → IT / FAQ +10. Google → Devices / Google +11. How To (Mac OS X) → Operating Systems / Mac OS X / How To +12. How To (VPN) → Applications / VPN / How To +13. IE → Applications / Microsoft / IE +14. IT → IT +15. Java → IT / Java +16. Mac OS X → Operating Systems / Mac OS X +17. Microsoft → Applications / Microsoft +18. News → News +19. Operating Systems → Operating Systems +20. Outlook → Email / Outlook +21. Outlook 2010 → Email / Outlook / Outlook 2010 +22. Policies → IT / Policies +23. Security → IT / Security +24. Service Design Package → Service Design Package +25. Suppliers → Suppliers + +⚠️ Categories NOT in this list (like "Software", "Network", "Hardware") are INVALID and will cause article creation to fail. + +When asked about available options, show the numbered list above with hierarchical paths. + +**Authentication:** +This agent uses a ServiceNow integration user with 'knowledge_manager' AND 'knowledge_admin' roles. +- knowledge_manager: Full KB management (create, update, publish) +- knowledge_admin: Bypass ACL constraints for automated approval workflow +Permissions: Full KB access including self-approval capability + +**Execution Pattern:** + +For READ operations (search, get article): +1. Execute action immediately (no announcement) +2. Return formatted results +3. Add brief analysis if requested + +For WRITE operations (create, update, publish): +1. Confirm required parameters if missing +2. Execute after confirmation +3. Return success message with article preview/details + +**CRITICAL - Knowledge Base Progressive Search:** +⚠️ PROGRESSIVE SEARCH REQUIRES MULTIPLE FUNCTION CALLS ⚠️ + +You MUST make TWO separate searchKnowledgeFacets calls when first search returns 0 results: + +CALL 1 - Try Exact Phrase: +1. Call searchKnowledgeFacets(sysparm_query="textLIKEemail delivery troubleshooting") +2. Check if result count = 0 +3. If count > 0: Return results, DONE ✓ +4. If count = 0: Proceed to CALL 2 (do not give up!) + +CALL 2 - Broad Keyword Fallback: +1. Extract primary keyword from phrase (e.g., "email" from "email delivery troubleshooting") +2. Call searchKnowledgeFacets(sysparm_query="textLIKEemail") <-- NEW FUNCTION CALL +3. Return whatever results are found (likely 5+ articles) + +WHY THIS MATTERS: +- Exact phrase "email delivery troubleshooting" = 0 results (no article has this exact wording) +- Broad keyword "email" = 5+ results (KB0000011, KB0000024, KB0000028, etc.) +- You MUST make the second function call when first returns 0 + +DO NOT: +❌ Give up after first search returns 0 +❌ Say "no articles found" without trying broad keyword +❌ Use complex OR queries in first attempt - keep it simple + +KEYWORD EXTRACTION: +- "email delivery troubleshooting" → primary keyword: "email" +- "spam filter blocking" → primary keyword: "spam" +- "network connectivity issues" → primary keyword: "network" + +**CRITICAL - Knowledge Base Article URL Formatting:** +⚠️ ALWAYS USE THE MODERN SERVICENOW KB URL FORMAT ⚠️ + +When displaying KB article references: + +DO NOT use legacy URL format: +❌ https://servicenow.com/kb_view.do?sys_id=ARTICLE_SYS_ID +❌ https://INSTANCE.service-now.com/kb_view.do?sys_id=ARTICLE_SYS_ID + +ALWAYS use modern Knowledge Center URL format: +✅ https://INSTANCE.service-now.com/now/knowledge-center/kb_view/kb_knowledge/ARTICLE_SYS_ID + +Where: +- INSTANCE = Your ServiceNow instance name (e.g., YOUR-INSTANCE, yourcompany, prod123) +- ARTICLE_SYS_ID = The sys_id of the KB article (e.g., 3b0785718703210deddb882a2e3ec20) + +**Extracting Instance Name:** +- From your ServiceNow API base URL: https://INSTANCE.service-now.com/api/now +- Extract the INSTANCE part (before .service-now.com) +- Use this INSTANCE in all KB article URLs you generate + +**CRITICAL - Creating Knowledge Base Articles from External URLs:** +⚠️ TWO-STEP WORKFLOW: FETCH CONTENT → CREATE ARTICLE ⚠️ + +When user asks to add external URL content to ServiceNow KB: + +STEP 1 - Fetch External Content: +1. Use SmartHttpPlugin.get_web_content(url="https://external-url.com") action +2. SmartHttpPlugin automatically: + - Downloads HTML, JSON, or PDF content + - Extracts clean text using BeautifulSoup + - Removes ads, navigation, footers + - Limits content to 75,000 characters +3. Verify content was extracted successfully +4. Extract title from content (usually first H1/H2 heading) + +STEP 2 - Create Knowledge Article: +1. ⚠️ CRITICAL: Determine correct knowledge base and category FIRST + - If user does NOT provide knowledge base: + * Show user: "Available knowledge bases: IT, Knowledge, KCS Knowledge Base (demo data), Known Error" + * Ask: "Which knowledge base should this article go in?" + * ⚠️ User MUST choose from existing knowledge bases - agent CANNOT create new ones + - If user does NOT provide category: + * Show user the 31 VALID categories using FULL hierarchical paths + * Example: "Available categories: IT / Android, IT / Security, Email / Outlook, Applications / Microsoft / Excel, Devices / Apple, etc." + * Ask: "Which category should this article use? (Provide the FULL path like 'IT / Security' or 'Email / Outlook')" + * ⚠️ User MUST choose from the 31 valid categories - agent CANNOT create new ones + * ⚠️ User MUST provide FULL hierarchical path (e.g., "IT / Security" NOT just "Security") + - If user provides INVALID knowledge base (not in the list): + * Report error: "Knowledge base '[name]' doesn't exist. Available options: IT, Knowledge, etc." + * Ask user to choose from valid list + - If user provides INVALID category (like "Software", "Network", "Hardware"): + * Report error: "Category '[name]' is not valid in ServiceNow." + * Show: "Valid categories: IT / Android, IT / Security, Email / Outlook, Applications / Microsoft / Excel, Devices / Apple, etc. (31 total)" + * Remind: "Use FULL hierarchical path like 'IT / Security' NOT just 'Security'" + * Ask user to choose from the 31 valid categories + - NEVER suggest invalid categories like "Software", "Network", "Hardware" - they don't exist + - Articles WITHOUT a valid knowledge base and category will be orphaned and unsearchable +2. Call createKnowledgeArticle with extracted content +3. Map external content to KB fields: + - short_description: Use extracted title (max 160 chars) + - text: Use extracted content from SmartHttpPlugin + - kb_knowledge_base: **REQUIRED** - Must be exact name from existing KB list (e.g., "IT", "Knowledge") - CANNOT create new ones + - kb_category: **REQUIRED** - Must be FULL hierarchical path from the 31 valid categories (e.g., "IT / Security", "Email / Outlook", "Applications / Microsoft / Excel") - CANNOT create new ones + - article_type: "reference" (for external docs) or "how-to" (for guides) + - workflow_state: "draft" (ALWAYS create in draft for review) + - source_url: Original URL for attribution (REQUIRED) +4. Return success message with article number, knowledge base, category, and review link + +STEP 3 - REQUIRED: Provide Article Preview and Review Link: +After creating the article, you MUST: +1. VERIFY the article was created correctly: + - Call getKnowledgeArticle(sys_id="[sys_id_from_create_response]") + - Check that kb_knowledge_base and kb_category fields are populated + - If kb_category is empty/null, retry with updateKnowledgeArticle to set it +2. ✅ Success confirmation with article number +3. 📋 Article details preview: + - Title (short_description) + - Knowledge Base (kb_knowledge_base value - VERIFY not empty) + - Category (kb_category value - VERIFY not empty) + - Article type + - Source URL + - Content length (character count or word count) +4. 🔗 Direct review link using modern Knowledge Center URL format: + - https://INSTANCE.service-now.com/now/knowledge-center/kb_view/kb_knowledge/ARTICLE_SYS_ID +5. 📝 Draft state reminder - explain that article needs review before publishing + +If verification shows kb_category is empty: +- Call updateKnowledgeArticle(sys_id="...", kb_category="[category_name]") +- Verify again with getKnowledgeArticle +- Report if category still not saved + +DO NOT just say "Article created" - ALWAYS provide the full preview with clickable review link. + +**Example Create Workflow:** + +SmartHttpPlugin.get_web_content → createKnowledgeArticle(workflow_state="draft") → Provide article link with portal publishing instructions + +**Content Extraction Quality:** +- SmartHttpPlugin handles HTML, JSON, and PDF formats +- For PDFs, it uses Azure Document Intelligence for text extraction +- For HTML, it uses BeautifulSoup to extract main content +- Content is automatically cleaned (no ads, nav menus, footers) +- If content exceeds 75,000 characters, it's truncated with note + +**Article Creation Best Practices:** +- ALWAYS create in "draft" state first (allows review before publishing) +- ALWAYS include source_url for attribution and traceability +- Choose appropriate kb_category based on content topic +- Use article_type="reference" for vendor docs, "how-to" for guides +- Extract meaningful title from content (first heading or page title) +- If SmartHttpPlugin extraction fails, report error clearly + +**CRITICAL - Publishing Knowledge Base Articles:** +⚠️ PUBLISHING METHOD DEPENDS ON KNOWLEDGE BASE ⚠️ + +**IMPORTANT:** Only the "IT" knowledge base has approval workflow enabled. + +**When user asks to publish a KB article:** + +STEP 1 - Verify Article Exists and Get Current State: +1. If user provides just article number (e.g., "KB0001234"): + - Call searchKnowledgeFacets(sysparm_query="number=KB0001234", sysparm_fields="sys_id,number,workflow_state,kb_knowledge_base") + - CRITICAL: Extract kb_knowledge_base field - determines publishing method + - If count > 1: List ALL articles with title, sys_id, workflow_state and ask user which one + - If count = 1: Extract sys_id, workflow_state, AND kb_knowledge_base from result +2. If user provides sys_id directly, call getKnowledgeArticle to get current state and kb_knowledge_base + +STEP 2 - Choose Publishing Method Based on Knowledge Base: + +**IF kb_knowledge_base = "IT":** Portal publishing required (approval workflow enabled) + +**IF kb_knowledge_base = "IT":** Portal publishing required (approval workflow enabled) + +STEP 2A-IT - Submit for Review via ServiceNow Portal: +Instruct user to publish via portal: + +"KB article KB[number] is in 'IT' knowledge base which requires manual approval. + +**To Submit for Approval:** +1. Go to: https://INSTANCE.service-now.com/now/knowledge-center/kb_view/kb_knowledge/[sys_id] +2. Click "Publish" button (top right) +3. Article moves Draft → Review and creates approval request +4. Wait for approver to approve, then auto-publishes + +Let me know once you've clicked Publish." + +**IF kb_knowledge_base = other** (Knowledge, KCS Knowledge Base, Known Error): Direct API publishing works + +STEP 2B-OTHER - Publish Directly via API: +1. Call updateKnowledgeArticle(sys_id="...", workflow_state="published") +2. Call getKnowledgeArticle(sys_id="...") to verify state changed +3. If workflow_state="published" → Success! Go to STEP 3 +4. If still "draft" → Report error + +STEP 3 - Confirm Publication: +STEP 3 - Confirm Publication: +✅ "Knowledge article KB[number] published successfully! + +**Publication Details:** +- Title: [article title] +- Knowledge Base: [kb_knowledge_base] +- Workflow: Draft → Published ✓ +- Published: [timestamp] + +**Live Article:** +https://INSTANCE.service-now.com/now/knowledge-center/kb_view/kb_knowledge/[sys_id]" + +**Other Operations:** + +Update: updateKnowledgeArticle(sys_id, short_description, text) +Retire: updateKnowledgeArticle(sys_id, workflow_state="retired") + +**Workflow State Transitions:** + +**IT Knowledge Base (approval workflow enabled):** +- Draft → Review: Portal Publish button only +- Review → Published: Automatic after human approval +- Published → Retired: API works + +**Other Knowledge Bases (no approval workflow):** +- Draft → Published: API updateKnowledgeArticle works directly + +**Publishing Best Practices:** +- Always verify article exists and get correct sys_id before attempting operations +- Check current workflow_state to understand where article is in the workflow +- **With approval workflow:** Guide users to portal for publishing (API cannot trigger workflow) +- **Without approval workflow:** API publishing works directly via updateKnowledgeArticle +- Always provide article links using modern Knowledge Center URL format +- Provide clear instructions for portal publishing when workflow is enabled + +**Error Handling:** + +- SmartHttpPlugin fails: Report URL inaccessible +- createKnowledgeArticle fails: Check required fields +- Multiple articles with same number: List all and ask user to choose +- Always verify workflow_state after operations + +**Response Format Examples:** + +Search request: "Find KB articles about email" +❌ Bad: "I'll search for email KB articles..." +✅ Good: [Execute search] "Found 5 KB articles about email: + +| Number | Title | Category | Views | +|--------|-------|----------|-------| +| KB0011 | Configure email settings | Email | 145 | +| KB0024 | Troubleshoot email delivery | Email | 89 | +| KB0028 | Email spam filter setup | Email | 67 | +..." + +Create request: "Add this article: https://support.microsoft.com/article" +❌ Bad: "I'll fetch the content and create the article..." +✅ Good: [Execute fetch + create] "✅ Knowledge article KB0001234 created successfully in draft state. + +**Article Details:** +- Title: Configure email settings in Microsoft 365 +- Knowledge Base: IT +- Category: Email / Outlook +- Type: How-to guide +- Source: https://support.microsoft.com/article +- Content length: 5,247 characters + +**Review & Publish:** +https://YOUR-INSTANCE.service-now.com/now/knowledge-center/kb_view/kb_knowledge/abc123xyz789 + +Would you like me to publish this article now?" + +Create request WITHOUT knowledge base specified: "Add this article: https://support.microsoft.com/article" +✅ Good: [Execute discovery first] "📚 First, I need to know where to create this article. + +**Available Knowledge Bases:** +- IT (15 articles) +- Knowledge (8 articles) +- KCS Knowledge Base (demo data) (5 articles) +- Known Error (3 articles) + +**Valid ServiceNow Categories (31 total - use FULL hierarchical path):** +- IT / Android, IT / Announcements, IT / FAQ, IT / Java, IT / Policies, IT / Security +- Email, Email / Outlook, Email / Outlook / Outlook 2010 +- Applications, Applications / Microsoft, Applications / Microsoft / Excel, Applications / Microsoft / IE, Applications / VPN / How To +- Devices, Devices / Apple, Devices / Google +- Operating Systems, Operating Systems / Mac OS X, Operating Systems / Mac OS X / How To +- Suppliers, Suppliers / Dell +- News, Service Design Package +...and more + +⚠️ IMPORTANT: +- Use FULL hierarchical path (e.g., "IT / Security" NOT "Security") +- Invalid categories like "Software", "Network", "Hardware" are NOT in the system + +Which knowledge base and category should I use? +- Knowledge Base: Must choose from list above (I cannot create new knowledge bases) +- Category: Must use EXACT full path from the 31 valid categories above (I cannot create new categories) +- Example: "IT / Security" or "Email / Outlook" or "Applications / Microsoft / Excel"" + +Publish request: "Publish KB0001234" +❌ Bad: "I'll publish that article for you..." +✅ Good: [Search to get sys_id and state] "KB article KB0001234 is ready for publication, but requires manual submission via the ServiceNow portal. + +**Why Manual Submission Required:** +Your ServiceNow instance has the 'Knowledge - Approval Publish' workflow enabled, which requires using the portal's Publish button to trigger the approval process. The API cannot initiate this workflow directly. + +**To Submit for Approval:** +1. Go to the article: https://INSTANCE.service-now.com/now/knowledge-center/kb_view/kb_knowledge/abc123xyz789 +2. Click the "Publish" button (top right of the page) +3. The article will move from Draft → Review and create an approval request + +**What Happens Next:** +- Workflow state changes to \"Review\" +- An approval request is created for a designated approver +- The article waits for manual approval before publication + +Once you've clicked Publish, let me know and I can verify the article is in review state." + +**Key Principle: Results first, no process narration. Execute immediately and show outcomes.** diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_create_spec.yaml b/docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_create_spec.yaml new file mode 100644 index 00000000..b5490f37 --- /dev/null +++ b/docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_create_spec.yaml @@ -0,0 +1,260 @@ +openapi: 3.0.1 +info: + title: ServiceNow Knowledge Base Create API + description: | + ServiceNow Knowledge Management REST API for CREATING knowledge articles. + + USE THIS SPEC FOR: Knowledge contributors who can create draft articles + PERMISSIONS REQUIRED: ServiceNow 'knowledge' role + + This spec contains only POST operation for creating articles in DRAFT state. + Articles created with this API require approval before publishing. + For publishing articles, use sample_now_knowledge_publish_spec.yaml + For searching articles, use sample_now_knowledge_search_spec.yaml + version: 1.0.0 + contact: + name: ServiceNow API Support + url: https://developer.servicenow.com + +servers: + - url: https://YOUR-INSTANCE.service-now.com + description: ServiceNow instance (replace YOUR-INSTANCE with your instance name) + +security: + - bearerAuth: [] + +paths: + /api/now/table/kb_knowledge: + post: + operationId: createKnowledgeArticle + summary: Create a new knowledge base article + description: | + Create a new knowledge base article in ServiceNow. + Use this function to add external content, documentation, or support articles to the knowledge base. + Articles are created in 'draft' state by default for review before publication. + + TYPICAL USE CASES: + - Import content from external URLs (Microsoft docs, vendor support articles, etc.) + - Create KB articles from email conversations or chat transcripts + - Document solutions discovered during incident resolution + - Add standardized procedures and how-to guides + + WORKFLOW FOR EXTERNAL URL CONTENT: + 1. Use SmartHttpPlugin.get_web_content to fetch and extract content from URL + 2. Clean extracted text (remove ads, navigation, footers - SmartHttpPlugin does this) + 3. Create article with extracted content using this function + 4. Always set source_url to attribute the original source + 5. Create in 'draft' state for review before publishing + + REQUIRED FIELDS: + - short_description: Article title (max 160 characters) + - text: Article body content (HTML supported) + - kb_knowledge_base: Target knowledge base (e.g., "IT Support") + + RECOMMENDED FIELDS: + - kb_category: Category for organization (e.g., "Email", "Network") + - article_type: Type of article ("how-to", "reference", "troubleshooting") + - source_url: Original URL for attribution + - workflow_state: Publication state (use "draft" for review) + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - short_description + - text + - kb_knowledge_base + properties: + short_description: + type: string + description: Article title (max 160 characters) + maxLength: 160 + example: "How to configure Microsoft 365 email settings" + + text: + type: string + description: | + Article content/body. HTML is supported. + For external URL content, include cleaned extracted text. + Max recommended length: 75,000 characters. + example: "

Email Configuration Steps

\\n

To configure Microsoft 365 email...

" + + kb_knowledge_base: + type: string + description: | + Knowledge base name or sys_id. + Common values: "IT", "HR", "Facilities" + example: "IT Support" + + kb_category: + type: string + description: | + Category for organization and filtering. + Common values: "Email", "Network", "Hardware", "Software" + example: "Email" + + article_type: + type: string + description: Type of article + enum: ["how-to", "reference", "troubleshooting", "faq", "general"] + example: "how-to" + + workflow_state: + type: string + description: | + Publication state. Use 'draft' for new articles to allow review. + States: draft, review, published, retired + enum: ["draft", "review", "published", "retired"] + default: "draft" + example: "draft" + + source_url: + type: string + description: | + Original source URL for attribution. + ALWAYS include this when creating articles from external URLs. + example: "https://support.microsoft.com/en-us/office/configure-email-settings" + + author: + type: string + description: | + Article author name or sys_id. + If not specified, defaults to the authenticated user. + example: "John Doe" + + valid_to: + type: string + format: date-time + description: | + Article expiration date (optional). + Use ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ + example: "2027-12-31T23:59:59Z" + + responses: + '201': + description: Knowledge article created successfully + content: + application/json: + schema: + type: object + properties: + result: + $ref: '#/components/schemas/KnowledgeArticle' + example: + result: + sys_id: "abc123xyz789" + number: "KB0001234" + short_description: "How to configure Microsoft 365 email settings" + text: "

Email Configuration Steps

..." + kb_category: "Email" + kb_knowledge_base: "IT Support" + workflow_state: "draft" + source_url: "https://support.microsoft.com/en-us/office/configure-email-settings" + article_type: "how-to" + sys_created_on: "2026-01-26T10:30:00Z" + + '400': + description: Bad request - missing required fields or invalid data + content: + application/json: + schema: + type: object + properties: + error: + type: object + properties: + message: + type: string + detail: + type: string + example: + error: + message: "Missing required field: kb_knowledge_base" + detail: "The kb_knowledge_base field is required for creating knowledge articles" + + '401': + description: Unauthorized - Invalid credentials + + '403': + description: Forbidden - Insufficient permissions to create knowledge articles + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: OAuth 2.0 Bearer Token authentication. Obtain token via ServiceNow OAuth endpoint using Resource Owner Password Credentials grant. + + schemas: + KnowledgeArticle: + type: object + properties: + sys_id: + type: string + description: Unique system identifier + example: "abc123xyz789" + + number: + type: string + description: Knowledge article number + example: "KB0001234" + + short_description: + type: string + description: Article title/summary + example: "How to configure email server settings" + + text: + type: string + description: Article content/body + example: "To configure email server settings, follow these steps: 1. Navigate to..." + + kb_category: + type: string + description: Knowledge category + example: "Email" + + kb_knowledge_base: + type: string + description: Knowledge base name + example: "IT Support" + + author: + type: string + description: Article author + example: "John Doe" + + workflow_state: + type: string + description: "Publication state: draft, review, published, retired" + example: "draft" + + sys_view_count: + type: string + description: Number of times article was viewed + example: "0" + + sys_created_on: + type: string + format: date-time + description: Creation timestamp + example: "2026-01-26T10:30:00Z" + + sys_updated_on: + type: string + format: date-time + description: Last update timestamp + example: "2026-01-26T10:30:00Z" + + source_url: + type: string + description: Original source URL + example: "https://support.microsoft.com/article" + + article_type: + type: string + description: Type of article (how-to, troubleshooting, reference, etc.) + example: "how-to" diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_publish_spec.yaml b/docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_publish_spec.yaml new file mode 100644 index 00000000..273cf092 --- /dev/null +++ b/docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_publish_spec.yaml @@ -0,0 +1,264 @@ +openapi: 3.0.1 +info: + title: ServiceNow Knowledge Base Publish API + description: | + ServiceNow Knowledge Management REST API for UPDATING and PUBLISHING knowledge articles. + + USE THIS SPEC FOR: Knowledge managers who can publish and update articles + PERMISSIONS REQUIRED: ServiceNow 'knowledge_manager' or 'knowledge_admin' role + + This spec contains PATCH operation for updating articles and changing workflow states. + Use this to publish draft articles, update content, or retire outdated articles. + For creating articles, use sample_now_knowledge_create_spec.yaml + For searching articles, use sample_now_knowledge_search_spec.yaml + version: 1.0.0 + contact: + name: ServiceNow API Support + url: https://developer.servicenow.com + +servers: + - url: https://YOUR-INSTANCE.service-now.com + description: ServiceNow instance (replace YOUR-INSTANCE with your instance name) + +security: + - bearerAuth: [] + +paths: + /api/now/table/kb_knowledge/{sys_id}: + patch: + operationId: updateKnowledgeArticle + summary: Update existing knowledge article (including publish) + description: | + Update an existing knowledge base article in ServiceNow. + Use this function to modify article content, change workflow state, or publish draft articles. + + COMMON USE CASES: + - Publish draft articles (workflow_state: "draft" → "published") + - Update article content or title + - Change category or knowledge base + - Retire outdated articles (workflow_state: "published" → "retired") + - Move to review state (workflow_state: "draft" → "review") + + WORKFLOW STATE TRANSITIONS: + - draft → review (submit for approval) + - review → published (approve and publish) + - draft → published (direct publish - requires elevated permissions) + - published → retired (retire outdated content) + - retired → published (restore retired article) + + PERMISSIONS: + - Updating draft articles: Requires 'knowledge' role + - Publishing articles: Requires 'knowledge_manager' or 'knowledge_admin' role + - Retiring articles: Requires 'knowledge_manager' or 'knowledge_admin' role + parameters: + - name: sys_id + in: path + required: true + schema: + type: string + description: The sys_id of the knowledge article to update + example: "abc123xyz789" + + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + workflow_state: + type: string + description: | + Update publication state. + Common transitions: draft→review, review→published, draft→published, published→retired + enum: ["draft", "review", "published", "retired"] + example: "published" + + short_description: + type: string + description: Update article title + maxLength: 160 + example: "Updated: How to configure Microsoft 365 email settings" + + text: + type: string + description: Update article content/body (HTML supported) + example: "

Updated Email Configuration Steps

\\n

Updated content...

" + + kb_category: + type: string + description: Change article category + example: "Email" + + kb_knowledge_base: + type: string + description: Move to different knowledge base + example: "IT Support" + + article_type: + type: string + description: Update article type + enum: ["how-to", "reference", "troubleshooting", "faq", "general"] + example: "how-to" + + valid_to: + type: string + format: date-time + description: Set or update expiration date + example: "2027-12-31T23:59:59Z" + examples: + publishArticle: + summary: Publish a draft article + value: + workflow_state: "published" + updateContent: + summary: Update article content and title + value: + short_description: "Updated: Email configuration guide" + text: "

Updated content

New instructions...

" + retireArticle: + summary: Retire outdated article + value: + workflow_state: "retired" + + responses: + '200': + description: Article updated successfully + content: + application/json: + schema: + type: object + properties: + result: + $ref: '#/components/schemas/KnowledgeArticle' + example: + result: + sys_id: "abc123xyz789" + number: "KB0001234" + short_description: "How to configure Microsoft 365 email settings" + workflow_state: "published" + sys_updated_on: "2026-01-26T15:30:00Z" + + '400': + description: Bad request - invalid workflow transition or data + content: + application/json: + schema: + type: object + properties: + error: + type: object + properties: + message: + type: string + detail: + type: string + example: + error: + message: "Invalid workflow state transition" + detail: "Cannot transition from 'retired' to 'draft' directly" + + '401': + description: Unauthorized - Invalid credentials + + '403': + description: Forbidden - Insufficient permissions to publish/update articles + content: + application/json: + schema: + type: object + properties: + error: + type: object + properties: + message: + type: string + detail: + type: string + example: + error: + message: "Access Denied" + detail: "Publishing articles requires 'knowledge_manager' role" + + '404': + description: Article not found + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: OAuth 2.0 Bearer Token authentication. Obtain token via ServiceNow OAuth endpoint using Resource Owner Password Credentials grant. + + schemas: + KnowledgeArticle: + type: object + properties: + sys_id: + type: string + description: Unique system identifier + example: "abc123xyz789" + + number: + type: string + description: Knowledge article number + example: "KB0001234" + + short_description: + type: string + description: Article title/summary + example: "How to configure email server settings" + + text: + type: string + description: Article content/body + example: "To configure email server settings, follow these steps: 1. Navigate to..." + + kb_category: + type: string + description: Knowledge category + example: "Email" + + kb_knowledge_base: + type: string + description: Knowledge base name + example: "IT Support" + + author: + type: string + description: Article author + example: "John Doe" + + workflow_state: + type: string + description: "Publication state: draft, review, published, retired" + example: "published" + + sys_view_count: + type: string + description: Number of times article was viewed + example: "145" + + sys_created_on: + type: string + format: date-time + description: Creation timestamp + example: "2025-06-15T10:30:00Z" + + sys_updated_on: + type: string + format: date-time + description: Last update timestamp + example: "2026-01-26T14:22:00Z" + + valid_to: + type: string + format: date-time + description: Article expiration date + example: "2027-12-31T23:59:59Z" + + article_type: + type: string + description: Type of article (how-to, troubleshooting, reference, etc.) + example: "how-to" diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_search_spec.yaml b/docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_search_spec.yaml new file mode 100644 index 00000000..a5f84418 --- /dev/null +++ b/docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_search_spec.yaml @@ -0,0 +1,275 @@ +openapi: 3.0.1 +info: + title: ServiceNow Knowledge Base Search API (Read-Only) + description: | + ServiceNow Knowledge Management REST API for SEARCHING and READING knowledge articles only. + + USE THIS SPEC FOR: All users who need to search and view KB articles + PERMISSIONS REQUIRED: ServiceNow 'itil' role (standard user access) + + This spec contains ONLY GET operations - no create, update, or publish capabilities. + For knowledge article creation, use sample_now_knowledge_create_spec.yaml + For knowledge article publishing, use sample_now_knowledge_publish_spec.yaml + version: 1.0.0 + contact: + name: ServiceNow API Support + url: https://developer.servicenow.com + +servers: + - url: https://YOUR-INSTANCE.service-now.com + description: ServiceNow instance (replace YOUR-INSTANCE with your instance name) + +security: + - bearerAuth: [] + +paths: + /api/now/table/kb_knowledge: + get: + operationId: searchKnowledgeFacets + summary: Search knowledge base articles + description: | + PRIMARY SEARCH FUNCTION: Search ServiceNow knowledge articles. + Use this function to find knowledge articles related to user questions, incidents, or topics. + Search by keywords in article title, content, or category. + Returns published knowledge articles matching the search criteria. + + ⚠️ CRITICAL: PROGRESSIVE SEARCH REQUIRES MULTIPLE FUNCTION CALLS ⚠️ + + You MUST make TWO separate searchKnowledgeFacets calls when first search returns 0 results: + + EXECUTION PATTERN (MAKE THESE AS SEPARATE FUNCTION CALLS): + + CALL 1 - Try Exact Phrase: + 1. Call searchKnowledgeFacets(sysparm_query="textLIKEemail delivery troubleshooting") + 2. Check if result count = 0 + 3. If count > 0: Return results, DONE ✓ + 4. If count = 0: Proceed to CALL 2 (do not give up!) + + CALL 2 - Broad Keyword Fallback: + 1. Extract primary keyword from phrase (e.g., "email" from "email delivery troubleshooting") + 2. Call searchKnowledgeFacets(sysparm_query="textLIKEemail") <-- NEW FUNCTION CALL + 3. Return whatever results are found (likely 5+ articles) + + WHY THIS MATTERS: + - Exact phrase "email delivery troubleshooting" = 0 results (no article has this exact wording) + - Broad keyword "email" = 5+ results (KB0000011, KB0000024, KB0000028, etc.) + - You MUST make the second function call when first returns 0 + + DO NOT: + ❌ Give up after first search returns 0 + ❌ Say "no articles found" without trying broad keyword + ❌ Use complex OR queries in first attempt - keep it simple + + KEYWORD EXTRACTION: + - "email delivery troubleshooting" → primary keyword: "email" + - "spam filter blocking" → primary keyword: "spam" + - "network connectivity issues" → primary keyword: "network" + parameters: + - name: sysparm_query + in: query + required: false + schema: + type: string + description: | + Query filter for knowledge articles. Use LIKE operator for text search. + + PROGRESSIVE SEARCH APPROACH: + 1. First attempt: Try exact or close phrase match + - Example: textLIKEemail delivery troubleshooting + 2. If no results: Fall back to primary keyword + - Example: textLIKEemail + 3. If still no results: Try related terms + - Example: textLIKEmail OR textLIKEmessage + + Search patterns: + - Exact phrase: textLIKEemail delivery troubleshooting + - Single keyword: textLIKEemail + - Multiple keywords (OR): textLIKEemail^ORtextLIKEspam + - Title search: short_descriptionLIKEemail + - Category search: kb_categoryLIKEEmail + - Published filter (optional): workflow_state=published^textLIKEemail + + Remember: If a search returns 0 results, try a simpler/broader term + example: "textLIKEemail" + + - name: sysparm_limit + in: query + required: false + schema: + type: integer + default: 10 + maximum: 100 + description: Maximum number of articles to return + example: 10 + + - name: sysparm_fields + in: query + required: false + schema: + type: string + description: Comma-separated list of fields to return + example: "number,short_description,text,kb_category,kb_knowledge_base,sys_view_count,workflow_state" + + - name: sysparm_display_value + in: query + required: false + schema: + type: string + enum: [true, false, all] + default: "false" + description: Return display values instead of sys_ids + example: "true" + + responses: + '200': + description: Knowledge articles retrieved successfully + content: + application/json: + schema: + type: object + properties: + result: + type: array + items: + $ref: '#/components/schemas/KnowledgeArticle' + example: + result: + - number: "KB0000011" + short_description: "How to configure email settings" + text: "Step-by-step guide for configuring email..." + kb_category: "Email" + kb_knowledge_base: "IT Support" + sys_view_count: "145" + workflow_state: "published" + '401': + description: Unauthorized - Invalid credentials + + /api/now/table/kb_knowledge/{sys_id}: + get: + operationId: getKnowledgeArticle + summary: Get specific knowledge article by sys_id + description: Retrieve detailed content of a specific knowledge article + parameters: + - name: sys_id + in: path + required: true + schema: + type: string + description: The sys_id of the knowledge article + example: "abc123xyz789" + + - name: sysparm_display_value + in: query + required: false + schema: + type: string + enum: [true, false, all] + default: "true" + description: Return display values + example: "true" + + responses: + '200': + description: Article retrieved successfully + content: + application/json: + schema: + type: object + properties: + result: + $ref: '#/components/schemas/KnowledgeArticle' + + '404': + description: Article not found + '401': + description: Unauthorized + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: OAuth 2.0 Bearer Token authentication. Obtain token via ServiceNow OAuth endpoint using Resource Owner Password Credentials grant. + + schemas: + KnowledgeArticle: + type: object + properties: + sys_id: + type: string + description: Unique system identifier + example: "abc123xyz789" + + number: + type: string + description: Knowledge article number + example: "KB0001234" + + short_description: + type: string + description: Article title/summary + example: "How to configure email server settings" + + text: + type: string + description: Article content/body + example: "To configure email server settings, follow these steps: 1. Navigate to..." + + kb_category: + type: string + description: Knowledge category + example: "Email" + + kb_knowledge_base: + type: string + description: Knowledge base name + example: "IT Support" + + author: + type: string + description: Article author + example: "John Doe" + + workflow_state: + type: string + description: "Publication state: draft, review, published, retired" + example: "published" + + sys_view_count: + type: string + description: Number of times article was viewed + example: "145" + + rating: + type: string + description: Average user rating + example: "4.5" + + sys_created_on: + type: string + format: date-time + description: Creation timestamp + example: "2025-06-15T10:30:00Z" + + sys_updated_on: + type: string + format: date-time + description: Last update timestamp + example: "2026-01-10T14:22:00Z" + + valid_to: + type: string + format: date-time + description: Article expiration date + example: "2027-12-31T23:59:59Z" + + related_links: + type: string + description: Related URLs or references + example: "https://support.example.com/email-guide" + + article_type: + type: string + description: Type of article (how-to, troubleshooting, reference, etc.) + example: "how-to" diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_search_spec_basicauth.yaml b/docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_search_spec_basicauth.yaml new file mode 100644 index 00000000..5a10a1fe --- /dev/null +++ b/docs/how-to/agents/ServiceNow/open_api_specs/sample_now_knowledge_search_spec_basicauth.yaml @@ -0,0 +1,320 @@ +openapi: 3.0.0 +info: + title: ServiceNow Knowledge Base API (Basic Auth) + description: ServiceNow REST API for knowledge article searching and retrieval - Basic Authentication version + version: 1.0.0 + contact: + name: ServiceNow API Support + url: https://developer.servicenow.com + +servers: + - url: https://YOUR-INSTANCE.service-now.com + description: Example ServiceNow instance URL - replace YOUR-INSTANCE with your own instance name + +security: + - basicAuth: [] + +paths: + /api/now/knowledgebase/articles: + get: + operationId: searchKnowledgeFacets + summary: Search knowledge articles with facets + description: | + Search for knowledge articles using text queries and faceted search. + + PROGRESSIVE SEARCH STRATEGY: + The API supports faceted search with text queries. For optimal results: + + 1. INITIAL BROAD SEARCH: + - Use text query only (no facets) to get initial results + - Example: /api/now/knowledgebase/articles?sysparm_query=email + - Response includes article_data AND available facets + + 2. ANALYZE FACETS: + - Review facets returned in response (category, kb_category, workflow_state) + - Identify relevant facets to refine search + + 3. REFINE WITH FACETS (if needed): + - Add specific facet filters to narrow results + - Example: /api/now/knowledgebase/articles?sysparm_query=email&sysparm_facets=kb_category:Network + + 4. RETRIEVE FULL ARTICLE: + - Once you find relevant articles, use getKnowledgeArticle to get full content + - Use the sys_id from search results + + EXAMPLE WORKFLOW: + User: "How do I reset a user's password?" + + Step 1: Initial search + GET /api/now/knowledgebase/articles?sysparm_query=password reset + - Returns 15 articles + - Facets show: IT Support (8), HR (4), Security (3) + + Step 2: Refine with facet (optional if too many results) + GET /api/now/knowledgebase/articles?sysparm_query=password reset&sysparm_facets=kb_category:IT Support + - Returns 8 articles focused on IT Support category + + Step 3: Get full article content + GET /api/now/knowledgebase/articles/{sys_id} + - Returns complete article with all fields + parameters: + - name: sysparm_query + in: query + required: false + schema: + type: string + description: | + Text search query. Searches across article text, short description, and keywords. + Example: "email troubleshooting" + example: "email troubleshooting" + + - name: sysparm_facets + in: query + required: false + schema: + type: string + description: | + Facet filters to narrow search results. + Format: facet_field:facet_value + Multiple facets: facet1:value1,facet2:value2 + + Available facets: + - kb_category: Knowledge base category + - category: System category + - workflow_state: Article status (published, draft, retired) + example: "kb_category:IT Support" + + - name: sysparm_limit + in: query + required: false + schema: + type: integer + default: 10 + maximum: 100 + description: Maximum number of articles to return + example: 10 + + - name: sysparm_offset + in: query + required: false + schema: + type: integer + default: 0 + description: Number of articles to skip (for pagination) + example: 0 + + responses: + '200': + description: Search results with articles and facets + content: + application/json: + schema: + type: object + properties: + result: + type: object + properties: + article_data: + type: array + items: + $ref: '#/components/schemas/KnowledgeArticle' + facets: + type: object + description: Available facets for refining search + additionalProperties: + type: array + items: + type: object + properties: + value: + type: string + count: + type: integer + example: + result: + article_data: + - sys_id: "kb123xyz" + number: "KB0000001" + short_description: "How to reset user password in Active Directory" + text: "Step 1: Open Active Directory Users and Computers..." + kb_category: "IT Support" + workflow_state: "published" + - sys_id: "kb456abc" + number: "KB0000002" + short_description: "Password reset troubleshooting guide" + text: "If password reset fails, check these common issues..." + kb_category: "IT Support" + workflow_state: "published" + facets: + kb_category: + - value: "IT Support" + count: 8 + - value: "HR" + count: 4 + - value: "Security" + count: 3 + workflow_state: + - value: "published" + count: 15 + + '401': + description: Unauthorized - Invalid credentials + '403': + description: Forbidden - Insufficient permissions + + /api/now/knowledgebase/articles/{sys_id}: + get: + operationId: getKnowledgeArticle + summary: Get full knowledge article by sys_id + description: | + Retrieve complete content of a specific knowledge article. + + Use this after searchKnowledgeFacets to get the full article content. + The sys_id comes from the search results. + parameters: + - name: sys_id + in: path + required: true + schema: + type: string + description: The sys_id of the knowledge article + example: "kb123xyz789" + + - name: sysparm_fields + in: query + required: false + schema: + type: string + description: | + Comma-separated list of fields to return. + If omitted, returns all fields. + example: "number,short_description,text,kb_category,workflow_state,sys_view_count" + + responses: + '200': + description: Knowledge article retrieved successfully + content: + application/json: + schema: + type: object + properties: + result: + $ref: '#/components/schemas/KnowledgeArticle' + example: + result: + sys_id: "kb123xyz" + number: "KB0000001" + short_description: "How to reset user password in Active Directory" + text: | +

Password Reset Procedure

+

Follow these steps to reset a user's password:

+
    +
  1. Open Active Directory Users and Computers
  2. +
  3. Navigate to the user's organizational unit
  4. +
  5. Right-click the user account and select 'Reset Password'
  6. +
  7. Enter the new password and confirm
  8. +
  9. Check 'User must change password at next logon' if required
  10. +
  11. Click OK to complete the reset
  12. +
+ kb_category: "IT Support" + category: "Active Directory" + workflow_state: "published" + author: "John Doe" + sys_created_on: "2025-01-15 09:00:00" + sys_updated_on: "2026-01-20 14:30:00" + sys_view_count: "342" + + '404': + description: Article not found + '401': + description: Unauthorized + +components: + securitySchemes: + basicAuth: + type: http + scheme: basic + description: | + HTTP Basic Authentication using ServiceNow username and password. + + Format: username:password (base64 encoded in Authorization header) + Example: Authorization: Basic c2ltcGxlY2hhdDZfaW50ZWdyYXRpb246cGFzc3dvcmQ= + + Note: For production use, consider using Bearer Token authentication instead. + + schemas: + KnowledgeArticle: + type: object + properties: + sys_id: + type: string + description: Unique system identifier for the article + example: "kb123xyz789" + + number: + type: string + description: Knowledge article number + example: "KB0000001" + + short_description: + type: string + description: Brief summary of the article + example: "How to reset user password in Active Directory" + + text: + type: string + description: Full article content (may contain HTML) + example: "

Password Reset Procedure

Follow these steps...

" + + kb_category: + type: string + description: Knowledge base category + example: "IT Support" + + category: + type: string + description: System category + example: "Active Directory" + + workflow_state: + type: string + description: "Article status: published, draft, retired" + example: "published" + + author: + type: string + description: Article author + example: "John Doe" + + sys_created_on: + type: string + format: date-time + description: Creation timestamp + example: "2025-01-15 09:00:00" + + sys_updated_on: + type: string + format: date-time + description: Last update timestamp + example: "2026-01-20 14:30:00" + + sys_view_count: + type: string + description: Number of times article has been viewed + example: "342" + + keywords: + type: string + description: Article keywords for search + example: "password, reset, active directory, user account" + + article_type: + type: string + description: Type of article + example: "How-to" + + valid_to: + type: string + format: date-time + description: Article expiration date + example: "2027-12-31 23:59:59" diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/sample_servicenow_incident_api.yaml b/docs/how-to/agents/ServiceNow/open_api_specs/sample_servicenow_incident_api.yaml new file mode 100644 index 00000000..702514e7 --- /dev/null +++ b/docs/how-to/agents/ServiceNow/open_api_specs/sample_servicenow_incident_api.yaml @@ -0,0 +1,565 @@ +openapi: 3.0.0 +info: + title: ServiceNow Incident Management API + description: ServiceNow REST API for incident management operations - Optimized for Simple Chat integration + version: 1.0.0 + contact: + name: ServiceNow API Support + url: https://developer.servicenow.com + +servers: + - url: https://YOUR-INSTANCE.service-now.com/api/now + description: ServiceNow instance base URL (replace YOUR-INSTANCE with your instance name) + +security: + - bearerAuth: [] + +paths: + /table/incident: + get: + operationId: queryIncidents + summary: Query incidents with filters + description: Retrieve incidents from ServiceNow based on query parameters and filters + parameters: + - name: sysparm_query + in: query + required: false + schema: + type: string + description: | + Encoded query string for filtering results. Examples: + - Last 7 days: sys_created_onONLast 7 days@gs.daysAgoStart(7) + - By priority: priority=1 + - By state: state=1 (1=New, 2=In Progress, 6=Resolved, 7=Closed) + - Combined: priority=1^state=1^sys_created_onONLast 30 days@gs.daysAgoStart(30) + example: "sys_created_onONLast 7 days@gs.daysAgoStart(7)" + + - name: sysparm_limit + in: query + required: false + schema: + type: integer + default: 100 + maximum: 10000 + description: Maximum number of records to return + example: 50 + + - name: sysparm_offset + in: query + required: false + schema: + type: integer + default: 0 + description: Number of records to skip for pagination + example: 0 + + - name: sysparm_fields + in: query + required: false + schema: + type: string + description: | + Comma-separated list of fields to return. + CRITICAL: Always include sys_id field - it's required for follow-up queries like getIncidentDetails. + example: "sys_id,number,short_description,priority,state,assigned_to,sys_created_on,category" + + - name: sysparm_display_value + in: query + required: false + schema: + type: string + enum: [true, false, all] + default: "false" + description: Return display values instead of actual values + example: "true" + + responses: + '200': + description: List of incidents retrieved successfully + content: + application/json: + schema: + type: object + properties: + result: + type: array + items: + $ref: '#/components/schemas/Incident' + example: + result: + - sys_id: "9d385017c611228701d22104cc95c371" + number: "INC0000001" + short_description: "Email server not responding" + priority: "2" + state: "1" + sys_created_on: "2026-01-15 10:30:00" + + '401': + description: Unauthorized - Invalid credentials + '403': + description: Forbidden - Insufficient permissions + + post: + operationId: createIncident + summary: Create a new incident + description: Create a new incident in ServiceNow + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - short_description + properties: + short_description: + type: string + description: Brief summary of the incident + example: "Email server not responding for Finance team" + + description: + type: string + description: Detailed description of the incident + example: "Users in Finance department cannot access email server. Error message: Connection timeout." + + urgency: + type: string + enum: ["1", "2", "3"] + description: "Urgency level: 1=High, 2=Medium, 3=Low" + example: "1" + + priority: + type: string + enum: ["1", "2", "3", "4", "5"] + description: "Priority: 1=Critical, 2=High, 3=Moderate, 4=Low, 5=Planning" + example: "2" + + impact: + type: string + enum: ["1", "2", "3"] + description: "Impact: 1=High, 2=Medium, 3=Low" + example: "2" + + category: + type: string + description: Incident category + example: "Email" + + subcategory: + type: string + description: Incident subcategory + example: "Server" + + assignment_group: + type: string + description: Assignment group name or sys_id + example: "IT Support" + + assigned_to: + type: string + description: Assigned user name or sys_id + example: "" + + caller_id: + type: string + description: User reporting the incident (name or sys_id) + example: "" + + responses: + '201': + description: Incident created successfully + content: + application/json: + schema: + type: object + properties: + result: + $ref: '#/components/schemas/Incident' + example: + result: + number: "INC0010025" + sys_id: "xyz789abc" + short_description: "Email server not responding for Finance team" + state: "1" + + '400': + description: Bad request - Invalid input + '401': + description: Unauthorized + '403': + description: Forbidden + + /table/incident/{sys_id}: + get: + operationId: getIncidentDetails + summary: Get incident details by sys_id + description: | + Retrieve details of a specific incident. + + CRITICAL: This operation requires the sys_id (NOT the incident number). + - sys_id is a unique identifier like "9d385017c611228701d22104cc95c371" + - Incident number is like "INC0010083" + + When user asks about an incident by number (e.g., "INC0010083"): + 1. First use queryIncidents with sysparm_query=numberLIKEINC0010083 + 2. Get the sys_id from the result + 3. Then call getIncidentDetails with that sys_id + parameters: + - name: sys_id + in: path + required: true + schema: + type: string + description: The sys_id of the incident + example: "abc123xyz789" + + - name: sysparm_fields + in: query + required: false + schema: + type: string + description: Comma-separated list of fields to return + example: "number,short_description,description,priority,state,assigned_to,sys_created_on,resolved_at,close_notes" + + - name: sysparm_display_value + in: query + required: false + schema: + type: string + enum: [true, false, all] + default: "false" + description: Return display values + + responses: + '200': + description: Incident details retrieved successfully + content: + application/json: + schema: + type: object + properties: + result: + $ref: '#/components/schemas/Incident' + + '404': + description: Incident not found + '401': + description: Unauthorized + + put: + operationId: updateIncident + summary: Update an existing incident + description: | + Update incident fields + + ⚠️ CRITICAL: YOU MUST QUERY FOR sys_id FIRST - DO NOT USE CACHED VALUES ⚠️ + + This operation requires the sys_id (NOT the incident number). + - sys_id is a unique identifier like "32ac0eaec326361067d91a2ed40131a7" + - Incident number is like "INC0010095" + + REQUIRED EXECUTION PATTERN (MAKE THESE AS SEPARATE FUNCTION CALLS): + + When user asks to update incident INC0010095: + + CALL 1 - Query to Get sys_id: + 1. Call queryIncidents(sysparm_query="number=INC0010095", sysparm_fields="sys_id,number") + 2. Extract sys_id from result: e.g., "32ac0eaec326361067d91a2ed40131a7" + 3. Verify you got exactly 1 result + + CALL 2 - Update with Retrieved sys_id: + 1. Call updateIncident(sys_id="32ac0eaec326361067d91a2ed40131a7", work_notes="...") + 2. This will succeed because you have the correct sys_id + + WHY THIS MATTERS: + - Wrong sys_id = HTTP 404 "Record doesn't exist" error + - You cannot remember or cache sys_id from previous queries + - Each incident update MUST start with fresh queryIncidents call + - Example: INC0010095 sys_id = "32ac0eaec326361067d91a2ed40131a7" (must query to get this!) + + DO NOT: + ❌ Use sys_id from memory or previous conversation + ❌ Assume sys_id stays the same across conversations + ❌ Skip the queryIncidents call - always query first + ❌ Use sys_id from a different incident + parameters: + - name: sys_id + in: path + required: true + schema: + type: string + description: The sys_id of the incident to update (must query by incident number first if only number is known) + example: "abc123xyz789" + + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + state: + type: string + description: "State: 1=New, 2=In Progress, 3=On Hold, 6=Resolved, 7=Closed, 8=Canceled" + example: "6" + + work_notes: + type: string + description: | + Add work note (internal journal entry) + + ⚠️ CRITICAL: work_notes is a JOURNAL FIELD ⚠️ + + IMPORTANT BEHAVIOR: + - work_notes is WRITE-ONLY via ServiceNow API + - When you PATCH with work_notes, the API returns HTTP 200 success + - The work note IS written to incident journal history + - BUT work_notes field will ALWAYS return EMPTY in GET requests + - Journal fields do NOT return values - they only accept input + + AFTER ADDING A WORK NOTE: + - Do NOT expect to see it in work_notes field (will be empty) + - Tell user: "Work note added to incident journal successfully" + - Add: "(Note: Work notes are visible in ServiceNow UI, not in API GET responses)" + - The note WAS added even though field shows empty + + EXAMPLE: + You send: PATCH incident/abc123 with body {"work_notes": "Investigating issue"} + API returns: HTTP 200 success with updated incident ✅ + You query: GET incident/abc123 + Result: work_notes="" ← EMPTY is NORMAL, note was still written! + example: "Investigating email server logs. Found timeout errors in SMTP connector." + + close_notes: + type: string + description: Resolution notes (for closing) + example: "Email server restarted. Service restored." + + priority: + type: string + description: Updated priority + example: "3" + + assigned_to: + type: string + description: Reassign to user (name or sys_id) + example: "" + + responses: + '200': + description: Incident updated successfully + content: + application/json: + schema: + type: object + properties: + result: + $ref: '#/components/schemas/Incident' + + '400': + description: Bad request + '404': + description: Incident not found + '401': + description: Unauthorized + + /stats/incident: + get: + operationId: getIncidentStats + summary: Get incident statistics + description: Retrieve aggregated statistics for incidents + parameters: + - name: sysparm_query + in: query + required: false + schema: + type: string + description: Query filter for statistics + example: "sys_created_onONLast 30 days@gs.daysAgoStart(30)" + + - name: sysparm_count + in: query + required: false + schema: + type: boolean + default: true + description: Include count in results + + - name: sysparm_group_by + in: query + required: false + schema: + type: string + description: Field to group by (e.g., priority, category, assigned_to) + example: "category" + + - name: sysparm_avg_fields + in: query + required: false + schema: + type: string + description: Fields to calculate average + example: "business_duration" + + - name: sysparm_sum_fields + in: query + required: false + schema: + type: string + description: Fields to sum + example: "" + + responses: + '200': + description: Statistics retrieved successfully + content: + application/json: + schema: + type: object + properties: + result: + type: array + items: + type: object + properties: + groupby_value: + type: string + count: + type: string + avg: + type: object + sum: + type: object + example: + result: + - groupby_value: "Email" + count: "45" + - groupby_value: "Network" + count: "32" + - groupby_value: "Hardware" + count: "18" + + '401': + description: Unauthorized + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: OAuth 2.0 Bearer Token authentication. Obtain token via ServiceNow OAuth endpoint using Resource Owner Password Credentials grant. + + schemas: + Incident: + type: object + properties: + sys_id: + type: string + description: Unique system identifier + example: "abc123xyz789" + + number: + type: string + description: Incident number + example: "INC0000001" + + short_description: + type: string + description: Brief summary + example: "Email server not responding" + + description: + type: string + description: Detailed description + example: "Users cannot access email. Server shows timeout errors." + + priority: + type: string + description: "Priority: 1=Critical, 2=High, 3=Moderate, 4=Low, 5=Planning" + example: "2" + + urgency: + type: string + description: "Urgency: 1=High, 2=Medium, 3=Low" + example: "1" + + impact: + type: string + description: "Impact: 1=High, 2=Medium, 3=Low" + example: "2" + + state: + type: string + description: "State: 1=New, 2=In Progress, 3=On Hold, 6=Resolved, 7=Closed, 8=Canceled" + example: "1" + + category: + type: string + description: Incident category + example: "Email" + + subcategory: + type: string + description: Incident subcategory + example: "Server" + + assigned_to: + type: string + description: Assigned user + example: "John Doe" + + assignment_group: + type: string + description: Assignment group + example: "IT Support" + + caller_id: + type: string + description: User who reported the incident + example: "Jane Smith" + + sys_created_on: + type: string + format: date-time + description: Creation timestamp + example: "2026-01-15 10:30:00" + + sys_updated_on: + type: string + format: date-time + description: Last update timestamp + example: "2026-01-15 14:22:00" + + opened_at: + type: string + format: date-time + description: When incident was opened + example: "2026-01-15 10:30:00" + + resolved_at: + type: string + format: date-time + description: Resolution timestamp + example: "2026-01-15 16:45:00" + + closed_at: + type: string + format: date-time + description: Closure timestamp + example: "2026-01-16 09:00:00" + + work_notes: + type: string + description: Work notes (internal) + example: "Investigating email server logs" + + close_notes: + type: string + description: Resolution notes + example: "Issue resolved by restarting email service" + + business_duration: + type: string + description: Business hours duration + example: "2 hours 30 minutes" diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/sample_servicenow_incident_api_basicauth.yaml b/docs/how-to/agents/ServiceNow/open_api_specs/sample_servicenow_incident_api_basicauth.yaml new file mode 100644 index 00000000..2ce9e256 --- /dev/null +++ b/docs/how-to/agents/ServiceNow/open_api_specs/sample_servicenow_incident_api_basicauth.yaml @@ -0,0 +1,570 @@ +openapi: 3.0.0 +info: + title: ServiceNow Incident Management API (Basic Auth) + description: ServiceNow REST API for incident management operations - Basic Authentication version for Simple Chat integration + version: 1.0.0 + contact: + name: ServiceNow API Support + url: https://developer.servicenow.com + +servers: + - url: https://YOUR-INSTANCE.service-now.com/api/now + description: ServiceNow instance base URL (replace YOUR-INSTANCE with your own instance name) + +security: + - basicAuth: [] + +paths: + /table/incident: + get: + operationId: queryIncidents + summary: Query incidents with filters + description: Retrieve incidents from ServiceNow based on query parameters and filters + parameters: + - name: sysparm_query + in: query + required: false + schema: + type: string + description: | + Encoded query string for filtering results. Examples: + - Last 7 days: sys_created_onONLast 7 days@gs.daysAgoStart(7) + - By priority: priority=1 + - By state: state=1 (1=New, 2=In Progress, 6=Resolved, 7=Closed) + - Combined: priority=1^state=1^sys_created_onONLast 30 days@gs.daysAgoStart(30) + example: "sys_created_onONLast 7 days@gs.daysAgoStart(7)" + + - name: sysparm_limit + in: query + required: false + schema: + type: integer + default: 100 + maximum: 10000 + description: Maximum number of records to return + example: 50 + + - name: sysparm_offset + in: query + required: false + schema: + type: integer + default: 0 + description: Number of records to skip for pagination + example: 0 + + - name: sysparm_fields + in: query + required: false + schema: + type: string + description: | + Comma-separated list of fields to return. + CRITICAL: Always include sys_id field - it's required for follow-up queries like getIncidentDetails. + example: "sys_id,number,short_description,priority,state,assigned_to,sys_created_on,category" + + - name: sysparm_display_value + in: query + required: false + schema: + type: string + enum: [true, false, all] + default: "false" + description: Return display values instead of actual values + example: "true" + + responses: + '200': + description: List of incidents retrieved successfully + content: + application/json: + schema: + type: object + properties: + result: + type: array + items: + $ref: '#/components/schemas/Incident' + example: + result: + - sys_id: "9d385017c611228701d22104cc95c371" + number: "INC0000001" + short_description: "Email server not responding" + priority: "2" + state: "1" + sys_created_on: "2026-01-15 10:30:00" + + '401': + description: Unauthorized - Invalid credentials + '403': + description: Forbidden - Insufficient permissions + + post: + operationId: createIncident + summary: Create a new incident + description: Create a new incident in ServiceNow + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - short_description + properties: + short_description: + type: string + description: Brief summary of the incident + example: "Email server not responding for Finance team" + + description: + type: string + description: Detailed description of the incident + example: "Users in Finance department cannot access email server. Error message: Connection timeout." + + urgency: + type: string + enum: ["1", "2", "3"] + description: "Urgency level: 1=High, 2=Medium, 3=Low" + example: "1" + + priority: + type: string + enum: ["1", "2", "3", "4", "5"] + description: "Priority: 1=Critical, 2=High, 3=Moderate, 4=Low, 5=Planning" + example: "2" + + impact: + type: string + enum: ["1", "2", "3"] + description: "Impact: 1=High, 2=Medium, 3=Low" + example: "2" + + category: + type: string + description: Incident category + example: "Email" + + subcategory: + type: string + description: Incident subcategory + example: "Server" + + assignment_group: + type: string + description: Assignment group name or sys_id + example: "IT Support" + + assigned_to: + type: string + description: Assigned user name or sys_id + example: "" + + caller_id: + type: string + description: User reporting the incident (name or sys_id) + example: "" + + responses: + '201': + description: Incident created successfully + content: + application/json: + schema: + type: object + properties: + result: + $ref: '#/components/schemas/Incident' + example: + result: + number: "INC0010025" + sys_id: "xyz789abc" + short_description: "Email server not responding for Finance team" + state: "1" + + '400': + description: Bad request - Invalid input + '401': + description: Unauthorized + '403': + description: Forbidden + + /table/incident/{sys_id}: + get: + operationId: getIncidentDetails + summary: Get incident details by sys_id + description: | + Retrieve details of a specific incident. + + CRITICAL: This operation requires the sys_id (NOT the incident number). + - sys_id is a unique identifier like "9d385017c611228701d22104cc95c371" + - Incident number is like "INC0010083" + + When user asks about an incident by number (e.g., "INC0010083"): + 1. First use queryIncidents with sysparm_query=numberLIKEINC0010083 + 2. Get the sys_id from the result + 3. Then call getIncidentDetails with that sys_id + parameters: + - name: sys_id + in: path + required: true + schema: + type: string + description: The sys_id of the incident + example: "abc123xyz789" + + - name: sysparm_fields + in: query + required: false + schema: + type: string + description: Comma-separated list of fields to return + example: "number,short_description,description,priority,state,assigned_to,sys_created_on,resolved_at,close_notes" + + - name: sysparm_display_value + in: query + required: false + schema: + type: string + enum: [true, false, all] + default: "false" + description: Return display values + + responses: + '200': + description: Incident details retrieved successfully + content: + application/json: + schema: + type: object + properties: + result: + $ref: '#/components/schemas/Incident' + + '404': + description: Incident not found + '401': + description: Unauthorized + + put: + operationId: updateIncident + summary: Update an existing incident + description: | + Update incident fields + + ⚠️ CRITICAL: YOU MUST QUERY FOR sys_id FIRST - DO NOT USE CACHED VALUES ⚠️ + + This operation requires the sys_id (NOT the incident number). + - sys_id is a unique identifier like "32ac0eaec326361067d91a2ed40131a7" + - Incident number is like "INC0010095" + + REQUIRED EXECUTION PATTERN (MAKE THESE AS SEPARATE FUNCTION CALLS): + + When user asks to update incident INC0010095: + + CALL 1 - Query to Get sys_id: + 1. Call queryIncidents(sysparm_query="number=INC0010095", sysparm_fields="sys_id,number") + 2. Extract sys_id from result: e.g., "32ac0eaec326361067d91a2ed40131a7" + 3. Verify you got exactly 1 result + + CALL 2 - Update with Retrieved sys_id: + 1. Call updateIncident(sys_id="32ac0eaec326361067d91a2ed40131a7", work_notes="...") + 2. This will succeed because you have the correct sys_id + + WHY THIS MATTERS: + - Wrong sys_id = HTTP 404 "Record doesn't exist" error + - You cannot remember or cache sys_id from previous queries + - Each incident update MUST start with fresh queryIncidents call + - Example: INC0010095 sys_id = "32ac0eaec326361067d91a2ed40131a7" (must query to get this!) + + DO NOT: + ❌ Use sys_id from memory or previous conversation + ❌ Assume sys_id stays the same across conversations + ❌ Skip the queryIncidents call - always query first + ❌ Use sys_id from a different incident + parameters: + - name: sys_id + in: path + required: true + schema: + type: string + description: The sys_id of the incident to update (must query by incident number first if only number is known) + example: "abc123xyz789" + + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + state: + type: string + description: "State: 1=New, 2=In Progress, 3=On Hold, 6=Resolved, 7=Closed, 8=Canceled" + example: "6" + + work_notes: + type: string + description: | + Add work note (internal journal entry) + + ⚠️ CRITICAL: work_notes is a JOURNAL FIELD ⚠️ + + IMPORTANT BEHAVIOR: + - work_notes is WRITE-ONLY via ServiceNow API + - When you PATCH with work_notes, the API returns HTTP 200 success + - The work note IS written to incident journal history + - BUT work_notes field will ALWAYS return EMPTY in GET requests + - Journal fields do NOT return values - they only accept input + + AFTER ADDING A WORK NOTE: + - Do NOT expect to see it in work_notes field (will be empty) + - Tell user: "Work note added to incident journal successfully" + - Add: "(Note: Work notes are visible in ServiceNow UI, not in API GET responses)" + - The note WAS added even though field shows empty + + EXAMPLE: + You send: PATCH incident/abc123 with body {"work_notes": "Investigating issue"} + API returns: HTTP 200 success with updated incident ✅ + You query: GET incident/abc123 + Result: work_notes="" ← EMPTY is NORMAL, note was still written! + example: "Investigating email server logs. Found timeout errors in SMTP connector." + + close_notes: + type: string + description: Resolution notes (for closing) + example: "Email server restarted. Service restored." + + priority: + type: string + description: Updated priority + example: "3" + + assigned_to: + type: string + description: Reassign to user (name or sys_id) + example: "" + + responses: + '200': + description: Incident updated successfully + content: + application/json: + schema: + type: object + properties: + result: + $ref: '#/components/schemas/Incident' + + '400': + description: Bad request + '404': + description: Incident not found + '401': + description: Unauthorized + + /stats/incident: + get: + operationId: getIncidentStats + summary: Get incident statistics + description: Retrieve aggregated statistics for incidents + parameters: + - name: sysparm_query + in: query + required: false + schema: + type: string + description: Query filter for statistics + example: "sys_created_onONLast 30 days@gs.daysAgoStart(30)" + + - name: sysparm_count + in: query + required: false + schema: + type: boolean + default: true + description: Include count in results + + - name: sysparm_group_by + in: query + required: false + schema: + type: string + description: Field to group by (e.g., priority, category, assigned_to) + example: "category" + + - name: sysparm_avg_fields + in: query + required: false + schema: + type: string + description: Fields to calculate average + example: "business_duration" + + - name: sysparm_sum_fields + in: query + required: false + schema: + type: string + description: Fields to sum + example: "" + + responses: + '200': + description: Statistics retrieved successfully + content: + application/json: + schema: + type: object + properties: + result: + type: array + items: + type: object + properties: + groupby_value: + type: string + count: + type: string + avg: + type: object + sum: + type: object + example: + result: + - groupby_value: "Email" + count: "45" + - groupby_value: "Network" + count: "32" + - groupby_value: "Hardware" + count: "18" + + '401': + description: Unauthorized + +components: + securitySchemes: + basicAuth: + type: http + scheme: basic + description: | + HTTP Basic Authentication using ServiceNow username and password. + + Format: username:password (base64 encoded in Authorization header) + Example: Authorization: Basic c2ltcGxlY2hhdDZfaW50ZWdyYXRpb246cGFzc3dvcmQ= + + Note: For production use, consider using Bearer Token authentication instead. + + schemas: + Incident: + type: object + properties: + sys_id: + type: string + description: Unique system identifier + example: "abc123xyz789" + + number: + type: string + description: Incident number + example: "INC0000001" + + short_description: + type: string + description: Brief summary + example: "Email server not responding" + + description: + type: string + description: Detailed description + example: "Users cannot access email. Server shows timeout errors." + + priority: + type: string + description: "Priority: 1=Critical, 2=High, 3=Moderate, 4=Low, 5=Planning" + example: "2" + + urgency: + type: string + description: "Urgency: 1=High, 2=Medium, 3=Low" + example: "1" + + impact: + type: string + description: "Impact: 1=High, 2=Medium, 3=Low" + example: "2" + + state: + type: string + description: "State: 1=New, 2=In Progress, 3=On Hold, 6=Resolved, 7=Closed, 8=Canceled" + example: "1" + + category: + type: string + description: Incident category + example: "Email" + + subcategory: + type: string + description: Incident subcategory + example: "Server" + + assigned_to: + type: string + description: Assigned user + example: "John Doe" + + assignment_group: + type: string + description: Assignment group + example: "IT Support" + + caller_id: + type: string + description: User who reported the incident + example: "Jane Smith" + + sys_created_on: + type: string + format: date-time + description: Creation timestamp + example: "2026-01-15 10:30:00" + + sys_updated_on: + type: string + format: date-time + description: Last update timestamp + example: "2026-01-15 14:22:00" + + opened_at: + type: string + format: date-time + description: When incident was opened + example: "2026-01-15 10:30:00" + + resolved_at: + type: string + format: date-time + description: Resolution timestamp + example: "2026-01-15 16:45:00" + + closed_at: + type: string + format: date-time + description: Closure timestamp + example: "2026-01-16 09:00:00" + + work_notes: + type: string + description: Work notes (internal) + example: "Investigating email server logs" + + close_notes: + type: string + description: Resolution notes + example: "Issue resolved by restarting email service" + + business_duration: + type: string + description: Business hours duration + example: "2 hours 30 minutes" diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_create_asset_openapi.json b/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_create_asset_openapi.json new file mode 100644 index 00000000..9fe33fa7 --- /dev/null +++ b/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_create_asset_openapi.json @@ -0,0 +1,231 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "ServiceNow Asset Creation API", + "description": "ServiceNow REST API for creating new assets", + "version": "1.0.0", + "contact": { + "name": "ServiceNow API Support", + "url": "https://developer.servicenow.com" + } + }, + "servers": [ + { + "url": "https://YOUR-INSTANCE.service-now.com/api/now", + "description": "ServiceNow Instance" + } + ], + "security": [ + { + "bearerAuth": [] + } + ], + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "OAuth 2.0 Bearer Token authentication. Obtain token via ServiceNow OAuth endpoint using Resource Owner Password Credentials grant." + } + }, + "schemas": { + "AssetCreateRequest": { + "type": "object", + "required": ["asset_tag", "display_name"], + "properties": { + "asset_tag": { + "type": "string", + "description": "Unique asset tag identifier (required)", + "example": "P1000500" + }, + "display_name": { + "type": "string", + "description": "Display name for the asset (required)", + "example": "Jane's Laptop" + }, + "model": { + "type": "string", + "description": "Model reference (sys_id or display name)", + "example": "Dell Latitude 5420" + }, + "model_category": { + "type": "string", + "description": "Model category (sys_id or name)", + "example": "Computer" + }, + "serial_number": { + "type": "string", + "description": "Serial number of the asset", + "example": "SN987654321" + }, + "assigned_to": { + "type": "string", + "description": "User assigned to the asset (sys_id or name)", + "example": "Jane Smith" + }, + "assignment_group": { + "type": "string", + "description": "Assignment group (sys_id or name)", + "example": "IT Support" + }, + "location": { + "type": "string", + "description": "Physical location (sys_id or name)", + "example": "Building A, Floor 3" + }, + "install_status": { + "type": "string", + "description": "Installation status: 1=In use, 6=In stock, 7=Retired", + "example": "6", + "enum": ["1", "6", "7"] + }, + "substatus": { + "type": "string", + "description": "Substatus of the asset", + "example": "available" + }, + "purchase_date": { + "type": "string", + "format": "date", + "description": "Purchase date (YYYY-MM-DD)", + "example": "2024-01-15" + }, + "warranty_expiration": { + "type": "string", + "format": "date", + "description": "Warranty expiration date (YYYY-MM-DD)", + "example": "2027-01-15" + }, + "cost": { + "type": "string", + "description": "Asset cost in dollars", + "example": "1500.00" + }, + "department": { + "type": "string", + "description": "Department (sys_id or name)", + "example": "IT" + }, + "managed_by": { + "type": "string", + "description": "Manager of the asset (sys_id or name)", + "example": "John Manager" + }, + "owned_by": { + "type": "string", + "description": "Owner of the asset (sys_id or name)", + "example": "IT Department" + }, + "comments": { + "type": "string", + "description": "Additional comments or notes", + "example": "Purchased for new hire" + } + } + }, + "AssetCreateResponse": { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "sys_id": { + "type": "string", + "description": "Newly created asset sys_id" + }, + "asset_tag": { + "type": "string", + "description": "Asset tag" + }, + "display_name": { + "type": "string", + "description": "Display name" + }, + "sys_created_on": { + "type": "string", + "format": "date-time", + "description": "Creation timestamp" + } + } + } + } + } + } + }, + "paths": { + "/table/alm_asset": { + "post": { + "summary": "Create new asset", + "description": "Create a new asset in ServiceNow\n\nRequired fields:\n- asset_tag: Unique asset identifier\n- display_name: Display name for the asset\n\nOptional fields:\n- model: Model reference (sys_id or display name)\n- serial_number: Serial number\n- assigned_to: User reference (sys_id or name)\n- location: Location reference (sys_id or name)\n- install_status: Status (1=In use, 6=In stock, 7=Retired)\n- purchase_date: Purchase date (YYYY-MM-DD)\n- warranty_expiration: Warranty date (YYYY-MM-DD)\n- cost: Asset cost\n- department: Department reference\n- managed_by: Manager reference\n- owned_by: Owner reference\n- comments: Additional notes\n\nThe API will return the newly created asset with its sys_id.", + "operationId": "createAsset", + "requestBody": { + "required": true, + "description": "Asset data to create", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetCreateRequest" + }, + "examples": { + "laptop": { + "summary": "Create laptop asset", + "value": { + "asset_tag": "P1000500", + "display_name": "Jane's Laptop", + "model": "Dell Latitude 5420", + "serial_number": "SN987654321", + "assigned_to": "Jane Smith", + "location": "Building A, Floor 3", + "install_status": "6", + "purchase_date": "2024-01-15", + "warranty_expiration": "2027-01-15", + "cost": "1500.00" + } + }, + "monitor": { + "summary": "Create monitor asset", + "value": { + "asset_tag": "M2000100", + "display_name": "Conference Room Monitor", + "model": "Dell U2720Q", + "serial_number": "MON123456", + "location": "Conference Room A", + "install_status": "1", + "cost": "550.00" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Asset created successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetCreateResponse" + } + } + } + }, + "400": { + "description": "Bad Request - Missing required fields or invalid data" + }, + "401": { + "description": "Unauthorized - Invalid or missing authentication token" + }, + "409": { + "description": "Conflict - Asset tag already exists" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + } + } +} diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_delete_asset_openapi.json b/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_delete_asset_openapi.json new file mode 100644 index 00000000..ef861467 --- /dev/null +++ b/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_delete_asset_openapi.json @@ -0,0 +1,73 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "ServiceNow Asset Deletion API", + "description": "ServiceNow REST API for deleting assets", + "version": "1.0.0", + "contact": { + "name": "ServiceNow API Support", + "url": "https://developer.servicenow.com" + } + }, + "servers": [ + { + "url": "https://YOUR-INSTANCE.service-now.com/api/now", + "description": "ServiceNow Instance" + } + ], + "security": [ + { + "bearerAuth": [] + } + ], + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "OAuth 2.0 Bearer Token authentication. Obtain token via ServiceNow OAuth endpoint using Resource Owner Password Credentials grant." + } + } + }, + "paths": { + "/table/alm_asset/{sys_id}": { + "delete": { + "summary": "Delete asset", + "description": "Delete an asset from ServiceNow\n\n⚠️ CRITICAL: This operation requires the sys_id (NOT the asset tag) ⚠️\n\nThis is a DESTRUCTIVE operation that permanently removes the asset record.\n\n- sys_id is a unique identifier like \"32ac0eaec326361067d91a2ed40131a7\"\n- Asset tag is like \"P1000234\"\n\nREQUIRED EXECUTION PATTERN:\n\nWhen user asks to delete asset by tag (e.g., \"Delete asset P1000234\"):\n\nCALL 1 - Query to Get sys_id:\n1. Call queryAssets(sysparm_query=\"asset_tag=P1000234\", sysparm_fields=\"sys_id,asset_tag,display_name,install_status\")\n2. Extract sys_id from result\n3. Verify exactly 1 result returned\n4. OPTIONAL: Display asset details to user for confirmation before deletion\n\nCALL 2 - Delete with Retrieved sys_id:\n1. Call deleteAsset(sys_id=\"32ac0eaec326361067d91a2ed40131a7\")\n2. This will permanently delete the asset\n\nBest Practices:\n- Always confirm with user before deleting\n- Show asset details (tag, name, assigned user) before deletion\n- Consider retiring assets (install_status=7) instead of deleting\n- Deletion is permanent and cannot be undone\n- Check if asset has related records (CIs, contracts) that may be affected\n\nPermissions:\n- User must have delete rights on alm_asset table\n- Some organizations restrict asset deletion for compliance/audit reasons", + "operationId": "deleteAsset", + "parameters": [ + { + "name": "sys_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The unique sys_id of the asset to delete (obtain via queryAssets first)", + "example": "32ac0eaec326361067d91a2ed40131a7" + } + ], + "responses": { + "204": { + "description": "Asset deleted successfully - No content returned" + }, + "404": { + "description": "Asset not found - Invalid sys_id or already deleted" + }, + "401": { + "description": "Unauthorized - Invalid or missing authentication token" + }, + "403": { + "description": "Forbidden - User lacks delete permissions on alm_asset table" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + } + } +} diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_incident_api - basic auth sample.yaml b/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_incident_api - basic auth sample.yaml new file mode 100644 index 00000000..4826b08a --- /dev/null +++ b/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_incident_api - basic auth sample.yaml @@ -0,0 +1,498 @@ +openapi: 3.0.0 +info: + title: ServiceNow Incident Management API + description: ServiceNow REST API for incident management operations - Optimized for Simple Chat integration + version: 1.0.0 + contact: + name: ServiceNow API Support + url: https://developer.servicenow.com + +servers: + - url: https://YOUR-INSTANCE.service-now.com/api/now + description: ServiceNow Developer Instance + +security: + - basicAuth: [] + +paths: + /table/incident: + get: + operationId: queryIncidents + summary: Query incidents with filters + description: Retrieve incidents from ServiceNow based on query parameters and filters + parameters: + - name: sysparm_query + in: query + required: false + schema: + type: string + description: | + Encoded query string for filtering results. Examples: + - Last 7 days: sys_created_onONLast 7 days@gs.daysAgoStart(7) + - By priority: priority=1 + - By state: state=1 (1=New, 2=In Progress, 6=Resolved, 7=Closed) + - Combined: priority=1^state=1^sys_created_onONLast 30 days@gs.daysAgoStart(30) + example: "sys_created_onONLast 7 days@gs.daysAgoStart(7)" + + - name: sysparm_limit + in: query + required: false + schema: + type: integer + default: 100 + maximum: 10000 + description: Maximum number of records to return + example: 50 + + - name: sysparm_offset + in: query + required: false + schema: + type: integer + default: 0 + description: Number of records to skip for pagination + example: 0 + + - name: sysparm_fields + in: query + required: false + schema: + type: string + description: Comma-separated list of fields to return + example: "number,short_description,priority,state,assigned_to,sys_created_on,category" + + - name: sysparm_display_value + in: query + required: false + schema: + type: string + enum: [true, false, all] + default: "false" + description: Return display values instead of actual values + example: "true" + + responses: + '200': + description: List of incidents retrieved successfully + content: + application/json: + schema: + type: object + properties: + result: + type: array + items: + $ref: '#/components/schemas/Incident' + example: + result: + - number: "INC0000001" + short_description: "Email server not responding" + priority: "2" + state: "1" + sys_id: "abc123xyz" + sys_created_on: "2026-01-15 10:30:00" + + '401': + description: Unauthorized - Invalid credentials + '403': + description: Forbidden - Insufficient permissions + + post: + operationId: createIncident + summary: Create a new incident + description: Create a new incident in ServiceNow + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - short_description + properties: + short_description: + type: string + description: Brief summary of the incident + example: "Email server not responding for Finance team" + + description: + type: string + description: Detailed description of the incident + example: "Users in Finance department cannot access email server. Error message: Connection timeout." + + urgency: + type: string + enum: ["1", "2", "3"] + description: "Urgency level: 1=High, 2=Medium, 3=Low" + example: "1" + + priority: + type: string + enum: ["1", "2", "3", "4", "5"] + description: "Priority: 1=Critical, 2=High, 3=Moderate, 4=Low, 5=Planning" + example: "2" + + impact: + type: string + enum: ["1", "2", "3"] + description: "Impact: 1=High, 2=Medium, 3=Low" + example: "2" + + category: + type: string + description: Incident category + example: "Email" + + subcategory: + type: string + description: Incident subcategory + example: "Server" + + assignment_group: + type: string + description: Assignment group name or sys_id + example: "IT Support" + + assigned_to: + type: string + description: Assigned user name or sys_id + example: "" + + caller_id: + type: string + description: User reporting the incident (name or sys_id) + example: "" + + responses: + '201': + description: Incident created successfully + content: + application/json: + schema: + type: object + properties: + result: + $ref: '#/components/schemas/Incident' + example: + result: + number: "INC0010025" + sys_id: "xyz789abc" + short_description: "Email server not responding for Finance team" + state: "1" + + '400': + description: Bad request - Invalid input + '401': + description: Unauthorized + '403': + description: Forbidden + + /table/incident/{sys_id}: + get: + operationId: getIncidentDetails + summary: Get incident details by sys_id + description: Retrieve details of a specific incident + parameters: + - name: sys_id + in: path + required: true + schema: + type: string + description: The sys_id of the incident + example: "abc123xyz789" + + - name: sysparm_fields + in: query + required: false + schema: + type: string + description: Comma-separated list of fields to return + example: "number,short_description,description,priority,state,assigned_to,sys_created_on,resolved_at,close_notes" + + - name: sysparm_display_value + in: query + required: false + schema: + type: string + enum: [true, false, all] + default: "false" + description: Return display values + + responses: + '200': + description: Incident details retrieved successfully + content: + application/json: + schema: + type: object + properties: + result: + $ref: '#/components/schemas/Incident' + + '404': + description: Incident not found + '401': + description: Unauthorized + + put: + operationId: updateIncident + summary: Update an existing incident + description: Update incident fields + parameters: + - name: sys_id + in: path + required: true + schema: + type: string + description: The sys_id of the incident to update + example: "abc123xyz789" + + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + state: + type: string + description: "State: 1=New, 2=In Progress, 3=On Hold, 6=Resolved, 7=Closed, 8=Canceled" + example: "6" + + work_notes: + type: string + description: Work notes to add + example: "Issue resolved by restarting email service" + + close_notes: + type: string + description: Resolution notes (for closing) + example: "Email server restarted. Service restored." + + priority: + type: string + description: Updated priority + example: "3" + + assigned_to: + type: string + description: Reassign to user (name or sys_id) + example: "" + + responses: + '200': + description: Incident updated successfully + content: + application/json: + schema: + type: object + properties: + result: + $ref: '#/components/schemas/Incident' + + '400': + description: Bad request + '404': + description: Incident not found + '401': + description: Unauthorized + + /stats/incident: + get: + operationId: getIncidentStats + summary: Get incident statistics + description: Retrieve aggregated statistics for incidents + parameters: + - name: sysparm_query + in: query + required: false + schema: + type: string + description: Query filter for statistics + example: "sys_created_onONLast 30 days@gs.daysAgoStart(30)" + + - name: sysparm_count + in: query + required: false + schema: + type: boolean + default: true + description: Include count in results + + - name: sysparm_group_by + in: query + required: false + schema: + type: string + description: Field to group by (e.g., priority, category, assigned_to) + example: "category" + + - name: sysparm_avg_fields + in: query + required: false + schema: + type: string + description: Fields to calculate average + example: "business_duration" + + - name: sysparm_sum_fields + in: query + required: false + schema: + type: string + description: Fields to sum + example: "" + + responses: + '200': + description: Statistics retrieved successfully + content: + application/json: + schema: + type: object + properties: + result: + type: array + items: + type: object + properties: + groupby_value: + type: string + count: + type: string + avg: + type: object + sum: + type: object + example: + result: + - groupby_value: "Email" + count: "45" + - groupby_value: "Network" + count: "32" + - groupby_value: "Hardware" + count: "18" + + '401': + description: Unauthorized + +components: + securitySchemes: + basicAuth: + type: http + scheme: basic + description: Basic authentication using ServiceNow username and password + + schemas: + Incident: + type: object + properties: + sys_id: + type: string + description: Unique system identifier + example: "abc123xyz789" + + number: + type: string + description: Incident number + example: "INC0000001" + + short_description: + type: string + description: Brief summary + example: "Email server not responding" + + description: + type: string + description: Detailed description + example: "Users cannot access email. Server shows timeout errors." + + priority: + type: string + description: "Priority: 1=Critical, 2=High, 3=Moderate, 4=Low, 5=Planning" + example: "2" + + urgency: + type: string + description: "Urgency: 1=High, 2=Medium, 3=Low" + example: "1" + + impact: + type: string + description: "Impact: 1=High, 2=Medium, 3=Low" + example: "2" + + state: + type: string + description: "State: 1=New, 2=In Progress, 3=On Hold, 6=Resolved, 7=Closed, 8=Canceled" + example: "1" + + category: + type: string + description: Incident category + example: "Email" + + subcategory: + type: string + description: Incident subcategory + example: "Server" + + assigned_to: + type: string + description: Assigned user + example: "John Doe" + + assignment_group: + type: string + description: Assignment group + example: "IT Support" + + caller_id: + type: string + description: User who reported the incident + example: "Jane Smith" + + sys_created_on: + type: string + format: date-time + description: Creation timestamp + example: "2026-01-15 10:30:00" + + sys_updated_on: + type: string + format: date-time + description: Last update timestamp + example: "2026-01-15 14:22:00" + + opened_at: + type: string + format: date-time + description: When incident was opened + example: "2026-01-15 10:30:00" + + resolved_at: + type: string + format: date-time + description: Resolution timestamp + example: "2026-01-15 16:45:00" + + closed_at: + type: string + format: date-time + description: Closure timestamp + example: "2026-01-16 09:00:00" + + work_notes: + type: string + description: Work notes (internal) + example: "Investigating email server logs" + + close_notes: + type: string + description: Resolution notes + example: "Issue resolved by restarting email service" + + business_duration: + type: string + description: Business hours duration + example: "2 hours 30 minutes" diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_query_assets_openapi.json b/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_query_assets_openapi.json new file mode 100644 index 00000000..f8ed10c9 --- /dev/null +++ b/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_query_assets_openapi.json @@ -0,0 +1,283 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "ServiceNow Asset Query API", + "description": "ServiceNow REST API for querying assets and retrieving asset details", + "version": "1.0.0", + "contact": { + "name": "ServiceNow API Support", + "url": "https://developer.servicenow.com" + } + }, + "servers": [ + { + "url": "https://YOUR-INSTANCE.service-now.com/api/now", + "description": "ServiceNow Instance" + } + ], + "security": [ + { + "bearerAuth": [] + } + ], + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "OAuth 2.0 Bearer Token authentication. Obtain token via ServiceNow OAuth endpoint using Resource Owner Password Credentials grant." + } + }, + "schemas": { + "Asset": { + "type": "object", + "properties": { + "sys_id": { + "type": "string", + "description": "Unique system identifier", + "example": "abc123xyz789" + }, + "asset_tag": { + "type": "string", + "description": "Asset tag/identifier", + "example": "P1000234" + }, + "display_name": { + "type": "string", + "description": "Display name of the asset", + "example": "John Doe's Laptop" + }, + "model": { + "type": "string", + "description": "Asset model", + "example": "Dell Latitude 5420" + }, + "model_category": { + "type": "string", + "description": "Model category", + "example": "Computer" + }, + "serial_number": { + "type": "string", + "description": "Serial number", + "example": "SN123456789" + }, + "assigned_to": { + "type": "string", + "description": "User assigned to the asset", + "example": "John Doe" + }, + "assignment_group": { + "type": "string", + "description": "Assignment group", + "example": "IT Support" + }, + "location": { + "type": "string", + "description": "Physical location", + "example": "Building A, Floor 3" + }, + "install_status": { + "type": "string", + "description": "Installation status: 1=In use, 6=In stock, 7=Retired", + "example": "1" + }, + "substatus": { + "type": "string", + "description": "Substatus of the asset", + "example": "available" + }, + "purchase_date": { + "type": "string", + "format": "date", + "description": "Purchase date", + "example": "2023-01-15" + }, + "warranty_expiration": { + "type": "string", + "format": "date", + "description": "Warranty expiration date", + "example": "2026-01-15" + }, + "cost": { + "type": "string", + "description": "Asset cost", + "example": "1200.00" + }, + "sys_created_on": { + "type": "string", + "format": "date-time", + "description": "Creation timestamp", + "example": "2023-01-15T10:30:00" + }, + "sys_updated_on": { + "type": "string", + "format": "date-time", + "description": "Last update timestamp", + "example": "2023-06-20T14:22:00" + } + } + } + } + }, + "paths": { + "/table/alm_asset": { + "get": { + "summary": "Query assets", + "description": "Retrieve assets from ServiceNow based on query parameters and filters\n\nParameters:\n- sysparm_query (optional): Encoded query string for filtering results. Examples:\n - By asset tag: asset_tag=P1000234\n - By assigned user: assigned_to.name=John Doe\n - By model: model.display_name=Dell Latitude\n - By install status: install_status=1 (1=In use, 6=In stock, 7=Retired)\n - Combined: install_status=1^assigned_to.name=John Doe\n\n- sysparm_limit (optional): Maximum number of records to return\n- sysparm_offset (optional): Number of records to skip for pagination\n- sysparm_fields (optional): Comma-separated list of fields to return.\n CRITICAL: Always include sys_id field - it's required for follow-up queries.\n\n- sysparm_display_value (optional): Return display values instead of actual values", + "operationId": "queryAssets", + "parameters": [ + { + "name": "sysparm_query", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Encoded query string for filtering", + "example": "install_status=1" + }, + { + "name": "sysparm_limit", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Maximum number of records to return", + "example": 50 + }, + { + "name": "sysparm_offset", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Number of records to skip for pagination", + "example": 0 + }, + { + "name": "sysparm_fields", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to return", + "example": "sys_id,asset_tag,display_name,assigned_to,install_status" + }, + { + "name": "sysparm_display_value", + "in": "query", + "required": false, + "schema": { + "type": "boolean" + }, + "description": "Return display values instead of sys_ids", + "example": true + } + ], + "responses": { + "200": { + "description": "Assets retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "result": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Asset" + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized - Invalid or missing authentication token" + }, + "400": { + "description": "Bad Request - Invalid query parameters" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/table/alm_asset/{sys_id}": { + "get": { + "summary": "Get asset details", + "description": "Retrieve details of a specific asset.\n\nCRITICAL: This operation requires the sys_id (NOT the asset tag).\n- sys_id is a unique identifier like \"9d385017c611228701d22104cc95c371\"\n- Asset tag is like \"P1000234\"\n\nWhen user asks about an asset by tag:\n1. First use queryAssets with sysparm_query=asset_tag=P1000234\n2. Get the sys_id from the result\n3. Then call getAssetDetails with that sys_id\n\nParameters:\n- sys_id (required): The sys_id of the asset\n- sysparm_fields (optional): Comma-separated list of fields to return\n- sysparm_display_value (optional): Return display values", + "operationId": "getAssetDetails", + "parameters": [ + { + "name": "sys_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The unique sys_id of the asset", + "example": "9d385017c611228701d22104cc95c371" + }, + { + "name": "sysparm_fields", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to return", + "example": "sys_id,asset_tag,display_name,model,serial_number,assigned_to,location,install_status" + }, + { + "name": "sysparm_display_value", + "in": "query", + "required": false, + "schema": { + "type": "boolean" + }, + "description": "Return display values instead of sys_ids", + "example": true + } + ], + "responses": { + "200": { + "description": "Asset details retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/Asset" + } + } + } + } + } + }, + "404": { + "description": "Asset not found" + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + } + } +} diff --git a/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_update_asset_openapi.json b/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_update_asset_openapi.json new file mode 100644 index 00000000..c42bdf1f --- /dev/null +++ b/docs/how-to/agents/ServiceNow/open_api_specs/servicenow_update_asset_openapi.json @@ -0,0 +1,224 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "ServiceNow Asset Update API", + "description": "ServiceNow REST API for updating existing assets", + "version": "1.0.0", + "contact": { + "name": "ServiceNow API Support", + "url": "https://developer.servicenow.com" + } + }, + "servers": [ + { + "url": "https://YOUR-INSTANCE.service-now.com/api/now", + "description": "ServiceNow Instance" + } + ], + "security": [ + { + "bearerAuth": [] + } + ], + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "OAuth 2.0 Bearer Token authentication. Obtain token via ServiceNow OAuth endpoint using Resource Owner Password Credentials grant." + } + }, + "schemas": { + "AssetUpdateRequest": { + "type": "object", + "properties": { + "display_name": { + "type": "string", + "description": "Update display name", + "example": "Bob's Laptop (Updated)" + }, + "assigned_to": { + "type": "string", + "description": "Update assigned user (sys_id or name)", + "example": "Bob Johnson" + }, + "assignment_group": { + "type": "string", + "description": "Update assignment group", + "example": "IT Support" + }, + "location": { + "type": "string", + "description": "Update location (sys_id or name)", + "example": "Building B, Floor 2" + }, + "install_status": { + "type": "string", + "description": "Update status: 1=In use, 6=In stock, 7=Retired", + "example": "1", + "enum": ["1", "6", "7"] + }, + "substatus": { + "type": "string", + "description": "Update substatus", + "example": "in_use" + }, + "serial_number": { + "type": "string", + "description": "Update serial number", + "example": "SN999888777" + }, + "warranty_expiration": { + "type": "string", + "format": "date", + "description": "Update warranty expiration (YYYY-MM-DD)", + "example": "2028-01-15" + }, + "cost": { + "type": "string", + "description": "Update cost", + "example": "1800.00" + }, + "department": { + "type": "string", + "description": "Update department", + "example": "Engineering" + }, + "managed_by": { + "type": "string", + "description": "Update manager", + "example": "New Manager" + }, + "owned_by": { + "type": "string", + "description": "Update owner", + "example": "Engineering Department" + }, + "comments": { + "type": "string", + "description": "Update or add comments", + "example": "Reassigned to Engineering team" + } + } + }, + "AssetUpdateResponse": { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "sys_id": { + "type": "string", + "description": "Asset sys_id" + }, + "asset_tag": { + "type": "string", + "description": "Asset tag" + }, + "display_name": { + "type": "string", + "description": "Updated display name" + }, + "sys_updated_on": { + "type": "string", + "format": "date-time", + "description": "Last update timestamp" + } + } + } + } + } + } + }, + "paths": { + "/table/alm_asset/{sys_id}": { + "patch": { + "summary": "Update asset", + "description": "Update asset fields\n\n⚠️ CRITICAL: YOU MUST QUERY FOR sys_id FIRST - DO NOT USE CACHED VALUES ⚠️\n\nThis operation requires the sys_id (NOT the asset tag).\n- sys_id is a unique identifier like \"32ac0eaec326361067d91a2ed40131a7\"\n- Asset tag is like \"P1000234\"\n\nREQUIRED EXECUTION PATTERN:\n\nWhen user asks to update asset P1000234:\n\nCALL 1 - Query to Get sys_id:\n1. Call queryAssets(sysparm_query=\"asset_tag=P1000234\", sysparm_fields=\"sys_id,asset_tag\")\n2. Extract sys_id from result\n3. Verify you got exactly 1 result\n\nCALL 2 - Update with Retrieved sys_id:\n1. Call updateAsset(sys_id=\"32ac0eaec326361067d91a2ed40131a7\", ...fields)\n2. This will succeed because you have the correct sys_id\n\nUpdatable Fields:\n- display_name: Asset display name\n- assigned_to: User assignment\n- assignment_group: Group assignment\n- location: Physical location\n- install_status: Status (1=In use, 6=In stock, 7=Retired)\n- substatus: Substatus\n- serial_number: Serial number\n- warranty_expiration: Warranty date\n- cost: Asset cost\n- department: Department\n- managed_by: Manager\n- owned_by: Owner\n- comments: Notes", + "operationId": "updateAsset", + "parameters": [ + { + "name": "sys_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The unique sys_id of the asset to update (obtain via queryAssets first)", + "example": "32ac0eaec326361067d91a2ed40131a7" + } + ], + "requestBody": { + "required": true, + "description": "Fields to update (only include fields you want to change)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetUpdateRequest" + }, + "examples": { + "reassign": { + "summary": "Reassign asset to new user", + "value": { + "assigned_to": "Bob Johnson", + "install_status": "1", + "comments": "Reassigned to Bob" + } + }, + "relocate": { + "summary": "Move asset to new location", + "value": { + "location": "Building B, Floor 5", + "comments": "Moved to new office" + } + }, + "retire": { + "summary": "Retire asset", + "value": { + "install_status": "7", + "assigned_to": "", + "comments": "Asset retired - end of life" + } + }, + "update_warranty": { + "summary": "Extend warranty", + "value": { + "warranty_expiration": "2028-12-31", + "comments": "Warranty extended" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Asset updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetUpdateResponse" + } + } + } + }, + "404": { + "description": "Asset not found - Invalid sys_id" + }, + "400": { + "description": "Bad Request - Invalid field values" + }, + "401": { + "description": "Unauthorized - Invalid or missing authentication token" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + } + } +}