diff --git a/src/Aspire.Hosting.CodeGeneration.Go/AtsGoCodeGenerator.cs b/src/Aspire.Hosting.CodeGeneration.Go/AtsGoCodeGenerator.cs index 6c06d4f4e71..2e95a2c2da7 100644 --- a/src/Aspire.Hosting.CodeGeneration.Go/AtsGoCodeGenerator.cs +++ b/src/Aspire.Hosting.CodeGeneration.Go/AtsGoCodeGenerator.cs @@ -457,6 +457,7 @@ package aspire "context" "fmt" "os" + "strings" "time" ) @@ -464,6 +465,7 @@ package aspire var _ = context.Background var _ = fmt.Errorf var _ = os.Getenv + var _ = strings.EqualFold var _ = time.Second """); WriteLine(); @@ -540,7 +542,12 @@ private void GenerateDtoTypes(IReadOnlyList dtoTypes) foreach (var property in dto.Properties) { var propertyName = ToPascalCase(property.Name); - var propertyType = MapDtoPropertyTypeToGo(property.Type, property.IsOptional); + // Callback-typed DTO properties carry the same metadata as method parameters, so + // render the strongly-typed func signature (e.g. func(ctx InputsDialogValidationContext)) + // instead of the weak func(...any) any fallback. + var propertyType = property.IsCallback + ? RenderCallbackType(DtoPropertyToParameterInfo(property)) + : MapDtoPropertyTypeToGo(property.Type, property.IsOptional); var jsonTag = $"`json:\"{property.Name},omitempty\"`"; WriteLine($"\t{propertyName} {propertyType} {jsonTag}"); } @@ -553,6 +560,11 @@ private void GenerateDtoTypes(IReadOnlyList dtoTypes) foreach (var property in dto.Properties) { var propertyName = ToPascalCase(property.Name); + if (property.IsCallback) + { + EmitDtoCallbackToMap(property, propertyName); + continue; + } var propertyType = MapDtoPropertyTypeToGo(property.Type, property.IsOptional); if (IsNilableGoType(propertyType)) { @@ -814,6 +826,11 @@ private void GenerateConcreteHandleTypes(Dictionary private void EmitCallbackRegistration(string indent, AtsParameterInfo p, string callbackExpr) + { + WriteLine($"{indent}if {callbackExpr} != nil {{"); + WriteLine($"{indent}\tcb := {callbackExpr}"); + WriteLine($"{indent}\tshim := func(args ...any) any {{"); + EmitGoCallbackShimBody($"{indent}\t\t", p); + WriteLine($"{indent}\t}}"); + WriteLine($"{indent}\treqArgs[\"{p.Name}\"] = s.client.registerCallback(shim)"); + WriteLine($"{indent}}}"); + } + + // Emits a strongly-typed DTO callback property into the DTO's ToMap output. Method parameters + // pre-register their callback (putting a string id into reqArgs), but DTO properties instead + // embed the shim func directly in the map; the client's marshalTransportValue walks the + // serialized args, finds the func(...any) any value, and registers it. This keeps the public + // DTO field strongly typed (e.g. func(ctx InputsDialogValidationContext)) while reusing the + // exact arg-decoding/return/writeback behavior of method-parameter callbacks. + private void EmitDtoCallbackToMap(AtsDtoPropertyInfo property, string propertyName) + { + var p = DtoPropertyToParameterInfo(property); + WriteLine($"\tif d.{propertyName} != nil {{"); + WriteLine($"\t\tcb := d.{propertyName}"); + WriteLine($"\t\tm[\"{property.Name}\"] = func(args ...any) any {{"); + EmitGoCallbackShimBody("\t\t\t", p); + WriteLine($"\t\t}}"); + WriteLine($"\t}}"); + } + + private static AtsParameterInfo DtoPropertyToParameterInfo(AtsDtoPropertyInfo property) + => new() + { + Name = property.Name, + Type = property.Type, + IsOptional = property.IsOptional, + IsCallback = property.IsCallback, + CallbackParameters = property.CallbackParameters, + CallbackReturnType = property.CallbackReturnType, + }; + + // Writes the body of a `func(args ...any) any` callback shim, assuming the user's typed + // callback is in scope as `cb`. Shared by method-parameter and DTO-property callbacks. + private void EmitGoCallbackShimBody(string indent, AtsParameterInfo p) { var hasReturn = p.CallbackReturnType is not null && p.CallbackReturnType.TypeId != AtsConstants.Void; @@ -1390,17 +1505,14 @@ private void EmitCallbackRegistration(string indent, AtsParameterInfo p, string } callExpr.Append(')'); - WriteLine($"{indent}if {callbackExpr} != nil {{"); - WriteLine($"{indent}\tcb := {callbackExpr}"); - WriteLine($"{indent}\tshim := func(args ...any) any {{"); if (hasReturn) { - WriteLine($"{indent}\t\treturn {callExpr}"); + WriteLine($"{indent}return {callExpr}"); } else if (p.CallbackParameters is null) { // Legacy untyped callback returning any — preserve return value. - WriteLine($"{indent}\t\treturn {callExpr}"); + WriteLine($"{indent}return {callExpr}"); } else if (p.CallbackParameters is { Count: > 0 } callbackParameters && callbackParameters.Any(cp => cp.Type.Category == AtsTypeCategory.Dto)) { @@ -1410,28 +1522,25 @@ private void EmitCallbackRegistration(string indent, AtsParameterInfo p, string var argName = $"arg{i}"; argNames.Add(argName); var goType = MapTypeRefToGo(callbackParameters[i].Type, false); - WriteLine($"{indent}\t\t{argName} := callbackArg[{goType}](args, {i})"); + WriteLine($"{indent}{argName} := callbackArg[{goType}](args, {i})"); } - WriteLine($"{indent}\t\tcb({string.Join(", ", argNames)})"); - WriteLine($"{indent}\t\treturn map[string]any{{"); + WriteLine($"{indent}cb({string.Join(", ", argNames)})"); + WriteLine($"{indent}return map[string]any{{"); for (var i = 0; i < callbackParameters.Count; i++) { if (callbackParameters[i].Type.Category == AtsTypeCategory.Dto) { - WriteLine($"{indent}\t\t\t\"p{i}\": serializeValue({argNames[i]}),"); + WriteLine($"{indent}\t\"p{i}\": serializeValue({argNames[i]}),"); } } - WriteLine($"{indent}\t\t}}"); + WriteLine($"{indent}}}"); } else { - WriteLine($"{indent}\t\t{callExpr}"); - WriteLine($"{indent}\t\treturn nil"); + WriteLine($"{indent}{callExpr}"); + WriteLine($"{indent}return nil"); } - WriteLine($"{indent}\t}}"); - WriteLine($"{indent}\treqArgs[\"{p.Name}\"] = s.client.registerCallback(shim)"); - WriteLine($"{indent}}}"); } // ── List / Dict accessor methods ───────────────────────────────────────── diff --git a/src/Aspire.Hosting.CodeGeneration.Java/AtsJavaCodeGenerator.cs b/src/Aspire.Hosting.CodeGeneration.Java/AtsJavaCodeGenerator.cs index 586b94b2757..9ca635af0df 100644 --- a/src/Aspire.Hosting.CodeGeneration.Java/AtsJavaCodeGenerator.cs +++ b/src/Aspire.Hosting.CodeGeneration.Java/AtsJavaCodeGenerator.cs @@ -42,6 +42,8 @@ internal sealed class AtsJavaCodeGenerator : ICodeGenerator private readonly Dictionary _capabilityOptionsClassMap = new(StringComparer.Ordinal); private readonly HashSet _resourceBuilderHandleClasses = new(StringComparer.Ordinal); + private const string InteractionInputCollectionTypeId = "Aspire.Hosting/Aspire.Hosting.InteractionInputCollection"; + /// public string Language => "Java"; @@ -534,7 +536,7 @@ private void GenerateDtoTypes(IReadOnlyList dtoTypes) foreach (var property in dto.Properties) { var fieldName = ToCamelCase(property.Name); - var fieldType = MapDtoPropertyTypeToJava(property.Type, property.IsOptional); + var fieldType = MapDtoFieldTypeToJava(property); WriteLine($" private {fieldType} {fieldName};"); } WriteLine(); @@ -544,7 +546,7 @@ private void GenerateDtoTypes(IReadOnlyList dtoTypes) { var fieldName = ToCamelCase(property.Name); var methodName = ToPascalCase(property.Name); - var fieldType = MapDtoPropertyTypeToJava(property.Type, property.IsOptional); + var fieldType = MapDtoFieldTypeToJava(property); WriteLine($" public {fieldType} get{methodName}() {{ return {fieldName}; }}"); WriteLine($" public void set{methodName}({fieldType} value) {{ this.{fieldName} = value; }}"); @@ -556,6 +558,13 @@ private void GenerateDtoTypes(IReadOnlyList dtoTypes) WriteLine($" var value = new {dtoName}();"); foreach (var property in dto.Properties) { + // Strongly-typed callback properties cannot be reconstructed from transport data: + // callbacks only flow from client to host, never back. Skip them in fromMap so the + // generated code does not pass a raw transport value to the typed setter. + if (IsStronglyTypedDtoCallback(property)) + { + continue; + } var fieldName = ToCamelCase(property.Name); var methodName = ToPascalCase(property.Name); var transportValueName = $"{fieldName}Value"; @@ -572,7 +581,14 @@ private void GenerateDtoTypes(IReadOnlyList dtoTypes) foreach (var property in dto.Properties) { var fieldName = ToCamelCase(property.Name); - WriteLine($" map.put(\"{property.Name}\", AspireClient.serializeValue({fieldName}));"); + if (IsStronglyTypedDtoCallback(property)) + { + EmitJavaDtoCallbackToMap(property); + } + else + { + WriteLine($" map.put(\"{property.Name}\", AspireClient.serializeValue({fieldName}));"); + } } WriteLine(" return map;"); WriteLine(" }"); @@ -1048,6 +1064,11 @@ private void GenerateHandleTypes( } } + if (string.Equals(handleType.TypeId, InteractionInputCollectionTypeId, StringComparison.Ordinal)) + { + GenerateInteractionInputCollectionAccessors(); + } + if (string.Equals(handleType.ClassName, "DistributedApplication", StringComparison.Ordinal)) { GenerateDistributedApplicationBuilderHelpers(); @@ -1058,6 +1079,44 @@ private void GenerateHandleTypes( } } + private void GenerateInteractionInputCollectionAccessors() + { + // These accessors are hand-authored on top of the generated toArray capability for parity with .NET and TypeScript. + WriteLine(" /** Gets the input with the specified name, or null if no input matches. */"); + WriteLine(" public InteractionInput get(String name) {"); + WriteLine(" for (var input : toArray()) {"); + WriteLine(" if (input.getName() != null && input.getName().equalsIgnoreCase(name)) {"); + WriteLine(" return input;"); + WriteLine(" }"); + WriteLine(" }"); + WriteLine(" return null;"); + WriteLine(" }"); + WriteLine(); + + WriteLine(" /** Gets the input with the specified name, or throws if no input matches. */"); + WriteLine(" public InteractionInput required(String name) {"); + WriteLine(" var input = get(name);"); + WriteLine(" if (input == null) {"); + WriteLine(" throw new IllegalArgumentException(\"no input with name '\" + name + \"' was found\");"); + WriteLine(" }"); + WriteLine(" return input;"); + WriteLine(" }"); + WriteLine(); + + WriteLine(" /** Gets the value of the input with the specified name, or an empty string if no input matches or it has no value. */"); + WriteLine(" public String value(String name) {"); + WriteLine(" var input = get(name);"); + WriteLine(" return input == null || input.getValue() == null ? \"\" : input.getValue();"); + WriteLine(" }"); + WriteLine(); + + WriteLine(" /** Gets the value of the input with the specified name, or throws if no input matches. */"); + WriteLine(" public String requiredValue(String name) {"); + WriteLine(" return required(name).getValue();"); + WriteLine(" }"); + WriteLine(); + } + private void GenerateDistributedApplicationBuilderHelpers() { var builderClassName = _classNames.TryGetValue(AtsConstants.BuilderTypeId, out var name) @@ -1441,9 +1500,14 @@ private void GenerateOptionsOverloads( string optionsClassName) { var requiredParameterList = string.Join(", ", requiredParameters.Select(parameter => $"{MapParameterToJava(parameter)} {ToCamelCase(parameter.Name)}")); + // Name the options-bag parameter "optionsBag" rather than "options" to avoid colliding with a flattened + // local. Some capabilities have an optional parameter literally named "options" (for example the interaction + // prompts), and the flattening below declares "var options = optionsBag.getOptions()". Sharing the name would + // make the local shadow the parameter, which is a Java compile error. This matches the TypeScript generator, + // which also uses "optionsBag". var publicParameterList = string.IsNullOrEmpty(requiredParameterList) - ? $"{optionsClassName} options" - : $"{requiredParameterList}, {optionsClassName} options"; + ? $"{optionsClassName} optionsBag" + : $"{requiredParameterList}, {optionsClassName} optionsBag"; if (!string.IsNullOrEmpty(capability.Description)) { @@ -1454,7 +1518,7 @@ private void GenerateOptionsOverloads( foreach (var parameter in optionalParameters) { var paramName = ToCamelCase(parameter.Name); - WriteLine($" var {paramName} = options == null ? null : options.{GetOptionGetterName(parameter)}();"); + WriteLine($" var {paramName} = optionsBag == null ? null : optionsBag.{GetOptionGetterName(parameter)}();"); } var implementationArguments = requiredParameters @@ -1667,19 +1731,67 @@ private void GenerateCallbackBody(string callbackName, AtsParameterInfo callback } } + // A DTO callback property is rendered with a strong functional-interface type only when it has + // at most one parameter. The runtime marshaller registers DTO-embedded callbacks as a single-arg + // Function (args[0] only), so multi-parameter DTO callbacks must keep the weak Object fallback to + // avoid generating a strongly-typed API that silently drops arguments. All current DTO callbacks + // (e.g. validation/prepare-request contexts) are single-parameter. + private static bool IsStronglyTypedDtoCallback(AtsDtoPropertyInfo property) + => property.IsCallback && (property.CallbackParameters?.Count ?? 0) <= 1; + + private string MapDtoFieldTypeToJava(AtsDtoPropertyInfo property) + => IsStronglyTypedDtoCallback(property) + ? GenerateCallbackTypeSignature(property.CallbackParameters, property.CallbackReturnType) + : MapDtoPropertyTypeToJava(property.Type, property.IsOptional); + + // Serializes a strongly-typed DTO callback property by wrapping the user's AspireAction/AspireFunc + // in a java.util.function.Function. The client's marshalTransportValue detects Function values in + // the serialized DTO map and registers them, invoking the Function with the unwrapped first + // argument. This mirrors the typed arg-conversion used for method-parameter callbacks. + private void EmitJavaDtoCallbackToMap(AtsDtoPropertyInfo property) + { + var fieldName = ToCamelCase(property.Name); + var hasReturnType = property.CallbackReturnType != null && property.CallbackReturnType.TypeId != AtsConstants.Void; + var callbackParameter = property.CallbackParameters is { Count: 1 } ? property.CallbackParameters[0] : null; + + WriteLine($" map.put(\"{property.Name}\", {fieldName} == null ? null : (java.util.function.Function) (transportArg -> {{"); + var invocationArgument = string.Empty; + if (callbackParameter is not null) + { + var callbackParameterName = ToCamelCase(callbackParameter.Name); + WriteLine($" var {callbackParameterName} = {GetCallbackArgumentExpression(callbackParameter, "transportArg")};"); + invocationArgument = callbackParameterName; + } + + var invocation = $"{fieldName}.invoke({invocationArgument})"; + if (hasReturnType) + { + WriteLine($" return AspireClient.awaitValue({invocation});"); + } + else + { + WriteLine($" {invocation};"); + WriteLine(" return null;"); + } + WriteLine(" }));"); + } + private string GetCallbackArgumentExpression(AtsCallbackParameterInfo callbackParameter, int index) + => GetCallbackArgumentExpression(callbackParameter, $"args[{index}]"); + + private string GetCallbackArgumentExpression(AtsCallbackParameterInfo callbackParameter, string argumentExpression) { if (callbackParameter.Type?.TypeId == AtsConstants.CancellationToken) { - return $"CancellationToken.fromValue(args[{index}])"; + return $"CancellationToken.fromValue({argumentExpression})"; } if (IsUnionType(callbackParameter.Type)) { - return $"AspireUnion.of(args[{index}])"; + return $"AspireUnion.of({argumentExpression})"; } - return RenderJavaTransportValueConversion(callbackParameter.Type, $"args[{index}]", callbackParameter.Type?.IsNullable == true); + return RenderJavaTransportValueConversion(callbackParameter.Type, argumentExpression, callbackParameter.Type?.IsNullable == true); } private string RenderJavaTransportValueConversion(AtsTypeRef? typeRef, string valueExpression, bool isOptional, int depth = 0) diff --git a/src/Aspire.Hosting.CodeGeneration.Python/AtsPythonCodeGenerator.cs b/src/Aspire.Hosting.CodeGeneration.Python/AtsPythonCodeGenerator.cs index 48d1447bbbf..56c9b16c682 100644 --- a/src/Aspire.Hosting.CodeGeneration.Python/AtsPythonCodeGenerator.cs +++ b/src/Aspire.Hosting.CodeGeneration.Python/AtsPythonCodeGenerator.cs @@ -125,6 +125,10 @@ private sealed record OptionVariation( private sealed record MergedCapabilityDispatch(string AlternateCapabilityId, string DiscriminatingParamName); private readonly Dictionary _mergedCapabilityDispatches = new(StringComparer.Ordinal); + // Type ID of InteractionInputCollection. The by-name accessors below are hand-authored on top of the + // generated to_array capability so Python matches the .NET indexer and TypeScript get/value helpers. + private const string InteractionInputCollectionTypeId = "Aspire.Hosting/Aspire.Hosting.InteractionInputCollection"; + private PythonModuleBuilder _moduleBuilder = null!; // Mapping of typeId -> wrapper class name for all generated wrapper types @@ -832,7 +836,14 @@ private void GenerateDtoClasses(IReadOnlyList dtoTypes) sb.AppendLine(CultureInfo.InvariantCulture, $"class {className}(typing.TypedDict, total=False):"); foreach (var prop in dtoType.Properties) { - var propType = MapDtoPropertyTypeToPython(prop.Type); + // Callback-typed DTO properties carry the same CallbackParameters/CallbackReturnType + // metadata as method parameters, so render the strongly-typed Callable signature + // (e.g. typing.Callable[[InputsDialogValidationContext], None]) instead of a bare + // typing.Callable. The runtime marshaller already registers callables embedded in + // DTO dicts, so no serialization change is needed. + var propType = prop.IsCallback + ? GenerateCallbackTypeSignature(prop.CallbackParameters, prop.CallbackReturnType) + : MapDtoPropertyTypeToPython(prop.Type); sb.AppendLine(CultureInfo.InvariantCulture, $" {prop.Name}: {propType}"); } sb.AppendLine(); @@ -1054,6 +1065,45 @@ private void GenerateTypeClass(BuilderModel model) { GenerateTypeClassMethod(sb, method); } + + if (string.Equals(model.TypeId, InteractionInputCollectionTypeId, StringComparison.Ordinal)) + { + EmitInteractionInputCollectionAccessors(sb); + } + } + + private static void EmitInteractionInputCollectionAccessors(System.Text.StringBuilder sb) + { + sb.AppendLine(" def get(self, name: str) -> InteractionInput | None:"); + sb.AppendLine(" \"\"\"Get the input with the specified name, or None if no input matches.\"\"\""); + sb.AppendLine(" lookup_name = name.lower()"); + sb.AppendLine(" for interaction_input in self.to_array():"); + sb.AppendLine(" input_name = interaction_input.get(\"Name\")"); + sb.AppendLine(" if input_name is not None and input_name.lower() == lookup_name:"); + sb.AppendLine(" return interaction_input"); + sb.AppendLine(" return None"); + sb.AppendLine(); + + sb.AppendLine(" def required(self, name: str) -> InteractionInput:"); + sb.AppendLine(" \"\"\"Get the input with the specified name, or raise ValueError if no input matches.\"\"\""); + sb.AppendLine(" interaction_input = self.get(name)"); + sb.AppendLine(" if interaction_input is None:"); + sb.AppendLine(" raise ValueError(f\"no input with name '{name}' was found\")"); + sb.AppendLine(" return interaction_input"); + sb.AppendLine(); + + sb.AppendLine(" def value(self, name: str) -> str:"); + sb.AppendLine(" \"\"\"Get the input value with the specified name, or an empty string if no input matches.\"\"\""); + sb.AppendLine(" interaction_input = self.get(name)"); + sb.AppendLine(" if interaction_input is None:"); + sb.AppendLine(" return \"\""); + sb.AppendLine(" return interaction_input.get(\"Value\") or \"\""); + sb.AppendLine(); + + sb.AppendLine(" def required_value(self, name: str) -> str:"); + sb.AppendLine(" \"\"\"Get the input value with the specified name, or raise ValueError if no input matches.\"\"\""); + sb.AppendLine(" return self.required(name).get(\"Value\") or \"\""); + sb.AppendLine(); } /// diff --git a/src/Aspire.Hosting.CodeGeneration.Rust/AtsRustCodeGenerator.cs b/src/Aspire.Hosting.CodeGeneration.Rust/AtsRustCodeGenerator.cs index 4acdeac6666..caa831065a1 100644 --- a/src/Aspire.Hosting.CodeGeneration.Rust/AtsRustCodeGenerator.cs +++ b/src/Aspire.Hosting.CodeGeneration.Rust/AtsRustCodeGenerator.cs @@ -945,7 +945,16 @@ private string MapTypeRefToRustForDto(AtsTypeRef? typeRef, bool isOptional) // Use Handle directly for handle types in DTOs since Handle implements Serialize/Deserialize AtsTypeCategory.Handle => "Handle", AtsTypeCategory.Dto => MapDtoType(typeRef.TypeId), - AtsTypeCategory.Callback => "Value", // Callbacks can't be serialized in DTOs + // DTO callback properties remain weakly typed (a serde_json::Value) in Rust. Unlike Go, Java, + // Python, and TypeScript -- whose generators emit strongly-typed DTO callbacks and whose + // runtimes auto-register closures embedded in serialized DTO maps -- Rust DTOs are built on + // serde derive, and a Box closure is not serde-serializable. Supporting it would + // require serde-skipping the field plus a hand-written to_map that registers the closure via + // the global register_callback. This matches the existing behavior for all other DTO + // callbacks (e.g. HttpCommandExportOptions.PrepareRequest from PR #17950), and there is no + // Rust polyglot bench to validate a runtime implementation, so Rust DTO callbacks are left + // weakly typed as a known follow-up. + AtsTypeCategory.Callback => "Value", AtsTypeCategory.Array => $"Vec<{MapTypeRefToRustForDto(typeRef.ElementType, false)}>", AtsTypeCategory.List => $"Vec<{MapTypeRefToRustForDto(typeRef.ElementType, false)}>", AtsTypeCategory.Dict => $"HashMap<{MapTypeRefToRustForDto(typeRef.KeyType, false)}, {MapTypeRefToRustForDto(typeRef.ValueType, false)}>", diff --git a/src/Aspire.Hosting.CodeGeneration.TypeScript/AtsTypeScriptCodeGenerator.cs b/src/Aspire.Hosting.CodeGeneration.TypeScript/AtsTypeScriptCodeGenerator.cs index 3991dc40531..b6a33f0971c 100644 --- a/src/Aspire.Hosting.CodeGeneration.TypeScript/AtsTypeScriptCodeGenerator.cs +++ b/src/Aspire.Hosting.CodeGeneration.TypeScript/AtsTypeScriptCodeGenerator.cs @@ -892,7 +892,8 @@ private string GenerateAspireSdk(AtsContext context) ReferenceExpression, refExpr, AspireDict, - AspireList + AspireList, + InteractionInputCollectionPromiseImpl } from './base.mjs'; export { @@ -902,13 +903,15 @@ private string GenerateAspireSdk(AtsContext context) export type { InteractionInput, - InteractionInputOption + InteractionInputOption, + InteractionInputCollectionPromise } from './base.mjs'; import type { Awaitable, InteractionInput, InteractionInputCollection, + InteractionInputCollectionPromise, InputType } from './base.mjs'; """); @@ -1008,6 +1011,18 @@ import type { _typesWithPromiseWrappers.Add(typeClass.TypeId); } } + + // InteractionInputCollection is a hand-written base.mts type: its by-name accessors + // (value/get/required/requiredValue) are client-side conveniences, not ATS capabilities, so + // it is never registered as a generated type class. Register it as a promise-wrapper type so + // collection-returning getters (result.inputs(), validationContext.inputs(), command + // arguments()) emit the fluent InteractionInputCollectionPromise thenable instead of a bare + // Promise. That lets callers chain `await x.inputs().value("c")` + // without an intermediate await, matching the C#/Go/Java/Python surfaces. The wrapper + // (InteractionInputCollectionPromise / InteractionInputCollectionPromiseImpl) is hand-written + // in base.mts; it is intentionally absent from _wrapperClassNames so the getter impl keeps + // using the marshaller-based collection construction rather than a handle+Impl wrapper. + _typesWithPromiseWrappers.Add(InteractionInputCollectionTypeId); // Note: ReferenceExpression is intentionally NOT added to _wrapperClassNames. // It is a value type defined in base.mts with a private constructor and static factory, // not a handle-based wrapper. It is handled via MapTypeRefToTypeScript instead. @@ -1419,12 +1434,18 @@ private static bool TryGetDirectOptionsParameter(List optional // When ATS already exposes a single DTO parameter named "options", reuse that DTO type // directly so the generated TypeScript API stays flat instead of wrapping it in another // generated options object. - if (optionalParams.Count != 1) + // + // A trailing cancellation token is threaded as its own parameter (see + // GetTrailingCancellationTokenParameter), so ignore it here. That keeps the generated + // signature flat — e.g. promptNotification(title, message, options?, cancellationToken?) + // — instead of bundling both optionals into a generated { options?, cancellationToken? } bag. + var nonCancellationOptionals = optionalParams.Where(p => !IsCancellationTokenType(p.Type)).ToList(); + if (nonCancellationOptionals.Count != 1) { return false; } - var candidate = optionalParams[0]; + var candidate = nonCancellationOptionals[0]; if (!string.Equals(candidate.Name, "options", StringComparison.Ordinal) || candidate.Type?.Category != AtsTypeCategory.Dto) { return false; @@ -1434,6 +1455,21 @@ private static bool TryGetDirectOptionsParameter(List optional return true; } + /// + /// When the options DTO is threaded directly (see ), + /// returns the trailing cancellation token optional parameter (if any) so it can be appended to + /// the generated method as its own argument rather than being folded into a generated options bag. + /// + private static AtsParameterInfo? GetTrailingCancellationTokenParameter(List optionalParams) + { + if (!TryGetDirectOptionsParameter(optionalParams, out _)) + { + return null; + } + + return optionalParams.FirstOrDefault(p => IsCancellationTokenType(p.Type)); + } + /// /// Registers an options interface to be generated later. /// Uses method name to create the interface name. When methods share a name but have @@ -1616,7 +1652,8 @@ private string BuildPublicParameterList( List requiredParams, bool hasOptionals, string optionsInterfaceName, - string optionsParameterName = "options") + string optionsParameterName = "options", + AtsParameterInfo? trailingCancellationToken = null) { var publicParamDefs = new List(); foreach (var param in requiredParams) @@ -1628,6 +1665,10 @@ private string BuildPublicParameterList( { publicParamDefs.Add($"{optionsParameterName}?: {optionsInterfaceName}"); } + if (trailingCancellationToken is not null) + { + publicParamDefs.Add($"{trailingCancellationToken.Name}?: {MapParameterToTypeScript(trailingCancellationToken)}"); + } return string.Join(", ", publicParamDefs); } @@ -1815,7 +1856,7 @@ private void GenerateBuilderInterface(BuilderModel builder) var hasOptionals = optionalParams.Count > 0; var hasDirectOptionsParameter = TryGetDirectOptionsParameter(optionalParams, out var directOptionsParam); var optionsInterfaceName = hasDirectOptionsParameter ? MapParameterToTypeScript(directOptionsParam!) : ResolveOptionsInterfaceName(capability); - var publicParamsString = BuildPublicParameterList(requiredParams, hasOptionals, optionsInterfaceName); + var publicParamsString = BuildPublicParameterList(requiredParams, hasOptionals, optionsInterfaceName, trailingCancellationToken: GetTrailingCancellationTokenParameter(optionalParams)); var hasNonBuilderReturn = !capability.ReturnsBuilder && capability.ReturnType != null; WriteCapabilityDocComment(" ", capability, requiredParams, hasOptionals ? "options" : null); @@ -1875,7 +1916,7 @@ private void GenerateBuilderPromiseInterface(BuilderModel builder) var hasOptionals = optionalParams.Count > 0; var hasDirectOptionsParameter = TryGetDirectOptionsParameter(optionalParams, out var directOptionsParam); var optionsInterfaceName = hasDirectOptionsParameter ? MapParameterToTypeScript(directOptionsParam!) : ResolveOptionsInterfaceName(capability); - var paramsString = BuildPublicParameterList(requiredParams, hasOptionals, optionsInterfaceName); + var paramsString = BuildPublicParameterList(requiredParams, hasOptionals, optionsInterfaceName, trailingCancellationToken: GetTrailingCancellationTokenParameter(optionalParams)); var hasNonBuilderReturn = !capability.ReturnsBuilder && capability.ReturnType != null; WriteCapabilityDocComment(" ", capability, requiredParams, hasOptionals ? "options" : null); @@ -1912,7 +1953,7 @@ private void GenerateTypeClassInterfaceMethod(string className, AtsCapabilityInf var hasOptionals = optionalParams.Count > 0; var hasDirectOptionsParameter = TryGetDirectOptionsParameter(optionalParams, out var directOptionsParam); var optionsInterfaceName = hasDirectOptionsParameter ? MapParameterToTypeScript(directOptionsParam!) : ResolveOptionsInterfaceName(capability); - var paramsString = BuildPublicParameterList(requiredParams, hasOptionals, optionsInterfaceName); + var paramsString = BuildPublicParameterList(requiredParams, hasOptionals, optionsInterfaceName, trailingCancellationToken: GetTrailingCancellationTokenParameter(optionalParams)); var isVoid = capability.ReturnType == null || capability.ReturnType.TypeId == AtsConstants.Void; WriteCapabilityDocComment(" ", capability, requiredParams, hasOptionals ? "options" : null); @@ -2084,7 +2125,7 @@ private void GenerateBuilderMethod(BuilderModel builder, AtsCapabilityInfo capab var publicOptionsParamName = GetPublicOptionsParameterName(userParams, hasOptionals, hasDirectOptionsParameter); // Build parameter list for public method - var publicParamsString = BuildPublicParameterList(requiredParams, hasOptionals, optionsTypeName, publicOptionsParamName); + var publicParamsString = BuildPublicParameterList(requiredParams, hasOptionals, optionsTypeName, publicOptionsParamName, GetTrailingCancellationTokenParameter(optionalParams)); // Build parameter list for internal method (all params positional for callback registration) var internalParamDefs = new List(); @@ -2661,6 +2702,7 @@ private void GenerateThenableClass(BuilderModel builder) var hasOptionals = optionalParams.Count > 0; var hasDirectOptionsParameter = TryGetDirectOptionsParameter(optionalParams, out var directOptionsParam); var optionsTypeName = hasDirectOptionsParameter ? MapParameterToTypeScript(directOptionsParam!) : ResolveOptionsInterfaceName(capability); + var trailingCancellationToken = GetTrailingCancellationTokenParameter(optionalParams); // Build parameter list using options pattern var publicParamDefs = new List(); @@ -2673,6 +2715,10 @@ private void GenerateThenableClass(BuilderModel builder) { publicParamDefs.Add($"options?: {optionsTypeName}"); } + if (trailingCancellationToken is not null) + { + publicParamDefs.Add($"{trailingCancellationToken.Name}?: {MapParameterToTypeScript(trailingCancellationToken)}"); + } var paramsString = string.Join(", ", publicParamDefs); // Forward args to underlying object's method (which handles options extraction) @@ -2685,6 +2731,10 @@ private void GenerateThenableClass(BuilderModel builder) { forwardArgs.Add("options"); } + if (trailingCancellationToken is not null) + { + forwardArgs.Add(trailingCancellationToken.Name); + } var argsString = string.Join(", ", forwardArgs); // Check if this method returns a non-builder type @@ -3476,6 +3526,24 @@ private void GenerateGetterOnlyPropertyMethod(string propertyName, AtsCapability return; } + // Promise-wrapper types that are NOT registered as generated wrapper classes (currently only + // InteractionInputCollection, a hand-written base.mts type) wrap the marshalled collection + // promise in their hand-written ...Promise thenable so by-name accessors chain without an + // intermediate await. Awaiting the wrapper still resolves to the plain collection, preserving + // the existing `await (await x.inputs()).value(...)` form. + if (TryGetPromiseWrapperType(getter.ReturnType, out var promiseInterfaceName, out var promiseImplementationClassName)) + { + var collectionType = GetGetterOnlyPropertyReturnType(getter.ReturnType); + WriteLine($" {propertyName}(): {promiseInterfaceName} {{"); + WriteLine($" return new {promiseImplementationClassName}(this._client.invokeCapability<{collectionType}>("); + WriteLine($" '{getter.CapabilityId}',"); + WriteLine(" { context: this._handle }"); + WriteLine(" ), this._client, false);"); + WriteLine(" }"); + WriteLine(); + return; + } + var returnType = GetGetterOnlyPropertyReturnType(getter.ReturnType); WriteLine($" async {propertyName}(): Promise<{returnType}> {{"); @@ -3784,7 +3852,7 @@ private void GenerateContextMethod(AtsCapabilityInfo method) var publicOptionsParamName = GetPublicOptionsParameterName(userParams, hasOptionals, hasDirectOptionsParameter); // Build parameter list using options pattern - var paramsString = BuildPublicParameterList(requiredParams, hasOptionals, optionsInterfaceName, publicOptionsParamName); + var paramsString = BuildPublicParameterList(requiredParams, hasOptionals, optionsInterfaceName, publicOptionsParamName, GetTrailingCancellationTokenParameter(optionalParams)); // Determine return type var returnType = GetReturnTypeId(method) != null @@ -3899,7 +3967,7 @@ private void GenerateWrapperMethod(AtsCapabilityInfo capability) var publicOptionsParamName = GetPublicOptionsParameterName(userParams, hasOptionals, hasDirectOptionsParameter); // Build parameter list using options pattern - var paramsString = BuildPublicParameterList(requiredParams, hasOptionals, optionsInterfaceName, publicOptionsParamName); + var paramsString = BuildPublicParameterList(requiredParams, hasOptionals, optionsInterfaceName, publicOptionsParamName, GetTrailingCancellationTokenParameter(optionalParams)); // Determine return type var returnType = MapTypeRefToTypeScript(capability.ReturnType); @@ -4017,7 +4085,7 @@ private void GenerateTypeClassMethod(BuilderModel model, AtsCapabilityInfo capab var publicOptionsParamName = GetPublicOptionsParameterName(userParams, hasOptionals, hasDirectOptionsParameter); // Build parameter list for public method - var publicParamsString = BuildPublicParameterList(requiredParams, hasOptionals, optionsInterfaceName, publicOptionsParamName); + var publicParamsString = BuildPublicParameterList(requiredParams, hasOptionals, optionsInterfaceName, publicOptionsParamName, GetTrailingCancellationTokenParameter(optionalParams)); // Build parameter list for internal method (all params positional) var internalParamDefs = new List(); @@ -4135,7 +4203,7 @@ private void GenerateTypeClassMethod(BuilderModel model, AtsCapabilityInfo capab WriteLine($"): {promiseClass} {{"); // Extract optional params and forward - foreach (var param in optionalParams) + foreach (var param in hasDirectOptionsParameter ? [] : optionalParams) { var localParameterName = GetLocalParameterName(param); WriteLine($" {(IsWidenedHandleType(param.Type) ? "let" : "const")} {localParameterName} = {publicOptionsParamName}?.{param.Name};"); @@ -4155,7 +4223,7 @@ private void GenerateTypeClassMethod(BuilderModel model, AtsCapabilityInfo capab WriteLine($"): Promise<{returnType}> {{"); // Extract optional params from options object - foreach (var param in optionalParams) + foreach (var param in hasDirectOptionsParameter ? [] : optionalParams) { var localParameterName = GetLocalParameterName(param); WriteLine($" {(IsWidenedHandleType(param.Type) ? "let" : "const")} {localParameterName} = {publicOptionsParamName}?.{param.Name};"); @@ -4277,6 +4345,7 @@ private void GenerateTypeClassThenableWrapper(BuilderModel model, List 0; var hasDirectOptionsParameter = TryGetDirectOptionsParameter(optionalParams, out var directOptionsParam); var optionsInterfaceName = hasDirectOptionsParameter ? MapParameterToTypeScript(directOptionsParam!) : ResolveOptionsInterfaceName(capability); + var trailingCancellationToken = GetTrailingCancellationTokenParameter(optionalParams); // Build parameter list using options pattern var publicParamDefs = new List(); @@ -4289,6 +4358,10 @@ private void GenerateTypeClassThenableWrapper(BuilderModel model, List new InteractionInputCollection(handle as InteractionInputCollectionHandle, client) ); +/** + * Thenable wrapper for {@link InteractionInputCollection} that enables fluent by-name access. + * + * Collection-returning getters (for example `result.inputs()`, `validationContext.inputs()`, and a + * command's `arguments()`) return this instead of a bare `Promise`. It + * is awaitable — `await x.inputs()` still resolves to the {@link InteractionInputCollection} — but + * it also forwards the by-name accessors, so callers can chain `await result.inputs().value("color")` + * without an intermediate await. The C#, Go, Java, and Python surfaces already chain this way; this + * restores the same ergonomics for TypeScript, where the underlying accessor is an async RPC. + */ +export interface InteractionInputCollectionPromise extends PromiseLike { + /** Returns a snapshot copy of all inputs in the collection. */ + toArray(): Promise; + /** Gets the input with the specified name, or `undefined` if no such input exists. */ + get(name: string): Promise; + /** Gets the input with the specified name, throwing if no such input exists. */ + required(name: string): Promise; + /** Gets the value of the input with the specified name, or `undefined` if absent. */ + value(name: string): Promise; + /** Gets the value of the input with the specified name, throwing if absent or unset. */ + requiredValue(name: string): Promise; +} + +export class InteractionInputCollectionPromiseImpl implements InteractionInputCollectionPromise { + constructor(private _promise: Promise, private _client: AspireClientRpc, track = true) { + if (track) { _client.trackPromise(_promise); } + } + + then( + onfulfilled?: ((value: InteractionInputCollection) => TResult1 | PromiseLike) | null, + onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null + ): PromiseLike { + return this._promise.then(onfulfilled, onrejected); + } + + toArray(): Promise { + return this._promise.then(c => c.toArray()); + } + + get(name: string): Promise { + return this._promise.then(c => c.get(name)); + } + + required(name: string): Promise { + return this._promise.then(c => c.required(name)); + } + + value(name: string): Promise { + return this._promise.then(c => c.value(name)); + } + + requiredValue(name: string): Promise { + return this._promise.then(c => c.requiredValue(name)); + } +} + // ============================================================================ // ResourceBuilderBase // ============================================================================ diff --git a/src/Aspire.Hosting/ApplicationModel/ResourceCommandAnnotation.cs b/src/Aspire.Hosting/ApplicationModel/ResourceCommandAnnotation.cs index 09106bd5360..b25dd333f5a 100644 --- a/src/Aspire.Hosting/ApplicationModel/ResourceCommandAnnotation.cs +++ b/src/Aspire.Hosting/ApplicationModel/ResourceCommandAnnotation.cs @@ -444,7 +444,7 @@ public sealed class ExecuteCommandContext /// The service provider. /// [Obsolete("Use Services instead.")] - [AspireExportIgnore(Reason = "IServiceProvider is not usable from polyglot command callbacks.")] + [AspireExportIgnore(Reason = "Obsolete alias for Services.")] public IServiceProvider ServiceProvider { get => Services; @@ -454,7 +454,6 @@ public IServiceProvider ServiceProvider /// /// The service provider. /// - [AspireExportIgnore(Reason = "IServiceProvider is not usable from polyglot command callbacks.")] public required IServiceProvider Services { get; init; } /// diff --git a/src/Aspire.Hosting/Ats/AtsTypeMappings.cs b/src/Aspire.Hosting/Ats/AtsTypeMappings.cs index 00882748bfb..4d75a392f07 100644 --- a/src/Aspire.Hosting/Ats/AtsTypeMappings.cs +++ b/src/Aspire.Hosting/Ats/AtsTypeMappings.cs @@ -1,4 +1,5 @@ #pragma warning disable ASPIREPIPELINES001 +#pragma warning disable ASPIREINTERACTION001 // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. @@ -56,6 +57,7 @@ [assembly: AspireExport(typeof(ResourceNotificationService))] [assembly: AspireExport(typeof(ResourceLoggerService))] [assembly: AspireExport(typeof(ResourceCommandService))] +[assembly: AspireExport(typeof(IInteractionService))] // Additional framework and hosting types we reference [assembly: AspireExport(typeof(IConfiguration))] diff --git a/src/Aspire.Hosting/Ats/InteractionExports.cs b/src/Aspire.Hosting/Ats/InteractionExports.cs new file mode 100644 index 00000000000..b6f0c81936a --- /dev/null +++ b/src/Aspire.Hosting/Ats/InteractionExports.cs @@ -0,0 +1,776 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.DependencyInjection; + +namespace Aspire.Hosting.Ats; + +#pragma warning disable ASPIREINTERACTION001 // IInteractionService and related types are experimental. + +/// +/// ATS exports for the interaction service. +/// +/// +/// +/// The interaction service surface is tailored for polyglot app hosts rather than exposed directly. The shipped +/// .NET API models inputs with delegate-bearing option types (for example ) +/// that cannot be serialized as ATS DTOs. Instead, polyglot callers build inputs through factory capabilities that +/// return the opaque handle, attach behavior such as dynamic loading via +/// callbacks on that handle, and then pass the handles to the prompt capabilities. +/// +/// +internal static class InteractionExports +{ + /// + /// Gets the interaction service from the service provider. + /// + /// The service provider handle. + /// An interaction service handle. + [AspireExport] + public static IInteractionService GetInteractionService(this IServiceProvider serviceProvider) + { + ArgumentNullException.ThrowIfNull(serviceProvider); + + return serviceProvider.GetRequiredService(); + } + + /// + /// Gets a value indicating whether the interaction service is available to prompt the user. + /// + /// The interaction service handle. + /// when the service can prompt the user; otherwise . + [AspireExport] + public static bool IsAvailable(this IInteractionService interactionService) + { + ArgumentNullException.ThrowIfNull(interactionService); + + return interactionService.IsAvailable; + } + + /// + /// Prompts the user for confirmation with an OK/Cancel dialog. + /// + [AspireExport] + public static async Task PromptConfirmation( + this IInteractionService interactionService, + string title, + string message, + InteractionMessageBoxOptions? options = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(interactionService); + + var result = await interactionService.PromptConfirmationAsync(title, message, options?.ToOptions(), cancellationToken).ConfigureAwait(false); + return BoolInteractionResult.From(result); + } + + /// + /// Prompts the user with a message box dialog. + /// + [AspireExport] + public static async Task PromptMessageBox( + this IInteractionService interactionService, + string title, + string message, + InteractionMessageBoxOptions? options = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(interactionService); + + var result = await interactionService.PromptMessageBoxAsync(title, message, options?.ToOptions(), cancellationToken).ConfigureAwait(false); + return BoolInteractionResult.From(result); + } + + /// + /// Prompts the user with a notification. + /// + [AspireExport] + public static async Task PromptNotification( + this IInteractionService interactionService, + string title, + string message, + InteractionNotificationOptions? options = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(interactionService); + + var result = await interactionService.PromptNotificationAsync(title, message, options?.ToOptions(), cancellationToken).ConfigureAwait(false); + return BoolInteractionResult.From(result); + } + + /// + /// Prompts the user for a single input. + /// + // Prompts can invoke dynamic-loading and validation callbacks that re-enter the remote host through ATS, so the + // synchronous invocation path must run on a background thread to keep the JSON-RPC loop processing nested callbacks. + [AspireExport(RunSyncOnBackgroundThread = true)] + public static async Task PromptInput( + this IInteractionService interactionService, + string title, + string? message, + InteractionInputBuilder input, + InteractionInputsDialogOptions? options = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(interactionService); + ArgumentNullException.ThrowIfNull(input); + + var result = await interactionService.PromptInputAsync(title, message, input.Input, options?.ToOptions(), cancellationToken).ConfigureAwait(false); + return InputInteractionResult.From(result); + } + + /// + /// Prompts the user for multiple inputs. + /// + // Prompts can invoke dynamic-loading and validation callbacks that re-enter the remote host through ATS, so the + // synchronous invocation path must run on a background thread to keep the JSON-RPC loop processing nested callbacks. + [AspireExport(RunSyncOnBackgroundThread = true)] + public static async Task PromptInputs( + this IInteractionService interactionService, + string title, + string? message, + InteractionInputBuilder[] inputs, + InteractionInputsDialogOptions? options = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(interactionService); + ArgumentNullException.ThrowIfNull(inputs); + + var interactionInputs = new InteractionInput[inputs.Length]; + for (var i = 0; i < inputs.Length; i++) + { + var input = inputs[i] ?? throw new ArgumentException($"The input at index {i} cannot be null.", nameof(inputs)); + interactionInputs[i] = input.Input; + } + + var result = await interactionService.PromptInputsAsync(title, message, interactionInputs, options?.ToOptions(), cancellationToken).ConfigureAwait(false); + return InputsInteractionResult.From(result); + } + + // The input factories hang off IInteractionService so the ATS scanner treats the service handle as the + // receiver (polyglot: interactionService.createTextInput(...)). The receiver itself is unused because inputs + // are independent of the service, so suppress the unused-parameter analyzer for the factory block. +#pragma warning disable IDE0060 // Remove unused parameter + /// + /// Creates a single-line text input. + /// + [AspireExport] + public static InteractionInputBuilder CreateTextInput(this IInteractionService interactionService, string name, CreateInteractionInputOptions? options = null) + { + return InteractionInputBuilder.Create(name, InputType.Text, options); + } + + /// + /// Creates a secret (masked) text input. + /// + [AspireExport] + public static InteractionInputBuilder CreateSecretInput(this IInteractionService interactionService, string name, CreateInteractionInputOptions? options = null) + { + return InteractionInputBuilder.Create(name, InputType.SecretText, options); + } + + /// + /// Creates a boolean (checkbox) input. + /// + [AspireExport] + public static InteractionInputBuilder CreateBooleanInput(this IInteractionService interactionService, string name, CreateInteractionInputOptions? options = null) + { + return InteractionInputBuilder.Create(name, InputType.Boolean, options); + } + + /// + /// Creates a numeric input. + /// + [AspireExport] + public static InteractionInputBuilder CreateNumberInput(this IInteractionService interactionService, string name, CreateInteractionInputOptions? options = null) + { + return InteractionInputBuilder.Create(name, InputType.Number, options); + } + + /// + /// Creates a choice input that selects from a list of options. + /// + /// The interaction service. + /// The name of the input. + /// The available choices, in display order. Each option pairs a submitted value with a display label. + /// Optional configuration for the input. + [AspireExport] + public static InteractionInputBuilder CreateChoiceInput(this IInteractionService interactionService, string name, IReadOnlyList? choices = null, CreateInteractionInputOptions? options = null) + { + var builder = InteractionInputBuilder.Create(name, InputType.Choice, options); + if (choices is { Count: > 0 }) + { + builder.Input.Options = ToOptionList(choices); + } + + return builder; + } +#pragma warning restore IDE0060 // Remove unused parameter + + // Preserve the caller-specified order: the native Options list is ordered, and the order is user-visible in the + // rendered dropdown. Materialize a copy so a caller-held list cannot mutate the input after the fact. + internal static IReadOnlyList> ToOptionList(IReadOnlyList choices) + { + var list = new List>(choices.Count); + foreach (var choice in choices) + { + list.Add(KeyValuePair.Create(choice.Value, choice.Label)); + } + + return list; + } + + // The engine returns the same InteractionInput instances that the builders own, and those still carry the + // dynamic-loading delegate on DynamicLoading.LoadCallback. That delegate is a .NET Func that cannot be + // serialized across the ATS/JSON-RPC boundary, so project result inputs onto callback-free copies before they + // are sent back to the polyglot caller. The caller only consumes data fields such as Name, Value and Options. + internal static InteractionInput ToResultInput(InteractionInput input) + { + return new InteractionInput + { + Name = input.Name, + Label = input.Label, + Description = input.Description, + EnableDescriptionMarkdown = input.EnableDescriptionMarkdown, + InputType = input.InputType, + Required = input.Required, + Options = input.Options, + Value = input.Value, + Placeholder = input.Placeholder, + AllowCustomChoice = input.AllowCustomChoice, + Disabled = input.Disabled, + MaxLength = input.MaxLength, + // DynamicLoading is intentionally omitted: it holds the non-serializable LoadCallback delegate. + }; + } +} + +/// +/// An opaque, server-side builder for an used by polyglot app hosts. +/// +/// +/// The builder owns the live instance. Dynamic-loading callbacks mutate this same +/// instance through , which is why the input is modeled as a handle here +/// instead of the by-value InteractionInput DTO. +/// +[AspireExport] +internal sealed class InteractionInputBuilder +{ + private InteractionInputBuilder(InteractionInput input) + { + Input = input; + } + + internal InteractionInput Input { get; } + + internal static InteractionInputBuilder Create(string name, InputType inputType, CreateInteractionInputOptions? options) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + + var input = new InteractionInput + { + Name = name, + InputType = inputType, + Label = options?.Label, + Description = options?.Description, + EnableDescriptionMarkdown = options?.EnableDescriptionMarkdown ?? false, + Required = options?.Required ?? false, + Placeholder = options?.Placeholder, + Value = options?.Value, + AllowCustomChoice = options?.AllowCustomChoice ?? false, + Disabled = options?.Disabled ?? false, + MaxLength = options?.MaxLength, + }; + + return new InteractionInputBuilder(input); + } + + /// + /// Sets the choice options for the input. + /// + /// The available choices, in display order. Each option pairs a submitted value with a display label. + /// The same builder handle. + [AspireExport] + public InteractionInputBuilder WithChoiceOptions(IReadOnlyList choices) + { + ArgumentNullException.ThrowIfNull(choices); + + Input.Options = InteractionExports.ToOptionList(choices); + return this; + } + + /// + /// Sets the value of the input. + /// + /// The value to assign. + /// The same builder handle. + [AspireExport] + public InteractionInputBuilder WithValue(string? value) + { + Input.Value = value; + return this; + } + + /// + /// Attaches a callback that dynamically loads or updates the input after the prompt starts. + /// + /// The callback invoked to load the input. Use the supplied context to read other inputs and update this input. + /// Optional configuration that controls when the callback runs. + /// The same builder handle. + [AspireExport] + public InteractionInputBuilder WithDynamicLoading(Func callback, DynamicLoadingOptions? options = null) + { + ArgumentNullException.ThrowIfNull(callback); + + // Bridge the engine's LoadInputContext to the curated polyglot context so callbacks never see the raw + // IServiceProvider and can only mutate the live input through guarded setters. + Input.SetDynamicLoading(new InputLoadOptions + { + LoadCallback = loadContext => callback(new InteractionInputLoadContext(loadContext)), + AlwaysLoadOnStart = options?.AlwaysLoadOnStart ?? false, + DependsOnInputs = options?.DependsOnInputs, + }); + + return this; + } +} + +/// +/// The context passed to a polyglot dynamic-loading callback. Exposes the loading input as a handle and provides +/// read access to the other inputs in the prompt. +/// +[AspireExport(ExposeProperties = true)] +internal sealed class InteractionInputLoadContext +{ + private readonly LoadInputContext _inner; + private readonly InteractionLoadingInput _input; + + internal InteractionInputLoadContext(LoadInputContext inner) + { + _inner = inner; + _input = new InteractionLoadingInput(inner); + } + + /// + /// Gets a handle to the input that is loading. Mutate the input through this handle. + /// + /// A handle to the loading input. + /// + /// Mirrors the native LoadInputContext.Input: the callback updates the live input it is loading, rather than + /// the context itself. The input is a handle (not a by-value DTO) so guarded setters route back to the server-side + /// input across the ATS boundary. + /// + [AspireExport] + public InteractionLoadingInput Input() + { + return _input; + } + + /// + /// Gets all inputs in the prompt, including the one currently loading. + /// + /// + /// Mirrors the native LoadInputContext.AllInputs. Use the collection's by-name accessors (for example + /// value or requiredValue) to read the dependency inputs declared via + /// . This is the same + /// idiom used by the validation callback and prompt results, so reading inputs by name is consistent across every + /// callback context. This is exposed as a property (rather than a method) so it routes through the generated + /// collection accessor, matching the other contexts that surface an . + /// + public InteractionInputCollection Inputs => _inner.AllInputs; +} + +/// +/// A handle to the input currently being loaded by a dynamic-loading callback. Mirrors the native +/// LoadInputContext.Input by letting callbacks update the live input directly. +/// +/// +/// The handle owns the live for the duration of the load callback. Setters are routed +/// back to the server-side input across the ATS boundary, which is why this is a handle rather than the by-value +/// InteractionInput DTO. +/// +[AspireExport] +internal sealed class InteractionLoadingInput +{ + private readonly LoadInputContext _inner; + + internal InteractionLoadingInput(LoadInputContext inner) + { + _inner = inner; + } + + /// + /// Gets the name of the input. + /// + /// The input name. + [AspireExport] + public string GetName() + { + return _inner.Input.Name; + } + + /// + /// Sets the choice options for the input. + /// + /// The available choices, in display order. Each option pairs a submitted value with a display label. + [AspireExport] + public void SetChoiceOptions(IReadOnlyList choices) + { + ArgumentNullException.ThrowIfNull(choices); + + // Honor cancellation so a stale load that was superseded by a newer one does not overwrite the input. + _inner.CancellationToken.ThrowIfCancellationRequested(); + _inner.Input.Options = InteractionExports.ToOptionList(choices); + } + + /// + /// Sets the value of the input. + /// + /// The value to assign. + [AspireExport] + public void SetValue(string? value) + { + _inner.CancellationToken.ThrowIfCancellationRequested(); + _inner.Input.Value = value; + } +} + +/// +/// A single selectable option for a choice input. Options are presented in the order supplied. +/// +[AspireDto] +internal sealed class InteractionChoiceOption +{ + /// + /// Gets or sets the value submitted when this option is selected. + /// + public string Value { get; set; } = string.Empty; + + /// + /// Gets or sets the label displayed for this option. + /// + public string Label { get; set; } = string.Empty; +} + +/// +/// Optional configuration shared by interaction input factory capabilities. +/// +[AspireDto] +internal sealed class CreateInteractionInputOptions +{ + /// + /// Gets or sets the label for the input. Defaults to the input name when not specified. + /// + public string? Label { get; init; } + + /// + /// Gets or sets the description for the input. + /// + public string? Description { get; init; } + + /// + /// Gets or sets a value indicating whether the description is rendered as Markdown. + /// + public bool? EnableDescriptionMarkdown { get; init; } + + /// + /// Gets or sets a value indicating whether the input is required. + /// + public bool? Required { get; init; } + + /// + /// Gets or sets the placeholder text for the input. + /// + public string? Placeholder { get; init; } + + /// + /// Gets or sets the initial value of the input. + /// + public string? Value { get; init; } + + /// + /// Gets or sets a value indicating whether a custom choice is allowed. Only used by choice inputs. + /// + public bool? AllowCustomChoice { get; init; } + + /// + /// Gets or sets a value indicating whether the input is disabled. + /// + public bool? Disabled { get; init; } + + /// + /// Gets or sets the maximum length for text inputs. + /// + public int? MaxLength { get; init; } +} + +/// +/// Options controlling when a dynamic-loading callback runs. +/// +[AspireDto] +internal sealed class DynamicLoadingOptions +{ + /// + /// Gets or sets a value indicating whether the callback always runs at the start of the prompt. + /// + public bool? AlwaysLoadOnStart { get; init; } + + /// + /// Gets or sets the names of inputs this input depends on. The callback runs when any of them change. + /// + public IReadOnlyList? DependsOnInputs { get; init; } +} + +/// +/// Options for message box and confirmation prompts. +/// +[AspireDto] +internal sealed class InteractionMessageBoxOptions +{ + /// + /// Gets or sets the primary button text. + /// + public string? PrimaryButtonText { get; init; } + + /// + /// Gets or sets the secondary button text. + /// + public string? SecondaryButtonText { get; init; } + + /// + /// Gets or sets a value indicating whether the secondary button is shown. + /// + public bool? ShowSecondaryButton { get; init; } + + /// + /// Gets or sets a value indicating whether the dismiss button is shown. + /// + public bool? ShowDismiss { get; init; } + + /// + /// Gets or sets a value indicating whether Markdown in the message is rendered. + /// + public bool? EnableMessageMarkdown { get; init; } + + /// + /// Gets or sets the intent of the message box. + /// + public MessageIntent? Intent { get; init; } + + internal MessageBoxInteractionOptions ToOptions() + { + return new MessageBoxInteractionOptions + { + PrimaryButtonText = PrimaryButtonText, + SecondaryButtonText = SecondaryButtonText, + ShowSecondaryButton = ShowSecondaryButton, + ShowDismiss = ShowDismiss, + EnableMessageMarkdown = EnableMessageMarkdown, + Intent = Intent, + }; + } +} + +/// +/// Options for notification prompts. +/// +[AspireDto] +internal sealed class InteractionNotificationOptions +{ + /// + /// Gets or sets the primary button text. + /// + public string? PrimaryButtonText { get; init; } + + /// + /// Gets or sets the secondary button text. + /// + public string? SecondaryButtonText { get; init; } + + /// + /// Gets or sets a value indicating whether the secondary button is shown. + /// + public bool? ShowSecondaryButton { get; init; } + + /// + /// Gets or sets a value indicating whether the dismiss button is shown. + /// + public bool? ShowDismiss { get; init; } + + /// + /// Gets or sets a value indicating whether Markdown in the message is rendered. + /// + public bool? EnableMessageMarkdown { get; init; } + + /// + /// Gets or sets the intent of the notification. + /// + public MessageIntent? Intent { get; init; } + + /// + /// Gets or sets the text for a link in the notification. + /// + public string? LinkText { get; init; } + + /// + /// Gets or sets the URL for the link in the notification. + /// + public string? LinkUrl { get; init; } + + internal NotificationInteractionOptions ToOptions() + { + return new NotificationInteractionOptions + { + PrimaryButtonText = PrimaryButtonText, + SecondaryButtonText = SecondaryButtonText, + ShowSecondaryButton = ShowSecondaryButton, + ShowDismiss = ShowDismiss, + EnableMessageMarkdown = EnableMessageMarkdown, + Intent = Intent, + LinkText = LinkText, + LinkUrl = LinkUrl, + }; + } +} + +/// +/// Options for inputs dialog prompts. +/// +[AspireDto] +internal sealed class InteractionInputsDialogOptions +{ + /// + /// Gets or sets the primary button text. + /// + public string? PrimaryButtonText { get; init; } + + /// + /// Gets or sets the secondary button text. + /// + public string? SecondaryButtonText { get; init; } + + /// + /// Gets or sets a value indicating whether the secondary button is shown. + /// + public bool? ShowSecondaryButton { get; init; } + + /// + /// Gets or sets a value indicating whether the dismiss button is shown. + /// + public bool? ShowDismiss { get; init; } + + /// + /// Gets or sets a value indicating whether Markdown in the message is rendered. + /// + public bool? EnableMessageMarkdown { get; init; } + + /// + /// Gets or sets a callback invoked to validate the inputs before the dialog is accepted. The callback + /// receives a validation context that exposes the current inputs and can record validation errors. + /// + public Func? ValidationCallback { get; init; } + + internal InputsDialogInteractionOptions ToOptions() + { + return new InputsDialogInteractionOptions + { + PrimaryButtonText = PrimaryButtonText, + SecondaryButtonText = SecondaryButtonText, + ShowSecondaryButton = ShowSecondaryButton, + ShowDismiss = ShowDismiss, + EnableMessageMarkdown = EnableMessageMarkdown, + ValidationCallback = ValidationCallback, + }; + } +} + +/// +/// The result of a boolean interaction prompt. +/// +[AspireDto] +internal sealed class BoolInteractionResult +{ + /// + /// Gets a value indicating whether the interaction was canceled by the user. + /// + public required bool Canceled { get; init; } + + /// + /// Gets the value returned from the interaction. Not meaningful when is . + /// + public bool Value { get; init; } + + internal static BoolInteractionResult From(InteractionResult result) + { + return new BoolInteractionResult + { + Canceled = result.Canceled, + Value = !result.Canceled && result.Data, + }; + } +} + +/// +/// The result of a single-input interaction prompt. +/// +[AspireDto] +internal sealed class InputInteractionResult +{ + /// + /// Gets a value indicating whether the interaction was canceled by the user. + /// + public required bool Canceled { get; init; } + + /// + /// Gets the input returned from the interaction. Not present when is . + /// + public InteractionInput? Input { get; init; } + + internal static InputInteractionResult From(InteractionResult result) + { + return new InputInteractionResult + { + Canceled = result.Canceled, + Input = result.Canceled || result.Data is null ? null : InteractionExports.ToResultInput(result.Data), + }; + } +} + +/// +/// The result of a multi-input interaction prompt. +/// +/// +/// Modeled as a handle (not a by-value DTO) so the returned inputs are surfaced as the +/// handle. That lets polyglot callers reuse the same name-based +/// accessors (for example result.inputs().value("color")) that the validation and command-argument +/// collections already expose, instead of having to scan a serialized array by hand. +/// +[AspireExport(ExposeProperties = true)] +internal sealed class InputsInteractionResult +{ + /// + /// Gets a value indicating whether the interaction was canceled by the user. + /// + public required bool Canceled { get; init; } + + /// + /// Gets the inputs returned from the interaction. Empty when is . + /// + public required InteractionInputCollection Inputs { get; init; } + + internal static InputsInteractionResult From(InteractionResult result) + { + // The engine returns the live input instances, which still carry the non-serializable dynamic-loading + // callback on DynamicLoading. Project onto callback-free copies (ToResultInput) before wrapping them in a + // fresh collection so the handle can be enumerated/serialized safely after the prompt completes. + var inputs = result.Canceled || result.Data is null + ? new InteractionInputCollection([]) + : new InteractionInputCollection(result.Data.Select(InteractionExports.ToResultInput).ToArray()); + + return new InputsInteractionResult + { + Canceled = result.Canceled, + Inputs = inputs, + }; + } +} diff --git a/src/Aspire.Hosting/IInteractionService.cs b/src/Aspire.Hosting/IInteractionService.cs index 03ddc8961e4..4ff4a8fb3e8 100644 --- a/src/Aspire.Hosting/IInteractionService.cs +++ b/src/Aspire.Hosting/IInteractionService.cs @@ -327,6 +327,11 @@ public bool Required /// Dynamic loading is used to load data and update inputs after a prompt has started. /// It can also be used to reload data and update inputs after a dependant input has changed. /// + // Excluded from the ATS surface: InputLoadOptions holds a non-serializable LoadCallback delegate, and the + // dynamic-loading payload is always stripped from interaction results at runtime (see InteractionExports.ToResultInput). + // Polyglot app hosts configure dynamic loading through InteractionInputBuilder.WithDynamicLoading, never by reading + // this property back, so advertising it on the result DTO would describe a value that is always null on the wire. + [AspireExportIgnore(Reason = "InputLoadOptions carries a non-serializable callback and is never populated on interaction results.")] public InputLoadOptions? DynamicLoading { get => _dynamicLoading; diff --git a/src/Aspire.Hosting/api/Aspire.Hosting.ats.txt b/src/Aspire.Hosting/api/Aspire.Hosting.ats.txt index 0a68c5696b1..16b2c2ff95e 100644 --- a/src/Aspire.Hosting/api/Aspire.Hosting.ats.txt +++ b/src/Aspire.Hosting/api/Aspire.Hosting.ats.txt @@ -29,6 +29,7 @@ Aspire.Hosting/Aspire.Hosting.ApplicationModel.EnvironmentCallbackContext Aspire.Hosting/Aspire.Hosting.ApplicationModel.EnvironmentEditor Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecutableResource Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecuteCommandContext [ExposeProperties] +Aspire.Hosting/Aspire.Hosting.ApplicationModel.HttpCommandPrepareRequestContext [ExposeProperties] Aspire.Hosting/Aspire.Hosting.ApplicationModel.IAspireStore [interface, ExposeProperties] Aspire.Hosting/Aspire.Hosting.ApplicationModel.IComputeEnvironmentResource [interface] Aspire.Hosting/Aspire.Hosting.ApplicationModel.IExecutionConfigurationBuilder [interface] @@ -57,6 +58,10 @@ Aspire.Hosting/Aspire.Hosting.ApplicationModel.ResourceUrlsCallbackContext Aspire.Hosting/Aspire.Hosting.ApplicationModel.ResourceUrlsEditor Aspire.Hosting/Aspire.Hosting.ApplicationModel.UpdateCommandStateContext [ExposeProperties] Aspire.Hosting/Aspire.Hosting.Ats.EventingSubscriberRegistrationContext [ExposeProperties] +Aspire.Hosting/Aspire.Hosting.Ats.InputsInteractionResult [ExposeProperties] +Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder +Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputLoadContext [ExposeProperties] +Aspire.Hosting/Aspire.Hosting.Ats.InteractionLoadingInput Aspire.Hosting/Aspire.Hosting.DistributedApplication Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContext [ExposeProperties] Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContextOptions @@ -67,6 +72,7 @@ Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEventing [interfac Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationResourceEvent [interface] Aspire.Hosting/Aspire.Hosting.ExternalServiceResource Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder [interface] +Aspire.Hosting/Aspire.Hosting.IInteractionService [interface] Aspire.Hosting/Aspire.Hosting.InputsDialogValidationContext [ExposeProperties] Aspire.Hosting/Aspire.Hosting.InteractionInputCollection Aspire.Hosting/Aspire.Hosting.IResourceWithContainerFiles [interface] @@ -131,6 +137,7 @@ Aspire.Hosting/Aspire.Hosting.ApplicationModel.GenerateParameterDefault # Repres Upper: boolean # Gets or sets a value indicating whether to include uppercase alphabet characters in the result. Aspire.Hosting/Aspire.Hosting.ApplicationModel.HttpCommandExportOptions # ATS-friendly configuration for resource HTTP commands. CommandName: string # Gets or sets the command name. + CommandOptions?: Aspire.Hosting/Aspire.Hosting.ApplicationModel.CommandOptions # Optional command configuration. ConfirmationMessage: string # When a confirmation message is specified, the UI will prompt with an OK/Cancel dialog before starting the command. Description: string # Optional description of the command, to be shown in the UI. EndpointName: string # Gets or sets the HTTP endpoint name to send the request to when the command is invoked. @@ -138,7 +145,13 @@ Aspire.Hosting/Aspire.Hosting.ApplicationModel.HttpCommandExportOptions # ATS-fr IconVariant?: enum:Aspire.Hosting.ApplicationModel.IconVariant # The icon variant. IsHighlighted: boolean # A flag indicating whether the command is highlighted in the UI. MethodName: string # Gets or sets the HTTP method name to use when sending the request. + PrepareRequest?: callback # Gets or sets a callback to be invoked to configure the request before it is sent. ResultMode: enum:Aspire.Hosting.ApplicationModel.HttpCommandResultMode # Gets or sets how the HTTP response content should be returned as command result data. +Aspire.Hosting/Aspire.Hosting.ApplicationModel.HttpCommandRequestExportData # ATS-friendly request data returned from HTTP command prepare-request callbacks. + Content: string # Gets or sets the request content. + ContentType: string # Gets or sets the request content type. + Headers: Aspire.Hosting/Dict # Gets or sets the request headers. + MethodName: string # Gets or sets the HTTP method name to use when sending the request. Aspire.Hosting/Aspire.Hosting.ApplicationModel.HttpsCertificateExecutionConfigurationContext # Configuration context for server authentication certificate configuration. CertificatePath: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ReferenceExpression # Expression that will resolve to the path of the server authentication certificate in PEM format. For containers this will be a path inside the container. KeyPath: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ReferenceExpression # Expression that will resolve to the path of the server authentication certificate key in PEM format. For containers this will be a path inside the container. @@ -146,6 +159,7 @@ Aspire.Hosting/Aspire.Hosting.ApplicationModel.HttpsCertificateExecutionConfigur Aspire.Hosting/Aspire.Hosting.ApplicationModel.ProcessCommandExportOptions # ATS-friendly configuration for resource process commands. Arguments: string[] # The command-line arguments for the process. CommandOptions: Aspire.Hosting/Aspire.Hosting.ApplicationModel.CommandOptions # Optional command configuration. + CreateProcessSpec?: callback # A callback that creates the local process specification when the command is invoked. DisplayImmediately?: boolean # A value indicating whether returned command output should be displayed immediately in the dashboard. EnvironmentVariables: Aspire.Hosting/Dict # The environment variables to set for the process. ExecutablePath: string # The executable path or command name to start. @@ -182,6 +196,9 @@ Aspire.Hosting/Aspire.Hosting.ApplicationModel.UpdateCommandStateResourceSnapsho Aspire.Hosting/Aspire.Hosting.Ats.AddContainerOptions # Options for configuring a container image in polyglot apphosts. Image: string # The container image name. Tag?: string # The container image tag. +Aspire.Hosting/Aspire.Hosting.Ats.BoolInteractionResult # The result of a boolean interaction prompt. + Canceled: boolean # Gets a value indicating whether the interaction was canceled by the user. + Value?: boolean # Gets the value returned from the interaction. Not meaningful when `Canceled` is `true`. Aspire.Hosting/Aspire.Hosting.Ats.CertificateTrustExecutionConfigurationExportData # ATS-friendly certificate trust data returned from an execution-configuration result. CertificateSubjects: string[] # The certificate subjects included in the trust configuration. CustomBundlePaths: string[] # The relative custom bundle paths. @@ -199,6 +216,19 @@ Aspire.Hosting/Aspire.Hosting.Ats.CreateBuilderOptions # Options for creating a DisableDashboard: boolean # Determines whether the dashboard is disabled. EnableResourceLogging: boolean # Enables resource logging. ProjectDirectory: string # The directory containing the AppHost project file. +Aspire.Hosting/Aspire.Hosting.Ats.CreateInteractionInputOptions # Optional configuration shared by interaction input factory capabilities. + AllowCustomChoice?: boolean # Gets or sets a value indicating whether a custom choice is allowed. Only used by choice inputs. + Description?: string # Gets or sets the description for the input. + Disabled?: boolean # Gets or sets a value indicating whether the input is disabled. + EnableDescriptionMarkdown?: boolean # Gets or sets a value indicating whether the description is rendered as Markdown. + Label?: string # Gets or sets the label for the input. Defaults to the input name when not specified. + MaxLength?: number # Gets or sets the maximum length for text inputs. + Placeholder?: string # Gets or sets the placeholder text for the input. + Required?: boolean # Gets or sets a value indicating whether the input is required. + Value?: string # Gets or sets the initial value of the input. +Aspire.Hosting/Aspire.Hosting.Ats.DynamicLoadingOptions # Options controlling when a dynamic-loading callback runs. + AlwaysLoadOnStart?: boolean # Gets or sets a value indicating whether the callback always runs at the start of the prompt. + DependsOnInputs?: string[] # Gets or sets the names of inputs this input depends on. The callback runs when any of them change. Aspire.Hosting/Aspire.Hosting.Ats.HealthCheckResult # ATS-friendly custom health check result. Data?: Aspire.Hosting/Dict # Gets optional string data for the health check result. Description?: string # Gets an optional description for the health check result. @@ -215,6 +245,35 @@ Aspire.Hosting/Aspire.Hosting.Ats.HttpsCertificateInfo # ATS-friendly certificat Issuer: string # The certificate issuer. Subject: string # The certificate subject. Thumbprint?: string # The certificate thumbprint. +Aspire.Hosting/Aspire.Hosting.Ats.InputInteractionResult # The result of a single-input interaction prompt. + Canceled: boolean # Gets a value indicating whether the interaction was canceled by the user. + Input?: Aspire.Hosting/Aspire.Hosting.InteractionInput # Gets the input returned from the interaction. Not present when `Canceled` is `true`. +Aspire.Hosting/Aspire.Hosting.Ats.InteractionChoiceOption # A single selectable option for a choice input. Options are presented in the order supplied. + Label: string # Gets or sets the label displayed for this option. + Value: string # Gets or sets the value submitted when this option is selected. +Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputsDialogOptions # Options for inputs dialog prompts. + EnableMessageMarkdown?: boolean # Gets or sets a value indicating whether Markdown in the message is rendered. + PrimaryButtonText?: string # Gets or sets the primary button text. + SecondaryButtonText?: string # Gets or sets the secondary button text. + ShowDismiss?: boolean # Gets or sets a value indicating whether the dismiss button is shown. + ShowSecondaryButton?: boolean # Gets or sets a value indicating whether the secondary button is shown. + ValidationCallback?: callback # Gets or sets a callback invoked to validate the inputs before the dialog is accepted. The callback receives a validation context that exposes the current inputs and can record validation errors. +Aspire.Hosting/Aspire.Hosting.Ats.InteractionMessageBoxOptions # Options for message box and confirmation prompts. + EnableMessageMarkdown?: boolean # Gets or sets a value indicating whether Markdown in the message is rendered. + Intent?: enum:Aspire.Hosting.MessageIntent # Gets or sets the intent of the message box. + PrimaryButtonText?: string # Gets or sets the primary button text. + SecondaryButtonText?: string # Gets or sets the secondary button text. + ShowDismiss?: boolean # Gets or sets a value indicating whether the dismiss button is shown. + ShowSecondaryButton?: boolean # Gets or sets a value indicating whether the secondary button is shown. +Aspire.Hosting/Aspire.Hosting.Ats.InteractionNotificationOptions # Options for notification prompts. + EnableMessageMarkdown?: boolean # Gets or sets a value indicating whether Markdown in the message is rendered. + Intent?: enum:Aspire.Hosting.MessageIntent # Gets or sets the intent of the notification. + LinkText?: string # Gets or sets the text for a link in the notification. + LinkUrl?: string # Gets or sets the URL for the link in the notification. + PrimaryButtonText?: string # Gets or sets the primary button text. + SecondaryButtonText?: string # Gets or sets the secondary button text. + ShowDismiss?: boolean # Gets or sets a value indicating whether the dismiss button is shown. + ShowSecondaryButton?: boolean # Gets or sets a value indicating whether the secondary button is shown. Aspire.Hosting/Aspire.Hosting.Ats.ParameterCustomInputOptions # Options for customizing parameter inputs from polyglot app hosts. AllowCustomChoice?: boolean # Gets or sets whether custom choices are allowed. Description: string # Gets or sets the description for the input. @@ -242,7 +301,6 @@ Aspire.Hosting/Aspire.Hosting.InteractionInput # Represents an input for an inte AllowCustomChoice?: boolean # Gets a value indicating whether a custom choice is allowed. Only used by `Choice` inputs. Description?: string # Gets or sets the description for the input. Disabled: boolean # Gets or sets a value indicating whether a custom choice is allowed. Only used by `Choice` inputs. - DynamicLoading?: Aspire.Hosting/Aspire.Hosting.InputLoadOptions # Gets the `InputLoadOptions` for the input. Dynamic loading is used to load data and update inputs after a prompt has started. It can also be used to reload data and update inputs after a dependant input has changed. EnableDescriptionMarkdown?: boolean # Gets or sets a value indicating whether the description should be rendered as Markdown. Setting this to `true` allows a description to contain Markdown elements such as links, text decoration and lists. InputType: enum:Aspire.Hosting.InputType # Gets or sets the type of the input. Label?: string # Gets or sets the label for the input. If not specified, the name will be used as the label. @@ -269,6 +327,7 @@ enum:Aspire.Hosting.ApplicationModel.UrlDisplayLocation = SummaryAndDetails | De enum:Aspire.Hosting.ApplicationModel.WaitBehavior = WaitOnResourceUnavailable | StopOnResourceUnavailable enum:Aspire.Hosting.DistributedApplicationOperation = Run | Publish enum:Aspire.Hosting.InputType = Text | SecretText | Choice | Boolean | Number +enum:Aspire.Hosting.MessageIntent = None | Success | Warning | Error | Information | Confirmation enum:Aspire.Hosting.OtlpProtocol = Grpc | HttpProtobuf | HttpJson enum:Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus = Unhealthy | Degraded | Healthy enum:System.Net.Sockets.ProtocolType = IP | IPv6HopByHopOptions | Unspecified | Icmp | Igmp | Ggp | IPv4 | Tcp | Pup | Udp | Idp | IPv6 | IPv6RoutingHeader | IPv6FragmentHeader | IPSecEncapsulatingSecurityPayload | IPSecAuthenticationHeader | IcmpV6 | IPv6NoNextHeader | IPv6DestinationOptions | ND | Raw | Ipx | Spx | SpxII | Unknown @@ -384,8 +443,13 @@ Aspire.Hosting.ApplicationModel/ExecuteCommandContext.arguments(context: Aspire. Aspire.Hosting.ApplicationModel/ExecuteCommandContext.cancellationToken(context: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecuteCommandContext) -> cancellationToken Aspire.Hosting.ApplicationModel/ExecuteCommandContext.logger(context: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecuteCommandContext) -> Microsoft.Extensions.Logging.Abstractions/Microsoft.Extensions.Logging.ILogger Aspire.Hosting.ApplicationModel/ExecuteCommandContext.resourceName(context: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecuteCommandContext) -> string +Aspire.Hosting.ApplicationModel/ExecuteCommandContext.services(context: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecuteCommandContext) -> System.ComponentModel/System.IServiceProvider Aspire.Hosting.ApplicationModel/getEndpoint(context: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ResourceUrlsCallbackContext, name: string) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.EndpointReference Aspire.Hosting.ApplicationModel/getValueAsync(context: Aspire.Hosting/Aspire.Hosting.ApplicationModel.ReferenceExpression, cancellationToken: cancellationToken) -> string +Aspire.Hosting.ApplicationModel/HttpCommandPrepareRequestContext.arguments(context: Aspire.Hosting/Aspire.Hosting.ApplicationModel.HttpCommandPrepareRequestContext) -> Aspire.Hosting/Aspire.Hosting.InteractionInputCollection +Aspire.Hosting.ApplicationModel/HttpCommandPrepareRequestContext.cancellationToken(context: Aspire.Hosting/Aspire.Hosting.ApplicationModel.HttpCommandPrepareRequestContext) -> cancellationToken +Aspire.Hosting.ApplicationModel/HttpCommandPrepareRequestContext.endpoint(context: Aspire.Hosting/Aspire.Hosting.ApplicationModel.HttpCommandPrepareRequestContext) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.EndpointReference +Aspire.Hosting.ApplicationModel/HttpCommandPrepareRequestContext.resourceName(context: Aspire.Hosting/Aspire.Hosting.ApplicationModel.HttpCommandPrepareRequestContext) -> string Aspire.Hosting.ApplicationModel/IAspireStore.basePath(context: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IAspireStore) -> string Aspire.Hosting.ApplicationModel/info(context: Aspire.Hosting/Aspire.Hosting.ApplicationModel.LogFacade, message: string) -> void Aspire.Hosting.ApplicationModel/InitializeResourceEvent.eventing(context: Aspire.Hosting/Aspire.Hosting.ApplicationModel.InitializeResourceEvent) -> Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEventing @@ -412,6 +476,16 @@ Aspire.Hosting.ApplicationModel/UpdateCommandStateContext.resourceSnapshot(conte Aspire.Hosting.ApplicationModel/warning(context: Aspire.Hosting/Aspire.Hosting.ApplicationModel.LogFacade, message: string) -> void Aspire.Hosting.Ats/EventingSubscriberRegistrationContext.cancellationToken(context: Aspire.Hosting/Aspire.Hosting.Ats.EventingSubscriberRegistrationContext) -> cancellationToken Aspire.Hosting.Ats/EventingSubscriberRegistrationContext.executionContext(context: Aspire.Hosting/Aspire.Hosting.Ats.EventingSubscriberRegistrationContext) -> Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContext +Aspire.Hosting.Ats/getName(context: Aspire.Hosting/Aspire.Hosting.Ats.InteractionLoadingInput) -> string +Aspire.Hosting.Ats/input(context: Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputLoadContext) -> Aspire.Hosting/Aspire.Hosting.Ats.InteractionLoadingInput +Aspire.Hosting.Ats/InputsInteractionResult.canceled(context: Aspire.Hosting/Aspire.Hosting.Ats.InputsInteractionResult) -> boolean +Aspire.Hosting.Ats/InputsInteractionResult.inputs(context: Aspire.Hosting/Aspire.Hosting.Ats.InputsInteractionResult) -> Aspire.Hosting/Aspire.Hosting.InteractionInputCollection +Aspire.Hosting.Ats/InteractionInputLoadContext.inputs(context: Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputLoadContext) -> Aspire.Hosting/Aspire.Hosting.InteractionInputCollection +Aspire.Hosting.Ats/setChoiceOptions(context: Aspire.Hosting/Aspire.Hosting.Ats.InteractionLoadingInput, choices: Aspire.Hosting/Aspire.Hosting.Ats.InteractionChoiceOption[]) -> void +Aspire.Hosting.Ats/setValue(context: Aspire.Hosting/Aspire.Hosting.Ats.InteractionLoadingInput, value: string) -> void +Aspire.Hosting.Ats/withChoiceOptions(context: Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder, choices: Aspire.Hosting/Aspire.Hosting.Ats.InteractionChoiceOption[]) -> Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder +Aspire.Hosting.Ats/withDynamicLoading(context: Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder, callback: callback, options?: Aspire.Hosting/Aspire.Hosting.Ats.DynamicLoadingOptions) -> Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder +Aspire.Hosting.Ats/withValue(context: Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder, value: string) -> Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder Aspire.Hosting.Eventing/IDistributedApplicationEventing.unsubscribe(context: Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEventing, subscription: Aspire.Hosting/Aspire.Hosting.Eventing.DistributedApplicationEventSubscription) -> void Aspire.Hosting.Pipelines/addTag(context: Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineStep, tag: string) -> void Aspire.Hosting.Pipelines/dependsOn(context: Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineStep, stepName: string) -> void @@ -479,11 +553,16 @@ Aspire.Hosting/completeStepMarkdown(markdownString: string, completionState?: st Aspire.Hosting/completeTask(completionMessage?: string, completionState?: string, cancellationToken?: cancellationToken) -> void Aspire.Hosting/completeTaskMarkdown(markdownString: string, completionState?: string, cancellationToken?: cancellationToken) -> void Aspire.Hosting/configure(callback: callback) -> void +Aspire.Hosting/createBooleanInput(name: string, options?: Aspire.Hosting/Aspire.Hosting.Ats.CreateInteractionInputOptions) -> Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder Aspire.Hosting/createBuilder() -> Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder +Aspire.Hosting/createChoiceInput(name: string, choices?: Aspire.Hosting/Aspire.Hosting.Ats.InteractionChoiceOption[], options?: Aspire.Hosting/Aspire.Hosting.Ats.CreateInteractionInputOptions) -> Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder Aspire.Hosting/createExecutionConfiguration() -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.IExecutionConfigurationBuilder Aspire.Hosting/createLogger(categoryName: string) -> Microsoft.Extensions.Logging.Abstractions/Microsoft.Extensions.Logging.ILogger Aspire.Hosting/createMarkdownTask(markdownString: string, cancellationToken?: cancellationToken) -> Aspire.Hosting/Aspire.Hosting.Pipelines.IReportingTask +Aspire.Hosting/createNumberInput(name: string, options?: Aspire.Hosting/Aspire.Hosting.Ats.CreateInteractionInputOptions) -> Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder +Aspire.Hosting/createSecretInput(name: string, options?: Aspire.Hosting/Aspire.Hosting.Ats.CreateInteractionInputOptions) -> Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder Aspire.Hosting/createTask(statusText: string, cancellationToken?: cancellationToken) -> Aspire.Hosting/Aspire.Hosting.Pipelines.IReportingTask +Aspire.Hosting/createTextInput(name: string, options?: Aspire.Hosting/Aspire.Hosting.Ats.CreateInteractionInputOptions) -> Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder Aspire.Hosting/Dict.clear() -> void Aspire.Hosting/Dict.count() -> number Aspire.Hosting/Dict.get(key: any) -> any @@ -500,6 +579,7 @@ Aspire.Hosting/DistributedApplicationExecutionContext.isRunMode(context: Aspire. Aspire.Hosting/DistributedApplicationExecutionContext.operation(context: Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContext) -> enum:Aspire.Hosting.DistributedApplicationOperation Aspire.Hosting/DistributedApplicationExecutionContext.publisherName(context: Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContext) -> string Aspire.Hosting/DistributedApplicationExecutionContext.serviceProvider(context: Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContext) -> System.ComponentModel/System.IServiceProvider +Aspire.Hosting/DistributedApplicationExecutionContext.services(context: Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContext) -> System.ComponentModel/System.IServiceProvider Aspire.Hosting/DistributedApplicationExecutionContext.setPublisherName(context: Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContext, value: string) -> Aspire.Hosting/Aspire.Hosting.DistributedApplicationExecutionContext Aspire.Hosting/dockerfileBuilderAddContainerFilesStages(resource: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, logger?: Microsoft.Extensions.Logging.Abstractions/Microsoft.Extensions.Logging.ILogger) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.Docker.DockerfileBuilder Aspire.Hosting/dockerfileBuilderArg(name: string, defaultValue?: string) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.Docker.DockerfileBuilder @@ -534,6 +614,7 @@ Aspire.Hosting/getEndpoint(name: string) -> Aspire.Hosting/Aspire.Hosting.Applic Aspire.Hosting/getEventing() -> Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEventing Aspire.Hosting/getFileNameWithContent(filenameTemplate: string, sourceFilename: string) -> string Aspire.Hosting/getHttpsCertificateData() -> Aspire.Hosting/Aspire.Hosting.Ats.HttpsCertificateExecutionConfigurationExportData +Aspire.Hosting/getInteractionService() -> Aspire.Hosting/Aspire.Hosting.IInteractionService Aspire.Hosting/getLoggerFactory() -> Microsoft.Extensions.Logging.Abstractions/Microsoft.Extensions.Logging.ILoggerFactory Aspire.Hosting/getOrSetSecret(resourceBuilder: Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResource, name: string, value: string) -> void Aspire.Hosting/getResourceCommandService() -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.ResourceCommandService @@ -553,6 +634,7 @@ Aspire.Hosting/InputsDialogValidationContext.addValidationError(context: Aspire. Aspire.Hosting/InputsDialogValidationContext.cancellationToken(context: Aspire.Hosting/Aspire.Hosting.InputsDialogValidationContext) -> cancellationToken Aspire.Hosting/InputsDialogValidationContext.inputs(context: Aspire.Hosting/Aspire.Hosting.InputsDialogValidationContext) -> Aspire.Hosting/Aspire.Hosting.InteractionInputCollection Aspire.Hosting/InteractionInputCollection.toArray(context: Aspire.Hosting/Aspire.Hosting.InteractionInputCollection) -> Aspire.Hosting/Aspire.Hosting.InteractionInput[] +Aspire.Hosting/isAvailable() -> boolean Aspire.Hosting/isDevelopment() -> boolean Aspire.Hosting/isEnvironment(environmentName: string) -> boolean Aspire.Hosting/isProduction() -> boolean @@ -589,6 +671,11 @@ Aspire.Hosting/ProjectResourceOptions.launchProfileName(context: Aspire.Hosting/ Aspire.Hosting/ProjectResourceOptions.setExcludeKestrelEndpoints(context: Aspire.Hosting/Aspire.Hosting.ProjectResourceOptions, value: boolean) -> Aspire.Hosting/Aspire.Hosting.ProjectResourceOptions Aspire.Hosting/ProjectResourceOptions.setExcludeLaunchProfile(context: Aspire.Hosting/Aspire.Hosting.ProjectResourceOptions, value: boolean) -> Aspire.Hosting/Aspire.Hosting.ProjectResourceOptions Aspire.Hosting/ProjectResourceOptions.setLaunchProfileName(context: Aspire.Hosting/Aspire.Hosting.ProjectResourceOptions, value: string) -> Aspire.Hosting/Aspire.Hosting.ProjectResourceOptions +Aspire.Hosting/promptConfirmation(title: string, message: string, options?: Aspire.Hosting/Aspire.Hosting.Ats.InteractionMessageBoxOptions, cancellationToken?: cancellationToken) -> Aspire.Hosting/Aspire.Hosting.Ats.BoolInteractionResult +Aspire.Hosting/promptInput(title: string, message: string, input: Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder, options?: Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputsDialogOptions, cancellationToken?: cancellationToken) -> Aspire.Hosting/Aspire.Hosting.Ats.InputInteractionResult +Aspire.Hosting/promptInputs(title: string, message: string, inputs: Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder[], options?: Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputsDialogOptions, cancellationToken?: cancellationToken) -> Aspire.Hosting/Aspire.Hosting.Ats.InputsInteractionResult +Aspire.Hosting/promptMessageBox(title: string, message: string, options?: Aspire.Hosting/Aspire.Hosting.Ats.InteractionMessageBoxOptions, cancellationToken?: cancellationToken) -> Aspire.Hosting/Aspire.Hosting.Ats.BoolInteractionResult +Aspire.Hosting/promptNotification(title: string, message: string, options?: Aspire.Hosting/Aspire.Hosting.Ats.InteractionNotificationOptions, cancellationToken?: cancellationToken) -> Aspire.Hosting/Aspire.Hosting.Ats.BoolInteractionResult Aspire.Hosting/publishAsConnectionString() -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource Aspire.Hosting/publishAsContainer() -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.ContainerResource Aspire.Hosting/publishAsDockerFile(configure: callback) -> Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecutableResource diff --git a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/AtsGeneratedAspire.verified.go b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/AtsGeneratedAspire.verified.go index 31853835349..54ea3548b95 100644 --- a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/AtsGeneratedAspire.verified.go +++ b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/AtsGeneratedAspire.verified.go @@ -10,6 +10,7 @@ import ( "context" "fmt" "os" + "strings" "time" ) @@ -17,6 +18,7 @@ import ( var _ = context.Background var _ = fmt.Errorf var _ = os.Getenv +var _ = strings.EqualFold var _ = time.Second // ============================================================================ diff --git a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.go b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.go index 55475ebddfe..83bd3591940 100644 --- a/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.go +++ b/tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.go @@ -10,6 +10,7 @@ import ( "context" "fmt" "os" + "strings" "time" ) @@ -17,6 +18,7 @@ import ( var _ = context.Background var _ = fmt.Errorf var _ = os.Getenv +var _ = strings.EqualFold var _ = time.Second // ============================================================================ @@ -166,6 +168,18 @@ const ( HealthStatusHealthy HealthStatus = "Healthy" ) +// MessageIntent represents MessageIntent. +type MessageIntent string + +const ( + MessageIntentNone MessageIntent = "None" + MessageIntentSuccess MessageIntent = "Success" + MessageIntentWarning MessageIntent = "Warning" + MessageIntentError MessageIntent = "Error" + MessageIntentInformation MessageIntent = "Information" + MessageIntentConfirmation MessageIntent = "Confirmation" +) + // ResourceCommandVisibility represents ResourceCommandVisibility. type ResourceCommandVisibility string @@ -243,7 +257,6 @@ type InteractionInput struct { InputType InputType `json:"InputType,omitempty"` Required *bool `json:"Required,omitempty"` Options []any `json:"Options,omitempty"` - DynamicLoading any `json:"DynamicLoading,omitempty"` Value string `json:"Value,omitempty"` Placeholder *string `json:"Placeholder,omitempty"` AllowCustomChoice *bool `json:"AllowCustomChoice,omitempty"` @@ -261,7 +274,6 @@ func (d *InteractionInput) ToMap() map[string]any { m["InputType"] = serializeValue(d.InputType) if d.Required != nil { m["Required"] = serializeValue(d.Required) } if d.Options != nil { m["Options"] = serializeValue(d.Options) } - if d.DynamicLoading != nil { m["DynamicLoading"] = serializeValue(d.DynamicLoading) } m["Value"] = serializeValue(d.Value) if d.Placeholder != nil { m["Placeholder"] = serializeValue(d.Placeholder) } if d.AllowCustomChoice != nil { m["AllowCustomChoice"] = serializeValue(d.AllowCustomChoice) } @@ -398,6 +410,166 @@ func (d *HealthCheckResult) ToMap() map[string]any { return m } +// InteractionChoiceOption represents InteractionChoiceOption. +type InteractionChoiceOption struct { + Value string `json:"Value,omitempty"` + Label string `json:"Label,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *InteractionChoiceOption) ToMap() map[string]any { + m := map[string]any{} + m["Value"] = serializeValue(d.Value) + m["Label"] = serializeValue(d.Label) + return m +} + +// CreateInteractionInputOptions represents CreateInteractionInputOptions. +type CreateInteractionInputOptions struct { + Label *string `json:"Label,omitempty"` + Description *string `json:"Description,omitempty"` + EnableDescriptionMarkdown *bool `json:"EnableDescriptionMarkdown,omitempty"` + Required *bool `json:"Required,omitempty"` + Placeholder *string `json:"Placeholder,omitempty"` + Value *string `json:"Value,omitempty"` + AllowCustomChoice *bool `json:"AllowCustomChoice,omitempty"` + Disabled *bool `json:"Disabled,omitempty"` + MaxLength *float64 `json:"MaxLength,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *CreateInteractionInputOptions) ToMap() map[string]any { + m := map[string]any{} + if d.Label != nil { m["Label"] = serializeValue(d.Label) } + if d.Description != nil { m["Description"] = serializeValue(d.Description) } + if d.EnableDescriptionMarkdown != nil { m["EnableDescriptionMarkdown"] = serializeValue(d.EnableDescriptionMarkdown) } + if d.Required != nil { m["Required"] = serializeValue(d.Required) } + if d.Placeholder != nil { m["Placeholder"] = serializeValue(d.Placeholder) } + if d.Value != nil { m["Value"] = serializeValue(d.Value) } + if d.AllowCustomChoice != nil { m["AllowCustomChoice"] = serializeValue(d.AllowCustomChoice) } + if d.Disabled != nil { m["Disabled"] = serializeValue(d.Disabled) } + if d.MaxLength != nil { m["MaxLength"] = serializeValue(d.MaxLength) } + return m +} + +// DynamicLoadingOptions represents DynamicLoadingOptions. +type DynamicLoadingOptions struct { + AlwaysLoadOnStart *bool `json:"AlwaysLoadOnStart,omitempty"` + DependsOnInputs []string `json:"DependsOnInputs,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *DynamicLoadingOptions) ToMap() map[string]any { + m := map[string]any{} + if d.AlwaysLoadOnStart != nil { m["AlwaysLoadOnStart"] = serializeValue(d.AlwaysLoadOnStart) } + if d.DependsOnInputs != nil { m["DependsOnInputs"] = serializeValue(d.DependsOnInputs) } + return m +} + +// InteractionMessageBoxOptions represents InteractionMessageBoxOptions. +type InteractionMessageBoxOptions struct { + PrimaryButtonText *string `json:"PrimaryButtonText,omitempty"` + SecondaryButtonText *string `json:"SecondaryButtonText,omitempty"` + ShowSecondaryButton *bool `json:"ShowSecondaryButton,omitempty"` + ShowDismiss *bool `json:"ShowDismiss,omitempty"` + EnableMessageMarkdown *bool `json:"EnableMessageMarkdown,omitempty"` + Intent *MessageIntent `json:"Intent,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *InteractionMessageBoxOptions) ToMap() map[string]any { + m := map[string]any{} + if d.PrimaryButtonText != nil { m["PrimaryButtonText"] = serializeValue(d.PrimaryButtonText) } + if d.SecondaryButtonText != nil { m["SecondaryButtonText"] = serializeValue(d.SecondaryButtonText) } + if d.ShowSecondaryButton != nil { m["ShowSecondaryButton"] = serializeValue(d.ShowSecondaryButton) } + if d.ShowDismiss != nil { m["ShowDismiss"] = serializeValue(d.ShowDismiss) } + if d.EnableMessageMarkdown != nil { m["EnableMessageMarkdown"] = serializeValue(d.EnableMessageMarkdown) } + if d.Intent != nil { m["Intent"] = serializeValue(d.Intent) } + return m +} + +// InteractionNotificationOptions represents InteractionNotificationOptions. +type InteractionNotificationOptions struct { + PrimaryButtonText *string `json:"PrimaryButtonText,omitempty"` + SecondaryButtonText *string `json:"SecondaryButtonText,omitempty"` + ShowSecondaryButton *bool `json:"ShowSecondaryButton,omitempty"` + ShowDismiss *bool `json:"ShowDismiss,omitempty"` + EnableMessageMarkdown *bool `json:"EnableMessageMarkdown,omitempty"` + Intent *MessageIntent `json:"Intent,omitempty"` + LinkText *string `json:"LinkText,omitempty"` + LinkUrl *string `json:"LinkUrl,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *InteractionNotificationOptions) ToMap() map[string]any { + m := map[string]any{} + if d.PrimaryButtonText != nil { m["PrimaryButtonText"] = serializeValue(d.PrimaryButtonText) } + if d.SecondaryButtonText != nil { m["SecondaryButtonText"] = serializeValue(d.SecondaryButtonText) } + if d.ShowSecondaryButton != nil { m["ShowSecondaryButton"] = serializeValue(d.ShowSecondaryButton) } + if d.ShowDismiss != nil { m["ShowDismiss"] = serializeValue(d.ShowDismiss) } + if d.EnableMessageMarkdown != nil { m["EnableMessageMarkdown"] = serializeValue(d.EnableMessageMarkdown) } + if d.Intent != nil { m["Intent"] = serializeValue(d.Intent) } + if d.LinkText != nil { m["LinkText"] = serializeValue(d.LinkText) } + if d.LinkUrl != nil { m["LinkUrl"] = serializeValue(d.LinkUrl) } + return m +} + +// InteractionInputsDialogOptions represents InteractionInputsDialogOptions. +type InteractionInputsDialogOptions struct { + PrimaryButtonText *string `json:"PrimaryButtonText,omitempty"` + SecondaryButtonText *string `json:"SecondaryButtonText,omitempty"` + ShowSecondaryButton *bool `json:"ShowSecondaryButton,omitempty"` + ShowDismiss *bool `json:"ShowDismiss,omitempty"` + EnableMessageMarkdown *bool `json:"EnableMessageMarkdown,omitempty"` + ValidationCallback func(arg InputsDialogValidationContext) `json:"ValidationCallback,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *InteractionInputsDialogOptions) ToMap() map[string]any { + m := map[string]any{} + if d.PrimaryButtonText != nil { m["PrimaryButtonText"] = serializeValue(d.PrimaryButtonText) } + if d.SecondaryButtonText != nil { m["SecondaryButtonText"] = serializeValue(d.SecondaryButtonText) } + if d.ShowSecondaryButton != nil { m["ShowSecondaryButton"] = serializeValue(d.ShowSecondaryButton) } + if d.ShowDismiss != nil { m["ShowDismiss"] = serializeValue(d.ShowDismiss) } + if d.EnableMessageMarkdown != nil { m["EnableMessageMarkdown"] = serializeValue(d.EnableMessageMarkdown) } + if d.ValidationCallback != nil { + cb := d.ValidationCallback + m["ValidationCallback"] = func(args ...any) any { + cb(callbackArg[InputsDialogValidationContext](args, 0)) + return nil + } + } + return m +} + +// BoolInteractionResult represents BoolInteractionResult. +type BoolInteractionResult struct { + Canceled bool `json:"Canceled,omitempty"` + Value *bool `json:"Value,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *BoolInteractionResult) ToMap() map[string]any { + m := map[string]any{} + m["Canceled"] = serializeValue(d.Canceled) + if d.Value != nil { m["Value"] = serializeValue(d.Value) } + return m +} + +// InputInteractionResult represents InputInteractionResult. +type InputInteractionResult struct { + Canceled bool `json:"Canceled,omitempty"` + Input *InteractionInput `json:"Input,omitempty"` +} + +// ToMap converts the DTO to a map for JSON serialization. +func (d *InputInteractionResult) ToMap() map[string]any { + m := map[string]any{} + m["Canceled"] = serializeValue(d.Canceled) + if d.Input != nil { m["Input"] = serializeValue(d.Input) } + return m +} + // ResourceEventDto represents ResourceEventDto. type ResourceEventDto struct { ResourceName string `json:"ResourceName,omitempty"` @@ -491,13 +663,13 @@ type CommandOptions struct { Description string `json:"Description,omitempty"` Parameter any `json:"Parameter,omitempty"` Arguments []*InteractionInput `json:"Arguments,omitempty"` - ValidateArguments func(...any) any `json:"ValidateArguments,omitempty"` + ValidateArguments func(arg InputsDialogValidationContext) `json:"ValidateArguments,omitempty"` Visibility ResourceCommandVisibility `json:"Visibility,omitempty"` ConfirmationMessage string `json:"ConfirmationMessage,omitempty"` IconName string `json:"IconName,omitempty"` IconVariant *IconVariant `json:"IconVariant,omitempty"` IsHighlighted bool `json:"IsHighlighted,omitempty"` - UpdateState func(...any) any `json:"UpdateState,omitempty"` + UpdateState func(arg UpdateCommandStateContext) ResourceCommandState `json:"UpdateState,omitempty"` } // ToMap converts the DTO to a map for JSON serialization. @@ -506,13 +678,24 @@ func (d *CommandOptions) ToMap() map[string]any { m["Description"] = serializeValue(d.Description) if d.Parameter != nil { m["Parameter"] = serializeValue(d.Parameter) } if d.Arguments != nil { m["Arguments"] = serializeValue(d.Arguments) } - if d.ValidateArguments != nil { m["ValidateArguments"] = serializeValue(d.ValidateArguments) } + if d.ValidateArguments != nil { + cb := d.ValidateArguments + m["ValidateArguments"] = func(args ...any) any { + cb(callbackArg[InputsDialogValidationContext](args, 0)) + return nil + } + } m["Visibility"] = serializeValue(d.Visibility) m["ConfirmationMessage"] = serializeValue(d.ConfirmationMessage) m["IconName"] = serializeValue(d.IconName) if d.IconVariant != nil { m["IconVariant"] = serializeValue(d.IconVariant) } m["IsHighlighted"] = serializeValue(d.IsHighlighted) - if d.UpdateState != nil { m["UpdateState"] = serializeValue(d.UpdateState) } + if d.UpdateState != nil { + cb := d.UpdateState + m["UpdateState"] = func(args ...any) any { + return cb(callbackArg[UpdateCommandStateContext](args, 0)) + } + } return m } @@ -527,7 +710,7 @@ type HttpCommandExportOptions struct { CommandName string `json:"CommandName,omitempty"` EndpointName string `json:"EndpointName,omitempty"` MethodName string `json:"MethodName,omitempty"` - PrepareRequest func(...any) any `json:"PrepareRequest,omitempty"` + PrepareRequest func(arg HttpCommandPrepareRequestContext) *HttpCommandRequestExportData `json:"PrepareRequest,omitempty"` ResultMode HttpCommandResultMode `json:"ResultMode,omitempty"` } @@ -543,7 +726,12 @@ func (d *HttpCommandExportOptions) ToMap() map[string]any { m["CommandName"] = serializeValue(d.CommandName) m["EndpointName"] = serializeValue(d.EndpointName) m["MethodName"] = serializeValue(d.MethodName) - if d.PrepareRequest != nil { m["PrepareRequest"] = serializeValue(d.PrepareRequest) } + if d.PrepareRequest != nil { + cb := d.PrepareRequest + m["PrepareRequest"] = func(args ...any) any { + return cb(callbackArg[HttpCommandPrepareRequestContext](args, 0)) + } + } m["ResultMode"] = serializeValue(d.ResultMode) return m } @@ -619,7 +807,7 @@ type ProcessCommandExportOptions struct { InheritEnvironmentVariables *bool `json:"InheritEnvironmentVariables,omitempty"` StandardInputContent string `json:"StandardInputContent,omitempty"` KillEntireProcessTree *bool `json:"KillEntireProcessTree,omitempty"` - CreateProcessSpec func(...any) any `json:"CreateProcessSpec,omitempty"` + CreateProcessSpec func(arg ExecuteCommandContext) *ProcessCommandSpecExportData `json:"CreateProcessSpec,omitempty"` CommandOptions *CommandOptions `json:"CommandOptions,omitempty"` MaxOutputLineCount *float64 `json:"MaxOutputLineCount,omitempty"` DisplayImmediately *bool `json:"DisplayImmediately,omitempty"` @@ -636,7 +824,12 @@ func (d *ProcessCommandExportOptions) ToMap() map[string]any { if d.InheritEnvironmentVariables != nil { m["InheritEnvironmentVariables"] = serializeValue(d.InheritEnvironmentVariables) } m["StandardInputContent"] = serializeValue(d.StandardInputContent) if d.KillEntireProcessTree != nil { m["KillEntireProcessTree"] = serializeValue(d.KillEntireProcessTree) } - if d.CreateProcessSpec != nil { m["CreateProcessSpec"] = serializeValue(d.CreateProcessSpec) } + if d.CreateProcessSpec != nil { + cb := d.CreateProcessSpec + m["CreateProcessSpec"] = func(args ...any) any { + return cb(callbackArg[ExecuteCommandContext](args, 0)) + } + } if d.CommandOptions != nil { m["CommandOptions"] = serializeValue(d.CommandOptions) } if d.MaxOutputLineCount != nil { m["MaxOutputLineCount"] = serializeValue(d.MaxOutputLineCount) } if d.DisplayImmediately != nil { m["DisplayImmediately"] = serializeValue(d.DisplayImmediately) } @@ -14317,6 +14510,7 @@ type ExecuteCommandContext interface { CancellationToken() (*CancellationToken, error) Logger() Logger ResourceName() (string, error) + Services() ServiceProvider Err() error } @@ -14398,6 +14592,25 @@ func (s *executeCommandContext) ResourceName() (string, error) { return decodeAs[string](result) } +// Services the service provider. +func (s *executeCommandContext) Services() ServiceProvider { + if s.err != nil { return &serviceProvider{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting.ApplicationModel/ExecuteCommandContext.services", reqArgs) + if err != nil { + return &serviceProvider{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting.ApplicationModel/ExecuteCommandContext.services returned unexpected type %T", result) + return &serviceProvider{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &serviceProvider{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + // ExecutionConfigurationBuilder is the public interface for handle type ExecutionConfigurationBuilder. type ExecutionConfigurationBuilder interface { handleReference @@ -15969,159 +16182,766 @@ func (s *inputsDialogValidationContext) Inputs() InteractionInputCollection { return &interactionInputCollection{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} } -// InteractionInputCollection is the public interface for handle type InteractionInputCollection. -type InteractionInputCollection interface { +// InputsInteractionResult is the public interface for handle type InputsInteractionResult. +type InputsInteractionResult interface { handleReference - ToArray() ([]*InteractionInput, error) + Canceled() (bool, error) + Inputs() InteractionInputCollection Err() error } -// interactionInputCollection is the unexported impl of InteractionInputCollection. -type interactionInputCollection struct { +// inputsInteractionResult is the unexported impl of InputsInteractionResult. +type inputsInteractionResult struct { *resourceBuilderBase } -// newInteractionInputCollectionFromHandle wraps an existing handle as InteractionInputCollection. -func newInteractionInputCollectionFromHandle(h *handle, c *client) InteractionInputCollection { - return &interactionInputCollection{resourceBuilderBase: newResourceBuilderBase(h, c)} +// newInputsInteractionResultFromHandle wraps an existing handle as InputsInteractionResult. +func newInputsInteractionResultFromHandle(h *handle, c *client) InputsInteractionResult { + return &inputsInteractionResult{resourceBuilderBase: newResourceBuilderBase(h, c)} } -// ToArray gets all inputs in declaration order. -func (s *interactionInputCollection) ToArray() ([]*InteractionInput, error) { - if s.err != nil { var zero []*InteractionInput; return zero, s.err } +// Canceled gets a value indicating whether the interaction was canceled by the user. +func (s *inputsInteractionResult) Canceled() (bool, error) { + if s.err != nil { var zero bool; return zero, s.err } ctx := context.Background() reqArgs := map[string]any{ "context": s.handle.ToJSON(), } - result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/InteractionInputCollection.toArray", reqArgs) + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting.Ats/InputsInteractionResult.canceled", reqArgs) if err != nil { - var zero []*InteractionInput + var zero bool return zero, err } - return decodeAs[[]*InteractionInput](result) + return decodeAs[bool](result) } -// LogFacade is the public interface for handle type LogFacade. -type LogFacade interface { +// Inputs gets the inputs returned from the interaction. Empty when `Canceled` is `true`. +func (s *inputsInteractionResult) Inputs() InteractionInputCollection { + if s.err != nil { return &interactionInputCollection{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting.Ats/InputsInteractionResult.inputs", reqArgs) + if err != nil { + return &interactionInputCollection{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting.Ats/InputsInteractionResult.inputs returned unexpected type %T", result) + return &interactionInputCollection{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &interactionInputCollection{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + +// InteractionInputBuilder is the public interface for handle type InteractionInputBuilder. +type InteractionInputBuilder interface { handleReference - Debug(message string) error - Error(message string) error - Info(message string) error - Warning(message string) error + WithChoiceOptions(choices []*InteractionChoiceOption) InteractionInputBuilder + WithDynamicLoading(callback func(arg InteractionInputLoadContext), options ...*WithDynamicLoadingOptions) InteractionInputBuilder + WithValue(value string) InteractionInputBuilder Err() error } -// logFacade is the unexported impl of LogFacade. -type logFacade struct { +// interactionInputBuilder is the unexported impl of InteractionInputBuilder. +type interactionInputBuilder struct { *resourceBuilderBase } -// newLogFacadeFromHandle wraps an existing handle as LogFacade. -func newLogFacadeFromHandle(h *handle, c *client) LogFacade { - return &logFacade{resourceBuilderBase: newResourceBuilderBase(h, c)} +// newInteractionInputBuilderFromHandle wraps an existing handle as InteractionInputBuilder. +func newInteractionInputBuilderFromHandle(h *handle, c *client) InteractionInputBuilder { + return &interactionInputBuilder{resourceBuilderBase: newResourceBuilderBase(h, c)} } -// Debug writes a debug log message. -func (s *logFacade) Debug(message string) error { - if s.err != nil { return s.err } +// WithChoiceOptions sets the choice options for the input. +func (s *interactionInputBuilder) WithChoiceOptions(choices []*InteractionChoiceOption) InteractionInputBuilder { + if s.err != nil { return s } ctx := context.Background() reqArgs := map[string]any{ "context": s.handle.ToJSON(), } - reqArgs["message"] = serializeValue(message) - _, err := s.client.invokeCapability(ctx, "Aspire.Hosting.ApplicationModel/debug", reqArgs) - return err + if choices != nil { reqArgs["choices"] = serializeValue(choices) } + if _, err := s.client.invokeCapability(ctx, "Aspire.Hosting.Ats/withChoiceOptions", reqArgs); err != nil { s.setErr(err) } + return s } -// Error writes an error log message. -func (s *logFacade) Error(message string) error { - if s.err != nil { return s.err } +// WithDynamicLoading attaches a callback that dynamically loads or updates the input after the prompt starts. +func (s *interactionInputBuilder) WithDynamicLoading(callback func(arg InteractionInputLoadContext), options ...*WithDynamicLoadingOptions) InteractionInputBuilder { + if s.err != nil { return s } ctx := context.Background() reqArgs := map[string]any{ "context": s.handle.ToJSON(), } - reqArgs["message"] = serializeValue(message) - _, err := s.client.invokeCapability(ctx, "Aspire.Hosting.ApplicationModel/error", reqArgs) - return err -} - -// Info writes an informational log message. -func (s *logFacade) Info(message string) error { - if s.err != nil { return s.err } - ctx := context.Background() - reqArgs := map[string]any{ - "context": s.handle.ToJSON(), + if callback != nil { + cb := callback + shim := func(args ...any) any { + cb(callbackArg[InteractionInputLoadContext](args, 0)) + return nil + } + reqArgs["callback"] = s.client.registerCallback(shim) } - reqArgs["message"] = serializeValue(message) - _, err := s.client.invokeCapability(ctx, "Aspire.Hosting.ApplicationModel/info", reqArgs) - return err + if len(options) > 0 { + merged := &WithDynamicLoadingOptions{} + for _, opt := range options { + if opt != nil { merged = deepUpdate(merged, opt) } + } + for k, v := range merged.ToMap() { reqArgs[k] = v } + } + if _, err := s.client.invokeCapability(ctx, "Aspire.Hosting.Ats/withDynamicLoading", reqArgs); err != nil { s.setErr(err) } + return s } -// Warning writes a warning log message. -func (s *logFacade) Warning(message string) error { - if s.err != nil { return s.err } +// WithValue sets the value of the input. +func (s *interactionInputBuilder) WithValue(value string) InteractionInputBuilder { + if s.err != nil { return s } ctx := context.Background() reqArgs := map[string]any{ "context": s.handle.ToJSON(), } - reqArgs["message"] = serializeValue(message) - _, err := s.client.invokeCapability(ctx, "Aspire.Hosting.ApplicationModel/warning", reqArgs) - return err + reqArgs["value"] = serializeValue(value) + if _, err := s.client.invokeCapability(ctx, "Aspire.Hosting.Ats/withValue", reqArgs); err != nil { s.setErr(err) } + return s } -// Logger is the public interface for handle type Logger. -type Logger interface { +// InteractionInputCollection is the public interface for handle type InteractionInputCollection. +type InteractionInputCollection interface { handleReference - Log(level string, message string) error - LogDebug(message string) error - LogError(message string) error - LogInformation(message string) error - LogWarning(message string) error + ToArray() ([]*InteractionInput, error) + Get(name string) (*InteractionInput, error) + Required(name string) (*InteractionInput, error) + Value(name string) (string, error) + RequiredValue(name string) (string, error) Err() error } -// logger is the unexported impl of Logger. -type logger struct { +// interactionInputCollection is the unexported impl of InteractionInputCollection. +type interactionInputCollection struct { *resourceBuilderBase } -// newLoggerFromHandle wraps an existing handle as Logger. -func newLoggerFromHandle(h *handle, c *client) Logger { - return &logger{resourceBuilderBase: newResourceBuilderBase(h, c)} +// newInteractionInputCollectionFromHandle wraps an existing handle as InteractionInputCollection. +func newInteractionInputCollectionFromHandle(h *handle, c *client) InteractionInputCollection { + return &interactionInputCollection{resourceBuilderBase: newResourceBuilderBase(h, c)} } -// Log logs a message with a specified log level. -func (s *logger) Log(level string, message string) error { - if s.err != nil { return s.err } +// ToArray gets all inputs in declaration order. +func (s *interactionInputCollection) ToArray() ([]*InteractionInput, error) { + if s.err != nil { var zero []*InteractionInput; return zero, s.err } ctx := context.Background() reqArgs := map[string]any{ - "logger": s.handle.ToJSON(), + "context": s.handle.ToJSON(), } - reqArgs["level"] = serializeValue(level) - reqArgs["message"] = serializeValue(message) - _, err := s.client.invokeCapability(ctx, "Aspire.Hosting/log", reqArgs) - return err + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/InteractionInputCollection.toArray", reqArgs) + if err != nil { + var zero []*InteractionInput + return zero, err + } + return decodeAs[[]*InteractionInput](result) } -// LogDebug logs a debug message. -func (s *logger) LogDebug(message string) error { - if s.err != nil { return s.err } - ctx := context.Background() - reqArgs := map[string]any{ - "logger": s.handle.ToJSON(), +// Get returns the input with the specified name, or nil if no input matches. +func (s *interactionInputCollection) Get(name string) (*InteractionInput, error) { + if s.err != nil { return nil, s.err } + inputs, err := s.ToArray() + if err != nil { return nil, err } + for _, input := range inputs { + if strings.EqualFold(input.Name, name) { return input, nil } } - reqArgs["message"] = serializeValue(message) - _, err := s.client.invokeCapability(ctx, "Aspire.Hosting/logDebug", reqArgs) - return err + return nil, nil } -// LogError logs an error message. -func (s *logger) LogError(message string) error { - if s.err != nil { return s.err } - ctx := context.Background() - reqArgs := map[string]any{ - "logger": s.handle.ToJSON(), - } +// Required returns the input with the specified name, or an error if no input matches. +func (s *interactionInputCollection) Required(name string) (*InteractionInput, error) { + input, err := s.Get(name) + if err != nil { return nil, err } + if input == nil { return nil, fmt.Errorf("no input with name '%s' was found", name) } + return input, nil +} + +// Value returns the value of the input with the specified name, or an empty string if no input matches or it has no value. +func (s *interactionInputCollection) Value(name string) (string, error) { + input, err := s.Get(name) + if err != nil { return "", err } + if input == nil { return "", nil } + return input.Value, nil +} + +// RequiredValue returns the value of the input with the specified name, or an error if no input matches. +func (s *interactionInputCollection) RequiredValue(name string) (string, error) { + input, err := s.Required(name) + if err != nil { return "", err } + return input.Value, nil +} + +// InteractionInputLoadContext is the public interface for handle type InteractionInputLoadContext. +type InteractionInputLoadContext interface { + handleReference + Input() InteractionLoadingInput + Inputs() InteractionInputCollection + Err() error +} + +// interactionInputLoadContext is the unexported impl of InteractionInputLoadContext. +type interactionInputLoadContext struct { + *resourceBuilderBase +} + +// newInteractionInputLoadContextFromHandle wraps an existing handle as InteractionInputLoadContext. +func newInteractionInputLoadContextFromHandle(h *handle, c *client) InteractionInputLoadContext { + return &interactionInputLoadContext{resourceBuilderBase: newResourceBuilderBase(h, c)} +} + +// Input gets a handle to the input that is loading. Mutate the input through this handle. +func (s *interactionInputLoadContext) Input() InteractionLoadingInput { + if s.err != nil { return &interactionLoadingInput{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting.Ats/input", reqArgs) + if err != nil { + return &interactionLoadingInput{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting.Ats/input returned unexpected type %T", result) + return &interactionLoadingInput{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &interactionLoadingInput{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + +// Inputs gets all inputs in the prompt, including the one currently loading. +func (s *interactionInputLoadContext) Inputs() InteractionInputCollection { + if s.err != nil { return &interactionInputCollection{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting.Ats/InteractionInputLoadContext.inputs", reqArgs) + if err != nil { + return &interactionInputCollection{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting.Ats/InteractionInputLoadContext.inputs returned unexpected type %T", result) + return &interactionInputCollection{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &interactionInputCollection{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + +// InteractionLoadingInput is the public interface for handle type InteractionLoadingInput. +type InteractionLoadingInput interface { + handleReference + GetName() (string, error) + SetChoiceOptions(choices []*InteractionChoiceOption) error + SetValue(value string) error + Err() error +} + +// interactionLoadingInput is the unexported impl of InteractionLoadingInput. +type interactionLoadingInput struct { + *resourceBuilderBase +} + +// newInteractionLoadingInputFromHandle wraps an existing handle as InteractionLoadingInput. +func newInteractionLoadingInputFromHandle(h *handle, c *client) InteractionLoadingInput { + return &interactionLoadingInput{resourceBuilderBase: newResourceBuilderBase(h, c)} +} + +// GetName gets the name of the input. +func (s *interactionLoadingInput) GetName() (string, error) { + if s.err != nil { var zero string; return zero, s.err } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting.Ats/getName", reqArgs) + if err != nil { + var zero string + return zero, err + } + return decodeAs[string](result) +} + +// SetChoiceOptions sets the choice options for the input. +func (s *interactionLoadingInput) SetChoiceOptions(choices []*InteractionChoiceOption) error { + if s.err != nil { return s.err } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + if choices != nil { reqArgs["choices"] = serializeValue(choices) } + _, err := s.client.invokeCapability(ctx, "Aspire.Hosting.Ats/setChoiceOptions", reqArgs) + return err +} + +// SetValue sets the value of the input. +func (s *interactionLoadingInput) SetValue(value string) error { + if s.err != nil { return s.err } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + reqArgs["value"] = serializeValue(value) + _, err := s.client.invokeCapability(ctx, "Aspire.Hosting.Ats/setValue", reqArgs) + return err +} + +// InteractionService is the public interface for handle type InteractionService. +type InteractionService interface { + handleReference + CreateBooleanInput(name string, options ...*CreateBooleanInputOptions) InteractionInputBuilder + CreateChoiceInput(name string, options ...*CreateChoiceInputOptions) InteractionInputBuilder + CreateNumberInput(name string, options ...*CreateNumberInputOptions) InteractionInputBuilder + CreateSecretInput(name string, options ...*CreateSecretInputOptions) InteractionInputBuilder + CreateTextInput(name string, options ...*CreateTextInputOptions) InteractionInputBuilder + IsAvailable() (bool, error) + PromptConfirmation(title string, message string, options ...*PromptConfirmationOptions) (*BoolInteractionResult, error) + PromptInput(title string, message string, input InteractionInputBuilder, options ...*PromptInputOptions) (*InputInteractionResult, error) + PromptInputs(title string, message string, inputs []InteractionInputBuilder, options ...*PromptInputsOptions) InputsInteractionResult + PromptMessageBox(title string, message string, options ...*PromptMessageBoxOptions) (*BoolInteractionResult, error) + PromptNotification(title string, message string, options ...*PromptNotificationOptions) (*BoolInteractionResult, error) + Err() error +} + +// interactionService is the unexported impl of InteractionService. +type interactionService struct { + *resourceBuilderBase +} + +// newInteractionServiceFromHandle wraps an existing handle as InteractionService. +func newInteractionServiceFromHandle(h *handle, c *client) InteractionService { + return &interactionService{resourceBuilderBase: newResourceBuilderBase(h, c)} +} + +// CreateBooleanInput creates a boolean (checkbox) input. +func (s *interactionService) CreateBooleanInput(name string, options ...*CreateBooleanInputOptions) InteractionInputBuilder { + if s.err != nil { return &interactionInputBuilder{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "interactionService": s.handle.ToJSON(), + } + reqArgs["name"] = serializeValue(name) + if len(options) > 0 { + merged := &CreateBooleanInputOptions{} + for _, opt := range options { + if opt != nil { merged = deepUpdate(merged, opt) } + } + for k, v := range merged.ToMap() { reqArgs[k] = v } + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/createBooleanInput", reqArgs) + if err != nil { + return &interactionInputBuilder{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting/createBooleanInput returned unexpected type %T", result) + return &interactionInputBuilder{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &interactionInputBuilder{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + +// CreateChoiceInput creates a choice input that selects from a list of options. +func (s *interactionService) CreateChoiceInput(name string, options ...*CreateChoiceInputOptions) InteractionInputBuilder { + if s.err != nil { return &interactionInputBuilder{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "interactionService": s.handle.ToJSON(), + } + reqArgs["name"] = serializeValue(name) + if len(options) > 0 { + merged := &CreateChoiceInputOptions{} + for _, opt := range options { + if opt != nil { merged = deepUpdate(merged, opt) } + } + for k, v := range merged.ToMap() { reqArgs[k] = v } + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/createChoiceInput", reqArgs) + if err != nil { + return &interactionInputBuilder{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting/createChoiceInput returned unexpected type %T", result) + return &interactionInputBuilder{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &interactionInputBuilder{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + +// CreateNumberInput creates a numeric input. +func (s *interactionService) CreateNumberInput(name string, options ...*CreateNumberInputOptions) InteractionInputBuilder { + if s.err != nil { return &interactionInputBuilder{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "interactionService": s.handle.ToJSON(), + } + reqArgs["name"] = serializeValue(name) + if len(options) > 0 { + merged := &CreateNumberInputOptions{} + for _, opt := range options { + if opt != nil { merged = deepUpdate(merged, opt) } + } + for k, v := range merged.ToMap() { reqArgs[k] = v } + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/createNumberInput", reqArgs) + if err != nil { + return &interactionInputBuilder{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting/createNumberInput returned unexpected type %T", result) + return &interactionInputBuilder{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &interactionInputBuilder{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + +// CreateSecretInput creates a secret (masked) text input. +func (s *interactionService) CreateSecretInput(name string, options ...*CreateSecretInputOptions) InteractionInputBuilder { + if s.err != nil { return &interactionInputBuilder{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "interactionService": s.handle.ToJSON(), + } + reqArgs["name"] = serializeValue(name) + if len(options) > 0 { + merged := &CreateSecretInputOptions{} + for _, opt := range options { + if opt != nil { merged = deepUpdate(merged, opt) } + } + for k, v := range merged.ToMap() { reqArgs[k] = v } + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/createSecretInput", reqArgs) + if err != nil { + return &interactionInputBuilder{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting/createSecretInput returned unexpected type %T", result) + return &interactionInputBuilder{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &interactionInputBuilder{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + +// CreateTextInput creates a single-line text input. +func (s *interactionService) CreateTextInput(name string, options ...*CreateTextInputOptions) InteractionInputBuilder { + if s.err != nil { return &interactionInputBuilder{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "interactionService": s.handle.ToJSON(), + } + reqArgs["name"] = serializeValue(name) + if len(options) > 0 { + merged := &CreateTextInputOptions{} + for _, opt := range options { + if opt != nil { merged = deepUpdate(merged, opt) } + } + for k, v := range merged.ToMap() { reqArgs[k] = v } + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/createTextInput", reqArgs) + if err != nil { + return &interactionInputBuilder{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting/createTextInput returned unexpected type %T", result) + return &interactionInputBuilder{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &interactionInputBuilder{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + +// IsAvailable gets a value indicating whether the interaction service is available to prompt the user. +func (s *interactionService) IsAvailable() (bool, error) { + if s.err != nil { var zero bool; return zero, s.err } + ctx := context.Background() + reqArgs := map[string]any{ + "interactionService": s.handle.ToJSON(), + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/isAvailable", reqArgs) + if err != nil { + var zero bool + return zero, err + } + return decodeAs[bool](result) +} + +// PromptConfirmation prompts the user for confirmation with an OK/Cancel dialog. +func (s *interactionService) PromptConfirmation(title string, message string, options ...*PromptConfirmationOptions) (*BoolInteractionResult, error) { + if s.err != nil { var zero *BoolInteractionResult; return zero, s.err } + ctx := context.Background() + reqArgs := map[string]any{ + "interactionService": s.handle.ToJSON(), + } + reqArgs["title"] = serializeValue(title) + reqArgs["message"] = serializeValue(message) + if len(options) > 0 { + merged := &PromptConfirmationOptions{} + for _, opt := range options { + if opt != nil { merged = deepUpdate(merged, opt) } + } + for k, v := range merged.ToMap() { reqArgs[k] = v } + if merged.CancellationToken != nil { + ctx = merged.CancellationToken.Context() + if id := s.client.registerCancellation(merged.CancellationToken); id != "" { + reqArgs["cancellationToken"] = id + } + } + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/promptConfirmation", reqArgs) + if err != nil { + var zero *BoolInteractionResult + return zero, err + } + return decodeAs[*BoolInteractionResult](result) +} + +// PromptInput prompts the user for a single input. +func (s *interactionService) PromptInput(title string, message string, input InteractionInputBuilder, options ...*PromptInputOptions) (*InputInteractionResult, error) { + if s.err != nil { var zero *InputInteractionResult; return zero, s.err } + if input != nil { if err := input.Err(); err != nil { var zero *InputInteractionResult; return zero, err } } + ctx := context.Background() + reqArgs := map[string]any{ + "interactionService": s.handle.ToJSON(), + } + reqArgs["title"] = serializeValue(title) + reqArgs["message"] = serializeValue(message) + reqArgs["input"] = serializeValue(input) + if len(options) > 0 { + merged := &PromptInputOptions{} + for _, opt := range options { + if opt != nil { merged = deepUpdate(merged, opt) } + } + for k, v := range merged.ToMap() { reqArgs[k] = v } + if merged.CancellationToken != nil { + ctx = merged.CancellationToken.Context() + if id := s.client.registerCancellation(merged.CancellationToken); id != "" { + reqArgs["cancellationToken"] = id + } + } + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/promptInput", reqArgs) + if err != nil { + var zero *InputInteractionResult + return zero, err + } + return decodeAs[*InputInteractionResult](result) +} + +// PromptInputs prompts the user for multiple inputs. +func (s *interactionService) PromptInputs(title string, message string, inputs []InteractionInputBuilder, options ...*PromptInputsOptions) InputsInteractionResult { + if s.err != nil { return &inputsInteractionResult{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "interactionService": s.handle.ToJSON(), + } + reqArgs["title"] = serializeValue(title) + reqArgs["message"] = serializeValue(message) + if inputs != nil { reqArgs["inputs"] = serializeValue(inputs) } + if len(options) > 0 { + merged := &PromptInputsOptions{} + for _, opt := range options { + if opt != nil { merged = deepUpdate(merged, opt) } + } + for k, v := range merged.ToMap() { reqArgs[k] = v } + if merged.CancellationToken != nil { + ctx = merged.CancellationToken.Context() + if id := s.client.registerCancellation(merged.CancellationToken); id != "" { + reqArgs["cancellationToken"] = id + } + } + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/promptInputs", reqArgs) + if err != nil { + return &inputsInteractionResult{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting/promptInputs returned unexpected type %T", result) + return &inputsInteractionResult{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &inputsInteractionResult{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + +// PromptMessageBox prompts the user with a message box dialog. +func (s *interactionService) PromptMessageBox(title string, message string, options ...*PromptMessageBoxOptions) (*BoolInteractionResult, error) { + if s.err != nil { var zero *BoolInteractionResult; return zero, s.err } + ctx := context.Background() + reqArgs := map[string]any{ + "interactionService": s.handle.ToJSON(), + } + reqArgs["title"] = serializeValue(title) + reqArgs["message"] = serializeValue(message) + if len(options) > 0 { + merged := &PromptMessageBoxOptions{} + for _, opt := range options { + if opt != nil { merged = deepUpdate(merged, opt) } + } + for k, v := range merged.ToMap() { reqArgs[k] = v } + if merged.CancellationToken != nil { + ctx = merged.CancellationToken.Context() + if id := s.client.registerCancellation(merged.CancellationToken); id != "" { + reqArgs["cancellationToken"] = id + } + } + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/promptMessageBox", reqArgs) + if err != nil { + var zero *BoolInteractionResult + return zero, err + } + return decodeAs[*BoolInteractionResult](result) +} + +// PromptNotification prompts the user with a notification. +func (s *interactionService) PromptNotification(title string, message string, options ...*PromptNotificationOptions) (*BoolInteractionResult, error) { + if s.err != nil { var zero *BoolInteractionResult; return zero, s.err } + ctx := context.Background() + reqArgs := map[string]any{ + "interactionService": s.handle.ToJSON(), + } + reqArgs["title"] = serializeValue(title) + reqArgs["message"] = serializeValue(message) + if len(options) > 0 { + merged := &PromptNotificationOptions{} + for _, opt := range options { + if opt != nil { merged = deepUpdate(merged, opt) } + } + for k, v := range merged.ToMap() { reqArgs[k] = v } + if merged.CancellationToken != nil { + ctx = merged.CancellationToken.Context() + if id := s.client.registerCancellation(merged.CancellationToken); id != "" { + reqArgs["cancellationToken"] = id + } + } + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/promptNotification", reqArgs) + if err != nil { + var zero *BoolInteractionResult + return zero, err + } + return decodeAs[*BoolInteractionResult](result) +} + +// LogFacade is the public interface for handle type LogFacade. +type LogFacade interface { + handleReference + Debug(message string) error + Error(message string) error + Info(message string) error + Warning(message string) error + Err() error +} + +// logFacade is the unexported impl of LogFacade. +type logFacade struct { + *resourceBuilderBase +} + +// newLogFacadeFromHandle wraps an existing handle as LogFacade. +func newLogFacadeFromHandle(h *handle, c *client) LogFacade { + return &logFacade{resourceBuilderBase: newResourceBuilderBase(h, c)} +} + +// Debug writes a debug log message. +func (s *logFacade) Debug(message string) error { + if s.err != nil { return s.err } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + reqArgs["message"] = serializeValue(message) + _, err := s.client.invokeCapability(ctx, "Aspire.Hosting.ApplicationModel/debug", reqArgs) + return err +} + +// Error writes an error log message. +func (s *logFacade) Error(message string) error { + if s.err != nil { return s.err } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + reqArgs["message"] = serializeValue(message) + _, err := s.client.invokeCapability(ctx, "Aspire.Hosting.ApplicationModel/error", reqArgs) + return err +} + +// Info writes an informational log message. +func (s *logFacade) Info(message string) error { + if s.err != nil { return s.err } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + reqArgs["message"] = serializeValue(message) + _, err := s.client.invokeCapability(ctx, "Aspire.Hosting.ApplicationModel/info", reqArgs) + return err +} + +// Warning writes a warning log message. +func (s *logFacade) Warning(message string) error { + if s.err != nil { return s.err } + ctx := context.Background() + reqArgs := map[string]any{ + "context": s.handle.ToJSON(), + } + reqArgs["message"] = serializeValue(message) + _, err := s.client.invokeCapability(ctx, "Aspire.Hosting.ApplicationModel/warning", reqArgs) + return err +} + +// Logger is the public interface for handle type Logger. +type Logger interface { + handleReference + Log(level string, message string) error + LogDebug(message string) error + LogError(message string) error + LogInformation(message string) error + LogWarning(message string) error + Err() error +} + +// logger is the unexported impl of Logger. +type logger struct { + *resourceBuilderBase +} + +// newLoggerFromHandle wraps an existing handle as Logger. +func newLoggerFromHandle(h *handle, c *client) Logger { + return &logger{resourceBuilderBase: newResourceBuilderBase(h, c)} +} + +// Log logs a message with a specified log level. +func (s *logger) Log(level string, message string) error { + if s.err != nil { return s.err } + ctx := context.Background() + reqArgs := map[string]any{ + "logger": s.handle.ToJSON(), + } + reqArgs["level"] = serializeValue(level) + reqArgs["message"] = serializeValue(message) + _, err := s.client.invokeCapability(ctx, "Aspire.Hosting/log", reqArgs) + return err +} + +// LogDebug logs a debug message. +func (s *logger) LogDebug(message string) error { + if s.err != nil { return s.err } + ctx := context.Background() + reqArgs := map[string]any{ + "logger": s.handle.ToJSON(), + } + reqArgs["message"] = serializeValue(message) + _, err := s.client.invokeCapability(ctx, "Aspire.Hosting/logDebug", reqArgs) + return err +} + +// LogError logs an error message. +func (s *logger) LogError(message string) error { + if s.err != nil { return s.err } + ctx := context.Background() + reqArgs := map[string]any{ + "logger": s.handle.ToJSON(), + } reqArgs["message"] = serializeValue(message) _, err := s.client.invokeCapability(ctx, "Aspire.Hosting/logError", reqArgs) return err @@ -20581,6 +21401,7 @@ type ServiceProvider interface { GetAspireStore() AspireStore GetDistributedApplicationModel() DistributedApplicationModel GetEventing() DistributedApplicationEventing + GetInteractionService() InteractionService GetLoggerFactory() LoggerFactory GetResourceCommandService() ResourceCommandService GetResourceLoggerService() ResourceLoggerService @@ -20656,6 +21477,25 @@ func (s *serviceProvider) GetEventing() DistributedApplicationEventing { return &distributedApplicationEventing{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} } +// GetInteractionService gets the interaction service from the service provider. +func (s *serviceProvider) GetInteractionService() InteractionService { + if s.err != nil { return &interactionService{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } + ctx := context.Background() + reqArgs := map[string]any{ + "serviceProvider": s.handle.ToJSON(), + } + result, err := s.client.invokeCapability(ctx, "Aspire.Hosting/getInteractionService", reqArgs) + if err != nil { + return &interactionService{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + href, ok := result.(handleReference) + if !ok { + err := fmt.Errorf("aspire: Aspire.Hosting/getInteractionService returned unexpected type %T", result) + return &interactionService{resourceBuilderBase: newErroredResourceBuilder(err, s.client)} + } + return &interactionService{resourceBuilderBase: newResourceBuilderBase(href.getHandle(), s.client)} +} + // GetLoggerFactory gets the logger factory from the service provider. func (s *serviceProvider) GetLoggerFactory() LoggerFactory { if s.err != nil { return &loggerFactory{resourceBuilderBase: newErroredResourceBuilder(s.err, s.client)} } @@ -26180,6 +27020,145 @@ func (o *BuildOptions) ToMap() map[string]any { return m } +// PromptConfirmationOptions carries optional parameters for PromptConfirmation. +type PromptConfirmationOptions struct { + Options *InteractionMessageBoxOptions `json:"options,omitempty"` + CancellationToken *CancellationToken `json:"-"` +} + +func (o *PromptConfirmationOptions) ToMap() map[string]any { + m := map[string]any{} + if o == nil { return m } + if o.Options != nil { m["options"] = serializeValue(o.Options) } + return m +} + +// PromptMessageBoxOptions carries optional parameters for PromptMessageBox. +type PromptMessageBoxOptions struct { + Options *InteractionMessageBoxOptions `json:"options,omitempty"` + CancellationToken *CancellationToken `json:"-"` +} + +func (o *PromptMessageBoxOptions) ToMap() map[string]any { + m := map[string]any{} + if o == nil { return m } + if o.Options != nil { m["options"] = serializeValue(o.Options) } + return m +} + +// PromptNotificationOptions carries optional parameters for PromptNotification. +type PromptNotificationOptions struct { + Options *InteractionNotificationOptions `json:"options,omitempty"` + CancellationToken *CancellationToken `json:"-"` +} + +func (o *PromptNotificationOptions) ToMap() map[string]any { + m := map[string]any{} + if o == nil { return m } + if o.Options != nil { m["options"] = serializeValue(o.Options) } + return m +} + +// PromptInputOptions carries optional parameters for PromptInput. +type PromptInputOptions struct { + Options *InteractionInputsDialogOptions `json:"options,omitempty"` + CancellationToken *CancellationToken `json:"-"` +} + +func (o *PromptInputOptions) ToMap() map[string]any { + m := map[string]any{} + if o == nil { return m } + if o.Options != nil { m["options"] = serializeValue(o.Options) } + return m +} + +// PromptInputsOptions carries optional parameters for PromptInputs. +type PromptInputsOptions struct { + Options *InteractionInputsDialogOptions `json:"options,omitempty"` + CancellationToken *CancellationToken `json:"-"` +} + +func (o *PromptInputsOptions) ToMap() map[string]any { + m := map[string]any{} + if o == nil { return m } + if o.Options != nil { m["options"] = serializeValue(o.Options) } + return m +} + +// CreateTextInputOptions carries optional parameters for CreateTextInput. +type CreateTextInputOptions struct { + Options *CreateInteractionInputOptions `json:"options,omitempty"` +} + +func (o *CreateTextInputOptions) ToMap() map[string]any { + m := map[string]any{} + if o == nil { return m } + if o.Options != nil { m["options"] = serializeValue(o.Options) } + return m +} + +// CreateSecretInputOptions carries optional parameters for CreateSecretInput. +type CreateSecretInputOptions struct { + Options *CreateInteractionInputOptions `json:"options,omitempty"` +} + +func (o *CreateSecretInputOptions) ToMap() map[string]any { + m := map[string]any{} + if o == nil { return m } + if o.Options != nil { m["options"] = serializeValue(o.Options) } + return m +} + +// CreateBooleanInputOptions carries optional parameters for CreateBooleanInput. +type CreateBooleanInputOptions struct { + Options *CreateInteractionInputOptions `json:"options,omitempty"` +} + +func (o *CreateBooleanInputOptions) ToMap() map[string]any { + m := map[string]any{} + if o == nil { return m } + if o.Options != nil { m["options"] = serializeValue(o.Options) } + return m +} + +// CreateNumberInputOptions carries optional parameters for CreateNumberInput. +type CreateNumberInputOptions struct { + Options *CreateInteractionInputOptions `json:"options,omitempty"` +} + +func (o *CreateNumberInputOptions) ToMap() map[string]any { + m := map[string]any{} + if o == nil { return m } + if o.Options != nil { m["options"] = serializeValue(o.Options) } + return m +} + +// CreateChoiceInputOptions carries optional parameters for CreateChoiceInput. +type CreateChoiceInputOptions struct { + Choices []*InteractionChoiceOption `json:"choices,omitempty"` + Options *CreateInteractionInputOptions `json:"options,omitempty"` +} + +func (o *CreateChoiceInputOptions) ToMap() map[string]any { + m := map[string]any{} + if o == nil { return m } + if o.Choices != nil { m["choices"] = serializeValue(o.Choices) } + if o.Options != nil { m["options"] = serializeValue(o.Options) } + return m +} + +// WithDynamicLoadingOptions carries optional parameters for WithDynamicLoading. +type WithDynamicLoadingOptions struct { + Options *DynamicLoadingOptions `json:"options,omitempty"` +} + +func (o *WithDynamicLoadingOptions) ToMap() map[string]any { + m := map[string]any{} + if o == nil { return m } + if o.Options != nil { m["options"] = serializeValue(o.Options) } + return m +} + // WaitForResourceStateOptions carries optional parameters for WaitForResourceState. type WaitForResourceStateOptions struct { TargetState *string `json:"targetState,omitempty"` @@ -26687,9 +27666,24 @@ func registerWrappers(c *client) { c.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.InputsDialogValidationContext", func(h *handle, c *client) any { return newInputsDialogValidationContextFromHandle(h, c) }) + c.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Ats.InputsInteractionResult", func(h *handle, c *client) any { + return newInputsInteractionResultFromHandle(h, c) + }) + c.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder", func(h *handle, c *client) any { + return newInteractionInputBuilderFromHandle(h, c) + }) c.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.InteractionInputCollection", func(h *handle, c *client) any { return newInteractionInputCollectionFromHandle(h, c) }) + c.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputLoadContext", func(h *handle, c *client) any { + return newInteractionInputLoadContextFromHandle(h, c) + }) + c.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Ats.InteractionLoadingInput", func(h *handle, c *client) any { + return newInteractionLoadingInputFromHandle(h, c) + }) + c.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.IInteractionService", func(h *handle, c *client) any { + return newInteractionServiceFromHandle(h, c) + }) c.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.LogFacade", func(h *handle, c *client) any { return newLogFacadeFromHandle(h, c) }) diff --git a/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/AtsGeneratedAspire.verified.java b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/AtsGeneratedAspire.verified.java index 1511e3911c8..f2b580d7c54 100644 --- a/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/AtsGeneratedAspire.verified.java +++ b/tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/AtsGeneratedAspire.verified.java @@ -1,4 +1,4 @@ -// ===== Aspire.java ===== +// ===== Aspire.java ===== // Aspire.java - GENERATED CODE - DO NOT EDIT package aspire; @@ -1791,9 +1791,9 @@ public class TestDatabaseResource extends ResourceBuilderBase { } /** Adds an optional string parameter */ - public TestDatabaseResource withOptionalString(WithOptionalStringOptions options) { - var value = options == null ? null : options.getValue(); - var enabled = options == null ? null : options.getEnabled(); + public TestDatabaseResource withOptionalString(WithOptionalStringOptions optionsBag) { + var value = optionsBag == null ? null : optionsBag.getValue(); + var enabled = optionsBag == null ? null : optionsBag.getEnabled(); return withOptionalStringImpl(value, enabled); } @@ -2002,8 +2002,8 @@ public TestDatabaseResource withCancellableOperation(AspireAction1 new ResourceNotificationService(h, c)); AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ResourceLoggerService", (h, c) -> new ResourceLoggerService(h, c)); AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.ResourceCommandService", (h, c) -> new ResourceCommandService(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.IInteractionService", (h, c) -> new IInteractionService(h, c)); AspireClient.registerHandleWrapper("Microsoft.Extensions.Configuration.Abstractions/Microsoft.Extensions.Configuration.IConfiguration", (h, c) -> new IConfiguration(h, c)); AspireClient.registerHandleWrapper("Microsoft.Extensions.Configuration.Abstractions/Microsoft.Extensions.Configuration.IConfigurationSection", (h, c) -> new IConfigurationSection(h, c)); AspireClient.registerHandleWrapper("Microsoft.Extensions.Hosting.Abstractions/Microsoft.Extensions.Hosting.IHostEnvironment", (h, c) -> new IHostEnvironment(h, c)); @@ -1371,6 +1372,10 @@ public class AspireRegistrations { AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationResourceEvent", (h, c) -> new IDistributedApplicationResourceEvent(h, c)); AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Eventing.IDistributedApplicationEventing", (h, c) -> new IDistributedApplicationEventing(h, c)); AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Ats.EventingSubscriberRegistrationContext", (h, c) -> new EventingSubscriberRegistrationContext(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder", (h, c) -> new InteractionInputBuilder(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputLoadContext", (h, c) -> new InteractionInputLoadContext(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Ats.InteractionLoadingInput", (h, c) -> new InteractionLoadingInput(h, c)); + AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.Ats.InputsInteractionResult", (h, c) -> new InputsInteractionResult(h, c)); AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.AfterResourcesCreatedEvent", (h, c) -> new AfterResourcesCreatedEvent(h, c)); AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.BeforeResourceStartedEvent", (h, c) -> new BeforeResourceStartedEvent(h, c)); AspireClient.registerHandleWrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.BeforeStartEvent", (h, c) -> new BeforeStartEvent(h, c)); @@ -1596,6 +1601,42 @@ public DistributedApplicationModel model() { } +// ===== BoolInteractionResult.java ===== +// BoolInteractionResult.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** BoolInteractionResult DTO. */ +public class BoolInteractionResult implements JsonSerializable { + private boolean canceled; + private Boolean value; + + public boolean getCanceled() { return canceled; } + public void setCanceled(boolean value) { this.canceled = value; } + public Boolean getValue() { return value; } + public void setValue(Boolean value) { this.value = value; } + + @SuppressWarnings("unchecked") + public static BoolInteractionResult fromMap(Map map) { + var value = new BoolInteractionResult(); + var canceledValue = map.get("Canceled"); + value.setCanceled((Boolean) canceledValue); + var valueValue = map.get("Value"); + value.setValue(valueValue == null ? null : (Boolean) valueValue); + return value; + } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("Canceled", AspireClient.serializeValue(canceled)); + map.put("Value", AspireClient.serializeValue(value)); + return map; + } +} + // ===== BuildOptions.java ===== // BuildOptions.java - GENERATED CODE - DO NOT EDIT @@ -1651,9 +1692,9 @@ public CSharpAppResource withContainerRegistry(ResourceBuilderBase registry) { } /** Configures custom base images for generated Dockerfiles. */ - public CSharpAppResource withDockerfileBaseImage(WithDockerfileBaseImageOptions options) { - var buildImage = options == null ? null : options.getBuildImage(); - var runtimeImage = options == null ? null : options.getRuntimeImage(); + public CSharpAppResource withDockerfileBaseImage(WithDockerfileBaseImageOptions optionsBag) { + var buildImage = optionsBag == null ? null : optionsBag.getBuildImage(); + var runtimeImage = optionsBag == null ? null : optionsBag.getRuntimeImage(); return withDockerfileBaseImageImpl(buildImage, runtimeImage); } @@ -1676,9 +1717,9 @@ private CSharpAppResource withDockerfileBaseImageImpl(String buildImage, String } /** Marks the resource as hosting a Model Context Protocol (MCP) server on the specified endpoint. */ - public CSharpAppResource withMcpServer(WithMcpServerOptions options) { - var path = options == null ? null : options.getPath(); - var endpointName = options == null ? null : options.getEndpointName(); + public CSharpAppResource withMcpServer(WithMcpServerOptions optionsBag) { + var path = optionsBag == null ? null : optionsBag.getPath(); + var endpointName = optionsBag == null ? null : optionsBag.getEndpointName(); return withMcpServerImpl(path, endpointName); } @@ -1931,10 +1972,10 @@ public CSharpAppResource withReference(String source) { } /** Adds a reference to another resource */ - public CSharpAppResource withReference(AspireUnion source, WithReferenceOptions options) { - var connectionName = options == null ? null : options.getConnectionName(); - var optional = options == null ? null : options.getOptional(); - var name = options == null ? null : options.getName(); + public CSharpAppResource withReference(AspireUnion source, WithReferenceOptions optionsBag) { + var connectionName = optionsBag == null ? null : optionsBag.getConnectionName(); + var optional = optionsBag == null ? null : optionsBag.getOptional(); + var name = optionsBag == null ? null : optionsBag.getName(); return withReferenceImpl(source, connectionName, optional, name); } @@ -1985,9 +2026,9 @@ public CSharpAppResource withEndpointCallback(String endpointName, AspireAction1 } /** Updates an HTTP endpoint via callback */ - public CSharpAppResource withHttpEndpointCallback(AspireAction1 callback, WithHttpEndpointCallbackOptions options) { - var name = options == null ? null : options.getName(); - var createIfNotExists = options == null ? null : options.getCreateIfNotExists(); + public CSharpAppResource withHttpEndpointCallback(AspireAction1 callback, WithHttpEndpointCallbackOptions optionsBag) { + var name = optionsBag == null ? null : optionsBag.getName(); + var createIfNotExists = optionsBag == null ? null : optionsBag.getCreateIfNotExists(); return withHttpEndpointCallbackImpl(callback, name, createIfNotExists); } @@ -2018,9 +2059,9 @@ private CSharpAppResource withHttpEndpointCallbackImpl(AspireAction1 callback, WithHttpsEndpointCallbackOptions options) { - var name = options == null ? null : options.getName(); - var createIfNotExists = options == null ? null : options.getCreateIfNotExists(); + public CSharpAppResource withHttpsEndpointCallback(AspireAction1 callback, WithHttpsEndpointCallbackOptions optionsBag) { + var name = optionsBag == null ? null : optionsBag.getName(); + var createIfNotExists = optionsBag == null ? null : optionsBag.getCreateIfNotExists(); return withHttpsEndpointCallbackImpl(callback, name, createIfNotExists); } @@ -2051,15 +2092,15 @@ private CSharpAppResource withHttpsEndpointCallbackImpl(AspireAction1 callback, WithPipelineStepFactoryOptions options) { - var dependsOn = options == null ? null : options.getDependsOn(); - var requiredBy = options == null ? null : options.getRequiredBy(); - var tags = options == null ? null : options.getTags(); - var description = options == null ? null : options.getDescription(); + public CSharpAppResource withPipelineStepFactory(String stepName, AspireAction1 callback, WithPipelineStepFactoryOptions optionsBag) { + var dependsOn = optionsBag == null ? null : optionsBag.getDependsOn(); + var requiredBy = optionsBag == null ? null : optionsBag.getRequiredBy(); + var tags = optionsBag == null ? null : optionsBag.getTags(); + var description = optionsBag == null ? null : optionsBag.getDescription(); return withPipelineStepFactoryImpl(stepName, callback, dependsOn, requiredBy, tags, description); } @@ -2875,9 +2916,9 @@ public IExecutionConfigurationBuilder createExecutionConfiguration() { } /** Adds an optional string parameter */ - public CSharpAppResource withOptionalString(WithOptionalStringOptions options) { - var value = options == null ? null : options.getValue(); - var enabled = options == null ? null : options.getEnabled(); + public CSharpAppResource withOptionalString(WithOptionalStringOptions optionsBag) { + var value = optionsBag == null ? null : optionsBag.getValue(); + var enabled = optionsBag == null ? null : optionsBag.getEnabled(); return withOptionalStringImpl(value, enabled); } @@ -3126,9 +3167,9 @@ public CSharpAppResource withMergeEndpointScheme(String endpointName, double por } /** Configures resource logging */ - public CSharpAppResource withMergeLogging(String logLevel, WithMergeLoggingOptions options) { - var enableConsole = options == null ? null : options.getEnableConsole(); - var maxFiles = options == null ? null : options.getMaxFiles(); + public CSharpAppResource withMergeLogging(String logLevel, WithMergeLoggingOptions optionsBag) { + var enableConsole = optionsBag == null ? null : optionsBag.getEnableConsole(); + var maxFiles = optionsBag == null ? null : optionsBag.getMaxFiles(); return withMergeLoggingImpl(logLevel, enableConsole, maxFiles); } @@ -3152,9 +3193,9 @@ private CSharpAppResource withMergeLoggingImpl(String logLevel, Boolean enableCo } /** Configures resource logging with file path */ - public CSharpAppResource withMergeLoggingPath(String logLevel, String logPath, WithMergeLoggingPathOptions options) { - var enableConsole = options == null ? null : options.getEnableConsole(); - var maxFiles = options == null ? null : options.getMaxFiles(); + public CSharpAppResource withMergeLoggingPath(String logLevel, String logPath, WithMergeLoggingPathOptions optionsBag) { + var enableConsole = optionsBag == null ? null : optionsBag.getEnableConsole(); + var maxFiles = optionsBag == null ? null : optionsBag.getMaxFiles(); return withMergeLoggingPathImpl(logLevel, logPath, enableConsole, maxFiles); } @@ -3510,13 +3551,13 @@ public class CommandOptions implements JsonSerializable { private String description; private Object parameter; private InteractionInput[] arguments; - private Object validateArguments; + private AspireAction1 validateArguments; private ResourceCommandVisibility visibility; private String confirmationMessage; private String iconName; private IconVariant iconVariant; private boolean isHighlighted; - private Object updateState; + private AspireFunc1 updateState; public String getDescription() { return description; } public void setDescription(String value) { this.description = value; } @@ -3524,8 +3565,8 @@ public class CommandOptions implements JsonSerializable { public void setParameter(Object value) { this.parameter = value; } public InteractionInput[] getArguments() { return arguments; } public void setArguments(InteractionInput[] value) { this.arguments = value; } - public Object getValidateArguments() { return validateArguments; } - public void setValidateArguments(Object value) { this.validateArguments = value; } + public AspireAction1 getValidateArguments() { return validateArguments; } + public void setValidateArguments(AspireAction1 value) { this.validateArguments = value; } public ResourceCommandVisibility getVisibility() { return visibility; } public void setVisibility(ResourceCommandVisibility value) { this.visibility = value; } public String getConfirmationMessage() { return confirmationMessage; } @@ -3536,8 +3577,8 @@ public class CommandOptions implements JsonSerializable { public void setIconVariant(IconVariant value) { this.iconVariant = value; } public boolean getIsHighlighted() { return isHighlighted; } public void setIsHighlighted(boolean value) { this.isHighlighted = value; } - public Object getUpdateState() { return updateState; } - public void setUpdateState(Object value) { this.updateState = value; } + public AspireFunc1 getUpdateState() { return updateState; } + public void setUpdateState(AspireFunc1 value) { this.updateState = value; } @SuppressWarnings("unchecked") public static CommandOptions fromMap(Map map) { @@ -3548,8 +3589,6 @@ public static CommandOptions fromMap(Map map) { value.setParameter(parameterValue); var argumentsValue = map.get("Arguments"); value.setArguments((InteractionInput[]) argumentsValue); - var validateArgumentsValue = map.get("ValidateArguments"); - value.setValidateArguments(validateArgumentsValue); var visibilityValue = map.get("Visibility"); value.setVisibility(ResourceCommandVisibility.fromValue((String) visibilityValue)); var confirmationMessageValue = map.get("ConfirmationMessage"); @@ -3560,8 +3599,6 @@ public static CommandOptions fromMap(Map map) { value.setIconVariant(iconVariantValue == null ? null : IconVariant.fromValue((String) iconVariantValue)); var isHighlightedValue = map.get("IsHighlighted"); value.setIsHighlighted((Boolean) isHighlightedValue); - var updateStateValue = map.get("UpdateState"); - value.setUpdateState(updateStateValue); return value; } @@ -3570,13 +3607,20 @@ public Map toMap() { map.put("Description", AspireClient.serializeValue(description)); map.put("Parameter", AspireClient.serializeValue(parameter)); map.put("Arguments", AspireClient.serializeValue(arguments)); - map.put("ValidateArguments", AspireClient.serializeValue(validateArguments)); + map.put("ValidateArguments", validateArguments == null ? null : (java.util.function.Function) (transportArg -> { + var arg = (InputsDialogValidationContext) transportArg; + validateArguments.invoke(arg); + return null; + })); map.put("Visibility", AspireClient.serializeValue(visibility)); map.put("ConfirmationMessage", AspireClient.serializeValue(confirmationMessage)); map.put("IconName", AspireClient.serializeValue(iconName)); map.put("IconVariant", AspireClient.serializeValue(iconVariant)); map.put("IsHighlighted", AspireClient.serializeValue(isHighlighted)); - map.put("UpdateState", AspireClient.serializeValue(updateState)); + map.put("UpdateState", updateState == null ? null : (java.util.function.Function) (transportArg -> { + var arg = (UpdateCommandStateContext) transportArg; + return AspireClient.awaitValue(updateState.invoke(arg)); + })); return map; } } @@ -4130,9 +4174,9 @@ public ContainerRegistryResource withContainerRegistry(ResourceBuilderBase regis } /** Configures custom base images for generated Dockerfiles. */ - public ContainerRegistryResource withDockerfileBaseImage(WithDockerfileBaseImageOptions options) { - var buildImage = options == null ? null : options.getBuildImage(); - var runtimeImage = options == null ? null : options.getRuntimeImage(); + public ContainerRegistryResource withDockerfileBaseImage(WithDockerfileBaseImageOptions optionsBag) { + var buildImage = optionsBag == null ? null : optionsBag.getBuildImage(); + var runtimeImage = optionsBag == null ? null : optionsBag.getRuntimeImage(); return withDockerfileBaseImageImpl(buildImage, runtimeImage); } @@ -4422,9 +4466,9 @@ public ContainerRegistryResource withHidden() { } /** Hides the resource from default resource lists after successful completion */ - public ContainerRegistryResource withHiddenOnCompletion(WithHiddenOnCompletionOptions options) { - var exitCode = options == null ? null : options.getExitCode(); - var exitCodes = options == null ? null : options.getExitCodes(); + public ContainerRegistryResource withHiddenOnCompletion(WithHiddenOnCompletionOptions optionsBag) { + var exitCode = optionsBag == null ? null : optionsBag.getExitCode(); + var exitCodes = optionsBag == null ? null : optionsBag.getExitCodes(); return withHiddenOnCompletionImpl(exitCode, exitCodes); } @@ -4447,11 +4491,11 @@ private ContainerRegistryResource withHiddenOnCompletionImpl(Double exitCode, do } /** Adds a pipeline step to the resource that will be executed during deployment. */ - public ContainerRegistryResource withPipelineStepFactory(String stepName, AspireAction1 callback, WithPipelineStepFactoryOptions options) { - var dependsOn = options == null ? null : options.getDependsOn(); - var requiredBy = options == null ? null : options.getRequiredBy(); - var tags = options == null ? null : options.getTags(); - var description = options == null ? null : options.getDescription(); + public ContainerRegistryResource withPipelineStepFactory(String stepName, AspireAction1 callback, WithPipelineStepFactoryOptions optionsBag) { + var dependsOn = optionsBag == null ? null : optionsBag.getDependsOn(); + var requiredBy = optionsBag == null ? null : optionsBag.getRequiredBy(); + var tags = optionsBag == null ? null : optionsBag.getTags(); + var description = optionsBag == null ? null : optionsBag.getDescription(); return withPipelineStepFactoryImpl(stepName, callback, dependsOn, requiredBy, tags, description); } @@ -4585,9 +4629,9 @@ public IExecutionConfigurationBuilder createExecutionConfiguration() { } /** Adds an optional string parameter */ - public ContainerRegistryResource withOptionalString(WithOptionalStringOptions options) { - var value = options == null ? null : options.getValue(); - var enabled = options == null ? null : options.getEnabled(); + public ContainerRegistryResource withOptionalString(WithOptionalStringOptions optionsBag) { + var value = optionsBag == null ? null : optionsBag.getValue(); + var enabled = optionsBag == null ? null : optionsBag.getEnabled(); return withOptionalStringImpl(value, enabled); } @@ -4811,9 +4855,9 @@ public ContainerRegistryResource withMergeEndpointScheme(String endpointName, do } /** Configures resource logging */ - public ContainerRegistryResource withMergeLogging(String logLevel, WithMergeLoggingOptions options) { - var enableConsole = options == null ? null : options.getEnableConsole(); - var maxFiles = options == null ? null : options.getMaxFiles(); + public ContainerRegistryResource withMergeLogging(String logLevel, WithMergeLoggingOptions optionsBag) { + var enableConsole = optionsBag == null ? null : optionsBag.getEnableConsole(); + var maxFiles = optionsBag == null ? null : optionsBag.getMaxFiles(); return withMergeLoggingImpl(logLevel, enableConsole, maxFiles); } @@ -4837,9 +4881,9 @@ private ContainerRegistryResource withMergeLoggingImpl(String logLevel, Boolean } /** Configures resource logging with file path */ - public ContainerRegistryResource withMergeLoggingPath(String logLevel, String logPath, WithMergeLoggingPathOptions options) { - var enableConsole = options == null ? null : options.getEnableConsole(); - var maxFiles = options == null ? null : options.getMaxFiles(); + public ContainerRegistryResource withMergeLoggingPath(String logLevel, String logPath, WithMergeLoggingPathOptions optionsBag) { + var enableConsole = optionsBag == null ? null : optionsBag.getEnableConsole(); + var maxFiles = optionsBag == null ? null : optionsBag.getMaxFiles(); return withMergeLoggingPathImpl(logLevel, logPath, enableConsole, maxFiles); } @@ -5022,9 +5066,9 @@ public ContainerResource publishAsContainer() { } /** Causes Aspire to build the specified container image from a Dockerfile. */ - public ContainerResource withDockerfile(String contextPath, WithDockerfileOptions options) { - var dockerfilePath = options == null ? null : options.getDockerfilePath(); - var stage = options == null ? null : options.getStage(); + public ContainerResource withDockerfile(String contextPath, WithDockerfileOptions optionsBag) { + var dockerfilePath = optionsBag == null ? null : optionsBag.getDockerfilePath(); + var stage = optionsBag == null ? null : optionsBag.getStage(); return withDockerfileImpl(contextPath, dockerfilePath, stage); } @@ -5108,10 +5152,10 @@ public ContainerResource withBuildSecret(String name, ParameterResource value) { } /** Adds container certificate path overrides used for certificate trust at run time. */ - public ContainerResource withContainerCertificatePaths(WithContainerCertificatePathsOptions options) { - var customCertificatesDestination = options == null ? null : options.getCustomCertificatesDestination(); - var defaultCertificateBundlePaths = options == null ? null : options.getDefaultCertificateBundlePaths(); - var defaultCertificateDirectoryPaths = options == null ? null : options.getDefaultCertificateDirectoryPaths(); + public ContainerResource withContainerCertificatePaths(WithContainerCertificatePathsOptions optionsBag) { + var customCertificatesDestination = optionsBag == null ? null : optionsBag.getCustomCertificatesDestination(); + var defaultCertificateBundlePaths = optionsBag == null ? null : optionsBag.getDefaultCertificateBundlePaths(); + var defaultCertificateDirectoryPaths = optionsBag == null ? null : optionsBag.getDefaultCertificateDirectoryPaths(); return withContainerCertificatePathsImpl(customCertificatesDestination, defaultCertificateBundlePaths, defaultCertificateDirectoryPaths); } @@ -5178,9 +5222,9 @@ public ContainerResource withDockerfileBuilder(String contextPath, AspireAction1 } /** Configures custom base images for generated Dockerfiles. */ - public ContainerResource withDockerfileBaseImage(WithDockerfileBaseImageOptions options) { - var buildImage = options == null ? null : options.getBuildImage(); - var runtimeImage = options == null ? null : options.getRuntimeImage(); + public ContainerResource withDockerfileBaseImage(WithDockerfileBaseImageOptions optionsBag) { + var buildImage = optionsBag == null ? null : optionsBag.getBuildImage(); + var runtimeImage = optionsBag == null ? null : optionsBag.getRuntimeImage(); return withDockerfileBaseImageImpl(buildImage, runtimeImage); } @@ -5212,9 +5256,9 @@ public ContainerResource withContainerNetworkAlias(String alias) { } /** Marks the resource as hosting a Model Context Protocol (MCP) server on the specified endpoint. */ - public ContainerResource withMcpServer(WithMcpServerOptions options) { - var path = options == null ? null : options.getPath(); - var endpointName = options == null ? null : options.getEndpointName(); + public ContainerResource withMcpServer(WithMcpServerOptions optionsBag) { + var path = optionsBag == null ? null : optionsBag.getPath(); + var endpointName = optionsBag == null ? null : optionsBag.getEndpointName(); return withMcpServerImpl(path, endpointName); } @@ -5438,10 +5482,10 @@ public ContainerResource withReference(String source) { } /** Adds a reference to another resource */ - public ContainerResource withReference(AspireUnion source, WithReferenceOptions options) { - var connectionName = options == null ? null : options.getConnectionName(); - var optional = options == null ? null : options.getOptional(); - var name = options == null ? null : options.getName(); + public ContainerResource withReference(AspireUnion source, WithReferenceOptions optionsBag) { + var connectionName = optionsBag == null ? null : optionsBag.getConnectionName(); + var optional = optionsBag == null ? null : optionsBag.getOptional(); + var name = optionsBag == null ? null : optionsBag.getName(); return withReferenceImpl(source, connectionName, optional, name); } @@ -5492,9 +5536,9 @@ public ContainerResource withEndpointCallback(String endpointName, AspireAction1 } /** Updates an HTTP endpoint via callback */ - public ContainerResource withHttpEndpointCallback(AspireAction1 callback, WithHttpEndpointCallbackOptions options) { - var name = options == null ? null : options.getName(); - var createIfNotExists = options == null ? null : options.getCreateIfNotExists(); + public ContainerResource withHttpEndpointCallback(AspireAction1 callback, WithHttpEndpointCallbackOptions optionsBag) { + var name = optionsBag == null ? null : optionsBag.getName(); + var createIfNotExists = optionsBag == null ? null : optionsBag.getCreateIfNotExists(); return withHttpEndpointCallbackImpl(callback, name, createIfNotExists); } @@ -5525,9 +5569,9 @@ private ContainerResource withHttpEndpointCallbackImpl(AspireAction1 callback, WithHttpsEndpointCallbackOptions options) { - var name = options == null ? null : options.getName(); - var createIfNotExists = options == null ? null : options.getCreateIfNotExists(); + public ContainerResource withHttpsEndpointCallback(AspireAction1 callback, WithHttpsEndpointCallbackOptions optionsBag) { + var name = optionsBag == null ? null : optionsBag.getName(); + var createIfNotExists = optionsBag == null ? null : optionsBag.getCreateIfNotExists(); return withHttpsEndpointCallbackImpl(callback, name, createIfNotExists); } @@ -5558,15 +5602,15 @@ private ContainerResource withHttpsEndpointCallbackImpl(AspireAction1 callback, WithPipelineStepFactoryOptions options) { - var dependsOn = options == null ? null : options.getDependsOn(); - var requiredBy = options == null ? null : options.getRequiredBy(); - var tags = options == null ? null : options.getTags(); - var description = options == null ? null : options.getDescription(); + public ContainerResource withPipelineStepFactory(String stepName, AspireAction1 callback, WithPipelineStepFactoryOptions optionsBag) { + var dependsOn = optionsBag == null ? null : optionsBag.getDependsOn(); + var requiredBy = optionsBag == null ? null : optionsBag.getRequiredBy(); + var tags = optionsBag == null ? null : optionsBag.getTags(); + var description = optionsBag == null ? null : optionsBag.getDescription(); return withPipelineStepFactoryImpl(stepName, callback, dependsOn, requiredBy, tags, description); } @@ -6263,9 +6307,9 @@ public ContainerResource withPipelineConfiguration(AspireAction1 toMap() { } } +// ===== CreateChoiceInputOptions.java ===== +// CreateChoiceInputOptions.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** Options for CreateChoiceInput. */ +public final class CreateChoiceInputOptions { + private InteractionChoiceOption[] choices; + private CreateInteractionInputOptions options; + + public InteractionChoiceOption[] getChoices() { return choices; } + public CreateChoiceInputOptions choices(InteractionChoiceOption[] value) { + this.choices = value; + return this; + } + + public CreateInteractionInputOptions getOptions() { return options; } + public CreateChoiceInputOptions options(CreateInteractionInputOptions value) { + this.options = value; + return this; + } + +} + +// ===== CreateInteractionInputOptions.java ===== +// CreateInteractionInputOptions.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** CreateInteractionInputOptions DTO. */ +public class CreateInteractionInputOptions implements JsonSerializable { + private String label; + private String description; + private Boolean enableDescriptionMarkdown; + private Boolean required; + private String placeholder; + private String value; + private Boolean allowCustomChoice; + private Boolean disabled; + private Double maxLength; + + public String getLabel() { return label; } + public void setLabel(String value) { this.label = value; } + public String getDescription() { return description; } + public void setDescription(String value) { this.description = value; } + public Boolean getEnableDescriptionMarkdown() { return enableDescriptionMarkdown; } + public void setEnableDescriptionMarkdown(Boolean value) { this.enableDescriptionMarkdown = value; } + public Boolean getRequired() { return required; } + public void setRequired(Boolean value) { this.required = value; } + public String getPlaceholder() { return placeholder; } + public void setPlaceholder(String value) { this.placeholder = value; } + public String getValue() { return value; } + public void setValue(String value) { this.value = value; } + public Boolean getAllowCustomChoice() { return allowCustomChoice; } + public void setAllowCustomChoice(Boolean value) { this.allowCustomChoice = value; } + public Boolean getDisabled() { return disabled; } + public void setDisabled(Boolean value) { this.disabled = value; } + public Double getMaxLength() { return maxLength; } + public void setMaxLength(Double value) { this.maxLength = value; } + + @SuppressWarnings("unchecked") + public static CreateInteractionInputOptions fromMap(Map map) { + var value = new CreateInteractionInputOptions(); + var labelValue = map.get("Label"); + value.setLabel(labelValue == null ? null : (String) labelValue); + var descriptionValue = map.get("Description"); + value.setDescription(descriptionValue == null ? null : (String) descriptionValue); + var enableDescriptionMarkdownValue = map.get("EnableDescriptionMarkdown"); + value.setEnableDescriptionMarkdown(enableDescriptionMarkdownValue == null ? null : (Boolean) enableDescriptionMarkdownValue); + var requiredValue = map.get("Required"); + value.setRequired(requiredValue == null ? null : (Boolean) requiredValue); + var placeholderValue = map.get("Placeholder"); + value.setPlaceholder(placeholderValue == null ? null : (String) placeholderValue); + var valueValue = map.get("Value"); + value.setValue(valueValue == null ? null : (String) valueValue); + var allowCustomChoiceValue = map.get("AllowCustomChoice"); + value.setAllowCustomChoice(allowCustomChoiceValue == null ? null : (Boolean) allowCustomChoiceValue); + var disabledValue = map.get("Disabled"); + value.setDisabled(disabledValue == null ? null : (Boolean) disabledValue); + var maxLengthValue = map.get("MaxLength"); + value.setMaxLength(maxLengthValue == null ? null : ((Number) maxLengthValue).doubleValue()); + return value; + } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("Label", AspireClient.serializeValue(label)); + map.put("Description", AspireClient.serializeValue(description)); + map.put("EnableDescriptionMarkdown", AspireClient.serializeValue(enableDescriptionMarkdown)); + map.put("Required", AspireClient.serializeValue(required)); + map.put("Placeholder", AspireClient.serializeValue(placeholder)); + map.put("Value", AspireClient.serializeValue(value)); + map.put("AllowCustomChoice", AspireClient.serializeValue(allowCustomChoice)); + map.put("Disabled", AspireClient.serializeValue(disabled)); + map.put("MaxLength", AspireClient.serializeValue(maxLength)); + return map; + } +} + // ===== DistributedApplication.java ===== // DistributedApplication.java - GENERATED CODE - DO NOT EDIT @@ -7390,9 +7539,9 @@ public DotnetToolResource withContainerRegistry(ResourceBuilderBase registry) { } /** Configures custom base images for generated Dockerfiles. */ - public DotnetToolResource withDockerfileBaseImage(WithDockerfileBaseImageOptions options) { - var buildImage = options == null ? null : options.getBuildImage(); - var runtimeImage = options == null ? null : options.getRuntimeImage(); + public DotnetToolResource withDockerfileBaseImage(WithDockerfileBaseImageOptions optionsBag) { + var buildImage = optionsBag == null ? null : optionsBag.getBuildImage(); + var runtimeImage = optionsBag == null ? null : optionsBag.getRuntimeImage(); return withDockerfileBaseImageImpl(buildImage, runtimeImage); } @@ -7500,9 +7649,9 @@ public DotnetToolResource withWorkingDirectory(String workingDirectory) { } /** Marks the resource as hosting a Model Context Protocol (MCP) server on the specified endpoint. */ - public DotnetToolResource withMcpServer(WithMcpServerOptions options) { - var path = options == null ? null : options.getPath(); - var endpointName = options == null ? null : options.getEndpointName(); + public DotnetToolResource withMcpServer(WithMcpServerOptions optionsBag) { + var path = optionsBag == null ? null : optionsBag.getPath(); + var endpointName = optionsBag == null ? null : optionsBag.getEndpointName(); return withMcpServerImpl(path, endpointName); } @@ -7718,10 +7867,10 @@ public DotnetToolResource withReference(String source) { } /** Adds a reference to another resource */ - public DotnetToolResource withReference(AspireUnion source, WithReferenceOptions options) { - var connectionName = options == null ? null : options.getConnectionName(); - var optional = options == null ? null : options.getOptional(); - var name = options == null ? null : options.getName(); + public DotnetToolResource withReference(AspireUnion source, WithReferenceOptions optionsBag) { + var connectionName = optionsBag == null ? null : optionsBag.getConnectionName(); + var optional = optionsBag == null ? null : optionsBag.getOptional(); + var name = optionsBag == null ? null : optionsBag.getName(); return withReferenceImpl(source, connectionName, optional, name); } @@ -7772,9 +7921,9 @@ public DotnetToolResource withEndpointCallback(String endpointName, AspireAction } /** Updates an HTTP endpoint via callback */ - public DotnetToolResource withHttpEndpointCallback(AspireAction1 callback, WithHttpEndpointCallbackOptions options) { - var name = options == null ? null : options.getName(); - var createIfNotExists = options == null ? null : options.getCreateIfNotExists(); + public DotnetToolResource withHttpEndpointCallback(AspireAction1 callback, WithHttpEndpointCallbackOptions optionsBag) { + var name = optionsBag == null ? null : optionsBag.getName(); + var createIfNotExists = optionsBag == null ? null : optionsBag.getCreateIfNotExists(); return withHttpEndpointCallbackImpl(callback, name, createIfNotExists); } @@ -7805,9 +7954,9 @@ private DotnetToolResource withHttpEndpointCallbackImpl(AspireAction1 callback, WithHttpsEndpointCallbackOptions options) { - var name = options == null ? null : options.getName(); - var createIfNotExists = options == null ? null : options.getCreateIfNotExists(); + public DotnetToolResource withHttpsEndpointCallback(AspireAction1 callback, WithHttpsEndpointCallbackOptions optionsBag) { + var name = optionsBag == null ? null : optionsBag.getName(); + var createIfNotExists = optionsBag == null ? null : optionsBag.getCreateIfNotExists(); return withHttpsEndpointCallbackImpl(callback, name, createIfNotExists); } @@ -7838,15 +7987,15 @@ private DotnetToolResource withHttpsEndpointCallbackImpl(AspireAction1 callback, WithPipelineStepFactoryOptions options) { - var dependsOn = options == null ? null : options.getDependsOn(); - var requiredBy = options == null ? null : options.getRequiredBy(); - var tags = options == null ? null : options.getTags(); - var description = options == null ? null : options.getDescription(); + public DotnetToolResource withPipelineStepFactory(String stepName, AspireAction1 callback, WithPipelineStepFactoryOptions optionsBag) { + var dependsOn = optionsBag == null ? null : optionsBag.getDependsOn(); + var requiredBy = optionsBag == null ? null : optionsBag.getRequiredBy(); + var tags = optionsBag == null ? null : optionsBag.getTags(); + var description = optionsBag == null ? null : optionsBag.getDescription(); return withPipelineStepFactoryImpl(stepName, callback, dependsOn, requiredBy, tags, description); } @@ -8639,9 +8788,9 @@ public IExecutionConfigurationBuilder createExecutionConfiguration() { } /** Adds an optional string parameter */ - public DotnetToolResource withOptionalString(WithOptionalStringOptions options) { - var value = options == null ? null : options.getValue(); - var enabled = options == null ? null : options.getEnabled(); + public DotnetToolResource withOptionalString(WithOptionalStringOptions optionsBag) { + var value = optionsBag == null ? null : optionsBag.getValue(); + var enabled = optionsBag == null ? null : optionsBag.getEnabled(); return withOptionalStringImpl(value, enabled); } @@ -8890,9 +9039,9 @@ public DotnetToolResource withMergeEndpointScheme(String endpointName, double po } /** Configures resource logging */ - public DotnetToolResource withMergeLogging(String logLevel, WithMergeLoggingOptions options) { - var enableConsole = options == null ? null : options.getEnableConsole(); - var maxFiles = options == null ? null : options.getMaxFiles(); + public DotnetToolResource withMergeLogging(String logLevel, WithMergeLoggingOptions optionsBag) { + var enableConsole = optionsBag == null ? null : optionsBag.getEnableConsole(); + var maxFiles = optionsBag == null ? null : optionsBag.getMaxFiles(); return withMergeLoggingImpl(logLevel, enableConsole, maxFiles); } @@ -8916,9 +9065,9 @@ private DotnetToolResource withMergeLoggingImpl(String logLevel, Boolean enableC } /** Configures resource logging with file path */ - public DotnetToolResource withMergeLoggingPath(String logLevel, String logPath, WithMergeLoggingPathOptions options) { - var enableConsole = options == null ? null : options.getEnableConsole(); - var maxFiles = options == null ? null : options.getMaxFiles(); + public DotnetToolResource withMergeLoggingPath(String logLevel, String logPath, WithMergeLoggingPathOptions optionsBag) { + var enableConsole = optionsBag == null ? null : optionsBag.getEnableConsole(); + var maxFiles = optionsBag == null ? null : optionsBag.getMaxFiles(); return withMergeLoggingPathImpl(logLevel, logPath, enableConsole, maxFiles); } @@ -8969,6 +9118,42 @@ public DotnetToolResource withMergeRouteMiddleware(String path, String method, S } +// ===== DynamicLoadingOptions.java ===== +// DynamicLoadingOptions.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** DynamicLoadingOptions DTO. */ +public class DynamicLoadingOptions implements JsonSerializable { + private Boolean alwaysLoadOnStart; + private String[] dependsOnInputs; + + public Boolean getAlwaysLoadOnStart() { return alwaysLoadOnStart; } + public void setAlwaysLoadOnStart(Boolean value) { this.alwaysLoadOnStart = value; } + public String[] getDependsOnInputs() { return dependsOnInputs; } + public void setDependsOnInputs(String[] value) { this.dependsOnInputs = value; } + + @SuppressWarnings("unchecked") + public static DynamicLoadingOptions fromMap(Map map) { + var value = new DynamicLoadingOptions(); + var alwaysLoadOnStartValue = map.get("AlwaysLoadOnStart"); + value.setAlwaysLoadOnStart(alwaysLoadOnStartValue == null ? null : (Boolean) alwaysLoadOnStartValue); + var dependsOnInputsValue = map.get("DependsOnInputs"); + value.setDependsOnInputs((String[]) dependsOnInputsValue); + return value; + } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("AlwaysLoadOnStart", AspireClient.serializeValue(alwaysLoadOnStart)); + map.put("DependsOnInputs", AspireClient.serializeValue(dependsOnInputs)); + return map; + } +} + // ===== EndpointProperty.java ===== // EndpointProperty.java - GENERATED CODE - DO NOT EDIT @@ -9637,9 +9822,9 @@ public ExecutableResource withContainerRegistry(ResourceBuilderBase registry) { } /** Configures custom base images for generated Dockerfiles. */ - public ExecutableResource withDockerfileBaseImage(WithDockerfileBaseImageOptions options) { - var buildImage = options == null ? null : options.getBuildImage(); - var runtimeImage = options == null ? null : options.getRuntimeImage(); + public ExecutableResource withDockerfileBaseImage(WithDockerfileBaseImageOptions optionsBag) { + var buildImage = optionsBag == null ? null : optionsBag.getBuildImage(); + var runtimeImage = optionsBag == null ? null : optionsBag.getRuntimeImage(); return withDockerfileBaseImageImpl(buildImage, runtimeImage); } @@ -9696,9 +9881,9 @@ public ExecutableResource withWorkingDirectory(String workingDirectory) { } /** Marks the resource as hosting a Model Context Protocol (MCP) server on the specified endpoint. */ - public ExecutableResource withMcpServer(WithMcpServerOptions options) { - var path = options == null ? null : options.getPath(); - var endpointName = options == null ? null : options.getEndpointName(); + public ExecutableResource withMcpServer(WithMcpServerOptions optionsBag) { + var path = optionsBag == null ? null : optionsBag.getPath(); + var endpointName = optionsBag == null ? null : optionsBag.getEndpointName(); return withMcpServerImpl(path, endpointName); } @@ -9914,10 +10099,10 @@ public ExecutableResource withReference(String source) { } /** Adds a reference to another resource */ - public ExecutableResource withReference(AspireUnion source, WithReferenceOptions options) { - var connectionName = options == null ? null : options.getConnectionName(); - var optional = options == null ? null : options.getOptional(); - var name = options == null ? null : options.getName(); + public ExecutableResource withReference(AspireUnion source, WithReferenceOptions optionsBag) { + var connectionName = optionsBag == null ? null : optionsBag.getConnectionName(); + var optional = optionsBag == null ? null : optionsBag.getOptional(); + var name = optionsBag == null ? null : optionsBag.getName(); return withReferenceImpl(source, connectionName, optional, name); } @@ -9968,9 +10153,9 @@ public ExecutableResource withEndpointCallback(String endpointName, AspireAction } /** Updates an HTTP endpoint via callback */ - public ExecutableResource withHttpEndpointCallback(AspireAction1 callback, WithHttpEndpointCallbackOptions options) { - var name = options == null ? null : options.getName(); - var createIfNotExists = options == null ? null : options.getCreateIfNotExists(); + public ExecutableResource withHttpEndpointCallback(AspireAction1 callback, WithHttpEndpointCallbackOptions optionsBag) { + var name = optionsBag == null ? null : optionsBag.getName(); + var createIfNotExists = optionsBag == null ? null : optionsBag.getCreateIfNotExists(); return withHttpEndpointCallbackImpl(callback, name, createIfNotExists); } @@ -10001,9 +10186,9 @@ private ExecutableResource withHttpEndpointCallbackImpl(AspireAction1 callback, WithHttpsEndpointCallbackOptions options) { - var name = options == null ? null : options.getName(); - var createIfNotExists = options == null ? null : options.getCreateIfNotExists(); + public ExecutableResource withHttpsEndpointCallback(AspireAction1 callback, WithHttpsEndpointCallbackOptions optionsBag) { + var name = optionsBag == null ? null : optionsBag.getName(); + var createIfNotExists = optionsBag == null ? null : optionsBag.getCreateIfNotExists(); return withHttpsEndpointCallbackImpl(callback, name, createIfNotExists); } @@ -10034,15 +10219,15 @@ private ExecutableResource withHttpsEndpointCallbackImpl(AspireAction1 callback, WithPipelineStepFactoryOptions options) { - var dependsOn = options == null ? null : options.getDependsOn(); - var requiredBy = options == null ? null : options.getRequiredBy(); - var tags = options == null ? null : options.getTags(); - var description = options == null ? null : options.getDescription(); + public ExecutableResource withPipelineStepFactory(String stepName, AspireAction1 callback, WithPipelineStepFactoryOptions optionsBag) { + var dependsOn = optionsBag == null ? null : optionsBag.getDependsOn(); + var requiredBy = optionsBag == null ? null : optionsBag.getRequiredBy(); + var tags = optionsBag == null ? null : optionsBag.getTags(); + var description = optionsBag == null ? null : optionsBag.getDescription(); return withPipelineStepFactoryImpl(stepName, callback, dependsOn, requiredBy, tags, description); } @@ -10835,9 +11020,9 @@ public IExecutionConfigurationBuilder createExecutionConfiguration() { } /** Adds an optional string parameter */ - public ExecutableResource withOptionalString(WithOptionalStringOptions options) { - var value = options == null ? null : options.getValue(); - var enabled = options == null ? null : options.getEnabled(); + public ExecutableResource withOptionalString(WithOptionalStringOptions optionsBag) { + var value = optionsBag == null ? null : optionsBag.getValue(); + var enabled = optionsBag == null ? null : optionsBag.getEnabled(); return withOptionalStringImpl(value, enabled); } @@ -11086,9 +11271,9 @@ public ExecutableResource withMergeEndpointScheme(String endpointName, double po } /** Configures resource logging */ - public ExecutableResource withMergeLogging(String logLevel, WithMergeLoggingOptions options) { - var enableConsole = options == null ? null : options.getEnableConsole(); - var maxFiles = options == null ? null : options.getMaxFiles(); + public ExecutableResource withMergeLogging(String logLevel, WithMergeLoggingOptions optionsBag) { + var enableConsole = optionsBag == null ? null : optionsBag.getEnableConsole(); + var maxFiles = optionsBag == null ? null : optionsBag.getMaxFiles(); return withMergeLoggingImpl(logLevel, enableConsole, maxFiles); } @@ -11112,9 +11297,9 @@ private ExecutableResource withMergeLoggingImpl(String logLevel, Boolean enableC } /** Configures resource logging with file path */ - public ExecutableResource withMergeLoggingPath(String logLevel, String logPath, WithMergeLoggingPathOptions options) { - var enableConsole = options == null ? null : options.getEnableConsole(); - var maxFiles = options == null ? null : options.getMaxFiles(); + public ExecutableResource withMergeLoggingPath(String logLevel, String logPath, WithMergeLoggingPathOptions optionsBag) { + var enableConsole = optionsBag == null ? null : optionsBag.getEnableConsole(); + var maxFiles = optionsBag == null ? null : optionsBag.getMaxFiles(); return withMergeLoggingPathImpl(logLevel, logPath, enableConsole, maxFiles); } @@ -11206,6 +11391,14 @@ public class ExecuteCommandContext extends HandleWrapperBase { super(handle, client); } + /** The service provider. */ + public IServiceProvider services() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + var result = getClient().invokeCapability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.services", reqArgs); + return (IServiceProvider) result; + } + /** The resource name. */ public String resourceName() { Map reqArgs = new HashMap<>(); @@ -11322,9 +11515,9 @@ public ExternalServiceResource withContainerRegistry(ResourceBuilderBase registr } /** Configures custom base images for generated Dockerfiles. */ - public ExternalServiceResource withDockerfileBaseImage(WithDockerfileBaseImageOptions options) { - var buildImage = options == null ? null : options.getBuildImage(); - var runtimeImage = options == null ? null : options.getRuntimeImage(); + public ExternalServiceResource withDockerfileBaseImage(WithDockerfileBaseImageOptions optionsBag) { + var buildImage = optionsBag == null ? null : optionsBag.getBuildImage(); + var runtimeImage = optionsBag == null ? null : optionsBag.getRuntimeImage(); return withDockerfileBaseImageImpl(buildImage, runtimeImage); } @@ -11347,10 +11540,10 @@ private ExternalServiceResource withDockerfileBaseImageImpl(String buildImage, S } /** Adds an HTTP health check to the external service for polyglot app hosts. */ - public ExternalServiceResource withHttpHealthCheck(WithHttpHealthCheckOptions options) { - var path = options == null ? null : options.getPath(); - var statusCode = options == null ? null : options.getStatusCode(); - var endpointName = options == null ? null : options.getEndpointName(); + public ExternalServiceResource withHttpHealthCheck(WithHttpHealthCheckOptions optionsBag) { + var path = optionsBag == null ? null : optionsBag.getPath(); + var statusCode = optionsBag == null ? null : optionsBag.getStatusCode(); + var endpointName = optionsBag == null ? null : optionsBag.getEndpointName(); return withHttpHealthCheckImpl(path, statusCode, endpointName); } @@ -11643,9 +11836,9 @@ public ExternalServiceResource withHidden() { } /** Hides the resource from default resource lists after successful completion */ - public ExternalServiceResource withHiddenOnCompletion(WithHiddenOnCompletionOptions options) { - var exitCode = options == null ? null : options.getExitCode(); - var exitCodes = options == null ? null : options.getExitCodes(); + public ExternalServiceResource withHiddenOnCompletion(WithHiddenOnCompletionOptions optionsBag) { + var exitCode = optionsBag == null ? null : optionsBag.getExitCode(); + var exitCodes = optionsBag == null ? null : optionsBag.getExitCodes(); return withHiddenOnCompletionImpl(exitCode, exitCodes); } @@ -11668,11 +11861,11 @@ private ExternalServiceResource withHiddenOnCompletionImpl(Double exitCode, doub } /** Adds a pipeline step to the resource that will be executed during deployment. */ - public ExternalServiceResource withPipelineStepFactory(String stepName, AspireAction1 callback, WithPipelineStepFactoryOptions options) { - var dependsOn = options == null ? null : options.getDependsOn(); - var requiredBy = options == null ? null : options.getRequiredBy(); - var tags = options == null ? null : options.getTags(); - var description = options == null ? null : options.getDescription(); + public ExternalServiceResource withPipelineStepFactory(String stepName, AspireAction1 callback, WithPipelineStepFactoryOptions optionsBag) { + var dependsOn = optionsBag == null ? null : optionsBag.getDependsOn(); + var requiredBy = optionsBag == null ? null : optionsBag.getRequiredBy(); + var tags = optionsBag == null ? null : optionsBag.getTags(); + var description = optionsBag == null ? null : optionsBag.getDescription(); return withPipelineStepFactoryImpl(stepName, callback, dependsOn, requiredBy, tags, description); } @@ -11806,9 +11999,9 @@ public IExecutionConfigurationBuilder createExecutionConfiguration() { } /** Adds an optional string parameter */ - public ExternalServiceResource withOptionalString(WithOptionalStringOptions options) { - var value = options == null ? null : options.getValue(); - var enabled = options == null ? null : options.getEnabled(); + public ExternalServiceResource withOptionalString(WithOptionalStringOptions optionsBag) { + var value = optionsBag == null ? null : optionsBag.getValue(); + var enabled = optionsBag == null ? null : optionsBag.getEnabled(); return withOptionalStringImpl(value, enabled); } @@ -12032,9 +12225,9 @@ public ExternalServiceResource withMergeEndpointScheme(String endpointName, doub } /** Configures resource logging */ - public ExternalServiceResource withMergeLogging(String logLevel, WithMergeLoggingOptions options) { - var enableConsole = options == null ? null : options.getEnableConsole(); - var maxFiles = options == null ? null : options.getMaxFiles(); + public ExternalServiceResource withMergeLogging(String logLevel, WithMergeLoggingOptions optionsBag) { + var enableConsole = optionsBag == null ? null : optionsBag.getEnableConsole(); + var maxFiles = optionsBag == null ? null : optionsBag.getMaxFiles(); return withMergeLoggingImpl(logLevel, enableConsole, maxFiles); } @@ -12058,9 +12251,9 @@ private ExternalServiceResource withMergeLoggingImpl(String logLevel, Boolean en } /** Configures resource logging with file path */ - public ExternalServiceResource withMergeLoggingPath(String logLevel, String logPath, WithMergeLoggingPathOptions options) { - var enableConsole = options == null ? null : options.getEnableConsole(); - var maxFiles = options == null ? null : options.getMaxFiles(); + public ExternalServiceResource withMergeLoggingPath(String logLevel, String logPath, WithMergeLoggingPathOptions optionsBag) { + var enableConsole = optionsBag == null ? null : optionsBag.getEnableConsole(); + var maxFiles = optionsBag == null ? null : optionsBag.getMaxFiles(); return withMergeLoggingPathImpl(logLevel, logPath, enableConsole, maxFiles); } @@ -12349,7 +12542,7 @@ public class HttpCommandExportOptions implements JsonSerializable { private String commandName; private String endpointName; private String methodName; - private Object prepareRequest; + private AspireFunc1 prepareRequest; private HttpCommandResultMode resultMode; public CommandOptions getCommandOptions() { return commandOptions; } @@ -12370,8 +12563,8 @@ public class HttpCommandExportOptions implements JsonSerializable { public void setEndpointName(String value) { this.endpointName = value; } public String getMethodName() { return methodName; } public void setMethodName(String value) { this.methodName = value; } - public Object getPrepareRequest() { return prepareRequest; } - public void setPrepareRequest(Object value) { this.prepareRequest = value; } + public AspireFunc1 getPrepareRequest() { return prepareRequest; } + public void setPrepareRequest(AspireFunc1 value) { this.prepareRequest = value; } public HttpCommandResultMode getResultMode() { return resultMode; } public void setResultMode(HttpCommandResultMode value) { this.resultMode = value; } @@ -12396,8 +12589,6 @@ public static HttpCommandExportOptions fromMap(Map map) { value.setEndpointName(endpointNameValue == null ? null : (String) endpointNameValue); var methodNameValue = map.get("MethodName"); value.setMethodName(methodNameValue == null ? null : (String) methodNameValue); - var prepareRequestValue = map.get("PrepareRequest"); - value.setPrepareRequest(prepareRequestValue); var resultModeValue = map.get("ResultMode"); value.setResultMode(HttpCommandResultMode.fromValue((String) resultModeValue)); return value; @@ -12414,7 +12605,10 @@ public Map toMap() { map.put("CommandName", AspireClient.serializeValue(commandName)); map.put("EndpointName", AspireClient.serializeValue(endpointName)); map.put("MethodName", AspireClient.serializeValue(methodName)); - map.put("PrepareRequest", AspireClient.serializeValue(prepareRequest)); + map.put("PrepareRequest", prepareRequest == null ? null : (java.util.function.Function) (transportArg -> { + var arg = (HttpCommandPrepareRequestContext) transportArg; + return AspireClient.awaitValue(prepareRequest.invoke(arg)); + })); map.put("ResultMode", AspireClient.serializeValue(resultMode)); return map; } @@ -12954,9 +13148,9 @@ public ContainerResource addContainer(String name, AspireUnion image) { } /** Adds a Dockerfile to the application model that can be treated like a container resource. */ - public ContainerResource addDockerfile(String name, String contextPath, AddDockerfileOptions options) { - var dockerfilePath = options == null ? null : options.getDockerfilePath(); - var stage = options == null ? null : options.getStage(); + public ContainerResource addDockerfile(String name, String contextPath, AddDockerfileOptions optionsBag) { + var dockerfilePath = optionsBag == null ? null : optionsBag.getDockerfilePath(); + var stage = optionsBag == null ? null : optionsBag.getStage(); return addDockerfileImpl(name, contextPath, dockerfilePath, stage); } @@ -13126,10 +13320,10 @@ public DistributedApplication build() { } /** Adds a parameter resource */ - public ParameterResource addParameter(String name, AddParameterOptions options) { - var value = options == null ? null : options.getValue(); - var publishValueAsDefault = options == null ? null : options.getPublishValueAsDefault(); - var secret = options == null ? null : options.getSecret(); + public ParameterResource addParameter(String name, AddParameterOptions optionsBag) { + var value = optionsBag == null ? null : optionsBag.getValue(); + var publishValueAsDefault = optionsBag == null ? null : optionsBag.getPublishValueAsDefault(); + var secret = optionsBag == null ? null : optionsBag.getSecret(); return addParameterImpl(name, value, publishValueAsDefault, secret); } @@ -13173,9 +13367,9 @@ public ParameterResource addParameterFromConfiguration(String name, String confi } /** Adds a parameter with a generated default value */ - public ParameterResource addParameterWithGeneratedValue(String name, GenerateParameterDefault value, AddParameterWithGeneratedValueOptions options) { - var secret = options == null ? null : options.getSecret(); - var persist = options == null ? null : options.getPersist(); + public ParameterResource addParameterWithGeneratedValue(String name, GenerateParameterDefault value, AddParameterWithGeneratedValueOptions optionsBag) { + var secret = optionsBag == null ? null : optionsBag.getSecret(); + var persist = optionsBag == null ? null : optionsBag.getPersist(); return addParameterWithGeneratedValueImpl(name, value, secret, persist); } @@ -13471,9 +13665,9 @@ public IDistributedApplicationPipeline disableBuildOnlyContainerValidation() { } /** Adds an application-level pipeline step in a TypeScript-friendly shape. */ - public void addStep(String stepName, AspireAction1 callback, AddStepOptions options) { - var dependsOn = options == null ? null : options.getDependsOn(); - var requiredBy = options == null ? null : options.getRequiredBy(); + public void addStep(String stepName, AspireAction1 callback, AddStepOptions optionsBag) { + var dependsOn = optionsBag == null ? null : optionsBag.getDependsOn(); + var requiredBy = optionsBag == null ? null : optionsBag.getRequiredBy(); addStepImpl(stepName, callback, dependsOn, requiredBy); } @@ -13551,9 +13745,9 @@ public class IExecutionConfigurationBuilder extends HandleWrapperBase { } /** Builds the execution configuration for the specified builder. */ - public IExecutionConfigurationResult build(DistributedApplicationExecutionContext executionContext, BuildOptions options) { - var resourceLogger = options == null ? null : options.getResourceLogger(); - var cancellationToken = options == null ? null : options.getCancellationToken(); + public IExecutionConfigurationResult build(DistributedApplicationExecutionContext executionContext, BuildOptions optionsBag) { + var resourceLogger = optionsBag == null ? null : optionsBag.getResourceLogger(); + var cancellationToken = optionsBag == null ? null : optionsBag.getCancellationToken(); return buildImpl(executionContext, resourceLogger, cancellationToken); } @@ -13772,139 +13966,398 @@ public boolean isEnvironment(String environmentName) { } -// ===== ILogger.java ===== -// ILogger.java - GENERATED CODE - DO NOT EDIT +// ===== IInteractionService.java ===== +// IInteractionService.java - GENERATED CODE - DO NOT EDIT package aspire; import java.util.*; import java.util.function.*; -/** Wrapper for Microsoft.Extensions.Logging.Abstractions/Microsoft.Extensions.Logging.ILogger. */ -public class ILogger extends HandleWrapperBase { - ILogger(Handle handle, AspireClient client) { +/** Wrapper for Aspire.Hosting/Aspire.Hosting.IInteractionService. */ +public class IInteractionService extends HandleWrapperBase { + IInteractionService(Handle handle, AspireClient client) { super(handle, client); } - /** Logs an information message. */ - public void logInformation(String message) { + /** Gets a value indicating whether the interaction service is available to prompt the user. */ + public boolean isAvailable() { Map reqArgs = new HashMap<>(); - reqArgs.put("logger", AspireClient.serializeValue(getHandle())); - reqArgs.put("message", AspireClient.serializeValue(message)); - getClient().invokeCapability("Aspire.Hosting/logInformation", reqArgs); + reqArgs.put("interactionService", AspireClient.serializeValue(getHandle())); + var result = getClient().invokeCapability("Aspire.Hosting/isAvailable", reqArgs); + return (Boolean) result; } - /** Logs a warning message. */ - public void logWarning(String message) { - Map reqArgs = new HashMap<>(); - reqArgs.put("logger", AspireClient.serializeValue(getHandle())); - reqArgs.put("message", AspireClient.serializeValue(message)); - getClient().invokeCapability("Aspire.Hosting/logWarning", reqArgs); + /** Prompts the user for confirmation with an OK/Cancel dialog. */ + public BoolInteractionResult promptConfirmation(String title, String message, PromptConfirmationOptions optionsBag) { + var options = optionsBag == null ? null : optionsBag.getOptions(); + var cancellationToken = optionsBag == null ? null : optionsBag.getCancellationToken(); + return promptConfirmationImpl(title, message, options, cancellationToken); } - /** Logs an error message. */ - public void logError(String message) { - Map reqArgs = new HashMap<>(); - reqArgs.put("logger", AspireClient.serializeValue(getHandle())); - reqArgs.put("message", AspireClient.serializeValue(message)); - getClient().invokeCapability("Aspire.Hosting/logError", reqArgs); + public BoolInteractionResult promptConfirmation(String title, String message) { + return promptConfirmation(title, message, null); } - /** Logs a debug message. */ - public void logDebug(String message) { + /** Prompts the user for confirmation with an OK/Cancel dialog. */ + private BoolInteractionResult promptConfirmationImpl(String title, String message, InteractionMessageBoxOptions options, CancellationToken cancellationToken) { Map reqArgs = new HashMap<>(); - reqArgs.put("logger", AspireClient.serializeValue(getHandle())); + reqArgs.put("interactionService", AspireClient.serializeValue(getHandle())); + reqArgs.put("title", AspireClient.serializeValue(title)); reqArgs.put("message", AspireClient.serializeValue(message)); - getClient().invokeCapability("Aspire.Hosting/logDebug", reqArgs); + if (options != null) { + reqArgs.put("options", AspireClient.serializeValue(options)); + } + if (cancellationToken != null) { + reqArgs.put("cancellationToken", getClient().registerCancellation(cancellationToken)); + } + var result = getClient().invokeCapability("Aspire.Hosting/promptConfirmation", reqArgs); + return BoolInteractionResult.fromMap((Map) result); } - /** Logs a message with a specified log level. */ - public void log(String level, String message) { - Map reqArgs = new HashMap<>(); - reqArgs.put("logger", AspireClient.serializeValue(getHandle())); - reqArgs.put("level", AspireClient.serializeValue(level)); - reqArgs.put("message", AspireClient.serializeValue(message)); - getClient().invokeCapability("Aspire.Hosting/log", reqArgs); + /** Prompts the user with a message box dialog. */ + public BoolInteractionResult promptMessageBox(String title, String message, PromptMessageBoxOptions optionsBag) { + var options = optionsBag == null ? null : optionsBag.getOptions(); + var cancellationToken = optionsBag == null ? null : optionsBag.getCancellationToken(); + return promptMessageBoxImpl(title, message, options, cancellationToken); } -} - -// ===== ILoggerFactory.java ===== -// ILoggerFactory.java - GENERATED CODE - DO NOT EDIT + public BoolInteractionResult promptMessageBox(String title, String message) { + return promptMessageBox(title, message, null); + } -package aspire; + /** Prompts the user with a message box dialog. */ + private BoolInteractionResult promptMessageBoxImpl(String title, String message, InteractionMessageBoxOptions options, CancellationToken cancellationToken) { + Map reqArgs = new HashMap<>(); + reqArgs.put("interactionService", AspireClient.serializeValue(getHandle())); + reqArgs.put("title", AspireClient.serializeValue(title)); + reqArgs.put("message", AspireClient.serializeValue(message)); + if (options != null) { + reqArgs.put("options", AspireClient.serializeValue(options)); + } + if (cancellationToken != null) { + reqArgs.put("cancellationToken", getClient().registerCancellation(cancellationToken)); + } + var result = getClient().invokeCapability("Aspire.Hosting/promptMessageBox", reqArgs); + return BoolInteractionResult.fromMap((Map) result); + } -import java.util.*; -import java.util.function.*; + /** Prompts the user with a notification. */ + public BoolInteractionResult promptNotification(String title, String message, PromptNotificationOptions optionsBag) { + var options = optionsBag == null ? null : optionsBag.getOptions(); + var cancellationToken = optionsBag == null ? null : optionsBag.getCancellationToken(); + return promptNotificationImpl(title, message, options, cancellationToken); + } -/** Wrapper for Microsoft.Extensions.Logging.Abstractions/Microsoft.Extensions.Logging.ILoggerFactory. */ -public class ILoggerFactory extends HandleWrapperBase { - ILoggerFactory(Handle handle, AspireClient client) { - super(handle, client); + public BoolInteractionResult promptNotification(String title, String message) { + return promptNotification(title, message, null); } - /** Creates a logger for the specified category name. */ - public ILogger createLogger(String categoryName) { + /** Prompts the user with a notification. */ + private BoolInteractionResult promptNotificationImpl(String title, String message, InteractionNotificationOptions options, CancellationToken cancellationToken) { Map reqArgs = new HashMap<>(); - reqArgs.put("loggerFactory", AspireClient.serializeValue(getHandle())); - reqArgs.put("categoryName", AspireClient.serializeValue(categoryName)); - var result = getClient().invokeCapability("Aspire.Hosting/createLogger", reqArgs); - return (ILogger) result; + reqArgs.put("interactionService", AspireClient.serializeValue(getHandle())); + reqArgs.put("title", AspireClient.serializeValue(title)); + reqArgs.put("message", AspireClient.serializeValue(message)); + if (options != null) { + reqArgs.put("options", AspireClient.serializeValue(options)); + } + if (cancellationToken != null) { + reqArgs.put("cancellationToken", getClient().registerCancellation(cancellationToken)); + } + var result = getClient().invokeCapability("Aspire.Hosting/promptNotification", reqArgs); + return BoolInteractionResult.fromMap((Map) result); } -} - -// ===== IReportingStep.java ===== -// IReportingStep.java - GENERATED CODE - DO NOT EDIT - -package aspire; + /** Prompts the user for a single input. */ + public InputInteractionResult promptInput(String title, String message, InteractionInputBuilder input, PromptInputOptions optionsBag) { + var options = optionsBag == null ? null : optionsBag.getOptions(); + var cancellationToken = optionsBag == null ? null : optionsBag.getCancellationToken(); + return promptInputImpl(title, message, input, options, cancellationToken); + } -import java.util.*; -import java.util.function.*; + public InputInteractionResult promptInput(String title, String message, HandleWrapperBase input, PromptInputOptions options) { + return promptInput(title, message, new InteractionInputBuilder(input.getHandle(), input.getClient()), options); + } -/** Wrapper for Aspire.Hosting/Aspire.Hosting.Pipelines.IReportingStep. */ -public class IReportingStep extends HandleWrapperBase { - IReportingStep(Handle handle, AspireClient client) { - super(handle, client); + public InputInteractionResult promptInput(String title, String message, InteractionInputBuilder input) { + return promptInput(title, message, input, null); } - public IReportingTask createTask(String statusText) { - return createTask(statusText, null); + public InputInteractionResult promptInput(String title, String message, HandleWrapperBase input) { + return promptInput(title, message, new InteractionInputBuilder(input.getHandle(), input.getClient())); } - /** Creates a reporting task with plain-text status text. */ - public IReportingTask createTask(String statusText, CancellationToken cancellationToken) { + /** Prompts the user for a single input. */ + private InputInteractionResult promptInputImpl(String title, String message, InteractionInputBuilder input, InteractionInputsDialogOptions options, CancellationToken cancellationToken) { Map reqArgs = new HashMap<>(); - reqArgs.put("reportingStep", AspireClient.serializeValue(getHandle())); - reqArgs.put("statusText", AspireClient.serializeValue(statusText)); + reqArgs.put("interactionService", AspireClient.serializeValue(getHandle())); + reqArgs.put("title", AspireClient.serializeValue(title)); + reqArgs.put("message", AspireClient.serializeValue(message)); + reqArgs.put("input", AspireClient.serializeValue(input)); + if (options != null) { + reqArgs.put("options", AspireClient.serializeValue(options)); + } if (cancellationToken != null) { reqArgs.put("cancellationToken", getClient().registerCancellation(cancellationToken)); } - var result = getClient().invokeCapability("Aspire.Hosting/createTask", reqArgs); - return (IReportingTask) result; + var result = getClient().invokeCapability("Aspire.Hosting/promptInput", reqArgs); + return InputInteractionResult.fromMap((Map) result); } - public IReportingTask createMarkdownTask(String markdownString) { - return createMarkdownTask(markdownString, null); + /** Prompts the user for multiple inputs. */ + public InputsInteractionResult promptInputs(String title, String message, InteractionInputBuilder[] inputs, PromptInputsOptions optionsBag) { + var options = optionsBag == null ? null : optionsBag.getOptions(); + var cancellationToken = optionsBag == null ? null : optionsBag.getCancellationToken(); + return promptInputsImpl(title, message, inputs, options, cancellationToken); } - /** Creates a reporting task with Markdown-formatted status text. */ - public IReportingTask createMarkdownTask(String markdownString, CancellationToken cancellationToken) { + public InputsInteractionResult promptInputs(String title, String message, InteractionInputBuilder[] inputs) { + return promptInputs(title, message, inputs, null); + } + + /** Prompts the user for multiple inputs. */ + private InputsInteractionResult promptInputsImpl(String title, String message, InteractionInputBuilder[] inputs, InteractionInputsDialogOptions options, CancellationToken cancellationToken) { Map reqArgs = new HashMap<>(); - reqArgs.put("reportingStep", AspireClient.serializeValue(getHandle())); - reqArgs.put("markdownString", AspireClient.serializeValue(markdownString)); + reqArgs.put("interactionService", AspireClient.serializeValue(getHandle())); + reqArgs.put("title", AspireClient.serializeValue(title)); + reqArgs.put("message", AspireClient.serializeValue(message)); + reqArgs.put("inputs", AspireClient.serializeValue(inputs)); + if (options != null) { + reqArgs.put("options", AspireClient.serializeValue(options)); + } if (cancellationToken != null) { reqArgs.put("cancellationToken", getClient().registerCancellation(cancellationToken)); } - var result = getClient().invokeCapability("Aspire.Hosting/createMarkdownTask", reqArgs); - return (IReportingTask) result; + var result = getClient().invokeCapability("Aspire.Hosting/promptInputs", reqArgs); + return (InputsInteractionResult) result; } - /** Logs a plain-text message for the reporting step. */ - public void logStep(String level, String message) { + public InteractionInputBuilder createTextInput(String name) { + return createTextInput(name, null); + } + + /** Creates a single-line text input. */ + public InteractionInputBuilder createTextInput(String name, CreateInteractionInputOptions options) { Map reqArgs = new HashMap<>(); - reqArgs.put("reportingStep", AspireClient.serializeValue(getHandle())); - reqArgs.put("level", AspireClient.serializeValue(level)); + reqArgs.put("interactionService", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + if (options != null) { + reqArgs.put("options", AspireClient.serializeValue(options)); + } + var result = getClient().invokeCapability("Aspire.Hosting/createTextInput", reqArgs); + return (InteractionInputBuilder) result; + } + + public InteractionInputBuilder createSecretInput(String name) { + return createSecretInput(name, null); + } + + /** Creates a secret (masked) text input. */ + public InteractionInputBuilder createSecretInput(String name, CreateInteractionInputOptions options) { + Map reqArgs = new HashMap<>(); + reqArgs.put("interactionService", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + if (options != null) { + reqArgs.put("options", AspireClient.serializeValue(options)); + } + var result = getClient().invokeCapability("Aspire.Hosting/createSecretInput", reqArgs); + return (InteractionInputBuilder) result; + } + + public InteractionInputBuilder createBooleanInput(String name) { + return createBooleanInput(name, null); + } + + /** Creates a boolean (checkbox) input. */ + public InteractionInputBuilder createBooleanInput(String name, CreateInteractionInputOptions options) { + Map reqArgs = new HashMap<>(); + reqArgs.put("interactionService", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + if (options != null) { + reqArgs.put("options", AspireClient.serializeValue(options)); + } + var result = getClient().invokeCapability("Aspire.Hosting/createBooleanInput", reqArgs); + return (InteractionInputBuilder) result; + } + + public InteractionInputBuilder createNumberInput(String name) { + return createNumberInput(name, null); + } + + /** Creates a numeric input. */ + public InteractionInputBuilder createNumberInput(String name, CreateInteractionInputOptions options) { + Map reqArgs = new HashMap<>(); + reqArgs.put("interactionService", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + if (options != null) { + reqArgs.put("options", AspireClient.serializeValue(options)); + } + var result = getClient().invokeCapability("Aspire.Hosting/createNumberInput", reqArgs); + return (InteractionInputBuilder) result; + } + + /** Creates a choice input that selects from a list of options. */ + public InteractionInputBuilder createChoiceInput(String name, CreateChoiceInputOptions optionsBag) { + var choices = optionsBag == null ? null : optionsBag.getChoices(); + var options = optionsBag == null ? null : optionsBag.getOptions(); + return createChoiceInputImpl(name, choices, options); + } + + public InteractionInputBuilder createChoiceInput(String name) { + return createChoiceInput(name, null); + } + + /** Creates a choice input that selects from a list of options. */ + private InteractionInputBuilder createChoiceInputImpl(String name, InteractionChoiceOption[] choices, CreateInteractionInputOptions options) { + Map reqArgs = new HashMap<>(); + reqArgs.put("interactionService", AspireClient.serializeValue(getHandle())); + reqArgs.put("name", AspireClient.serializeValue(name)); + if (choices != null) { + reqArgs.put("choices", AspireClient.serializeValue(choices)); + } + if (options != null) { + reqArgs.put("options", AspireClient.serializeValue(options)); + } + var result = getClient().invokeCapability("Aspire.Hosting/createChoiceInput", reqArgs); + return (InteractionInputBuilder) result; + } + +} + +// ===== ILogger.java ===== +// ILogger.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** Wrapper for Microsoft.Extensions.Logging.Abstractions/Microsoft.Extensions.Logging.ILogger. */ +public class ILogger extends HandleWrapperBase { + ILogger(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Logs an information message. */ + public void logInformation(String message) { + Map reqArgs = new HashMap<>(); + reqArgs.put("logger", AspireClient.serializeValue(getHandle())); + reqArgs.put("message", AspireClient.serializeValue(message)); + getClient().invokeCapability("Aspire.Hosting/logInformation", reqArgs); + } + + /** Logs a warning message. */ + public void logWarning(String message) { + Map reqArgs = new HashMap<>(); + reqArgs.put("logger", AspireClient.serializeValue(getHandle())); + reqArgs.put("message", AspireClient.serializeValue(message)); + getClient().invokeCapability("Aspire.Hosting/logWarning", reqArgs); + } + + /** Logs an error message. */ + public void logError(String message) { + Map reqArgs = new HashMap<>(); + reqArgs.put("logger", AspireClient.serializeValue(getHandle())); + reqArgs.put("message", AspireClient.serializeValue(message)); + getClient().invokeCapability("Aspire.Hosting/logError", reqArgs); + } + + /** Logs a debug message. */ + public void logDebug(String message) { + Map reqArgs = new HashMap<>(); + reqArgs.put("logger", AspireClient.serializeValue(getHandle())); + reqArgs.put("message", AspireClient.serializeValue(message)); + getClient().invokeCapability("Aspire.Hosting/logDebug", reqArgs); + } + + /** Logs a message with a specified log level. */ + public void log(String level, String message) { + Map reqArgs = new HashMap<>(); + reqArgs.put("logger", AspireClient.serializeValue(getHandle())); + reqArgs.put("level", AspireClient.serializeValue(level)); + reqArgs.put("message", AspireClient.serializeValue(message)); + getClient().invokeCapability("Aspire.Hosting/log", reqArgs); + } + +} + +// ===== ILoggerFactory.java ===== +// ILoggerFactory.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** Wrapper for Microsoft.Extensions.Logging.Abstractions/Microsoft.Extensions.Logging.ILoggerFactory. */ +public class ILoggerFactory extends HandleWrapperBase { + ILoggerFactory(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Creates a logger for the specified category name. */ + public ILogger createLogger(String categoryName) { + Map reqArgs = new HashMap<>(); + reqArgs.put("loggerFactory", AspireClient.serializeValue(getHandle())); + reqArgs.put("categoryName", AspireClient.serializeValue(categoryName)); + var result = getClient().invokeCapability("Aspire.Hosting/createLogger", reqArgs); + return (ILogger) result; + } + +} + +// ===== IReportingStep.java ===== +// IReportingStep.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.Pipelines.IReportingStep. */ +public class IReportingStep extends HandleWrapperBase { + IReportingStep(Handle handle, AspireClient client) { + super(handle, client); + } + + public IReportingTask createTask(String statusText) { + return createTask(statusText, null); + } + + /** Creates a reporting task with plain-text status text. */ + public IReportingTask createTask(String statusText, CancellationToken cancellationToken) { + Map reqArgs = new HashMap<>(); + reqArgs.put("reportingStep", AspireClient.serializeValue(getHandle())); + reqArgs.put("statusText", AspireClient.serializeValue(statusText)); + if (cancellationToken != null) { + reqArgs.put("cancellationToken", getClient().registerCancellation(cancellationToken)); + } + var result = getClient().invokeCapability("Aspire.Hosting/createTask", reqArgs); + return (IReportingTask) result; + } + + public IReportingTask createMarkdownTask(String markdownString) { + return createMarkdownTask(markdownString, null); + } + + /** Creates a reporting task with Markdown-formatted status text. */ + public IReportingTask createMarkdownTask(String markdownString, CancellationToken cancellationToken) { + Map reqArgs = new HashMap<>(); + reqArgs.put("reportingStep", AspireClient.serializeValue(getHandle())); + reqArgs.put("markdownString", AspireClient.serializeValue(markdownString)); + if (cancellationToken != null) { + reqArgs.put("cancellationToken", getClient().registerCancellation(cancellationToken)); + } + var result = getClient().invokeCapability("Aspire.Hosting/createMarkdownTask", reqArgs); + return (IReportingTask) result; + } + + /** Logs a plain-text message for the reporting step. */ + public void logStep(String level, String message) { + Map reqArgs = new HashMap<>(); + reqArgs.put("reportingStep", AspireClient.serializeValue(getHandle())); + reqArgs.put("level", AspireClient.serializeValue(level)); reqArgs.put("message", AspireClient.serializeValue(message)); getClient().invokeCapability("Aspire.Hosting/logStep", reqArgs); } @@ -13919,9 +14372,9 @@ public void logStepMarkdown(String level, String markdownString) { } /** Completes the reporting step with plain-text completion text. */ - public void completeStep(String completionText, CompleteStepOptions options) { - var completionState = options == null ? null : options.getCompletionState(); - var cancellationToken = options == null ? null : options.getCancellationToken(); + public void completeStep(String completionText, CompleteStepOptions optionsBag) { + var completionState = optionsBag == null ? null : optionsBag.getCompletionState(); + var cancellationToken = optionsBag == null ? null : optionsBag.getCancellationToken(); completeStepImpl(completionText, completionState, cancellationToken); } @@ -13944,9 +14397,9 @@ private void completeStepImpl(String completionText, String completionState, Can } /** Completes the reporting step with Markdown-formatted completion text. */ - public void completeStepMarkdown(String markdownString, CompleteStepMarkdownOptions options) { - var completionState = options == null ? null : options.getCompletionState(); - var cancellationToken = options == null ? null : options.getCancellationToken(); + public void completeStepMarkdown(String markdownString, CompleteStepMarkdownOptions optionsBag) { + var completionState = optionsBag == null ? null : optionsBag.getCompletionState(); + var cancellationToken = optionsBag == null ? null : optionsBag.getCancellationToken(); completeStepMarkdownImpl(markdownString, completionState, cancellationToken); } @@ -14015,10 +14468,10 @@ public void updateTaskMarkdown(String markdownString, CancellationToken cancella } /** Completes the reporting task with plain-text completion text. */ - public void completeTask(CompleteTaskOptions options) { - var completionMessage = options == null ? null : options.getCompletionMessage(); - var completionState = options == null ? null : options.getCompletionState(); - var cancellationToken = options == null ? null : options.getCancellationToken(); + public void completeTask(CompleteTaskOptions optionsBag) { + var completionMessage = optionsBag == null ? null : optionsBag.getCompletionMessage(); + var completionState = optionsBag == null ? null : optionsBag.getCompletionState(); + var cancellationToken = optionsBag == null ? null : optionsBag.getCancellationToken(); completeTaskImpl(completionMessage, completionState, cancellationToken); } @@ -14043,9 +14496,9 @@ private void completeTaskImpl(String completionMessage, String completionState, } /** Completes the reporting task with Markdown-formatted completion text. */ - public void completeTaskMarkdown(String markdownString, CompleteTaskMarkdownOptions options) { - var completionState = options == null ? null : options.getCompletionState(); - var cancellationToken = options == null ? null : options.getCancellationToken(); + public void completeTaskMarkdown(String markdownString, CompleteTaskMarkdownOptions optionsBag) { + var completionState = optionsBag == null ? null : optionsBag.getCompletionState(); + var cancellationToken = optionsBag == null ? null : optionsBag.getCancellationToken(); completeTaskMarkdownImpl(markdownString, completionState, cancellationToken); } @@ -14244,6 +14697,14 @@ public IDistributedApplicationEventing getEventing() { return (IDistributedApplicationEventing) result; } + /** Gets the interaction service from the service provider. */ + public IInteractionService getInteractionService() { + Map reqArgs = new HashMap<>(); + reqArgs.put("serviceProvider", AspireClient.serializeValue(getHandle())); + var result = getClient().invokeCapability("Aspire.Hosting/getInteractionService", reqArgs); + return (IInteractionService) result; + } + /** Gets the logger factory from the service provider. */ public ILoggerFactory getLoggerFactory() { Map reqArgs = new HashMap<>(); @@ -14506,203 +14967,654 @@ public IServiceProvider services() { } -// ===== InputType.java ===== -// InputType.java - GENERATED CODE - DO NOT EDIT +// ===== InputInteractionResult.java ===== +// InputInteractionResult.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** InputInteractionResult DTO. */ +public class InputInteractionResult implements JsonSerializable { + private boolean canceled; + private InteractionInput input; + + public boolean getCanceled() { return canceled; } + public void setCanceled(boolean value) { this.canceled = value; } + public InteractionInput getInput() { return input; } + public void setInput(InteractionInput value) { this.input = value; } + + @SuppressWarnings("unchecked") + public static InputInteractionResult fromMap(Map map) { + var value = new InputInteractionResult(); + var canceledValue = map.get("Canceled"); + value.setCanceled((Boolean) canceledValue); + var inputValue = map.get("Input"); + value.setInput(inputValue == null ? null : InteractionInput.fromMap((Map) inputValue)); + return value; + } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("Canceled", AspireClient.serializeValue(canceled)); + map.put("Input", AspireClient.serializeValue(input)); + return map; + } +} + +// ===== InputType.java ===== +// InputType.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** InputType enum. */ +public enum InputType implements WireValueEnum { + TEXT("Text"), + SECRET_TEXT("SecretText"), + CHOICE("Choice"), + BOOLEAN("Boolean"), + NUMBER("Number"); + + private final String value; + + InputType(String value) { + this.value = value; + } + + public String getValue() { return value; } + + public static InputType fromValue(String value) { + for (InputType e : values()) { + if (e.value.equals(value)) return e; + } + throw new IllegalArgumentException("Unknown value: " + value); + } +} + +// ===== InputsDialogValidationContext.java ===== +// InputsDialogValidationContext.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.InputsDialogValidationContext. */ +public class InputsDialogValidationContext extends HandleWrapperBase { + InputsDialogValidationContext(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets the inputs that are being validated. */ + public InteractionInputCollection inputs() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + var result = getClient().invokeCapability("Aspire.Hosting/InputsDialogValidationContext.inputs", reqArgs); + return (InteractionInputCollection) result; + } + + /** Gets the cancellation token for the validation operation. */ + public CancellationToken cancellationToken() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + var result = getClient().invokeCapability("Aspire.Hosting/InputsDialogValidationContext.cancellationToken", reqArgs); + return (CancellationToken) result; + } + + /** Adds a validation error for the input with the specified name. */ + public void addValidationError(String inputName, String errorMessage) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("inputName", AspireClient.serializeValue(inputName)); + reqArgs.put("errorMessage", AspireClient.serializeValue(errorMessage)); + getClient().invokeCapability("Aspire.Hosting/InputsDialogValidationContext.addValidationError", reqArgs); + } + +} + +// ===== InputsInteractionResult.java ===== +// InputsInteractionResult.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.Ats.InputsInteractionResult. */ +public class InputsInteractionResult extends HandleWrapperBase { + InputsInteractionResult(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets a value indicating whether the interaction was canceled by the user. */ + public boolean canceled() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + var result = getClient().invokeCapability("Aspire.Hosting.Ats/InputsInteractionResult.canceled", reqArgs); + return (Boolean) result; + } + + /** Gets the inputs returned from the interaction. Empty when `Canceled` is `true`. */ + public InteractionInputCollection inputs() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + var result = getClient().invokeCapability("Aspire.Hosting.Ats/InputsInteractionResult.inputs", reqArgs); + return (InteractionInputCollection) result; + } + +} + +// ===== InteractionChoiceOption.java ===== +// InteractionChoiceOption.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** InteractionChoiceOption DTO. */ +public class InteractionChoiceOption implements JsonSerializable { + private String value; + private String label; + + public String getValue() { return value; } + public void setValue(String value) { this.value = value; } + public String getLabel() { return label; } + public void setLabel(String value) { this.label = value; } + + @SuppressWarnings("unchecked") + public static InteractionChoiceOption fromMap(Map map) { + var value = new InteractionChoiceOption(); + var valueValue = map.get("Value"); + value.setValue((String) valueValue); + var labelValue = map.get("Label"); + value.setLabel((String) labelValue); + return value; + } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("Value", AspireClient.serializeValue(value)); + map.put("Label", AspireClient.serializeValue(label)); + return map; + } +} + +// ===== InteractionInput.java ===== +// InteractionInput.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** InteractionInput DTO. */ +public class InteractionInput implements JsonSerializable { + private String name; + private String label; + private String description; + private Boolean enableDescriptionMarkdown; + private InputType inputType; + private Boolean required; + private Object[] options; + private String value; + private String placeholder; + private Boolean allowCustomChoice; + private boolean disabled; + private Double maxLength; + + public String getName() { return name; } + public void setName(String value) { this.name = value; } + public String getLabel() { return label; } + public void setLabel(String value) { this.label = value; } + public String getDescription() { return description; } + public void setDescription(String value) { this.description = value; } + public Boolean getEnableDescriptionMarkdown() { return enableDescriptionMarkdown; } + public void setEnableDescriptionMarkdown(Boolean value) { this.enableDescriptionMarkdown = value; } + public InputType getInputType() { return inputType; } + public void setInputType(InputType value) { this.inputType = value; } + public Boolean getRequired() { return required; } + public void setRequired(Boolean value) { this.required = value; } + public Object[] getOptions() { return options; } + public void setOptions(Object[] value) { this.options = value; } + public String getValue() { return value; } + public void setValue(String value) { this.value = value; } + public String getPlaceholder() { return placeholder; } + public void setPlaceholder(String value) { this.placeholder = value; } + public Boolean getAllowCustomChoice() { return allowCustomChoice; } + public void setAllowCustomChoice(Boolean value) { this.allowCustomChoice = value; } + public boolean getDisabled() { return disabled; } + public void setDisabled(boolean value) { this.disabled = value; } + public Double getMaxLength() { return maxLength; } + public void setMaxLength(Double value) { this.maxLength = value; } + + @SuppressWarnings("unchecked") + public static InteractionInput fromMap(Map map) { + var value = new InteractionInput(); + var nameValue = map.get("Name"); + value.setName((String) nameValue); + var labelValue = map.get("Label"); + value.setLabel(labelValue == null ? null : (String) labelValue); + var descriptionValue = map.get("Description"); + value.setDescription(descriptionValue == null ? null : (String) descriptionValue); + var enableDescriptionMarkdownValue = map.get("EnableDescriptionMarkdown"); + value.setEnableDescriptionMarkdown(enableDescriptionMarkdownValue == null ? null : (Boolean) enableDescriptionMarkdownValue); + var inputTypeValue = map.get("InputType"); + value.setInputType(InputType.fromValue((String) inputTypeValue)); + var requiredValue = map.get("Required"); + value.setRequired(requiredValue == null ? null : (Boolean) requiredValue); + var optionsValue = map.get("Options"); + value.setOptions((Object[]) optionsValue); + var valueValue = map.get("Value"); + value.setValue(valueValue == null ? null : (String) valueValue); + var placeholderValue = map.get("Placeholder"); + value.setPlaceholder(placeholderValue == null ? null : (String) placeholderValue); + var allowCustomChoiceValue = map.get("AllowCustomChoice"); + value.setAllowCustomChoice(allowCustomChoiceValue == null ? null : (Boolean) allowCustomChoiceValue); + var disabledValue = map.get("Disabled"); + value.setDisabled((Boolean) disabledValue); + var maxLengthValue = map.get("MaxLength"); + value.setMaxLength(maxLengthValue == null ? null : ((Number) maxLengthValue).doubleValue()); + return value; + } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("Name", AspireClient.serializeValue(name)); + map.put("Label", AspireClient.serializeValue(label)); + map.put("Description", AspireClient.serializeValue(description)); + map.put("EnableDescriptionMarkdown", AspireClient.serializeValue(enableDescriptionMarkdown)); + map.put("InputType", AspireClient.serializeValue(inputType)); + map.put("Required", AspireClient.serializeValue(required)); + map.put("Options", AspireClient.serializeValue(options)); + map.put("Value", AspireClient.serializeValue(value)); + map.put("Placeholder", AspireClient.serializeValue(placeholder)); + map.put("AllowCustomChoice", AspireClient.serializeValue(allowCustomChoice)); + map.put("Disabled", AspireClient.serializeValue(disabled)); + map.put("MaxLength", AspireClient.serializeValue(maxLength)); + return map; + } +} + +// ===== InteractionInputBuilder.java ===== +// InteractionInputBuilder.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder. */ +public class InteractionInputBuilder extends HandleWrapperBase { + InteractionInputBuilder(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Sets the choice options for the input. */ + public InteractionInputBuilder withChoiceOptions(InteractionChoiceOption[] choices) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("choices", AspireClient.serializeValue(choices)); + var result = getClient().invokeCapability("Aspire.Hosting.Ats/withChoiceOptions", reqArgs); + return (InteractionInputBuilder) result; + } + + /** Sets the value of the input. */ + public InteractionInputBuilder withValue(String value) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + reqArgs.put("value", AspireClient.serializeValue(value)); + var result = getClient().invokeCapability("Aspire.Hosting.Ats/withValue", reqArgs); + return (InteractionInputBuilder) result; + } + + public InteractionInputBuilder withDynamicLoading(AspireAction1 callback) { + return withDynamicLoading(callback, null); + } + + /** Attaches a callback that dynamically loads or updates the input after the prompt starts. */ + public InteractionInputBuilder withDynamicLoading(AspireAction1 callback, DynamicLoadingOptions options) { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + var callbackId = getClient().registerCallback(args -> { + var arg = (InteractionInputLoadContext) args[0]; + callback.invoke(arg); + return null; + }); + if (callbackId != null) { + reqArgs.put("callback", callbackId); + } + if (options != null) { + reqArgs.put("options", AspireClient.serializeValue(options)); + } + var result = getClient().invokeCapability("Aspire.Hosting.Ats/withDynamicLoading", reqArgs); + return (InteractionInputBuilder) result; + } + +} + +// ===== InteractionInputCollection.java ===== +// InteractionInputCollection.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.InteractionInputCollection. */ +public class InteractionInputCollection extends HandleWrapperBase { + InteractionInputCollection(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets all inputs in declaration order. */ + public InteractionInput[] toArray() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + var result = getClient().invokeCapability("Aspire.Hosting/InteractionInputCollection.toArray", reqArgs); + return (InteractionInput[]) result; + } + + /** Gets the input with the specified name, or null if no input matches. */ + public InteractionInput get(String name) { + for (var input : toArray()) { + if (input.getName() != null && input.getName().equalsIgnoreCase(name)) { + return input; + } + } + return null; + } + + /** Gets the input with the specified name, or throws if no input matches. */ + public InteractionInput required(String name) { + var input = get(name); + if (input == null) { + throw new IllegalArgumentException("no input with name '" + name + "' was found"); + } + return input; + } + + /** Gets the value of the input with the specified name, or an empty string if no input matches or it has no value. */ + public String value(String name) { + var input = get(name); + return input == null || input.getValue() == null ? "" : input.getValue(); + } + + /** Gets the value of the input with the specified name, or throws if no input matches. */ + public String requiredValue(String name) { + return required(name).getValue(); + } + +} + +// ===== InteractionInputLoadContext.java ===== +// InteractionInputLoadContext.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** Wrapper for Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputLoadContext. */ +public class InteractionInputLoadContext extends HandleWrapperBase { + InteractionInputLoadContext(Handle handle, AspireClient client) { + super(handle, client); + } + + /** Gets all inputs in the prompt, including the one currently loading. */ + public InteractionInputCollection inputs() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + var result = getClient().invokeCapability("Aspire.Hosting.Ats/InteractionInputLoadContext.inputs", reqArgs); + return (InteractionInputCollection) result; + } + + /** Gets a handle to the input that is loading. Mutate the input through this handle. */ + public InteractionLoadingInput input() { + Map reqArgs = new HashMap<>(); + reqArgs.put("context", AspireClient.serializeValue(getHandle())); + var result = getClient().invokeCapability("Aspire.Hosting.Ats/input", reqArgs); + return (InteractionLoadingInput) result; + } + +} + +// ===== InteractionInputsDialogOptions.java ===== +// InteractionInputsDialogOptions.java - GENERATED CODE - DO NOT EDIT package aspire; import java.util.*; import java.util.function.*; -/** InputType enum. */ -public enum InputType implements WireValueEnum { - TEXT("Text"), - SECRET_TEXT("SecretText"), - CHOICE("Choice"), - BOOLEAN("Boolean"), - NUMBER("Number"); - - private final String value; +/** InteractionInputsDialogOptions DTO. */ +public class InteractionInputsDialogOptions implements JsonSerializable { + private String primaryButtonText; + private String secondaryButtonText; + private Boolean showSecondaryButton; + private Boolean showDismiss; + private Boolean enableMessageMarkdown; + private AspireAction1 validationCallback; + + public String getPrimaryButtonText() { return primaryButtonText; } + public void setPrimaryButtonText(String value) { this.primaryButtonText = value; } + public String getSecondaryButtonText() { return secondaryButtonText; } + public void setSecondaryButtonText(String value) { this.secondaryButtonText = value; } + public Boolean getShowSecondaryButton() { return showSecondaryButton; } + public void setShowSecondaryButton(Boolean value) { this.showSecondaryButton = value; } + public Boolean getShowDismiss() { return showDismiss; } + public void setShowDismiss(Boolean value) { this.showDismiss = value; } + public Boolean getEnableMessageMarkdown() { return enableMessageMarkdown; } + public void setEnableMessageMarkdown(Boolean value) { this.enableMessageMarkdown = value; } + public AspireAction1 getValidationCallback() { return validationCallback; } + public void setValidationCallback(AspireAction1 value) { this.validationCallback = value; } - InputType(String value) { - this.value = value; + @SuppressWarnings("unchecked") + public static InteractionInputsDialogOptions fromMap(Map map) { + var value = new InteractionInputsDialogOptions(); + var primaryButtonTextValue = map.get("PrimaryButtonText"); + value.setPrimaryButtonText(primaryButtonTextValue == null ? null : (String) primaryButtonTextValue); + var secondaryButtonTextValue = map.get("SecondaryButtonText"); + value.setSecondaryButtonText(secondaryButtonTextValue == null ? null : (String) secondaryButtonTextValue); + var showSecondaryButtonValue = map.get("ShowSecondaryButton"); + value.setShowSecondaryButton(showSecondaryButtonValue == null ? null : (Boolean) showSecondaryButtonValue); + var showDismissValue = map.get("ShowDismiss"); + value.setShowDismiss(showDismissValue == null ? null : (Boolean) showDismissValue); + var enableMessageMarkdownValue = map.get("EnableMessageMarkdown"); + value.setEnableMessageMarkdown(enableMessageMarkdownValue == null ? null : (Boolean) enableMessageMarkdownValue); + return value; } - public String getValue() { return value; } - - public static InputType fromValue(String value) { - for (InputType e : values()) { - if (e.value.equals(value)) return e; - } - throw new IllegalArgumentException("Unknown value: " + value); + public Map toMap() { + Map map = new HashMap<>(); + map.put("PrimaryButtonText", AspireClient.serializeValue(primaryButtonText)); + map.put("SecondaryButtonText", AspireClient.serializeValue(secondaryButtonText)); + map.put("ShowSecondaryButton", AspireClient.serializeValue(showSecondaryButton)); + map.put("ShowDismiss", AspireClient.serializeValue(showDismiss)); + map.put("EnableMessageMarkdown", AspireClient.serializeValue(enableMessageMarkdown)); + map.put("ValidationCallback", validationCallback == null ? null : (java.util.function.Function) (transportArg -> { + var arg = (InputsDialogValidationContext) transportArg; + validationCallback.invoke(arg); + return null; + })); + return map; } } -// ===== InputsDialogValidationContext.java ===== -// InputsDialogValidationContext.java - GENERATED CODE - DO NOT EDIT +// ===== InteractionLoadingInput.java ===== +// InteractionLoadingInput.java - GENERATED CODE - DO NOT EDIT package aspire; import java.util.*; import java.util.function.*; -/** Wrapper for Aspire.Hosting/Aspire.Hosting.InputsDialogValidationContext. */ -public class InputsDialogValidationContext extends HandleWrapperBase { - InputsDialogValidationContext(Handle handle, AspireClient client) { +/** Wrapper for Aspire.Hosting/Aspire.Hosting.Ats.InteractionLoadingInput. */ +public class InteractionLoadingInput extends HandleWrapperBase { + InteractionLoadingInput(Handle handle, AspireClient client) { super(handle, client); } - /** Gets the inputs that are being validated. */ - public InteractionInputCollection inputs() { + /** Gets the name of the input. */ + public String getName() { Map reqArgs = new HashMap<>(); reqArgs.put("context", AspireClient.serializeValue(getHandle())); - var result = getClient().invokeCapability("Aspire.Hosting/InputsDialogValidationContext.inputs", reqArgs); - return (InteractionInputCollection) result; + var result = getClient().invokeCapability("Aspire.Hosting.Ats/getName", reqArgs); + return (String) result; } - /** Gets the cancellation token for the validation operation. */ - public CancellationToken cancellationToken() { + /** Sets the choice options for the input. */ + public void setChoiceOptions(InteractionChoiceOption[] choices) { Map reqArgs = new HashMap<>(); reqArgs.put("context", AspireClient.serializeValue(getHandle())); - var result = getClient().invokeCapability("Aspire.Hosting/InputsDialogValidationContext.cancellationToken", reqArgs); - return (CancellationToken) result; + reqArgs.put("choices", AspireClient.serializeValue(choices)); + getClient().invokeCapability("Aspire.Hosting.Ats/setChoiceOptions", reqArgs); } - /** Adds a validation error for the input with the specified name. */ - public void addValidationError(String inputName, String errorMessage) { + /** Sets the value of the input. */ + public void setValue(String value) { Map reqArgs = new HashMap<>(); reqArgs.put("context", AspireClient.serializeValue(getHandle())); - reqArgs.put("inputName", AspireClient.serializeValue(inputName)); - reqArgs.put("errorMessage", AspireClient.serializeValue(errorMessage)); - getClient().invokeCapability("Aspire.Hosting/InputsDialogValidationContext.addValidationError", reqArgs); + reqArgs.put("value", AspireClient.serializeValue(value)); + getClient().invokeCapability("Aspire.Hosting.Ats/setValue", reqArgs); } } -// ===== InteractionInput.java ===== -// InteractionInput.java - GENERATED CODE - DO NOT EDIT +// ===== InteractionMessageBoxOptions.java ===== +// InteractionMessageBoxOptions.java - GENERATED CODE - DO NOT EDIT package aspire; import java.util.*; import java.util.function.*; -/** InteractionInput DTO. */ -public class InteractionInput implements JsonSerializable { - private String name; - private String label; - private String description; - private Boolean enableDescriptionMarkdown; - private InputType inputType; - private Boolean required; - private Object[] options; - private Object dynamicLoading; - private String value; - private String placeholder; - private Boolean allowCustomChoice; - private boolean disabled; - private Double maxLength; - - public String getName() { return name; } - public void setName(String value) { this.name = value; } - public String getLabel() { return label; } - public void setLabel(String value) { this.label = value; } - public String getDescription() { return description; } - public void setDescription(String value) { this.description = value; } - public Boolean getEnableDescriptionMarkdown() { return enableDescriptionMarkdown; } - public void setEnableDescriptionMarkdown(Boolean value) { this.enableDescriptionMarkdown = value; } - public InputType getInputType() { return inputType; } - public void setInputType(InputType value) { this.inputType = value; } - public Boolean getRequired() { return required; } - public void setRequired(Boolean value) { this.required = value; } - public Object[] getOptions() { return options; } - public void setOptions(Object[] value) { this.options = value; } - public Object getDynamicLoading() { return dynamicLoading; } - public void setDynamicLoading(Object value) { this.dynamicLoading = value; } - public String getValue() { return value; } - public void setValue(String value) { this.value = value; } - public String getPlaceholder() { return placeholder; } - public void setPlaceholder(String value) { this.placeholder = value; } - public Boolean getAllowCustomChoice() { return allowCustomChoice; } - public void setAllowCustomChoice(Boolean value) { this.allowCustomChoice = value; } - public boolean getDisabled() { return disabled; } - public void setDisabled(boolean value) { this.disabled = value; } - public Double getMaxLength() { return maxLength; } - public void setMaxLength(Double value) { this.maxLength = value; } +/** InteractionMessageBoxOptions DTO. */ +public class InteractionMessageBoxOptions implements JsonSerializable { + private String primaryButtonText; + private String secondaryButtonText; + private Boolean showSecondaryButton; + private Boolean showDismiss; + private Boolean enableMessageMarkdown; + private MessageIntent intent; + + public String getPrimaryButtonText() { return primaryButtonText; } + public void setPrimaryButtonText(String value) { this.primaryButtonText = value; } + public String getSecondaryButtonText() { return secondaryButtonText; } + public void setSecondaryButtonText(String value) { this.secondaryButtonText = value; } + public Boolean getShowSecondaryButton() { return showSecondaryButton; } + public void setShowSecondaryButton(Boolean value) { this.showSecondaryButton = value; } + public Boolean getShowDismiss() { return showDismiss; } + public void setShowDismiss(Boolean value) { this.showDismiss = value; } + public Boolean getEnableMessageMarkdown() { return enableMessageMarkdown; } + public void setEnableMessageMarkdown(Boolean value) { this.enableMessageMarkdown = value; } + public MessageIntent getIntent() { return intent; } + public void setIntent(MessageIntent value) { this.intent = value; } @SuppressWarnings("unchecked") - public static InteractionInput fromMap(Map map) { - var value = new InteractionInput(); - var nameValue = map.get("Name"); - value.setName((String) nameValue); - var labelValue = map.get("Label"); - value.setLabel(labelValue == null ? null : (String) labelValue); - var descriptionValue = map.get("Description"); - value.setDescription(descriptionValue == null ? null : (String) descriptionValue); - var enableDescriptionMarkdownValue = map.get("EnableDescriptionMarkdown"); - value.setEnableDescriptionMarkdown(enableDescriptionMarkdownValue == null ? null : (Boolean) enableDescriptionMarkdownValue); - var inputTypeValue = map.get("InputType"); - value.setInputType(InputType.fromValue((String) inputTypeValue)); - var requiredValue = map.get("Required"); - value.setRequired(requiredValue == null ? null : (Boolean) requiredValue); - var optionsValue = map.get("Options"); - value.setOptions((Object[]) optionsValue); - var dynamicLoadingValue = map.get("DynamicLoading"); - value.setDynamicLoading(dynamicLoadingValue); - var valueValue = map.get("Value"); - value.setValue(valueValue == null ? null : (String) valueValue); - var placeholderValue = map.get("Placeholder"); - value.setPlaceholder(placeholderValue == null ? null : (String) placeholderValue); - var allowCustomChoiceValue = map.get("AllowCustomChoice"); - value.setAllowCustomChoice(allowCustomChoiceValue == null ? null : (Boolean) allowCustomChoiceValue); - var disabledValue = map.get("Disabled"); - value.setDisabled((Boolean) disabledValue); - var maxLengthValue = map.get("MaxLength"); - value.setMaxLength(maxLengthValue == null ? null : ((Number) maxLengthValue).doubleValue()); + public static InteractionMessageBoxOptions fromMap(Map map) { + var value = new InteractionMessageBoxOptions(); + var primaryButtonTextValue = map.get("PrimaryButtonText"); + value.setPrimaryButtonText(primaryButtonTextValue == null ? null : (String) primaryButtonTextValue); + var secondaryButtonTextValue = map.get("SecondaryButtonText"); + value.setSecondaryButtonText(secondaryButtonTextValue == null ? null : (String) secondaryButtonTextValue); + var showSecondaryButtonValue = map.get("ShowSecondaryButton"); + value.setShowSecondaryButton(showSecondaryButtonValue == null ? null : (Boolean) showSecondaryButtonValue); + var showDismissValue = map.get("ShowDismiss"); + value.setShowDismiss(showDismissValue == null ? null : (Boolean) showDismissValue); + var enableMessageMarkdownValue = map.get("EnableMessageMarkdown"); + value.setEnableMessageMarkdown(enableMessageMarkdownValue == null ? null : (Boolean) enableMessageMarkdownValue); + var intentValue = map.get("Intent"); + value.setIntent(intentValue == null ? null : MessageIntent.fromValue((String) intentValue)); return value; } public Map toMap() { Map map = new HashMap<>(); - map.put("Name", AspireClient.serializeValue(name)); - map.put("Label", AspireClient.serializeValue(label)); - map.put("Description", AspireClient.serializeValue(description)); - map.put("EnableDescriptionMarkdown", AspireClient.serializeValue(enableDescriptionMarkdown)); - map.put("InputType", AspireClient.serializeValue(inputType)); - map.put("Required", AspireClient.serializeValue(required)); - map.put("Options", AspireClient.serializeValue(options)); - map.put("DynamicLoading", AspireClient.serializeValue(dynamicLoading)); - map.put("Value", AspireClient.serializeValue(value)); - map.put("Placeholder", AspireClient.serializeValue(placeholder)); - map.put("AllowCustomChoice", AspireClient.serializeValue(allowCustomChoice)); - map.put("Disabled", AspireClient.serializeValue(disabled)); - map.put("MaxLength", AspireClient.serializeValue(maxLength)); + map.put("PrimaryButtonText", AspireClient.serializeValue(primaryButtonText)); + map.put("SecondaryButtonText", AspireClient.serializeValue(secondaryButtonText)); + map.put("ShowSecondaryButton", AspireClient.serializeValue(showSecondaryButton)); + map.put("ShowDismiss", AspireClient.serializeValue(showDismiss)); + map.put("EnableMessageMarkdown", AspireClient.serializeValue(enableMessageMarkdown)); + map.put("Intent", AspireClient.serializeValue(intent)); return map; } } -// ===== InteractionInputCollection.java ===== -// InteractionInputCollection.java - GENERATED CODE - DO NOT EDIT +// ===== InteractionNotificationOptions.java ===== +// InteractionNotificationOptions.java - GENERATED CODE - DO NOT EDIT package aspire; import java.util.*; import java.util.function.*; -/** Wrapper for Aspire.Hosting/Aspire.Hosting.InteractionInputCollection. */ -public class InteractionInputCollection extends HandleWrapperBase { - InteractionInputCollection(Handle handle, AspireClient client) { - super(handle, client); - } +/** InteractionNotificationOptions DTO. */ +public class InteractionNotificationOptions implements JsonSerializable { + private String primaryButtonText; + private String secondaryButtonText; + private Boolean showSecondaryButton; + private Boolean showDismiss; + private Boolean enableMessageMarkdown; + private MessageIntent intent; + private String linkText; + private String linkUrl; + + public String getPrimaryButtonText() { return primaryButtonText; } + public void setPrimaryButtonText(String value) { this.primaryButtonText = value; } + public String getSecondaryButtonText() { return secondaryButtonText; } + public void setSecondaryButtonText(String value) { this.secondaryButtonText = value; } + public Boolean getShowSecondaryButton() { return showSecondaryButton; } + public void setShowSecondaryButton(Boolean value) { this.showSecondaryButton = value; } + public Boolean getShowDismiss() { return showDismiss; } + public void setShowDismiss(Boolean value) { this.showDismiss = value; } + public Boolean getEnableMessageMarkdown() { return enableMessageMarkdown; } + public void setEnableMessageMarkdown(Boolean value) { this.enableMessageMarkdown = value; } + public MessageIntent getIntent() { return intent; } + public void setIntent(MessageIntent value) { this.intent = value; } + public String getLinkText() { return linkText; } + public void setLinkText(String value) { this.linkText = value; } + public String getLinkUrl() { return linkUrl; } + public void setLinkUrl(String value) { this.linkUrl = value; } - /** Gets all inputs in declaration order. */ - public InteractionInput[] toArray() { - Map reqArgs = new HashMap<>(); - reqArgs.put("context", AspireClient.serializeValue(getHandle())); - var result = getClient().invokeCapability("Aspire.Hosting/InteractionInputCollection.toArray", reqArgs); - return (InteractionInput[]) result; + @SuppressWarnings("unchecked") + public static InteractionNotificationOptions fromMap(Map map) { + var value = new InteractionNotificationOptions(); + var primaryButtonTextValue = map.get("PrimaryButtonText"); + value.setPrimaryButtonText(primaryButtonTextValue == null ? null : (String) primaryButtonTextValue); + var secondaryButtonTextValue = map.get("SecondaryButtonText"); + value.setSecondaryButtonText(secondaryButtonTextValue == null ? null : (String) secondaryButtonTextValue); + var showSecondaryButtonValue = map.get("ShowSecondaryButton"); + value.setShowSecondaryButton(showSecondaryButtonValue == null ? null : (Boolean) showSecondaryButtonValue); + var showDismissValue = map.get("ShowDismiss"); + value.setShowDismiss(showDismissValue == null ? null : (Boolean) showDismissValue); + var enableMessageMarkdownValue = map.get("EnableMessageMarkdown"); + value.setEnableMessageMarkdown(enableMessageMarkdownValue == null ? null : (Boolean) enableMessageMarkdownValue); + var intentValue = map.get("Intent"); + value.setIntent(intentValue == null ? null : MessageIntent.fromValue((String) intentValue)); + var linkTextValue = map.get("LinkText"); + value.setLinkText(linkTextValue == null ? null : (String) linkTextValue); + var linkUrlValue = map.get("LinkUrl"); + value.setLinkUrl(linkUrlValue == null ? null : (String) linkUrlValue); + return value; } + public Map toMap() { + Map map = new HashMap<>(); + map.put("PrimaryButtonText", AspireClient.serializeValue(primaryButtonText)); + map.put("SecondaryButtonText", AspireClient.serializeValue(secondaryButtonText)); + map.put("ShowSecondaryButton", AspireClient.serializeValue(showSecondaryButton)); + map.put("ShowDismiss", AspireClient.serializeValue(showDismiss)); + map.put("EnableMessageMarkdown", AspireClient.serializeValue(enableMessageMarkdown)); + map.put("Intent", AspireClient.serializeValue(intent)); + map.put("LinkText", AspireClient.serializeValue(linkText)); + map.put("LinkUrl", AspireClient.serializeValue(linkUrl)); + return map; + } } // ===== JsonSerializable.java ===== @@ -14770,6 +15682,39 @@ public void debug(String message) { } +// ===== MessageIntent.java ===== +// MessageIntent.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** MessageIntent enum. */ +public enum MessageIntent implements WireValueEnum { + NONE("None"), + SUCCESS("Success"), + WARNING("Warning"), + ERROR("Error"), + INFORMATION("Information"), + CONFIRMATION("Confirmation"); + + private final String value; + + MessageIntent(String value) { + this.value = value; + } + + public String getValue() { return value; } + + public static MessageIntent fromValue(String value) { + for (MessageIntent e : values()) { + if (e.value.equals(value)) return e; + } + throw new IllegalArgumentException("Unknown value: " + value); + } +} + // ===== OtlpProtocol.java ===== // OtlpProtocol.java - GENERATED CODE - DO NOT EDIT @@ -14912,9 +15857,9 @@ public ParameterResource withContainerRegistry(ResourceBuilderBase registry) { } /** Configures custom base images for generated Dockerfiles. */ - public ParameterResource withDockerfileBaseImage(WithDockerfileBaseImageOptions options) { - var buildImage = options == null ? null : options.getBuildImage(); - var runtimeImage = options == null ? null : options.getRuntimeImage(); + public ParameterResource withDockerfileBaseImage(WithDockerfileBaseImageOptions optionsBag) { + var buildImage = optionsBag == null ? null : optionsBag.getBuildImage(); + var runtimeImage = optionsBag == null ? null : optionsBag.getRuntimeImage(); return withDockerfileBaseImageImpl(buildImage, runtimeImage); } @@ -15229,9 +16174,9 @@ public ParameterResource withHidden() { } /** Hides the resource from default resource lists after successful completion */ - public ParameterResource withHiddenOnCompletion(WithHiddenOnCompletionOptions options) { - var exitCode = options == null ? null : options.getExitCode(); - var exitCodes = options == null ? null : options.getExitCodes(); + public ParameterResource withHiddenOnCompletion(WithHiddenOnCompletionOptions optionsBag) { + var exitCode = optionsBag == null ? null : optionsBag.getExitCode(); + var exitCodes = optionsBag == null ? null : optionsBag.getExitCodes(); return withHiddenOnCompletionImpl(exitCode, exitCodes); } @@ -15254,11 +16199,11 @@ private ParameterResource withHiddenOnCompletionImpl(Double exitCode, double[] e } /** Adds a pipeline step to the resource that will be executed during deployment. */ - public ParameterResource withPipelineStepFactory(String stepName, AspireAction1 callback, WithPipelineStepFactoryOptions options) { - var dependsOn = options == null ? null : options.getDependsOn(); - var requiredBy = options == null ? null : options.getRequiredBy(); - var tags = options == null ? null : options.getTags(); - var description = options == null ? null : options.getDescription(); + public ParameterResource withPipelineStepFactory(String stepName, AspireAction1 callback, WithPipelineStepFactoryOptions optionsBag) { + var dependsOn = optionsBag == null ? null : optionsBag.getDependsOn(); + var requiredBy = optionsBag == null ? null : optionsBag.getRequiredBy(); + var tags = optionsBag == null ? null : optionsBag.getTags(); + var description = optionsBag == null ? null : optionsBag.getDescription(); return withPipelineStepFactoryImpl(stepName, callback, dependsOn, requiredBy, tags, description); } @@ -15392,9 +16337,9 @@ public IExecutionConfigurationBuilder createExecutionConfiguration() { } /** Adds an optional string parameter */ - public ParameterResource withOptionalString(WithOptionalStringOptions options) { - var value = options == null ? null : options.getValue(); - var enabled = options == null ? null : options.getEnabled(); + public ParameterResource withOptionalString(WithOptionalStringOptions optionsBag) { + var value = optionsBag == null ? null : optionsBag.getValue(); + var enabled = optionsBag == null ? null : optionsBag.getEnabled(); return withOptionalStringImpl(value, enabled); } @@ -15618,9 +16563,9 @@ public ParameterResource withMergeEndpointScheme(String endpointName, double por } /** Configures resource logging */ - public ParameterResource withMergeLogging(String logLevel, WithMergeLoggingOptions options) { - var enableConsole = options == null ? null : options.getEnableConsole(); - var maxFiles = options == null ? null : options.getMaxFiles(); + public ParameterResource withMergeLogging(String logLevel, WithMergeLoggingOptions optionsBag) { + var enableConsole = optionsBag == null ? null : optionsBag.getEnableConsole(); + var maxFiles = optionsBag == null ? null : optionsBag.getMaxFiles(); return withMergeLoggingImpl(logLevel, enableConsole, maxFiles); } @@ -15644,9 +16589,9 @@ private ParameterResource withMergeLoggingImpl(String logLevel, Boolean enableCo } /** Configures resource logging with file path */ - public ParameterResource withMergeLoggingPath(String logLevel, String logPath, WithMergeLoggingPathOptions options) { - var enableConsole = options == null ? null : options.getEnableConsole(); - var maxFiles = options == null ? null : options.getMaxFiles(); + public ParameterResource withMergeLoggingPath(String logLevel, String logPath, WithMergeLoggingPathOptions optionsBag) { + var enableConsole = optionsBag == null ? null : optionsBag.getEnableConsole(); + var maxFiles = optionsBag == null ? null : optionsBag.getMaxFiles(); return withMergeLoggingPathImpl(logLevel, logPath, enableConsole, maxFiles); } @@ -16122,7 +17067,7 @@ public class ProcessCommandExportOptions implements JsonSerializable { private Boolean inheritEnvironmentVariables; private String standardInputContent; private Boolean killEntireProcessTree; - private Object createProcessSpec; + private AspireFunc1 createProcessSpec; private CommandOptions commandOptions; private Double maxOutputLineCount; private Boolean displayImmediately; @@ -16142,8 +17087,8 @@ public class ProcessCommandExportOptions implements JsonSerializable { public void setStandardInputContent(String value) { this.standardInputContent = value; } public Boolean getKillEntireProcessTree() { return killEntireProcessTree; } public void setKillEntireProcessTree(Boolean value) { this.killEntireProcessTree = value; } - public Object getCreateProcessSpec() { return createProcessSpec; } - public void setCreateProcessSpec(Object value) { this.createProcessSpec = value; } + public AspireFunc1 getCreateProcessSpec() { return createProcessSpec; } + public void setCreateProcessSpec(AspireFunc1 value) { this.createProcessSpec = value; } public CommandOptions getCommandOptions() { return commandOptions; } public void setCommandOptions(CommandOptions value) { this.commandOptions = value; } public Double getMaxOutputLineCount() { return maxOutputLineCount; } @@ -16170,8 +17115,6 @@ public static ProcessCommandExportOptions fromMap(Map map) { value.setStandardInputContent(standardInputContentValue == null ? null : (String) standardInputContentValue); var killEntireProcessTreeValue = map.get("KillEntireProcessTree"); value.setKillEntireProcessTree(killEntireProcessTreeValue == null ? null : (Boolean) killEntireProcessTreeValue); - var createProcessSpecValue = map.get("CreateProcessSpec"); - value.setCreateProcessSpec(createProcessSpecValue); var commandOptionsValue = map.get("CommandOptions"); value.setCommandOptions(commandOptionsValue == null ? null : CommandOptions.fromMap((Map) commandOptionsValue)); var maxOutputLineCountValue = map.get("MaxOutputLineCount"); @@ -16192,7 +17135,10 @@ public Map toMap() { map.put("InheritEnvironmentVariables", AspireClient.serializeValue(inheritEnvironmentVariables)); map.put("StandardInputContent", AspireClient.serializeValue(standardInputContent)); map.put("KillEntireProcessTree", AspireClient.serializeValue(killEntireProcessTree)); - map.put("CreateProcessSpec", AspireClient.serializeValue(createProcessSpec)); + map.put("CreateProcessSpec", createProcessSpec == null ? null : (java.util.function.Function) (transportArg -> { + var arg = (ExecuteCommandContext) transportArg; + return AspireClient.awaitValue(createProcessSpec.invoke(arg)); + })); map.put("CommandOptions", AspireClient.serializeValue(commandOptions)); map.put("MaxOutputLineCount", AspireClient.serializeValue(maxOutputLineCount)); map.put("DisplayImmediately", AspireClient.serializeValue(displayImmediately)); @@ -16343,9 +17289,9 @@ public ProjectResource withContainerRegistry(ResourceBuilderBase registry) { } /** Configures custom base images for generated Dockerfiles. */ - public ProjectResource withDockerfileBaseImage(WithDockerfileBaseImageOptions options) { - var buildImage = options == null ? null : options.getBuildImage(); - var runtimeImage = options == null ? null : options.getRuntimeImage(); + public ProjectResource withDockerfileBaseImage(WithDockerfileBaseImageOptions optionsBag) { + var buildImage = optionsBag == null ? null : optionsBag.getBuildImage(); + var runtimeImage = optionsBag == null ? null : optionsBag.getRuntimeImage(); return withDockerfileBaseImageImpl(buildImage, runtimeImage); } @@ -16368,9 +17314,9 @@ private ProjectResource withDockerfileBaseImageImpl(String buildImage, String ru } /** Marks the resource as hosting a Model Context Protocol (MCP) server on the specified endpoint. */ - public ProjectResource withMcpServer(WithMcpServerOptions options) { - var path = options == null ? null : options.getPath(); - var endpointName = options == null ? null : options.getEndpointName(); + public ProjectResource withMcpServer(WithMcpServerOptions optionsBag) { + var path = optionsBag == null ? null : optionsBag.getPath(); + var endpointName = optionsBag == null ? null : optionsBag.getEndpointName(); return withMcpServerImpl(path, endpointName); } @@ -16623,10 +17569,10 @@ public ProjectResource withReference(String source) { } /** Adds a reference to another resource */ - public ProjectResource withReference(AspireUnion source, WithReferenceOptions options) { - var connectionName = options == null ? null : options.getConnectionName(); - var optional = options == null ? null : options.getOptional(); - var name = options == null ? null : options.getName(); + public ProjectResource withReference(AspireUnion source, WithReferenceOptions optionsBag) { + var connectionName = optionsBag == null ? null : optionsBag.getConnectionName(); + var optional = optionsBag == null ? null : optionsBag.getOptional(); + var name = optionsBag == null ? null : optionsBag.getName(); return withReferenceImpl(source, connectionName, optional, name); } @@ -16677,9 +17623,9 @@ public ProjectResource withEndpointCallback(String endpointName, AspireAction1 callback, WithHttpEndpointCallbackOptions options) { - var name = options == null ? null : options.getName(); - var createIfNotExists = options == null ? null : options.getCreateIfNotExists(); + public ProjectResource withHttpEndpointCallback(AspireAction1 callback, WithHttpEndpointCallbackOptions optionsBag) { + var name = optionsBag == null ? null : optionsBag.getName(); + var createIfNotExists = optionsBag == null ? null : optionsBag.getCreateIfNotExists(); return withHttpEndpointCallbackImpl(callback, name, createIfNotExists); } @@ -16710,9 +17656,9 @@ private ProjectResource withHttpEndpointCallbackImpl(AspireAction1 callback, WithHttpsEndpointCallbackOptions options) { - var name = options == null ? null : options.getName(); - var createIfNotExists = options == null ? null : options.getCreateIfNotExists(); + public ProjectResource withHttpsEndpointCallback(AspireAction1 callback, WithHttpsEndpointCallbackOptions optionsBag) { + var name = optionsBag == null ? null : optionsBag.getName(); + var createIfNotExists = optionsBag == null ? null : optionsBag.getCreateIfNotExists(); return withHttpsEndpointCallbackImpl(callback, name, createIfNotExists); } @@ -16743,15 +17689,15 @@ private ProjectResource withHttpsEndpointCallbackImpl(AspireAction1 callback, WithPipelineStepFactoryOptions options) { - var dependsOn = options == null ? null : options.getDependsOn(); - var requiredBy = options == null ? null : options.getRequiredBy(); - var tags = options == null ? null : options.getTags(); - var description = options == null ? null : options.getDescription(); + public ProjectResource withPipelineStepFactory(String stepName, AspireAction1 callback, WithPipelineStepFactoryOptions optionsBag) { + var dependsOn = optionsBag == null ? null : optionsBag.getDependsOn(); + var requiredBy = optionsBag == null ? null : optionsBag.getRequiredBy(); + var tags = optionsBag == null ? null : optionsBag.getTags(); + var description = optionsBag == null ? null : optionsBag.getDescription(); return withPipelineStepFactoryImpl(stepName, callback, dependsOn, requiredBy, tags, description); } @@ -17567,9 +18513,9 @@ public IExecutionConfigurationBuilder createExecutionConfiguration() { } /** Adds an optional string parameter */ - public ProjectResource withOptionalString(WithOptionalStringOptions options) { - var value = options == null ? null : options.getValue(); - var enabled = options == null ? null : options.getEnabled(); + public ProjectResource withOptionalString(WithOptionalStringOptions optionsBag) { + var value = optionsBag == null ? null : optionsBag.getValue(); + var enabled = optionsBag == null ? null : optionsBag.getEnabled(); return withOptionalStringImpl(value, enabled); } @@ -17818,9 +18764,9 @@ public ProjectResource withMergeEndpointScheme(String endpointName, double port, } /** Configures resource logging */ - public ProjectResource withMergeLogging(String logLevel, WithMergeLoggingOptions options) { - var enableConsole = options == null ? null : options.getEnableConsole(); - var maxFiles = options == null ? null : options.getMaxFiles(); + public ProjectResource withMergeLogging(String logLevel, WithMergeLoggingOptions optionsBag) { + var enableConsole = optionsBag == null ? null : optionsBag.getEnableConsole(); + var maxFiles = optionsBag == null ? null : optionsBag.getMaxFiles(); return withMergeLoggingImpl(logLevel, enableConsole, maxFiles); } @@ -17844,9 +18790,9 @@ private ProjectResource withMergeLoggingImpl(String logLevel, Boolean enableCons } /** Configures resource logging with file path */ - public ProjectResource withMergeLoggingPath(String logLevel, String logPath, WithMergeLoggingPathOptions options) { - var enableConsole = options == null ? null : options.getEnableConsole(); - var maxFiles = options == null ? null : options.getMaxFiles(); + public ProjectResource withMergeLoggingPath(String logLevel, String logPath, WithMergeLoggingPathOptions optionsBag) { + var enableConsole = optionsBag == null ? null : optionsBag.getEnableConsole(); + var maxFiles = optionsBag == null ? null : optionsBag.getMaxFiles(); return withMergeLoggingPathImpl(logLevel, logPath, enableConsole, maxFiles); } @@ -17964,6 +18910,141 @@ public ProjectResourceOptions setExcludeKestrelEndpoints(boolean value) { } +// ===== PromptConfirmationOptions.java ===== +// PromptConfirmationOptions.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** Options for PromptConfirmation. */ +public final class PromptConfirmationOptions { + private InteractionMessageBoxOptions options; + private CancellationToken cancellationToken; + + public InteractionMessageBoxOptions getOptions() { return options; } + public PromptConfirmationOptions options(InteractionMessageBoxOptions value) { + this.options = value; + return this; + } + + public CancellationToken getCancellationToken() { return cancellationToken; } + public PromptConfirmationOptions cancellationToken(CancellationToken value) { + this.cancellationToken = value; + return this; + } + +} + +// ===== PromptInputOptions.java ===== +// PromptInputOptions.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** Options for PromptInput. */ +public final class PromptInputOptions { + private InteractionInputsDialogOptions options; + private CancellationToken cancellationToken; + + public InteractionInputsDialogOptions getOptions() { return options; } + public PromptInputOptions options(InteractionInputsDialogOptions value) { + this.options = value; + return this; + } + + public CancellationToken getCancellationToken() { return cancellationToken; } + public PromptInputOptions cancellationToken(CancellationToken value) { + this.cancellationToken = value; + return this; + } + +} + +// ===== PromptInputsOptions.java ===== +// PromptInputsOptions.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** Options for PromptInputs. */ +public final class PromptInputsOptions { + private InteractionInputsDialogOptions options; + private CancellationToken cancellationToken; + + public InteractionInputsDialogOptions getOptions() { return options; } + public PromptInputsOptions options(InteractionInputsDialogOptions value) { + this.options = value; + return this; + } + + public CancellationToken getCancellationToken() { return cancellationToken; } + public PromptInputsOptions cancellationToken(CancellationToken value) { + this.cancellationToken = value; + return this; + } + +} + +// ===== PromptMessageBoxOptions.java ===== +// PromptMessageBoxOptions.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** Options for PromptMessageBox. */ +public final class PromptMessageBoxOptions { + private InteractionMessageBoxOptions options; + private CancellationToken cancellationToken; + + public InteractionMessageBoxOptions getOptions() { return options; } + public PromptMessageBoxOptions options(InteractionMessageBoxOptions value) { + this.options = value; + return this; + } + + public CancellationToken getCancellationToken() { return cancellationToken; } + public PromptMessageBoxOptions cancellationToken(CancellationToken value) { + this.cancellationToken = value; + return this; + } + +} + +// ===== PromptNotificationOptions.java ===== +// PromptNotificationOptions.java - GENERATED CODE - DO NOT EDIT + +package aspire; + +import java.util.*; +import java.util.function.*; + +/** Options for PromptNotification. */ +public final class PromptNotificationOptions { + private InteractionNotificationOptions options; + private CancellationToken cancellationToken; + + public InteractionNotificationOptions getOptions() { return options; } + public PromptNotificationOptions options(InteractionNotificationOptions value) { + this.options = value; + return this; + } + + public CancellationToken getCancellationToken() { return cancellationToken; } + public PromptNotificationOptions cancellationToken(CancellationToken value) { + this.cancellationToken = value; + return this; + } + +} + // ===== ProtocolType.java ===== // ProtocolType.java - GENERATED CODE - DO NOT EDIT @@ -18340,9 +19421,9 @@ public ExecuteCommandResult executeCommandAsync(IResource resource, String comma } /** Executes a command for the specified resource. */ - public ExecuteCommandResult executeCommandAsync(AspireUnion resource, String commandName, ExecuteCommandAsyncOptions options) { - var arguments = options == null ? null : options.getArguments(); - var cancellationToken = options == null ? null : options.getCancellationToken(); + public ExecuteCommandResult executeCommandAsync(AspireUnion resource, String commandName, ExecuteCommandAsyncOptions optionsBag) { + var arguments = optionsBag == null ? null : optionsBag.getArguments(); + var cancellationToken = optionsBag == null ? null : optionsBag.getCancellationToken(); return executeCommandAsyncImpl(resource, commandName, arguments, cancellationToken); } @@ -18626,9 +19707,9 @@ public ResourceEventDto tryGetResourceState(String resourceName) { } /** Publishes an update for a resource's state. */ - public void publishResourceUpdate(IResource resource, PublishResourceUpdateOptions options) { - var state = options == null ? null : options.getState(); - var stateStyle = options == null ? null : options.getStateStyle(); + public void publishResourceUpdate(IResource resource, PublishResourceUpdateOptions optionsBag) { + var state = optionsBag == null ? null : optionsBag.getState(); + var stateStyle = optionsBag == null ? null : optionsBag.getStateStyle(); publishResourceUpdateImpl(resource, state, stateStyle); } @@ -19209,9 +20290,9 @@ public TestDatabaseResource publishAsContainer() { } /** Causes Aspire to build the specified container image from a Dockerfile. */ - public TestDatabaseResource withDockerfile(String contextPath, WithDockerfileOptions options) { - var dockerfilePath = options == null ? null : options.getDockerfilePath(); - var stage = options == null ? null : options.getStage(); + public TestDatabaseResource withDockerfile(String contextPath, WithDockerfileOptions optionsBag) { + var dockerfilePath = optionsBag == null ? null : optionsBag.getDockerfilePath(); + var stage = optionsBag == null ? null : optionsBag.getStage(); return withDockerfileImpl(contextPath, dockerfilePath, stage); } @@ -19295,10 +20376,10 @@ public TestDatabaseResource withBuildSecret(String name, ParameterResource value } /** Adds container certificate path overrides used for certificate trust at run time. */ - public TestDatabaseResource withContainerCertificatePaths(WithContainerCertificatePathsOptions options) { - var customCertificatesDestination = options == null ? null : options.getCustomCertificatesDestination(); - var defaultCertificateBundlePaths = options == null ? null : options.getDefaultCertificateBundlePaths(); - var defaultCertificateDirectoryPaths = options == null ? null : options.getDefaultCertificateDirectoryPaths(); + public TestDatabaseResource withContainerCertificatePaths(WithContainerCertificatePathsOptions optionsBag) { + var customCertificatesDestination = optionsBag == null ? null : optionsBag.getCustomCertificatesDestination(); + var defaultCertificateBundlePaths = optionsBag == null ? null : optionsBag.getDefaultCertificateBundlePaths(); + var defaultCertificateDirectoryPaths = optionsBag == null ? null : optionsBag.getDefaultCertificateDirectoryPaths(); return withContainerCertificatePathsImpl(customCertificatesDestination, defaultCertificateBundlePaths, defaultCertificateDirectoryPaths); } @@ -19365,9 +20446,9 @@ public TestDatabaseResource withDockerfileBuilder(String contextPath, AspireActi } /** Configures custom base images for generated Dockerfiles. */ - public TestDatabaseResource withDockerfileBaseImage(WithDockerfileBaseImageOptions options) { - var buildImage = options == null ? null : options.getBuildImage(); - var runtimeImage = options == null ? null : options.getRuntimeImage(); + public TestDatabaseResource withDockerfileBaseImage(WithDockerfileBaseImageOptions optionsBag) { + var buildImage = optionsBag == null ? null : optionsBag.getBuildImage(); + var runtimeImage = optionsBag == null ? null : optionsBag.getRuntimeImage(); return withDockerfileBaseImageImpl(buildImage, runtimeImage); } @@ -19399,9 +20480,9 @@ public TestDatabaseResource withContainerNetworkAlias(String alias) { } /** Marks the resource as hosting a Model Context Protocol (MCP) server on the specified endpoint. */ - public TestDatabaseResource withMcpServer(WithMcpServerOptions options) { - var path = options == null ? null : options.getPath(); - var endpointName = options == null ? null : options.getEndpointName(); + public TestDatabaseResource withMcpServer(WithMcpServerOptions optionsBag) { + var path = optionsBag == null ? null : optionsBag.getPath(); + var endpointName = optionsBag == null ? null : optionsBag.getEndpointName(); return withMcpServerImpl(path, endpointName); } @@ -19625,10 +20706,10 @@ public TestDatabaseResource withReference(String source) { } /** Adds a reference to another resource */ - public TestDatabaseResource withReference(AspireUnion source, WithReferenceOptions options) { - var connectionName = options == null ? null : options.getConnectionName(); - var optional = options == null ? null : options.getOptional(); - var name = options == null ? null : options.getName(); + public TestDatabaseResource withReference(AspireUnion source, WithReferenceOptions optionsBag) { + var connectionName = optionsBag == null ? null : optionsBag.getConnectionName(); + var optional = optionsBag == null ? null : optionsBag.getOptional(); + var name = optionsBag == null ? null : optionsBag.getName(); return withReferenceImpl(source, connectionName, optional, name); } @@ -19679,9 +20760,9 @@ public TestDatabaseResource withEndpointCallback(String endpointName, AspireActi } /** Updates an HTTP endpoint via callback */ - public TestDatabaseResource withHttpEndpointCallback(AspireAction1 callback, WithHttpEndpointCallbackOptions options) { - var name = options == null ? null : options.getName(); - var createIfNotExists = options == null ? null : options.getCreateIfNotExists(); + public TestDatabaseResource withHttpEndpointCallback(AspireAction1 callback, WithHttpEndpointCallbackOptions optionsBag) { + var name = optionsBag == null ? null : optionsBag.getName(); + var createIfNotExists = optionsBag == null ? null : optionsBag.getCreateIfNotExists(); return withHttpEndpointCallbackImpl(callback, name, createIfNotExists); } @@ -19712,9 +20793,9 @@ private TestDatabaseResource withHttpEndpointCallbackImpl(AspireAction1 callback, WithHttpsEndpointCallbackOptions options) { - var name = options == null ? null : options.getName(); - var createIfNotExists = options == null ? null : options.getCreateIfNotExists(); + public TestDatabaseResource withHttpsEndpointCallback(AspireAction1 callback, WithHttpsEndpointCallbackOptions optionsBag) { + var name = optionsBag == null ? null : optionsBag.getName(); + var createIfNotExists = optionsBag == null ? null : optionsBag.getCreateIfNotExists(); return withHttpsEndpointCallbackImpl(callback, name, createIfNotExists); } @@ -19745,15 +20826,15 @@ private TestDatabaseResource withHttpsEndpointCallbackImpl(AspireAction1 callback, WithPipelineStepFactoryOptions options) { - var dependsOn = options == null ? null : options.getDependsOn(); - var requiredBy = options == null ? null : options.getRequiredBy(); - var tags = options == null ? null : options.getTags(); - var description = options == null ? null : options.getDescription(); + public TestDatabaseResource withPipelineStepFactory(String stepName, AspireAction1 callback, WithPipelineStepFactoryOptions optionsBag) { + var dependsOn = optionsBag == null ? null : optionsBag.getDependsOn(); + var requiredBy = optionsBag == null ? null : optionsBag.getRequiredBy(); + var tags = optionsBag == null ? null : optionsBag.getTags(); + var description = optionsBag == null ? null : optionsBag.getDescription(); return withPipelineStepFactoryImpl(stepName, callback, dependsOn, requiredBy, tags, description); } @@ -20450,9 +21531,9 @@ public TestDatabaseResource withPipelineConfiguration(AspireAction1 callback, WithHttpEndpointCallbackOptions options) { - var name = options == null ? null : options.getName(); - var createIfNotExists = options == null ? null : options.getCreateIfNotExists(); + public TestRedisResource withHttpEndpointCallback(AspireAction1 callback, WithHttpEndpointCallbackOptions optionsBag) { + var name = optionsBag == null ? null : optionsBag.getName(); + var createIfNotExists = optionsBag == null ? null : optionsBag.getCreateIfNotExists(); return withHttpEndpointCallbackImpl(callback, name, createIfNotExists); } @@ -21797,9 +22878,9 @@ private TestRedisResource withHttpEndpointCallbackImpl(AspireAction1 callback, WithHttpsEndpointCallbackOptions options) { - var name = options == null ? null : options.getName(); - var createIfNotExists = options == null ? null : options.getCreateIfNotExists(); + public TestRedisResource withHttpsEndpointCallback(AspireAction1 callback, WithHttpsEndpointCallbackOptions optionsBag) { + var name = optionsBag == null ? null : optionsBag.getName(); + var createIfNotExists = optionsBag == null ? null : optionsBag.getCreateIfNotExists(); return withHttpsEndpointCallbackImpl(callback, name, createIfNotExists); } @@ -21830,15 +22911,15 @@ private TestRedisResource withHttpsEndpointCallbackImpl(AspireAction1 callback, WithPipelineStepFactoryOptions options) { - var dependsOn = options == null ? null : options.getDependsOn(); - var requiredBy = options == null ? null : options.getRequiredBy(); - var tags = options == null ? null : options.getTags(); - var description = options == null ? null : options.getDescription(); + public TestRedisResource withPipelineStepFactory(String stepName, AspireAction1 callback, WithPipelineStepFactoryOptions optionsBag) { + var dependsOn = optionsBag == null ? null : optionsBag.getDependsOn(); + var requiredBy = optionsBag == null ? null : optionsBag.getRequiredBy(); + var tags = optionsBag == null ? null : optionsBag.getTags(); + var description = optionsBag == null ? null : optionsBag.getDescription(); return withPipelineStepFactoryImpl(stepName, callback, dependsOn, requiredBy, tags, description); } @@ -22535,9 +23616,9 @@ public TestRedisResource withPipelineConfiguration(AspireAction1 callback, WithHttpEndpointCallbackOptions options) { - var name = options == null ? null : options.getName(); - var createIfNotExists = options == null ? null : options.getCreateIfNotExists(); + public TestVaultResource withHttpEndpointCallback(AspireAction1 callback, WithHttpEndpointCallbackOptions optionsBag) { + var name = optionsBag == null ? null : optionsBag.getName(); + var createIfNotExists = optionsBag == null ? null : optionsBag.getCreateIfNotExists(); return withHttpEndpointCallbackImpl(callback, name, createIfNotExists); } @@ -23900,9 +24981,9 @@ private TestVaultResource withHttpEndpointCallbackImpl(AspireAction1 callback, WithHttpsEndpointCallbackOptions options) { - var name = options == null ? null : options.getName(); - var createIfNotExists = options == null ? null : options.getCreateIfNotExists(); + public TestVaultResource withHttpsEndpointCallback(AspireAction1 callback, WithHttpsEndpointCallbackOptions optionsBag) { + var name = optionsBag == null ? null : optionsBag.getName(); + var createIfNotExists = optionsBag == null ? null : optionsBag.getCreateIfNotExists(); return withHttpsEndpointCallbackImpl(callback, name, createIfNotExists); } @@ -23933,15 +25014,15 @@ private TestVaultResource withHttpsEndpointCallbackImpl(AspireAction1 callback, WithPipelineStepFactoryOptions options) { - var dependsOn = options == null ? null : options.getDependsOn(); - var requiredBy = options == null ? null : options.getRequiredBy(); - var tags = options == null ? null : options.getTags(); - var description = options == null ? null : options.getDescription(); + public TestVaultResource withPipelineStepFactory(String stepName, AspireAction1 callback, WithPipelineStepFactoryOptions optionsBag) { + var dependsOn = optionsBag == null ? null : optionsBag.getDependsOn(); + var requiredBy = optionsBag == null ? null : optionsBag.getRequiredBy(); + var tags = optionsBag == null ? null : optionsBag.getTags(); + var description = optionsBag == null ? null : optionsBag.getDescription(); return withPipelineStepFactoryImpl(stepName, callback, dependsOn, requiredBy, tags, description); } @@ -24638,9 +25719,9 @@ public TestVaultResource withPipelineConfiguration(AspireAction1 bool: InputType = typing.Literal["Text", "SecretText", "Choice", "Boolean", "Number"] +MessageIntent = typing.Literal["None", "Success", "Warning", "Error", "Information", "Confirmation"] + OtlpProtocol = typing.Literal["Grpc", "HttpProtobuf", "HttpJson"] ProbeType = typing.Literal["Startup", "Readiness", "Liveness"] @@ -1743,6 +1745,10 @@ class AddContainerOptions(typing.TypedDict, total=False): Image: str Tag: str | None +class BoolInteractionResult(typing.TypedDict, total=False): + Canceled: bool + Value: bool + class CertificateTrustExecutionConfigurationContext(typing.TypedDict, total=False): CertificateBundlePath: ReferenceExpression CertificateDirectoriesPath: ReferenceExpression @@ -1758,13 +1764,13 @@ class CommandOptions(typing.TypedDict, total=False): Description: str | None Parameter: typing.Any Arguments: typing.Iterable[InteractionInput] - ValidateArguments: typing.Callable + ValidateArguments: typing.Callable[[InputsDialogValidationContext], None] Visibility: ResourceCommandVisibility ConfirmationMessage: str | None IconName: str | None IconVariant: IconVariant | None IsHighlighted: bool - UpdateState: typing.Callable + UpdateState: typing.Callable[[UpdateCommandStateContext], ResourceCommandState] class CommandResultData(typing.TypedDict, total=False): Value: str @@ -1786,6 +1792,21 @@ class CreateBuilderOptions(typing.TypedDict, total=False): AllowUnsecuredTransport: bool EnableResourceLogging: bool +class CreateInteractionInputOptions(typing.TypedDict, total=False): + Label: str | None + Description: str | None + EnableDescriptionMarkdown: bool | None + Required: bool | None + Placeholder: str | None + Value: str | None + AllowCustomChoice: bool | None + Disabled: bool | None + MaxLength: int | None + +class DynamicLoadingOptions(typing.TypedDict, total=False): + AlwaysLoadOnStart: bool | None + DependsOnInputs: typing.Iterable[str] + class ExecuteCommandResult(typing.TypedDict, total=False): Success: bool Canceled: bool @@ -1819,7 +1840,7 @@ class HttpCommandExportOptions(typing.TypedDict, total=False): CommandName: str | None EndpointName: str | None MethodName: str | None - PrepareRequest: typing.Callable + PrepareRequest: typing.Callable[[HttpCommandPrepareRequestContext], HttpCommandRequestExportData] ResultMode: HttpCommandResultMode class HttpCommandRequestExportData(typing.TypedDict, total=False): @@ -1847,6 +1868,14 @@ class HttpsCertificateInfo(typing.TypedDict, total=False): Issuer: str Thumbprint: str | None +class InputInteractionResult(typing.TypedDict, total=False): + Canceled: bool + Input: InteractionInput + +class InteractionChoiceOption(typing.TypedDict, total=False): + Value: str + Label: str + class InteractionInput(typing.TypedDict, total=False): Name: str Label: str | None @@ -1855,13 +1884,38 @@ class InteractionInput(typing.TypedDict, total=False): InputType: InputType Required: bool Options: typing.Iterable[typing.Any] - DynamicLoading: typing.Any Value: str | None Placeholder: str | None AllowCustomChoice: bool Disabled: bool MaxLength: int | None +class InteractionInputsDialogOptions(typing.TypedDict, total=False): + PrimaryButtonText: str | None + SecondaryButtonText: str | None + ShowSecondaryButton: bool | None + ShowDismiss: bool | None + EnableMessageMarkdown: bool | None + ValidationCallback: typing.Callable[[InputsDialogValidationContext], None] + +class InteractionMessageBoxOptions(typing.TypedDict, total=False): + PrimaryButtonText: str | None + SecondaryButtonText: str | None + ShowSecondaryButton: bool | None + ShowDismiss: bool | None + EnableMessageMarkdown: bool | None + Intent: MessageIntent | None + +class InteractionNotificationOptions(typing.TypedDict, total=False): + PrimaryButtonText: str | None + SecondaryButtonText: str | None + ShowSecondaryButton: bool | None + ShowDismiss: bool | None + EnableMessageMarkdown: bool | None + Intent: MessageIntent | None + LinkText: str | None + LinkUrl: str | None + class ParameterCustomInputOptions(typing.TypedDict, total=False): InputType: InputType | None Label: str | None @@ -1882,7 +1936,7 @@ class ProcessCommandExportOptions(typing.TypedDict, total=False): InheritEnvironmentVariables: bool | None StandardInputContent: str | None KillEntireProcessTree: bool | None - CreateProcessSpec: typing.Callable + CreateProcessSpec: typing.Callable[[ExecuteCommandContext], ProcessCommandSpecExportData] CommandOptions: CommandOptions MaxOutputLineCount: int | None DisplayImmediately: bool | None @@ -2852,6 +2906,170 @@ def is_env(self, env_name: str) -> bool: return result +class AbstractInteractionService: + """Type class for AbstractInteractionService.""" + + def __init__(self, handle: Handle, client: AspireClient) -> None: + self._handle = handle + self._client = client + + def __repr__(self) -> str: + return f"AbstractInteractionService(handle={self._handle.handle_id})" + + @_uncached_property + def handle(self) -> Handle: + """The underlying object reference handle.""" + return self._handle + + def is_available(self) -> bool: + """Gets a value indicating whether the interaction service is available to prompt the user.""" + rpc_args: dict[str, typing.Any] = {'interactionService': self._handle} + result = self._client.invoke_capability( + 'Aspire.Hosting/isAvailable', + rpc_args, + ) + return result + + def prompt_confirmation(self, title: str, message: str, *, options: InteractionMessageBoxOptions | None = None, timeout: int | None = None) -> BoolInteractionResult: + """Prompts the user for confirmation with an OK/Cancel dialog.""" + rpc_args: dict[str, typing.Any] = {'interactionService': self._handle} + rpc_args['title'] = title + rpc_args['message'] = message + if options is not None: + rpc_args['options'] = options + if timeout is not None: + rpc_args['cancellationToken'] = self._client.register_cancellation_token(timeout) + result = self._client.invoke_capability( + 'Aspire.Hosting/promptConfirmation', + rpc_args, + ) + return typing.cast(BoolInteractionResult, result) + + def prompt_message_box(self, title: str, message: str, *, options: InteractionMessageBoxOptions | None = None, timeout: int | None = None) -> BoolInteractionResult: + """Prompts the user with a message box dialog.""" + rpc_args: dict[str, typing.Any] = {'interactionService': self._handle} + rpc_args['title'] = title + rpc_args['message'] = message + if options is not None: + rpc_args['options'] = options + if timeout is not None: + rpc_args['cancellationToken'] = self._client.register_cancellation_token(timeout) + result = self._client.invoke_capability( + 'Aspire.Hosting/promptMessageBox', + rpc_args, + ) + return typing.cast(BoolInteractionResult, result) + + def prompt_notification(self, title: str, message: str, *, options: InteractionNotificationOptions | None = None, timeout: int | None = None) -> BoolInteractionResult: + """Prompts the user with a notification.""" + rpc_args: dict[str, typing.Any] = {'interactionService': self._handle} + rpc_args['title'] = title + rpc_args['message'] = message + if options is not None: + rpc_args['options'] = options + if timeout is not None: + rpc_args['cancellationToken'] = self._client.register_cancellation_token(timeout) + result = self._client.invoke_capability( + 'Aspire.Hosting/promptNotification', + rpc_args, + ) + return typing.cast(BoolInteractionResult, result) + + def prompt_input(self, title: str, message: str, input: InteractionInputBuilder, *, options: InteractionInputsDialogOptions | None = None, timeout: int | None = None) -> InputInteractionResult: + """Prompts the user for a single input.""" + rpc_args: dict[str, typing.Any] = {'interactionService': self._handle} + rpc_args['title'] = title + rpc_args['message'] = message + rpc_args['input'] = input + if options is not None: + rpc_args['options'] = options + if timeout is not None: + rpc_args['cancellationToken'] = self._client.register_cancellation_token(timeout) + result = self._client.invoke_capability( + 'Aspire.Hosting/promptInput', + rpc_args, + ) + return typing.cast(InputInteractionResult, result) + + def prompt_inputs(self, title: str, message: str, inputs: typing.Iterable[InteractionInputBuilder], *, options: InteractionInputsDialogOptions | None = None, timeout: int | None = None) -> InputsInteractionResult: + """Prompts the user for multiple inputs.""" + rpc_args: dict[str, typing.Any] = {'interactionService': self._handle} + rpc_args['title'] = title + rpc_args['message'] = message + rpc_args['inputs'] = inputs + if options is not None: + rpc_args['options'] = options + if timeout is not None: + rpc_args['cancellationToken'] = self._client.register_cancellation_token(timeout) + result = self._client.invoke_capability( + 'Aspire.Hosting/promptInputs', + rpc_args, + ) + return typing.cast(InputsInteractionResult, result) + + def create_text_input(self, name: str, *, options: CreateInteractionInputOptions | None = None) -> InteractionInputBuilder: + """Creates a single-line text input.""" + rpc_args: dict[str, typing.Any] = {'interactionService': self._handle} + rpc_args['name'] = name + if options is not None: + rpc_args['options'] = options + result = self._client.invoke_capability( + 'Aspire.Hosting/createTextInput', + rpc_args, + ) + return typing.cast(InteractionInputBuilder, result) + + def create_secret_input(self, name: str, *, options: CreateInteractionInputOptions | None = None) -> InteractionInputBuilder: + """Creates a secret (masked) text input.""" + rpc_args: dict[str, typing.Any] = {'interactionService': self._handle} + rpc_args['name'] = name + if options is not None: + rpc_args['options'] = options + result = self._client.invoke_capability( + 'Aspire.Hosting/createSecretInput', + rpc_args, + ) + return typing.cast(InteractionInputBuilder, result) + + def create_boolean_input(self, name: str, *, options: CreateInteractionInputOptions | None = None) -> InteractionInputBuilder: + """Creates a boolean (checkbox) input.""" + rpc_args: dict[str, typing.Any] = {'interactionService': self._handle} + rpc_args['name'] = name + if options is not None: + rpc_args['options'] = options + result = self._client.invoke_capability( + 'Aspire.Hosting/createBooleanInput', + rpc_args, + ) + return typing.cast(InteractionInputBuilder, result) + + def create_number_input(self, name: str, *, options: CreateInteractionInputOptions | None = None) -> InteractionInputBuilder: + """Creates a numeric input.""" + rpc_args: dict[str, typing.Any] = {'interactionService': self._handle} + rpc_args['name'] = name + if options is not None: + rpc_args['options'] = options + result = self._client.invoke_capability( + 'Aspire.Hosting/createNumberInput', + rpc_args, + ) + return typing.cast(InteractionInputBuilder, result) + + def create_choice_input(self, name: str, *, choices: typing.Iterable[InteractionChoiceOption] | None = None, options: CreateInteractionInputOptions | None = None) -> InteractionInputBuilder: + """Creates a choice input that selects from a list of options.""" + rpc_args: dict[str, typing.Any] = {'interactionService': self._handle} + rpc_args['name'] = name + if choices is not None: + rpc_args['choices'] = choices + if options is not None: + rpc_args['options'] = options + result = self._client.invoke_capability( + 'Aspire.Hosting/createChoiceInput', + rpc_args, + ) + return typing.cast(InteractionInputBuilder, result) + + class AbstractLogger: """Type class for AbstractLogger.""" @@ -3136,6 +3354,15 @@ def get_eventing(self) -> AbstractDistributedApplicationEventing: ) return typing.cast(AbstractDistributedApplicationEventing, result) + def get_interaction_service(self) -> AbstractInteractionService: + """Gets the interaction service from the service provider.""" + rpc_args: dict[str, typing.Any] = {'serviceProvider': self._handle} + result = self._client.invoke_capability( + 'Aspire.Hosting/getInteractionService', + rpc_args, + ) + return typing.cast(AbstractInteractionService, result) + def get_logger_factory(self) -> AbstractLoggerFactory: """Gets the logger factory from the service provider.""" rpc_args: dict[str, typing.Any] = {'serviceProvider': self._handle} @@ -4800,6 +5027,15 @@ def handle(self) -> Handle: """The underlying object reference handle.""" return self._handle + @_cached_property + def services(self) -> AbstractServiceProvider: + """The service provider.""" + result = self._client.invoke_capability( + 'Aspire.Hosting.ApplicationModel/ExecuteCommandContext.services', + {'context': self._handle} + ) + return typing.cast(AbstractServiceProvider, result) + @_cached_property def resource_name(self) -> str: """The resource name.""" @@ -4991,6 +5227,88 @@ def add_validation_error(self, input_name: str, error_message: str) -> None: ) +class InputsInteractionResult: + """Type class for InputsInteractionResult.""" + + def __init__(self, handle: Handle, client: AspireClient) -> None: + self._handle = handle + self._client = client + + def __repr__(self) -> str: + return f"InputsInteractionResult(handle={self._handle.handle_id})" + + @_uncached_property + def handle(self) -> Handle: + """The underlying object reference handle.""" + return self._handle + + @_cached_property + def canceled(self) -> bool: + """Gets a value indicating whether the interaction was canceled by the user.""" + result = self._client.invoke_capability( + 'Aspire.Hosting.Ats/InputsInteractionResult.canceled', + {'context': self._handle} + ) + return typing.cast(bool, result) + + @_cached_property + def inputs(self) -> InteractionInputCollection: + """Gets the inputs returned from the interaction. Empty when `Canceled` is `true`.""" + result = self._client.invoke_capability( + 'Aspire.Hosting.Ats/InputsInteractionResult.inputs', + {'context': self._handle} + ) + return typing.cast(InteractionInputCollection, result) + + +class InteractionInputBuilder: + """Type class for InteractionInputBuilder.""" + + def __init__(self, handle: Handle, client: AspireClient) -> None: + self._handle = handle + self._client = client + + def __repr__(self) -> str: + return f"InteractionInputBuilder(handle={self._handle.handle_id})" + + @_uncached_property + def handle(self) -> Handle: + """The underlying object reference handle.""" + return self._handle + + def with_choice_options(self, choices: typing.Iterable[InteractionChoiceOption]) -> InteractionInputBuilder: + """Sets the choice options for the input.""" + rpc_args: dict[str, typing.Any] = {'context': self._handle} + rpc_args['choices'] = choices + result = self._client.invoke_capability( + 'Aspire.Hosting.Ats/withChoiceOptions', + rpc_args, + ) + return typing.cast(InteractionInputBuilder, result) + + def with_value(self, value: str) -> InteractionInputBuilder: + """Sets the value of the input.""" + rpc_args: dict[str, typing.Any] = {'context': self._handle} + rpc_args['value'] = value + result = self._client.invoke_capability( + 'Aspire.Hosting.Ats/withValue', + rpc_args, + ) + return typing.cast(InteractionInputBuilder, result) + + def with_dynamic_loading(self, callback: typing.Callable[[InteractionInputLoadContext], None], *, options: DynamicLoadingOptions | None = None) -> InteractionInputBuilder: + """Attaches a callback that dynamically loads or updates the input after the prompt starts.""" + rpc_args: dict[str, typing.Any] = {'context': self._handle} + rpc_args['callback'] = self._client.register_callback(callback) + if options is not None: + rpc_args['options'] = options + result = self._client.invoke_capability( + 'Aspire.Hosting.Ats/withDynamicLoading', + rpc_args, + ) + return typing.cast(InteractionInputBuilder, result) + + class InteractionInputCollection: """Type class for InteractionInputCollection.""" @@ -5015,6 +5333,110 @@ def to_array(self) -> typing.Iterable[InteractionInput]: ) return result + def get(self, name: str) -> InteractionInput | None: + """Get the input with the specified name, or None if no input matches.""" + lookup_name = name.lower() + for interaction_input in self.to_array(): + input_name = interaction_input.get("Name") + if input_name is not None and input_name.lower() == lookup_name: + return interaction_input + return None + + def required(self, name: str) -> InteractionInput: + """Get the input with the specified name, or raise ValueError if no input matches.""" + interaction_input = self.get(name) + if interaction_input is None: + raise ValueError(f"no input with name '{name}' was found") + return interaction_input + + def value(self, name: str) -> str: + """Get the input value with the specified name, or an empty string if no input matches.""" + interaction_input = self.get(name) + if interaction_input is None: + return "" + return interaction_input.get("Value") or "" + + def required_value(self, name: str) -> str: + """Get the input value with the specified name, or raise ValueError if no input matches.""" + return self.required(name).get("Value") or "" + + +class InteractionInputLoadContext: + """Type class for InteractionInputLoadContext.""" + + def __init__(self, handle: Handle, client: AspireClient) -> None: + self._handle = handle + self._client = client + + def __repr__(self) -> str: + return f"InteractionInputLoadContext(handle={self._handle.handle_id})" + + @_uncached_property + def handle(self) -> Handle: + """The underlying object reference handle.""" + return self._handle + + @_cached_property + def inputs(self) -> InteractionInputCollection: + """Gets all inputs in the prompt, including the one currently loading.""" + result = self._client.invoke_capability( + 'Aspire.Hosting.Ats/InteractionInputLoadContext.inputs', + {'context': self._handle} + ) + return typing.cast(InteractionInputCollection, result) + + def input(self) -> InteractionLoadingInput: + """Gets a handle to the input that is loading. Mutate the input through this handle.""" + rpc_args: dict[str, typing.Any] = {'context': self._handle} + result = self._client.invoke_capability( + 'Aspire.Hosting.Ats/input', + rpc_args, + ) + return typing.cast(InteractionLoadingInput, result) + + +class InteractionLoadingInput: + """Type class for InteractionLoadingInput.""" + + def __init__(self, handle: Handle, client: AspireClient) -> None: + self._handle = handle + self._client = client + + def __repr__(self) -> str: + return f"InteractionLoadingInput(handle={self._handle.handle_id})" + + @_uncached_property + def handle(self) -> Handle: + """The underlying object reference handle.""" + return self._handle + + def get_name(self) -> str: + """Gets the name of the input.""" + rpc_args: dict[str, typing.Any] = {'context': self._handle} + result = self._client.invoke_capability( + 'Aspire.Hosting.Ats/getName', + rpc_args, + ) + return result + + def set_choice_options(self, choices: typing.Iterable[InteractionChoiceOption]) -> None: + """Sets the choice options for the input.""" + rpc_args: dict[str, typing.Any] = {'context': self._handle} + rpc_args['choices'] = choices + self._client.invoke_capability( + 'Aspire.Hosting.Ats/setChoiceOptions', + rpc_args + ) + + def set_value(self, value: str) -> None: + """Sets the value of the input.""" + rpc_args: dict[str, typing.Any] = {'context': self._handle} + rpc_args['value'] = value + self._client.invoke_capability( + 'Aspire.Hosting.Ats/setValue', + rpc_args + ) + class LogFacade: """Type class for LogFacade.""" @@ -11603,6 +12025,7 @@ def create_builder( _register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IExecutionConfigurationBuilder", AbstractExecutionConfigurationBuilder) _register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IExecutionConfigurationResult", AbstractExecutionConfigurationResult) _register_handle_wrapper("Microsoft.Extensions.Hosting.Abstractions/Microsoft.Extensions.Hosting.IHostEnvironment", AbstractHostEnvironment) +_register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.IInteractionService", AbstractInteractionService) _register_handle_wrapper("Microsoft.Extensions.Logging.Abstractions/Microsoft.Extensions.Logging.ILogger", AbstractLogger) _register_handle_wrapper("Microsoft.Extensions.Logging.Abstractions/Microsoft.Extensions.Logging.ILoggerFactory", AbstractLoggerFactory) _register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.Pipelines.IReportingStep", AbstractReportingStep) @@ -11640,7 +12063,11 @@ def create_builder( _register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.HttpCommandPrepareRequestContext", HttpCommandPrepareRequestContext) _register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.InitializeResourceEvent", InitializeResourceEvent) _register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.InputsDialogValidationContext", InputsDialogValidationContext) +_register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.Ats.InputsInteractionResult", InputsInteractionResult) +_register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder", InteractionInputBuilder) _register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.InteractionInputCollection", InteractionInputCollection) +_register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputLoadContext", InteractionInputLoadContext) +_register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.Ats.InteractionLoadingInput", InteractionLoadingInput) _register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.ApplicationModel.LogFacade", LogFacade) _register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineConfigurationContext", PipelineConfigurationContext) _register_handle_wrapper("Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineContext", PipelineContext) diff --git a/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.rs b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.rs index f4fda492d81..9d2f3d96e2e 100644 --- a/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.rs +++ b/tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.rs @@ -384,6 +384,37 @@ impl std::fmt::Display for HealthStatus { } } +/// MessageIntent +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum MessageIntent { + #[default] + #[serde(rename = "None")] + None, + #[serde(rename = "Success")] + Success, + #[serde(rename = "Warning")] + Warning, + #[serde(rename = "Error")] + Error, + #[serde(rename = "Information")] + Information, + #[serde(rename = "Confirmation")] + Confirmation, +} + +impl std::fmt::Display for MessageIntent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::None => write!(f, "None"), + Self::Success => write!(f, "Success"), + Self::Warning => write!(f, "Warning"), + Self::Error => write!(f, "Error"), + Self::Information => write!(f, "Information"), + Self::Confirmation => write!(f, "Confirmation"), + } + } +} + /// ResourceCommandVisibility #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum ResourceCommandVisibility { @@ -562,8 +593,6 @@ pub struct InteractionInput { pub required: Option, #[serde(rename = "Options")] pub options: Vec, - #[serde(rename = "DynamicLoading", skip_serializing_if = "Option::is_none")] - pub dynamic_loading: Option, #[serde(rename = "Value")] pub value: String, #[serde(rename = "Placeholder", skip_serializing_if = "Option::is_none")] @@ -594,9 +623,6 @@ impl InteractionInput { map.insert("Required".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); } map.insert("Options".to_string(), serde_json::to_value(&self.options).unwrap_or(Value::Null)); - if let Some(ref v) = self.dynamic_loading { - map.insert("DynamicLoading".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); - } map.insert("Value".to_string(), serde_json::to_value(&self.value).unwrap_or(Value::Null)); if let Some(ref v) = self.placeholder { map.insert("Placeholder".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); @@ -801,6 +827,279 @@ impl HealthCheckResult { } } +/// InteractionChoiceOption +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct InteractionChoiceOption { + #[serde(rename = "Value")] + pub value: String, + #[serde(rename = "Label")] + pub label: String, +} + +impl InteractionChoiceOption { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert("Value".to_string(), serde_json::to_value(&self.value).unwrap_or(Value::Null)); + map.insert("Label".to_string(), serde_json::to_value(&self.label).unwrap_or(Value::Null)); + map + } +} + +/// CreateInteractionInputOptions +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct CreateInteractionInputOptions { + #[serde(rename = "Label", skip_serializing_if = "Option::is_none")] + pub label: Option, + #[serde(rename = "Description", skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(rename = "EnableDescriptionMarkdown", skip_serializing_if = "Option::is_none")] + pub enable_description_markdown: Option, + #[serde(rename = "Required", skip_serializing_if = "Option::is_none")] + pub required: Option, + #[serde(rename = "Placeholder", skip_serializing_if = "Option::is_none")] + pub placeholder: Option, + #[serde(rename = "Value", skip_serializing_if = "Option::is_none")] + pub value: Option, + #[serde(rename = "AllowCustomChoice", skip_serializing_if = "Option::is_none")] + pub allow_custom_choice: Option, + #[serde(rename = "Disabled", skip_serializing_if = "Option::is_none")] + pub disabled: Option, + #[serde(rename = "MaxLength", skip_serializing_if = "Option::is_none")] + pub max_length: Option, +} + +impl CreateInteractionInputOptions { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + if let Some(ref v) = self.label { + map.insert("Label".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.description { + map.insert("Description".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.enable_description_markdown { + map.insert("EnableDescriptionMarkdown".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.required { + map.insert("Required".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.placeholder { + map.insert("Placeholder".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.value { + map.insert("Value".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.allow_custom_choice { + map.insert("AllowCustomChoice".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.disabled { + map.insert("Disabled".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.max_length { + map.insert("MaxLength".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + map + } +} + +/// DynamicLoadingOptions +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct DynamicLoadingOptions { + #[serde(rename = "AlwaysLoadOnStart", skip_serializing_if = "Option::is_none")] + pub always_load_on_start: Option, + #[serde(rename = "DependsOnInputs", skip_serializing_if = "Option::is_none")] + pub depends_on_inputs: Option>, +} + +impl DynamicLoadingOptions { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + if let Some(ref v) = self.always_load_on_start { + map.insert("AlwaysLoadOnStart".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.depends_on_inputs { + map.insert("DependsOnInputs".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + map + } +} + +/// InteractionMessageBoxOptions +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct InteractionMessageBoxOptions { + #[serde(rename = "PrimaryButtonText", skip_serializing_if = "Option::is_none")] + pub primary_button_text: Option, + #[serde(rename = "SecondaryButtonText", skip_serializing_if = "Option::is_none")] + pub secondary_button_text: Option, + #[serde(rename = "ShowSecondaryButton", skip_serializing_if = "Option::is_none")] + pub show_secondary_button: Option, + #[serde(rename = "ShowDismiss", skip_serializing_if = "Option::is_none")] + pub show_dismiss: Option, + #[serde(rename = "EnableMessageMarkdown", skip_serializing_if = "Option::is_none")] + pub enable_message_markdown: Option, + #[serde(rename = "Intent", skip_serializing_if = "Option::is_none")] + pub intent: Option, +} + +impl InteractionMessageBoxOptions { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + if let Some(ref v) = self.primary_button_text { + map.insert("PrimaryButtonText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.secondary_button_text { + map.insert("SecondaryButtonText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.show_secondary_button { + map.insert("ShowSecondaryButton".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.show_dismiss { + map.insert("ShowDismiss".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.enable_message_markdown { + map.insert("EnableMessageMarkdown".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.intent { + map.insert("Intent".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + map + } +} + +/// InteractionNotificationOptions +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct InteractionNotificationOptions { + #[serde(rename = "PrimaryButtonText", skip_serializing_if = "Option::is_none")] + pub primary_button_text: Option, + #[serde(rename = "SecondaryButtonText", skip_serializing_if = "Option::is_none")] + pub secondary_button_text: Option, + #[serde(rename = "ShowSecondaryButton", skip_serializing_if = "Option::is_none")] + pub show_secondary_button: Option, + #[serde(rename = "ShowDismiss", skip_serializing_if = "Option::is_none")] + pub show_dismiss: Option, + #[serde(rename = "EnableMessageMarkdown", skip_serializing_if = "Option::is_none")] + pub enable_message_markdown: Option, + #[serde(rename = "Intent", skip_serializing_if = "Option::is_none")] + pub intent: Option, + #[serde(rename = "LinkText", skip_serializing_if = "Option::is_none")] + pub link_text: Option, + #[serde(rename = "LinkUrl", skip_serializing_if = "Option::is_none")] + pub link_url: Option, +} + +impl InteractionNotificationOptions { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + if let Some(ref v) = self.primary_button_text { + map.insert("PrimaryButtonText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.secondary_button_text { + map.insert("SecondaryButtonText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.show_secondary_button { + map.insert("ShowSecondaryButton".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.show_dismiss { + map.insert("ShowDismiss".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.enable_message_markdown { + map.insert("EnableMessageMarkdown".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.intent { + map.insert("Intent".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.link_text { + map.insert("LinkText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.link_url { + map.insert("LinkUrl".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + map + } +} + +/// InteractionInputsDialogOptions +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct InteractionInputsDialogOptions { + #[serde(rename = "PrimaryButtonText", skip_serializing_if = "Option::is_none")] + pub primary_button_text: Option, + #[serde(rename = "SecondaryButtonText", skip_serializing_if = "Option::is_none")] + pub secondary_button_text: Option, + #[serde(rename = "ShowSecondaryButton", skip_serializing_if = "Option::is_none")] + pub show_secondary_button: Option, + #[serde(rename = "ShowDismiss", skip_serializing_if = "Option::is_none")] + pub show_dismiss: Option, + #[serde(rename = "EnableMessageMarkdown", skip_serializing_if = "Option::is_none")] + pub enable_message_markdown: Option, + #[serde(rename = "ValidationCallback", skip_serializing_if = "Option::is_none")] + pub validation_callback: Option, +} + +impl InteractionInputsDialogOptions { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + if let Some(ref v) = self.primary_button_text { + map.insert("PrimaryButtonText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.secondary_button_text { + map.insert("SecondaryButtonText".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.show_secondary_button { + map.insert("ShowSecondaryButton".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.show_dismiss { + map.insert("ShowDismiss".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.enable_message_markdown { + map.insert("EnableMessageMarkdown".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = self.validation_callback { + map.insert("ValidationCallback".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + map + } +} + +/// BoolInteractionResult +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct BoolInteractionResult { + #[serde(rename = "Canceled")] + pub canceled: bool, + #[serde(rename = "Value", skip_serializing_if = "Option::is_none")] + pub value: Option, +} + +impl BoolInteractionResult { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert("Canceled".to_string(), serde_json::to_value(&self.canceled).unwrap_or(Value::Null)); + if let Some(ref v) = self.value { + map.insert("Value".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + map + } +} + +/// InputInteractionResult +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct InputInteractionResult { + #[serde(rename = "Canceled")] + pub canceled: bool, + #[serde(rename = "Input", skip_serializing_if = "Option::is_none")] + pub input: Option, +} + +impl InputInteractionResult { + pub fn to_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert("Canceled".to_string(), serde_json::to_value(&self.canceled).unwrap_or(Value::Null)); + if let Some(ref v) = self.input { + map.insert("Input".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + map + } +} + /// ResourceEventDto #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct ResourceEventDto { @@ -8963,6 +9262,15 @@ impl ExecuteCommandContext { &self.client } + /// The service provider. + pub fn services(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.ApplicationModel/ExecuteCommandContext.services", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IServiceProvider::new(handle, self.client.clone())) + } + /// The resource name. pub fn resource_name(&self) -> Result> { let mut args: HashMap = HashMap::new(); @@ -10775,6 +11083,197 @@ impl IHostEnvironment { } } +/// Wrapper for Aspire.Hosting/Aspire.Hosting.IInteractionService +pub struct IInteractionService { + handle: Handle, + client: Arc, +} + +impl HasHandle for IInteractionService { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl IInteractionService { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets a value indicating whether the interaction service is available to prompt the user. + pub fn is_available(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("interactionService".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/isAvailable", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Prompts the user for confirmation with an OK/Cancel dialog. + pub fn prompt_confirmation(&self, title: &str, message: &str, options: Option, cancellation_token: Option<&CancellationToken>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("interactionService".to_string(), self.handle.to_json()); + args.insert("title".to_string(), serde_json::to_value(&title).unwrap_or(Value::Null)); + args.insert("message".to_string(), serde_json::to_value(&message).unwrap_or(Value::Null)); + if let Some(ref v) = options { + args.insert("options".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(token) = cancellation_token { + let token_id = register_cancellation(token, self.client.clone()); + args.insert("cancellationToken".to_string(), Value::String(token_id)); + } + let result = self.client.invoke_capability("Aspire.Hosting/promptConfirmation", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Prompts the user with a message box dialog. + pub fn prompt_message_box(&self, title: &str, message: &str, options: Option, cancellation_token: Option<&CancellationToken>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("interactionService".to_string(), self.handle.to_json()); + args.insert("title".to_string(), serde_json::to_value(&title).unwrap_or(Value::Null)); + args.insert("message".to_string(), serde_json::to_value(&message).unwrap_or(Value::Null)); + if let Some(ref v) = options { + args.insert("options".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(token) = cancellation_token { + let token_id = register_cancellation(token, self.client.clone()); + args.insert("cancellationToken".to_string(), Value::String(token_id)); + } + let result = self.client.invoke_capability("Aspire.Hosting/promptMessageBox", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Prompts the user with a notification. + pub fn prompt_notification(&self, title: &str, message: &str, options: Option, cancellation_token: Option<&CancellationToken>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("interactionService".to_string(), self.handle.to_json()); + args.insert("title".to_string(), serde_json::to_value(&title).unwrap_or(Value::Null)); + args.insert("message".to_string(), serde_json::to_value(&message).unwrap_or(Value::Null)); + if let Some(ref v) = options { + args.insert("options".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(token) = cancellation_token { + let token_id = register_cancellation(token, self.client.clone()); + args.insert("cancellationToken".to_string(), Value::String(token_id)); + } + let result = self.client.invoke_capability("Aspire.Hosting/promptNotification", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Prompts the user for a single input. + pub fn prompt_input(&self, title: &str, message: &str, input: &InteractionInputBuilder, options: Option, cancellation_token: Option<&CancellationToken>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("interactionService".to_string(), self.handle.to_json()); + args.insert("title".to_string(), serde_json::to_value(&title).unwrap_or(Value::Null)); + args.insert("message".to_string(), serde_json::to_value(&message).unwrap_or(Value::Null)); + args.insert("input".to_string(), input.handle().to_json()); + if let Some(ref v) = options { + args.insert("options".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(token) = cancellation_token { + let token_id = register_cancellation(token, self.client.clone()); + args.insert("cancellationToken".to_string(), Value::String(token_id)); + } + let result = self.client.invoke_capability("Aspire.Hosting/promptInput", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Prompts the user for multiple inputs. + pub fn prompt_inputs(&self, title: &str, message: &str, inputs: Vec, options: Option, cancellation_token: Option<&CancellationToken>) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("interactionService".to_string(), self.handle.to_json()); + args.insert("title".to_string(), serde_json::to_value(&title).unwrap_or(Value::Null)); + args.insert("message".to_string(), serde_json::to_value(&message).unwrap_or(Value::Null)); + let handles: Vec = inputs.iter().map(|item| item.handle().to_json()).collect(); + args.insert("inputs".to_string(), Value::Array(handles)); + if let Some(ref v) = options { + args.insert("options".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(token) = cancellation_token { + let token_id = register_cancellation(token, self.client.clone()); + args.insert("cancellationToken".to_string(), Value::String(token_id)); + } + let result = self.client.invoke_capability("Aspire.Hosting/promptInputs", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(InputsInteractionResult::new(handle, self.client.clone())) + } + + /// Creates a single-line text input. + pub fn create_text_input(&self, name: &str, options: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("interactionService".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + if let Some(ref v) = options { + args.insert("options".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/createTextInput", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(InteractionInputBuilder::new(handle, self.client.clone())) + } + + /// Creates a secret (masked) text input. + pub fn create_secret_input(&self, name: &str, options: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("interactionService".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + if let Some(ref v) = options { + args.insert("options".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/createSecretInput", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(InteractionInputBuilder::new(handle, self.client.clone())) + } + + /// Creates a boolean (checkbox) input. + pub fn create_boolean_input(&self, name: &str, options: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("interactionService".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + if let Some(ref v) = options { + args.insert("options".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/createBooleanInput", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(InteractionInputBuilder::new(handle, self.client.clone())) + } + + /// Creates a numeric input. + pub fn create_number_input(&self, name: &str, options: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("interactionService".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + if let Some(ref v) = options { + args.insert("options".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/createNumberInput", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(InteractionInputBuilder::new(handle, self.client.clone())) + } + + /// Creates a choice input that selects from a list of options. + pub fn create_choice_input(&self, name: &str, choices: Option>, options: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("interactionService".to_string(), self.handle.to_json()); + args.insert("name".to_string(), serde_json::to_value(&name).unwrap_or(Value::Null)); + if let Some(ref v) = choices { + args.insert("choices".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + if let Some(ref v) = options { + args.insert("options".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting/createChoiceInput", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(InteractionInputBuilder::new(handle, self.client.clone())) + } +} + /// Wrapper for Microsoft.Extensions.Logging.Abstractions/Microsoft.Extensions.Logging.ILogger pub struct ILogger { handle: Handle, @@ -11345,6 +11844,15 @@ impl IServiceProvider { Ok(IDistributedApplicationEventing::new(handle, self.client.clone())) } + /// Gets the interaction service from the service provider. + pub fn get_interaction_service(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("serviceProvider".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting/getInteractionService", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(IInteractionService::new(handle, self.client.clone())) + } + /// Gets the logger factory from the service provider. pub fn get_logger_factory(&self) -> Result> { let mut args: HashMap = HashMap::new(); @@ -11636,6 +12144,109 @@ impl InputsDialogValidationContext { } } +/// Wrapper for Aspire.Hosting/Aspire.Hosting.Ats.InputsInteractionResult +pub struct InputsInteractionResult { + handle: Handle, + client: Arc, +} + +impl HasHandle for InputsInteractionResult { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl InputsInteractionResult { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets a value indicating whether the interaction was canceled by the user. + pub fn canceled(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.Ats/InputsInteractionResult.canceled", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Gets the inputs returned from the interaction. Empty when `Canceled` is `true`. + pub fn inputs(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.Ats/InputsInteractionResult.inputs", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(InteractionInputCollection::new(handle, self.client.clone())) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder +pub struct InteractionInputBuilder { + handle: Handle, + client: Arc, +} + +impl HasHandle for InteractionInputBuilder { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl InteractionInputBuilder { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Sets the choice options for the input. + pub fn with_choice_options(&self, choices: Vec) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("choices".to_string(), serde_json::to_value(&choices).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.Ats/withChoiceOptions", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(InteractionInputBuilder::new(handle, self.client.clone())) + } + + /// Sets the value of the input. + pub fn with_value(&self, value: &str) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.Ats/withValue", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(InteractionInputBuilder::new(handle, self.client.clone())) + } + + /// Attaches a callback that dynamically loads or updates the input after the prompt starts. + pub fn with_dynamic_loading(&self, callback: impl Fn(Vec) -> Value + Send + Sync + 'static, options: Option) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let callback_id = register_callback(callback); + args.insert("callback".to_string(), Value::String(callback_id)); + if let Some(ref v) = options { + args.insert("options".to_string(), serde_json::to_value(v).unwrap_or(Value::Null)); + } + let result = self.client.invoke_capability("Aspire.Hosting.Ats/withDynamicLoading", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(InteractionInputBuilder::new(handle, self.client.clone())) + } +} + /// Wrapper for Aspire.Hosting/Aspire.Hosting.InteractionInputCollection pub struct InteractionInputCollection { handle: Handle, @@ -11670,6 +12281,102 @@ impl InteractionInputCollection { } } +/// Wrapper for Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputLoadContext +pub struct InteractionInputLoadContext { + handle: Handle, + client: Arc, +} + +impl HasHandle for InteractionInputLoadContext { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl InteractionInputLoadContext { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets all inputs in the prompt, including the one currently loading. + pub fn inputs(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.Ats/InteractionInputLoadContext.inputs", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(InteractionInputCollection::new(handle, self.client.clone())) + } + + /// Gets a handle to the input that is loading. Mutate the input through this handle. + pub fn input(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.Ats/input", args)?; + let handle: Handle = serde_json::from_value(result)?; + Ok(InteractionLoadingInput::new(handle, self.client.clone())) + } +} + +/// Wrapper for Aspire.Hosting/Aspire.Hosting.Ats.InteractionLoadingInput +pub struct InteractionLoadingInput { + handle: Handle, + client: Arc, +} + +impl HasHandle for InteractionLoadingInput { + fn handle(&self) -> &Handle { + &self.handle + } +} + +impl InteractionLoadingInput { + pub fn new(handle: Handle, client: Arc) -> Self { + Self { handle, client } + } + + pub fn handle(&self) -> &Handle { + &self.handle + } + + pub fn client(&self) -> &Arc { + &self.client + } + + /// Gets the name of the input. + pub fn get_name(&self) -> Result> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + let result = self.client.invoke_capability("Aspire.Hosting.Ats/getName", args)?; + Ok(serde_json::from_value(result)?) + } + + /// Sets the choice options for the input. + pub fn set_choice_options(&self, choices: Vec) -> Result<(), Box> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("choices".to_string(), serde_json::to_value(&choices).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.Ats/setChoiceOptions", args)?; + Ok(()) + } + + /// Sets the value of the input. + pub fn set_value(&self, value: &str) -> Result<(), Box> { + let mut args: HashMap = HashMap::new(); + args.insert("context".to_string(), self.handle.to_json()); + args.insert("value".to_string(), serde_json::to_value(&value).unwrap_or(Value::Null)); + let result = self.client.invoke_capability("Aspire.Hosting.Ats/setValue", args)?; + Ok(()) + } +} + /// Wrapper for Aspire.Hosting/Aspire.Hosting.ApplicationModel.LogFacade pub struct LogFacade { handle: Handle, diff --git a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs index 3b58a0259f7..58a90d331aa 100644 --- a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs +++ b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs @@ -111,7 +111,7 @@ public void GenerateDistributedApplication_WithHostingTypes_KeepsReferenceExpres Assert.Contains("condition: extractHandleForExpr(state.condition),", files["base.mts"]); Assert.Contains("('$handle' in json || '$expr' in json)", files["base.mts"]); Assert.Contains("registerCancellation(state.client, cancellationToken)", files["base.mts"]); - Assert.Contains("arguments(): Promise", aspireTs); + Assert.Contains("arguments(): InteractionInputCollectionPromise", aspireTs); Assert.DoesNotContain("setArguments", aspireTs); } diff --git a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/AtsGeneratedAspire.verified.ts b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/AtsGeneratedAspire.verified.ts index 214adcf2ddb..1032ac8f00a 100644 --- a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/AtsGeneratedAspire.verified.ts +++ b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/AtsGeneratedAspire.verified.ts @@ -25,7 +25,8 @@ import { ReferenceExpression, refExpr, AspireDict, - AspireList + AspireList, + InteractionInputCollectionPromiseImpl } from './base.mjs'; export { @@ -35,13 +36,15 @@ export { export type { InteractionInput, - InteractionInputOption + InteractionInputOption, + InteractionInputCollectionPromise } from './base.mjs'; import type { Awaitable, InteractionInput, InteractionInputCollection, + InteractionInputCollectionPromise, InputType } from './base.mjs'; diff --git a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.ts b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.ts index 3444213199c..d0dc2e16ace 100644 --- a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.ts +++ b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.ts @@ -25,7 +25,8 @@ import { ReferenceExpression, refExpr, AspireDict, - AspireList + AspireList, + InteractionInputCollectionPromiseImpl } from './base.mjs'; export { @@ -35,13 +36,15 @@ export { export type { InteractionInput, - InteractionInputOption + InteractionInputOption, + InteractionInputCollectionPromise } from './base.mjs'; import type { Awaitable, InteractionInput, InteractionInputCollection, + InteractionInputCollectionPromise, InputType } from './base.mjs'; @@ -312,6 +315,37 @@ type UpdateCommandStateContextHandle = Handle<'Aspire.Hosting/Aspire.Hosting.App /** Context passed to ATS-friendly eventing subscriber registrations. */ type EventingSubscriberRegistrationContextHandle = Handle<'Aspire.Hosting/Aspire.Hosting.Ats.EventingSubscriberRegistrationContext'>; +/** + * The result of a multi-input interaction prompt. + * + * Modeled as a handle (not a by-value DTO) so the returned inputs are surfaced as the + * `InteractionInputCollection` handle. That lets polyglot callers reuse the same name-based + * accessors (for example `result.inputs().value("color")`) that the validation and command-argument + * collections already expose, instead of having to scan a serialized array by hand. + */ +type InputsInteractionResultHandle = Handle<'Aspire.Hosting/Aspire.Hosting.Ats.InputsInteractionResult'>; + +/** + * An opaque, server-side builder for an `InteractionInput` used by polyglot app hosts. + * + * The builder owns the live `InteractionInput` instance. Dynamic-loading callbacks mutate this same + * instance through `InteractionInputLoadContext`, which is why the input is modeled as a handle here + * instead of the by-value `InteractionInput` DTO. + */ +type InteractionInputBuilderHandle = Handle<'Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder'>; + +/** The context passed to a polyglot dynamic-loading callback. Exposes the loading input as a handle and provides read access to the other inputs in the prompt. */ +type InteractionInputLoadContextHandle = Handle<'Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputLoadContext'>; + +/** + * A handle to the input currently being loaded by a dynamic-loading callback. Mirrors the native `LoadInputContext.Input` by letting callbacks update the live input directly. + * + * The handle owns the live `InteractionInput` for the duration of the load callback. Setters are routed + * back to the server-side input across the ATS boundary, which is why this is a handle rather than the by-value + * `InteractionInput` DTO. + */ +type InteractionLoadingInputHandle = Handle<'Aspire.Hosting/Aspire.Hosting.Ats.InteractionLoadingInput'>; + /** Represents a distributed application that implements the {@link IHost} and {@link IAsyncDisposable} interfaces. */ type DistributedApplicationHandle = Handle<'Aspire.Hosting/Aspire.Hosting.DistributedApplication'>; @@ -330,6 +364,9 @@ type ExternalServiceResourceHandle = Handle<'Aspire.Hosting/Aspire.Hosting.Exter /** A builder for creating instances of {@link DistributedApplication}. */ type IDistributedApplicationBuilderHandle = Handle<'Aspire.Hosting/Aspire.Hosting.IDistributedApplicationBuilder'>; +/** A service to interact with the current development environment. */ +type IInteractionServiceHandle = Handle<'Aspire.Hosting/Aspire.Hosting.IInteractionService'>; + /** Represents the context for validating inputs in an inputs dialog interaction. */ type InputsDialogValidationContextHandle = Handle<'Aspire.Hosting/Aspire.Hosting.InputsDialogValidationContext'>; @@ -533,6 +570,22 @@ export enum ImagePullPolicy { Never = "Never", } +/** Specifies the intent or purpose of a message in an interaction. */ +export enum MessageIntent { + /** No specific intent. */ + None = "None", + /** Indicates a successful operation. */ + Success = "Success", + /** Indicates a warning. */ + Warning = "Warning", + /** Indicates an error. */ + Error = "Error", + /** Provides informational content. */ + Information = "Information", + /** Requests confirmation from the user. */ + Confirmation = "Confirmation", +} + /** Protocols available for OTLP exporters. */ export enum OtlpProtocol { /** A gRPC-based OTLP exporter. */ @@ -649,6 +702,14 @@ export interface AddContainerOptions { tag?: string | null; } +/** The result of a boolean interaction prompt. */ +export interface BoolInteractionResult { + /** Gets a value indicating whether the interaction was canceled by the user. */ + canceled?: boolean; + /** Gets the value returned from the interaction. Not meaningful when `Canceled` is `true`. */ + value?: boolean; +} + /** Context for configuring certificate trust configuration properties. */ export interface CertificateTrustExecutionConfigurationContext { /** The path to the PEM certificate bundle file in the resource context (e.g., container filesystem). */ @@ -757,6 +818,36 @@ export interface CreateBuilderOptions { throwOnPendingRejections?: boolean; } +/** Optional configuration shared by interaction input factory capabilities. */ +export interface CreateInteractionInputOptions { + /** Gets or sets the label for the input. Defaults to the input name when not specified. */ + label?: string | null; + /** Gets or sets the description for the input. */ + description?: string | null; + /** Gets or sets a value indicating whether the description is rendered as Markdown. */ + enableDescriptionMarkdown?: boolean | null; + /** Gets or sets a value indicating whether the input is required. */ + required?: boolean | null; + /** Gets or sets the placeholder text for the input. */ + placeholder?: string | null; + /** Gets or sets the initial value of the input. */ + value?: string | null; + /** Gets or sets a value indicating whether a custom choice is allowed. Only used by choice inputs. */ + allowCustomChoice?: boolean | null; + /** Gets or sets a value indicating whether the input is disabled. */ + disabled?: boolean | null; + /** Gets or sets the maximum length for text inputs. */ + maxLength?: number | null; +} + +/** Options controlling when a dynamic-loading callback runs. */ +export interface DynamicLoadingOptions { + /** Gets or sets a value indicating whether the callback always runs at the start of the prompt. */ + alwaysLoadOnStart?: boolean | null; + /** Gets or sets the names of inputs this input depends on. The callback runs when any of them change. */ + dependsOnInputs?: string[]; +} + /** The result of executing a command. Returned from `ExecuteCommand`. */ export interface ExecuteCommandResult { /** A flag that indicates whether the command was successful. */ @@ -904,6 +995,74 @@ export interface HttpsCertificateInfo { thumbprint?: string | null; } +/** The result of a single-input interaction prompt. */ +export interface InputInteractionResult { + /** Gets a value indicating whether the interaction was canceled by the user. */ + canceled?: boolean; + /** Gets the input returned from the interaction. Not present when `Canceled` is `true`. */ + input?: InteractionInput; +} + +/** A single selectable option for a choice input. Options are presented in the order supplied. */ +export interface InteractionChoiceOption { + /** Gets or sets the value submitted when this option is selected. */ + value?: string; + /** Gets or sets the label displayed for this option. */ + label?: string; +} + +/** Options for inputs dialog prompts. */ +export interface InteractionInputsDialogOptions { + /** Gets or sets the primary button text. */ + primaryButtonText?: string | null; + /** Gets or sets the secondary button text. */ + secondaryButtonText?: string | null; + /** Gets or sets a value indicating whether the secondary button is shown. */ + showSecondaryButton?: boolean | null; + /** Gets or sets a value indicating whether the dismiss button is shown. */ + showDismiss?: boolean | null; + /** Gets or sets a value indicating whether Markdown in the message is rendered. */ + enableMessageMarkdown?: boolean | null; + /** Gets or sets a callback invoked to validate the inputs before the dialog is accepted. The callback receives a validation context that exposes the current inputs and can record validation errors. */ + validationCallback?: (arg: InputsDialogValidationContext) => Promise; +} + +/** Options for message box and confirmation prompts. */ +export interface InteractionMessageBoxOptions { + /** Gets or sets the primary button text. */ + primaryButtonText?: string | null; + /** Gets or sets the secondary button text. */ + secondaryButtonText?: string | null; + /** Gets or sets a value indicating whether the secondary button is shown. */ + showSecondaryButton?: boolean | null; + /** Gets or sets a value indicating whether the dismiss button is shown. */ + showDismiss?: boolean | null; + /** Gets or sets a value indicating whether Markdown in the message is rendered. */ + enableMessageMarkdown?: boolean | null; + /** Gets or sets the intent of the message box. */ + intent?: MessageIntent | null; +} + +/** Options for notification prompts. */ +export interface InteractionNotificationOptions { + /** Gets or sets the primary button text. */ + primaryButtonText?: string | null; + /** Gets or sets the secondary button text. */ + secondaryButtonText?: string | null; + /** Gets or sets a value indicating whether the secondary button is shown. */ + showSecondaryButton?: boolean | null; + /** Gets or sets a value indicating whether the dismiss button is shown. */ + showDismiss?: boolean | null; + /** Gets or sets a value indicating whether Markdown in the message is rendered. */ + enableMessageMarkdown?: boolean | null; + /** Gets or sets the intent of the notification. */ + intent?: MessageIntent | null; + /** Gets or sets the text for a link in the notification. */ + linkText?: string | null; + /** Gets or sets the URL for the link in the notification. */ + linkUrl?: string | null; +} + /** Options for customizing parameter inputs from polyglot app hosts. */ export interface ParameterCustomInputOptions { /** Gets or sets the type of the input. */ @@ -1286,6 +1445,13 @@ export interface CopyOptions { chown?: string; } +export interface CreateChoiceInputOptions { + /** The available choices, in display order. Each option pairs a submitted value with a display label. */ + choices?: InteractionChoiceOption[]; + /** Optional configuration for the input. */ + options?: CreateInteractionInputOptions; +} + export interface CreateMarkdownTaskOptions { cancellationToken?: AbortSignal | CancellationToken; } @@ -4982,6 +5148,8 @@ class EventingSubscriberRegistrationContextPromiseImpl implements EventingSubscr /** Context for {@link ResourceCommandAnnotation.ExecuteCommand}. */ export interface ExecuteCommandContext { toJSON(): MarshalledHandle; + /** The service provider. */ + services(): ServiceProviderPromise; /** The resource name. */ resourceName(): Promise; /** The cancellation token. */ @@ -4995,10 +5163,12 @@ export interface ExecuteCommandContext { * submitted values populated. CLI positional arguments are mapped by declaration order. Dashboard, MCP, and other * named-payload clients are mapped by `Name`. */ - arguments(): Promise; + arguments(): InteractionInputCollectionPromise; } export interface ExecuteCommandContextPromise extends PromiseLike { + /** The service provider. */ + services(): ServiceProviderPromise; /** The resource name. */ resourceName(): Promise; /** The cancellation token. */ @@ -5012,7 +5182,7 @@ export interface ExecuteCommandContextPromise extends PromiseLike; + arguments(): InteractionInputCollectionPromise; } // ============================================================================ @@ -5026,6 +5196,17 @@ class ExecuteCommandContextImpl implements ExecuteCommandContext { /** Serialize for JSON-RPC transport */ toJSON(): MarshalledHandle { return this._handle.toJSON(); } + services(): ServiceProviderPromise { + const promise = (async () => { + const handle = await this._client.invokeCapability( + 'Aspire.Hosting.ApplicationModel/ExecuteCommandContext.services', + { context: this._handle } + ); + return new ServiceProviderImpl(handle, this._client); + })(); + return new ServiceProviderPromiseImpl(promise, this._client, false); + } + async resourceName(): Promise { return await this._client.invokeCapability( 'Aspire.Hosting.ApplicationModel/ExecuteCommandContext.resourceName', @@ -5052,11 +5233,11 @@ class ExecuteCommandContextImpl implements ExecuteCommandContext { return new LoggerPromiseImpl(promise, this._client, false); } - async arguments(): Promise { - return await this._client.invokeCapability( + arguments(): InteractionInputCollectionPromise { + return new InteractionInputCollectionPromiseImpl(this._client.invokeCapability( 'Aspire.Hosting.ApplicationModel/ExecuteCommandContext.arguments', { context: this._handle } - ); + ), this._client, false); } } @@ -5076,6 +5257,10 @@ class ExecuteCommandContextPromiseImpl implements ExecuteCommandContextPromise { return this._promise.then(onfulfilled, onrejected); } + services(): ServiceProviderPromise { + return new ServiceProviderPromiseImpl(this._promise.then(obj => obj.services()), this._client, false); + } + resourceName(): Promise { return this._promise.then(obj => obj.resourceName()); } @@ -5088,8 +5273,8 @@ class ExecuteCommandContextPromiseImpl implements ExecuteCommandContextPromise { return new LoggerPromiseImpl(this._promise.then(obj => obj.logger()), this._client, false); } - arguments(): Promise { - return this._promise.then(obj => obj.arguments()); + arguments(): InteractionInputCollectionPromise { + return new InteractionInputCollectionPromiseImpl(this._promise.then(obj => obj.arguments()), this._client, false); } } @@ -5108,7 +5293,7 @@ export interface HttpCommandPrepareRequestContext { /** The cancellation token. */ cancellationToken(): Promise; /** Gets the invocation arguments supplied by the client when the command is executed. */ - arguments(): Promise; + arguments(): InteractionInputCollectionPromise; } export interface HttpCommandPrepareRequestContextPromise extends PromiseLike { @@ -5119,7 +5304,7 @@ export interface HttpCommandPrepareRequestContextPromise extends PromiseLike; /** Gets the invocation arguments supplied by the client when the command is executed. */ - arguments(): Promise; + arguments(): InteractionInputCollectionPromise; } // ============================================================================ @@ -5159,11 +5344,11 @@ class HttpCommandPrepareRequestContextImpl implements HttpCommandPrepareRequestC return CancellationToken.fromValue(result); } - async arguments(): Promise { - return await this._client.invokeCapability( + arguments(): InteractionInputCollectionPromise { + return new InteractionInputCollectionPromiseImpl(this._client.invokeCapability( 'Aspire.Hosting.ApplicationModel/HttpCommandPrepareRequestContext.arguments', { context: this._handle } - ); + ), this._client, false); } } @@ -5195,8 +5380,8 @@ class HttpCommandPrepareRequestContextPromiseImpl implements HttpCommandPrepareR return this._promise.then(obj => obj.cancellationToken()); } - arguments(): Promise { - return this._promise.then(obj => obj.arguments()); + arguments(): InteractionInputCollectionPromise { + return new InteractionInputCollectionPromiseImpl(this._promise.then(obj => obj.arguments()), this._client, false); } } @@ -5356,7 +5541,7 @@ class InitializeResourceEventPromiseImpl implements InitializeResourceEventPromi export interface InputsDialogValidationContext { toJSON(): MarshalledHandle; /** Gets the inputs that are being validated. */ - inputs(): Promise; + inputs(): InteractionInputCollectionPromise; /** Gets the cancellation token for the validation operation. */ cancellationToken(): Promise; /** @@ -5369,7 +5554,7 @@ export interface InputsDialogValidationContext { export interface InputsDialogValidationContextPromise extends PromiseLike { /** Gets the inputs that are being validated. */ - inputs(): Promise; + inputs(): InteractionInputCollectionPromise; /** Gets the cancellation token for the validation operation. */ cancellationToken(): Promise; /** @@ -5391,11 +5576,11 @@ class InputsDialogValidationContextImpl implements InputsDialogValidationContext /** Serialize for JSON-RPC transport */ toJSON(): MarshalledHandle { return this._handle.toJSON(); } - async inputs(): Promise { - return await this._client.invokeCapability( + inputs(): InteractionInputCollectionPromise { + return new InteractionInputCollectionPromiseImpl(this._client.invokeCapability( 'Aspire.Hosting/InputsDialogValidationContext.inputs', { context: this._handle } - ); + ), this._client, false); } async cancellationToken(): Promise { @@ -5442,8 +5627,8 @@ class InputsDialogValidationContextPromiseImpl implements InputsDialogValidation return this._promise.then(onfulfilled, onrejected); } - inputs(): Promise { - return this._promise.then(obj => obj.inputs()); + inputs(): InteractionInputCollectionPromise { + return new InteractionInputCollectionPromiseImpl(this._promise.then(obj => obj.inputs()), this._client, false); } cancellationToken(): Promise { @@ -5456,6 +5641,523 @@ class InputsDialogValidationContextPromiseImpl implements InputsDialogValidation } +// ============================================================================ +// InputsInteractionResult +// ============================================================================ + +/** + * The result of a multi-input interaction prompt. + * + * Modeled as a handle (not a by-value DTO) so the returned inputs are surfaced as the + * `InteractionInputCollection` handle. That lets polyglot callers reuse the same name-based + * accessors (for example `result.inputs().value("color")`) that the validation and command-argument + * collections already expose, instead of having to scan a serialized array by hand. + */ +export interface InputsInteractionResult { + toJSON(): MarshalledHandle; + /** Gets a value indicating whether the interaction was canceled by the user. */ + canceled(): Promise; + /** Gets the inputs returned from the interaction. Empty when `Canceled` is `true`. */ + inputs(): InteractionInputCollectionPromise; +} + +export interface InputsInteractionResultPromise extends PromiseLike { + /** Gets a value indicating whether the interaction was canceled by the user. */ + canceled(): Promise; + /** Gets the inputs returned from the interaction. Empty when `Canceled` is `true`. */ + inputs(): InteractionInputCollectionPromise; +} + +// ============================================================================ +// InputsInteractionResultImpl +// ============================================================================ + +/** + * The result of a multi-input interaction prompt. + * + * Modeled as a handle (not a by-value DTO) so the returned inputs are surfaced as the + * `InteractionInputCollection` handle. That lets polyglot callers reuse the same name-based + * accessors (for example `result.inputs().value("color")`) that the validation and command-argument + * collections already expose, instead of having to scan a serialized array by hand. + */ +class InputsInteractionResultImpl implements InputsInteractionResult { + constructor(private _handle: InputsInteractionResultHandle, private _client: AspireClientRpc) {} + + /** Serialize for JSON-RPC transport */ + toJSON(): MarshalledHandle { return this._handle.toJSON(); } + + async canceled(): Promise { + return await this._client.invokeCapability( + 'Aspire.Hosting.Ats/InputsInteractionResult.canceled', + { context: this._handle } + ); + } + + inputs(): InteractionInputCollectionPromise { + return new InteractionInputCollectionPromiseImpl(this._client.invokeCapability( + 'Aspire.Hosting.Ats/InputsInteractionResult.inputs', + { context: this._handle } + ), this._client, false); + } + +} + +/** + * Thenable wrapper for InputsInteractionResult that enables fluent chaining. + */ +class InputsInteractionResultPromiseImpl implements InputsInteractionResultPromise { + constructor(private _promise: Promise, private _client: AspireClientRpc, track = true) { + if (track) { _client.trackPromise(_promise); } + } + + then( + onfulfilled?: ((value: InputsInteractionResult) => TResult1 | PromiseLike) | null, + onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null + ): PromiseLike { + return this._promise.then(onfulfilled, onrejected); + } + + canceled(): Promise { + return this._promise.then(obj => obj.canceled()); + } + + inputs(): InteractionInputCollectionPromise { + return new InteractionInputCollectionPromiseImpl(this._promise.then(obj => obj.inputs()), this._client, false); + } + +} + +// ============================================================================ +// InteractionInputBuilder +// ============================================================================ + +/** + * An opaque, server-side builder for an `InteractionInput` used by polyglot app hosts. + * + * The builder owns the live `InteractionInput` instance. Dynamic-loading callbacks mutate this same + * instance through `InteractionInputLoadContext`, which is why the input is modeled as a handle here + * instead of the by-value `InteractionInput` DTO. + */ +export interface InteractionInputBuilder { + toJSON(): MarshalledHandle; + /** + * Sets the choice options for the input. + * @param choices The available choices, in display order. Each option pairs a submitted value with a display label. + * @returns The same builder handle. + */ + withChoiceOptions(choices: InteractionChoiceOption[]): InteractionInputBuilderPromise; + /** + * Sets the value of the input. + * @param value The value to assign. + * @returns The same builder handle. + */ + withValue(value: string): InteractionInputBuilderPromise; + /** + * Attaches a callback that dynamically loads or updates the input after the prompt starts. + * @param callback The callback invoked to load the input. Use the supplied context to read other inputs and update this input. + * @param options Additional options. + * @returns The same builder handle. + */ + withDynamicLoading(callback: (arg: InteractionInputLoadContext) => Promise, options?: DynamicLoadingOptions): InteractionInputBuilderPromise; +} + +export interface InteractionInputBuilderPromise extends PromiseLike { + /** + * Sets the choice options for the input. + * @param choices The available choices, in display order. Each option pairs a submitted value with a display label. + * @returns The same builder handle. + */ + withChoiceOptions(choices: InteractionChoiceOption[]): InteractionInputBuilderPromise; + /** + * Sets the value of the input. + * @param value The value to assign. + * @returns The same builder handle. + */ + withValue(value: string): InteractionInputBuilderPromise; + /** + * Attaches a callback that dynamically loads or updates the input after the prompt starts. + * @param callback The callback invoked to load the input. Use the supplied context to read other inputs and update this input. + * @param options Additional options. + * @returns The same builder handle. + */ + withDynamicLoading(callback: (arg: InteractionInputLoadContext) => Promise, options?: DynamicLoadingOptions): InteractionInputBuilderPromise; +} + +// ============================================================================ +// InteractionInputBuilderImpl +// ============================================================================ + +/** + * An opaque, server-side builder for an `InteractionInput` used by polyglot app hosts. + * + * The builder owns the live `InteractionInput` instance. Dynamic-loading callbacks mutate this same + * instance through `InteractionInputLoadContext`, which is why the input is modeled as a handle here + * instead of the by-value `InteractionInput` DTO. + */ +class InteractionInputBuilderImpl implements InteractionInputBuilder { + constructor(private _handle: InteractionInputBuilderHandle, private _client: AspireClientRpc) {} + + /** Serialize for JSON-RPC transport */ + toJSON(): MarshalledHandle { return this._handle.toJSON(); } + + /** @internal */ + async _withChoiceOptionsInternal(choices: InteractionChoiceOption[]): Promise { + const rpcArgs: Record = { context: this._handle, choices }; + const result = await this._client.invokeCapability( + 'Aspire.Hosting.Ats/withChoiceOptions', + rpcArgs + ); + return new InteractionInputBuilderImpl(result, this._client); + } + + /** + * Sets the choice options for the input. + * @param choices The available choices, in display order. Each option pairs a submitted value with a display label. + * @returns The same builder handle. + */ + withChoiceOptions(choices: InteractionChoiceOption[]): InteractionInputBuilderPromise { + return new InteractionInputBuilderPromiseImpl(this._withChoiceOptionsInternal(choices), this._client); + } + + /** @internal */ + async _withValueInternal(value: string): Promise { + const rpcArgs: Record = { context: this._handle, value }; + const result = await this._client.invokeCapability( + 'Aspire.Hosting.Ats/withValue', + rpcArgs + ); + return new InteractionInputBuilderImpl(result, this._client); + } + + /** + * Sets the value of the input. + * @param value The value to assign. + * @returns The same builder handle. + */ + withValue(value: string): InteractionInputBuilderPromise { + return new InteractionInputBuilderPromiseImpl(this._withValueInternal(value), this._client); + } + + /** @internal */ + async _withDynamicLoadingInternal(callback: (arg: InteractionInputLoadContext) => Promise, options?: DynamicLoadingOptions): Promise { + const callbackId = registerCallback(async (argData: unknown) => { + const argHandle = wrapIfHandle(argData) as InteractionInputLoadContextHandle; + const arg = new InteractionInputLoadContextImpl(argHandle, this._client); + await callback(arg); + }); + const rpcArgs: Record = { context: this._handle, callback: callbackId }; + if (options !== undefined) rpcArgs.options = options; + const result = await this._client.invokeCapability( + 'Aspire.Hosting.Ats/withDynamicLoading', + rpcArgs + ); + return new InteractionInputBuilderImpl(result, this._client); + } + + /** + * Attaches a callback that dynamically loads or updates the input after the prompt starts. + * @param callback The callback invoked to load the input. Use the supplied context to read other inputs and update this input. + * @param options Additional options. + * @returns The same builder handle. + */ + withDynamicLoading(callback: (arg: InteractionInputLoadContext) => Promise, options?: DynamicLoadingOptions): InteractionInputBuilderPromise { + return new InteractionInputBuilderPromiseImpl(this._withDynamicLoadingInternal(callback, options), this._client); + } + +} + +/** + * Thenable wrapper for InteractionInputBuilder that enables fluent chaining. + */ +class InteractionInputBuilderPromiseImpl implements InteractionInputBuilderPromise { + constructor(private _promise: Promise, private _client: AspireClientRpc, track = true) { + if (track) { _client.trackPromise(_promise); } + } + + then( + onfulfilled?: ((value: InteractionInputBuilder) => TResult1 | PromiseLike) | null, + onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null + ): PromiseLike { + return this._promise.then(onfulfilled, onrejected); + } + + withChoiceOptions(choices: InteractionChoiceOption[]): InteractionInputBuilderPromise { + return new InteractionInputBuilderPromiseImpl(this._promise.then(obj => obj.withChoiceOptions(choices)), this._client); + } + + withValue(value: string): InteractionInputBuilderPromise { + return new InteractionInputBuilderPromiseImpl(this._promise.then(obj => obj.withValue(value)), this._client); + } + + withDynamicLoading(callback: (arg: InteractionInputLoadContext) => Promise, options?: DynamicLoadingOptions): InteractionInputBuilderPromise { + return new InteractionInputBuilderPromiseImpl(this._promise.then(obj => obj.withDynamicLoading(callback, options)), this._client); + } + +} + +// ============================================================================ +// InteractionInputLoadContext +// ============================================================================ + +/** The context passed to a polyglot dynamic-loading callback. Exposes the loading input as a handle and provides read access to the other inputs in the prompt. */ +export interface InteractionInputLoadContext { + toJSON(): MarshalledHandle; + /** + * Gets all inputs in the prompt, including the one currently loading. + * + * Mirrors the native `LoadInputContext.AllInputs`. Use the collection's by-name accessors (for example + * `value` or `requiredValue`) to read the dependency inputs declared via + * `DependsOnInputs`. This is the same `InteractionInputCollection` + * idiom used by the validation callback and prompt results, so reading inputs by name is consistent across every + * callback context. This is exposed as a property (rather than a method) so it routes through the generated + * collection accessor, matching the other contexts that surface an `InteractionInputCollection`. + */ + inputs(): InteractionInputCollectionPromise; + /** + * Gets a handle to the input that is loading. Mutate the input through this handle. + * + * Mirrors the native `LoadInputContext.Input`: the callback updates the live input it is loading, rather than + * the context itself. The input is a handle (not a by-value DTO) so guarded setters route back to the server-side + * input across the ATS boundary. + * @returns A handle to the loading input. + */ + input(): InteractionLoadingInputPromise; +} + +export interface InteractionInputLoadContextPromise extends PromiseLike { + /** + * Gets all inputs in the prompt, including the one currently loading. + * + * Mirrors the native `LoadInputContext.AllInputs`. Use the collection's by-name accessors (for example + * `value` or `requiredValue`) to read the dependency inputs declared via + * `DependsOnInputs`. This is the same `InteractionInputCollection` + * idiom used by the validation callback and prompt results, so reading inputs by name is consistent across every + * callback context. This is exposed as a property (rather than a method) so it routes through the generated + * collection accessor, matching the other contexts that surface an `InteractionInputCollection`. + */ + inputs(): InteractionInputCollectionPromise; + /** + * Gets a handle to the input that is loading. Mutate the input through this handle. + * + * Mirrors the native `LoadInputContext.Input`: the callback updates the live input it is loading, rather than + * the context itself. The input is a handle (not a by-value DTO) so guarded setters route back to the server-side + * input across the ATS boundary. + * @returns A handle to the loading input. + */ + input(): InteractionLoadingInputPromise; +} + +// ============================================================================ +// InteractionInputLoadContextImpl +// ============================================================================ + +/** The context passed to a polyglot dynamic-loading callback. Exposes the loading input as a handle and provides read access to the other inputs in the prompt. */ +class InteractionInputLoadContextImpl implements InteractionInputLoadContext { + constructor(private _handle: InteractionInputLoadContextHandle, private _client: AspireClientRpc) {} + + /** Serialize for JSON-RPC transport */ + toJSON(): MarshalledHandle { return this._handle.toJSON(); } + + inputs(): InteractionInputCollectionPromise { + return new InteractionInputCollectionPromiseImpl(this._client.invokeCapability( + 'Aspire.Hosting.Ats/InteractionInputLoadContext.inputs', + { context: this._handle } + ), this._client, false); + } + + /** @internal */ + async _inputInternal(): Promise { + const rpcArgs: Record = { context: this._handle }; + const result = await this._client.invokeCapability( + 'Aspire.Hosting.Ats/input', + rpcArgs + ); + return new InteractionLoadingInputImpl(result, this._client); + } + + /** + * Gets a handle to the input that is loading. Mutate the input through this handle. + * + * Mirrors the native `LoadInputContext.Input`: the callback updates the live input it is loading, rather than + * the context itself. The input is a handle (not a by-value DTO) so guarded setters route back to the server-side + * input across the ATS boundary. + * @returns A handle to the loading input. + */ + input(): InteractionLoadingInputPromise { + return new InteractionLoadingInputPromiseImpl(this._inputInternal(), this._client); + } + +} + +/** + * Thenable wrapper for InteractionInputLoadContext that enables fluent chaining. + */ +class InteractionInputLoadContextPromiseImpl implements InteractionInputLoadContextPromise { + constructor(private _promise: Promise, private _client: AspireClientRpc, track = true) { + if (track) { _client.trackPromise(_promise); } + } + + then( + onfulfilled?: ((value: InteractionInputLoadContext) => TResult1 | PromiseLike) | null, + onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null + ): PromiseLike { + return this._promise.then(onfulfilled, onrejected); + } + + inputs(): InteractionInputCollectionPromise { + return new InteractionInputCollectionPromiseImpl(this._promise.then(obj => obj.inputs()), this._client, false); + } + + input(): InteractionLoadingInputPromise { + return new InteractionLoadingInputPromiseImpl(this._promise.then(obj => obj.input()), this._client); + } + +} + +// ============================================================================ +// InteractionLoadingInput +// ============================================================================ + +/** + * A handle to the input currently being loaded by a dynamic-loading callback. Mirrors the native `LoadInputContext.Input` by letting callbacks update the live input directly. + * + * The handle owns the live `InteractionInput` for the duration of the load callback. Setters are routed + * back to the server-side input across the ATS boundary, which is why this is a handle rather than the by-value + * `InteractionInput` DTO. + */ +export interface InteractionLoadingInput { + toJSON(): MarshalledHandle; + /** + * Gets the name of the input. + * @returns The input name. + */ + getName(): Promise; + /** + * Sets the choice options for the input. + * @param choices The available choices, in display order. Each option pairs a submitted value with a display label. + */ + setChoiceOptions(choices: InteractionChoiceOption[]): InteractionLoadingInputPromise; + /** + * Sets the value of the input. + * @param value The value to assign. + */ + setValue(value: string): InteractionLoadingInputPromise; +} + +export interface InteractionLoadingInputPromise extends PromiseLike { + /** + * Gets the name of the input. + * @returns The input name. + */ + getName(): Promise; + /** + * Sets the choice options for the input. + * @param choices The available choices, in display order. Each option pairs a submitted value with a display label. + */ + setChoiceOptions(choices: InteractionChoiceOption[]): InteractionLoadingInputPromise; + /** + * Sets the value of the input. + * @param value The value to assign. + */ + setValue(value: string): InteractionLoadingInputPromise; +} + +// ============================================================================ +// InteractionLoadingInputImpl +// ============================================================================ + +/** + * A handle to the input currently being loaded by a dynamic-loading callback. Mirrors the native `LoadInputContext.Input` by letting callbacks update the live input directly. + * + * The handle owns the live `InteractionInput` for the duration of the load callback. Setters are routed + * back to the server-side input across the ATS boundary, which is why this is a handle rather than the by-value + * `InteractionInput` DTO. + */ +class InteractionLoadingInputImpl implements InteractionLoadingInput { + constructor(private _handle: InteractionLoadingInputHandle, private _client: AspireClientRpc) {} + + /** Serialize for JSON-RPC transport */ + toJSON(): MarshalledHandle { return this._handle.toJSON(); } + + /** + * Gets the name of the input. + * @returns The input name. + */ + async getName(): Promise { + const rpcArgs: Record = { context: this._handle }; + return await this._client.invokeCapability( + 'Aspire.Hosting.Ats/getName', + rpcArgs + ); + } + + /** @internal */ + async _setChoiceOptionsInternal(choices: InteractionChoiceOption[]): Promise { + const rpcArgs: Record = { context: this._handle, choices }; + await this._client.invokeCapability( + 'Aspire.Hosting.Ats/setChoiceOptions', + rpcArgs + ); + return this; + } + + /** + * Sets the choice options for the input. + * @param choices The available choices, in display order. Each option pairs a submitted value with a display label. + */ + setChoiceOptions(choices: InteractionChoiceOption[]): InteractionLoadingInputPromise { + return new InteractionLoadingInputPromiseImpl(this._setChoiceOptionsInternal(choices), this._client); + } + + /** @internal */ + async _setValueInternal(value: string): Promise { + const rpcArgs: Record = { context: this._handle, value }; + await this._client.invokeCapability( + 'Aspire.Hosting.Ats/setValue', + rpcArgs + ); + return this; + } + + /** + * Sets the value of the input. + * @param value The value to assign. + */ + setValue(value: string): InteractionLoadingInputPromise { + return new InteractionLoadingInputPromiseImpl(this._setValueInternal(value), this._client); + } + +} + +/** + * Thenable wrapper for InteractionLoadingInput that enables fluent chaining. + */ +class InteractionLoadingInputPromiseImpl implements InteractionLoadingInputPromise { + constructor(private _promise: Promise, private _client: AspireClientRpc, track = true) { + if (track) { _client.trackPromise(_promise); } + } + + then( + onfulfilled?: ((value: InteractionLoadingInput) => TResult1 | PromiseLike) | null, + onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null + ): PromiseLike { + return this._promise.then(onfulfilled, onrejected); + } + + getName(): Promise { + return this._promise.then(obj => obj.getName()); + } + + setChoiceOptions(choices: InteractionChoiceOption[]): InteractionLoadingInputPromise { + return new InteractionLoadingInputPromiseImpl(this._promise.then(obj => obj.setChoiceOptions(choices)), this._client); + } + + setValue(value: string): InteractionLoadingInputPromise { + return new InteractionLoadingInputPromiseImpl(this._promise.then(obj => obj.setValue(value)), this._client); + } + +} + // ============================================================================ // LogFacade // ============================================================================ @@ -10727,6 +11429,418 @@ class HostEnvironmentPromiseImpl implements HostEnvironmentPromise { } +// ============================================================================ +// InteractionService +// ============================================================================ + +/** A service to interact with the current development environment. */ +export interface InteractionService { + toJSON(): MarshalledHandle; + /** + * Gets a value indicating whether the interaction service is available to prompt the user. + * @returns `true` when the service can prompt the user; otherwise `false`. + */ + isAvailable(): Promise; + /** + * Prompts the user for confirmation with an OK/Cancel dialog. + * @param options Additional options. + */ + promptConfirmation(title: string, message: string, options?: InteractionMessageBoxOptions, cancellationToken?: AbortSignal | CancellationToken): Promise; + /** + * Prompts the user with a message box dialog. + * @param options Additional options. + */ + promptMessageBox(title: string, message: string, options?: InteractionMessageBoxOptions, cancellationToken?: AbortSignal | CancellationToken): Promise; + /** + * Prompts the user with a notification. + * @param options Additional options. + */ + promptNotification(title: string, message: string, options?: InteractionNotificationOptions, cancellationToken?: AbortSignal | CancellationToken): Promise; + /** + * Prompts the user for a single input. + * @param options Additional options. + */ + promptInput(title: string, message: string, input: Awaitable, options?: InteractionInputsDialogOptions, cancellationToken?: AbortSignal | CancellationToken): Promise; + /** + * Prompts the user for multiple inputs. + * @param options Additional options. + */ + promptInputs(title: string, message: string, inputs: InteractionInputBuilder[], options?: InteractionInputsDialogOptions, cancellationToken?: AbortSignal | CancellationToken): InputsInteractionResultPromise; + /** + * Creates a single-line text input. + * @param options Additional options. + */ + createTextInput(name: string, options?: CreateInteractionInputOptions): InteractionInputBuilderPromise; + /** + * Creates a secret (masked) text input. + * @param options Additional options. + */ + createSecretInput(name: string, options?: CreateInteractionInputOptions): InteractionInputBuilderPromise; + /** + * Creates a boolean (checkbox) input. + * @param options Additional options. + */ + createBooleanInput(name: string, options?: CreateInteractionInputOptions): InteractionInputBuilderPromise; + /** + * Creates a numeric input. + * @param options Additional options. + */ + createNumberInput(name: string, options?: CreateInteractionInputOptions): InteractionInputBuilderPromise; + /** + * Creates a choice input that selects from a list of options. + * @param name The name of the input. + * @param options Additional options. + */ + createChoiceInput(name: string, options?: CreateChoiceInputOptions): InteractionInputBuilderPromise; +} + +export interface InteractionServicePromise extends PromiseLike { + /** + * Gets a value indicating whether the interaction service is available to prompt the user. + * @returns `true` when the service can prompt the user; otherwise `false`. + */ + isAvailable(): Promise; + /** + * Prompts the user for confirmation with an OK/Cancel dialog. + * @param options Additional options. + */ + promptConfirmation(title: string, message: string, options?: InteractionMessageBoxOptions, cancellationToken?: AbortSignal | CancellationToken): Promise; + /** + * Prompts the user with a message box dialog. + * @param options Additional options. + */ + promptMessageBox(title: string, message: string, options?: InteractionMessageBoxOptions, cancellationToken?: AbortSignal | CancellationToken): Promise; + /** + * Prompts the user with a notification. + * @param options Additional options. + */ + promptNotification(title: string, message: string, options?: InteractionNotificationOptions, cancellationToken?: AbortSignal | CancellationToken): Promise; + /** + * Prompts the user for a single input. + * @param options Additional options. + */ + promptInput(title: string, message: string, input: Awaitable, options?: InteractionInputsDialogOptions, cancellationToken?: AbortSignal | CancellationToken): Promise; + /** + * Prompts the user for multiple inputs. + * @param options Additional options. + */ + promptInputs(title: string, message: string, inputs: InteractionInputBuilder[], options?: InteractionInputsDialogOptions, cancellationToken?: AbortSignal | CancellationToken): InputsInteractionResultPromise; + /** + * Creates a single-line text input. + * @param options Additional options. + */ + createTextInput(name: string, options?: CreateInteractionInputOptions): InteractionInputBuilderPromise; + /** + * Creates a secret (masked) text input. + * @param options Additional options. + */ + createSecretInput(name: string, options?: CreateInteractionInputOptions): InteractionInputBuilderPromise; + /** + * Creates a boolean (checkbox) input. + * @param options Additional options. + */ + createBooleanInput(name: string, options?: CreateInteractionInputOptions): InteractionInputBuilderPromise; + /** + * Creates a numeric input. + * @param options Additional options. + */ + createNumberInput(name: string, options?: CreateInteractionInputOptions): InteractionInputBuilderPromise; + /** + * Creates a choice input that selects from a list of options. + * @param name The name of the input. + * @param options Additional options. + */ + createChoiceInput(name: string, options?: CreateChoiceInputOptions): InteractionInputBuilderPromise; +} + +// ============================================================================ +// InteractionServiceImpl +// ============================================================================ + +/** A service to interact with the current development environment. */ +class InteractionServiceImpl implements InteractionService { + constructor(private _handle: IInteractionServiceHandle, private _client: AspireClientRpc) {} + + /** Serialize for JSON-RPC transport */ + toJSON(): MarshalledHandle { return this._handle.toJSON(); } + + /** + * Gets a value indicating whether the interaction service is available to prompt the user. + * @returns `true` when the service can prompt the user; otherwise `false`. + */ + async isAvailable(): Promise { + const rpcArgs: Record = { interactionService: this._handle }; + return await this._client.invokeCapability( + 'Aspire.Hosting/isAvailable', + rpcArgs + ); + } + + /** + * Prompts the user for confirmation with an OK/Cancel dialog. + * @param options Additional options. + */ + async promptConfirmation(title: string, message: string, options?: InteractionMessageBoxOptions, cancellationToken?: AbortSignal | CancellationToken): Promise { + const rpcArgs: Record = { interactionService: this._handle, title, message }; + if (options !== undefined) rpcArgs.options = options; + if (cancellationToken !== undefined) rpcArgs.cancellationToken = CancellationToken.fromValue(cancellationToken); + return await this._client.invokeCapability( + 'Aspire.Hosting/promptConfirmation', + rpcArgs + ); + } + + /** + * Prompts the user with a message box dialog. + * @param options Additional options. + */ + async promptMessageBox(title: string, message: string, options?: InteractionMessageBoxOptions, cancellationToken?: AbortSignal | CancellationToken): Promise { + const rpcArgs: Record = { interactionService: this._handle, title, message }; + if (options !== undefined) rpcArgs.options = options; + if (cancellationToken !== undefined) rpcArgs.cancellationToken = CancellationToken.fromValue(cancellationToken); + return await this._client.invokeCapability( + 'Aspire.Hosting/promptMessageBox', + rpcArgs + ); + } + + /** + * Prompts the user with a notification. + * @param options Additional options. + */ + async promptNotification(title: string, message: string, options?: InteractionNotificationOptions, cancellationToken?: AbortSignal | CancellationToken): Promise { + const rpcArgs: Record = { interactionService: this._handle, title, message }; + if (options !== undefined) rpcArgs.options = options; + if (cancellationToken !== undefined) rpcArgs.cancellationToken = CancellationToken.fromValue(cancellationToken); + return await this._client.invokeCapability( + 'Aspire.Hosting/promptNotification', + rpcArgs + ); + } + + /** + * Prompts the user for a single input. + * @param options Additional options. + */ + async promptInput(title: string, message: string, input: Awaitable, options?: InteractionInputsDialogOptions, cancellationToken?: AbortSignal | CancellationToken): Promise { + input = isPromiseLike(input) ? await input : input; + const __optionsForRpc = options === undefined || options === null ? options : { ...options }; + if (__optionsForRpc !== undefined && __optionsForRpc !== null) { + const __optionsForRpcData = __optionsForRpc as Record; + const ____optionsForRpcValidationCallback = __optionsForRpc.validationCallback; + if (____optionsForRpcValidationCallback !== undefined) { + const ____optionsForRpcValidationCallbackId = ____optionsForRpcValidationCallback ? registerCallback(async (argData: unknown) => { + const argHandle = wrapIfHandle(argData) as InputsDialogValidationContextHandle; + const arg = new InputsDialogValidationContextImpl(argHandle, this._client); + await ____optionsForRpcValidationCallback(arg); + }) : undefined; + __optionsForRpcData["validationCallback"] = ____optionsForRpcValidationCallbackId; + } + } + const rpcArgs: Record = { interactionService: this._handle, title, message, input }; + if (options !== undefined) rpcArgs.options = __optionsForRpc; + if (cancellationToken !== undefined) rpcArgs.cancellationToken = CancellationToken.fromValue(cancellationToken); + return await this._client.invokeCapability( + 'Aspire.Hosting/promptInput', + rpcArgs + ); + } + + /** @internal */ + async _promptInputsInternal(title: string, message: string, inputs: InteractionInputBuilder[], options?: InteractionInputsDialogOptions, cancellationToken?: AbortSignal | CancellationToken): Promise { + const __optionsForRpc = options === undefined || options === null ? options : { ...options }; + if (__optionsForRpc !== undefined && __optionsForRpc !== null) { + const __optionsForRpcData = __optionsForRpc as Record; + const ____optionsForRpcValidationCallback = __optionsForRpc.validationCallback; + if (____optionsForRpcValidationCallback !== undefined) { + const ____optionsForRpcValidationCallbackId = ____optionsForRpcValidationCallback ? registerCallback(async (argData: unknown) => { + const argHandle = wrapIfHandle(argData) as InputsDialogValidationContextHandle; + const arg = new InputsDialogValidationContextImpl(argHandle, this._client); + await ____optionsForRpcValidationCallback(arg); + }) : undefined; + __optionsForRpcData["validationCallback"] = ____optionsForRpcValidationCallbackId; + } + } + const rpcArgs: Record = { interactionService: this._handle, title, message, inputs }; + if (options !== undefined) rpcArgs.options = __optionsForRpc; + if (cancellationToken !== undefined) rpcArgs.cancellationToken = CancellationToken.fromValue(cancellationToken); + const result = await this._client.invokeCapability( + 'Aspire.Hosting/promptInputs', + rpcArgs + ); + return new InputsInteractionResultImpl(result, this._client); + } + + /** + * Prompts the user for multiple inputs. + * @param options Additional options. + */ + promptInputs(title: string, message: string, inputs: InteractionInputBuilder[], options?: InteractionInputsDialogOptions, cancellationToken?: AbortSignal | CancellationToken): InputsInteractionResultPromise { + return new InputsInteractionResultPromiseImpl(this._promptInputsInternal(title, message, inputs, options, cancellationToken), this._client); + } + + /** @internal */ + async _createTextInputInternal(name: string, options?: CreateInteractionInputOptions): Promise { + const rpcArgs: Record = { interactionService: this._handle, name }; + if (options !== undefined) rpcArgs.options = options; + const result = await this._client.invokeCapability( + 'Aspire.Hosting/createTextInput', + rpcArgs + ); + return new InteractionInputBuilderImpl(result, this._client); + } + + /** + * Creates a single-line text input. + * @param options Additional options. + */ + createTextInput(name: string, options?: CreateInteractionInputOptions): InteractionInputBuilderPromise { + return new InteractionInputBuilderPromiseImpl(this._createTextInputInternal(name, options), this._client); + } + + /** @internal */ + async _createSecretInputInternal(name: string, options?: CreateInteractionInputOptions): Promise { + const rpcArgs: Record = { interactionService: this._handle, name }; + if (options !== undefined) rpcArgs.options = options; + const result = await this._client.invokeCapability( + 'Aspire.Hosting/createSecretInput', + rpcArgs + ); + return new InteractionInputBuilderImpl(result, this._client); + } + + /** + * Creates a secret (masked) text input. + * @param options Additional options. + */ + createSecretInput(name: string, options?: CreateInteractionInputOptions): InteractionInputBuilderPromise { + return new InteractionInputBuilderPromiseImpl(this._createSecretInputInternal(name, options), this._client); + } + + /** @internal */ + async _createBooleanInputInternal(name: string, options?: CreateInteractionInputOptions): Promise { + const rpcArgs: Record = { interactionService: this._handle, name }; + if (options !== undefined) rpcArgs.options = options; + const result = await this._client.invokeCapability( + 'Aspire.Hosting/createBooleanInput', + rpcArgs + ); + return new InteractionInputBuilderImpl(result, this._client); + } + + /** + * Creates a boolean (checkbox) input. + * @param options Additional options. + */ + createBooleanInput(name: string, options?: CreateInteractionInputOptions): InteractionInputBuilderPromise { + return new InteractionInputBuilderPromiseImpl(this._createBooleanInputInternal(name, options), this._client); + } + + /** @internal */ + async _createNumberInputInternal(name: string, options?: CreateInteractionInputOptions): Promise { + const rpcArgs: Record = { interactionService: this._handle, name }; + if (options !== undefined) rpcArgs.options = options; + const result = await this._client.invokeCapability( + 'Aspire.Hosting/createNumberInput', + rpcArgs + ); + return new InteractionInputBuilderImpl(result, this._client); + } + + /** + * Creates a numeric input. + * @param options Additional options. + */ + createNumberInput(name: string, options?: CreateInteractionInputOptions): InteractionInputBuilderPromise { + return new InteractionInputBuilderPromiseImpl(this._createNumberInputInternal(name, options), this._client); + } + + /** @internal */ + async _createChoiceInputInternal(name: string, choices?: InteractionChoiceOption[], options?: CreateInteractionInputOptions): Promise { + const rpcArgs: Record = { interactionService: this._handle, name }; + if (choices !== undefined) rpcArgs.choices = choices; + if (options !== undefined) rpcArgs.options = options; + const result = await this._client.invokeCapability( + 'Aspire.Hosting/createChoiceInput', + rpcArgs + ); + return new InteractionInputBuilderImpl(result, this._client); + } + + /** + * Creates a choice input that selects from a list of options. + * @param name The name of the input. + * @param optionsBag Additional options. + */ + createChoiceInput(name: string, optionsBag?: CreateChoiceInputOptions): InteractionInputBuilderPromise { + const choices = optionsBag?.choices; + const options = optionsBag?.options; + return new InteractionInputBuilderPromiseImpl(this._createChoiceInputInternal(name, choices, options), this._client); + } + +} + +/** + * Thenable wrapper for InteractionService that enables fluent chaining. + */ +class InteractionServicePromiseImpl implements InteractionServicePromise { + constructor(private _promise: Promise, private _client: AspireClientRpc, track = true) { + if (track) { _client.trackPromise(_promise); } + } + + then( + onfulfilled?: ((value: InteractionService) => TResult1 | PromiseLike) | null, + onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null + ): PromiseLike { + return this._promise.then(onfulfilled, onrejected); + } + + isAvailable(): Promise { + return this._promise.then(obj => obj.isAvailable()); + } + + promptConfirmation(title: string, message: string, options?: InteractionMessageBoxOptions, cancellationToken?: AbortSignal | CancellationToken): Promise { + return this._promise.then(obj => obj.promptConfirmation(title, message, options, cancellationToken)); + } + + promptMessageBox(title: string, message: string, options?: InteractionMessageBoxOptions, cancellationToken?: AbortSignal | CancellationToken): Promise { + return this._promise.then(obj => obj.promptMessageBox(title, message, options, cancellationToken)); + } + + promptNotification(title: string, message: string, options?: InteractionNotificationOptions, cancellationToken?: AbortSignal | CancellationToken): Promise { + return this._promise.then(obj => obj.promptNotification(title, message, options, cancellationToken)); + } + + promptInput(title: string, message: string, input: Awaitable, options?: InteractionInputsDialogOptions, cancellationToken?: AbortSignal | CancellationToken): Promise { + return this._promise.then(obj => obj.promptInput(title, message, input, options, cancellationToken)); + } + + promptInputs(title: string, message: string, inputs: InteractionInputBuilder[], options?: InteractionInputsDialogOptions, cancellationToken?: AbortSignal | CancellationToken): InputsInteractionResultPromise { + return new InputsInteractionResultPromiseImpl(this._promise.then(obj => obj.promptInputs(title, message, inputs, options, cancellationToken)), this._client); + } + + createTextInput(name: string, options?: CreateInteractionInputOptions): InteractionInputBuilderPromise { + return new InteractionInputBuilderPromiseImpl(this._promise.then(obj => obj.createTextInput(name, options)), this._client); + } + + createSecretInput(name: string, options?: CreateInteractionInputOptions): InteractionInputBuilderPromise { + return new InteractionInputBuilderPromiseImpl(this._promise.then(obj => obj.createSecretInput(name, options)), this._client); + } + + createBooleanInput(name: string, options?: CreateInteractionInputOptions): InteractionInputBuilderPromise { + return new InteractionInputBuilderPromiseImpl(this._promise.then(obj => obj.createBooleanInput(name, options)), this._client); + } + + createNumberInput(name: string, options?: CreateInteractionInputOptions): InteractionInputBuilderPromise { + return new InteractionInputBuilderPromiseImpl(this._promise.then(obj => obj.createNumberInput(name, options)), this._client); + } + + createChoiceInput(name: string, options?: CreateChoiceInputOptions): InteractionInputBuilderPromise { + return new InteractionInputBuilderPromiseImpl(this._promise.then(obj => obj.createChoiceInput(name, options)), this._client); + } + +} + // ============================================================================ // Logger // ============================================================================ @@ -11387,6 +12501,11 @@ export interface ServiceProvider { * @returns The distributed application eventing handle. */ getEventing(): DistributedApplicationEventingPromise; + /** + * Gets the interaction service from the service provider. + * @returns An interaction service handle. + */ + getInteractionService(): InteractionServicePromise; /** * Gets the logger factory from the service provider. * @returns A logger factory handle. @@ -11430,6 +12549,11 @@ export interface ServiceProviderPromise extends PromiseLike { * @returns The distributed application eventing handle. */ getEventing(): DistributedApplicationEventingPromise; + /** + * Gets the interaction service from the service provider. + * @returns An interaction service handle. + */ + getInteractionService(): InteractionServicePromise; /** * Gets the logger factory from the service provider. * @returns A logger factory handle. @@ -11509,6 +12633,24 @@ class ServiceProviderImpl implements ServiceProvider { return new DistributedApplicationEventingPromiseImpl(this._getEventingInternal(), this._client); } + /** @internal */ + async _getInteractionServiceInternal(): Promise { + const rpcArgs: Record = { serviceProvider: this._handle }; + const result = await this._client.invokeCapability( + 'Aspire.Hosting/getInteractionService', + rpcArgs + ); + return new InteractionServiceImpl(result, this._client); + } + + /** + * Gets the interaction service from the service provider. + * @returns An interaction service handle. + */ + getInteractionService(): InteractionServicePromise { + return new InteractionServicePromiseImpl(this._getInteractionServiceInternal(), this._client); + } + /** @internal */ async _getLoggerFactoryInternal(): Promise { const rpcArgs: Record = { serviceProvider: this._handle }; @@ -11642,6 +12784,10 @@ class ServiceProviderPromiseImpl implements ServiceProviderPromise { return new DistributedApplicationEventingPromiseImpl(this._promise.then(obj => obj.getEventing()), this._client); } + getInteractionService(): InteractionServicePromise { + return new InteractionServicePromiseImpl(this._promise.then(obj => obj.getInteractionService()), this._client); + } + getLoggerFactory(): LoggerFactoryPromise { return new LoggerFactoryPromiseImpl(this._promise.then(obj => obj.getLoggerFactory()), this._client); } @@ -55481,6 +56627,10 @@ registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.ApplicationModel.ExecuteCom registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.ApplicationModel.HttpCommandPrepareRequestContext', (handle, client) => new HttpCommandPrepareRequestContextImpl(handle as HttpCommandPrepareRequestContextHandle, client)); registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.ApplicationModel.InitializeResourceEvent', (handle, client) => new InitializeResourceEventImpl(handle as InitializeResourceEventHandle, client)); registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.InputsDialogValidationContext', (handle, client) => new InputsDialogValidationContextImpl(handle as InputsDialogValidationContextHandle, client)); +registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.Ats.InputsInteractionResult', (handle, client) => new InputsInteractionResultImpl(handle as InputsInteractionResultHandle, client)); +registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputBuilder', (handle, client) => new InteractionInputBuilderImpl(handle as InteractionInputBuilderHandle, client)); +registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.Ats.InteractionInputLoadContext', (handle, client) => new InteractionInputLoadContextImpl(handle as InteractionInputLoadContextHandle, client)); +registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.Ats.InteractionLoadingInput', (handle, client) => new InteractionLoadingInputImpl(handle as InteractionLoadingInputHandle, client)); registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.ApplicationModel.LogFacade', (handle, client) => new LogFacadeImpl(handle as LogFacadeHandle, client)); registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineConfigurationContext', (handle, client) => new PipelineConfigurationContextImpl(handle as PipelineConfigurationContextHandle, client)); registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.Pipelines.PipelineContext', (handle, client) => new PipelineContextImpl(handle as PipelineContextHandle, client)); @@ -55514,6 +56664,7 @@ registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.Pipelines.IDistributedAppli registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.ApplicationModel.IExecutionConfigurationBuilder', (handle, client) => new ExecutionConfigurationBuilderImpl(handle as IExecutionConfigurationBuilderHandle, client)); registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.ApplicationModel.IExecutionConfigurationResult', (handle, client) => new ExecutionConfigurationResultImpl(handle as IExecutionConfigurationResultHandle, client)); registerHandleWrapper('Microsoft.Extensions.Hosting.Abstractions/Microsoft.Extensions.Hosting.IHostEnvironment', (handle, client) => new HostEnvironmentImpl(handle as IHostEnvironmentHandle, client)); +registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.IInteractionService', (handle, client) => new InteractionServiceImpl(handle as IInteractionServiceHandle, client)); registerHandleWrapper('Microsoft.Extensions.Logging.Abstractions/Microsoft.Extensions.Logging.ILogger', (handle, client) => new LoggerImpl(handle as ILoggerHandle, client)); registerHandleWrapper('Microsoft.Extensions.Logging.Abstractions/Microsoft.Extensions.Logging.ILoggerFactory', (handle, client) => new LoggerFactoryImpl(handle as ILoggerFactoryHandle, client)); registerHandleWrapper('Aspire.Hosting/Aspire.Hosting.Pipelines.IReportingStep', (handle, client) => new ReportingStepImpl(handle as IReportingStepHandle, client)); diff --git a/tests/Aspire.Hosting.RemoteHost.Tests/Aspire.Hosting.RemoteHost.Tests.csproj b/tests/Aspire.Hosting.RemoteHost.Tests/Aspire.Hosting.RemoteHost.Tests.csproj index da1fe269407..d8eaeba348c 100644 --- a/tests/Aspire.Hosting.RemoteHost.Tests/Aspire.Hosting.RemoteHost.Tests.csproj +++ b/tests/Aspire.Hosting.RemoteHost.Tests/Aspire.Hosting.RemoteHost.Tests.csproj @@ -5,6 +5,10 @@ net10.0 + + + + diff --git a/tests/Aspire.Hosting.RemoteHost.Tests/AtsExportsTests.cs b/tests/Aspire.Hosting.RemoteHost.Tests/AtsExportsTests.cs index 34aa103945e..6ccd25ea6f7 100644 --- a/tests/Aspire.Hosting.RemoteHost.Tests/AtsExportsTests.cs +++ b/tests/Aspire.Hosting.RemoteHost.Tests/AtsExportsTests.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Aspire.Hosting.Ats; +using Aspire.Hosting.Tests; using Xunit; namespace Aspire.Hosting.RemoteHost.Tests; @@ -70,6 +71,51 @@ public void ParseLogLevel_StrictModeThrowsForUnknownLevel() Assert.Equal("level", exception.ParamName); } + // Polyglot callers cannot mutate the input handles they pass to PromptInputs (those handles live on the + // server and only the data fields cross the wire), so submitted values must travel back through the result. + // This round-trips a prompt to prove the result carries the values keyed by input name. +#pragma warning disable ASPIREINTERACTION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. + [Fact] + public async Task PromptInputs_ResultCarriesSubmittedValuesByName() + { + var interactionService = new TestInteractionService(); + + var promptTask = InteractionExports.PromptInputs( + interactionService, + "Configure", + "Fill out the form.", + [ + InteractionExports.CreateTextInput(interactionService, "region"), + InteractionExports.CreateTextInput(interactionService, "zone"), + ]); + + var data = await interactionService.Interactions.Reader.ReadAsync(); + data.Inputs["region"].Value = "westus"; + data.Inputs["zone"].Value = "a"; + data.CompletionTcs.SetResult(InteractionResult.Ok(data.Inputs)); + + var result = await promptTask; + + Assert.False(result.Canceled); + // Inputs is now the InteractionInputCollection handle, so callers can read submitted values by name + // (mirroring the .NET indexer) in addition to enumerating them. + Assert.Equal("westus", result.Inputs["region"].Value); + Assert.Equal("a", result.Inputs["zone"].Value); + Assert.Collection( + result.Inputs, + input => + { + Assert.Equal("region", input.Name); + Assert.Equal("westus", input.Value); + }, + input => + { + Assert.Equal("zone", input.Name); + Assert.Equal("a", input.Value); + }); + } +#pragma warning restore ASPIREINTERACTION001 + private sealed class TestHostEnvironment : IHostEnvironment { public string EnvironmentName { get; set; } = Environments.Production; diff --git a/tests/PolyglotAppHosts/Aspire.Hosting/Go/apphost.go b/tests/PolyglotAppHosts/Aspire.Hosting/Go/apphost.go index e8c3a4dca3f..8f74e517c83 100644 --- a/tests/PolyglotAppHosts/Aspire.Hosting/Go/apphost.go +++ b/tests/PolyglotAppHosts/Aspire.Hosting/Go/apphost.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "log" "apphost/modules/aspire" @@ -542,14 +543,7 @@ ENTRYPOINT ["dotnet", "App.dll"] _ = container.WithHealthCheck("custom_check") _ = container.WithHttpHealthCheck() _ = container.WithHttpHealthCheck() - updateCommandState := func(args ...any) any { - if len(args) == 0 { - return aspire.ResourceCommandStateDisabled - } - ctx, ok := args[0].(aspire.UpdateCommandStateContext) - if !ok { - return aspire.ResourceCommandStateDisabled - } + updateCommandState := func(ctx aspire.UpdateCommandStateContext) aspire.ResourceCommandState { snapshot, err := ctx.ResourceSnapshot() if err != nil || snapshot.HealthStatus == nil { return aspire.ResourceCommandStateDisabled @@ -567,11 +561,11 @@ ENTRYPOINT ["dotnet", "App.dll"] }, }) _ = container.WithCommand("echo", "Echo", func(ctx aspire.ExecuteCommandContext) *aspire.ExecuteCommandResult { - commandArguments, err := ctx.Arguments().ToArray() + message, err := ctx.Arguments().Value("message") if err != nil { return &aspire.ExecuteCommandResult{Success: false, ErrorMessage: aspire.StringPtr(aspire.FormatError(err))} } - return &aspire.ExecuteCommandResult{Success: commandArguments[0].Value == "hello"} + return &aspire.ExecuteCommandResult{Success: message == "hello"} }, &aspire.WithCommandOptions{ CommandOptions: &aspire.CommandOptions{ Arguments: []*aspire.InteractionInput{ @@ -596,6 +590,189 @@ ENTRYPOINT ["dotnet", "App.dll"] return result }) + // Test bench for the polyglot IInteractionService API: prompts for a region, then dynamically + // loads the available zones for that region into a second choice input. Reached via the command's + // service provider (Services().GetInteractionService()), which only prompts when the + // interaction service is available (the interactive dashboard path). + _ = container.WithCommand("pick-zone", "Pick Zone", func(ctx aspire.ExecuteCommandContext) *aspire.ExecuteCommandResult { + interactionService := ctx.Services().GetInteractionService() + + available, err := interactionService.IsAvailable() + if err != nil { + return &aspire.ExecuteCommandResult{Success: false, ErrorMessage: aspire.StringPtr(aspire.FormatError(err))} + } + if !available { + return &aspire.ExecuteCommandResult{Success: true, Message: aspire.StringPtr("Interaction service is not available.")} + } + + regionInput := interactionService.CreateChoiceInput("region", &aspire.CreateChoiceInputOptions{ + Choices: []*aspire.InteractionChoiceOption{{Value: "us", Label: "United States"}, {Value: "eu", Label: "Europe"}}, + }) + + zoneInput := interactionService.CreateChoiceInput("zone").WithDynamicLoading(func(loadContext aspire.InteractionInputLoadContext) { + region, _ := loadContext.Inputs().Value("region") + zones := []*aspire.InteractionChoiceOption{{Value: "us-east", Label: "US East"}, {Value: "us-west", Label: "US West"}} + if region == "eu" { + zones = []*aspire.InteractionChoiceOption{{Value: "eu-west", Label: "EU West"}, {Value: "eu-north", Label: "EU North"}} + } + _ = loadContext.Input().SetChoiceOptions(zones) + }) + + result := interactionService.PromptInputs("Pick a zone", "Choose a region, then pick a zone from the dynamically loaded options.", []aspire.InteractionInputBuilder{regionInput, zoneInput}) + + canceled, err := result.Canceled() + if err != nil { + return &aspire.ExecuteCommandResult{Success: false, ErrorMessage: aspire.StringPtr(aspire.FormatError(err))} + } + return &aspire.ExecuteCommandResult{Success: !canceled, Canceled: aspire.BoolPtr(canceled)} + }) + + // Exhaustive coverage of the remaining IInteractionService surface so every newly added member is + // exercised by the polyglot typecheck: all prompt overloads, every input factory and builder method, + // the dynamic-loading context accessors/setters, and the option/result DTO fields. + _ = container.WithCommand("interaction-showcase", "Interaction Showcase", func(ctx aspire.ExecuteCommandContext) *aspire.ExecuteCommandResult { + interactionService := ctx.Services().GetInteractionService() + + available, err := interactionService.IsAvailable() + if err != nil { + return &aspire.ExecuteCommandResult{Success: false, ErrorMessage: aspire.StringPtr(aspire.FormatError(err))} + } + if !available { + return &aspire.ExecuteCommandResult{Success: true, Message: aspire.StringPtr("Interaction service is not available.")} + } + + confirmIntent := aspire.MessageIntentConfirmation + confirmation, err := interactionService.PromptConfirmation("Confirm", "Proceed?", &aspire.PromptConfirmationOptions{ + Options: &aspire.InteractionMessageBoxOptions{ + PrimaryButtonText: aspire.StringPtr("Yes"), + SecondaryButtonText: aspire.StringPtr("No"), + ShowSecondaryButton: aspire.BoolPtr(true), + ShowDismiss: aspire.BoolPtr(true), + EnableMessageMarkdown: aspire.BoolPtr(true), + Intent: &confirmIntent, + }, + }) + if err != nil { + return &aspire.ExecuteCommandResult{Success: false, ErrorMessage: aspire.StringPtr(aspire.FormatError(err))} + } + + infoIntent := aspire.MessageIntentInformation + messageBox, err := interactionService.PromptMessageBox("Notice", "Read this.", &aspire.PromptMessageBoxOptions{ + Options: &aspire.InteractionMessageBoxOptions{PrimaryButtonText: aspire.StringPtr("OK"), Intent: &infoIntent}, + }) + if err != nil { + return &aspire.ExecuteCommandResult{Success: false, ErrorMessage: aspire.StringPtr(aspire.FormatError(err))} + } + + warnIntent := aspire.MessageIntentWarning + notification, err := interactionService.PromptNotification("Heads up", "Something happened.", &aspire.PromptNotificationOptions{ + Options: &aspire.InteractionNotificationOptions{ + Intent: &warnIntent, + LinkText: aspire.StringPtr("Learn more"), + LinkUrl: aspire.StringPtr("https://aspire.dev"), + ShowDismiss: aspire.BoolPtr(true), + }, + }) + if err != nil { + return &aspire.ExecuteCommandResult{Success: false, ErrorMessage: aspire.StringPtr(aspire.FormatError(err))} + } + + textInput := interactionService.CreateTextInput("name", &aspire.CreateTextInputOptions{ + Options: &aspire.CreateInteractionInputOptions{ + Label: aspire.StringPtr("Name"), + Description: aspire.StringPtr("Your **name**"), + EnableDescriptionMarkdown: aspire.BoolPtr(true), + Required: aspire.BoolPtr(true), + Placeholder: aspire.StringPtr("Jane Doe"), + Value: aspire.StringPtr("Jane"), + MaxLength: aspire.Float64Ptr(64), + Disabled: aspire.BoolPtr(false), + }, + }) + secretInput := interactionService.CreateSecretInput("password", &aspire.CreateSecretInputOptions{ + Options: &aspire.CreateInteractionInputOptions{Required: aspire.BoolPtr(true)}, + }) + booleanInput := interactionService.CreateBooleanInput("enabled", &aspire.CreateBooleanInputOptions{ + Options: &aspire.CreateInteractionInputOptions{Value: aspire.StringPtr("true")}, + }) + numberInput := interactionService.CreateNumberInput("count", &aspire.CreateNumberInputOptions{ + Options: &aspire.CreateInteractionInputOptions{Value: aspire.StringPtr("1")}, + }) + choiceInput := interactionService.CreateChoiceInput("color", &aspire.CreateChoiceInputOptions{ + Choices: []*aspire.InteractionChoiceOption{{Value: "r", Label: "Red"}, {Value: "g", Label: "Green"}}, + Options: &aspire.CreateInteractionInputOptions{AllowCustomChoice: aspire.BoolPtr(true)}, + }) + presetInput := interactionService.CreateTextInput("greeting").WithValue("hello") + sizeInput := interactionService.CreateChoiceInput("size").WithChoiceOptions([]*aspire.InteractionChoiceOption{{Value: "s", Label: "Small"}, {Value: "l", Label: "Large"}}) + dependentInput := interactionService.CreateChoiceInput("shade").WithDynamicLoading(func(loadContext aspire.InteractionInputLoadContext) { + input := loadContext.Input() + inputName, _ := input.GetName() + color, _ := loadContext.Inputs().Value("color") + shades := []*aspire.InteractionChoiceOption{{Value: "lime", Label: "Lime"}, {Value: "forest", Label: "Forest"}} + if color == "r" { + shades = []*aspire.InteractionChoiceOption{{Value: "crimson", Label: "Crimson"}, {Value: "scarlet", Label: "Scarlet"}} + } + _ = input.SetChoiceOptions(shades) + _ = input.SetValue(inputName) + }, &aspire.WithDynamicLoadingOptions{ + Options: &aspire.DynamicLoadingOptions{AlwaysLoadOnStart: aspire.BoolPtr(true), DependsOnInputs: []string{"color"}}, + }) + + single, err := interactionService.PromptInput("Single input", "Enter a value.", interactionService.CreateTextInput("solo"), &aspire.PromptInputOptions{ + Options: &aspire.InteractionInputsDialogOptions{ + PrimaryButtonText: aspire.StringPtr("Save"), + ValidationCallback: func(validationContext aspire.InputsDialogValidationContext) { + solo, _ := validationContext.Inputs().Value("solo") + if solo == "" { + _ = validationContext.AddValidationError("solo", "A value is required.") + } + }, + }, + }) + if err != nil { + return &aspire.ExecuteCommandResult{Success: false, ErrorMessage: aspire.StringPtr(aspire.FormatError(err))} + } + + multi := interactionService.PromptInputs("Multiple inputs", "Fill out the form.", + []aspire.InteractionInputBuilder{textInput, secretInput, booleanInput, numberInput, choiceInput, presetInput, sizeInput, dependentInput}, + &aspire.PromptInputsOptions{ + Options: &aspire.InteractionInputsDialogOptions{ + PrimaryButtonText: aspire.StringPtr("Submit"), + EnableMessageMarkdown: aspire.BoolPtr(true), + ValidationCallback: func(validationContext aspire.InputsDialogValidationContext) { + name, _ := validationContext.Inputs().Value("name") + if name == "bad" { + _ = validationContext.AddValidationError("name", "Name cannot be 'bad'.") + } + }, + }, + }) + + selectedColor, _ := multi.Inputs().Value("color") + soloValue := "" + if single.Input != nil { + soloValue = single.Input.Value + } + + multiCanceled, err := multi.Canceled() + if err != nil { + return &aspire.ExecuteCommandResult{Success: false, ErrorMessage: aspire.StringPtr(aspire.FormatError(err))} + } + + success := !confirmation.Canceled && + confirmation.Value != nil && *confirmation.Value && + !messageBox.Canceled && + !notification.Canceled && + !single.Canceled && + !multiCanceled + + return &aspire.ExecuteCommandResult{ + Success: success, + Canceled: aspire.BoolPtr(multiCanceled), + Message: aspire.StringPtr(fmt.Sprintf("color=%s solo=%s", selectedColor, soloValue)), + } + }) + app, err := builder.Build() if err != nil { log.Fatalf(aspire.FormatError(err)) diff --git a/tests/PolyglotAppHosts/Aspire.Hosting/Java/AppHost.java b/tests/PolyglotAppHosts/Aspire.Hosting/Java/AppHost.java index 92061bc4492..0b296dbeea5 100644 --- a/tests/PolyglotAppHosts/Aspire.Hosting/Java/AppHost.java +++ b/tests/PolyglotAppHosts/Aspire.Hosting/Java/AppHost.java @@ -246,7 +246,7 @@ void main() throws Exception { container.withHttpHealthCheck(); container.withHttpHealthCheck(); var commandOptions = new CommandOptions(); - commandOptions.setUpdateState((Function) (ctx) -> { + commandOptions.setUpdateState((ctx) -> { var snapshot = ctx.resourceSnapshot(); return snapshot.getHealthStatus() == HealthStatus.HEALTHY ? ResourceCommandState.ENABLED : ResourceCommandState.DISABLED; }); @@ -262,9 +262,8 @@ void main() throws Exception { messageArgument.setRequired(true); echoCommandOptions.setArguments(new InteractionInput[] { messageArgument }); container.withCommand("echo", "Echo", (ctx) -> { - var commandArguments = ctx.arguments().toArray(); var result = new ExecuteCommandResult(); - result.setSuccess("hello".equals(commandArguments[0].getValue())); + result.setSuccess("hello".equals(ctx.arguments().value("message"))); return result; }, echoCommandOptions); container.withCommand("restart", "Restart", (ctx) -> { @@ -276,6 +275,165 @@ void main() throws Exception { .arguments(Map.of("message", "hello")) .cancellationToken(cancellationToken)); }); + // Test bench for the polyglot IInteractionService API: prompts for a region, then dynamically + // loads the available zones for that region into a second choice input. Reached via the command's + // service provider (services().getInteractionService()), which only prompts when the + // interaction service is available (the interactive dashboard path). + container.withCommand("pick-zone", "Pick Zone", (ctx) -> { + var interactionService = ctx.services().getInteractionService(); + if (!interactionService.isAvailable()) { + var unavailable = new ExecuteCommandResult(); + unavailable.setSuccess(true); + unavailable.setMessage("Interaction service is not available."); + return unavailable; + } + + var regionInput = interactionService.createChoiceInput( + "region", + new CreateChoiceInputOptions().choices(new InteractionChoiceOption[] { opt("us", "United States"), opt("eu", "Europe") })); + + var zoneInput = interactionService.createChoiceInput("zone").withDynamicLoading((loadContext) -> { + var region = loadContext.inputs().value("region"); + InteractionChoiceOption[] zones = "eu".equals(region) + ? new InteractionChoiceOption[] { opt("eu-west", "EU West"), opt("eu-north", "EU North") } + : new InteractionChoiceOption[] { opt("us-east", "US East"), opt("us-west", "US West") }; + loadContext.input().setChoiceOptions(zones); + }); + + var result = interactionService.promptInputs( + "Pick a zone", + "Choose a region, then pick a zone from the dynamically loaded options.", + new InteractionInputBuilder[] { regionInput, zoneInput }); + + var canceled = result.canceled(); + var commandResult = new ExecuteCommandResult(); + commandResult.setSuccess(!canceled); + commandResult.setCanceled(canceled); + return commandResult; + }); + // Exhaustive coverage of the remaining IInteractionService surface so every newly added member is + // exercised by the polyglot typecheck: all prompt overloads, every input factory and builder method, + // the dynamic-loading context accessors/setters, and the option/result DTO fields. + container.withCommand("interaction-showcase", "Interaction Showcase", (ctx) -> { + var interactionService = ctx.services().getInteractionService(); + if (!interactionService.isAvailable()) { + var unavailable = new ExecuteCommandResult(); + unavailable.setSuccess(true); + unavailable.setMessage("Interaction service is not available."); + return unavailable; + } + + var confirmBox = new InteractionMessageBoxOptions(); + confirmBox.setPrimaryButtonText("Yes"); + confirmBox.setSecondaryButtonText("No"); + confirmBox.setShowSecondaryButton(true); + confirmBox.setShowDismiss(true); + confirmBox.setEnableMessageMarkdown(true); + confirmBox.setIntent(MessageIntent.CONFIRMATION); + var confirmation = interactionService.promptConfirmation("Confirm", "Proceed?", + new PromptConfirmationOptions().options(confirmBox)); + + var infoBox = new InteractionMessageBoxOptions(); + infoBox.setPrimaryButtonText("OK"); + infoBox.setIntent(MessageIntent.INFORMATION); + var messageBox = interactionService.promptMessageBox("Notice", "Read this.", + new PromptMessageBoxOptions().options(infoBox)); + + var notificationOptions = new InteractionNotificationOptions(); + notificationOptions.setIntent(MessageIntent.WARNING); + notificationOptions.setLinkText("Learn more"); + notificationOptions.setLinkUrl("https://aspire.dev"); + notificationOptions.setShowDismiss(true); + var notification = interactionService.promptNotification("Heads up", "Something happened.", + new PromptNotificationOptions().options(notificationOptions)); + + var textOptions = new CreateInteractionInputOptions(); + textOptions.setLabel("Name"); + textOptions.setDescription("Your **name**"); + textOptions.setEnableDescriptionMarkdown(true); + textOptions.setRequired(true); + textOptions.setPlaceholder("Jane Doe"); + textOptions.setValue("Jane"); + textOptions.setMaxLength(64.0); + textOptions.setDisabled(false); + var textInput = interactionService.createTextInput("name", textOptions); + + var secretOptions = new CreateInteractionInputOptions(); + secretOptions.setRequired(true); + var secretInput = interactionService.createSecretInput("password", secretOptions); + + var booleanOptions = new CreateInteractionInputOptions(); + booleanOptions.setValue("true"); + var booleanInput = interactionService.createBooleanInput("enabled", booleanOptions); + + var numberOptions = new CreateInteractionInputOptions(); + numberOptions.setValue("1"); + var numberInput = interactionService.createNumberInput("count", numberOptions); + + var choiceExtras = new CreateInteractionInputOptions(); + choiceExtras.setAllowCustomChoice(true); + var choiceInput = interactionService.createChoiceInput("color", + new CreateChoiceInputOptions().choices(new InteractionChoiceOption[] { opt("r", "Red"), opt("g", "Green") }).options(choiceExtras)); + + var presetInput = interactionService.createTextInput("greeting").withValue("hello"); + var sizeInput = interactionService.createChoiceInput("size") + .withChoiceOptions(new InteractionChoiceOption[] { opt("s", "Small"), opt("l", "Large") }); + + var dynamicLoadingOptions = new DynamicLoadingOptions(); + dynamicLoadingOptions.setAlwaysLoadOnStart(true); + dynamicLoadingOptions.setDependsOnInputs(new String[] { "color" }); + var dependentInput = interactionService.createChoiceInput("shade").withDynamicLoading((loadContext) -> { + var input = loadContext.input(); + var inputName = input.getName(); + var color = loadContext.inputs().value("color"); + InteractionChoiceOption[] shades = "r".equals(color) + ? new InteractionChoiceOption[] { opt("crimson", "Crimson"), opt("scarlet", "Scarlet") } + : new InteractionChoiceOption[] { opt("lime", "Lime"), opt("forest", "Forest") }; + input.setChoiceOptions(shades); + input.setValue(inputName); + }, dynamicLoadingOptions); + + var singleDialogOptions = new InteractionInputsDialogOptions(); + singleDialogOptions.setPrimaryButtonText("Save"); + singleDialogOptions.setValidationCallback((validationContext) -> { + if (validationContext.inputs().value("solo").isEmpty()) { + validationContext.addValidationError("solo", "A value is required."); + } + }); + var single = interactionService.promptInput("Single input", "Enter a value.", + interactionService.createTextInput("solo"), + new PromptInputOptions().options(singleDialogOptions)); + + var multiDialogOptions = new InteractionInputsDialogOptions(); + multiDialogOptions.setPrimaryButtonText("Submit"); + multiDialogOptions.setEnableMessageMarkdown(true); + multiDialogOptions.setValidationCallback((validationContext) -> { + if ("bad".equals(validationContext.inputs().value("name"))) { + validationContext.addValidationError("name", "Name cannot be 'bad'."); + } + }); + var multi = interactionService.promptInputs("Multiple inputs", "Fill out the form.", + new InteractionInputBuilder[] { textInput, secretInput, booleanInput, numberInput, choiceInput, presetInput, sizeInput, dependentInput }, + new PromptInputsOptions().options(multiDialogOptions)); + + String selectedColor = multi.inputs().value("color"); + String soloValue = single.getInput() != null ? single.getInput().getValue() : ""; + + var multiCanceled = multi.canceled(); + var success = !confirmation.getCanceled() + && Boolean.TRUE.equals(confirmation.getValue()) + && !messageBox.getCanceled() + && !notification.getCanceled() + && !single.getCanceled() + && !multiCanceled; + + var commandResult = new ExecuteCommandResult(); + commandResult.setSuccess(success); + commandResult.setCanceled(multiCanceled); + commandResult.setMessage("color=" + (selectedColor == null ? "" : selectedColor) + + " solo=" + (soloValue == null ? "" : soloValue)); + return commandResult; + }); container.withHealthCheck("custom_check"); container.withHttpCommand("/health", "Health Check"); var httpCmdOptions = new HttpCommandExportOptions(); @@ -285,3 +443,10 @@ void main() throws Exception { var app = builder.build(); app.run(); } + + InteractionChoiceOption opt(String value, String label) { + var option = new InteractionChoiceOption(); + option.setValue(value); + option.setLabel(label); + return option; + } diff --git a/tests/PolyglotAppHosts/Aspire.Hosting/Python/apphost.py b/tests/PolyglotAppHosts/Aspire.Hosting/Python/apphost.py index 89386509a1b..89ffe0c9843 100644 --- a/tests/PolyglotAppHosts/Aspire.Hosting/Python/apphost.py +++ b/tests/PolyglotAppHosts/Aspire.Hosting/Python/apphost.py @@ -429,8 +429,7 @@ def update_command_state(ctx): return "Enabled" if snapshot.get("HealthStatus") == "Healthy" else "Disabled" def echo_command(ctx): - command_arguments = list(ctx.arguments.to_array()) - return {"success": command_arguments[0]["Value"] == "hello"} + return {"success": ctx.arguments.value("message") == "hello"} container.with_command( "noop", @@ -449,6 +448,156 @@ def restart_command(_ctx): command_options={"Arguments": [{"Name": "message", "InputType": "Text", "Required": True}]} ) container.with_command("restart", "Restart", restart_command) + # Test bench for the polyglot IInteractionService API: prompts for a region, then dynamically + # loads the available zones for that region into a second choice input. Reached via the command's + # service provider (services.get_interaction_service()), which only prompts when the + # interaction service is available (the interactive dashboard path). + def pick_zone_command(ctx): + interaction_service = ctx.services.get_interaction_service() + if not interaction_service.is_available(): + return {"success": True, "message": "Interaction service is not available."} + + region_input = interaction_service.create_choice_input( + "region", + choices=[{"Value": "us", "Label": "United States"}, {"Value": "eu", "Label": "Europe"}] + ) + + def load_zones(load_context): + region = load_context.inputs().value("region") + zones = ([{"Value": "eu-west", "Label": "EU West"}, {"Value": "eu-north", "Label": "EU North"}] + if region == "eu" + else [{"Value": "us-east", "Label": "US East"}, {"Value": "us-west", "Label": "US West"}]) + load_context.input().set_choice_options(zones) + + zone_input = interaction_service.create_choice_input("zone").with_dynamic_loading(load_zones) + + result = interaction_service.prompt_inputs( + "Pick a zone", + "Choose a region, then pick a zone from the dynamically loaded options.", + [region_input, zone_input] + ) + canceled = result.canceled() + return {"success": not canceled, "canceled": canceled} + + container.with_command("pick-zone", "Pick Zone", pick_zone_command) + # Exhaustive coverage of the remaining IInteractionService surface so every newly added member is + # exercised by the polyglot typecheck: all prompt overloads, every input factory and builder method, + # the dynamic-loading context accessors/setters, and the option/result DTO fields. + def interaction_showcase_command(ctx): + interaction_service = ctx.services.get_interaction_service() + if not interaction_service.is_available(): + return {"success": True, "message": "Interaction service is not available."} + + confirmation = interaction_service.prompt_confirmation( + "Confirm", + "Proceed?", + options={ + "PrimaryButtonText": "Yes", + "SecondaryButtonText": "No", + "ShowSecondaryButton": True, + "ShowDismiss": True, + "EnableMessageMarkdown": True, + "Intent": "Confirmation", + } + ) + + message_box = interaction_service.prompt_message_box( + "Notice", + "Read this.", + options={"PrimaryButtonText": "OK", "Intent": "Information"} + ) + + notification = interaction_service.prompt_notification( + "Heads up", + "Something happened.", + options={ + "Intent": "Warning", + "LinkText": "Learn more", + "LinkUrl": "https://aspire.dev", + "ShowDismiss": True, + } + ) + + text_input = interaction_service.create_text_input( + "name", + options={ + "Label": "Name", + "Description": "Your **name**", + "EnableDescriptionMarkdown": True, + "Required": True, + "Placeholder": "Jane Doe", + "Value": "Jane", + "MaxLength": 64, + "Disabled": False, + } + ) + secret_input = interaction_service.create_secret_input("password", options={"Required": True}) + boolean_input = interaction_service.create_boolean_input("enabled", options={"Value": "true"}) + number_input = interaction_service.create_number_input("count", options={"Value": "1"}) + choice_input = interaction_service.create_choice_input( + "color", + choices=[{"Value": "r", "Label": "Red"}, {"Value": "g", "Label": "Green"}], + options={"AllowCustomChoice": True} + ) + preset_input = interaction_service.create_text_input("greeting").with_value("hello") + size_input = interaction_service.create_choice_input("size").with_choice_options([{"Value": "s", "Label": "Small"}, {"Value": "l", "Label": "Large"}]) + + def load_shade(load_context): + input = load_context.input() + input_name = input.get_name() + color = load_context.inputs().value("color") + input.set_choice_options( + [{"Value": "crimson", "Label": "Crimson"}, {"Value": "scarlet", "Label": "Scarlet"}] + if color == "r" + else [{"Value": "lime", "Label": "Lime"}, {"Value": "forest", "Label": "Forest"}] + ) + input.set_value(input_name) + + dependent_input = interaction_service.create_choice_input("shade").with_dynamic_loading( + load_shade, + options={"AlwaysLoadOnStart": True, "DependsOnInputs": ["color"]} + ) + + def validate_solo(validation_context): + if not validation_context.inputs().value("solo"): + validation_context.add_validation_error("solo", "A value is required.") + + single = interaction_service.prompt_input( + "Single input", + "Enter a value.", + interaction_service.create_text_input("solo"), + options={"PrimaryButtonText": "Save", "ValidationCallback": validate_solo}, + ) + + def validate_form(validation_context): + if validation_context.inputs().value("name") == "bad": + validation_context.add_validation_error("name", "Name cannot be 'bad'.") + + multi = interaction_service.prompt_inputs( + "Multiple inputs", + "Fill out the form.", + [text_input, secret_input, boolean_input, number_input, choice_input, preset_input, size_input, dependent_input], + options={"PrimaryButtonText": "Submit", "EnableMessageMarkdown": True, "ValidationCallback": validate_form}, + ) + + selected_color = multi.inputs().value("color") + solo_value = (single.get("Input") or {}).get("Value") + + multi_canceled = multi.canceled() + success = (not confirmation.get("Canceled", False) + and confirmation.get("Value", False) + and not message_box.get("Canceled", False) + and not notification.get("Canceled", False) + and not single.get("Canceled", False) + and not multi_canceled) + + return { + "success": bool(success), + "canceled": multi_canceled, + "message": f"color={selected_color or ''} solo={solo_value or ''}", + } + + container.with_command("interaction-showcase", "Interaction Showcase", interaction_showcase_command) # withHttpCommand container.with_http_command("/health", "Health Check") container.with_http_command("/api/reset", "Reset", options={"MethodName": "POST", "ConfirmationMessage": "Are you sure?"}) diff --git a/tests/PolyglotAppHosts/Aspire.Hosting/TypeScript/apphost.mts b/tests/PolyglotAppHosts/Aspire.Hosting/TypeScript/apphost.mts index c2fedbbd8a4..fb0cdc5f509 100644 --- a/tests/PolyglotAppHosts/Aspire.Hosting/TypeScript/apphost.mts +++ b/tests/PolyglotAppHosts/Aspire.Hosting/TypeScript/apphost.mts @@ -9,12 +9,13 @@ import { HealthStatus, IconVariant, InputType, + MessageIntent, OtlpProtocol, ProbeType, ResourceCommandState, refExpr, } from './.aspire/modules/aspire.mjs'; -import type { DockerfileBuilderCallbackContext, DockerfileFactoryContext, HealthCheckResult } from './.aspire/modules/aspire.mjs'; +import type { DockerfileBuilderCallbackContext, DockerfileFactoryContext, HealthCheckResult, InteractionChoiceOption } from './.aspire/modules/aspire.mjs'; import { fileURLToPath } from 'node:url'; const builder = await createBuilder(); @@ -737,9 +738,9 @@ await container.withCommand("noop", "Noop", async () => { }); await container.withCommand("echo", "Echo", async (ctx) => { const commandInputs = await ctx.arguments(); - const commandArguments = await commandInputs.toArray(); + const message = await commandInputs.value("message"); - return { success: commandArguments[0]?.value === "hello" }; + return { success: message === "hello" }; }, { commandOptions: { arguments: [ @@ -759,6 +760,155 @@ await container.withCommand("restart", "Restart", async (ctx) => { cancellationToken }); }); +// Test bench for the polyglot IInteractionService API: prompts for a region, then dynamically +// loads the available zones for that region into a second choice input. Reached via the command's +// service provider (services().getInteractionService()), which only prompts when the +// interaction service is available (the interactive dashboard path). +await container.withCommand("pick-zone", "Pick Zone", async (ctx) => { + const interactionService = await ctx.services().getInteractionService(); + + if (!(await interactionService.isAvailable())) { + return { success: true, message: "Interaction service is not available." }; + } + + const regionInput = await interactionService.createChoiceInput("region", { + choices: [{ value: "us", label: "United States" }, { value: "eu", label: "Europe" }] + }); + + const zoneInput = await interactionService + .createChoiceInput("zone") + .withDynamicLoading(async (loadContext) => { + const region = await loadContext.inputs().value("region"); + + const zones: InteractionChoiceOption[] = region === "eu" + ? [{ value: "eu-west", label: "EU West" }, { value: "eu-north", label: "EU North" }] + : [{ value: "us-east", label: "US East" }, { value: "us-west", label: "US West" }]; + + await loadContext.input().setChoiceOptions(zones); + }); + + const result = await interactionService.promptInputs( + "Pick a zone", + "Choose a region, then pick a zone from the dynamically loaded options.", + [regionInput, zoneInput]); + + const canceled = await result.canceled(); + return { success: !canceled, canceled }; +}); +// Exhaustive coverage of the remaining IInteractionService surface so every newly added member is +// exercised by the polyglot typecheck: all prompt overloads, every input factory and builder method, +// the dynamic-loading context accessors/setters, and the option/result DTO fields. +await container.withCommand("interaction-showcase", "Interaction Showcase", async (ctx) => { + const interactionService = await ctx.services().getInteractionService(); + + if (!(await interactionService.isAvailable())) { + return { success: true, message: "Interaction service is not available." }; + } + + const confirmation = await interactionService.promptConfirmation("Confirm", "Proceed?", { + primaryButtonText: "Yes", + secondaryButtonText: "No", + showSecondaryButton: true, + showDismiss: true, + enableMessageMarkdown: true, + intent: MessageIntent.Confirmation + }); + + const messageBox = await interactionService.promptMessageBox("Notice", "Read this.", { + primaryButtonText: "OK", + intent: MessageIntent.Information + }); + + const notification = await interactionService.promptNotification("Heads up", "Something happened.", { + intent: MessageIntent.Warning, + linkText: "Learn more", + linkUrl: "https://aspire.dev", + showDismiss: true + }); + + const textInput = await interactionService.createTextInput("name", { + label: "Name", + description: "Your **name**", + enableDescriptionMarkdown: true, + required: true, + placeholder: "Jane Doe", + value: "Jane", + maxLength: 64, + disabled: false + }); + const secretInput = await interactionService.createSecretInput("password", { required: true }); + const booleanInput = await interactionService.createBooleanInput("enabled", { value: "true" }); + const numberInput = await interactionService.createNumberInput("count", { value: "1" }); + const choiceInput = await interactionService.createChoiceInput("color", { + choices: [{ value: "r", label: "Red" }, { value: "g", label: "Green" }], + options: { allowCustomChoice: true } + }); + const presetInput = await interactionService.createTextInput("greeting").withValue("hello"); + const sizeInput = await interactionService + .createChoiceInput("size") + .withChoiceOptions([{ value: "s", label: "Small" }, { value: "l", label: "Large" }]); + const dependentInput = await interactionService + .createChoiceInput("shade") + .withDynamicLoading(async (loadContext) => { + const input = loadContext.input(); + const inputName = await input.getName(); + const color = await loadContext.inputs().value("color"); + + await input.setChoiceOptions(color === "r" + ? [{ value: "crimson", label: "Crimson" }, { value: "scarlet", label: "Scarlet" }] + : [{ value: "lime", label: "Lime" }, { value: "forest", label: "Forest" }]); + await input.setValue(inputName); + }, { + alwaysLoadOnStart: true, + dependsOnInputs: ["color"] + }); + + const single = await interactionService.promptInput( + "Single input", + "Enter a value.", + interactionService.createTextInput("solo"), + { + primaryButtonText: "Save", + validationCallback: async (validationContext) => { + const solo = await validationContext.inputs().value("solo"); + if (!solo) { + await validationContext.addValidationError("solo", "A value is required."); + } + } + }); + + const multi = await interactionService.promptInputs( + "Multiple inputs", + "Fill out the form.", + [textInput, secretInput, booleanInput, numberInput, choiceInput, presetInput, sizeInput, dependentInput], + { + primaryButtonText: "Submit", + enableMessageMarkdown: true, + validationCallback: async (validationContext) => { + const name = await validationContext.inputs().value("name"); + if (name === "bad") { + await validationContext.addValidationError("name", "Name cannot be 'bad'."); + } + } + }); + + const selectedColor = await multi.inputs().value("color"); + const soloValue = single.input?.value; + + const multiCanceled = await multi.canceled(); + const success = !confirmation.canceled + && confirmation.value === true + && !messageBox.canceled + && !notification.canceled + && !single.canceled + && !multiCanceled; + + return { + success, + canceled: multiCanceled, + message: `color=${selectedColor ?? ""} solo=${soloValue ?? ""}` + }; +}); // withProcessCommand await container.withProcessCommand("dotnet-version", "Show .NET version", {