mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-06-27 13:16:26 +00:00
Removed Anthropic Provider support, because it differs too much from the Chat Completion / Responses API version
This commit is contained in:
parent
d0e1966e9e
commit
2da420e549
@ -3172,6 +3172,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3364063757"] = "The selec
|
||||
-- Close
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3448155331"] = "Close"
|
||||
|
||||
-- Tool calling for this provider is not implemented yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3776963202"] = "Tool calling for this provider is not implemented yet."
|
||||
|
||||
-- No tools are available in this context.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TOOLSELECTION::T3904490680"] = "No tools are available in this context."
|
||||
|
||||
@ -6727,9 +6730,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T3948127789"] = "Suggestion"
|
||||
-- Your stage directions
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T779923726"] = "Your stage directions"
|
||||
|
||||
-- The tool calling request failed with status code {0}. See the logs for details.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::ANTHROPIC::PROVIDERANTHROPIC::T3117779001"] = "The tool calling request failed with status code {0}. See the logs for details."
|
||||
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The server might be down or having issues. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1000247110"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The server might be down or having issues. The provider message is: '{2}'"
|
||||
|
||||
@ -8017,9 +8017,6 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS:
|
||||
-- Maximum Content Characters
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2801581200"] = "Maximum Content Characters"
|
||||
|
||||
-- Optional host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, example.com. Allowed private hosts require a high-confidence provider. For allowed internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2866833707"] = "Optional host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, example.com. Allowed private hosts require a high-confidence provider. For allowed internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication."
|
||||
|
||||
-- Optional HTTP timeout for loading a web page in seconds.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T2941521561"] = "Optional HTTP timeout for loading a web page in seconds."
|
||||
|
||||
@ -8038,6 +8035,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS:
|
||||
-- The web page was not loaded because private or VPN web pages require a High-confidence provider.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T3856267430"] = "The web page was not loaded because private or VPN web pages require a High-confidence provider."
|
||||
|
||||
-- Optional host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a high-confidence provider. For allowed internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::READWEBPAGETOOL::T854695329"] = "Optional host allowlist for private or VPN web pages. For security reasons, private or VPN web pages aren't allowed to be read by default. Separate host patterns with commas, such as example.de, *.example.de. Allowed private hosts require a high-confidence provider. For allowed internal hosts, AI Studio also tries the operating system's default sign-in automatically when the server responds with integrated authentication."
|
||||
|
||||
-- Maximum Results
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::TOOLCALLINGSYSTEM::TOOLCALLINGIMPLEMENTATIONS::SEARXNGWEBSEARCHTOOL::T1273024715"] = "Maximum Results"
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
@inherits MSGComponentBase
|
||||
|
||||
<div class="d-flex">
|
||||
<MudTooltip Text="@T("Select tools")" Placement="Placement.Top">
|
||||
<MudTooltip Text="@this.ToolButtonTooltip" Placement="Placement.Top">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Build" Class="@this.PopoverButtonClasses" OnClick="@this.ToggleSelection"/>
|
||||
</MudTooltip>
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
<MudCardContent Style="min-width: 28em; max-height: 60vh; max-width: 48vw; overflow: auto;">
|
||||
@if (!this.SupportsTools)
|
||||
{
|
||||
<MudText Typo="Typo.body1">@T("The selected provider or model does not support tool calling.")</MudText>
|
||||
<MudText Typo="Typo.body1">@this.UnsupportedToolsMessage</MudText>
|
||||
}
|
||||
else if (this.Disabled)
|
||||
{
|
||||
|
||||
@ -51,9 +51,21 @@ public partial class ToolSelection : MSGComponentBase
|
||||
|
||||
private bool SupportsTools =>
|
||||
this.LLMProvider != AIStudio.Settings.Provider.NONE &&
|
||||
this.LLMProvider.GetModelCapabilities().Contains(Capability.CHAT_COMPLETION_API) &&
|
||||
(this.LLMProvider.GetModelCapabilities().Contains(Capability.CHAT_COMPLETION_API) ||
|
||||
this.LLMProvider.GetModelCapabilities().Contains(Capability.RESPONSES_API)) &&
|
||||
this.LLMProvider.GetModelCapabilities().Contains(Capability.FUNCTION_CALLING);
|
||||
|
||||
private bool IsAnthropicProvider => this.LLMProvider != AIStudio.Settings.Provider.NONE &&
|
||||
this.LLMProvider.UsedLLMProvider is LLMProviders.ANTHROPIC;
|
||||
|
||||
private string ToolButtonTooltip => this.SupportsTools
|
||||
? this.T("Select tools")
|
||||
: this.UnsupportedToolsMessage;
|
||||
|
||||
private string UnsupportedToolsMessage => this.IsAnthropicProvider
|
||||
? this.T("Tool calling for this provider is not implemented yet.")
|
||||
: this.T("The selected model does not support tool calling.");
|
||||
|
||||
private ConfidenceLevel ProviderConfidence => this.LLMProvider == AIStudio.Settings.Provider.NONE
|
||||
? ConfidenceLevel.NONE
|
||||
: this.LLMProvider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level;
|
||||
|
||||
@ -1,74 +0,0 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AIStudio.Provider.Anthropic;
|
||||
|
||||
public sealed record AnthropicTool
|
||||
{
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
public string Description { get; init; } = string.Empty;
|
||||
|
||||
public bool Strict { get; init; }
|
||||
|
||||
public JsonElement InputSchema { get; init; }
|
||||
}
|
||||
|
||||
public sealed record AnthropicMessage(IList<JsonElement> Content, string Role) : IMessage<IList<JsonElement>>;
|
||||
|
||||
public sealed record AnthropicToolResultMessage(IList<AnthropicToolResultContent> Content, string Role = "user") : IMessage<IList<AnthropicToolResultContent>>;
|
||||
|
||||
public sealed record AnthropicToolResultContent
|
||||
{
|
||||
public string Type { get; init; } = "tool_result";
|
||||
|
||||
public string ToolUseId { get; init; } = string.Empty;
|
||||
|
||||
public string Content { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed record AnthropicResponse
|
||||
{
|
||||
public string StopReason { get; init; } = string.Empty;
|
||||
|
||||
public IList<JsonElement> Content { get; init; } = [];
|
||||
|
||||
public IReadOnlyList<AnthropicToolUse> GetToolUses() => this.Content
|
||||
.Where(x => ReadString(x, "type").Equals("tool_use", StringComparison.Ordinal))
|
||||
.Select(x => new AnthropicToolUse
|
||||
{
|
||||
Id = ReadString(x, "id"),
|
||||
Name = ReadString(x, "name"),
|
||||
Input = x.TryGetProperty("input", out var input) ? input : default,
|
||||
})
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.Id) && !string.IsNullOrWhiteSpace(x.Name))
|
||||
.ToList();
|
||||
|
||||
public string GetTextOutput() => string.Concat(this.Content
|
||||
.Where(x => ReadString(x, "type").Equals("text", StringComparison.Ordinal))
|
||||
.Select(x => ReadString(x, "text")));
|
||||
|
||||
public bool HasFinalStopReason() => this.StopReason is $"" or "end_turn" or "stop_sequence";
|
||||
|
||||
private static string ReadString(JsonElement item, string propertyName)
|
||||
{
|
||||
if (item.ValueKind is not JsonValueKind.Object ||
|
||||
!item.TryGetProperty(propertyName, out var property) ||
|
||||
property.ValueKind is not JsonValueKind.String)
|
||||
return string.Empty;
|
||||
|
||||
return property.GetString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record AnthropicToolUse
|
||||
{
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
public JsonElement Input { get; init; }
|
||||
|
||||
public string Arguments => this.Input.ValueKind is JsonValueKind.Undefined
|
||||
? "{}"
|
||||
: this.Input.GetRawText();
|
||||
}
|
||||
@ -1,16 +1,12 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
using AIStudio.Tools.Rust;
|
||||
using AIStudio.Tools.ToolCallingSystem;
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace AIStudio.Provider.Anthropic;
|
||||
|
||||
@ -39,7 +35,7 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, n
|
||||
yield break;
|
||||
|
||||
// Parse the API parameters:
|
||||
var apiParameters = this.ParseAdditionalApiParameters("system", "tools");
|
||||
var apiParameters = this.ParseAdditionalApiParameters("system");
|
||||
var maxTokens = 4_096;
|
||||
if (TryPopIntParameter(apiParameters, "max_tokens", out var parsedMaxTokens))
|
||||
maxTokens = parsedMaxTokens;
|
||||
@ -77,40 +73,6 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, n
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
var toolRegistry = Program.SERVICE_PROVIDER.GetService<ToolRegistry>();
|
||||
var toolExecutor = Program.SERVICE_PROVIDER.GetService<ToolExecutor>();
|
||||
var currentAssistantContent = chatThread.Blocks.LastOrDefault(x => x.Role is ChatRole.AI)?.Content as ContentText;
|
||||
currentAssistantContent?.ToolInvocations.Clear();
|
||||
var providerConfidence = this.Provider.GetConfidence(settingsManager).Level;
|
||||
IReadOnlyList<(ToolDefinition Definition, IToolImplementation Implementation)> runnableTools = toolRegistry is null
|
||||
? []
|
||||
: await toolRegistry.GetRunnableToolsAsync(
|
||||
chatThread.RuntimeComponent,
|
||||
chatThread.RuntimeSelectedToolIds,
|
||||
this.Provider.GetModelCapabilities(chatModel),
|
||||
providerConfidence,
|
||||
settingsManager.IsToolSelectionVisible(chatThread.RuntimeComponent));
|
||||
|
||||
if (toolExecutor is not null && runnableTools.Count > 0)
|
||||
{
|
||||
var systemPrompt = chatThread.PrepareSystemPrompt(settingsManager, runnableTools.Select(x => x.Definition));
|
||||
await foreach (var content in this.StreamWithLocalTools(
|
||||
chatModel,
|
||||
messages,
|
||||
systemPrompt,
|
||||
maxTokens,
|
||||
apiParameters,
|
||||
runnableTools,
|
||||
toolExecutor,
|
||||
currentAssistantContent,
|
||||
requestedSecret,
|
||||
providerConfidence,
|
||||
token))
|
||||
yield return content;
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Prepare the Anthropic HTTP chat request:
|
||||
var chatRequest = JsonSerializer.Serialize(new ChatRequest
|
||||
@ -148,169 +110,6 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, n
|
||||
yield return content;
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<ContentStreamChunk> StreamWithLocalTools(
|
||||
Model chatModel,
|
||||
IList<IMessageBase> baseMessages,
|
||||
string systemPrompt,
|
||||
int maxTokens,
|
||||
IDictionary<string, object> apiParameters,
|
||||
IReadOnlyList<(ToolDefinition Definition, IToolImplementation Implementation)> runnableTools,
|
||||
ToolExecutor toolExecutor,
|
||||
ContentText? currentAssistantContent,
|
||||
RequestedSecret requestedSecret,
|
||||
ConfidenceLevel providerConfidence,
|
||||
[EnumeratorCancellation] CancellationToken token)
|
||||
{
|
||||
var providerTools = runnableTools
|
||||
.Select(x => (object)new AnthropicTool
|
||||
{
|
||||
Name = x.Definition.Function.Name,
|
||||
Description = x.Definition.Function.Description,
|
||||
Strict = x.Definition.Function.Strict,
|
||||
InputSchema = NormalizeInputSchemaForAnthropic(x.Definition.Function.Parameters),
|
||||
})
|
||||
.ToList();
|
||||
var internalMessages = new List<IMessageBase>();
|
||||
var toolCallCount = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var requestDto = new ChatRequest
|
||||
{
|
||||
Model = chatModel.Id,
|
||||
Messages = [..baseMessages, ..internalMessages],
|
||||
MaxTokens = maxTokens,
|
||||
Stream = false,
|
||||
System = systemPrompt,
|
||||
Tools = providerTools,
|
||||
AdditionalApiParameters = apiParameters,
|
||||
};
|
||||
var response = await this.ExecuteMessagesRequest(requestDto, requestedSecret, token);
|
||||
if (response is null)
|
||||
{
|
||||
if (currentAssistantContent is not null)
|
||||
{
|
||||
currentAssistantContent.ToolRuntimeStatus = new();
|
||||
await currentAssistantContent.StreamingEvent();
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
var textOutput = response.GetTextOutput();
|
||||
var toolUses = response.GetToolUses();
|
||||
if (toolUses.Count > 0 && !string.IsNullOrWhiteSpace(textOutput))
|
||||
yield return new ContentStreamChunk(textOutput, []);
|
||||
|
||||
if (toolUses.Count == 0)
|
||||
{
|
||||
if (currentAssistantContent is not null)
|
||||
{
|
||||
currentAssistantContent.ToolRuntimeStatus = new();
|
||||
await currentAssistantContent.StreamingEvent();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(textOutput))
|
||||
yield return new ContentStreamChunk(textOutput, []);
|
||||
|
||||
if (!response.HasFinalStopReason())
|
||||
{
|
||||
yield return new ContentStreamChunk($"The model stopped with reason '{response.StopReason}' before returning a final answer.", []);
|
||||
yield break;
|
||||
}
|
||||
|
||||
else if (toolCallCount > 0)
|
||||
yield return new ContentStreamChunk("The model completed the tool call but did not return a final answer.", []);
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (currentAssistantContent is not null)
|
||||
{
|
||||
currentAssistantContent.ToolRuntimeStatus = new ToolRuntimeStatus
|
||||
{
|
||||
IsRunning = true,
|
||||
ToolNames = toolUses
|
||||
.Select(x => runnableTools.FirstOrDefault(tool => tool.Definition.Function.Name.Equals(x.Name, StringComparison.Ordinal)).Implementation?.GetDisplayName() ?? x.Name)
|
||||
.ToList(),
|
||||
};
|
||||
await currentAssistantContent.StreamingEvent();
|
||||
}
|
||||
|
||||
internalMessages.Add(new AnthropicMessage(response.Content, "assistant"));
|
||||
var toolResults = new List<AnthropicToolResultContent>();
|
||||
foreach (var toolUse in toolUses)
|
||||
{
|
||||
toolCallCount++;
|
||||
if (toolCallCount > ToolSelectionRules.MAX_TOOL_CALLS)
|
||||
{
|
||||
var limitMessage = ToolSelectionRules.GetMaxToolCallsLimitMessage();
|
||||
currentAssistantContent?.ToolInvocations.Add(new ToolInvocationTrace
|
||||
{
|
||||
Order = toolCallCount,
|
||||
ToolId = toolUse.Name,
|
||||
ToolName = toolUse.Name,
|
||||
ToolCallId = toolUse.Id,
|
||||
Status = ToolInvocationTraceStatus.BLOCKED,
|
||||
StatusMessage = limitMessage,
|
||||
Result = limitMessage,
|
||||
});
|
||||
|
||||
if (currentAssistantContent is not null)
|
||||
{
|
||||
currentAssistantContent.ToolRuntimeStatus = new();
|
||||
await currentAssistantContent.StreamingEvent();
|
||||
}
|
||||
|
||||
yield return new ContentStreamChunk(limitMessage, []);
|
||||
yield break;
|
||||
}
|
||||
|
||||
var (toolContent, trace) = await toolExecutor.ExecuteAsync(
|
||||
toolUse.Id,
|
||||
toolUse.Name,
|
||||
toolUse.Arguments,
|
||||
runnableTools,
|
||||
providerConfidence,
|
||||
toolCallCount,
|
||||
token);
|
||||
|
||||
currentAssistantContent?.ToolInvocations.Add(trace);
|
||||
toolResults.Add(new AnthropicToolResultContent
|
||||
{
|
||||
ToolUseId = toolUse.Id,
|
||||
Content = toolContent,
|
||||
});
|
||||
}
|
||||
|
||||
internalMessages.Add(new AnthropicToolResultMessage(toolResults));
|
||||
|
||||
if (currentAssistantContent is not null)
|
||||
await currentAssistantContent.StreamingEvent();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<AnthropicResponse?> ExecuteMessagesRequest(ChatRequest requestDto, RequestedSecret requestedSecret, CancellationToken token)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, "messages");
|
||||
request.Headers.Add("x-api-key", await requestedSecret.Secret.Decrypt(ENCRYPTION));
|
||||
request.Headers.Add("anthropic-version", "2023-06-01");
|
||||
request.Content = new StringContent(JsonSerializer.Serialize(requestDto, JSON_SERIALIZER_OPTIONS), Encoding.UTF8, "application/json");
|
||||
|
||||
using var response = await this.HttpClient.SendAsync(request, token);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var responseBody = await response.Content.ReadAsStringAsync(token);
|
||||
LOGGER.LogError("Tool calling Anthropic Messages API request failed with status code {ResponseStatusCode} and body: '{ResponseBody}'.", response.StatusCode, responseBody);
|
||||
await MessageBus.INSTANCE.SendError(new(
|
||||
Icons.Material.Filled.Build,
|
||||
string.Format(TB("The tool calling request failed with status code {0}. See the logs for details."), (int)response.StatusCode)));
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<AnthropicResponse>(JSON_SERIALIZER_OPTIONS, token);
|
||||
}
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
/// <inheritdoc />
|
||||
public override async IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default)
|
||||
@ -368,9 +167,8 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, n
|
||||
{
|
||||
return Task.FromResult(ModelLoadResult.FromModels([]));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
private Task<ModelLoadResult> LoadModels(SecretStoreType storeType, CancellationToken token, string? apiKeyProvisional = null)
|
||||
{
|
||||
return this.LoadModelsResponse<ModelsResponse>(
|
||||
@ -393,72 +191,4 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, n
|
||||
},
|
||||
jsonSerializerOptions: JSON_SERIALIZER_OPTIONS);
|
||||
}
|
||||
|
||||
private static JsonElement NormalizeInputSchemaForAnthropic(JsonElement schema)
|
||||
{
|
||||
JsonNode? root = JsonNode.Parse(schema.GetRawText());
|
||||
if (root is JsonObject rootObject)
|
||||
NormalizeSchemaNode(rootObject);
|
||||
|
||||
return JsonSerializer.SerializeToElement(root);
|
||||
}
|
||||
|
||||
private static void NormalizeSchemaNode(JsonObject schemaObject)
|
||||
{
|
||||
var allowsNull = DeclaresNullType(schemaObject["type"]);
|
||||
if (allowsNull && schemaObject["enum"] is JsonArray enumArray)
|
||||
{
|
||||
for (var i = enumArray.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (enumArray[i]?.GetValueKind() is JsonValueKind.Null)
|
||||
enumArray.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (schemaObject["properties"] is JsonObject propertiesObject)
|
||||
{
|
||||
foreach (var property in propertiesObject)
|
||||
{
|
||||
if (property.Value is JsonObject childObject)
|
||||
NormalizeSchemaNode(childObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (schemaObject["items"] is JsonObject itemsObject)
|
||||
NormalizeSchemaNode(itemsObject);
|
||||
|
||||
if (schemaObject["anyOf"] is JsonArray anyOfArray)
|
||||
{
|
||||
foreach (var entry in anyOfArray)
|
||||
{
|
||||
if (entry is JsonObject childObject)
|
||||
NormalizeSchemaNode(childObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (schemaObject["oneOf"] is JsonArray oneOfArray)
|
||||
{
|
||||
foreach (var entry in oneOfArray)
|
||||
{
|
||||
if (entry is JsonObject childObject)
|
||||
NormalizeSchemaNode(childObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (schemaObject["allOf"] is JsonArray allOfArray)
|
||||
{
|
||||
foreach (var entry in allOfArray)
|
||||
{
|
||||
if (entry is JsonObject childObject)
|
||||
NormalizeSchemaNode(childObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool DeclaresNullType(JsonNode? typeNode) => typeNode switch
|
||||
{
|
||||
JsonValue value when value.TryGetValue<string>(out var typeName) => typeName.Equals("null", StringComparison.Ordinal),
|
||||
JsonArray array => array.Any(entry => entry is JsonValue value && value.TryGetValue<string>(out var typeName) && typeName.Equals("null", StringComparison.Ordinal)),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user