diff --git a/.fernignore b/.fernignore index 58ffc1e..d62dbe2 100644 --- a/.fernignore +++ b/.fernignore @@ -8,41 +8,48 @@ icon.png .github/workflows/ci.yml .github/workflows/publish-reference.yml src/AssemblyAI.sln -src/AssemblyAI/AssemblyAI.csproj -src/AssemblyAI/Core/JsonConfiguration.cs -src/AssemblyAI/Core/Public/ApiException.cs -src/AssemblyAI/Core/Public/AssemblyAIException.cs -src/AssemblyAI/Core/StringEnumSerializer.cs -src/AssemblyAI/Core/CustomConstants.cs -src/AssemblyAI/Types/Error.cs +src/AssemblyAI/AssemblyAI.Custom.props +src/AssemblyAI/AssemblyAIClient.cs src/AssemblyAI/UserAgent.cs src/AssemblyAI/Event.cs -src/AssemblyAI/AssemblyAIClient.cs src/AssemblyAI/DependencyInjectionExtensions.cs src/AssemblyAI/EnumConverter.cs +src/AssemblyAI/Types/Error.cs +src/AssemblyAI/Core/EnumSerializer.cs +src/AssemblyAI/Core/JsonConfiguration.cs +src/AssemblyAI/Core/Public/ExtendedRequestOptions.cs +src/AssemblyAI/Core/Public/AssemblyAIClientEnvironment.cs +src/AssemblyAI/Core/Public/ClientOptions.cs +src/AssemblyAI/Core/Public/ExtendedClientOptions.cs +src/AssemblyAI/Core/Public/ApiException.cs +src/AssemblyAI/Core/Public/AssemblyAIException.cs src/AssemblyAI/Files/ExtendedFilesClient.cs src/AssemblyAI/Transcripts/ExtendedTranscriptsClient.cs +src/AssemblyAI/Transcripts/TranscriptNotCompletedStatusException.cs src/AssemblyAI/Transcripts/Types/TranscriptExtensions.cs src/AssemblyAI/Transcripts/Types/TranscriptParamsMapper.cs src/AssemblyAI/Transcripts/Types/TranscriptParamsCloner.cs -src/AssemblyAI/Transcripts/TranscriptNotCompletedStatusException.cs +src/AssemblyAI/Lemur/ExtendedLemurClient.cs src/AssemblyAI/Lemur/Types/LemurResponse.cs +src/AssemblyAI/Lemur/Types/LemurModel.cs src/AssemblyAI/Realtime/RealtimeTranscriber.cs src/AssemblyAI/Realtime/WebsocketClient -src/AssemblyAI/Realtime/Types/RealtimeTranscript.cs src/AssemblyAI/Realtime/ExtendedRealtimeClient.cs src/AssemblyAI/Realtime/RealtimeTranscriberOptions.cs +src/AssemblyAI/Realtime/Types/RealtimeTranscript.cs src/AssemblyAI/Realtime/Types/TerminateSession.cs src/AssemblyAI/Realtime/Types/ForceEndUtterance.cs src/AssemblyAI/Realtime/Types/Realtime.cs -src/AssemblyAI.Test +src/AssemblyAI.Test/TestClient.cs +src/AssemblyAI.Test/Core/RawClientTests.cs src/AssemblyAI.UnitTests + src/AssemblyAI.IntegrationTests docfx +Samples # TODO: remove these ignores when AssemblyAI fixes the API +src/AssemblyAI/Transcripts/Types/ContentSafetyLabel.cs src/AssemblyAI/Transcripts/Types/ContentSafetyLabelsResult.cs -src/AssemblyAI/Transcripts/Types/TopicDetectionModelResult.cs - -Samples +src/AssemblyAI/Transcripts/Types/TopicDetectionModelResult.cs \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86fbd20..282cbc6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,13 +28,13 @@ jobs: strategy: fail-fast: false matrix: - framework: [net462, net6.0] + framework: [net462, net8.0] os: [ubuntu-latest, windows-latest] exclude: - os: ubuntu-latest framework: net462 name: Run Tests on ${{ matrix.os }} with ${{ matrix.framework }} - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} steps: - name: Checkout repo uses: actions/checkout@v4 @@ -44,9 +44,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: | - 8.x - 6.x + dotnet-version: 8.x - name: Install tools run: | diff --git a/reference.md b/reference.md new file mode 100644 index 0000000..f847917 --- /dev/null +++ b/reference.md @@ -0,0 +1,1009 @@ +# Reference +## Files +## Transcripts +
client.Transcripts.ListAsync(ListTranscriptParams { ... }) -> TranscriptList +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Retrieve a list of transcripts you created. +Transcripts are sorted from newest to oldest. The previous URL always points to a page with older transcripts. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Transcripts.ListAsync(new ListTranscriptParams()); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `ListTranscriptParams` + +
+
+
+
+ + +
+
+
+ +
client.Transcripts.SubmitAsync(TranscriptParams { ... }) -> Transcript +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Create a transcript from a media file that is accessible via a URL. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Transcripts.SubmitAsync( + new TranscriptParams + { + LanguageCode = TranscriptLanguageCode.EnUs, + LanguageDetection = true, + LanguageConfidenceThreshold = 0.7f, + Punctuate = true, + FormatText = true, + Disfluencies = false, + Multichannel = true, + DualChannel = false, + WebhookUrl = "https://your-webhook-url/path", + WebhookAuthHeaderName = "webhook-secret", + WebhookAuthHeaderValue = "webhook-secret-value", + AutoHighlights = true, + AudioStartFrom = 10, + AudioEndAt = 280, + WordBoost = new List() { "aws", "azure", "google cloud" }, + BoostParam = TranscriptBoostParam.High, + FilterProfanity = true, + RedactPii = true, + RedactPiiAudio = true, + RedactPiiAudioQuality = RedactPiiAudioQuality.Mp3, + RedactPiiPolicies = new List() + { + PiiPolicy.UsSocialSecurityNumber, + PiiPolicy.CreditCardNumber, + }, + RedactPiiSub = SubstitutionPolicy.Hash, + SpeakerLabels = true, + SpeakersExpected = 2, + ContentSafety = true, + IabCategories = true, + CustomSpelling = new List() + { + new TranscriptCustomSpelling + { + From = new List() { "dicarlo" }, + To = "Decarlo", + }, + }, + SentimentAnalysis = true, + AutoChapters = true, + EntityDetection = true, + SpeechThreshold = 0.5f, + Summarization = true, + SummaryModel = SummaryModel.Informative, + SummaryType = SummaryType.Bullets, + CustomTopics = true, + Topics = new List() { "topics" }, + AudioUrl = "https://assembly.ai/wildfires.mp3", + } +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `TranscriptParams` + +
+
+
+
+ + +
+
+
+ +
client.Transcripts.GetAsync(transcriptId) -> Transcript +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get the transcript resource. The transcript is ready when the "status" is "completed". +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Transcripts.GetAsync("transcript_id"); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**transcriptId:** `string` — ID of the transcript + +
+
+
+
+ + +
+
+
+ +
client.Transcripts.DeleteAsync(transcriptId) -> Transcript +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Remove the data from the transcript and mark it as deleted. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Transcripts.DeleteAsync("{transcript_id}"); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**transcriptId:** `string` — ID of the transcript + +
+
+
+
+ + +
+
+
+ +
client.Transcripts.GetSubtitlesAsync(transcriptId, subtitleFormat, GetSubtitlesParams { ... }) -> string +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Export your transcript in SRT or VTT format to use with a video player for subtitles and closed captions. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Transcripts.GetSubtitlesAsync( + "string", + SubtitleFormat.Srt, + new GetSubtitlesParams { CharsPerCaption = 1 } +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**transcriptId:** `string` — ID of the transcript + +
+
+ +
+
+ +**subtitleFormat:** `SubtitleFormat` — The format of the captions + +
+
+ +
+
+ +**request:** `GetSubtitlesParams` + +
+
+
+
+ + +
+
+
+ +
client.Transcripts.GetSentencesAsync(transcriptId) -> SentencesResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get the transcript split by sentences. The API will attempt to semantically segment the transcript into sentences to create more reader-friendly transcripts. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Transcripts.GetSentencesAsync("transcript_id"); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**transcriptId:** `string` — ID of the transcript + +
+
+
+
+ + +
+
+
+ +
client.Transcripts.GetParagraphsAsync(transcriptId) -> ParagraphsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Get the transcript split by paragraphs. The API will attempt to semantically segment your transcript into paragraphs to create more reader-friendly transcripts. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Transcripts.GetParagraphsAsync("transcript_id"); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**transcriptId:** `string` — ID of the transcript + +
+
+
+
+ + +
+
+
+ +
client.Transcripts.WordSearchAsync(transcriptId, WordSearchParams { ... }) -> WordSearchResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Search through the transcript for keywords. You can search for individual words, numbers, or phrases containing up to five words or numbers. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Transcripts.WordSearchAsync("string", new WordSearchParams { Words = ["string"] }); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**transcriptId:** `string` — ID of the transcript + +
+
+ +
+
+ +**request:** `WordSearchParams` + +
+
+
+
+ + +
+
+
+ +
client.Transcripts.GetRedactedAudioAsync(transcriptId) -> RedactedAudioResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Retrieve the redacted audio object containing the status and URL to the redacted audio. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Transcripts.GetRedactedAudioAsync("transcript_id"); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**transcriptId:** `string` — ID of the transcript + +
+
+
+
+ + +
+
+
+ +## Realtime +
client.Realtime.CreateTemporaryTokenAsync(CreateRealtimeTemporaryTokenParams { ... }) -> RealtimeTemporaryTokenResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Create a temporary authentication token for Streaming Speech-to-Text +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Realtime.CreateTemporaryTokenAsync( + new CreateRealtimeTemporaryTokenParams { ExpiresIn = 480 } +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `CreateRealtimeTemporaryTokenParams` + +
+
+
+
+ + +
+
+
+ +## LeMUR +
client.Lemur.TaskAsync(LemurTaskParams { ... }) -> LemurTaskResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Use the LeMUR task endpoint to input your own LLM prompt. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Lemur.TaskAsync( + new LemurTaskParams + { + TranscriptIds = new List() { "64nygnr62k-405c-4ae8-8a6b-d90b40ff3cce" }, + Context = "This is an interview about wildfires.", + FinalModel = LemurModel.AnthropicClaude35Sonnet, + MaxOutputSize = 3000, + Temperature = 0f, + Prompt = "List all the locations affected by wildfires.", + } +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `LemurTaskParams` + +
+
+
+
+ + +
+
+
+ +
client.Lemur.SummaryAsync(LemurSummaryParams { ... }) -> LemurSummaryResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Custom Summary allows you to distill a piece of audio into a few impactful sentences. +You can give the model context to obtain more targeted results while outputting the results in a variety of formats described in human language. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Lemur.SummaryAsync( + new LemurSummaryParams + { + TranscriptIds = new List() { "47b95ba5-8889-44d8-bc80-5de38306e582" }, + Context = "This is an interview about wildfires.", + FinalModel = LemurModel.AnthropicClaude35Sonnet, + MaxOutputSize = 3000, + Temperature = 0f, + } +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `LemurSummaryParams` + +
+
+
+
+ + +
+
+
+ +
client.Lemur.QuestionAnswerAsync(LemurQuestionAnswerParams { ... }) -> LemurQuestionAnswerResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Question & Answer allows you to ask free-form questions about a single transcript or a group of transcripts. +The questions can be any whose answers you find useful, such as judging whether a caller is likely to become a customer or whether all items on a meeting's agenda were covered. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Lemur.QuestionAnswerAsync( + new LemurQuestionAnswerParams + { + TranscriptIds = new List() { "64nygnr62k-405c-4ae8-8a6b-d90b40ff3cce" }, + Context = "This is an interview about wildfires.", + FinalModel = LemurModel.AnthropicClaude35Sonnet, + MaxOutputSize = 3000, + Temperature = 0f, + Questions = new List() + { + new LemurQuestion + { + Question = "Where are there wildfires?", + AnswerFormat = "List of countries in ISO 3166-1 alpha-2 format", + AnswerOptions = new List() { "US", "CA" }, + }, + new LemurQuestion + { + Question = "Is global warming affecting wildfires?", + AnswerOptions = new List() { "yes", "no" }, + }, + }, + } +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `LemurQuestionAnswerParams` + +
+
+
+
+ + +
+
+
+ +
client.Lemur.ActionItemsAsync(LemurActionItemsParams { ... }) -> LemurActionItemsResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Use LeMUR to generate a list of action items from a transcript +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Lemur.ActionItemsAsync( + new LemurActionItemsParams + { + TranscriptIds = new List() { "64nygnr62k-405c-4ae8-8a6b-d90b40ff3cce" }, + Context = "This is an interview about wildfires.", + FinalModel = LemurModel.AnthropicClaude35Sonnet, + MaxOutputSize = 3000, + Temperature = 0f, + AnswerFormat = "Bullet Points", + } +); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `LemurActionItemsParams` + +
+
+
+
+ + +
+
+
+ +
client.Lemur.GetResponseAsync(requestId) -> OneOf +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Retrieve a LeMUR response that was previously generated. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Lemur.GetResponseAsync("request_id"); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**requestId:** `string` + +The ID of the LeMUR request you previously made. +This would be found in the response of the original request. + +
+
+
+
+ + +
+
+
+ +
client.Lemur.PurgeRequestDataAsync(requestId) -> PurgeLemurRequestDataResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Delete the data for a previously submitted LeMUR request. +The LLM response data, as well as any context provided in the original request will be removed. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```csharp +await client.Lemur.PurgeRequestDataAsync("request_id"); +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**requestId:** `string` — The ID of the LeMUR request whose data you want to delete. This would be found in the response of the original request. + +
+
+
+
+ + +
+
+
diff --git a/src/AssemblyAI.IntegrationTests/AssemblyAI.IntegrationTests.csproj b/src/AssemblyAI.IntegrationTests/AssemblyAI.IntegrationTests.csproj index daa67ba..4f09e4c 100644 --- a/src/AssemblyAI.IntegrationTests/AssemblyAI.IntegrationTests.csproj +++ b/src/AssemblyAI.IntegrationTests/AssemblyAI.IntegrationTests.csproj @@ -1,7 +1,7 @@ - net462;net6.0 + net462;net8.0 enable enable 12 diff --git a/src/AssemblyAI.IntegrationTests/LemurTests.cs b/src/AssemblyAI.IntegrationTests/LemurTests.cs index 8977d4a..37502d9 100644 --- a/src/AssemblyAI.IntegrationTests/LemurTests.cs +++ b/src/AssemblyAI.IntegrationTests/LemurTests.cs @@ -13,7 +13,7 @@ public async Task Should_Generate_Summary() var client = Helpers.CreateClient(); var response = await client.Lemur.SummaryAsync(new LemurSummaryParams { - FinalModel = LemurModel.Basic, + FinalModel = LemurModel.AnthropicClaude3_Haiku, TranscriptIds = TranscriptIds, AnswerFormat = "one sentence" }).ConfigureAwait(false); @@ -32,7 +32,7 @@ public async Task Should_Generate_Answer() var client = Helpers.CreateClient(); var response = await client.Lemur.QuestionAnswerAsync(new LemurQuestionAnswerParams { - FinalModel = LemurModel.Basic, + FinalModel = LemurModel.AnthropicClaude3_Haiku, TranscriptIds = TranscriptIds, Questions = new[] { @@ -63,7 +63,7 @@ public async Task Should_Generate_Action_Items() var client = Helpers.CreateClient(); var response = await client.Lemur.ActionItemsAsync(new LemurActionItemsParams { - FinalModel = LemurModel.Basic, + FinalModel = LemurModel.AnthropicClaude2_1, TranscriptIds = TranscriptIds }).ConfigureAwait(false); @@ -81,7 +81,7 @@ public async Task Should_Generate_Task() var client = Helpers.CreateClient(); var response = await client.Lemur.TaskAsync(new LemurTaskParams { - FinalModel = LemurModel.Basic, + FinalModel = LemurModel.AnthropicClaude3_Haiku, TranscriptIds = TranscriptIds, Prompt = "Write a haiku about this conversation." }).ConfigureAwait(false); @@ -101,7 +101,7 @@ public void Should_Fail_To_Generate_Summary() var client = Helpers.CreateClient(); var ex = Assert.ThrowsAsync(async () => await client.Lemur.SummaryAsync(new LemurSummaryParams { - FinalModel = LemurModel.Basic, + FinalModel = LemurModel.AnthropicClaude3_Haiku, TranscriptIds = ["bad-id"], AnswerFormat = "one sentence" }).ConfigureAwait(false)); @@ -115,7 +115,7 @@ public async Task Should_Return_Response() var client = Helpers.CreateClient(); var taskResponse = await client.Lemur.TaskAsync(new LemurTaskParams { - FinalModel = LemurModel.Basic, + FinalModel = LemurModel.AnthropicClaude3_Haiku, TranscriptIds = TranscriptIds, Prompt = "Write a haiku about this conversation." }).ConfigureAwait(false); @@ -132,7 +132,7 @@ public async Task Should_Return_Response() var qaResponse = await client.Lemur.QuestionAnswerAsync(new LemurQuestionAnswerParams { - FinalModel = LemurModel.Basic, + FinalModel = LemurModel.AnthropicClaude3_Haiku, TranscriptIds = TranscriptIds, Questions = [ @@ -145,7 +145,7 @@ public async Task Should_Return_Response() }).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); - + var qaResponse2OneOf = await client.Lemur.GetResponseAsync(qaResponse.RequestId).ConfigureAwait(false); var qaResponse2 = qaResponse2OneOf.AsT1; Assert.Multiple(() => @@ -161,13 +161,13 @@ public async Task Should_Purge_Request_Data() var client = Helpers.CreateClient(); var summaryResponse = await client.Lemur.SummaryAsync(new LemurSummaryParams { - FinalModel = LemurModel.Basic, + FinalModel = LemurModel.AnthropicClaude3_Haiku, TranscriptIds = TranscriptIds, AnswerFormat = "one sentence" }).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); - + var deletionRequest = await client.Lemur.PurgeRequestDataAsync(summaryResponse.RequestId).ConfigureAwait(false); Assert.Multiple(() => { diff --git a/src/AssemblyAI.Test/AssemblyAI.Test.Custom.props b/src/AssemblyAI.Test/AssemblyAI.Test.Custom.props new file mode 100644 index 0000000..55e683b --- /dev/null +++ b/src/AssemblyAI.Test/AssemblyAI.Test.Custom.props @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/src/AssemblyAI.Test/AssemblyAI.Test.csproj b/src/AssemblyAI.Test/AssemblyAI.Test.csproj new file mode 100644 index 0000000..44d0560 --- /dev/null +++ b/src/AssemblyAI.Test/AssemblyAI.Test.csproj @@ -0,0 +1,33 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + \ No newline at end of file diff --git a/src/AssemblyAI.Test/Core/EnumSerializerTests.cs b/src/AssemblyAI.Test/Core/EnumSerializerTests.cs new file mode 100644 index 0000000..32fdcb7 --- /dev/null +++ b/src/AssemblyAI.Test/Core/EnumSerializerTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.Serialization; +using System.Text.Json; +using System.Text.Json.Serialization; +using AssemblyAI.Core; +using NUnit.Framework; + +namespace AssemblyAI.Test.Core +{ + [TestFixture] + public class StringEnumSerializerTests + { + private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true }; + + private const DummyEnum KnownEnumValue2 = DummyEnum.KnownValue2; + private const string KnownEnumValue2String = "known_value2"; + + private static readonly string JsonWithKnownEnum2 = $$""" + { + "enum_property": "{{KnownEnumValue2String}}" + } + """; + + [Test] + public void ShouldParseKnownEnumValue2() + { + var obj = JsonSerializer.Deserialize(JsonWithKnownEnum2, JsonOptions); + Assert.That(obj, Is.Not.Null); + Assert.That(obj.EnumProperty, Is.EqualTo(KnownEnumValue2)); + } + + [Test] + public void ShouldSerializeKnownEnumValue2() + { + var json = JsonSerializer.SerializeToElement( + new DummyObject { EnumProperty = KnownEnumValue2 }, + JsonOptions + ); + TestContext.Out.WriteLine("Serialized JSON: \n" + json); + var enumString = json.GetProperty("enum_property").GetString(); + Assert.That(enumString, Is.Not.Null); + Assert.That(enumString, Is.EqualTo(KnownEnumValue2String)); + } + } + + public class DummyObject + { + [JsonPropertyName("enum_property")] + public DummyEnum EnumProperty { get; set; } + } + + [JsonConverter(typeof(EnumSerializer))] + public enum DummyEnum + { + [EnumMember(Value = "known_value1")] + KnownValue1, + + [EnumMember(Value = "known_value2")] + KnownValue2, + } +} diff --git a/src/AssemblyAI.UnitTests/AssemblyAI.UnitTests.csproj b/src/AssemblyAI.UnitTests/AssemblyAI.UnitTests.csproj index f4966e9..8b39f6e 100644 --- a/src/AssemblyAI.UnitTests/AssemblyAI.UnitTests.csproj +++ b/src/AssemblyAI.UnitTests/AssemblyAI.UnitTests.csproj @@ -1,7 +1,7 @@ - net462;net6.0 + net462;net8.0 enable enable 12 @@ -26,7 +26,10 @@ - + + + FernGenerated\%(RecursiveDir)%(Filename)%(Extension) + @@ -36,4 +39,9 @@ all + + + + + \ No newline at end of file diff --git a/src/AssemblyAI.UnitTests/UserAgentTests.cs b/src/AssemblyAI.UnitTests/UserAgentTests.cs index e7b7ca5..d0ab48b 100644 --- a/src/AssemblyAI.UnitTests/UserAgentTests.cs +++ b/src/AssemblyAI.UnitTests/UserAgentTests.cs @@ -15,8 +15,8 @@ public void TestDefaultUserAgent() Assert.That(userAgentString, Does.StartWith("AssemblyAI/1.0 (")); Assert.That(userAgentString, Does.EndWith(")")); Assert.That(userAgentString, Does.Contain("sdk=CSharp/")); -#if NET6_0 - Assert.That(userAgentString, Does.Contain("runtime_env=.NET/6.")); +#if NET8_0 + Assert.That(userAgentString, Does.Contain("runtime_env=.NET/8.")); #elif NET462 Assert.That(userAgentString, Does.Contain("runtime_env=.NET Framework/4.")); #else diff --git a/src/AssemblyAI.sln b/src/AssemblyAI.sln index 5870e21..b4139df 100644 --- a/src/AssemblyAI.sln +++ b/src/AssemblyAI.sln @@ -7,7 +7,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssemblyAI", "AssemblyAI\As EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssemblyAI.IntegrationTests", "AssemblyAI.IntegrationTests\AssemblyAI.IntegrationTests.csproj", "{311AB518-6FCF-453B-A4B7-12E444C9479E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssemblyAI.UnitTests", "AssemblyAI.UnitTests\AssemblyAI.UnitTests.csproj", "{F432A09F-C463-438F-AF0D-EB7951F0DBB9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssemblyAI.UnitTests", "AssemblyAI.UnitTests\AssemblyAI.UnitTests.csproj", "{547C3456-F649-4E68-A591-0538486F7DE3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -26,9 +26,9 @@ Global {311AB518-6FCF-453B-A4B7-12E444C9479E}.Debug|Any CPU.Build.0 = Debug|Any CPU {311AB518-6FCF-453B-A4B7-12E444C9479E}.Release|Any CPU.ActiveCfg = Release|Any CPU {311AB518-6FCF-453B-A4B7-12E444C9479E}.Release|Any CPU.Build.0 = Release|Any CPU - {F432A09F-C463-438F-AF0D-EB7951F0DBB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F432A09F-C463-438F-AF0D-EB7951F0DBB9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F432A09F-C463-438F-AF0D-EB7951F0DBB9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F432A09F-C463-438F-AF0D-EB7951F0DBB9}.Release|Any CPU.Build.0 = Release|Any CPU + {547C3456-F649-4E68-A591-0538486F7DE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {547C3456-F649-4E68-A591-0538486F7DE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {547C3456-F649-4E68-A591-0538486F7DE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {547C3456-F649-4E68-A591-0538486F7DE3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/AssemblyAI/AssemblyAI.Custom.props b/src/AssemblyAI/AssemblyAI.Custom.props new file mode 100644 index 0000000..9aa6220 --- /dev/null +++ b/src/AssemblyAI/AssemblyAI.Custom.props @@ -0,0 +1,60 @@ + + + + net462;net8.0;net7.0;net6.0;netstandard2.0 + enable + 12 + enable + AssemblyAI + AssemblyAI + AssemblyAI + $(Version) + AssemblyAI C# .NET SDK + AssemblyAI + The AssemblyAI C# .NET SDK provides an easy-to-use interface for interacting with the AssemblyAI API, which supports async and real-time transcription, audio intelligence models, as well as the latest LeMUR models. + Copyright 2024 (c) AssemblyAI, Inc. All rights reserved. + ASR;Speech-To-Text;STT;Speech;AI;AssemblyAI + AssemblyAI + AssemblyAI + Library + MIT + https://github.com/AssemblyAI/assemblyai-csharp-sdk.git + https://www.assemblyai.com/favicon.png + icon.png + README.md + true + true + true + snupkg + git + + + true + + + + + + + + + + + + + + + + + + + + + <_Parameter1>AssemblyAI.UnitTests + + + \ No newline at end of file diff --git a/src/AssemblyAI/AssemblyAI.csproj b/src/AssemblyAI/AssemblyAI.csproj index 2213c92..593c6d6 100644 --- a/src/AssemblyAI/AssemblyAI.csproj +++ b/src/AssemblyAI/AssemblyAI.csproj @@ -1,109 +1,44 @@ + net462;net8.0;net7.0;net6.0;netstandard2.0 enable - false 12 enable - AssemblyAI - AssemblyAI - AssemblyAI - 1.2.0 - 1.2.0.0 - 1.2.0.0 - 1.2.0 - AssemblyAI C# .NET SDK - AssemblyAI - The AssemblyAI C# .NET SDK provides an easy-to-use interface for interacting with the AssemblyAI API, which supports async and real-time transcription, audio intelligence models, as well as the latest LeMUR models. - Copyright 2024 (c) AssemblyAI, Inc. All rights reserved. - ASR;Speech-To-Text;STT;Speech;AI;AssemblyAI - AssemblyAI - AssemblyAI - Library - MIT - https://github.com/AssemblyAI/assemblyai-csharp-sdk - https://github.com/AssemblyAI/assemblyai-csharp-sdk.git - https://www.assemblyai.com/favicon.png - icon.png + 1.2.1 + $(Version) + $(Version) README.md - true - true - true - snupkg - git - - - true - - - - - - - - + https://github.com/AssemblyAI/assemblyai-csharp-sdk true - - - - - + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - - - - - - - - + + - - + - <_Parameter1>AssemblyAI.UnitTests + <_Parameter1>AssemblyAI.Test - - - - - - - - - - - - - - - - - - - - + diff --git a/src/AssemblyAI/Core/CustomConstants.cs b/src/AssemblyAI/Core/CustomConstants.cs deleted file mode 100644 index 353dc84..0000000 --- a/src/AssemblyAI/Core/CustomConstants.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace AssemblyAI.Core; - -internal static class CustomConstants -{ - /// - /// This is used for the AssemblyAI User-Agent. - /// If you update this, make sure to also update the version numbers in AssemblyAI.csproj - /// - internal const string Version = "1.2.0"; -} \ No newline at end of file diff --git a/src/AssemblyAI/Core/StringEnumSerializer.cs b/src/AssemblyAI/Core/EnumSerializer.cs similarity index 94% rename from src/AssemblyAI/Core/StringEnumSerializer.cs rename to src/AssemblyAI/Core/EnumSerializer.cs index 9b53fef..6710643 100644 --- a/src/AssemblyAI/Core/StringEnumSerializer.cs +++ b/src/AssemblyAI/Core/EnumSerializer.cs @@ -4,13 +4,13 @@ namespace AssemblyAI.Core; -internal class StringEnumSerializer : JsonConverter +internal class EnumSerializer : JsonConverter where TEnum : struct, System.Enum { private readonly Dictionary _enumToString = new(); private readonly Dictionary _stringToEnum = new(); - public StringEnumSerializer() + public EnumSerializer() { var type = typeof(TEnum); var values = Enum.GetValues(type); diff --git a/src/AssemblyAI/Core/Extensions.cs b/src/AssemblyAI/Core/Extensions.cs index 167b9e5..a1c7fef 100644 --- a/src/AssemblyAI/Core/Extensions.cs +++ b/src/AssemblyAI/Core/Extensions.cs @@ -4,11 +4,11 @@ namespace AssemblyAI.Core; internal static class Extensions { - internal static string Stringify(this Enum value) + public static string Stringify(this Enum value) { var field = value.GetType().GetField(value.ToString()); var attribute = (EnumMemberAttribute) - Attribute.GetCustomAttribute(field!, typeof(EnumMemberAttribute))!; + Attribute.GetCustomAttribute(field, typeof(EnumMemberAttribute)); return attribute?.Value ?? value.ToString(); } } diff --git a/src/AssemblyAI/Core/IRequestOptions.cs b/src/AssemblyAI/Core/IRequestOptions.cs new file mode 100644 index 0000000..cf3ef5a --- /dev/null +++ b/src/AssemblyAI/Core/IRequestOptions.cs @@ -0,0 +1,34 @@ +using System; +using System.Net.Http; + +#nullable enable + +namespace AssemblyAI.Core; + +internal interface IRequestOptions +{ + /// + /// The Base URL for the API. + /// + public string? BaseUrl { get; init; } + + /// + /// The http client used to make requests. + /// + public HttpClient? HttpClient { get; init; } + + /// + /// The http headers sent with the request. + /// + internal Headers Headers { get; init; } + + /// + /// The http client used to make requests. + /// + public int? MaxRetries { get; init; } + + /// + /// The timeout for the request. + /// + public TimeSpan? Timeout { get; init; } +} diff --git a/src/AssemblyAI/Core/JsonConfiguration.cs b/src/AssemblyAI/Core/JsonConfiguration.cs index 7606b88..7d75fe4 100644 --- a/src/AssemblyAI/Core/JsonConfiguration.cs +++ b/src/AssemblyAI/Core/JsonConfiguration.cs @@ -1,34 +1,26 @@ using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; -using AssemblyAI.Lemur; -using OneOf; namespace AssemblyAI.Core; -/// -/// The JSON options used by the AssemblyAI SDK. -/// -internal static class JsonOptions +internal static partial class JsonOptions { - /// - /// The JSON options used by the AssemblyAI SDK. - /// - internal static readonly JsonSerializerOptions JsonSerializerOptions; + public static readonly JsonSerializerOptions JsonSerializerOptions; static JsonOptions() { - JsonSerializerOptions = new JsonSerializerOptions + var options = new JsonSerializerOptions { - Converters = - { - new DateTimeSerializer(), - new OneOfSerializer>() - }, + Converters = { new DateTimeSerializer(), new OneOfSerializer() }, WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; + ConfigureJsonSerializerOptions(options); + JsonSerializerOptions = options; } + + static partial void ConfigureJsonSerializerOptions(JsonSerializerOptions defaultOptions); } /// @@ -42,7 +34,7 @@ public static class JsonUtils /// Object to serialize /// Type of the object to serialize /// The object serialized as JSON - public static string Serialize(T obj) + public static string Serialize(T obj) => JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptions); /// @@ -51,7 +43,7 @@ public static string Serialize(T obj) /// Object to serialize /// Type of the object to serialize /// The object serialized as JSON - public static JsonDocument SerializeToDocument(T obj) + public static JsonDocument SerializeToDocument(T obj) => JsonSerializer.SerializeToDocument(obj, JsonOptions.JsonSerializerOptions); /// @@ -60,7 +52,7 @@ public static JsonDocument SerializeToDocument(T obj) /// Object to serialize /// Type of the object to serialize /// The object serialized as JSON - public static JsonElement SerializeToElement(T obj) + public static JsonElement SerializeToElement(T obj) => JsonSerializer.SerializeToElement(obj, JsonOptions.JsonSerializerOptions); /// @@ -78,7 +70,7 @@ public static JsonElement SerializeToElement(T obj) /// The JSON string /// The type to deserialize the JSON to /// The deserialized object of type T - public static T Deserialize(string json) + public static T Deserialize(string json) => JsonSerializer.Deserialize(json, JsonOptions.JsonSerializerOptions)!; /// @@ -87,7 +79,7 @@ public static T Deserialize(string json) /// The JSON string /// The type to deserialize the JSON to /// The deserialized object of type T - public static T Deserialize(JsonDocument json) + public static T Deserialize(JsonDocument json) => json.Deserialize(JsonOptions.JsonSerializerOptions)!; /// @@ -96,7 +88,7 @@ public static T Deserialize(JsonDocument json) /// The JSON string /// The type to deserialize the JSON to /// The deserialized object of type T - public static T Deserialize(JsonElement json) + public static T Deserialize(JsonElement json) => json.Deserialize(JsonOptions.JsonSerializerOptions)!; /// @@ -105,6 +97,6 @@ public static T Deserialize(JsonElement json) /// The JSON string /// The type to deserialize the JSON to /// The deserialized object of type T - public static T Deserialize(JsonNode json) + public static T Deserialize(JsonNode json) => json.Deserialize(JsonOptions.JsonSerializerOptions)!; } \ No newline at end of file diff --git a/src/AssemblyAI/Core/OneOfSerializer.cs b/src/AssemblyAI/Core/OneOfSerializer.cs index c4c18fd..786b12d 100644 --- a/src/AssemblyAI/Core/OneOfSerializer.cs +++ b/src/AssemblyAI/Core/OneOfSerializer.cs @@ -5,10 +5,9 @@ namespace AssemblyAI.Core; -internal class OneOfSerializer : JsonConverter - where TOneOf : IOneOf +internal class OneOfSerializer : JsonConverter { - public override TOneOf? Read( + public override IOneOf? Read( ref Utf8JsonReader reader, System.Type typeToConvert, JsonSerializerOptions options @@ -17,14 +16,14 @@ JsonSerializerOptions options if (reader.TokenType is JsonTokenType.Null) return default; - foreach (var (type, cast) in s_types) + foreach (var (type, cast) in GetOneOfTypes(typeToConvert)) { try { var readerCopy = reader; var result = JsonSerializer.Deserialize(ref readerCopy, type, options); reader.Skip(); - return (TOneOf)cast.Invoke(null, [result])!; + return (IOneOf)cast.Invoke(null, [result])!; } catch (JsonException) { } } @@ -34,20 +33,18 @@ JsonSerializerOptions options ); } - private static readonly (System.Type type, MethodInfo cast)[] s_types = GetOneOfTypes(); - - public override void Write(Utf8JsonWriter writer, TOneOf value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, IOneOf value, JsonSerializerOptions options) { JsonSerializer.Serialize(writer, value.Value, options); } - private static (System.Type type, MethodInfo cast)[] GetOneOfTypes() + private static (System.Type type, MethodInfo cast)[] GetOneOfTypes(System.Type typeToConvert) { - var casts = typeof(TOneOf) + var casts = typeToConvert .GetRuntimeMethods() .Where(m => m.IsSpecialName && m.Name == "op_Implicit") .ToArray(); - var type = typeof(TOneOf); + var type = typeToConvert; while (type != null) { if ( @@ -62,6 +59,11 @@ private static (System.Type type, MethodInfo cast)[] GetOneOfTypes() type = type.BaseType; } - throw new InvalidOperationException($"{typeof(TOneOf)} isn't OneOf or OneOfBase"); + throw new InvalidOperationException($"{type} isn't OneOf or OneOfBase"); + } + + public override bool CanConvert(System.Type typeToConvert) + { + return typeof(IOneOf).IsAssignableFrom(typeToConvert); } } diff --git a/src/AssemblyAI/Core/Public/ExtendedClientOptions.cs b/src/AssemblyAI/Core/Public/ExtendedClientOptions.cs index 291f27b..4f666e2 100644 --- a/src/AssemblyAI/Core/Public/ExtendedClientOptions.cs +++ b/src/AssemblyAI/Core/Public/ExtendedClientOptions.cs @@ -2,6 +2,8 @@ // ReSharper disable AutoPropertyCanBeMadeGetOnly.Global // ReSharper disable CheckNamespace +using AssemblyAI.Core; + namespace AssemblyAI; /// @@ -18,4 +20,21 @@ public partial class ClientOptions /// The AssemblyAI user agent /// public UserAgent UserAgent { get; set; } = new(); + + /// + /// Clones this and returns a new instance + /// + internal ClientOptions Clone() + { + return new ClientOptions + { + ApiKey = ApiKey, + UserAgent = UserAgent.Clone(), + BaseUrl = BaseUrl, + HttpClient = HttpClient, + MaxRetries = MaxRetries, + Timeout = Timeout, + Headers = new Headers(new Dictionary(Headers)), + }; + } } \ No newline at end of file diff --git a/src/AssemblyAI/Core/Public/RequestOptions.cs b/src/AssemblyAI/Core/Public/RequestOptions.cs index 688540f..bf56473 100644 --- a/src/AssemblyAI/Core/Public/RequestOptions.cs +++ b/src/AssemblyAI/Core/Public/RequestOptions.cs @@ -6,7 +6,7 @@ namespace AssemblyAI; -public partial class RequestOptions +public partial class RequestOptions : IRequestOptions { /// /// The Base URL for the API. @@ -31,5 +31,5 @@ public partial class RequestOptions /// /// The http headers sent with the request. /// - internal Headers Headers { get; init; } = new(); + Headers IRequestOptions.Headers { get; init; } = new(); } diff --git a/src/AssemblyAI/Core/Public/Version.cs b/src/AssemblyAI/Core/Public/Version.cs new file mode 100644 index 0000000..93e43c9 --- /dev/null +++ b/src/AssemblyAI/Core/Public/Version.cs @@ -0,0 +1,6 @@ +namespace AssemblyAI; + +internal class Version +{ + public const string Current = "1.2.1"; +} diff --git a/src/AssemblyAI/Core/RawClient.cs b/src/AssemblyAI/Core/RawClient.cs index 82444ad..63e6e93 100644 --- a/src/AssemblyAI/Core/RawClient.cs +++ b/src/AssemblyAI/Core/RawClient.cs @@ -1,4 +1,5 @@ using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Threading; @@ -11,8 +12,11 @@ namespace AssemblyAI.Core; /// internal class RawClient(ClientOptions clientOptions) { + private const int InitialRetryDelayMs = 1000; + private const int MaxRetryDelayMs = 60000; + /// - /// The http client used to make requests. + /// The client options applied on every request. /// public readonly ClientOptions Options = clientOptions; @@ -21,36 +25,13 @@ public async Task MakeRequestAsync( CancellationToken cancellationToken = default ) { - var url = BuildUrl(request); - var httpRequest = new HttpRequestMessage(request.Method, url); - if (request.ContentType != null) - { - request.Headers.Add("Content-Type", request.ContentType); - } - SetHeaders(httpRequest, Options.Headers); - SetHeaders(httpRequest, request.Headers); - SetHeaders(httpRequest, request.Options?.Headers ?? new()); + // Apply the request timeout. + var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var timeout = request.Options?.Timeout ?? Options.Timeout; + cts.CancelAfter(timeout); - // Add the request body to the request - if (request is JsonApiRequest jsonRequest) - { - if (jsonRequest.Body != null) - { - httpRequest.Content = new StringContent( - JsonUtils.Serialize(jsonRequest.Body), - Encoding.UTF8, - "application/json" - ); - } - } - else if (request is StreamApiRequest { Body: not null } streamRequest) - { - httpRequest.Content = new StreamContent(streamRequest.Body); - } - // Send the request - var httpClient = request.Options?.HttpClient ?? Options.HttpClient; - var response = await httpClient.SendAsync(httpRequest, cancellationToken); - return new ApiResponse { StatusCode = (int)response.StatusCode, Raw = response }; + // Send the request. + return await SendWithRetriesAsync(request, cts.Token); } public record BaseApiRequest @@ -67,7 +48,7 @@ public record BaseApiRequest public Headers Headers { get; init; } = new(); - public RequestOptions? Options { get; init; } + public IRequestOptions? Options { get; init; } } /// @@ -96,19 +77,70 @@ public record ApiResponse public required HttpResponseMessage Raw { get; init; } } - private void SetHeaders(HttpRequestMessage httpRequest, Headers headers) + private async Task SendWithRetriesAsync( + BaseApiRequest request, + CancellationToken cancellationToken + ) { - foreach (var header in headers) + var httpClient = request.Options?.HttpClient ?? Options.HttpClient; + var maxRetries = request.Options?.MaxRetries ?? Options.MaxRetries; + var response = await httpClient.SendAsync(BuildHttpRequest(request), cancellationToken); + for (var i = 0; i < maxRetries; i++) { - var value = header.Value?.Match(str => str, func => func.Invoke()); - if (value != null) + if (!ShouldRetry(response)) { - httpRequest.Headers.TryAddWithoutValidation(header.Key, value); + break; } + var delayMs = Math.Min(InitialRetryDelayMs * (int)Math.Pow(2, i), MaxRetryDelayMs); + await System.Threading.Tasks.Task.Delay(delayMs, cancellationToken); + response = await httpClient.SendAsync(BuildHttpRequest(request), cancellationToken); } + return new ApiResponse { StatusCode = (int)response.StatusCode, Raw = response }; } - private string BuildUrl(BaseApiRequest request) + private static bool ShouldRetry(HttpResponseMessage response) + { + var statusCode = (int)response.StatusCode; + return statusCode is 408 or 429 or >= 500; + } + + private HttpRequestMessage BuildHttpRequest(BaseApiRequest request) + { + var url = BuildUrl(request); + var httpRequest = new HttpRequestMessage(request.Method, url); + switch (request) + { + // Add the request body to the request. + case JsonApiRequest jsonRequest: + { + if (jsonRequest.Body != null) + { + httpRequest.Content = new StringContent( + JsonUtils.Serialize(jsonRequest.Body), + Encoding.UTF8, + "application/json" + ); + } + break; + } + case StreamApiRequest { Body: not null } streamRequest: + httpRequest.Content = new StreamContent(streamRequest.Body); + break; + } + if (request.ContentType != null) + { + httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse( + request.ContentType + ); + } + SetHeaders(httpRequest, Options.Headers); + SetHeaders(httpRequest, request.Headers); + SetHeaders(httpRequest, request.Options?.Headers ?? new Headers()); + + return httpRequest; + } + + private static string BuildUrl(BaseApiRequest request) { var baseUrl = request.Options?.BaseUrl ?? request.BaseUrl; var trimmedBaseUrl = baseUrl.TrimEnd('/'); @@ -139,7 +171,19 @@ private string BuildUrl(BaseApiRequest request) return current; } ); - url = url.Substring(0, url.Length - 1); + url = url[..^1]; return url; } + + private static void SetHeaders(HttpRequestMessage httpRequest, Headers headers) + { + foreach (var header in headers) + { + var value = header.Value?.Match(str => str, func => func.Invoke()); + if (value != null) + { + httpRequest.Headers.TryAddWithoutValidation(header.Key, value); + } + } + } } diff --git a/src/AssemblyAI/Files/FilesClient.cs b/src/AssemblyAI/Files/FilesClient.cs index 91ae90b..28bf90c 100644 --- a/src/AssemblyAI/Files/FilesClient.cs +++ b/src/AssemblyAI/Files/FilesClient.cs @@ -33,6 +33,7 @@ public async Task UploadAsync( Method = HttpMethod.Post, Path = "v2/upload", Body = request, + ContentType = "application/octet-stream", Options = options, }, cancellationToken diff --git a/src/AssemblyAI/Lemur/LemurClient.cs b/src/AssemblyAI/Lemur/LemurClient.cs index 36b72b0..130f8a1 100644 --- a/src/AssemblyAI/Lemur/LemurClient.cs +++ b/src/AssemblyAI/Lemur/LemurClient.cs @@ -26,7 +26,7 @@ internal LemurClient(RawClient client) /// await client.Lemur.TaskAsync( /// new LemurTaskParams /// { - /// TranscriptIds = new List() { "64nygnr62k-405c-4ae8-8a6b-d90b40ff3cce" }, + /// TranscriptIds = new List<string>() { "64nygnr62k-405c-4ae8-8a6b-d90b40ff3cce" }, /// Context = "This is an interview about wildfires.", /// FinalModel = LemurModel.AnthropicClaude35Sonnet, /// MaxOutputSize = 3000, @@ -49,6 +49,7 @@ public async Task TaskAsync( Method = HttpMethod.Post, Path = "lemur/v3/generate/task", Body = request, + ContentType = "application/json", Options = options, }, cancellationToken @@ -82,7 +83,7 @@ public async Task TaskAsync( /// await client.Lemur.SummaryAsync( /// new LemurSummaryParams /// { - /// TranscriptIds = new List() { "47b95ba5-8889-44d8-bc80-5de38306e582" }, + /// TranscriptIds = new List<string>() { "47b95ba5-8889-44d8-bc80-5de38306e582" }, /// Context = "This is an interview about wildfires.", /// FinalModel = LemurModel.AnthropicClaude35Sonnet, /// MaxOutputSize = 3000, @@ -104,6 +105,7 @@ public async Task SummaryAsync( Method = HttpMethod.Post, Path = "lemur/v3/generate/summary", Body = request, + ContentType = "application/json", Options = options, }, cancellationToken @@ -137,23 +139,23 @@ public async Task SummaryAsync( /// await client.Lemur.QuestionAnswerAsync( /// new LemurQuestionAnswerParams /// { - /// TranscriptIds = new List() { "64nygnr62k-405c-4ae8-8a6b-d90b40ff3cce" }, + /// TranscriptIds = new List<string>() { "64nygnr62k-405c-4ae8-8a6b-d90b40ff3cce" }, /// Context = "This is an interview about wildfires.", /// FinalModel = LemurModel.AnthropicClaude35Sonnet, /// MaxOutputSize = 3000, /// Temperature = 0f, - /// Questions = new List() + /// Questions = new List<LemurQuestion>() /// { /// new LemurQuestion /// { /// Question = "Where are there wildfires?", /// AnswerFormat = "List of countries in ISO 3166-1 alpha-2 format", - /// AnswerOptions = new List() { "US", "CA" }, + /// AnswerOptions = new List<string>() { "US", "CA" }, /// }, /// new LemurQuestion /// { /// Question = "Is global warming affecting wildfires?", - /// AnswerOptions = new List() { "yes", "no" }, + /// AnswerOptions = new List<string>() { "yes", "no" }, /// }, /// }, /// } @@ -173,6 +175,7 @@ public async Task QuestionAnswerAsync( Method = HttpMethod.Post, Path = "lemur/v3/generate/question-answer", Body = request, + ContentType = "application/json", Options = options, }, cancellationToken @@ -205,7 +208,7 @@ public async Task QuestionAnswerAsync( /// await client.Lemur.ActionItemsAsync( /// new LemurActionItemsParams /// { - /// TranscriptIds = new List() { "64nygnr62k-405c-4ae8-8a6b-d90b40ff3cce" }, + /// TranscriptIds = new List<string>() { "64nygnr62k-405c-4ae8-8a6b-d90b40ff3cce" }, /// Context = "This is an interview about wildfires.", /// FinalModel = LemurModel.AnthropicClaude35Sonnet, /// MaxOutputSize = 3000, @@ -228,6 +231,7 @@ public async Task ActionItemsAsync( Method = HttpMethod.Post, Path = "lemur/v3/generate/action-items", Body = request, + ContentType = "application/json", Options = options, }, cancellationToken diff --git a/src/AssemblyAI/Lemur/Requests/LemurActionItemsParams.cs b/src/AssemblyAI/Lemur/Requests/LemurActionItemsParams.cs index b2a08a1..8e892fb 100644 --- a/src/AssemblyAI/Lemur/Requests/LemurActionItemsParams.cs +++ b/src/AssemblyAI/Lemur/Requests/LemurActionItemsParams.cs @@ -11,7 +11,6 @@ public record LemurActionItemsParams /// /// How you want the action items to be returned. This can be any text. /// Defaults to "Bullet Points". - /// /// [JsonPropertyName("answer_format")] public string? AnswerFormat { get; set; } @@ -34,7 +33,6 @@ public record LemurActionItemsParams /// Context to provide the model. This can be a string or a free-form JSON value. /// [JsonPropertyName("context")] - [JsonConverter(typeof(OneOfSerializer>))] public OneOf? Context { get; set; } /// diff --git a/src/AssemblyAI/Lemur/Requests/LemurQuestionAnswerParams.cs b/src/AssemblyAI/Lemur/Requests/LemurQuestionAnswerParams.cs index dbdf8ef..40a5b04 100644 --- a/src/AssemblyAI/Lemur/Requests/LemurQuestionAnswerParams.cs +++ b/src/AssemblyAI/Lemur/Requests/LemurQuestionAnswerParams.cs @@ -32,7 +32,6 @@ public record LemurQuestionAnswerParams /// Context to provide the model. This can be a string or a free-form JSON value. /// [JsonPropertyName("context")] - [JsonConverter(typeof(OneOfSerializer>))] public OneOf? Context { get; set; } /// diff --git a/src/AssemblyAI/Lemur/Requests/LemurSummaryParams.cs b/src/AssemblyAI/Lemur/Requests/LemurSummaryParams.cs index 1c946e4..35e4633 100644 --- a/src/AssemblyAI/Lemur/Requests/LemurSummaryParams.cs +++ b/src/AssemblyAI/Lemur/Requests/LemurSummaryParams.cs @@ -10,7 +10,6 @@ public record LemurSummaryParams { /// /// How you want the summary to be returned. This can be any text. Examples: "TLDR", "bullet points" - /// /// [JsonPropertyName("answer_format")] public string? AnswerFormat { get; set; } @@ -33,7 +32,6 @@ public record LemurSummaryParams /// Context to provide the model. This can be a string or a free-form JSON value. /// [JsonPropertyName("context")] - [JsonConverter(typeof(OneOfSerializer>))] public OneOf? Context { get; set; } /// diff --git a/src/AssemblyAI/Lemur/Requests/LemurTaskParams.cs b/src/AssemblyAI/Lemur/Requests/LemurTaskParams.cs index bc38010..2901d5d 100644 --- a/src/AssemblyAI/Lemur/Requests/LemurTaskParams.cs +++ b/src/AssemblyAI/Lemur/Requests/LemurTaskParams.cs @@ -32,7 +32,6 @@ public record LemurTaskParams /// Context to provide the model. This can be a string or a free-form JSON value. /// [JsonPropertyName("context")] - [JsonConverter(typeof(OneOfSerializer>))] public OneOf? Context { get; set; } /// diff --git a/src/AssemblyAI/Lemur/Types/LemurBaseParams.cs b/src/AssemblyAI/Lemur/Types/LemurBaseParams.cs index d757dd1..138ebfc 100644 --- a/src/AssemblyAI/Lemur/Types/LemurBaseParams.cs +++ b/src/AssemblyAI/Lemur/Types/LemurBaseParams.cs @@ -26,7 +26,6 @@ public record LemurBaseParams /// Context to provide the model. This can be a string or a free-form JSON value. /// [JsonPropertyName("context")] - [JsonConverter(typeof(OneOfSerializer>))] public OneOf? Context { get; set; } /// diff --git a/src/AssemblyAI/Lemur/Types/LemurModel.cs b/src/AssemblyAI/Lemur/Types/LemurModel.cs index bf1b231..cf87e74 100644 --- a/src/AssemblyAI/Lemur/Types/LemurModel.cs +++ b/src/AssemblyAI/Lemur/Types/LemurModel.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Lemur; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum LemurModel { [EnumMember(Value = "anthropic/claude-3-5-sonnet")] diff --git a/src/AssemblyAI/Lemur/Types/LemurQuestion.cs b/src/AssemblyAI/Lemur/Types/LemurQuestion.cs index 59ac5fc..0fca4e2 100644 --- a/src/AssemblyAI/Lemur/Types/LemurQuestion.cs +++ b/src/AssemblyAI/Lemur/Types/LemurQuestion.cs @@ -18,7 +18,6 @@ public record LemurQuestion /// Any context about the transcripts you wish to provide. This can be a string or any object. /// [JsonPropertyName("context")] - [JsonConverter(typeof(OneOfSerializer>))] public OneOf? Context { get; set; } /// diff --git a/src/AssemblyAI/Realtime/RealtimeClient.cs b/src/AssemblyAI/Realtime/RealtimeClient.cs index d06e881..b9e1c4f 100644 --- a/src/AssemblyAI/Realtime/RealtimeClient.cs +++ b/src/AssemblyAI/Realtime/RealtimeClient.cs @@ -40,6 +40,7 @@ public async Task CreateTemporaryTokenAsync( Method = HttpMethod.Post, Path = "v2/realtime/token", Body = request, + ContentType = "application/json", Options = options, }, cancellationToken diff --git a/src/AssemblyAI/Realtime/Types/AudioEncoding.cs b/src/AssemblyAI/Realtime/Types/AudioEncoding.cs index e15f895..617d74b 100644 --- a/src/AssemblyAI/Realtime/Types/AudioEncoding.cs +++ b/src/AssemblyAI/Realtime/Types/AudioEncoding.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Realtime; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum AudioEncoding { [EnumMember(Value = "pcm_s16le")] diff --git a/src/AssemblyAI/Realtime/Types/FinalTranscript.cs b/src/AssemblyAI/Realtime/Types/FinalTranscript.cs index 4816900..89e3a43 100644 --- a/src/AssemblyAI/Realtime/Types/FinalTranscript.cs +++ b/src/AssemblyAI/Realtime/Types/FinalTranscript.cs @@ -20,7 +20,7 @@ public record FinalTranscript public required bool Punctuated { get; set; } /// - /// Whether the text is formatted, for example Dollar -> $ + /// Whether the text is formatted, for example Dollar -> $ /// [JsonPropertyName("text_formatted")] public required bool TextFormatted { get; set; } diff --git a/src/AssemblyAI/Realtime/Types/MessageType.cs b/src/AssemblyAI/Realtime/Types/MessageType.cs index e315ba9..b1e7925 100644 --- a/src/AssemblyAI/Realtime/Types/MessageType.cs +++ b/src/AssemblyAI/Realtime/Types/MessageType.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Realtime; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum MessageType { [EnumMember(Value = "SessionBegins")] diff --git a/src/AssemblyAI/Realtime/Types/RealtimeTranscriptType.cs b/src/AssemblyAI/Realtime/Types/RealtimeTranscriptType.cs index 67b92f8..fa64fcb 100644 --- a/src/AssemblyAI/Realtime/Types/RealtimeTranscriptType.cs +++ b/src/AssemblyAI/Realtime/Types/RealtimeTranscriptType.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Realtime; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum RealtimeTranscriptType { [EnumMember(Value = "PartialTranscript")] diff --git a/src/AssemblyAI/Transcripts/TranscriptsClient.cs b/src/AssemblyAI/Transcripts/TranscriptsClient.cs index eec587d..7fae161 100644 --- a/src/AssemblyAI/Transcripts/TranscriptsClient.cs +++ b/src/AssemblyAI/Transcripts/TranscriptsClient.cs @@ -110,13 +110,13 @@ public async Task ListAsync( /// AutoHighlights = true, /// AudioStartFrom = 10, /// AudioEndAt = 280, - /// WordBoost = new List() { "aws", "azure", "google cloud" }, + /// WordBoost = new List<string>() { "aws", "azure", "google cloud" }, /// BoostParam = TranscriptBoostParam.High, /// FilterProfanity = true, /// RedactPii = true, /// RedactPiiAudio = true, /// RedactPiiAudioQuality = RedactPiiAudioQuality.Mp3, - /// RedactPiiPolicies = new List() + /// RedactPiiPolicies = new List<PiiPolicy>() /// { /// PiiPolicy.UsSocialSecurityNumber, /// PiiPolicy.CreditCardNumber, @@ -126,11 +126,11 @@ public async Task ListAsync( /// SpeakersExpected = 2, /// ContentSafety = true, /// IabCategories = true, - /// CustomSpelling = new List() + /// CustomSpelling = new List<TranscriptCustomSpelling>() /// { /// new TranscriptCustomSpelling /// { - /// From = new List() { "dicarlo" }, + /// From = new List<string>() { "dicarlo" }, /// To = "Decarlo", /// }, /// }, @@ -142,7 +142,7 @@ public async Task ListAsync( /// SummaryModel = SummaryModel.Informative, /// SummaryType = SummaryType.Bullets, /// CustomTopics = true, - /// Topics = new List() { "topics" }, + /// Topics = new List<string>() { "topics" }, /// AudioUrl = "https://assembly.ai/wildfires.mp3", /// } /// ); @@ -161,6 +161,7 @@ public async Task SubmitAsync( Method = HttpMethod.Post, Path = "v2/transcript", Body = request, + ContentType = "application/json", Options = options, }, cancellationToken @@ -302,7 +303,7 @@ public async Task GetSubtitlesAsync( new RawClient.JsonApiRequest { BaseUrl = _client.Options.BaseUrl, - Method = HttpMethod.Get, + Method = HttpMethod.Get, Path = $"v2/transcript/{transcriptId}/{subtitleFormat.Stringify()}", Query = _query, Options = options, diff --git a/src/AssemblyAI/Transcripts/Types/AudioIntelligenceModelStatus.cs b/src/AssemblyAI/Transcripts/Types/AudioIntelligenceModelStatus.cs index 84b0259..b5c3f15 100644 --- a/src/AssemblyAI/Transcripts/Types/AudioIntelligenceModelStatus.cs +++ b/src/AssemblyAI/Transcripts/Types/AudioIntelligenceModelStatus.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Transcripts; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum AudioIntelligenceModelStatus { [EnumMember(Value = "success")] diff --git a/src/AssemblyAI/Transcripts/Types/ContentSafetyLabelsResult.cs b/src/AssemblyAI/Transcripts/Types/ContentSafetyLabelsResult.cs index da73443..a785a55 100644 --- a/src/AssemblyAI/Transcripts/Types/ContentSafetyLabelsResult.cs +++ b/src/AssemblyAI/Transcripts/Types/ContentSafetyLabelsResult.cs @@ -1,5 +1,5 @@ using System.Text.Json.Serialization; -using AssemblyAI.Transcripts; +using AssemblyAI.Core; #nullable enable @@ -13,6 +13,9 @@ public record ContentSafetyLabelsResult [JsonPropertyName("status")] public AudioIntelligenceModelStatus Status { get; set; } + /// + /// An array of results for the Content Moderation model + /// [JsonPropertyName("results")] public IEnumerable Results { get; set; } = new List(); @@ -29,4 +32,9 @@ public record ContentSafetyLabelsResult [JsonPropertyName("severity_score_summary")] public Dictionary SeverityScoreSummary { get; set; } = new Dictionary(); + + public override string ToString() + { + return JsonUtils.Serialize(this); + } } diff --git a/src/AssemblyAI/Transcripts/Types/EntityType.cs b/src/AssemblyAI/Transcripts/Types/EntityType.cs index 98f95bc..21f9f17 100644 --- a/src/AssemblyAI/Transcripts/Types/EntityType.cs +++ b/src/AssemblyAI/Transcripts/Types/EntityType.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Transcripts; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum EntityType { [EnumMember(Value = "account_number")] diff --git a/src/AssemblyAI/Transcripts/Types/PiiPolicy.cs b/src/AssemblyAI/Transcripts/Types/PiiPolicy.cs index 441d1f0..d277e3f 100644 --- a/src/AssemblyAI/Transcripts/Types/PiiPolicy.cs +++ b/src/AssemblyAI/Transcripts/Types/PiiPolicy.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Transcripts; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum PiiPolicy { [EnumMember(Value = "account_number")] diff --git a/src/AssemblyAI/Transcripts/Types/RedactPiiAudioQuality.cs b/src/AssemblyAI/Transcripts/Types/RedactPiiAudioQuality.cs index 6b59661..3df5fe9 100644 --- a/src/AssemblyAI/Transcripts/Types/RedactPiiAudioQuality.cs +++ b/src/AssemblyAI/Transcripts/Types/RedactPiiAudioQuality.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Transcripts; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum RedactPiiAudioQuality { [EnumMember(Value = "mp3")] diff --git a/src/AssemblyAI/Transcripts/Types/Sentiment.cs b/src/AssemblyAI/Transcripts/Types/Sentiment.cs index 9748841..6841a5c 100644 --- a/src/AssemblyAI/Transcripts/Types/Sentiment.cs +++ b/src/AssemblyAI/Transcripts/Types/Sentiment.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Transcripts; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum Sentiment { [EnumMember(Value = "POSITIVE")] diff --git a/src/AssemblyAI/Transcripts/Types/SpeechModel.cs b/src/AssemblyAI/Transcripts/Types/SpeechModel.cs index ea981fd..defb060 100644 --- a/src/AssemblyAI/Transcripts/Types/SpeechModel.cs +++ b/src/AssemblyAI/Transcripts/Types/SpeechModel.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Transcripts; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum SpeechModel { [EnumMember(Value = "best")] diff --git a/src/AssemblyAI/Transcripts/Types/SubstitutionPolicy.cs b/src/AssemblyAI/Transcripts/Types/SubstitutionPolicy.cs index b29eeb6..632c992 100644 --- a/src/AssemblyAI/Transcripts/Types/SubstitutionPolicy.cs +++ b/src/AssemblyAI/Transcripts/Types/SubstitutionPolicy.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Transcripts; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum SubstitutionPolicy { [EnumMember(Value = "entity_name")] diff --git a/src/AssemblyAI/Transcripts/Types/SubtitleFormat.cs b/src/AssemblyAI/Transcripts/Types/SubtitleFormat.cs index ebe6a8f..5c5fad3 100644 --- a/src/AssemblyAI/Transcripts/Types/SubtitleFormat.cs +++ b/src/AssemblyAI/Transcripts/Types/SubtitleFormat.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Transcripts; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum SubtitleFormat { [EnumMember(Value = "srt")] diff --git a/src/AssemblyAI/Transcripts/Types/SummaryModel.cs b/src/AssemblyAI/Transcripts/Types/SummaryModel.cs index d935aa2..86cfbcb 100644 --- a/src/AssemblyAI/Transcripts/Types/SummaryModel.cs +++ b/src/AssemblyAI/Transcripts/Types/SummaryModel.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Transcripts; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum SummaryModel { [EnumMember(Value = "informative")] diff --git a/src/AssemblyAI/Transcripts/Types/SummaryType.cs b/src/AssemblyAI/Transcripts/Types/SummaryType.cs index 4145146..25632e8 100644 --- a/src/AssemblyAI/Transcripts/Types/SummaryType.cs +++ b/src/AssemblyAI/Transcripts/Types/SummaryType.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Transcripts; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum SummaryType { [EnumMember(Value = "bullets")] diff --git a/src/AssemblyAI/Transcripts/Types/TopicDetectionModelResult.cs b/src/AssemblyAI/Transcripts/Types/TopicDetectionModelResult.cs index fa7d26f..b41d7ad 100644 --- a/src/AssemblyAI/Transcripts/Types/TopicDetectionModelResult.cs +++ b/src/AssemblyAI/Transcripts/Types/TopicDetectionModelResult.cs @@ -1,5 +1,5 @@ using System.Text.Json.Serialization; -using AssemblyAI.Transcripts; +using AssemblyAI.Core; #nullable enable @@ -25,4 +25,9 @@ public record TopicDetectionModelResult /// [JsonPropertyName("summary")] public Dictionary Summary { get; set; } = new Dictionary(); + + public override string ToString() + { + return JsonUtils.Serialize(this); + } } diff --git a/src/AssemblyAI/Transcripts/Types/TopicDetectionResultLabelsItem.cs b/src/AssemblyAI/Transcripts/Types/TopicDetectionResultLabelsItem.cs index b9cc515..2c1ff25 100644 --- a/src/AssemblyAI/Transcripts/Types/TopicDetectionResultLabelsItem.cs +++ b/src/AssemblyAI/Transcripts/Types/TopicDetectionResultLabelsItem.cs @@ -14,7 +14,7 @@ public record TopicDetectionResultLabelsItem public required double Relevance { get; set; } /// - /// The IAB taxonomical label for the label of the detected topic, where > denotes supertopic/subtopic relationship + /// The IAB taxonomical label for the label of the detected topic, where > denotes supertopic/subtopic relationship /// [JsonPropertyName("label")] public required string Label { get; set; } diff --git a/src/AssemblyAI/Transcripts/Types/Transcript.cs b/src/AssemblyAI/Transcripts/Types/Transcript.cs index 93294f0..c20869d 100644 --- a/src/AssemblyAI/Transcripts/Types/Transcript.cs +++ b/src/AssemblyAI/Transcripts/Types/Transcript.cs @@ -5,7 +5,7 @@ namespace AssemblyAI.Transcripts; -public partial record Transcript +public record Transcript { /// /// The unique identifier of your transcript diff --git a/src/AssemblyAI/Transcripts/Types/TranscriptBoostParam.cs b/src/AssemblyAI/Transcripts/Types/TranscriptBoostParam.cs index 6f1ca95..47cdf2f 100644 --- a/src/AssemblyAI/Transcripts/Types/TranscriptBoostParam.cs +++ b/src/AssemblyAI/Transcripts/Types/TranscriptBoostParam.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Transcripts; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum TranscriptBoostParam { [EnumMember(Value = "low")] diff --git a/src/AssemblyAI/Transcripts/Types/TranscriptLanguageCode.cs b/src/AssemblyAI/Transcripts/Types/TranscriptLanguageCode.cs index 31f39d4..172e0f6 100644 --- a/src/AssemblyAI/Transcripts/Types/TranscriptLanguageCode.cs +++ b/src/AssemblyAI/Transcripts/Types/TranscriptLanguageCode.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Transcripts; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum TranscriptLanguageCode { [EnumMember(Value = "en")] diff --git a/src/AssemblyAI/Transcripts/Types/TranscriptReadyStatus.cs b/src/AssemblyAI/Transcripts/Types/TranscriptReadyStatus.cs index 999ae77..bcf7d89 100644 --- a/src/AssemblyAI/Transcripts/Types/TranscriptReadyStatus.cs +++ b/src/AssemblyAI/Transcripts/Types/TranscriptReadyStatus.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Transcripts; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum TranscriptReadyStatus { [EnumMember(Value = "completed")] diff --git a/src/AssemblyAI/Transcripts/Types/TranscriptStatus.cs b/src/AssemblyAI/Transcripts/Types/TranscriptStatus.cs index 9b253d0..6554b48 100644 --- a/src/AssemblyAI/Transcripts/Types/TranscriptStatus.cs +++ b/src/AssemblyAI/Transcripts/Types/TranscriptStatus.cs @@ -6,7 +6,7 @@ namespace AssemblyAI.Transcripts; -[JsonConverter(typeof(StringEnumSerializer))] +[JsonConverter(typeof(EnumSerializer))] public enum TranscriptStatus { [EnumMember(Value = "queued")] diff --git a/src/AssemblyAI/UserAgent.cs b/src/AssemblyAI/UserAgent.cs index b001f7e..625616d 100644 --- a/src/AssemblyAI/UserAgent.cs +++ b/src/AssemblyAI/UserAgent.cs @@ -40,7 +40,7 @@ public UserAgent(UserAgent a, UserAgent b) { _userAgent = Merge(a._userAgent, b._userAgent) as Dictionary; } - + /// /// Get or set a user agent item by key. /// @@ -71,7 +71,7 @@ public string ToAssemblyAIUserAgentString() private static UserAgent CreateDefaultUserAgent() { var defaultUserAgent = new Dictionary(); - defaultUserAgent["sdk"] = new UserAgentItem("CSharp", CustomConstants.Version); + defaultUserAgent["sdk"] = new UserAgentItem("CSharp", Version.Current); #if NET462_OR_GREATER defaultUserAgent["runtime_env"] = new UserAgentItem(".NET Framework", $"{Environment.Version}"); #else @@ -82,7 +82,7 @@ private static UserAgent CreateDefaultUserAgent() return new UserAgent(defaultUserAgent); } - + #if NET462_OR_GREATER #else /// @@ -152,6 +152,14 @@ private static Dictionary Merge( return newUserAgent as Dictionary; } + + /// + /// Clones this and returns a new instance + /// + internal UserAgent Clone() + { + return new UserAgent(_userAgent.ToDictionary(kv => kv.Key, kv => kv.Value?.Clone())); + } } /// @@ -163,4 +171,12 @@ public class UserAgentItem(string name, string version) { public string Name { get; set; } = name; public string Version { get; set; } = version; + + /// + /// Clones this and returns a new instance + /// + internal UserAgentItem Clone() + { + return new UserAgentItem(Name, Version); + } } \ No newline at end of file