// Copyright (c) Microsoft. All rights reserved. using System.Diagnostics.CodeAnalysis; namespace Microsoft.Agents.AI.DurableTask.Workflows; /// /// Provides helper methods for workflow naming conventions used in durable orchestrations. /// internal static class WorkflowNamingHelper { internal const string OrchestrationFunctionPrefix = "dafx-"; private const char ExecutorIdSuffixSeparator = '_'; /// /// Converts a workflow name to its corresponding orchestration function name. /// /// The workflow name. /// The orchestration function name. /// Thrown when the workflow name is null or empty. internal static string ToOrchestrationFunctionName(string workflowName) { ArgumentException.ThrowIfNullOrEmpty(workflowName); return string.Concat(OrchestrationFunctionPrefix, workflowName); } /// /// Converts an orchestration function name back to its workflow name. /// /// The orchestration function name. /// The workflow name. /// Thrown when the orchestration function name is null, empty, or doesn't have the expected prefix. internal static string ToWorkflowName(string orchestrationFunctionName) { ArgumentException.ThrowIfNullOrEmpty(orchestrationFunctionName); if (!TryGetWorkflowName(orchestrationFunctionName, out string? workflowName)) { throw new ArgumentException( $"Orchestration function name '{orchestrationFunctionName}' does not have the expected '{OrchestrationFunctionPrefix}' prefix or is missing a workflow name.", nameof(orchestrationFunctionName)); } return workflowName; } /// /// Extracts the executor name from an executor ID. /// /// /// /// For non-agentic executors, the executor ID is the same as the executor name (e.g., "OrderParser"). /// /// /// For agentic executors, the workflow builder appends a GUID suffix separated by an underscore /// (e.g., "Physicist_8884e71021334ce49517fa2b17b1695b"). This method extracts just the name portion. /// /// /// The executor ID, which may contain a GUID suffix. /// The executor name without any GUID suffix. /// Thrown when the executor ID is null or empty. internal static string GetExecutorName(string executorId) { ArgumentException.ThrowIfNullOrEmpty(executorId); int separatorIndex = executorId.LastIndexOf(ExecutorIdSuffixSeparator); if (separatorIndex > 0) { ReadOnlySpan suffix = executorId.AsSpan(separatorIndex + 1); if (IsGuidSuffix(suffix)) { return executorId[..separatorIndex]; } } return executorId; } /// /// Checks whether the given span looks like a sanitized GUID (32 hex characters). /// private static bool IsGuidSuffix(ReadOnlySpan value) { if (value.Length != 32) { return false; } foreach (char c in value) { if (!char.IsAsciiHexDigit(c)) { return false; } } return true; } private static bool TryGetWorkflowName(string? orchestrationFunctionName, [NotNullWhen(true)] out string? workflowName) { workflowName = null; if (string.IsNullOrEmpty(orchestrationFunctionName) || !orchestrationFunctionName.StartsWith(OrchestrationFunctionPrefix, StringComparison.Ordinal)) { return false; } workflowName = orchestrationFunctionName[OrchestrationFunctionPrefix.Length..]; return workflowName.Length > 0; } }