Generated the JSON contracts of providers, the runtime, and services at compile time (#1150)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-03 07:59:30 +01:00
1 parent e954881fda
commit 24e3c22db3
140 files changed
+1415 -292

No files matched your search

@@ -117,8 +117,8 @@ public sealed class AnthropicContentBlockBuilder
case TYPE_TEXT:
return this.BuildFromSeed(new()
{
["type"] = JsonSerializer.Serialize(TYPE_TEXT),
["text"] = JsonSerializer.Serialize(this.text.ToString()),
["type"] = JsonSerializer.Serialize(TYPE_TEXT, ProviderJsonOptions.OPTIONS),
["text"] = JsonSerializer.Serialize(this.text.ToString(), ProviderJsonOptions.OPTIONS),
});
case TYPE_THINKING:
@@ -129,9 +129,9 @@ public sealed class AnthropicContentBlockBuilder
//
return this.BuildFromSeed(new()
{
["type"] = JsonSerializer.Serialize(TYPE_THINKING),
["thinking"] = JsonSerializer.Serialize(this.thinking.ToString()),
["signature"] = JsonSerializer.Serialize(this.signature),
["type"] = JsonSerializer.Serialize(TYPE_THINKING, ProviderJsonOptions.OPTIONS),
["thinking"] = JsonSerializer.Serialize(this.thinking.ToString(), ProviderJsonOptions.OPTIONS),
["signature"] = JsonSerializer.Serialize(this.signature, ProviderJsonOptions.OPTIONS),
});
case TYPE_TOOL_USE:
@@ -10,4 +10,12 @@ namespace AIStudio.Provider.Anthropic;
/// thinking blocks, and the thinking blocks have to return exactly as they arrived — reading and
/// rebuilding them would risk changing them.
/// </remarks>
public sealed record AnthropicMessage(IList<JsonElement> Content, string Role = "assistant") : IMessage<IList<JsonElement>>;
public sealed record AnthropicMessage(IList<JsonElement> Content, string Role = "assistant") : IMessage<IList<JsonElement>>
{
//
// The properties of the parameters, declared again to be settable, as the messages demand it. Keep them in
// the order of the parameters, which is the order they get written in:
//
public IList<JsonElement> Content { get; set; } = Content;
public string Role { get; set; } = Role;
}
@@ -152,9 +152,9 @@ public sealed class AnthropicMessageStreamAccumulator
this.unparsableToolArguments[builder.ToolUseId] = builder.UnparsableToolArguments;
}
private static JsonElement EmptyTextBlock() => JsonSerializer.SerializeToElement(new
private static JsonElement EmptyTextBlock()
{
type = "text",
text = string.Empty,
});
using var document = JsonDocument.Parse("""{"type":"text","text":""}""");
return document.RootElement.Clone();
}
}
@@ -14,5 +14,5 @@ public sealed record AnthropicStreamMessage
/// <summary>
/// What the request carried, where the stream states it.
/// </summary>
public AnthropicUsage? Usage { get; init; }
public AnthropicUsage? Usage { get; set; }
}
@@ -11,11 +11,11 @@ namespace AIStudio.Provider.Anthropic;
/// </remarks>
public sealed record AnthropicTool
{
public string Name { get; init; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Description { get; init; } = string.Empty;
public string Description { get; set; } = string.Empty;
public bool Strict { get; init; }
public bool Strict { get; set; }
public JsonElement InputSchema { get; init; }
public JsonElement InputSchema { get; set; }
}
@@ -4,11 +4,11 @@ namespace AIStudio.Provider.Anthropic;
public sealed record AnthropicToolResultContent
{
public string Type { get; init; } = "tool_result";
public string Type { get; set; } = "tool_result";
public string ToolUseId { get; init; } = string.Empty;
public string ToolUseId { get; set; } = string.Empty;
public string Content { get; init; } = string.Empty;
public string Content { get; set; } = string.Empty;
/// <summary>
/// Whether the tool failed rather than returning a result.
@@ -18,5 +18,5 @@ public sealed record AnthropicToolResultContent
/// result stays byte-identical to what earlier versions sent.
/// </remarks>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public bool IsError { get; init; }
public bool IsError { get; set; }
}
@@ -7,4 +7,12 @@ namespace AIStudio.Provider.Anthropic;
/// All results of one turn belong in a single message. Splitting them across several messages
/// teaches the model to stop asking for more than one tool at a time.
/// </remarks>
public sealed record AnthropicToolResultMessage(IList<AnthropicToolResultContent> Content, string Role = "user") : IMessage<IList<AnthropicToolResultContent>>;
public sealed record AnthropicToolResultMessage(IList<AnthropicToolResultContent> Content, string Role = "user") : IMessage<IList<AnthropicToolResultContent>>
{
//
// The properties of the parameters, declared again to be settable, as the messages demand it. Keep them in
// the order of the parameters, which is the order they get written in:
//
public IList<AnthropicToolResultContent> Content { get; set; } = Content;
public string Role { get; set; } = Role;
}
@@ -23,17 +23,17 @@ public sealed record AnthropicUsage
/// <summary>
/// What the request carried after its last cache breakpoint, which is all of it without caching.
/// </summary>
public int? InputTokens { get; init; }
public int? InputTokens { get; set; }
/// <summary>
/// What the request wrote to the cache.
/// </summary>
public int? CacheCreationInputTokens { get; init; }
public int? CacheCreationInputTokens { get; set; }
/// <summary>
/// What the request read from the cache.
/// </summary>
public int? CacheReadInputTokens { get; init; }
public int? CacheReadInputTokens { get; set; }
/// <summary>
/// States what this block reports, as far as it can be believed.
@@ -10,7 +10,11 @@ namespace AIStudio.Provider.Anthropic;
/// <param name="MaxTokens">The maximum number of tokens to generate.</param>
/// <param name="Stream">Whether to stream the chat completion.</param>
/// <param name="System">The system prompt for the chat completion.</param>
public readonly record struct ChatRequest(
/// <remarks>
/// Not readonly, so that its properties can be settable: the source generation of System.Text.Json refuses to
/// write extension data which is init-only.
/// </remarks>
public record struct ChatRequest(
string Model,
IList<IMessageBase> Messages,
int MaxTokens,
@@ -26,9 +30,12 @@ public readonly record struct ChatRequest(
/// tools at all, and the final round of a tool conversation has to offer none.
/// </remarks>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public IList<AnthropicTool>? Tools { get; init; }
public IList<AnthropicTool>? Tools { get; set; }
// Attention: The "required" modifier is not supported for [JsonExtensionData].
//
// Attention: The "required" modifier is not supported for [JsonExtensionData], and neither is init: the source
// generation of System.Text.Json refuses to write the request at all then.
//
[JsonExtensionData]
public IDictionary<string, object> AdditionalApiParameters { get; init; } = new Dictionary<string, object>();
public IDictionary<string, object> AdditionalApiParameters { get; set; } = new Dictionary<string, object>();
}
@@ -4,7 +4,7 @@ public record SubContentBase64Image : ISubContentImageSource
{
public SubContentImageType Type => SubContentImageType.BASE64;
public string MediaType { get; init; } = string.Empty;
public string MediaType { get; set; } = string.Empty;
public string Data { get; init; } = string.Empty;
public string Data { get; set; } = string.Empty;
}
@@ -4,6 +4,15 @@ namespace AIStudio.Provider.Anthropic;
public record SubContentImage(SubContentType Type, ISubContentImageSource Source) : ISubContent
{
//
// The properties of the parameters, declared again to be settable. The source generation of System.Text.Json
// sets every init-only property while it creates the object, also one missing in the JSON, so its value from
// the parameterless constructor would get lost. Keep them first and in the order of the parameters, which is
// the order they get written in:
//
public SubContentType Type { get; set; } = Type;
public ISubContentImageSource Source { get; set; } = Source;
public SubContentImage() : this(SubContentType.IMAGE, new SubContentImageUrl())
{
}
@@ -4,5 +4,5 @@ public record SubContentImageUrl : ISubContentImageSource
{
public SubContentImageType Type => SubContentImageType.URL;
public string Url { get; init; } = string.Empty;
public string Url { get; set; } = string.Empty;
}