.NET SDK
Purpose
The .NET SDK provides a IAiApiMapperClient abstraction and orchestrator adapters for Microsoft.Extensions.AI, Semantic Kernel, and Azure AI Foundry Agents.
Main Capabilities
- Singleton client registered via
IServiceCollection - Automatic MCP session management and tool pagination
- System prompt and tool list fetching (opt-in in-memory cache via
CacheTools/CacheSystemPrompt) - Three authentication modes: API key, OAuth2 client credentials, delegated bearer
- Structured logging via
ILogger<ApiMapperClient> - Correlation ID propagation via
CorrelationIdProvideror a per-call argument - Resilience pipeline via
Microsoft.Extensions.Http.Resilience - Adapters for Microsoft.Extensions.AI, Semantic Kernel 1.73, Azure AI Foundry Agents
Packages
| Package | Description |
|---|---|
CodedProjects.AI.ApiMapper.Client |
Core client |
CodedProjects.AI.ApiMapper.Client.MicrosoftExtensionsAI |
ME.AI AIFunction adapter |
CodedProjects.AI.ApiMapper.Client.SemanticKernel |
SK KernelPlugin adapter |
CodedProjects.AI.ApiMapper.Client.AzureAIFoundry |
Azure AI Foundry toolset and run loop |
Registration
Register the client in Program.cs or Startup.cs. It is registered as a singleton.
builder.Services
.AddApiMapperClient(opts =>
{
opts.BaseUrl = configuration["ApiMapper:BaseUrl"];
opts.TenantId = Guid.Parse(configuration["ApiMapper:TenantId"]);
opts.ClientId = "my-app";
opts.SystemPromptResourceUri = "apimapper://toolsets/system-prompt";
})
.WithApiKey(configuration["ApiMapper:ApiKey"]);
See SDK Authentication for OAuth2 and delegated bearer options.
Raw Tool Loop
Fetch the system prompt, load tools, and drive a manual OpenAI tool-call loop.
var systemPrompt = await client.GetSystemPromptAsync();
var tools = await client.GetToolsAsync();
var chatTools = tools.Select(t => ChatTool.CreateFunctionTool(
t.Name, t.Description, BinaryData.FromObjectAsJson(t.InputSchema))).ToList();
var messages = new List<ChatMessage>
{
new SystemChatMessage(systemPrompt ?? "You are a helpful assistant."),
new UserChatMessage(userMessage)
};
while (true)
{
var response = await openai.CompleteChatAsync(messages,
new ChatCompletionOptions { Tools = { chatTools } });
if (response.Value.FinishReason == ChatFinishReason.ToolCalls)
{
messages.Add(new AssistantChatMessage(response.Value));
foreach (var call in response.Value.ToolCalls)
{
var args = JsonSerializer.Deserialize<Dictionary<string, object>>(call.FunctionArguments.ToString());
var result = await client.InvokeToolAsync(call.FunctionName, args);
messages.Add(new ToolChatMessage(call.Id, result?.ToText() ?? ""));
}
continue;
}
Console.WriteLine(response.Value.Content[0].Text);
break;
}
Microsoft.Extensions.AI
ApiMapperAIFunctionFactory.CreateFunctionsAsync converts tools to AIFunction instances. Pass them to any IChatClient.
var systemPrompt = await client.GetSystemPromptAsync();
var functions = await ApiMapperAIFunctionFactory.CreateFunctionsAsync(client);
var messages = new List<ChatMessage>
{
new(ChatRole.System, systemPrompt ?? "You are a helpful assistant."),
new(ChatRole.User, userMessage)
};
var options = new ChatOptions { Tools = [..functions] };
ChatResponse response;
do
{
response = await chatClient.GetResponseAsync(messages, options);
messages.AddRange(response.Messages);
}
while (response.FinishReason == ChatFinishReason.ToolCalls);
Console.WriteLine(response.Text);
Semantic Kernel
AddApiMapperPluginAsync registers all Runtime tools as a named KernelPlugin with automatic function calling.
await kernel.AddApiMapperPluginAsync(client, "RuntimeTools");
var systemPrompt = await client.GetSystemPromptAsync()
?? "You are a helpful assistant.";
var settings = new OpenAIPromptExecutionSettings
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};
var result = await kernel.InvokePromptAsync(
$"<system>{systemPrompt}</system>\n\n{userMessage}",
new KernelArguments(settings));
Console.WriteLine(result);
Azure AI Foundry Agents
ApiMapperFoundryToolset converts tools to Azure AI Agents FunctionToolDefinition instances. AgentRunLoop.RunAsync drives the submit → poll → tool call loop.
var systemPrompt = await client.GetSystemPromptAsync()
?? "You are a helpful assistant.";
var toolset = await ApiMapperFoundryToolset.CreateAsync(client);
var agent = await agentsClient.Administration.CreateAgentAsync(
model: modelDeployment,
instructions: systemPrompt,
tools: toolset.Definitions);
var thread = await agentsClient.Threads.CreateThreadAsync();
var answer = await AgentRunLoop.RunAsync(agentsClient, agent.Value, thread.Value, toolset, userMessage);
Console.WriteLine(answer);
Logging
The client uses ILogger<ApiMapperClient>. Configure your preferred sink in appsettings.json. Auth headers and secrets are never written to logs.
{
"Logging": {
"LogLevel": {
"CodedProjects.AI.ApiMapper.Client": "Information"
}
}
}
Set to Debug to see MCP handshakes, per-tool invocation messages, and cache hits (when caching is explicitly enabled).
Correlation IDs
Set CorrelationIdProvider to send the calling application's own identifier to the Runtime on every
request, or pass one for a single call. The per-call value wins.
builder.Services.AddApiMapperClient(opts =>
{
// Invoked once per request — Activity.Current is read at call time, not at registration.
opts.CorrelationIdProvider = () => Activity.Current?.TraceId.ToString();
});
// Overrides the provider for this call only.
var result = await client.InvokeToolAsync("crm-lookup", args, correlationId: "order-4711");
The existing InvokeToolAsync(toolName, arguments, cancellationToken) overload is unchanged, so no
existing call site needs to move.
A provider that throws is logged and ignored rather than failing the tool call. When the Runtime does
not echo the value back on X-External-Correlation-Id, the client logs a warning — see
SDK → Correlation IDs for the accepted format.
