Microsoft.DeclarativeAgents.Manifest
1.1.0
Prefix Reserved
See the version list below for details.
dotnet add package Microsoft.DeclarativeAgents.Manifest --version 1.1.0
NuGet\Install-Package Microsoft.DeclarativeAgents.Manifest -Version 1.1.0
<PackageReference Include="Microsoft.DeclarativeAgents.Manifest" Version="1.1.0" />
paket add Microsoft.DeclarativeAgents.Manifest --version 1.1.0
#r "nuget: Microsoft.DeclarativeAgents.Manifest, 1.1.0"
// Install Microsoft.DeclarativeAgents.Manifest as a Cake Addin #addin nuget:?package=Microsoft.DeclarativeAgents.Manifest&version=1.1.0 // Install Microsoft.DeclarativeAgents.Manifest as a Cake Tool #tool nuget:?package=Microsoft.DeclarativeAgents.Manifest&version=1.1.0
Microsoft.DeclarativeAgents.Manifest
This library is a parser/validator that reads/writes and validates JSON files that conform to the API Plugin and Declarative Declarative Agents specification.
Getting Started
Thank you for adopting the Microsoft Plugin Validator. We are excited to have you onboard!
- Demo the API Plugin validation logic! Demo
- Demo the Declarative Agents validation logic! Demo
- Download the NuGet package here
- Read our currently supported rules
- Find a bug? Check our backlog and if you don't see yours in the list Let Us Know
- Read the spec
Prerequisites
Before you begin, ensure you have met the following requirements:
- You have installed .NET SDK (version 6.0 or higher).
Note: The Microsoft.DeclarativeAgents.Manifest library also supports .NET Standard 2.0, making it compatible with a wide range of .NET implementations, including .NET Framework, .NET Core, and Xamarin.
Installation
Step 1: Clone the repository
git clone https://github.com/microsoft/Microsoft.Plugins.Manifest cd Microsoft.Plugins.Manifest
Step 2: Install dependencies
dotnet restore
Step 3: Build the library
dotnet build
Step 4: Run tests (optional but recommended)
dotnet test
Sample with rule customization
You can validate your documents using the sample in TestApplication\Program.cs
.
Ensure you are on the project root
cd Microsoft.Plugins.Manifest
Copy your input.json
in the TestApplication
directory.
# If you want to validate a DA document, use this command dotnet run --project .\TestApplication\TestApplication.csproj -- dc .\TestApplication\input.json # If you want to validate a plugin document, use this command dotnet run --project .\TestApplication\TestApplication.csproj -- p .\TestApplication\input.json
Reading a Plugin manifest
You can read a plugin manifest (example document located at ./tests/Examples/M365Consumer.json) from a JsonElement object parsed by System.Text.Json:
var results = PluginManifestDocument.Load(jsonElement);
or you can read directly from a stream and the underlying library will use System.Text.Json to parse the JSON
var results = await PluginManifestDocument.LoadAsync(stream);
Validating a Plugin manifest
The results
has a document
property that will contain a PluginManifestDocument
object if the library was able to successfully read the manifest. It has a IsValid
property to indicate if no errors were found in the document and it also has a Problems
collection that contains the list of errors and warnings discovered whilst parsing and running the validation ruleset.
Here is a hypothetical way to process the results of validation:
var manifestDocument = results.Document;
if (results.IsValid)
{
var warnings = results.Problems.Where(problem => problem.Severity == Severity.Warning).ToList();
} else
{
var warningsAndErrors = results.Problems;
}
Customize your rule set
There is a default set of validation rules that is processed after loading a document. The set of rules can be changed by providing an alternate set of rules as part of the load options. We maintain default rules for Authoring, Store and orchestrator which can be found in src\DeclarativeAgentsManifest\PluginValidation\PluginManifestRuleSets.cs. Below is an example of customizing with default authoring rules and removing the rule which checks the supported schema version.
var myRules = PluginManifestRuleSets.GetAuthoringDefaultRuleSet();
myRules.Remove(PluginManifestRules.RuleIds.SupportedSchemaVersion);
var options = new ReaderOptions { ValidationRules = myRules };
var result = PluginManifestDocument.Load(jsonElement, options);
For Declarative Declarative Agents
var myRules = DAManifestRuleSets.GetAuthoringDefaultRuleSet();
Document Resolution
In cases where you want your DA or Plugin documents to remain lean without bulky OpenApi descriptions (for the case of Plugin documents) or inlined Plugin documents (for the case of DA documents), we have provided a mechanism for you to resolve these documents at runtime.
Resolving PluginManifest Documents in Declarative Agents
After loading a JsonDocument into the Load/LoadAsync method of DAManifestDocument class and getting a DocumentValidationResult<DAManifestDocument>, you can resolve the referenced PluginManifestDocument in the actions arrays using a new ResolveReferencesAsync extension method on the DAManifestDocument class using these steps:
- Implement the
IFileProvider
interface that is exposed in the library. The interface adds aGetFileAsync
method that you need to implement. We call this method with thefile
member value and expect astream
of the resolved document. Here is a sample implementation:
public class FileProvider : IFileProvider
{
private readonly string _folderWithDocuments;
public FileProvider(string folderName)
{
_folderWithDocuments = folderName;
}
public async Task<Stream> GetFileAsync(string fileMemberValue)
{
var manifest = Directory.GetFiles(_folderWithDocuments, fileMemberValue, SearchOption.TopDirectoryOnly).FirstOrDefault();
if (manifest != null)
{
using (var stream = File.OpenRead(manifest))
{
var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream);
memoryStream.Position = 0;
return memoryStream;
}
}
else
{
throw new FileNotFoundException($"Manifest file '{fileMemberValue}' not found.");
}
}
}
- Pass an instance of the
FileProvider
to the file resolver like so:
// Create an instance of FileProvider
var fileProvider = new FileProvider();
var sampleDAManifest = """
{
"id": "d56f1b75-62e2-4bdd-b806-88d3986a67a4",
"version": "v0.5",
"actions": [
{
"id": "<unique-random-id>",
"file": "aiplugin.json"
}
],
"conversation_starters": [],
"name": "06-18-NAM Drone Delivery",
"description": "Virtual project manager for the drone delivery project, knowledgeable about all project details and documents.",
"instructions": "Your name is Drone Delivery Project Manager Assistant",
"capabilities": [
{
"name": "SharePoint",
"files": [],
"sites": [
{
"path": "https://m365copilotexttest.sharepoint.com/"
}
]
}
]
}""";
var doc = JsonDocument.Parse(sampleDAManifest);
// doc.RootElement is validated against a default ruleset found at the path `src/DeclarativeAgentsManifest/DaValidation.
var manifestValidationResult = DAManifestDocument.Load(doc.RootElement);
if(manifestValidationResult.IsValid)
{
// Call ResolveReferencesAsync passing the FIleProvider instance
var resolutionProblems = await manifestValidationResult.Document.ResolveReferencesAsync(fileProvider);
}
When we call DAManifestDocument.Load(doc.RootElement)
, the Json document is parsed and validated against a default ruleset which can be found at the path src/DeclarativeAgentsManifest/DaValidation
→ DefaultRuleSet.
Resolving OpenApi documents in PluginManifest Documents
Resolving OpenApi documents in PluginManifest documents is very similar to how we resolve plugin manifest documents in Declarative Declarative Agents. You first need to implement the IFileProvider interface as discussed before.
- Same implementation for IFileProvider in previous step
- Pass the FileProvider instance to the resolver.
// Create an instance of FileProvider
var fileProvider = new FileProvider();
var sampleManifest = """
{
"schema_version": "v2",
"name_for_human": "Sample Plugin",
"description_for_human": "A sample",
"functions": [],
"runtimes": [
{
"type": "OpenApi",
"auth": {
"type": "None"
},
"spec": {
"url": "openapidesc.json",
"api_description": ""
},
"run_for_functions": []
}
]
}
""";
var doc = JsonDocument.Parse(sampleManifest);
// doc.RootElement is validated against a default ruleset found at the path `src/DeclarativeAgentsManifest/PluginsValidation.
var manifestValidationResult = PluginManifestDocument.Load(doc.RootElement);
if(manifestValidationResult.IsValid)
{
// Call ResolveReferencesAsync passing the FileProvider instance
var resolutionProblems = await manifestValidationResult.Document.ResolveReferencesAsync(fileProvider);
}
For this specific example, the resolved openapidesc.json
file will be appended to the api_description
member after resolution. According to the spec, the url
member is not required when api_description
has an OpenApi description. You should expect the url field to be null after resolution. When writing out the document, the url member will not be written out. The result will be as shown below:
{
"schema_version": "v2",
"name_for_human": "Sample Plugin",
"description_for_human": "A sample",
"capabilities": {},
"runtimes": [
{
"type": "OpenApi",
"auth": {
"type": "None"
},
"spec": {
"api_description": "{\u0022openapi\u0022:\u00223.0.1\u0022,\u0022info\u0022:{\u0022title\u0022:\u0022Minimal API\u0022,\u0022version\u0022:\u00221.0.0\u0022},\u0022paths\u0022:{\u0022/hello\u0022:{\u0022get\u0022:{\u0022responses\u0022:{\u0022200\u0022:{\u0022description\u0022:\u0022A simple greeting\u0022}}}}}}"
}
}
]
}
Note that ResolveReferencesAsync
for both resolution cases returns a list of problems that occurred during resolution. You can combine these problems with those returned from document validation like so:
When we call PluginManifestDocument.Load(doc.RootElement)
, the Json document is parsed and validated against a default ruleset which can be found at the path src/DeclarativeAgentsManifest/PluginsValidation
→ PluginManifestRuleSets.GetDefaultAuthoringRuleSet()
.
Note that ResolveReferencesAsync
returns a list of problems that occurred during resolution. Since we are resolving a DAManifest document, the resolved document is validated against the default src/DeclarativeAgentsManifest/DaValidation
→ DAManifestRuleSet.DefaultAuthoringRuleSet
ruleset. Therefore, the resolutionProblems
returned will be DAManifestDocument
validation problems.
You can combine these problems with those returned from initial document validation.
Note that ResolveReferencesAsync returns a list of problems that occurred during document resolution. Since we are resolving a DeclarativeAgentsManifest document, the resolved document is validated against the default src/DeclarativeAgentsManifest/PluginsValidation
→ PluginManifest.DefaultAuthoringRuleSet
ruleset. Therefore, the resolutionProblems
returned will be PluginManifestDocument
validation problems.
In the case where we are resolving a DAManifest document, the resolved document is validated against the default src/DeclarativeAgentsManifest/DaValidation
→ DaManifestRuleSet.DefaultAuthoringRuleSet
ruleset. Therefore, the resolutionProblems
returned will be DAManifestDocument
validation problems.
You can combine these problems with those returned from initial document(PluginManifestDocument/DAManifestDocument) validation like so:
/// Other details elided
if(manifestValidationResult.IsValid)
{
// Call ResolveReferencesAsync passing the FileProvider instance
var resolutionProblems = await manifestValidationResult.Document.ResolveReferencesAsync(fileProvider);
// It is safe to combine these as resolutionProblems are returned from a different document type. So we don't run the risk of having duplicates
resolutionProblems.AddRange(manifestValidationResult.Problems);
}
Localization
Localization support is available for both DAManifest and PluginManifest documents. This means that some members of the documents can be adapted to different languages, regions or cultures. Note that localization is only supported for some members. To find out which members can be localized, refer to the spec. For instance, this member is localizable localizable title member. The spec will mention whether a member is localizable.
To localize a member, we use this regex syntax:
var localizationRegex = new(@"^\[\[([a-zA-Z0-9_]+)\]\]$"; // Here is a sample match [[DA_Name]]
A new Localize
method that returns a List<Problem>
was added as an extension method to both DAManifestDocument and PluginManifestDocument.
Localiization in DAManifestDocuments.
To localize a DAManifestDocument, we first need to Load a JsonDocument through the DAManifestDocument.Load/LoadAsync
methods. We then call Localize
, passing a dictionary
of localization strings like so:
const string sampleDAManifest = """
{
"$schema": "https://aka.ms/json-schemas/copilot-extensions/v1.0/declarative-copilot.schema.json",
"name": "[[DA_Name]]",
"description": "[[DA_Description]]",
"instructions": "# You are an assistant..."
}
""";
var json = JsonDocument.Parse(sampleDAManifest);
var dcManifestDocument = DAManifestDocument.Load(json.RootElement).Document!;
You need to provide localization strings for the localizable members in your document. For this case, our localizable members are name
and description
.
Dictionary<string, string> localizationStrings = new()
{
{
"DA_Name", "Copilote de Communications"
},
{
"DA_Description", "Un assistant pour les professionnels de la communication et des relations publiques chez Microsoft."
}
};
We then call Localize
on the DAManifestDocument passing the localization strings like so:
var localizationProblems = dcManifestDocument.Localize();
The dictionary lookup when localizing members is case-sensitive.
When we serialize the resulting DAManifestDocument
, the result will look like this:
{
"$schema": "https://aka.ms/json-schemas/copilot-extensions/v1.0/declarative-copilot.schema.json",
"name": "Copilote de Communications",
"description": "Un assistant pour les professionnels de la communication et des relations publiques chez Microsoft.",
"instructions": "# You are an assistant..."
}
For a case where a non-localizable field like version
is represented with the localization syntax like so:
const string sampleDAManifest = """
{
"$schema": "https://aka.ms/json-schemas/copilot-extensions/v1.0/declarative-copilot.schema.json",
"version": "[[Invalid_Version_Localization]]",
"name": "[[DA_Name]]",
"description": "[[DA_Description]]",
"instructions": "# You are an assistant..."
}
""";
the .Localize
method will return the error:
Localization key Invalid_Version_Localization in a non-localizable property version.
For a case where a localizable field is localized but the corresponding key-value pair for localization replacement is not provided in the localization dictionary like so:
const string sampleDAManifest = """
{
"$schema": "https://aka.ms/json-schemas/copilot-extensions/v1.0/declarative-copilot.schema.json",
"name": "[[DA_Name]]",
"description": "[[DA_Description]]",
"instructions": "# You are an assistant..."
}
""";
Dictionary<string, string> localizationStrings = new()
{
{
"DA_Description", "Un assistant pour les professionnels de la communication et des relations publiques chez Microsoft."
}
};
var localizationProblems = dcManifestDocument.Localize();
, the localizationProblems
list will contain the error:
DA_Name was not found in the localization strings.
The Localize
method modifies the given in-memory object. Therefore, if you want to localize the loaded manifest into multiple languages, you should clone the object first like so:
var dcManifestClone = dcManifestDocument.Clone();
// You can then call Localize with the localization strings that you want for this clone
Note that Localize
returns a list of validation problems. You can combine these problems with the initial problems returned from parsing and validating the initial document like we did when
resolving OpenApi descriptions and PluginManifest documents in previous steps.
Localization in PluginManifestDocument
Localization for PluginManifestDocument works like for DAManifestDocuments. To localize a DAManifestDocument, we first need to Load a JsonDocument through the PluginManifestDocument.Load/LoadAsync
methods as we did in DAs. We then call Localize
, passing a dictionary
of localization strings like so:
const string samplePluginManifest = """
{
"schema_version": "v2",
"name_for_human": "[[Plug_Name_For_Human]]",
"description_for_human": "DroneDelivery",
"namespace": "mscopilot",
"functions": [],
"runtimes": []
}
""";
var json = JsonDocument.Parse(samplePluginManifest);
var pluginManifestDocument = PluginManifestDocument.Load(json.RootElement).Document!;
You need to provide localization strings for the localizable members in your document. For this case, our localizable member name_for_human
.
Dictionary<string, string> localizationStrings = new()
{
{
"Plug_Name_For_Human", "Copilote"
}
};
We then call Localize
on the DAManifestDocument passing the localization strings like so:
var localizationProblems = pluginManifestDocument.Localize();
Note that Localize
returns a list of validation problems. You can combine these problems with the initial problems returned from parsing and validating the initial document like we did when
resolving OpenApi descriptions and PluginManifest documents in previous steps
When we serialize the resulting PluginManifestDocument
, the result will look like this:
{
"schema_version": "v2",
"name_for_human": "Copilote",
"description_for_human": "DroneDelivery",
"namespace": "mscopilot",
"functions": [],
"runtimes": []
}
After document resolution and localization for both DAManifestDocuments and PluginManifestDocuments, you can serialize the objects like so:
// Create a memory stream to capture JSON output
using (var memoryStream = new MemoryStream())
{
// Create Utf8JsonWriter with the memory stream
using (var writer = new Utf8JsonWriter(memoryStream, new JsonWriterOptions { Indented = true }))
{
manifestDocumentResult.Document.WriteInline(writer);
}
// Reset stream position to the beginning
memoryStream.Position = 0;
// Convert memory stream to string
string jsonString = Encoding.UTF8.GetString(memoryStream.ToArray());
// Output the JSON to the console
Console.WriteLine(jsonString);
}
Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.
Trademarks
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.OpenApi (>= 1.6.23)
- Microsoft.OpenApi.Readers (>= 1.6.23)
- Microsoft.VisualStudio.Threading (>= 17.12.19)
- System.Text.Json (>= 6.0.10)
-
net8.0
- Microsoft.OpenApi (>= 1.6.23)
- Microsoft.OpenApi.Readers (>= 1.6.23)
- Microsoft.VisualStudio.Threading (>= 17.12.19)
- System.Text.Json (>= 6.0.10)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Microsoft.DeclarativeAgents.Manifest:
Package | Downloads |
---|---|
Microsoft.OpenApi.Kiota.Builder
The core engine behind the OpenAPI based client generator. |
GitHub repositories (1)
Showing the top 1 popular GitHub repositories that depend on Microsoft.DeclarativeAgents.Manifest:
Repository | Stars |
---|---|
microsoft/kiota
OpenAPI based HTTP Client code generator
|