{"id":13773983,"url":"https://github.com/extism/dotnet-sdk","last_synced_at":"2025-07-17T03:07:37.392Z","repository":{"id":194805424,"uuid":"691133833","full_name":"extism/dotnet-sdk","owner":"extism","description":"Extism .NET Host SDK - easily run WebAssembly modules / plugins from C# and F# applications","archived":false,"fork":false,"pushed_at":"2025-06-23T08:14:40.000Z","size":30382,"stargazers_count":45,"open_issues_count":1,"forks_count":2,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-07-13T07:01:40.480Z","etag":null,"topics":["dotnet","plugin","wasi","wasm"],"latest_commit_sha":null,"homepage":"https://extism.org","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/extism.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-09-13T15:06:54.000Z","updated_at":"2025-07-10T12:36:26.000Z","dependencies_parsed_at":"2023-10-16T06:23:56.429Z","dependency_job_id":"1a6fe441-eae9-4a9c-98ee-eef2a70683c1","html_url":"https://github.com/extism/dotnet-sdk","commit_stats":null,"previous_names":["extism/dotnet-sdk"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/extism/dotnet-sdk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fdotnet-sdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fdotnet-sdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fdotnet-sdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fdotnet-sdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/extism","download_url":"https://codeload.github.com/extism/dotnet-sdk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fdotnet-sdk/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265522970,"owners_count":23781751,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dotnet","plugin","wasi","wasm"],"created_at":"2024-08-03T17:01:22.619Z","updated_at":"2025-07-17T03:07:37.373Z","avatar_url":"https://github.com/extism.png","language":"C#","funding_links":[],"categories":["\u003ca name=\"extism\"\u003e\u003c/a\u003e[Extism](https://github.com/extism/extism) \u003csup\u003e[top⇈](#contents)\u003c/sup\u003e"],"sub_categories":[],"readme":"# Extism .NET Host SDK\n\nThis repo houses the .NET SDK for integrating with the [Extism](https://extism.org/) runtime. Install this library into your host .NET applications to run Extism plugins.\n\n## Installation\n\nThis library depends on the native Extism runtime, we provide [native runtime packages](https://www.nuget.org/packages/Extism.runtime.all) for all supported operating systems. You can install with:\n\u003cimg src=\"https://img.shields.io/nuget/vpre/Extism.runtime.all\" /\u003e\n```\ndotnet add package Extism.runtime.all\n```\n\nThen, add the [Extism.Sdk NuGet package](https://www.nuget.org/packages/Extism.Sdk) to your project:\n\u003cimg src=\"https://img.shields.io/nuget/vpre/Extism.Sdk\" /\u003e\n```\ndotnet add package Extism.Sdk\n```\n\n### PowerShell\n\nOpen a PowerShell console and detect the Common Language Runtime (CLR) major version with the command\n```\n[System.Environment]::Version\n```\n\nDownload the [Extism.Sdk NuGet package](https://www.nuget.org/packages/Extism.Sdk) and change the extension from nupkg to zip. Open the zip file and go into the lib folder. Choose the net folder in dependency of the CLR major version and open it. Copy the file Extism.sdk.dll in your PowerShell script directory.\n\nDownload the [Extism native runtime package](https://www.nuget.org/packages/Extism.runtime.all#dependencies-body-tab) in dependency of your operating system and change the extension from nupkg to zip. Open the zip file and go into the runtimes folder. At the end of the path you will find a file with the name libextism.so (shared object) or extism.dll (dynamic link library). Copy this file in your PowerShell script directory.\n\n## Getting Started\n\nThis guide should walk you through some of the concepts in Extism and this .NET library.\n\nFirst you should add a using statement for Extism:\n\nC#:\n```csharp\nusing System;\n\nusing Extism.Sdk;\n```\n\nF#:\n```fsharp\nopen System\n\nopen Extism.Sdk\n```\n\n### PowerShell\n```powershell\n[System.String]$LibDir = $($PSScriptRoot)\n[System.String]$Extism = $($LibDir) + \"/Extism.Sdk.dll\"\nAdd-Type -Path $Extism\n```\n\n## Creating A Plug-in\n\nThe primary concept in Extism is the [plug-in](https://extism.org/docs/concepts/plug-in). You can think of a plug-in as a code module stored in a `.wasm` file.\n\nSince you may not have an Extism plug-in on hand to test, let's load a demo plug-in from the web:\n\nC#:\n```csharp\nvar manifest = new Manifest(new UrlWasmSource(\"https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm\"));\n\nusing var plugin = new Plugin(manifest, new HostFunction[] { }, withWasi: true);\n```\n\nF#:\n```fsharp\nlet uri = Uri(\"https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm\")\nlet manifest = Manifest(new UrlWasmSource(uri))\n\nlet plugin = new Plugin(manifest, Array.Empty\u003cHostFunction\u003e(), withWasi = true)\n```\n\nPowerShell:\n```powershell\n$Manifest = [Extism.Sdk.Manifest]::new(\n        [Extism.Sdk.UrlWasmSource]::new(\n            \"https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm\"\n        )\n    )\n\n$HostFunctionArray = [Extism.Sdk.HostFunction[]]::new(0)\n\n$Options = [Extism.Sdk.PluginIntializationOptions]::new()\n$Options.WithWasi = $True\n\n$Plugin = [Extism.Sdk.Plugin]::new($Manifest, $HostFunctionArray, $Options)\n```\n\n\u003e **Note**: The schema for this manifest can be found here: https://extism.org/docs/concepts/manifest/\n\n### Calling A Plug-in's Exports\n\nThis plug-in was written in Rust and it does one thing, it counts vowels in a string. As such, it exposes one \"export\" function: `count_vowels`. We can call exports using `Plugin.Call`:\n\nC#:\n```csharp\nvar output = plugin.Call(\"count_vowels\", \"Hello, World!\");\nConsole.WriteLine(output);\n// =\u003e {\"count\": 3, \"total\": 3, \"vowels\": \"aeiouAEIOU\"}\n```\n\nF#:\n```fsharp\nlet output = plugin.Call(\"count_vowels\", \"Hello, World!\")\nprintfn \"%s\" output\n// =\u003e {\"count\": 3, \"total\": 3, \"vowels\": \"aeiouAEIOU\"}\n```\n\nPowerShell:\n```powershell\n$output = $Plugin.Call(\"count_vowels\", \"Hello, World!\")\nWrite-Host $output\n# =\u003e {\"count\": 3, \"total\": 3, \"vowels\": \"aeiouAEIOU\"}\n```\n\nAll exports have a simple interface of optional bytes in, and optional bytes out. This plug-in happens to take a string and return a JSON encoded string with a report of results.\n\n## Precompiling plugins\n\nIf you're going to create more than one instance of the same plugin, we recommend pre-compiling the plugin and instantiate them:\n\nC#:\n\n```csharp\nvar manifest = new Manifest(new PathWasmSource(\"/path/to/plugin.wasm\"), \"main\"));\n\n// pre-compile the wasm file\nusing var compiledPlugin = new CompiledPlugin(_manifest, [], withWasi: true);\n\n// instantiate plugins\nusing var plugin = compiledPlugin.Instantiate();\n```\n\nF#:\n\n```fsharp\n// Create manifest\nlet manifest = Manifest(PathWasmSource(\"/path/to/plugin.wasm\"))\n\n// Pre-compile the wasm file\nuse compiledPlugin = new CompiledPlugin(manifest, Array.empty\u003cHostFunction\u003e, withWasi = true)\n\n// Instantiate plugins\nuse plugin = compiledPlugin.Instantiate()\n```\n\nThis can have a dramatic effect on performance*:\n\n```\n// * Summary *\n\nBenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4460/23H2/2023Update/SunValley3)\n13th Gen Intel Core i7-1365U, 1 CPU, 12 logical and 10 physical cores\n.NET SDK 9.0.100\n  [Host]     : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2\n  DefaultJob : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2\n\n\n| Method                    | Mean        | Error     | StdDev      |\n|-------------------------- |------------:|----------:|------------:|\n| CompiledPluginInstantiate |    266.2 ms |   6.66 ms |    19.11 ms |\n| PluginInstantiate         | 27,592.4 ms | 635.90 ms | 1,783.12 ms |\n```\n\n*: See [the complete benchmark](./test/Extism.Sdk.Benchmarks/Program.cs)\n\n### Plug-in State\n\nPlug-ins may be stateful or stateless. Plug-ins can maintain state b/w calls by the use of variables. Our count vowels plug-in remembers the total number of vowels it's ever counted in the \"total\" key in the result. You can see this by making subsequent calls to the export:\n\nC#:\n```csharp\nvar output = plugin.Call(\"count_vowels\", \"Hello, World!\");\nConsole.WriteLine(output);\n// =\u003e {\"count\": 3, \"total\": 6, \"vowels\": \"aeiouAEIOU\"}\n\noutput = plugin.Call(\"count_vowels\", \"Hello, World!\");\nConsole.WriteLine(output);\n// =\u003e {\"count\": 3, \"total\": 9, \"vowels\": \"aeiouAEIOU\"}\n```\n\nF#:\n```fsharp\nlet output1 = plugin.Call(\"count_vowels\", \"Hello, World!\")\nprintfn \"%s\" output1\n// =\u003e {\"count\": 3, \"total\": 6, \"vowels\": \"aeiouAEIOU\"}\n\nlet output2 = plugin.Call(\"count_vowels\", \"Hello, World!\")\nprintfn \"%s\" output2\n// =\u003e {\"count\": 3, \"total\": 9, \"vowels\": \"aeiouAEIOU\"}\n```\n\nThese variables will persist until this plug-in is freed or you initialize a new one.\n\n### Configuration\n\nPlug-ins may optionally take a configuration object. This is a static way to configure the plug-in. Our count-vowels plugin takes an optional configuration to change out which characters are considered vowels. Example:\n\nC#:\n```csharp\nvar manifest = new Manifest(new UrlWasmSource(\"\u003chttps://github.com/extism/plugins/releases/latest/download/count_vowels.wasm\u003e\"));\n\nusing var plugin = new Plugin(manifest, new HostFunction[] { }, withWasi: true);\n\nvar output = plugin.Call(\"count_vowels\", \"Yellow, World!\");\nConsole.WriteLine(output);\n// =\u003e {\"count\": 3, \"total\": 3, \"vowels\": \"aeiouAEIOU\"}\n\nmanifest = new Manifest(new UrlWasmSource(\"\u003chttps://github.com/extism/plugins/releases/latest/download/count_vowels.wasm\u003e\"))\n{\n    Config = new Dictionary\u003cstring, string\u003e\n    {\n        { \"vowels\", \"aeiouyAEIOUY\" }\n    },\n};\n\nusing var plugin2 = new Plugin(manifest, new HostFunction[] { }, withWasi: true);\n\nvar output2 = plugin2.Call(\"count_vowels\", \"Yellow, World!\");\nConsole.WriteLine(output2);\n// =\u003e {\"count\": 4, \"total\": 4, \"vowels\": \"aeiouAEIOUY\"}\n```\n\nF#:\n```fsharp\nlet uri = Uri(\"https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm\")\nlet manifest = Manifest(new UrlWasmSource(uri))\nmanifest.Config \u003c- dict [(\"vowels\", \"aeiouAEIOU\")]\n\nlet plugin = new Plugin(manifest, Array.Empty\u003cHostFunction\u003e(), withWasi = true)\n\nlet output = plugin.Call(\"count_vowels\", \"Yellow, World!\")\nConsole.WriteLine(output)\n// =\u003e {\"count\": 3, \"total\": 3, \"vowels\": \"aeiouAEIOU\"}\n\nlet manifest2 = \n    Manifest(new UrlWasmSource(Uri(\"https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm\")))\nmanifest2.Config \u003c- dict [(\"vowels\", \"aeiouyAEIOUY\")]\n\nlet plugin2 =\n    new Plugin(manifest2, Array.Empty\u003cHostFunction\u003e(), withWasi = true)\n\nlet output2 = plugin2.Call(\"count_vowels\", \"Yellow, World!\")\nprintfn \"%s\" output2\n// =\u003e {\"count\": 4, \"total\": 4, \"vowels\": \"aeiouAEIOUY\"}\n```\n\n### Host Functions\n\nLet's extend our count-vowels example a little bit: Instead of storing the `total` in an ephemeral plug-in var, let's store it in a persistent key-value store!\n\nWasm can't use our KV store on it's own. This is where `Host Functions` come in.\n\n[Host functions](https://extism.org/docs/concepts/host-functions) allow us to grant new capabilities to our plug-ins from our application. They are simply some Go functions you write which can be passed down and invoked from any language inside the plug-in.\n\nLet's load the manifest like usual but load up this `count_vowels_kvstore` plug-in:\n\nC#:\n```csharp\nvar manifest = new Manifest(new UrlWasmSource(\"https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm\"));\n```\n\nF#:\n```fsharp\nlet manifest = Manifest(new UrlWasmSource(Uri(\"https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm\")))\n```\n\n\u003e *Note*: The source code for this is [here](https://github.com/extism/plugins/blob/main/count_vowels_kvstore/src/lib.rs) and is written in rust, but it could be written in any of our PDK languages.\n\nUnlike our previous plug-in, this plug-in expects you to provide host functions that satisfy our its import interface for a KV store.\n\nWe want to expose two functions to our plugin, `void kv_write(key string, value byte[])` which writes a bytes value to a key and `byte[] kv_read(key string)` which reads the bytes at the given `key`.\n\nC#:\n```csharp\n// pretend this is Redis or something :)\nvar kvStore = new Dictionary\u003cstring, byte[]\u003e();\n\nvar functions = new[]\n{\n    HostFunction.FromMethod(\"kv_read\", null, (CurrentPlugin plugin, long keyOffset) =\u003e\n    {\n        var key = plugin.ReadString(keyOffset);\n        if (!kvStore.TryGetValue(key, out var value))\n        {\n            value = new byte[] { 0, 0, 0, 0 };\n        }\n\n        Console.WriteLine($\"Read {BitConverter.ToUInt32(value)} from key={key}\");\n        return plugin.WriteBytes(value);\n    }),\n\n    HostFunction.FromMethod(\"kv_write\", null, (CurrentPlugin plugin, long keyOffset, long valueOffset) =\u003e\n    {\n        var key = plugin.ReadString(keyOffset);\n        var value = plugin.ReadBytes(valueOffset);\n\n        Console.WriteLine($\"Writing value={BitConverter.ToUInt32(value)} from key={key}\");\n        kvStore[key] = value.ToArray();\n    })\n};\n```\n\nF#:\n```fsharp\nlet kvStore = new Dictionary\u003cstring, byte[]\u003e()\n\nlet functions =\n    [|\n        HostFunction.FromMethod(\"kv_read\", null, fun (plugin: CurrentPlugin) (offs: int64) -\u003e\n            let key = plugin.ReadString(offs)\n            let value = \n                match kvStore.TryGetValue(key) with\n                | true, v -\u003e v\n                | _ -\u003e [| 0uy; 0uy; 0uy; 0uy |] // Default value if key not found\n\n            Console.WriteLine($\"Read {BitConverter.ToUInt32(value, 0)} from key={key}\")\n            plugin.WriteBytes(value)\n        )\n\n        HostFunction.FromMethod(\"kv_write\", null, fun (plugin: CurrentPlugin) (kOffs: int64) (vOffs: int64) -\u003e\n            let key = plugin.ReadString(kOffs)\n            let value = plugin.ReadBytes(vOffs).ToArray()\n\n            Console.WriteLine($\"Writing value={BitConverter.ToUInt32(value, 0)} from key={key}\")\n            kvStore.[key] \u003c- value\n        )\n    |]\n```\n\n\u003e *Note*: In order to write host functions you should get familiar with the methods on the CurrentPlugin type. The `plugin` parameter is an instance of this type.\n\nWe need to pass these imports to the plug-in to create them. All imports of a plug-in must be satisfied for it to be initialized:\n\nC#:\n```csharp\nusing var plugin = new Plugin(manifest, functions, withWasi: true);\n\nvar output = plugin.Call(\"count_vowels\", \"Hello World!\");\n\nConsole.WriteLine(output);\n// =\u003e Read 0 from key=count-vowels\"\n// =\u003e Writing value=3 from key=count-vowels\"\n// =\u003e {\"count\": 3, \"total\": 3, \"vowels\": \"aeiouAEIOU\"}\n\noutput = plugin.Call(\"count_vowels\", \"Hello World!\");\n\nConsole.WriteLine(output);\n// =\u003e Read 3 from key=count-vowels\"\n// =\u003e Writing value=6 from key=count-vowels\"\n// =\u003e {\"count\": 3, \"total\": 6, \"vowels\": \"aeiouAEIOU\"}\n```\n\nF#:\n```fsharp\nlet plugin = new Plugin(manifest, functions, withWasi = true)\n\nlet output = plugin.Call(\"count_vowels\", \"Hello World!\")\nprintfn \"%s\" output\n// =\u003e Read 0 from key=count-vowels\n// =\u003e Writing value=3 from key=count-vowels\n// =\u003e {\"count\": 3, \"total\": 3, \"vowels\": \"aeiouAEIOU\"}\n\nlet output2 = plugin.Call(\"count_vowels\", \"Hello World!\")\nprintfn \"%s\" output2\n// =\u003e Read 3 from key=count-vowels\n// =\u003e Writing value=6 from key=count-vowels\n// =\u003e {\"count\": 3, \"total\": 6, \"vowels\": \"aeiouAEIOU\"}\n```\n\n## Passing context to host functions\n\nExtism provides two ways to pass context to host functions:\n\n### UserData\nUserData allows you to associate persistent state with a host function that remains available across all calls to that function. This is useful for maintaining configuration or state that should be available throughout the lifetime of the host function.\n\nC#:\n\n```csharp\nvar hostFunc = new HostFunction(\n    \"hello_world\",\n    new[] { ExtismValType.PTR },\n    new[] { ExtismValType.PTR }, \n    \"Hello again!\",  // \u003c= userData, this can be any .NET object\n    (CurrentPlugin plugin, Span\u003cExtismVal\u003e inputs, Span\u003cExtismVal\u003e outputs) =\u003e {\n        var text = plugin.GetUserData\u003cstring\u003e(); // \u003c= We're retrieving the data back\n        // Use text...\n    });\n```\n\nF#:\n\n```fsharp\n// Create host function with userData\nlet hostFunc = new HostFunction(\n    \"hello_world\",\n    [| ExtismValType.PTR |],\n    [| ExtismValType.PTR |], \n    \"Hello again!\",  // userData can be any .NET object\n    (fun (plugin: CurrentPlugin) (inputs: Span\u003cExtismVal\u003e) (outputs: Span\u003cExtismVal\u003e) -\u003e\n        // Retrieve the userData\n        let text = plugin.GetUserData\u003cstring\u003e()\n        printfn \"%s\" text // Prints: \"Hello again!\"\n        // Rest of function implementation...\n    ))\n```\n\nThe userData object is preserved for the lifetime of the host function and can be retrieved in any call using `CurrentPlugin.GetUserData\u003cT\u003e()`. If no userData was provided, `GetUserData\u003cT\u003e()` will return the default value for type `T`.\n\n### Call Host Context \n\nCall Host Context provides a way to pass per-call context data when invoking a plugin function. This is useful when you need to provide data specific to a particular function call rather than data that persists across all calls.\n\nC#:\n\n```csharp\n// Pass context for specific call\nvar context = new Dictionary\u003cstring, object\u003e { { \"requestId\", 42 } };\nvar result = plugin.CallWithHostContext(\"function_name\", inputData, context);\n\n// Access in host function\nvoid HostFunction(CurrentPlugin plugin, Span\u003cExtismVal\u003e inputs, Span\u003cExtismVal\u003e outputs)\n{\n    var context = plugin.GetCallHostContext\u003cDictionary\u003cstring, object\u003e\u003e();\n    // Use context...\n}\n```\n\nF#:\n\n```fsharp\n// Create context for specific call\nlet context = dict [ \"requestId\", box 42 ]\n\n// Call plugin with context\nlet result = plugin.CallWithHostContext(\"function_name\", inputData, context)\n\n// Access context in host function\nlet hostFunction (plugin: CurrentPlugin) (inputs: Span\u003cExtismVal\u003e) (outputs: Span\u003cExtismVal\u003e) =\n    match plugin.GetCallHostContext\u003cIDictionary\u003cstring, obj\u003e\u003e() with\n    | null -\u003e printfn \"No context available\"\n    | context -\u003e \n        let requestId = context.[\"requestId\"] :?\u003e int\n        printfn \"Request ID: %d\" requestId\n```\n\nHost context is only available for the duration of the specific function call and can be retrieved using `CurrentPlugin.GetHostContext\u003cT\u003e()`. If no context was provided for the call, `GetHostContext\u003cT\u003e()` will return the default value for type `T`.\n\n## Fuel limit\n\nThe fuel limit feature allows you to constrain plugin execution by limiting the number of instructions it can execute. This provides a safeguard against infinite loops or excessive resource consumption.\n\n### Setting a fuel limit\n\nSet the fuel limit when initializing a plugin:\n\nC#:\n\n```csharp\nvar manifest = new Manifest(...);\nvar options = new PluginIntializationOptions {\n    FuelLimit = 1000,  // plugin can execute 1000 instructions\n    WithWasi = true\n};\n\nvar plugin = new Plugin(manifest, functions, options);\n```\n\nF#:\n\n```fsharp\nlet manifest = Manifest(PathWasmSource(\"/path/to/plugin.wasm\"))\nlet options = PluginIntializationOptions(\n    FuelLimit = Nullable\u003cint64\u003e(1000L), // plugin can execute 1000 instructions\n    WithWasi = true\n)\n\nuse plugin = new Plugin(manifest, Array.empty\u003cHostFunction\u003e, options)\n```\n\nWhen the fuel limit is exceeded, the plugin execution is terminated and an `ExtismException` is thrown containing \"fuel\" in the error message.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextism%2Fdotnet-sdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fextism%2Fdotnet-sdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextism%2Fdotnet-sdk/lists"}