mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-04 22:47:21 +00:00
Generated the JSON contracts of providers, the runtime, and services at compile time (#1150)
This commit is contained in:
1 parent
e954881fda
commit
24e3c22db3
140 files changed
+1415
-292
No files matched your search
@@ -5,8 +5,8 @@ namespace AIStudio.Tools;
|
||||
public sealed class ContentStreamDocumentDetails
|
||||
{
|
||||
[JsonPropertyName("page_number")]
|
||||
public int? PageNumber { get; init; }
|
||||
public int? PageNumber { get; set; }
|
||||
|
||||
[JsonPropertyName("image")]
|
||||
public ContentStreamPptxImageData? Image { get; init; }
|
||||
public ContentStreamPptxImageData? Image { get; set; }
|
||||
}
|
||||
@@ -6,5 +6,5 @@ namespace AIStudio.Tools;
|
||||
public sealed class ContentStreamDocumentMetadata : ContentStreamSseMetadata
|
||||
{
|
||||
[JsonPropertyName("Document")]
|
||||
public ContentStreamDocumentDetails? Document { get; init; }
|
||||
public ContentStreamDocumentDetails? Document { get; set; }
|
||||
}
|
||||
@@ -7,23 +7,23 @@ namespace AIStudio.Tools;
|
||||
public sealed class ContentStreamErrorDetails
|
||||
{
|
||||
[JsonPropertyName("code")]
|
||||
public string? Code { get; init; }
|
||||
public string? Code { get; set; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string? Message { get; init; }
|
||||
public string? Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The page the failure belongs to, when the failure affects a single page only.
|
||||
/// </summary>
|
||||
[JsonPropertyName("page_number")]
|
||||
public int? PageNumber { get; init; }
|
||||
public int? PageNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The format the runtime identified by looking at the content, e.g. when it contradicts the
|
||||
/// file extension.
|
||||
/// </summary>
|
||||
[JsonPropertyName("detected_format")]
|
||||
public string? DetectedFormat { get; init; }
|
||||
public string? DetectedFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parsed error code.
|
||||
|
||||
@@ -7,5 +7,5 @@ namespace AIStudio.Tools;
|
||||
public sealed class ContentStreamErrorMetadata : ContentStreamSseMetadata
|
||||
{
|
||||
[JsonPropertyName("Error")]
|
||||
public ContentStreamErrorDetails? Error { get; init; }
|
||||
public ContentStreamErrorDetails? Error { get; set; }
|
||||
}
|
||||
@@ -7,5 +7,5 @@ namespace AIStudio.Tools;
|
||||
public sealed class ContentStreamPdfDetails
|
||||
{
|
||||
[JsonPropertyName("page_number")]
|
||||
public int? PageNumber { get; init; }
|
||||
public int? PageNumber { get; set; }
|
||||
}
|
||||
@@ -7,5 +7,5 @@ namespace AIStudio.Tools;
|
||||
public sealed class ContentStreamPdfMetadata : ContentStreamSseMetadata
|
||||
{
|
||||
[JsonPropertyName("Pdf")]
|
||||
public ContentStreamPdfDetails? Pdf { get; init; }
|
||||
public ContentStreamPdfDetails? Pdf { get; set; }
|
||||
}
|
||||
@@ -5,17 +5,17 @@ namespace AIStudio.Tools;
|
||||
public sealed class ContentStreamPptxImageData
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string? Id { get; init; }
|
||||
public string? Id { get; set; }
|
||||
|
||||
[JsonPropertyName("content")]
|
||||
public string? Content { get; init; }
|
||||
public string? Content { get; set; }
|
||||
|
||||
[JsonPropertyName("segment")]
|
||||
public int? Segment { get; init; }
|
||||
public int? Segment { get; set; }
|
||||
|
||||
[JsonPropertyName("is_end")]
|
||||
public bool IsEnd { get; init; }
|
||||
public bool IsEnd { get; set; }
|
||||
|
||||
[JsonPropertyName("media_type")]
|
||||
public string? MediaType { get; init; }
|
||||
public string? MediaType { get; set; }
|
||||
}
|
||||
@@ -7,8 +7,8 @@ namespace AIStudio.Tools;
|
||||
public sealed class ContentStreamPresentationDetails
|
||||
{
|
||||
[JsonPropertyName("slide_number")]
|
||||
public int? SlideNumber { get; init; }
|
||||
public int? SlideNumber { get; set; }
|
||||
|
||||
[JsonPropertyName("image")]
|
||||
public ContentStreamPptxImageData? Image { get; init; }
|
||||
public ContentStreamPptxImageData? Image { get; set; }
|
||||
}
|
||||
@@ -7,5 +7,5 @@ namespace AIStudio.Tools;
|
||||
public sealed class ContentStreamPresentationMetadata : ContentStreamSseMetadata
|
||||
{
|
||||
[JsonPropertyName("Presentation")]
|
||||
public ContentStreamPresentationDetails? Presentation { get; init; }
|
||||
public ContentStreamPresentationDetails? Presentation { get; set; }
|
||||
}
|
||||
@@ -17,12 +17,12 @@ namespace AIStudio.Tools;
|
||||
public sealed class ContentStreamPromptInjectionDetails
|
||||
{
|
||||
[JsonPropertyName("findings")]
|
||||
public List<PromptInjectionFinding>? Findings { get; init; }
|
||||
public List<PromptInjectionFinding>? Findings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How many passages were filtered. Can exceed the number of findings, because the runtime
|
||||
/// caps how many it reports in detail while it filters every single one.
|
||||
/// </summary>
|
||||
[JsonPropertyName("redacted_count")]
|
||||
public int RedactedCount { get; init; }
|
||||
public int RedactedCount { get; set; }
|
||||
}
|
||||
@@ -7,5 +7,5 @@ namespace AIStudio.Tools;
|
||||
public sealed class ContentStreamPromptInjectionMetadata : ContentStreamSseMetadata
|
||||
{
|
||||
[JsonPropertyName("PromptInjection")]
|
||||
public ContentStreamPromptInjectionDetails? PromptInjection { get; init; }
|
||||
public ContentStreamPromptInjectionDetails? PromptInjection { get; set; }
|
||||
}
|
||||
@@ -7,8 +7,8 @@ namespace AIStudio.Tools;
|
||||
public sealed class ContentStreamSpreadsheetDetails
|
||||
{
|
||||
[JsonPropertyName("sheet_name")]
|
||||
public string? SheetName { get; init; }
|
||||
public string? SheetName { get; set; }
|
||||
|
||||
[JsonPropertyName("row_number")]
|
||||
public int? RowNumber { get; init; }
|
||||
public int? RowNumber { get; set; }
|
||||
}
|
||||
@@ -7,5 +7,5 @@ namespace AIStudio.Tools;
|
||||
public sealed class ContentStreamSpreadsheetMetadata : ContentStreamSseMetadata
|
||||
{
|
||||
[JsonPropertyName("Spreadsheet")]
|
||||
public ContentStreamSpreadsheetDetails? Spreadsheet { get; init; }
|
||||
public ContentStreamSpreadsheetDetails? Spreadsheet { get; set; }
|
||||
}
|
||||
@@ -5,14 +5,14 @@ namespace AIStudio.Tools;
|
||||
public sealed class ContentStreamSseEvent
|
||||
{
|
||||
[JsonPropertyName("content")]
|
||||
public string? Content { get; init; }
|
||||
public string? Content { get; set; }
|
||||
|
||||
[JsonPropertyName("stream_id")]
|
||||
public string? StreamId { get; init; }
|
||||
public string? StreamId { get; set; }
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public ContentStreamSseMetadata? Metadata { get; init; }
|
||||
public ContentStreamSseMetadata? Metadata { get; set; }
|
||||
|
||||
[JsonPropertyName("token_count")]
|
||||
public int? TokenCount { get; init; }
|
||||
public int? TokenCount { get; set; }
|
||||
}
|
||||
@@ -6,5 +6,5 @@ namespace AIStudio.Tools;
|
||||
public sealed class ContentStreamTextDetails
|
||||
{
|
||||
[JsonPropertyName("line_number")]
|
||||
public int? LineNumber { get; init; }
|
||||
public int? LineNumber { get; set; }
|
||||
}
|
||||
@@ -6,5 +6,5 @@ namespace AIStudio.Tools;
|
||||
public sealed class ContentStreamTextMetadata : ContentStreamSseMetadata
|
||||
{
|
||||
[JsonPropertyName("Text")]
|
||||
public ContentStreamTextDetails? Text { get; init; }
|
||||
public ContentStreamTextDetails? Text { get; set; }
|
||||
}
|
||||
+18
-14
@@ -215,31 +215,35 @@ public sealed class QdrantEdgeClientImplementation(
|
||||
return client;
|
||||
}
|
||||
|
||||
// ReSharper disable NotAccessedPositionalProperty.Local
|
||||
private sealed record EnsureVectorStoreRequest(string StoreName, string DataSourceName, int VectorSize);
|
||||
//
|
||||
// The requests are internal rather than private, so that the JSON context of the runtime can name them. It
|
||||
// generates the contracts they get written with, and a context sees no private type of another class.
|
||||
//
|
||||
// ReSharper disable NotAccessedPositionalProperty.Global
|
||||
internal sealed record EnsureVectorStoreRequest(string StoreName, string DataSourceName, int VectorSize);
|
||||
|
||||
private sealed record CheckVectorStoreRequest(string StoreName);
|
||||
internal sealed record CheckVectorStoreRequest(string StoreName);
|
||||
|
||||
private sealed record InsertEmbeddingRequest(string StoreName, IReadOnlyList<VectorStoragePoint> Points);
|
||||
internal sealed record InsertEmbeddingRequest(string StoreName, IReadOnlyList<VectorStoragePoint> Points);
|
||||
|
||||
/// <param name="PointIds">The only points the search may return, or null for the whole store.</param>
|
||||
private sealed record SearchEmbeddingRequest(string StoreName, IReadOnlyList<float> Vector, int MaxMatches, IReadOnlyList<string>? PointIds, IReadOnlyList<HiddenChunks> HiddenChunks);
|
||||
internal sealed record SearchEmbeddingRequest(string StoreName, IReadOnlyList<float> Vector, int MaxMatches, IReadOnlyList<string>? PointIds, IReadOnlyList<HiddenChunks> HiddenChunks);
|
||||
|
||||
private sealed record RetrieveVectorsRequest(string StoreName, IReadOnlyList<string> PointIds);
|
||||
internal sealed record RetrieveVectorsRequest(string StoreName, IReadOnlyList<string> PointIds);
|
||||
|
||||
private sealed record DeleteEmbeddingByFileRequest(string StoreName, string FilePath, string ParentFileId, IReadOnlyCollection<string> KeepPointIds);
|
||||
internal sealed record DeleteEmbeddingByFileRequest(string StoreName, string FilePath, string ParentFileId, IReadOnlyCollection<string> KeepPointIds);
|
||||
|
||||
private sealed record RelocateEmbeddingFileRequest(string StoreName, string ParentFileId, string AbsolutePath, string FileName, string RelativePath, string Fingerprint);
|
||||
internal sealed record RelocateEmbeddingFileRequest(string StoreName, string ParentFileId, string AbsolutePath, string FileName, string RelativePath, string Fingerprint);
|
||||
|
||||
private sealed record OptimizeVectorStoreRequest(string StoreName);
|
||||
|
||||
private sealed record DeleteVectorStoreRequest(string StoreName);
|
||||
internal sealed record OptimizeVectorStoreRequest(string StoreName);
|
||||
|
||||
internal sealed record DeleteVectorStoreRequest(string StoreName);
|
||||
|
||||
/// <summary>
|
||||
/// Preparing a backup needs nothing but the request itself.
|
||||
/// </summary>
|
||||
private sealed record PrepareBackupRequest;
|
||||
internal sealed record PrepareBackupRequest;
|
||||
|
||||
private sealed record CopyStoreForBackupRequest(string BackupId, string StoreName);
|
||||
// ReSharper restore NotAccessedPositionalProperty.Local
|
||||
internal sealed record CopyStoreForBackupRequest(string BackupId, string StoreName);
|
||||
// ReSharper restore NotAccessedPositionalProperty.Global
|
||||
}
|
||||
@@ -27,7 +27,7 @@ public sealed record VectorSearchResult(
|
||||
/// <remarks>
|
||||
/// The vector store keeps only the pages of a chunk, cf. LocalIndexSearchService.SearchVectorsAsync.
|
||||
/// </remarks>
|
||||
public ChunkLocation? StoredLocation { get; init; }
|
||||
public ChunkLocation? StoredLocation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Where the chunk is: what the index store keeps, or the pages alone when it could not say.
|
||||
|
||||
@@ -9,7 +9,7 @@ public abstract class ERIClientBase(IERIDataSource dataSource) : IDisposable
|
||||
{
|
||||
protected readonly IERIDataSource DataSource = dataSource;
|
||||
|
||||
protected static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
||||
internal static readonly JsonSerializerOptions JSON_OPTIONS = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
AllowTrailingCommas = true,
|
||||
@@ -20,8 +20,9 @@ public abstract class ERIClientBase(IERIDataSource dataSource) : IDisposable
|
||||
Converters =
|
||||
{
|
||||
new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseUpper),
|
||||
}
|
||||
};
|
||||
},
|
||||
TypeInfoResolver = ERIClientJsonContext.Default,
|
||||
}.AsReadOnly();
|
||||
|
||||
protected readonly HttpClient HttpClient = ExternalHttpClientTimeout.CreateHttpClient(new Uri($"{dataSource.Hostname}:{dataSource.Port}"), ExternalHttpTrustPolicy.ALLOW_CUSTOM_ROOTS_WHEN_HOST_WHITELISTED);
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using AIStudio.Tools.ERIClient.DataModel;
|
||||
|
||||
namespace AIStudio.Tools.ERIClient;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json reads and writes the calls to ERI servers, built at compile time.
|
||||
/// </summary>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
|
||||
// The request:
|
||||
[JsonSerializable(typeof(RetrievalRequest))]
|
||||
|
||||
// The answers:
|
||||
[JsonSerializable(typeof(List<AuthScheme>))]
|
||||
[JsonSerializable(typeof(AuthResponse))]
|
||||
[JsonSerializable(typeof(DataSourceInfo))]
|
||||
[JsonSerializable(typeof(List<EmbeddingInfo>))]
|
||||
[JsonSerializable(typeof(List<RetrievalInfo>))]
|
||||
[JsonSerializable(typeof(List<Context>))]
|
||||
[JsonSerializable(typeof(SecurityRequirements))]
|
||||
internal sealed partial class ERIClientJsonContext : JsonSerializerContext;
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace AIStudio.Tools.Rust;
|
||||
|
||||
/// <summary>Request body used to count the tokens of a text in the Rust runtime.</summary>
|
||||
/// <param name="Text">The text to count.</param>
|
||||
/// <param name="TokenizerPath">Path of the tokenizer file to count with.</param>
|
||||
public sealed record CountTokensRequest(string Text, string TokenizerPath);
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace AIStudio.Tools.Rust;
|
||||
|
||||
/// <summary>Request body used to delete the stored tokenizer of a model in the Rust runtime.</summary>
|
||||
/// <param name="ModelId">The model whose tokenizer gets deleted.</param>
|
||||
public sealed record DeleteTokenizerRequest(string ModelId);
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace AIStudio.Tools.Rust;
|
||||
|
||||
/// <summary>Request body used to prepare a local image for a model in the Rust runtime.</summary>
|
||||
/// <param name="Path">Absolute path of the image.</param>
|
||||
/// <param name="MaxBytes">The size the prepared image may have at most.</param>
|
||||
public sealed record PrepareImageForModelRequest(string Path, long MaxBytes);
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace AIStudio.Tools.Rust;
|
||||
|
||||
/// <summary>Request body used to check and optionally optimize a local image in the Rust runtime.</summary>
|
||||
/// <param name="Path">Absolute path of the image.</param>
|
||||
/// <param name="Optimize">Whether the maximum-edge policy and re-encoding are applied.</param>
|
||||
public sealed record PrepareImageRequest(string Path, bool Optimize);
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace AIStudio.Tools.Rust;
|
||||
|
||||
/// <summary>Request body used to store a copy of a tokenizer file for a model in the Rust runtime.</summary>
|
||||
/// <param name="ModelId">The model the tokenizer belongs to.</param>
|
||||
/// <param name="FilePath">Absolute path of the tokenizer file.</param>
|
||||
public sealed record StoreTokenizerRequest(string ModelId, string FilePath);
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace AIStudio.Tools.Rust;
|
||||
|
||||
/// <summary>Request body used to check a tokenizer file in the Rust runtime.</summary>
|
||||
/// <param name="FilePath">Absolute path of the tokenizer file.</param>
|
||||
public sealed record ValidateTokenizerRequest(string FilePath);
|
||||
@@ -15,17 +15,17 @@ public sealed record PromptInjectionFinding
|
||||
/// Which rule matched, e.g. "instruction_override".
|
||||
/// </summary>
|
||||
[JsonPropertyName("rule_id")]
|
||||
public string RuleId { get; init; } = string.Empty;
|
||||
public string RuleId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The rule's family, e.g. "exfiltration".
|
||||
/// </summary>
|
||||
[JsonPropertyName("category")]
|
||||
public PromptInjectionFindingCategory Category { get; init; } = PromptInjectionFindingCategory.UNKNOWN;
|
||||
public PromptInjectionFindingCategory Category { get; set; } = PromptInjectionFindingCategory.UNKNOWN;
|
||||
|
||||
/// <summary>
|
||||
/// The passage as it appeared in the content, so the user can see what was removed.
|
||||
/// </summary>
|
||||
[JsonPropertyName("snippet")]
|
||||
public string Snippet { get; init; } = string.Empty;
|
||||
public string Snippet { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AIStudio.Tools.Services;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json reads the events of a file extraction from the runtime, built at
|
||||
/// compile time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A converter reads the metadata of an event as the type its JSON tells, so each of these types needs a
|
||||
/// contract of its own here. A test fails for one which is missing.
|
||||
/// </remarks>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
[JsonSerializable(typeof(ContentStreamSseEvent))]
|
||||
[JsonSerializable(typeof(string))]
|
||||
[JsonSerializable(typeof(ContentStreamTextMetadata))]
|
||||
[JsonSerializable(typeof(ContentStreamPdfMetadata))]
|
||||
[JsonSerializable(typeof(ContentStreamSpreadsheetMetadata))]
|
||||
[JsonSerializable(typeof(ContentStreamPresentationMetadata))]
|
||||
[JsonSerializable(typeof(ContentStreamImageMetadata))]
|
||||
[JsonSerializable(typeof(ContentStreamDocumentMetadata))]
|
||||
[JsonSerializable(typeof(ContentStreamErrorMetadata))]
|
||||
[JsonSerializable(typeof(ContentStreamPromptInjectionMetadata))]
|
||||
internal sealed partial class RustContentStreamJsonContext : JsonSerializerContext;
|
||||
@@ -0,0 +1,89 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using AIStudio.Tools.Databases.VectorStore;
|
||||
using AIStudio.Tools.Rust;
|
||||
|
||||
namespace AIStudio.Tools.Services;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json reads and writes nearly every call to the runtime, built at
|
||||
/// compile time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The calls to the vector store are generic: every query of its client needs the answer type with the type
|
||||
/// of its result, e.g., DatabaseQueryResponse<bool?>. A test fails for a request or an answer which is
|
||||
/// missing here.
|
||||
/// </remarks>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
|
||||
// The requests:
|
||||
[JsonSerializable(typeof(SelectSecretRequest))]
|
||||
[JsonSerializable(typeof(StoreSecretRequest))]
|
||||
[JsonSerializable(typeof(RustService.ShareFileRequest))]
|
||||
[JsonSerializable(typeof(PreviousDirectory))]
|
||||
[JsonSerializable(typeof(SelectFileOptions))]
|
||||
[JsonSerializable(typeof(SaveFileOptions))]
|
||||
[JsonSerializable(typeof(OpenPathRequest))]
|
||||
[JsonSerializable(typeof(OpenDocumentRequest))]
|
||||
[JsonSerializable(typeof(RegisterShortcutRequest))]
|
||||
[JsonSerializable(typeof(ValidateShortcutRequest))]
|
||||
[JsonSerializable(typeof(SetInterfaceSizeRequest))]
|
||||
[JsonSerializable(typeof(CreateMediaJobRequest))]
|
||||
[JsonSerializable(typeof(LogEventRequest))]
|
||||
[JsonSerializable(typeof(ValidateTokenizerRequest))]
|
||||
[JsonSerializable(typeof(StoreTokenizerRequest))]
|
||||
[JsonSerializable(typeof(DeleteTokenizerRequest))]
|
||||
[JsonSerializable(typeof(CountTokensRequest))]
|
||||
[JsonSerializable(typeof(PrepareImageRequest))]
|
||||
[JsonSerializable(typeof(PrepareImageForModelRequest))]
|
||||
|
||||
// The answers:
|
||||
[JsonSerializable(typeof(RequestedSecret))]
|
||||
[JsonSerializable(typeof(StoreSecretResponse))]
|
||||
[JsonSerializable(typeof(DeleteSecretResponse))]
|
||||
[JsonSerializable(typeof(ShareFileResponse))]
|
||||
[JsonSerializable(typeof(ImagePrepareResponse))]
|
||||
[JsonSerializable(typeof(PreparedModelImage))]
|
||||
[JsonSerializable(typeof(TauriEvent))]
|
||||
[JsonSerializable(typeof(UpdateResponse))]
|
||||
[JsonSerializable(typeof(SetClipboardResponse))]
|
||||
[JsonSerializable(typeof(DirectorySelectionResponse))]
|
||||
[JsonSerializable(typeof(FileSelectionResponse))]
|
||||
[JsonSerializable(typeof(FilesSelectionResponse))]
|
||||
[JsonSerializable(typeof(FileSaveResponse))]
|
||||
[JsonSerializable(typeof(OpenPathResponse))]
|
||||
[JsonSerializable(typeof(OpenDocumentResponse))]
|
||||
[JsonSerializable(typeof(ShortcutRegistrationResult))]
|
||||
[JsonSerializable(typeof(ShortcutValidationResponse))]
|
||||
[JsonSerializable(typeof(ShortcutResponse))]
|
||||
[JsonSerializable(typeof(RuntimeInfoResponse))]
|
||||
[JsonSerializable(typeof(AppExitResponse))]
|
||||
[JsonSerializable(typeof(AppRestartResponse))]
|
||||
[JsonSerializable(typeof(InterfaceSizeResponse))]
|
||||
[JsonSerializable(typeof(CreateMediaJobResponse))]
|
||||
[JsonSerializable(typeof(MediaJobEvent))]
|
||||
[JsonSerializable(typeof(List<EnterpriseConfig>))]
|
||||
[JsonSerializable(typeof(GetLogPathsResponse))]
|
||||
[JsonSerializable(typeof(TokenizerResponse))]
|
||||
|
||||
// The vector store, with the result of every query its client sends:
|
||||
[JsonSerializable(typeof(QdrantEdgeInfo))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.EnsureVectorStoreRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.CheckVectorStoreRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.InsertEmbeddingRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.SearchEmbeddingRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.RetrieveVectorsRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.DeleteEmbeddingByFileRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.RelocateEmbeddingFileRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.OptimizeVectorStoreRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.DeleteVectorStoreRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.PrepareBackupRequest))]
|
||||
[JsonSerializable(typeof(QdrantEdgeClientImplementation.CopyStoreForBackupRequest))]
|
||||
[JsonSerializable(typeof(RustService.DatabaseOperationResponse))]
|
||||
[JsonSerializable(typeof(RustService.DatabaseQueryResponse<VectorStoreEnsureResult>))]
|
||||
[JsonSerializable(typeof(RustService.DatabaseQueryResponse<bool?>))]
|
||||
[JsonSerializable(typeof(RustService.DatabaseQueryResponse<List<VectorSearchResult>>))]
|
||||
[JsonSerializable(typeof(RustService.DatabaseQueryResponse<List<StoredVector>>))]
|
||||
[JsonSerializable(typeof(RustService.DatabaseQueryResponse<VectorStoreBackupPreparation>))]
|
||||
[JsonSerializable(typeof(RustService.DatabaseQueryResponse<long?>))]
|
||||
internal sealed partial class RustJsonContext : JsonSerializerContext;
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using AIStudio.Tools.Rust;
|
||||
|
||||
namespace AIStudio.Tools.Services;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json reads and writes the calls to the prompt injection filter of the
|
||||
/// runtime, built at compile time.
|
||||
/// </summary>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
[JsonSerializable(typeof(SanitizePromptInjectionsRequest))]
|
||||
[JsonSerializable(typeof(SanitizePromptInjectionsResponse))]
|
||||
[JsonSerializable(typeof(SanitizePromptInjectionsBatchRequest))]
|
||||
[JsonSerializable(typeof(SanitizePromptInjectionsBatchResponse))]
|
||||
internal sealed partial class RustPromptInjectionJsonContext : JsonSerializerContext;
|
||||
@@ -63,7 +63,7 @@ public sealed partial class RustService
|
||||
private async Task<RequestedSecret> GetAPIKeyByKey(string secretKey, bool isTrying)
|
||||
{
|
||||
var secretRequest = new SelectSecretRequest(secretKey, Environment.UserName, isTrying);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, this.jsonRustSerializerOptions);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, JSON_OPTIONS);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
if(!isTrying)
|
||||
@@ -71,7 +71,7 @@ public sealed partial class RustService
|
||||
return new RequestedSecret(false, new EncryptedText(string.Empty), TB("Failed to get the API key due to an API issue."));
|
||||
}
|
||||
|
||||
var secret = await result.Content.ReadFromJsonAsync<RequestedSecret>(this.jsonRustSerializerOptions);
|
||||
var secret = await result.Content.ReadFromJsonAsync<RequestedSecret>(JSON_OPTIONS);
|
||||
if (secret.Success)
|
||||
this.logger!.LogDebug($"Successfully retrieved the API key for '{secretKey}'.");
|
||||
else if (isTrying)
|
||||
@@ -106,14 +106,14 @@ public sealed partial class RustService
|
||||
private async Task<StoreSecretResponse> StoreEncryptedAPIKeyByKey(string secretKey, EncryptedText encryptedKey)
|
||||
{
|
||||
var request = new StoreSecretRequest(secretKey, Environment.UserName, encryptedKey);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/store", request, this.jsonRustSerializerOptions);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/store", request, JSON_OPTIONS);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
this.logger!.LogError($"Failed to store the API key for '{secretKey}' due to an API issue: '{result.StatusCode}'");
|
||||
return new StoreSecretResponse(false, TB("Failed to store the API key due to an API issue."));
|
||||
}
|
||||
|
||||
var state = await result.Content.ReadFromJsonAsync<StoreSecretResponse>(this.jsonRustSerializerOptions);
|
||||
var state = await result.Content.ReadFromJsonAsync<StoreSecretResponse>(JSON_OPTIONS);
|
||||
if (!state.Success)
|
||||
this.logger!.LogError($"Failed to store the API key for '{secretKey}': '{state.Issue}'");
|
||||
else
|
||||
@@ -149,14 +149,14 @@ public sealed partial class RustService
|
||||
private async Task<DeleteSecretResponse> DeleteAPIKeyByKey(string secretKey, bool isTrying = false)
|
||||
{
|
||||
var request = new SelectSecretRequest(secretKey, Environment.UserName, false);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/delete", request, this.jsonRustSerializerOptions);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/delete", request, JSON_OPTIONS);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
this.logger!.LogError($"Failed to delete the API key for '{secretKey}' due to an API issue: '{result.StatusCode}'");
|
||||
return new DeleteSecretResponse{Success = false, WasEntryFound = false, Issue = TB("Failed to delete the API key due to an API issue.")};
|
||||
}
|
||||
|
||||
var state = await result.Content.ReadFromJsonAsync<DeleteSecretResponse>(this.jsonRustSerializerOptions);
|
||||
var state = await result.Content.ReadFromJsonAsync<DeleteSecretResponse>(JSON_OPTIONS);
|
||||
if (!state.Success && !isTrying)
|
||||
this.logger!.LogError($"Failed to delete the API key for '{secretKey}': '{state.Issue}'");
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ public sealed partial class RustService
|
||||
|
||||
public async Task<RuntimeInfoResponse> GetRuntimeInfo()
|
||||
{
|
||||
var response = await this.http.GetFromJsonAsync<RuntimeInfoResponse>("/system/runtime/info", this.jsonRustSerializerOptions);
|
||||
var response = await this.http.GetFromJsonAsync<RuntimeInfoResponse>("/system/runtime/info", JSON_OPTIONS);
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ public sealed partial class RustService
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<AppExitResponse>(this.jsonRustSerializerOptions);
|
||||
var result = await response.Content.ReadFromJsonAsync<AppExitResponse>(JSON_OPTIONS);
|
||||
if (result is null || !result.Success)
|
||||
{
|
||||
this.logger?.LogError("Failed to exit the app: {Error}", result?.ErrorMessage ?? "Unknown error");
|
||||
@@ -177,7 +177,7 @@ public sealed partial class RustService
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<AppRestartResponse>(this.jsonRustSerializerOptions);
|
||||
var result = await response.Content.ReadFromJsonAsync<AppRestartResponse>(JSON_OPTIONS);
|
||||
if (result is null || !result.Success)
|
||||
{
|
||||
this.logger?.LogError("Failed to restart the app: {Error}", result?.ErrorMessage ?? "Unknown error");
|
||||
@@ -203,7 +203,7 @@ public sealed partial class RustService
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await this.http.PostAsJsonAsync("/app/interface-size", request, this.jsonRustSerializerOptions);
|
||||
var response = await this.http.PostAsJsonAsync("/app/interface-size", request, JSON_OPTIONS);
|
||||
var result = await this.ReadInterfaceSizeResponse(response, "set the interface size");
|
||||
if (result is not null)
|
||||
this.logger?.LogInformation("Set the interface sizes of {ScreenCount} screens. AI Studio shows with the size '{Size}' at {CurrentPercent} % and a zoom of {Zoom:0.###} on the screen '{ScreenKey}'.", request.Screens.Count, result.Size, result.CurrentPercent, result.Zoom, result.ScreenKey);
|
||||
@@ -243,7 +243,7 @@ public sealed partial class RustService
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<InterfaceSizeResponse>(this.jsonRustSerializerOptions);
|
||||
var result = await response.Content.ReadFromJsonAsync<InterfaceSizeResponse>(JSON_OPTIONS);
|
||||
if (result is null || !result.Success)
|
||||
{
|
||||
this.logger?.LogError("Failed to {Action}: {Error}", action, result?.ErrorMessage ?? "Unknown error");
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed partial class RustService
|
||||
return;
|
||||
}
|
||||
|
||||
var state = await response.Content.ReadFromJsonAsync<SetClipboardResponse>(this.jsonRustSerializerOptions);
|
||||
var state = await response.Content.ReadFromJsonAsync<SetClipboardResponse>(JSON_OPTIONS);
|
||||
if (!state.Success)
|
||||
{
|
||||
this.logger!.LogError("Failed to copy the text to the clipboard.");
|
||||
|
||||
@@ -24,7 +24,7 @@ public sealed partial class RustService
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(45));
|
||||
|
||||
var databaseInfo = await this.http.GetFromJsonAsync<TDatabaseInfo>(infoPath, this.jsonRustSerializerOptions, cts.Token);
|
||||
var databaseInfo = await this.http.GetFromJsonAsync<TDatabaseInfo>(infoPath, JSON_OPTIONS, cts.Token);
|
||||
return databaseInfo ?? unavailableFactory("The database information response was empty.");
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
@@ -52,10 +52,10 @@ public sealed partial class RustService
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
cts.CancelAfter(TimeSpan.FromMinutes(5));
|
||||
|
||||
using var response = await this.http.PostAsJsonAsync(path, request, this.jsonRustSerializerOptions, cts.Token);
|
||||
using var response = await this.http.PostAsJsonAsync(path, request, JSON_OPTIONS, cts.Token);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var operation = await response.Content.ReadFromJsonAsync<DatabaseOperationResponse>(this.jsonRustSerializerOptions, cts.Token);
|
||||
var operation = await response.Content.ReadFromJsonAsync<DatabaseOperationResponse>(JSON_OPTIONS, cts.Token);
|
||||
if (operation is not { Success: true })
|
||||
throw CreateDatabaseException(operation?.Issue, operation?.IssueCode, $"The {databaseName} operation failed.");
|
||||
}
|
||||
@@ -65,10 +65,10 @@ public sealed partial class RustService
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
cts.CancelAfter(TimeSpan.FromMinutes(5));
|
||||
|
||||
using var response = await this.http.PostAsJsonAsync(path, request, this.jsonRustSerializerOptions, cts.Token);
|
||||
using var response = await this.http.PostAsJsonAsync(path, request, JSON_OPTIONS, cts.Token);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var operation = await response.Content.ReadFromJsonAsync<DatabaseQueryResponse<TResult>>(this.jsonRustSerializerOptions, cts.Token);
|
||||
var operation = await response.Content.ReadFromJsonAsync<DatabaseQueryResponse<TResult>>(JSON_OPTIONS, cts.Token);
|
||||
if (operation is not { Success: true })
|
||||
throw CreateDatabaseException(operation?.Issue, operation?.IssueCode, $"The {databaseName} query failed.");
|
||||
|
||||
@@ -94,7 +94,10 @@ public sealed partial class RustService
|
||||
};
|
||||
}
|
||||
|
||||
private sealed record DatabaseOperationResponse(bool Success, string Issue, string IssueCode);
|
||||
//
|
||||
// Internal rather than private, so that the JSON context of the runtime can name them:
|
||||
//
|
||||
internal sealed record DatabaseOperationResponse(bool Success, string Issue, string IssueCode);
|
||||
|
||||
private sealed record DatabaseQueryResponse<TResult>(bool Success, string Issue, string IssueCode, TResult? Data);
|
||||
internal sealed record DatabaseQueryResponse<TResult>(bool Success, string Issue, string IssueCode, TResult? Data);
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public sealed partial class RustService
|
||||
throw new HttpRequestException($"Failed to query the enterprise configurations: '{result.StatusCode}'");
|
||||
}
|
||||
|
||||
var configs = await result.Content.ReadFromJsonAsync<List<EnterpriseConfig>>(this.jsonRustSerializerOptions);
|
||||
var configs = await result.Content.ReadFromJsonAsync<List<EnterpriseConfig>>(JSON_OPTIONS);
|
||||
if (configs is null)
|
||||
throw new InvalidOperationException("Failed to parse the enterprise configurations from Rust.");
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public partial class RustService
|
||||
continue;
|
||||
|
||||
// Deserialize the Tauri event:
|
||||
var tauriEvent = JsonSerializer.Deserialize<TauriEvent>(line, this.jsonRustSerializerOptions);
|
||||
var tauriEvent = JsonSerializer.Deserialize<TauriEvent>(line, JSON_OPTIONS);
|
||||
|
||||
// Forward relevant events to the message bus:
|
||||
if (tauriEvent != default && tauriEvent.EventType
|
||||
|
||||
@@ -16,10 +16,10 @@ public sealed partial class RustService
|
||||
var startDirectory = initialDirectory is null ? null : PathTools.FindNearestExistingPath(initialDirectory);
|
||||
var result = startDirectory is null
|
||||
? await this.http.PostAsync($"/select/directory?title={encodedTitle}", null)
|
||||
: await this.http.PostAsJsonAsync($"/select/directory?title={encodedTitle}", new PreviousDirectory(startDirectory), this.jsonRustSerializerOptions);
|
||||
: await this.http.PostAsJsonAsync($"/select/directory?title={encodedTitle}", new PreviousDirectory(startDirectory), JSON_OPTIONS);
|
||||
|
||||
if (result.IsSuccessStatusCode)
|
||||
return await result.Content.ReadFromJsonAsync<DirectorySelectionResponse>(this.jsonRustSerializerOptions);
|
||||
return await result.Content.ReadFromJsonAsync<DirectorySelectionResponse>(JSON_OPTIONS);
|
||||
|
||||
this.logger!.LogError("Failed to select a directory: '{StatusCode}'", result.StatusCode);
|
||||
return new DirectorySelectionResponse(true, string.Empty);
|
||||
@@ -63,9 +63,9 @@ public sealed partial class RustService
|
||||
Filter = FileTypes.ToFileDialogFilter(filter)
|
||||
};
|
||||
|
||||
var result = await this.http.PostAsJsonAsync("/select/file", payload, this.jsonRustSerializerOptions);
|
||||
var result = await this.http.PostAsJsonAsync("/select/file", payload, JSON_OPTIONS);
|
||||
if (result.IsSuccessStatusCode)
|
||||
return await result.Content.ReadFromJsonAsync<FileSelectionResponse>(this.jsonRustSerializerOptions);
|
||||
return await result.Content.ReadFromJsonAsync<FileSelectionResponse>(JSON_OPTIONS);
|
||||
|
||||
this.logger!.LogError("Failed to select a file: '{StatusCode}'", result.StatusCode);
|
||||
return new FileSelectionResponse(true, string.Empty);
|
||||
@@ -87,9 +87,9 @@ public sealed partial class RustService
|
||||
Filter = FileTypes.ToFileDialogFilter(filter)
|
||||
};
|
||||
|
||||
var result = await this.http.PostAsJsonAsync("/select/files", payload, this.jsonRustSerializerOptions);
|
||||
var result = await this.http.PostAsJsonAsync("/select/files", payload, JSON_OPTIONS);
|
||||
if (result.IsSuccessStatusCode)
|
||||
return await result.Content.ReadFromJsonAsync<FilesSelectionResponse>(this.jsonRustSerializerOptions);
|
||||
return await result.Content.ReadFromJsonAsync<FilesSelectionResponse>(JSON_OPTIONS);
|
||||
|
||||
this.logger!.LogError("Failed to select files: '{StatusCode}'", result.StatusCode);
|
||||
return new FilesSelectionResponse(true, Array.Empty<string>());
|
||||
@@ -118,9 +118,9 @@ public sealed partial class RustService
|
||||
Filter = FileTypes.ToFileDialogFilter(filter)
|
||||
};
|
||||
|
||||
var result = await this.http.PostAsJsonAsync("/save/file", payload, this.jsonRustSerializerOptions);
|
||||
var result = await this.http.PostAsJsonAsync("/save/file", payload, JSON_OPTIONS);
|
||||
if (result.IsSuccessStatusCode)
|
||||
return await result.Content.ReadFromJsonAsync<FileSaveResponse>(this.jsonRustSerializerOptions);
|
||||
return await result.Content.ReadFromJsonAsync<FileSaveResponse>(JSON_OPTIONS);
|
||||
|
||||
this.logger!.LogError("Failed to select a file for writing operation: '{StatusCode}'", result.StatusCode);
|
||||
return new FileSaveResponse(true, string.Empty);
|
||||
@@ -133,7 +133,7 @@ public sealed partial class RustService
|
||||
HttpResponseMessage result;
|
||||
try
|
||||
{
|
||||
result = await this.http.PostAsJsonAsync("/open/path", new OpenPathRequest(path), this.jsonRustSerializerOptions);
|
||||
result = await this.http.PostAsJsonAsync("/open/path", new OpenPathRequest(path), JSON_OPTIONS);
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
@@ -154,7 +154,7 @@ public sealed partial class RustService
|
||||
return new OpenPathResponse(false, string.Format(TB("The runtime file manager endpoint returned '{0}'."), result.StatusCode));
|
||||
}
|
||||
|
||||
var response = await result.Content.ReadFromJsonAsync<OpenPathResponse>(this.jsonRustSerializerOptions);
|
||||
var response = await result.Content.ReadFromJsonAsync<OpenPathResponse>(JSON_OPTIONS);
|
||||
var normalizedResponse = response.Success
|
||||
? response
|
||||
: new OpenPathResponse(false, string.IsNullOrWhiteSpace(response.Issue) ? TB("The runtime file manager endpoint failed without details.") : response.Issue);
|
||||
@@ -187,7 +187,7 @@ public sealed partial class RustService
|
||||
HttpResponseMessage result;
|
||||
try
|
||||
{
|
||||
result = await this.http.PostAsJsonAsync("/open/document", new OpenDocumentRequest(path, pageNumber), this.jsonRustSerializerOptions);
|
||||
result = await this.http.PostAsJsonAsync("/open/document", new OpenDocumentRequest(path, pageNumber), JSON_OPTIONS);
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
@@ -208,7 +208,7 @@ public sealed partial class RustService
|
||||
return new OpenDocumentResponse(false, false, string.Format(TB("The runtime document endpoint returned '{0}'."), result.StatusCode));
|
||||
}
|
||||
|
||||
var response = await result.Content.ReadFromJsonAsync<OpenDocumentResponse>(this.jsonRustSerializerOptions);
|
||||
var response = await result.Content.ReadFromJsonAsync<OpenDocumentResponse>(JSON_OPTIONS);
|
||||
if (response.Success)
|
||||
{
|
||||
//
|
||||
|
||||
@@ -20,10 +20,10 @@ public sealed partial class RustService
|
||||
bool optimize,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var response = await this.http.PostAsJsonAsync("/image/prepare", new { path, optimize }, this.jsonRustSerializerOptions, token);
|
||||
using var response = await this.http.PostAsJsonAsync("/image/prepare", new PrepareImageRequest(path, optimize), JSON_OPTIONS, token);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<ImagePrepareResponse>(this.jsonRustSerializerOptions, token)
|
||||
return await response.Content.ReadFromJsonAsync<ImagePrepareResponse>(JSON_OPTIONS, token)
|
||||
?? throw new InvalidDataException("The Rust image preparation returned an empty response.");
|
||||
}
|
||||
|
||||
@@ -44,10 +44,10 @@ public sealed partial class RustService
|
||||
{
|
||||
try
|
||||
{
|
||||
using var response = await this.http.PostAsJsonAsync("/image/prepare-for-model", new { path, maxBytes }, this.jsonRustSerializerOptions, token);
|
||||
using var response = await this.http.PostAsJsonAsync("/image/prepare-for-model", new PrepareImageForModelRequest(path, maxBytes), JSON_OPTIONS, token);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<PreparedModelImage>(this.jsonRustSerializerOptions, token)
|
||||
return await response.Content.ReadFromJsonAsync<PreparedModelImage>(JSON_OPTIONS, token)
|
||||
?? PreparedModelImage.Failed(ModelImageErrorCode.UNKNOWN);
|
||||
}
|
||||
catch (Exception e) when (e is not OperationCanceledException)
|
||||
|
||||
@@ -10,7 +10,7 @@ public sealed partial class RustService
|
||||
/// <returns>The paths of the log files.</returns>
|
||||
public async Task<GetLogPathsResponse> GetLogPaths()
|
||||
{
|
||||
return await this.http.GetFromJsonAsync<GetLogPathsResponse>("/log/paths", this.jsonRustSerializerOptions);
|
||||
return await this.http.GetFromJsonAsync<GetLogPathsResponse>("/log/paths", JSON_OPTIONS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -33,7 +33,7 @@ public sealed partial class RustService
|
||||
// feed itself. The unobserved task exception handler in Program.cs remains the safety net here.
|
||||
//
|
||||
var request = new LogEventRequest(timestamp, level, category, message, exception, stackTrace);
|
||||
_ = this.http.PostAsJsonAsync("/log/event", request, this.jsonRustSerializerOptions);
|
||||
_ = this.http.PostAsJsonAsync("/log/event", request, JSON_OPTIONS);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -18,11 +18,11 @@ public partial class RustService
|
||||
using var response = await this.http.PostAsJsonAsync(
|
||||
"/media/jobs",
|
||||
new CreateMediaJobRequest(inputPath, outputPath, OpusBitrateBps: opusBitrateBps),
|
||||
this.jsonRustSerializerOptions,
|
||||
JSON_OPTIONS,
|
||||
token);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
var result = await response.Content.ReadFromJsonAsync<CreateMediaJobResponse>(this.jsonRustSerializerOptions, token);
|
||||
var result = await response.Content.ReadFromJsonAsync<CreateMediaJobResponse>(JSON_OPTIONS, token);
|
||||
return result?.JobId ?? throw new InvalidOperationException("The Rust runtime did not return a media job ID.");
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ public partial class RustService
|
||||
continue;
|
||||
|
||||
var json = line["data:".Length..].Trim();
|
||||
var mediaEvent = JsonSerializer.Deserialize<MediaJobEvent>(json, this.jsonRustSerializerOptions);
|
||||
var mediaEvent = JsonSerializer.Deserialize<MediaJobEvent>(json, JSON_OPTIONS);
|
||||
if (mediaEvent is not null)
|
||||
yield return mediaEvent;
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ public sealed partial class RustService
|
||||
|
||||
try
|
||||
{
|
||||
var sseEvent = JsonSerializer.Deserialize<ContentStreamSseEvent>(jsonContent);
|
||||
var sseEvent = JsonSerializer.Deserialize<ContentStreamSseEvent>(jsonContent, CONTENT_STREAM_JSON_OPTIONS);
|
||||
if (sseEvent is null)
|
||||
continue;
|
||||
|
||||
@@ -364,7 +364,7 @@ public sealed partial class RustService
|
||||
ContentStreamSseEvent? sseEvent = null;
|
||||
try
|
||||
{
|
||||
sseEvent = JsonSerializer.Deserialize<ContentStreamSseEvent>(jsonContent);
|
||||
sseEvent = JsonSerializer.Deserialize<ContentStreamSseEvent>(jsonContent, CONTENT_STREAM_JSON_OPTIONS);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
@@ -460,7 +460,7 @@ public sealed partial class RustService
|
||||
{
|
||||
try
|
||||
{
|
||||
var errorMessage = JsonSerializer.Deserialize<string>(jsonContent);
|
||||
var errorMessage = JsonSerializer.Deserialize<string>(jsonContent, CONTENT_STREAM_JSON_OPTIONS);
|
||||
if (string.IsNullOrWhiteSpace(errorMessage))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -65,14 +65,14 @@ public sealed partial class RustService
|
||||
var secretKey = SecretKey(secretId, storeType);
|
||||
var encryptedSecret = await this.encryptor!.Encrypt(secretData);
|
||||
var request = new StoreSecretRequest(secretKey, Environment.UserName, encryptedSecret);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/store", request, this.jsonRustSerializerOptions);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/store", request, JSON_OPTIONS);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
this.logger!.LogError($"Failed to store the secret data for '{secretKey}' due to an API issue: '{result.StatusCode}'");
|
||||
return new StoreSecretResponse(false, TB("Failed to store the secret data due to an API issue."));
|
||||
}
|
||||
|
||||
var state = await result.Content.ReadFromJsonAsync<StoreSecretResponse>(this.jsonRustSerializerOptions);
|
||||
var state = await result.Content.ReadFromJsonAsync<StoreSecretResponse>(JSON_OPTIONS);
|
||||
if (!state.Success)
|
||||
this.logger!.LogError($"Failed to store the secret data for '{secretKey}': '{state.Issue}'");
|
||||
|
||||
@@ -104,7 +104,7 @@ public sealed partial class RustService
|
||||
private async Task<RequestedSecret> GetSecretByKey(string secretKey, bool isTrying)
|
||||
{
|
||||
var secretRequest = new SelectSecretRequest(secretKey, Environment.UserName, isTrying);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, this.jsonRustSerializerOptions);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, JSON_OPTIONS);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
if(isTrying)
|
||||
@@ -114,7 +114,7 @@ public sealed partial class RustService
|
||||
return new RequestedSecret(false, new EncryptedText(string.Empty), TB("Failed to get the secret data due to an API issue."));
|
||||
}
|
||||
|
||||
var state = await result.Content.ReadFromJsonAsync<RequestedSecret>(this.jsonRustSerializerOptions);
|
||||
var state = await result.Content.ReadFromJsonAsync<RequestedSecret>(JSON_OPTIONS);
|
||||
if (!state.Success)
|
||||
{
|
||||
//
|
||||
@@ -138,14 +138,14 @@ public sealed partial class RustService
|
||||
private async Task<DeleteSecretResponse> DeleteSecretByKey(string secretKey)
|
||||
{
|
||||
var request = new SelectSecretRequest(secretKey, Environment.UserName, false);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/delete", request, this.jsonRustSerializerOptions);
|
||||
var result = await this.http.PostAsJsonAsync("/secrets/delete", request, JSON_OPTIONS);
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
this.logger!.LogError($"Failed to delete the secret data for '{secretKey}' due to an API issue: '{result.StatusCode}'");
|
||||
return new DeleteSecretResponse{Success = false, WasEntryFound = false, Issue = TB("Failed to delete the secret data due to an API issue.")};
|
||||
}
|
||||
|
||||
var state = await result.Content.ReadFromJsonAsync<DeleteSecretResponse>(this.jsonRustSerializerOptions);
|
||||
var state = await result.Content.ReadFromJsonAsync<DeleteSecretResponse>(JSON_OPTIONS);
|
||||
if (!state.Success)
|
||||
this.logger!.LogError($"Failed to delete the secret data for '{secretKey}': '{state.Issue}'");
|
||||
|
||||
|
||||
@@ -40,7 +40,8 @@ public sealed partial class RustService
|
||||
using var response = await this.http.PostAsJsonAsync(
|
||||
"/security/prompt-injection/sanitize",
|
||||
new SanitizePromptInjectionsRequest(text),
|
||||
cancellationToken: timeoutTokenSource.Token);
|
||||
PROMPT_INJECTION_JSON_OPTIONS,
|
||||
timeoutTokenSource.Token);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -48,7 +49,7 @@ public sealed partial class RustService
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<SanitizePromptInjectionsResponse>(timeoutTokenSource.Token);
|
||||
return await response.Content.ReadFromJsonAsync<SanitizePromptInjectionsResponse>(PROMPT_INJECTION_JSON_OPTIONS, timeoutTokenSource.Token);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -82,7 +83,8 @@ public sealed partial class RustService
|
||||
using var response = await this.http.PostAsJsonAsync(
|
||||
"/security/prompt-injection/sanitize-batch",
|
||||
new SanitizePromptInjectionsBatchRequest(texts),
|
||||
cancellationToken: timeoutTokenSource.Token);
|
||||
PROMPT_INJECTION_JSON_OPTIONS,
|
||||
timeoutTokenSource.Token);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -90,7 +92,7 @@ public sealed partial class RustService
|
||||
return null;
|
||||
}
|
||||
|
||||
var batchResponse = await response.Content.ReadFromJsonAsync<SanitizePromptInjectionsBatchResponse>(timeoutTokenSource.Token);
|
||||
var batchResponse = await response.Content.ReadFromJsonAsync<SanitizePromptInjectionsBatchResponse>(PROMPT_INJECTION_JSON_OPTIONS, timeoutTokenSource.Token);
|
||||
if (batchResponse.Results is null || batchResponse.Results.Count != texts.Count)
|
||||
{
|
||||
this.logger?.LogError("The prompt injection filter answered with {ResultCount} result(s) for {TextCount} text(s).", batchResponse.Results?.Count ?? 0, texts.Count);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// ReSharper disable NotAccessedPositionalProperty.Local
|
||||
// ReSharper disable NotAccessedPositionalProperty.Global
|
||||
using AIStudio.Tools.Rust;
|
||||
|
||||
namespace AIStudio.Tools.Services;
|
||||
@@ -14,14 +14,14 @@ public sealed partial class RustService
|
||||
{
|
||||
try
|
||||
{
|
||||
using var response = await this.http.PostAsJsonAsync("/share/file", new ShareFileRequest(filePath), this.jsonRustSerializerOptions);
|
||||
using var response = await this.http.PostAsJsonAsync("/share/file", new ShareFileRequest(filePath), JSON_OPTIONS);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
this.logger?.LogError($"The Rust runtime rejected the share request: {response.StatusCode}.");
|
||||
return new(ShareSheetOutcome.FAILED, $"The Rust runtime rejected the share request: {response.StatusCode}.");
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ShareFileResponse>(this.jsonRustSerializerOptions);
|
||||
var result = await response.Content.ReadFromJsonAsync<ShareFileResponse>(JSON_OPTIONS);
|
||||
if (result is null)
|
||||
{
|
||||
this.logger?.LogError("The Rust runtime answered the share request without a result.");
|
||||
@@ -50,5 +50,8 @@ public sealed partial class RustService
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record ShareFileRequest(string FilePath);
|
||||
/// <remarks>
|
||||
/// Internal rather than private, so that the JSON context of the runtime can name it.
|
||||
/// </remarks>
|
||||
internal sealed record ShareFileRequest(string FilePath);
|
||||
}
|
||||
@@ -18,7 +18,7 @@ public sealed partial class RustService
|
||||
try
|
||||
{
|
||||
var request = new RegisterShortcutRequest(shortcutId, shortcut, description, reconfigure);
|
||||
var response = await this.http.PostAsJsonAsync("/shortcuts/register", request, this.jsonRustSerializerOptions);
|
||||
var response = await this.http.PostAsJsonAsync("/shortcuts/register", request, JSON_OPTIONS);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -26,7 +26,7 @@ public sealed partial class RustService
|
||||
return ShortcutRegistrationResult.Failed(TB("The global shortcut could not be registered because the desktop service is unavailable."));
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ShortcutRegistrationResult>(this.jsonRustSerializerOptions);
|
||||
var result = await response.Content.ReadFromJsonAsync<ShortcutRegistrationResult>(JSON_OPTIONS);
|
||||
if (result is null || !result.Success)
|
||||
{
|
||||
this.logger?.LogError("Failed to register global shortcut '{ShortcutId}': {Error}", shortcutId, result?.ErrorMessage ?? "Unknown error");
|
||||
@@ -53,7 +53,7 @@ public sealed partial class RustService
|
||||
try
|
||||
{
|
||||
var request = new ValidateShortcutRequest(shortcut);
|
||||
var response = await this.http.PostAsJsonAsync("/shortcuts/validate", request, this.jsonRustSerializerOptions);
|
||||
var response = await this.http.PostAsJsonAsync("/shortcuts/validate", request, JSON_OPTIONS);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -61,7 +61,7 @@ public sealed partial class RustService
|
||||
return new ShortcutValidationResult(false, "Network error during validation", false, string.Empty);
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ShortcutValidationResponse>(this.jsonRustSerializerOptions);
|
||||
var result = await response.Content.ReadFromJsonAsync<ShortcutValidationResponse>(JSON_OPTIONS);
|
||||
if (result is null)
|
||||
return new ShortcutValidationResult(false, "Invalid response from server", false, string.Empty);
|
||||
|
||||
@@ -91,7 +91,7 @@ public sealed partial class RustService
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ShortcutResponse>(this.jsonRustSerializerOptions);
|
||||
var result = await response.Content.ReadFromJsonAsync<ShortcutResponse>(JSON_OPTIONS);
|
||||
if (result is null || !result.Success)
|
||||
{
|
||||
this.logger?.LogError("Failed to suspend shortcut processing: {Error}", result?.ErrorMessage ?? "Unknown error");
|
||||
@@ -123,7 +123,7 @@ public sealed partial class RustService
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<ShortcutResponse>(this.jsonRustSerializerOptions);
|
||||
var result = await response.Content.ReadFromJsonAsync<ShortcutResponse>(JSON_OPTIONS);
|
||||
if (result is null || !result.Success)
|
||||
{
|
||||
this.logger?.LogError("Failed to resume shortcut processing: {Error}", result?.ErrorMessage ?? "Unknown error");
|
||||
|
||||
@@ -15,9 +15,7 @@ public sealed partial class RustService : ITokenizerStorage
|
||||
|
||||
public async Task<TokenizerResponse> ValidateTokenizer(string filePath)
|
||||
{
|
||||
var result = await this.http.PostAsJsonAsync("/tokenizer/validate", new {
|
||||
file_path = filePath,
|
||||
}, this.jsonRustSerializerOptions);
|
||||
var result = await this.http.PostAsJsonAsync("/tokenizer/validate", new ValidateTokenizerRequest(filePath), JSON_OPTIONS);
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -25,7 +23,7 @@ public sealed partial class RustService : ITokenizerStorage
|
||||
return CreateUnavailableTokenizerResponse("An error occured while sending the path to the Rust framework for validation: "+result.StatusCode);
|
||||
}
|
||||
|
||||
var response = await result.Content.ReadFromJsonAsync<TokenizerResponse>(this.jsonRustSerializerOptions);
|
||||
var response = await result.Content.ReadFromJsonAsync<TokenizerResponse>(JSON_OPTIONS);
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -33,10 +31,7 @@ public sealed partial class RustService : ITokenizerStorage
|
||||
public async Task<TokenizerResponse> StoreTokenizer(string modelId, string filePath)
|
||||
{
|
||||
this.logger!.LogInformation($"Storing tokenizer for model '{modelId}' from file '{filePath}'");
|
||||
var result = await this.http.PostAsJsonAsync("/tokenizer/store", new {
|
||||
model_id = modelId,
|
||||
file_path = filePath,
|
||||
}, this.jsonRustSerializerOptions);
|
||||
var result = await this.http.PostAsJsonAsync("/tokenizer/store", new StoreTokenizerRequest(modelId, filePath), JSON_OPTIONS);
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -44,15 +39,13 @@ public sealed partial class RustService : ITokenizerStorage
|
||||
return CreateUnavailableTokenizerResponse("An error occured while sending the path to the Rust framework for storing: "+result.StatusCode);
|
||||
}
|
||||
|
||||
return await result.Content.ReadFromJsonAsync<TokenizerResponse>(this.jsonRustSerializerOptions);
|
||||
return await result.Content.ReadFromJsonAsync<TokenizerResponse>(JSON_OPTIONS);
|
||||
}
|
||||
|
||||
public async Task<TokenizerResponse> DeleteTokenizer(string modelId)
|
||||
{
|
||||
this.logger!.LogInformation($"Deleting tokenizer for model '{modelId}'");
|
||||
var result = await this.http.PostAsJsonAsync("/tokenizer/delete", new {
|
||||
model_id = modelId,
|
||||
}, this.jsonRustSerializerOptions);
|
||||
var result = await this.http.PostAsJsonAsync("/tokenizer/delete", new DeleteTokenizerRequest(modelId), JSON_OPTIONS);
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -60,7 +53,7 @@ public sealed partial class RustService : ITokenizerStorage
|
||||
return CreateUnavailableTokenizerResponse("An error occured while sending the tokenizer delete request to the Rust framework: "+result.StatusCode);
|
||||
}
|
||||
|
||||
return await result.Content.ReadFromJsonAsync<TokenizerResponse>(this.jsonRustSerializerOptions);
|
||||
return await result.Content.ReadFromJsonAsync<TokenizerResponse>(JSON_OPTIONS);
|
||||
}
|
||||
|
||||
public Task<TokenizerResponse?> GetTokenCount(AIStudio.Settings.Provider provider, string text, CancellationToken cancellationToken = default) =>
|
||||
@@ -71,10 +64,7 @@ public sealed partial class RustService : ITokenizerStorage
|
||||
|
||||
private async Task<TokenizerResponse?> GetTokenCount(string providerName, string tokenizerPath, string text, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await this.http.PostAsJsonAsync("/tokenizer/count", new {
|
||||
text,
|
||||
tokenizer_path = tokenizerPath,
|
||||
}, this.jsonRustSerializerOptions, cancellationToken);
|
||||
var result = await this.http.PostAsJsonAsync("/tokenizer/count", new CountTokensRequest(text, tokenizerPath), JSON_OPTIONS, cancellationToken);
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -82,6 +72,6 @@ public sealed partial class RustService : ITokenizerStorage
|
||||
return CreateUnavailableTokenizerResponse("Error while getting token count from Rust service: "+result.StatusCode);
|
||||
}
|
||||
|
||||
return await result.Content.ReadFromJsonAsync<TokenizerResponse>(this.jsonRustSerializerOptions, cancellationToken);
|
||||
return await result.Content.ReadFromJsonAsync<TokenizerResponse>(JSON_OPTIONS, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ public sealed partial class RustService
|
||||
try
|
||||
{
|
||||
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(45));
|
||||
var response = await this.http.GetFromJsonAsync<UpdateResponse>("/updates/check", this.jsonRustSerializerOptions, cts.Token);
|
||||
var response = await this.http.GetFromJsonAsync<UpdateResponse>("/updates/check", JSON_OPTIONS, cts.Token);
|
||||
|
||||
if (response == default)
|
||||
{
|
||||
|
||||
@@ -34,15 +34,41 @@ public sealed partial class RustService : BackgroundService
|
||||
private readonly SemaphoreSlim userLanguageLock = new(1, 1);
|
||||
private readonly SemaphoreSlim userNameLock = new(1, 1);
|
||||
|
||||
private readonly JsonSerializerOptions jsonRustSerializerOptions = new()
|
||||
/// <summary>
|
||||
/// The JSON options of nearly every call to the runtime, which names its fields in snake case.
|
||||
/// </summary>
|
||||
internal static readonly JsonSerializerOptions JSON_OPTIONS = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
Converters =
|
||||
{
|
||||
new RustEnumConverter(),
|
||||
},
|
||||
};
|
||||
|
||||
TypeInfoResolver = RustJsonContext.Default,
|
||||
}.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// The JSON options of the events of a file extraction, which name their fields themselves.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The same as the defaults of System.Text.Json, with which these events were read before.
|
||||
/// </remarks>
|
||||
internal static readonly JsonSerializerOptions CONTENT_STREAM_JSON_OPTIONS = new JsonSerializerOptions
|
||||
{
|
||||
TypeInfoResolver = RustContentStreamJsonContext.Default,
|
||||
}.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// The JSON options of the prompt injection filter, which names its fields in camel case.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The same as the defaults of the JSON calls of HttpClient, with which the filter was called before.
|
||||
/// </remarks>
|
||||
internal static readonly JsonSerializerOptions PROMPT_INJECTION_JSON_OPTIONS = new JsonSerializerOptions(JsonSerializerDefaults.Web)
|
||||
{
|
||||
TypeInfoResolver = RustPromptInjectionJsonContext.Default,
|
||||
}.AsReadOnly();
|
||||
|
||||
private ILogger<RustService>? logger;
|
||||
private Encryption? encryptor;
|
||||
private string? cachedUserLanguage;
|
||||
|
||||
@@ -5,4 +5,15 @@ namespace AIStudio.Tools;
|
||||
/// </summary>
|
||||
/// <param name="Title">The title of the source.</param>
|
||||
/// <param name="URL">The URL of the source.</param>
|
||||
public record Source(string Title, string URL, SourceOrigin Origin) : ISource;
|
||||
public record Source(string Title, string URL, SourceOrigin Origin) : ISource
|
||||
{
|
||||
//
|
||||
// The properties of the parameters, declared again to be settable. A derived record which passes a value of
|
||||
// its own to one of them, as the search results of Perplexity do with the origin, would otherwise lose it:
|
||||
// the source generation of System.Text.Json sets every init-only property while it creates the object, also
|
||||
// one missing in the JSON. Keep them in the order of the parameters, which is the order they get stored in:
|
||||
//
|
||||
public string Title { get; set; } = Title;
|
||||
public string URL { get; set; } = URL;
|
||||
public SourceOrigin Origin { get; set; } = Origin;
|
||||
}
|
||||
+1
-1
@@ -8,5 +8,5 @@ internal sealed record StaanQueryInfo
|
||||
/// <summary>
|
||||
/// The query Staan searched for after correcting it, or empty when it searched what it was given.
|
||||
/// </summary>
|
||||
public string AlteredQuery { get; init; } = string.Empty;
|
||||
public string AlteredQuery { get; set; } = string.Empty;
|
||||
}
|
||||
+3
-3
@@ -16,15 +16,15 @@ internal sealed record StaanSearchRequest
|
||||
/// What to search for. Staan rejects a query longer than 400 characters.
|
||||
/// </summary>
|
||||
[JsonPropertyName("q")]
|
||||
public required string Query { get; init; }
|
||||
public required string Query { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The market to search in. Staan offers de-de, en-us, and fr-fr, and defaults to fr-fr.
|
||||
/// </summary>
|
||||
public string? Market { get; init; }
|
||||
public string? Market { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Where in the result list to start, in steps of ten up to 30, or null for the first page.
|
||||
/// </summary>
|
||||
public int? Offset { get; init; }
|
||||
public int? Offset { get; set; }
|
||||
}
|
||||
+2
-2
@@ -10,7 +10,7 @@ namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch.
|
||||
/// </remarks>
|
||||
internal sealed record StaanSearchResponse
|
||||
{
|
||||
public StaanQueryInfo? Query { get; init; }
|
||||
public StaanQueryInfo? Query { get; set; }
|
||||
|
||||
public StaanWebSection? Web { get; init; }
|
||||
public StaanWebSection? Web { get; set; }
|
||||
}
|
||||
+3
-3
@@ -10,9 +10,9 @@ namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch.
|
||||
/// </remarks>
|
||||
internal sealed record StaanSearchResult
|
||||
{
|
||||
public string Title { get; init; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
public string Url { get; init; } = string.Empty;
|
||||
public string Url { get; set; } = string.Empty;
|
||||
|
||||
public string Snippet { get; init; } = string.Empty;
|
||||
public string Snippet { get; set; } = string.Empty;
|
||||
}
|
||||
+1
-1
@@ -5,5 +5,5 @@ namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch.
|
||||
/// </summary>
|
||||
internal sealed record StaanWebSection
|
||||
{
|
||||
public IReadOnlyList<StaanSearchResult> Results { get; init; } = [];
|
||||
public IReadOnlyList<StaanSearchResult> Results { get; set; } = [];
|
||||
}
|
||||
+7
-7
@@ -10,35 +10,35 @@ namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch.
|
||||
/// </remarks>
|
||||
internal sealed record TavilySearchRequest
|
||||
{
|
||||
public required string Query { get; init; }
|
||||
public required string Query { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How thoroughly to search. A basic search costs one credit, an advanced one costs two.
|
||||
/// </summary>
|
||||
public required string SearchDepth { get; init; }
|
||||
public required string SearchDepth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The most hits to return, at most 20.
|
||||
/// </summary>
|
||||
public int? MaxResults { get; init; }
|
||||
public int? MaxResults { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How far back to look: day, week, month, or year, or null for no restriction.
|
||||
/// </summary>
|
||||
public string? TimeRange { get; init; }
|
||||
public string? TimeRange { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The language to search in, as an ISO 639-1 code, or null for no restriction.
|
||||
/// </summary>
|
||||
public string? Language { get; init; }
|
||||
public string? Language { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the language is a requirement rather than a preference. Needs the language field.
|
||||
/// </summary>
|
||||
public bool? FilterByLanguage { get; init; }
|
||||
public bool? FilterByLanguage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to filter explicit results or null to leave the decision to Tavily.
|
||||
/// </summary>
|
||||
public bool? SafeSearch { get; init; }
|
||||
public bool? SafeSearch { get; set; }
|
||||
}
|
||||
+1
-1
@@ -10,5 +10,5 @@ namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch.
|
||||
/// </remarks>
|
||||
internal sealed record TavilySearchResponse
|
||||
{
|
||||
public IReadOnlyList<TavilySearchResult> Results { get; init; } = [];
|
||||
public IReadOnlyList<TavilySearchResult> Results { get; set; } = [];
|
||||
}
|
||||
+3
-3
@@ -11,12 +11,12 @@ namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch.
|
||||
/// </remarks>
|
||||
internal sealed record TavilySearchResult
|
||||
{
|
||||
public string Title { get; init; } = string.Empty;
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
public string Url { get; init; } = string.Empty;
|
||||
public string Url { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The excerpt of the page that matched, which is what other services call a snippet.
|
||||
/// </summary>
|
||||
public string Content { get; init; } = string.Empty;
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
+3
-2
@@ -14,10 +14,11 @@ namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch;
|
||||
/// </remarks>
|
||||
internal static class WebSearchJson
|
||||
{
|
||||
public static readonly JsonSerializerOptions OPTIONS = new()
|
||||
public static readonly JsonSerializerOptions OPTIONS = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
};
|
||||
TypeInfoResolver = WebSearchJsonContext.Default,
|
||||
}.AsReadOnly();
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch.Staan;
|
||||
using AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch.Tavily;
|
||||
|
||||
namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch;
|
||||
|
||||
/// <summary>
|
||||
/// The contracts with which System.Text.Json reads and writes the calls to the search services, built at
|
||||
/// compile time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// SearXNG is missing on purpose: its answers differ between instances, so its client reads them as a tree
|
||||
/// of JSON nodes instead.
|
||||
/// </remarks>
|
||||
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||
[JsonSerializable(typeof(StaanSearchRequest))]
|
||||
[JsonSerializable(typeof(StaanSearchResponse))]
|
||||
[JsonSerializable(typeof(TavilySearchRequest))]
|
||||
[JsonSerializable(typeof(TavilySearchResponse))]
|
||||
internal sealed partial class WebSearchJsonContext : JsonSerializerContext;
|
||||
@@ -80,7 +80,8 @@ public sealed class ToolParameterSchemaBuilder
|
||||
["additionalProperties"] = false,
|
||||
};
|
||||
|
||||
return JsonSerializer.Deserialize<JsonElement>(schema.ToJsonString());
|
||||
using var document = JsonDocument.Parse(schema.ToJsonString());
|
||||
return document.RootElement.Clone();
|
||||
}
|
||||
|
||||
private ToolParameterSchemaBuilder Add(string name, string jsonType, string description, bool isRequired, IReadOnlyList<string>? allowedValues = null)
|
||||
|
||||
Reference in new issue
Block a user