diff --git a/.dockerignore b/.dockerignore index 70779ad0..02089233 100644 --- a/.dockerignore +++ b/.dockerignore @@ -18,3 +18,4 @@ **/__pycache__ **/.pytest_cache **/.venv +.azure diff --git a/.gitignore b/.gitignore index 31f7e8f7..e08f9ce5 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* +.azure diff --git a/aspire-orchestrator/.editorconfig b/aspire-orchestrator/.editorconfig new file mode 100644 index 00000000..06817985 --- /dev/null +++ b/aspire-orchestrator/.editorconfig @@ -0,0 +1,407 @@ +# To learn more about .editorconfig see https://aka.ms/editorconfigdocs +############################### +# Core EditorConfig Options # +############################### +root = true +# All files +[*] +indent_style = space +end_of_line = lf + +# XML project files +[*.{vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML config files +[*.{targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# XML config files +[*.csproj] +indent_size = 4 + +# XML config files +[*.props] +indent_size = 4 + +[Directory.Packages.props] +indent_size = 2 + +# YAML config files +[*.{yml,yaml}] +tab_width = 2 +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +# JSON config files +[*.json] +tab_width = 2 +indent_size = 2 +insert_final_newline = false +trim_trailing_whitespace = true + +# Typescript files +[*.{ts,tsx}] +insert_final_newline = true +trim_trailing_whitespace = true +tab_width = 4 +indent_size = 4 +file_header_template = Copyright (c) Microsoft. All rights reserved. + +# Stylesheet files +[*.{css,scss,sass,less}] +insert_final_newline = true +trim_trailing_whitespace = true +tab_width = 4 +indent_size = 4 + +# Code files +[*.{cs,csx,vb,vbx}] +tab_width = 4 +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8-bom +file_header_template = Copyright (c) Microsoft. All rights reserved. + +############################### +# .NET Coding Conventions # +############################### +[*.{cs,vb}] +# Organize usings +dotnet_sort_system_directives_first = true +# this. preferences +dotnet_style_qualification_for_field = true:error +dotnet_style_qualification_for_property = true:error +dotnet_style_qualification_for_method = true:error +dotnet_style_qualification_for_event = true:error +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:error +dotnet_style_readonly_field = true:suggestion +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:silent +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +# Code quality rules +dotnet_code_quality_unused_parameters = all:suggestion + +[*.cs] + +# TODO: enable this but stop "dotnet format" from applying incorrect fixes and introducing a lot of unwanted changes. +dotnet_analyzer_diagnostic.severity = none + +# Note: these settings cause "dotnet format" to fix the code. You should review each change if you uses "dotnet format". +dotnet_diagnostic.RCS1007.severity = warning # Add braces +dotnet_diagnostic.RCS1036.severity = warning # Remove unnecessary blank line. +dotnet_diagnostic.RCS1037.severity = warning # Remove trailing white-space. +dotnet_diagnostic.RCS1097.severity = warning # Remove redundant 'ToString' call. +dotnet_diagnostic.RCS1138.severity = warning # Add summary to documentation comment. +dotnet_diagnostic.RCS1139.severity = warning # Add summary element to documentation comment. +dotnet_diagnostic.RCS1168.severity = warning # Parameter name 'foo' differs from base name 'bar'. +dotnet_diagnostic.RCS1175.severity = warning # Unused 'this' parameter 'operation'. +dotnet_diagnostic.RCS1192.severity = warning # Unnecessary usage of verbatim string literal. +dotnet_diagnostic.RCS1194.severity = warning # Implement exception constructors. +dotnet_diagnostic.RCS1211.severity = warning # Remove unnecessary else clause. +dotnet_diagnostic.RCS1214.severity = warning # Unnecessary interpolated string. +dotnet_diagnostic.RCS1225.severity = warning # Make class sealed. +dotnet_diagnostic.RCS1232.severity = warning # Order elements in documentation comment. + +# Diagnostics elevated as warnings +dotnet_diagnostic.CA1000.severity = warning # Do not declare static members on generic types +dotnet_diagnostic.CA1031.severity = warning # Do not catch general exception types +dotnet_diagnostic.CA1050.severity = none # Declare types in namespaces +dotnet_diagnostic.CA1063.severity = warning # Implement IDisposable correctly +dotnet_diagnostic.CA1064.severity = warning # Exceptions should be public +dotnet_diagnostic.CA1303.severity = warning # Do not pass literals as localized parameters +dotnet_diagnostic.CA1416.severity = warning # Validate platform compatibility +dotnet_diagnostic.CA1508.severity = warning # Avoid dead conditional code +dotnet_diagnostic.CA1852.severity = warning # Sealed classes +dotnet_diagnostic.CA1859.severity = warning # Use concrete types when possible for improved performance +dotnet_diagnostic.CA1860.severity = warning # Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance +dotnet_diagnostic.CA2000.severity = warning # Call System.IDisposable.Dispose on object before all references to it are out of scope +dotnet_diagnostic.CA2007.severity = error # Do not directly await a Task +dotnet_diagnostic.CA2201.severity = warning # Exception type System.Exception is not sufficiently specific +dotnet_diagnostic.CA2225.severity = warning # Operator overloads have named alternates + +dotnet_diagnostic.IDE0001.severity = warning # Simplify name +dotnet_diagnostic.IDE0005.severity = warning # Remove unnecessary using directives +dotnet_diagnostic.IDE1006.severity = warning # Code style errors, e.g. dotnet_naming_rule rules violations +dotnet_diagnostic.IDE0009.severity = warning # Add this or Me qualification +dotnet_diagnostic.IDE0011.severity = warning # Add braces +dotnet_diagnostic.IDE0018.severity = warning # Inline variable declaration +dotnet_diagnostic.IDE0032.severity = warning # Use auto-implemented property +dotnet_diagnostic.IDE0034.severity = warning # Simplify 'default' expression +dotnet_diagnostic.IDE0035.severity = warning # Remove unreachable code +dotnet_diagnostic.IDE0040.severity = warning # Add accessibility modifiers +dotnet_diagnostic.IDE0049.severity = warning # Use language keywords instead of framework type names for type references +dotnet_diagnostic.IDE0050.severity = warning # Convert anonymous type to tuple +dotnet_diagnostic.IDE0051.severity = warning # Remove unused private member +dotnet_diagnostic.IDE0055.severity = warning # Formatting rule +dotnet_diagnostic.IDE0060.severity = warning # Remove unused parameter +dotnet_diagnostic.IDE0070.severity = warning # Use 'System.HashCode.Combine' +dotnet_diagnostic.IDE0071.severity = warning # Simplify interpolation +dotnet_diagnostic.IDE0073.severity = warning # Require file header +dotnet_diagnostic.IDE0082.severity = warning # Convert typeof to nameof +dotnet_diagnostic.IDE0090.severity = warning # Simplify new expression +dotnet_diagnostic.IDE0130.severity = none # Namespace does not match folder structure +dotnet_diagnostic.IDE0161.severity = warning # Use file-scoped namespace + +dotnet_diagnostic.RCS1032.severity = warning # Remove redundant parentheses. +dotnet_diagnostic.RCS1118.severity = warning # Mark local variable as const. +dotnet_diagnostic.RCS1141.severity = warning # Add 'param' element to documentation comment. +dotnet_diagnostic.RCS1197.severity = warning # Optimize StringBuilder.AppendLine call. +dotnet_diagnostic.RCS1205.severity = warning # Order named arguments according to the order of parameters. +dotnet_diagnostic.RCS1229.severity = warning # Use async/await when necessary. + +dotnet_diagnostic.VSTHRD111.severity = error # Use .ConfigureAwait(bool) + +# Suppressed diagnostics + +# Commented out because `dotnet format` change can be disruptive. +# dotnet_diagnostic.RCS1085.severity = warning # Use auto-implemented property. + +# Commented out because `dotnet format` removes the xmldoc element, while we should add the missing documentation instead. +# dotnet_diagnostic.RCS1228.severity = warning # Unused element in documentation comment. + +dotnet_diagnostic.CA1002.severity = none # Change 'List' in '...' to use 'Collection' ... +dotnet_diagnostic.CA1032.severity = none # We're using RCS1194 which seems to cover more ctors +dotnet_diagnostic.CA1034.severity = none # Do not nest type. Alternatively, change its accessibility so that it is not externally visible +dotnet_diagnostic.CA1054.severity = none # URI parameters should not be strings +dotnet_diagnostic.CA1062.severity = none # Disable null check, C# already does it for us +dotnet_diagnostic.CS1591.severity = none # Missing XML comment for publicly visible type or member +dotnet_diagnostic.CA1805.severity = none # Member is explicitly initialized to its default value +dotnet_diagnostic.CA1822.severity = none # Member does not access instance data and can be marked as static +dotnet_diagnostic.CA1848.severity = none # For improved performance, use the LoggerMessage delegates +dotnet_diagnostic.CA2227.severity = none # Change to be read-only by removing the property setter +dotnet_diagnostic.CA2253.severity = none # Named placeholders in the logging message template should not be comprised of only numeric characters +dotnet_diagnostic.RCS1021.severity = none # Use expression-bodied lambda. +dotnet_diagnostic.RCS1061.severity = none # Merge 'if' with nested 'if'. +dotnet_diagnostic.RCS1069.severity = none # Remove unnecessary case label. +dotnet_diagnostic.RCS1074.severity = none # Remove redundant constructor. +dotnet_diagnostic.RCS1077.severity = none # Optimize LINQ method call. +dotnet_diagnostic.RCS1124.severity = none # Inline local variable. +dotnet_diagnostic.RCS1129.severity = none # Remove redundant field initialization. +dotnet_diagnostic.RCS1140.severity = none # Add exception to documentation comment. +dotnet_diagnostic.RCS1142.severity = none # Add 'typeparam' element to documentation comment. +dotnet_diagnostic.RCS1146.severity = none # Use conditional access. +dotnet_diagnostic.RCS1151.severity = none # Remove redundant cast. +dotnet_diagnostic.RCS1158.severity = none # Static member in generic type should use a type parameter. +dotnet_diagnostic.RCS1161.severity = none # Enum should declare explicit value +dotnet_diagnostic.RCS1163.severity = none # Unused parameter 'foo'. +dotnet_diagnostic.RCS1170.severity = none # Use read-only auto-implemented property. +dotnet_diagnostic.RCS1173.severity = none # Use coalesce expression instead of 'if'. +dotnet_diagnostic.RCS1181.severity = none # Convert comment to documentation comment. +dotnet_diagnostic.RCS1186.severity = none # Use Regex instance instead of static method. +dotnet_diagnostic.RCS1188.severity = none # Remove redundant auto-property initialization. +dotnet_diagnostic.RCS1189.severity = none # Add region name to #endregion. +dotnet_diagnostic.RCS1201.severity = none # Use method chaining. +dotnet_diagnostic.RCS1212.severity = none # Remove redundant assignment. +dotnet_diagnostic.RCS1217.severity = none # Convert interpolated string to concatenation. +dotnet_diagnostic.RCS1222.severity = none # Merge preprocessor directives. +dotnet_diagnostic.RCS1226.severity = none # Add paragraph to documentation comment. +dotnet_diagnostic.RCS1234.severity = none # Enum duplicate value +dotnet_diagnostic.RCS1238.severity = none # Avoid nested ?: operators. +dotnet_diagnostic.RCS1241.severity = none # Implement IComparable when implementing IComparable. +dotnet_diagnostic.IDE0001.severity = none # Simplify name +dotnet_diagnostic.IDE0002.severity = none # Simplify member access +dotnet_diagnostic.IDE0004.severity = none # Remove unnecessary cast +dotnet_diagnostic.IDE0035.severity = none # Remove unreachable code +dotnet_diagnostic.IDE0051.severity = none # Remove unused private member +dotnet_diagnostic.IDE0052.severity = none # Remove unread private member +dotnet_diagnostic.IDE0058.severity = none # Remove unused expression value +dotnet_diagnostic.IDE0059.severity = none # Unnecessary assignment of a value +dotnet_diagnostic.IDE0060.severity = none # Remove unused parameter +dotnet_diagnostic.IDE0080.severity = none # Remove unnecessary suppression operator +dotnet_diagnostic.IDE0100.severity = none # Remove unnecessary equality operator +dotnet_diagnostic.IDE0110.severity = none # Remove unnecessary discards +dotnet_diagnostic.IDE0032.severity = none # Use auto property +dotnet_diagnostic.IDE0160.severity = none # Use block-scoped namespace +dotnet_diagnostic.VSTHRD111.severity = none # Use .ConfigureAwait(bool) is hidden by default, set to none to prevent IDE from changing on autosave +dotnet_diagnostic.xUnit1004.severity = none # Test methods should not be skipped. Remove the Skip property to start running the test again. + +dotnet_diagnostic.SKEXP0003.severity = none # XYZ is for evaluation purposes only +dotnet_diagnostic.SKEXP0010.severity = none +dotnet_diagnostic.SKEXP0010.severity = none +dotnet_diagnostic.SKEXP0011.severity = none +dotnet_diagnostic.SKEXP0052.severity = none +dotnet_diagnostic.SKEXP0101.severity = none + +dotnet_diagnostic.KMEXP00.severity = none # XYZ is for evaluation purposes only +dotnet_diagnostic.KMEXP01.severity = none +dotnet_diagnostic.KMEXP02.severity = none +dotnet_diagnostic.KMEXP03.severity = none + +############################### +# C# Coding Conventions # +############################### + +# var preferences +csharp_style_var_for_built_in_types = false:none +csharp_style_var_when_type_is_apparent = false:none +csharp_style_var_elsewhere = false:none +# Expression-bodied members +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +# Null-checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion +# Modifier preferences +csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:suggestion +# Expression-level preferences +csharp_prefer_braces = true:error +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:error +csharp_style_inlined_variable_declaration = true:suggestion + +############################### +# C# Formatting Rules # +############################### + +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = false # Does not work with resharper, forcing code to be on long lines instead of wrapping +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true +# Indentation preferences +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true +csharp_using_directive_placement = outside_namespace:warning +csharp_prefer_simple_using_statement = true:suggestion +csharp_style_namespace_declarations = file_scoped:warning +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent + +############################### +# Global Naming Conventions # +############################### + +# Styles + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +dotnet_naming_style.camel_case_style.capitalization = camel_case + +dotnet_naming_style.static_underscored.capitalization = camel_case +dotnet_naming_style.static_underscored.required_prefix = s_ + +dotnet_naming_style.underscored.capitalization = camel_case +dotnet_naming_style.underscored.required_prefix = _ + +dotnet_naming_style.uppercase_with_underscore_separator.capitalization = all_upper +dotnet_naming_style.uppercase_with_underscore_separator.word_separator = _ + +dotnet_naming_style.end_in_async.required_prefix = +dotnet_naming_style.end_in_async.required_suffix = Async +dotnet_naming_style.end_in_async.capitalization = pascal_case +dotnet_naming_style.end_in_async.word_separator = + +# Symbols + +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const + +dotnet_naming_symbols.local_constant.applicable_kinds = local +dotnet_naming_symbols.local_constant.applicable_accessibilities = * +dotnet_naming_symbols.local_constant.required_modifiers = const + +dotnet_naming_symbols.private_constant_fields.applicable_kinds = field +dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private +dotnet_naming_symbols.private_constant_fields.required_modifiers = const + +dotnet_naming_symbols.private_static_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private +dotnet_naming_symbols.private_static_fields.required_modifiers = static + +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private + +dotnet_naming_symbols.any_async_methods.applicable_kinds = method +dotnet_naming_symbols.any_async_methods.applicable_accessibilities = * +dotnet_naming_symbols.any_async_methods.required_modifiers = async + +# Rules + +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = error + +dotnet_naming_rule.local_constant_should_be_pascal_case.symbols = local_constant +dotnet_naming_rule.local_constant_should_be_pascal_case.style = pascal_case_style +dotnet_naming_rule.local_constant_should_be_pascal_case.severity = error + +dotnet_naming_rule.private_constant_fields.symbols = private_constant_fields +dotnet_naming_rule.private_constant_fields.style = pascal_case_style +dotnet_naming_rule.private_constant_fields.severity = error + +dotnet_naming_rule.private_static_fields_underscored.symbols = private_static_fields +dotnet_naming_rule.private_static_fields_underscored.style = static_underscored +dotnet_naming_rule.private_static_fields_underscored.severity = error + +dotnet_naming_rule.private_fields_underscored.symbols = private_fields +dotnet_naming_rule.private_fields_underscored.style = underscored +dotnet_naming_rule.private_fields_underscored.severity = error + +##################################################################################################### + + +resharper_check_namespace_highlighting = none + +#resharper_inconsistent_naming_highlighting = none +#resharper_comment_typo_highlighting = none + + + + diff --git a/aspire-orchestrator/Aspire.AppHost/.gitignore b/aspire-orchestrator/Aspire.AppHost/.gitignore new file mode 100644 index 00000000..8e843802 --- /dev/null +++ b/aspire-orchestrator/Aspire.AppHost/.gitignore @@ -0,0 +1 @@ +.azure diff --git a/aspire-orchestrator/Aspire.AppHost/Aspire.AppHost.csproj b/aspire-orchestrator/Aspire.AppHost/Aspire.AppHost.csproj new file mode 100644 index 00000000..009dc84e --- /dev/null +++ b/aspire-orchestrator/Aspire.AppHost/Aspire.AppHost.csproj @@ -0,0 +1,28 @@ + + + + + + Exe + net8.0 + enable + enable + true + b43d9a6d-f7bd-491d-b1f9-82372d537e4e + SemanticWorkbench.Aspire.AppHost + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/aspire-orchestrator/Aspire.AppHost/Program.cs b/aspire-orchestrator/Aspire.AppHost/Program.cs new file mode 100644 index 00000000..2d65a03f --- /dev/null +++ b/aspire-orchestrator/Aspire.AppHost/Program.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft. All rights reserved. + +var builder = DistributedApplication.CreateBuilder(args); + +var authority = builder.AddParameterFromConfiguration("authority", "EntraId:Authority"); +var clientId = builder.AddParameterFromConfiguration("clientId", "EntraId:ClientId"); + +// Workbench backend +var workbenchService = builder.AddWorkbenchService("workbenchservice", projectDirectory: Path.Combine("..", "..", "workbench-service"), clientId: clientId); +var workbenchServiceEndpoint = workbenchService.GetSemanticWorkbenchEndpoint(builder.ExecutionContext.IsPublishMode); + +// Workbench frontend +var workbenchApp = builder.AddViteApp("workbenchapp", workingDirectory: Path.Combine("..", "..", "workbench-app"), packageManager: "pnpm") + .WithPnpmPackageInstallation() + .WithEnvironment(name: "VITE_SEMANTIC_WORKBENCH_SERVICE_URL", workbenchServiceEndpoint) + .WaitFor(workbenchService) + .PublishAsDockerFile([ + new DockerBuildArg("VITE_SEMANTIC_WORKBENCH_CLIENT_ID", clientId.Resource.Value), + new DockerBuildArg("VITE_SEMANTIC_WORKBENCH_AUTHORITY", authority.Resource.Value), + new DockerBuildArg("SSHD_ENABLED", "false") + ]); + +// Sample Python agent +builder.AddAssistantApp("skill-assistant", projectDirectory: Path.Combine("..", "..", "assistants", "skill-assistant"), assistantModuleName: "skill-assistant") + .WithEnvironment(name: "assistant__workbench_service_url", workbenchServiceEndpoint); + +// Sample .NET agent +builder.AddProject("simple-chatbot-dotnet") + .WithReference(workbenchServiceEndpoint) + .WaitFor(workbenchService) + .WithEnvironment(name: "Workbench__WorkbenchEndpoint", workbenchServiceEndpoint); + +if (!builder.ExecutionContext.IsPublishMode) +{ + workbenchApp.WithHttpsEndpoint(env: "PORT"); +} + +builder.Build().Run(); diff --git a/aspire-orchestrator/Aspire.AppHost/Properties/launchSettings.json b/aspire-orchestrator/Aspire.AppHost/Properties/launchSettings.json new file mode 100644 index 00000000..f136226d --- /dev/null +++ b/aspire-orchestrator/Aspire.AppHost/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17149;http://localhost:15061", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21236", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22204" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15061", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19243", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20261" + } + } + } +} diff --git a/aspire-orchestrator/Aspire.AppHost/appsettings.json b/aspire-orchestrator/Aspire.AppHost/appsettings.json new file mode 100644 index 00000000..71a9a93d --- /dev/null +++ b/aspire-orchestrator/Aspire.AppHost/appsettings.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + }, + "EntraID": { + "ClientId": "22cb77c3-ca98-4a26-b4db-ac4dcecba690", + "Authority": "https://login.microsoftonline.com/common" + } +} diff --git a/aspire-orchestrator/Aspire.Extensions/Aspire.Extensions.csproj b/aspire-orchestrator/Aspire.Extensions/Aspire.Extensions.csproj new file mode 100644 index 00000000..c14c91ab --- /dev/null +++ b/aspire-orchestrator/Aspire.Extensions/Aspire.Extensions.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + semantic workbench extensions + An Aspire integration for Semantic Workbench. + Aspire.Hosting.Extensions + + + + + + + + \ No newline at end of file diff --git a/aspire-orchestrator/Aspire.Extensions/DockerFileExtensions.cs b/aspire-orchestrator/Aspire.Extensions/DockerFileExtensions.cs new file mode 100644 index 00000000..940b7de7 --- /dev/null +++ b/aspire-orchestrator/Aspire.Extensions/DockerFileExtensions.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Aspire.Hosting; + +public static class DockerFileExtensions +{ + public static IResourceBuilder PublishAsDockerImage(this IResourceBuilder builder, + string? dockerContext = null, + string? dockerFilePath = "Dockerfile", + Action>? configure = null) + { + if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode) + { + return builder; + } + + // Bait and switch the ExecutableResource with a ContainerResource + builder.ApplicationBuilder.Resources.Remove(builder.Resource); + + var container = new ExecutableContainerResource(builder.Resource); + var cb = builder.ApplicationBuilder.AddResource(container); + cb.WithImage(builder.Resource.Name); + cb.WithDockerfile(contextPath: dockerContext ?? builder.Resource.WorkingDirectory, dockerfilePath: dockerFilePath); + // Clear the runtime args + cb.WithArgs(c => c.Args.Clear()); + + configure?.Invoke(cb); + + return builder; + } + + private sealed class ExecutableContainerResource(ExecutableResource er) : ContainerResource(er.Name) + { + public override ResourceAnnotationCollection Annotations => er.Annotations; + } +} diff --git a/aspire-orchestrator/Aspire.Extensions/PathNormalizer.cs b/aspire-orchestrator/Aspire.Extensions/PathNormalizer.cs new file mode 100644 index 00000000..3fe4ef79 --- /dev/null +++ b/aspire-orchestrator/Aspire.Extensions/PathNormalizer.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Aspire.Hosting.Extensions; + +internal static class PathNormalizer +{ + /// + /// Normalizes the path for the current platform. + /// + /// The path value. + /// Returns the normalized path value for the current platform. + public static string NormalizePathForCurrentPlatform(this string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return path; + } + + // Fix slashes + path = path.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar); + + return Path.GetFullPath(path); + } +} \ No newline at end of file diff --git a/aspire-orchestrator/Aspire.Extensions/UvAppHostingExtensions.cs b/aspire-orchestrator/Aspire.Extensions/UvAppHostingExtensions.cs new file mode 100644 index 00000000..2dcbdd32 --- /dev/null +++ b/aspire-orchestrator/Aspire.Extensions/UvAppHostingExtensions.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Aspire.Hosting.ComponentModel; +using Aspire.Hosting.Extensions; + +namespace Aspire.Hosting; + +public static class UvAppHostingExtensions +{ + public static IResourceBuilder AddUvApp( + this IDistributedApplicationBuilder builder, + string name, + string projectDirectory, + string scriptPath, + params string[] scriptArgs) + { + ArgumentNullException.ThrowIfNull(builder); + + return builder.AddUvApp(name, scriptPath, projectDirectory, ".venv", scriptArgs); + } + + private static IResourceBuilder AddUvApp(this IDistributedApplicationBuilder builder, + string name, + string scriptPath, + string projectDirectory, + string virtualEnvironmentPath, + params string[] args) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(scriptPath); + + string wd = projectDirectory ?? Path.Combine("..", name); + + projectDirectory = PathNormalizer.NormalizePathForCurrentPlatform(Path.Combine(builder.AppHostDirectory, wd)); + + var virtualEnvironment = new VirtualEnvironment(Path.IsPathRooted(virtualEnvironmentPath) + ? virtualEnvironmentPath + : Path.Join(projectDirectory, virtualEnvironmentPath)); + + var instrumentationExecutable = virtualEnvironment.GetExecutable("opentelemetry-instrument"); + // var pythonExecutable = virtualEnvironment.GetRequiredExecutable("python"); + // var projectExecutable = instrumentationExecutable ?? pythonExecutable; + + string[] allArgs = args is { Length: > 0 } + ? ["run", scriptPath, .. args] + : ["run", scriptPath]; + + var projectResource = new UvAppResource(name, projectDirectory); + + var resourceBuilder = builder.AddResource(projectResource) + .WithArgs(allArgs) + .WithArgs(context => + { + // If the project is to be automatically instrumented, add the instrumentation executable arguments first. + if (!string.IsNullOrEmpty(instrumentationExecutable)) + { + AddOpenTelemetryArguments(context); + + // // Add the python executable as the next argument so we can run the project. + // context.Args.Add(pythonExecutable!); + } + }); + + if (!string.IsNullOrEmpty(instrumentationExecutable)) + { + resourceBuilder.WithOtlpExporter(); + + // Make sure to attach the logging instrumentation setting, so we can capture logs. + // Without this you'll need to configure logging yourself. Which is kind of a pain. + resourceBuilder.WithEnvironment("OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED", "true"); + } + + return resourceBuilder; + } + + private static void AddOpenTelemetryArguments(CommandLineArgsCallbackContext context) + { + context.Args.Add("--traces_exporter"); + context.Args.Add("otlp"); + + context.Args.Add("--logs_exporter"); + context.Args.Add("console,otlp"); + + context.Args.Add("--metrics_exporter"); + context.Args.Add("otlp"); + } +} diff --git a/aspire-orchestrator/Aspire.Extensions/UvAppResource.cs b/aspire-orchestrator/Aspire.Extensions/UvAppResource.cs new file mode 100644 index 00000000..d807059c --- /dev/null +++ b/aspire-orchestrator/Aspire.Extensions/UvAppResource.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Aspire.Hosting.ComponentModel; + +public class UvAppResource(string name, string workingDirectory) + : ExecutableResource(name, "uv", workingDirectory), IResourceWithServiceDiscovery +{ + internal const string HttpEndpointName = "http"; +} diff --git a/aspire-orchestrator/Aspire.Extensions/VirtualEnvironment.cs b/aspire-orchestrator/Aspire.Extensions/VirtualEnvironment.cs new file mode 100644 index 00000000..d2c7b351 --- /dev/null +++ b/aspire-orchestrator/Aspire.Extensions/VirtualEnvironment.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Aspire.Hosting.ComponentModel; + +internal sealed class VirtualEnvironment(string virtualEnvironmentPath) +{ + /// + /// Locates an executable in the virtual environment. + /// + /// The name of the executable. + /// Returns the path to the executable if it exists in the virtual environment. + public string? GetExecutable(string name) + { + if (OperatingSystem.IsWindows()) + { + string[] allowedExtensions = [".exe", ".cmd", ".bat"]; + + return allowedExtensions + .Select(allowedExtension => Path.Join(virtualEnvironmentPath, "Scripts", name + allowedExtension)) + .FirstOrDefault(File.Exists); + } + + var executablePath = Path.Join(virtualEnvironmentPath, "bin", name); + return File.Exists(executablePath) ? executablePath : null; + } + + /// + /// Locates a required executable in the virtual environment. + /// + /// The name of the executable. + /// The path to the executable in the virtual environment. + /// Gets thrown when the executable couldn't be located. + public string GetRequiredExecutable(string name) + { + return this.GetExecutable(name) ?? throw new DistributedApplicationException( + $"The executable {name} could not be found in the virtual environment at '{virtualEnvironmentPath}' . " + + "Make sure the virtual environment is initialized and the executable is installed."); + } +} diff --git a/aspire-orchestrator/Aspire.Extensions/WorkbenchServiceHostingExtensions.cs b/aspire-orchestrator/Aspire.Extensions/WorkbenchServiceHostingExtensions.cs new file mode 100644 index 00000000..b34dca97 --- /dev/null +++ b/aspire-orchestrator/Aspire.Extensions/WorkbenchServiceHostingExtensions.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Aspire.Hosting; + +public static class WorkbenchServiceHostingExtensions +{ + public static IResourceBuilder AddWorkbenchService( + this IDistributedApplicationBuilder builder, + string name, + string projectDirectory, + IResourceBuilder clientId, + params string[] scriptArgs) + { + ArgumentNullException.ThrowIfNull(builder); + + var workbenchService = builder.AddUvApp(name, projectDirectory, "start-semantic-workbench-service", scriptArgs) + .PublishAsDockerImage(dockerContext: Path.Combine("..", ".."), + dockerFilePath: Path.Combine("workbench-service", "Dockerfile"), + configure: new(configure => configure + .WithBuildArg("SSHD_ENABLED", "false"))) + .WithEnvironment(name: "WORKBENCH__AUTH__ALLOWED_APP_ID", clientId.Resource.Value); + if (builder.ExecutionContext.IsPublishMode) + { + workbenchService.WithHttpsEndpoint(port: 3000); + } + else + { + workbenchService.WithHttpEndpoint(env: "PORT"); + } + + workbenchService.WithExternalHttpEndpoints(); + + return workbenchService; + } + + public static EndpointReference GetSemanticWorkbenchEndpoint(this IResourceBuilder workbenchService, bool isPublishMode) + { + ArgumentNullException.ThrowIfNull(workbenchService); + + return workbenchService.GetEndpoint(isPublishMode ? "https" : "http"); + } + + public static IResourceBuilder AddAssistantApp( + this IDistributedApplicationBuilder builder, + string name, + string projectDirectory, + string assistantModuleName) + { + ArgumentNullException.ThrowIfNull(builder); + + var assistant = builder.AddUvApp(name, projectDirectory, "start-assistant") + .PublishAsDockerImage(dockerContext: Path.Combine("..", ".."), + dockerFilePath: Path.Combine("tools", "docker", "Dockerfile.assistant"), + configure: new(configure => configure + .WithBuildArg("package", assistantModuleName) + .WithBuildArg("app", $"assistant.{assistantModuleName.Replace('-', '_')}:app") + )); + if (builder.ExecutionContext.IsPublishMode) + { + assistant.WithHttpEndpoint(port: 3001, env: "ASSISTANT__PORT"); + } + else + { + assistant.WithHttpEndpoint(env: "ASSISTANT__PORT"); + } + + var assistantEndpoint = assistant.GetEndpoint("http"); + assistant.WithEnvironment(name: "assistant__assistant_service_url", assistantEndpoint); + + return assistant; + } +} diff --git a/aspire-orchestrator/Aspire.ServiceDefaults/Aspire.ServiceDefaults.csproj b/aspire-orchestrator/Aspire.ServiceDefaults/Aspire.ServiceDefaults.csproj new file mode 100644 index 00000000..ddf72422 --- /dev/null +++ b/aspire-orchestrator/Aspire.ServiceDefaults/Aspire.ServiceDefaults.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + true + SemanticWorkbench.Aspire.ServiceDefaults + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/aspire-orchestrator/Aspire.ServiceDefaults/Extensions.cs b/aspire-orchestrator/Aspire.ServiceDefaults/Extensions.cs new file mode 100644 index 00000000..4397a884 --- /dev/null +++ b/aspire-orchestrator/Aspire.ServiceDefaults/Extensions.cs @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace SemanticWorkbench.Aspire.ServiceDefaults; + +// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation() + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/health"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} diff --git a/aspire-orchestrator/README.md b/aspire-orchestrator/README.md new file mode 100644 index 00000000..0cefccd6 --- /dev/null +++ b/aspire-orchestrator/README.md @@ -0,0 +1,54 @@ +# .NET Aspire Integratiuon + +## Run Prerequisites + +- [dotnet (8 or greater)](https://dotnet.microsoft.com/en-us/download) + +## Deployment Prerequisites + +- [Azure Developer CLI](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd?tabs=winget-windows%2Cbrew-mac%2Cscript-linux) +- [dotnet (9 or greater)](https://dotnet.microsoft.com/en-us/download) +- [Python 3.8 or greater](https://www.python.org/downloads/) +- [NodeJs v18.12 or greater](https://nodejs.org/en/download/) + +## Run with .NET Aspire + +1. Make sure you have accepted the [dotnet trust dev certificate](https://learn.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-9.0&tabs=visual-studio%2Clinux-sles#trust-the-aspnet-core-https-development-certificate) +```bash +dotnet dev-certs https --trust +``` + +2. Clone the repository + +```bash +make +cd aspire-orchestrator +cd Aspire.AppHost +dotnet run +``` + +## Deployment Steps with azd + +1. Clone the repository +2. In the root folder, run +```bash +make +``` +3. [Configure the app registration and configure workbenchapp and workbench service](../docs/CUSTOM_APP_REGISTRATION.md). Set the values in the `aspire-orchestrator/aspire-orchestrator/aspire-orchestrator/aspire-orchestrator/appsettings.json` file +```json +{ + "EntraID": { + "ClientId": "", + "Authority": "https://login.microsoftonline.com/" + } +} +``` +4. Authenticate with Azure Developer CLI +```bash +azd login +``` +5. Create the infrastructure and deploy the application +```bash +azd up +``` +6. Update the App Registration with the correct workbenchapp redirect URI \ No newline at end of file diff --git a/aspire-orchestrator/SemanticWorkbench.Aspire.sln b/aspire-orchestrator/SemanticWorkbench.Aspire.sln new file mode 100644 index 00000000..8ef9b393 --- /dev/null +++ b/aspire-orchestrator/SemanticWorkbench.Aspire.sln @@ -0,0 +1,48 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.0.0 +MinimumVisualStudioVersion = 17.8.0.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.AppHost", "Aspire.AppHost\Aspire.AppHost.csproj", "{85D9E2AF-8F25-4006-A375-A66E2259CB69}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.ServiceDefaults", "Aspire.ServiceDefaults\Aspire.ServiceDefaults.csproj", "{1A1BA42B-F071-42ED-BF6D-4BD51F2D7A41}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-03-simple-chatbot", "..\examples\dotnet\dotnet-03-simple-chatbot\dotnet-03-simple-chatbot.csproj", "{670F22E3-71CC-4A9B-8BB8-DA855CFC0EEC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Extensions", "Aspire.Extensions\Aspire.Extensions.csproj", "{773468A0-6628-4D14-8EE8-7C3F790B6192}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkbenchConnector", "..\libraries\dotnet\WorkbenchConnector\WorkbenchConnector.csproj", "{A4B94C9F-C917-4193-99CC-10E694A8A594}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {85D9E2AF-8F25-4006-A375-A66E2259CB69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85D9E2AF-8F25-4006-A375-A66E2259CB69}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85D9E2AF-8F25-4006-A375-A66E2259CB69}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85D9E2AF-8F25-4006-A375-A66E2259CB69}.Release|Any CPU.Build.0 = Release|Any CPU + {1A1BA42B-F071-42ED-BF6D-4BD51F2D7A41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A1BA42B-F071-42ED-BF6D-4BD51F2D7A41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A1BA42B-F071-42ED-BF6D-4BD51F2D7A41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A1BA42B-F071-42ED-BF6D-4BD51F2D7A41}.Release|Any CPU.Build.0 = Release|Any CPU + {670F22E3-71CC-4A9B-8BB8-DA855CFC0EEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {670F22E3-71CC-4A9B-8BB8-DA855CFC0EEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {670F22E3-71CC-4A9B-8BB8-DA855CFC0EEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {670F22E3-71CC-4A9B-8BB8-DA855CFC0EEC}.Release|Any CPU.Build.0 = Release|Any CPU + {773468A0-6628-4D14-8EE8-7C3F790B6192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {773468A0-6628-4D14-8EE8-7C3F790B6192}.Debug|Any CPU.Build.0 = Debug|Any CPU + {773468A0-6628-4D14-8EE8-7C3F790B6192}.Release|Any CPU.ActiveCfg = Release|Any CPU + {773468A0-6628-4D14-8EE8-7C3F790B6192}.Release|Any CPU.Build.0 = Release|Any CPU + {A4B94C9F-C917-4193-99CC-10E694A8A594}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4B94C9F-C917-4193-99CC-10E694A8A594}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4B94C9F-C917-4193-99CC-10E694A8A594}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4B94C9F-C917-4193-99CC-10E694A8A594}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {30B8E5F7-972C-4722-B57A-0626109F80B6} + EndGlobalSection +EndGlobal diff --git a/aspire-orchestrator/SemanticWorkbench.Aspire.sln.DotSettings b/aspire-orchestrator/SemanticWorkbench.Aspire.sln.DotSettings new file mode 100644 index 00000000..e0cd1b82 --- /dev/null +++ b/aspire-orchestrator/SemanticWorkbench.Aspire.sln.DotSettings @@ -0,0 +1,304 @@ + + False + False + True + True + FullFormat + True + True + True + True + True + SOLUTION + False + SUGGESTION + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + SUGGESTION + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + ERROR + True + Field, Property, Event, Method + True + True + True + NEXT_LINE + True + True + True + True + True + True + 1 + 1 + True + True + True + ALWAYS + True + True + False + 512 + True + Copyright (c) Microsoft. All rights reserved. + ACS + AI + AIGPT + AMQP + API + BOM + CORS + DB + DI + GPT + GRPC + HMAC + HTTP + IM + IO + IOS + JSON + JWT + MQ + MQTT + MS + MSAL + OCR + OID + OK + OS + PR + QA + SHA + SK + SKHTTP + SSL + TTL + UI + UID + URL + XML + YAML + False + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB"><ExtraRule Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="Async" Style="AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="Async" Style="aaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="Async" Style="AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="Async" Style="aaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="s_" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="Async" Style="AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local variables"><ElementKinds><Kind Name="LOCAL_VARIABLE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="Async" Style="aaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Constant fields (not private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local functions"><ElementKinds><Kind Name="LOCAL_FUNCTION" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="Async" Style="AaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Methods"><ElementKinds><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="Async" Style="AaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Parameters"><ElementKinds><Kind Name="PARAMETER" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="Async" Style="aaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"><ElementKinds><Kind Name="LOCAL_CONSTANT" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB"><ExtraRule Prefix="" Suffix="" Style="aaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="Async" Style="AaBb" /></Policy></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="s_" Suffix="" Style="aaBb" /></Policy> + + 2 + False + True + Console + PushToShowHints + True + True + True + True + True + True + True + True + True + True + True + True + False + TRACE + 8201 + Automatic + True + True + True + True + True + 2.0 + InCSharpFile + pragma + True + #pragma warning disable CA0000 // reason + +#pragma warning restore CA0000 + True + True + False + True + guid() + 0 + True + True + False + False + True + 2.0 + InCSharpFile + aaa + True + [Fact] +public void It$SOMENAME$() +{ + // Arrange + + // Act + + // Assert + +} + True + True + MSFT copyright + True + 2.0 + InCSharpFile + copy + // Copyright (c) Microsoft. All rights reserved. + + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + DO_NOT_SHOW + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + diff --git a/aspire-orchestrator/run.sh b/aspire-orchestrator/run.sh new file mode 100755 index 00000000..3a178add --- /dev/null +++ b/aspire-orchestrator/run.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +set -e + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/" +cd "$HERE" + +# Check node version, it must be major version 20 (any minor), otherwise show an error and exit +NODE_VERSION=$(node -v) +if [[ ! $NODE_VERSION =~ ^v(1[8-9]|[2-9][0-9]).* ]]; then + echo "Node version is $NODE_VERSION, expected 18.x.x or higher." + + # Attempt to source nvm + if [ -s "$NVM_DIR/nvm.sh" ]; then + . "$NVM_DIR/nvm.sh" + elif [ -s "$HOME/.nvm/nvm.sh" ]; then + export NVM_DIR="$HOME/.nvm" + . "$NVM_DIR/nvm.sh" + else + echo "nvm not found. Please install Node 18 or higher manually." + echo "See also README.md for instructions." + exit 1 + fi + + echo "Installing latest LTS Node version via nvm..." + nvm install --lts + nvm use --lts + + NODE_VERSION=$(node -v) + if [[ ! $NODE_VERSION =~ ^v(1[8-9]|[2-9][0-9]).* ]]; then + echo "Failed to switch to Node 18 or higher via nvm. You have $NODE_VERSION." + exit 1 + fi +fi + +cd Aspire.AppHost + +dotnet run \ No newline at end of file diff --git a/tools/docker/Dockerfile.aspire.assistant b/tools/docker/Dockerfile.aspire.assistant new file mode 100644 index 00000000..db0de017 --- /dev/null +++ b/tools/docker/Dockerfile.aspire.assistant @@ -0,0 +1,43 @@ +# syntax=docker/dockerfile:1 + +# Dockerfile for assistants +# Context root is expected to be the root of the repo +ARG python_image=python:3.11-slim + +# These build arguments will differ per assistant: +# package is the directory name of the assistant package under /assistants +ARG package +ARG app + +FROM ${python_image} AS build + +ARG package + +COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv + +# copy all library packages +COPY ./libraries/python /packages/libraries/python +# copy the assistant package +COPY ./assistants/${package} /packages/assistants/assistant + +# install the assistant and dependencies to /.venv +RUN uv sync --directory /packages/assistants/assistant --no-editable --no-dev --locked + +FROM ${python_image} + +ARG app + +COPY --from=build /packages/assistants/assistant/.venv /packages/assistants/assistant/.venv +ENV PATH=/packages/assistants/assistant/.venv/bin:$PATH + +COPY ./tools/docker/docker-entrypoint.sh /scripts/docker-entrypoint.sh +RUN chmod +x /scripts/docker-entrypoint.sh + +ENV ASSISTANT_APP=${app} + +ENV assistant__host=0.0.0.0 +ENV assistant__port=3001 + +SHELL ["/bin/bash", "-c"] +ENTRYPOINT ["/scripts/docker-entrypoint.sh"] +CMD ["start-assistant"] diff --git a/workbench-app/.dockerignore b/workbench-app/.dockerignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/workbench-app/.dockerignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/workbench-app/Dockerfile b/workbench-app/Dockerfile new file mode 100644 index 00000000..31e5d490 --- /dev/null +++ b/workbench-app/Dockerfile @@ -0,0 +1,39 @@ +# Stage 1: Build the React application +FROM node:18-alpine AS build +WORKDIR /app + +# Install pnpm +RUN npm install -g pnpm + +# Copy package.json and pnpm-lock.yaml +COPY package.json pnpm-lock.yaml ./ + +# Install dependencies +RUN pnpm install + +# Copy the rest of the application code +COPY . . + +# Build the application +RUN pnpm run build + +# Stage 2: Serve the app with Nginx +FROM nginx:alpine +RUN apk add --no-cache gettext +WORKDIR /usr/share/nginx/html + +# Remove default Nginx static files +RUN rm -rf ./* + +# Copy build artifacts from the previous stage +COPY --from=build /app/build . + +# Copy custom entrypoint script +COPY docker-entrypoint.sh /usr/bin/ +RUN chmod +x /usr/bin/docker-entrypoint.sh + +# Copy Nginx configuration +COPY nginx.conf /etc/nginx/conf.d/default.conf + +ENTRYPOINT ["docker-entrypoint.sh"] +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/workbench-app/docker-entrypoint.sh b/workbench-app/docker-entrypoint.sh new file mode 100644 index 00000000..8e21bf51 --- /dev/null +++ b/workbench-app/docker-entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# Allow static build of REact code to access env vars +# SEE https://create-react-app.dev/docs/title-and-meta-tags/#injecting-data-from-the-server-into-the-page +# Replace placeholders in static files with environment variables +envsubst '$VITE_SEMANTIC_WORKBENCH_SERVICE_URL' < /usr/share/nginx/html/index.html > /usr/share/nginx/html/index.html.tmp +mv /usr/share/nginx/html/index.html.tmp /usr/share/nginx/html/index.html + +envsubst '$PORT' < /etc/nginx/conf.d/default.conf > /etc/nginx/conf.d/default.conf.tmp +mv /etc/nginx/conf.d/default.conf.tmp /etc/nginx/conf.d/default.conf + +echo "VITE_SEMANTIC_WORKBENCH_SERVICE_URL = $VITE_SEMANTIC_WORKBENCH_SERVICE_URL" +echo "PORT = $PORT" + +exec "$@" \ No newline at end of file diff --git a/workbench-app/index.html b/workbench-app/index.html index 1ae0097f..115f2c65 100644 --- a/workbench-app/index.html +++ b/workbench-app/index.html @@ -16,4 +16,9 @@
+ diff --git a/workbench-app/nginx.conf b/workbench-app/nginx.conf new file mode 100644 index 00000000..faa61f18 --- /dev/null +++ b/workbench-app/nginx.conf @@ -0,0 +1,13 @@ +# Required so that nginx can resolve IPs when working with Docker Compose +resolver 127.0.0.11 ipv6=off; + +index index.html index.htm; + +server { + listen $PORT; + server_name reverseproxy 127.0.0.1; + + location / { + root /usr/share/nginx/html; + } +} \ No newline at end of file diff --git a/workbench-app/src/Constants.ts b/workbench-app/src/Constants.ts index f2fb432c..4627564c 100644 --- a/workbench-app/src/Constants.ts +++ b/workbench-app/src/Constants.ts @@ -1,3 +1,11 @@ +// Allow static build of React code to access env vars +// SEE https://create-react-app.dev/docs/title-and-meta-tags/#injecting-data-from-the-server-into-the-page +const serviceUrl = (window.VITE_SEMANTIC_WORKBENCH_SERVICE_URL && window.VITE_SEMANTIC_WORKBENCH_SERVICE_URL.startsWith('https://')) + ? window.VITE_SEMANTIC_WORKBENCH_SERVICE_URL + : (import.meta.env.VITE_SEMANTIC_WORKBENCH_SERVICE_URL) + ? import.meta.env.VITE_SEMANTIC_WORKBENCH_SERVICE_URL + : 'http://127.0.0.1:3000'; + export const Constants = { app: { name: 'Semantic Workbench', @@ -26,7 +34,7 @@ export const Constants = { id: 'local', name: 'Semantic Workbench backend service on localhost or GitHub Codespaces', // Can be overridden by env var VITE_SEMANTIC_WORKBENCH_SERVICE_URL - url: import.meta.env.VITE_SEMANTIC_WORKBENCH_SERVICE_URL || 'http://localhost:3000', + url: serviceUrl, brand: 'light', }, // { diff --git a/workbench-app/src/global.d.ts b/workbench-app/src/global.d.ts new file mode 100644 index 00000000..fc224a76 --- /dev/null +++ b/workbench-app/src/global.d.ts @@ -0,0 +1,9 @@ +export { }; + +// Allow static build of React code to access env vars +// SEE https://create-react-app.dev/docs/title-and-meta-tags/#injecting-data-from-the-server-into-the-page +declare global { + interface Window { + VITE_SEMANTIC_WORKBENCH_SERVICE_URL?: string; + } +} \ No newline at end of file diff --git a/workbench-service/Dockerfile.aspire b/workbench-service/Dockerfile.aspire new file mode 100644 index 00000000..01d611ac --- /dev/null +++ b/workbench-service/Dockerfile.aspire @@ -0,0 +1,41 @@ +# Note: this is a copy of workbench-service/Dockerfile with the exception of +# SSHD configuration that's been removed. SSHD open port breaks Azure Container +# deployment, plus SSH is not required and a potential issue public facing +# deployments. + +ARG python_image=python:3.11-slim + +FROM ${python_image} AS build + +COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv + +COPY ./libraries/python/semantic-workbench-api-model /libraries/python/semantic-workbench-api-model +COPY ./libraries/python/semantic-workbench-assistant /libraries/python/semantic-workbench-assistant +COPY ./workbench-service /workbench-service +COPY ./workbench-service/.env /workbench-service/.env + +RUN uv sync --directory /workbench-service --no-editable --no-dev --locked + +FROM ${python_image} + +COPY --from=build /workbench-service/.venv /workbench-service/.venv +ENV PATH=/workbench-service/.venv/bin:$PATH + +# alembic migrations related files +COPY ./workbench-service/alembic.ini /workbench-service/alembic.ini +COPY ./workbench-service/migrations /workbench-service/migrations + +COPY ./workbench-service/.env /workbench-service/.env + +# entrypoint script +COPY ./tools/docker/docker-entrypoint.sh /scripts/docker-entrypoint.sh +RUN chmod +x /scripts/docker-entrypoint.sh + +WORKDIR /workbench-service + +ENV workbench__service__host=0.0.0.0 +ENV workbench__service__port=3000 + +SHELL ["/bin/bash", "-c"] +ENTRYPOINT ["/scripts/docker-entrypoint.sh"] +CMD ["start-service"]