diff --git a/.gitignore b/.gitignore
index 866918b1..aed4005f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -248,3 +248,6 @@ web.config
app.config
settings.json
project.lock.json
+
+# Log
+log.txt
\ No newline at end of file
diff --git a/Deepgram.Dev.sln b/Deepgram.Dev.sln
index d0f59b96..08497041 100644
--- a/Deepgram.Dev.sln
+++ b/Deepgram.Dev.sln
@@ -177,6 +177,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Streaming", "tests\edge_cas
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Speak", "tests\edge_cases\tts_v1_client_example\Speak.csproj", "{AB053DDA-2487-476C-9793-A50C37F10CCA}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "agent", "agent", "{B7C828E2-1CDB-4F78-9AB7-CA2180795DF4}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "websocket", "websocket", "{B254E451-B210-4A01-8854-DA03AC2A1065}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "simple", "simple", "{FB6A2238-DD9A-4A47-B723-C173F2D2E31C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Agent", "examples\agent\websocket\simple\Agent.csproj", "{332347AC-E1D8-4B5D-A26F-50975AEF1F4F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -327,6 +335,10 @@ Global
{AB053DDA-2487-476C-9793-A50C37F10CCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AB053DDA-2487-476C-9793-A50C37F10CCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AB053DDA-2487-476C-9793-A50C37F10CCA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {332347AC-E1D8-4B5D-A26F-50975AEF1F4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {332347AC-E1D8-4B5D-A26F-50975AEF1F4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {332347AC-E1D8-4B5D-A26F-50975AEF1F4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {332347AC-E1D8-4B5D-A26F-50975AEF1F4F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -412,6 +424,10 @@ Global
{5CEEB2F0-F284-4BB3-8999-6E91151C0C06} = {1280E66D-A375-422A-ACB4-48F17E9C190E}
{964A87B4-31F8-4D68-AE64-64E66C9959FD} = {0BF29CA2-1CD6-4FF0-BC7B-B33C6B41E9A1}
{AB053DDA-2487-476C-9793-A50C37F10CCA} = {5CEEB2F0-F284-4BB3-8999-6E91151C0C06}
+ {B7C828E2-1CDB-4F78-9AB7-CA2180795DF4} = {C673DFD1-528A-4BAE-94E6-02EF058AC363}
+ {B254E451-B210-4A01-8854-DA03AC2A1065} = {B7C828E2-1CDB-4F78-9AB7-CA2180795DF4}
+ {FB6A2238-DD9A-4A47-B723-C173F2D2E31C} = {B254E451-B210-4A01-8854-DA03AC2A1065}
+ {332347AC-E1D8-4B5D-A26F-50975AEF1F4F} = {FB6A2238-DD9A-4A47-B723-C173F2D2E31C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8D4ABC6D-7126-4EE2-9303-43A954616B2A}
diff --git a/Deepgram/AgentWebSocketClient.cs b/Deepgram/AgentWebSocketClient.cs
new file mode 100644
index 00000000..eb84a76a
--- /dev/null
+++ b/Deepgram/AgentWebSocketClient.cs
@@ -0,0 +1,18 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Clients.Agent.v2.WebSocket;
+using Deepgram.Models.Authenticate.v1;
+
+namespace Deepgram;
+
+///
+/// Implements the latest supported version of the Agent Client.
+///
+public class AgentWebSocketClient : Client
+{
+ public AgentWebSocketClient(string apiKey = "", DeepgramWsClientOptions? deepgramClientOptions = null) : base(apiKey, deepgramClientOptions)
+ {
+ }
+}
diff --git a/Deepgram/ClientFactory.cs b/Deepgram/ClientFactory.cs
index 9eb46e2d..18c5bb65 100644
--- a/Deepgram/ClientFactory.cs
+++ b/Deepgram/ClientFactory.cs
@@ -15,6 +15,17 @@ namespace Deepgram;
public static class ClientFactory
{
+ ///
+ /// Create a new AgentWebSocketClient using the latest version
+ ///
+ ///
+ ///
+ ///
+ public static V2.IAgentWebSocketClient CreateAgentWebSocketClient(string apiKey = "", DeepgramWsClientOptions? options = null)
+ {
+ return new AgentWebSocketClient(apiKey, options);
+ }
+
///
/// Create a new AnalyzeClient
///
@@ -108,6 +119,15 @@ public static V2.ISpeakWebSocketClient CreateSpeakWebSocketClient(string apiKey
/// and the latest version will be renamed to v1.
///
+ ///
+ /// This method allows you to create an AgentClient with a specific version of the client.
+ /// TODO: this should be revisited at a later time. Opportunity to use reflection to get the type of the client
+ ///
+ public static object CreateAgentWebSocketClient(int version, string apiKey = "", DeepgramWsClientOptions? options = null)
+ {
+ return new AgentWebSocketClient(apiKey, options);
+ }
+
///
/// This method allows you to create an AnalyzeClient with a specific version of the client.
/// TODO: this should be revisited at a later time. Opportunity to use reflection to get the type of the client
diff --git a/Deepgram/Clients/Agent/v2/Websocket/Client.cs b/Deepgram/Clients/Agent/v2/Websocket/Client.cs
new file mode 100644
index 00000000..39b3ac2a
--- /dev/null
+++ b/Deepgram/Clients/Agent/v2/Websocket/Client.cs
@@ -0,0 +1,860 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Abstractions.v2;
+using Deepgram.Models.Authenticate.v1;
+using Deepgram.Models.Agent.v2.WebSocket;
+using Common = Deepgram.Models.Common.v2.WebSocket;
+using Deepgram.Clients.Interfaces.v2;
+
+namespace Deepgram.Clients.Agent.v2.WebSocket;
+
+///
+/// Implements version 2 of the Listen WebSocket Client.
+///
+public class Client : AbstractWebSocketClient, IAgentWebSocketClient
+{
+ /// Required DeepgramApiKey
+ /// for HttpClient Configuration
+ public Client(string? apiKey = null, IDeepgramClientOptions? options = null) : base(apiKey, options)
+ {
+ Log.Verbose("AgentWSClient", "ENTER");
+ Log.Debug("AgentWSClient", $"KeepAlive: {_deepgramClientOptions.KeepAlive}");
+ Log.Verbose("AgentWSClient", "LEAVE");
+ }
+
+ #region Event Handlers
+ ///
+ /// Fires when an event is received from the Deepgram API
+ ///
+ private event EventHandler? _audioReceived;
+ private event EventHandler? _agentAudioDoneReceived;
+ private event EventHandler? _agentStartedSpeakingReceived;
+ private event EventHandler? _agentThinkingReceived;
+ private event EventHandler? _conversationTextReceived;
+ private event EventHandler? _functionCallingReceived;
+ private event EventHandler? _functionCallRequestReceived;
+ private event EventHandler? _userStartedSpeakingReceived;
+ private event EventHandler? _welcomeReceived;
+ private event EventHandler? _settingsAppliedReceived;
+ private event EventHandler? _injectionRefusedReceived;
+ private event EventHandler? _instructionsUpdatedReceived;
+ private event EventHandler? _speakUpdatedReceived;
+ #endregion
+
+ ///
+ /// Connect to a Deepgram API Web Socket to begin transcribing audio
+ ///
+ /// Options to use when transcribing audio
+ /// The task object representing the asynchronous operation.
+ public async Task Connect(SettingsConfigurationSchema options, CancellationTokenSource? cancelToken = null, Dictionary? addons = null,
+ Dictionary? headers = null)
+ {
+ Log.Verbose("AgentWSClient.Connect", "ENTER");
+ Log.Information("Connect", $"options:\n{JsonSerializer.Serialize(options, JsonSerializeOptions.DefaultOptions)}");
+ Log.Debug("Connect", $"addons: {addons}");
+
+ try
+ {
+ var myURI = GetUri(_deepgramClientOptions);
+ Log.Debug("Connect", $"uri: {myURI}");
+ bool bConnected = await base.Connect(myURI.ToString(), cancelToken, headers);
+ if (!bConnected)
+ {
+ Log.Warning("Connect", "Connect failed");
+ Log.Verbose("AgentWSClient.Connect", "LEAVE");
+ return false;
+ }
+
+ // send the settings configuration
+ var bytes = Encoding.UTF8.GetBytes(options.ToString());
+ await SendMessageImmediately(bytes);
+
+ // keepalive thread
+ if (_deepgramClientOptions.KeepAlive)
+ {
+ Log.Debug("Connect", "Starting KeepAlive Thread...");
+ StartKeepAliveBackgroundThread();
+ }
+
+ Log.Debug("Connect", "Connect Succeeded");
+ Log.Verbose("AgentWSClient.Connect", "LEAVE");
+
+ return true;
+ }
+ catch (TaskCanceledException ex)
+ {
+ Log.Debug("Connect", "Connect cancelled.");
+ Log.Verbose("Connect", $"Connect cancelled. Info: {ex}");
+ Log.Verbose("AgentWSClient.Connect", "LEAVE");
+
+ return false;
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Connect", $"{ex.GetType()} thrown {ex.Message}");
+ Log.Verbose("Connect", $"Exception: {ex}");
+ Log.Verbose("AgentWSClient.Connect", "LEAVE");
+ throw;
+ }
+
+ void StartKeepAliveBackgroundThread() => Task.Run(async () => await ProcessKeepAlive());
+ }
+
+ #region Subscribe Event
+ ///
+ /// Subscribe to an Open event from the Deepgram API
+ ///
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ // Create a new event handler that wraps the original one
+ EventHandler wrappedHandler = (sender, args) =>
+ {
+ // Cast the event arguments to the desired type
+ var castedArgs = new OpenResponse();
+ castedArgs.Copy(args);
+ if (castedArgs != null)
+ {
+ // Invoke the original event handler with the casted arguments
+ eventHandler(sender, castedArgs);
+ }
+ };
+
+ // Pass the new event handler to the base Subscribe method
+ return await base.Subscribe(wrappedHandler);
+ }
+
+ ///
+ /// Subscribe to a Audio event from the Deepgram API
+ ///
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ await _mutexSubscribe.WaitAsync();
+ try
+ {
+ _audioReceived += (sender, e) => eventHandler(sender, e);
+ }
+ finally
+ {
+ _mutexSubscribe.Release();
+ }
+ return true;
+ }
+
+ ///
+ /// Subscribe to a AgentAudioDone event from the Deepgram API
+ ///
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ await _mutexSubscribe.WaitAsync();
+ try
+ {
+ _agentAudioDoneReceived += (sender, e) => eventHandler(sender, e);
+ }
+ finally
+ {
+ _mutexSubscribe.Release();
+ }
+ return true;
+ }
+
+ ///
+ /// Subscribe to a AgentStartedSpeaking event from the Deepgram API
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ await _mutexSubscribe.WaitAsync();
+ try
+ {
+ _agentStartedSpeakingReceived += (sender, e) => eventHandler(sender, e);
+ }
+ finally
+ {
+ _mutexSubscribe.Release();
+ }
+ return true;
+ }
+
+ ///
+ /// Subscribe to an AgentThinking event from the Deepgram API
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ await _mutexSubscribe.WaitAsync();
+ try
+ {
+ _agentThinkingReceived += (sender, e) => eventHandler(sender, e);
+ }
+ finally
+ {
+ _mutexSubscribe.Release();
+ }
+ return true;
+ }
+
+ ///
+ /// Subscribe to a ConversationText event from the Deepgram API
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ await _mutexSubscribe.WaitAsync();
+ try
+ {
+ _conversationTextReceived += (sender, e) => eventHandler(sender, e);
+ }
+ finally
+ {
+ _mutexSubscribe.Release();
+ }
+ return true;
+ }
+
+ ///
+ /// Subscribe to a FunctionCalling event from the Deepgram API
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ await _mutexSubscribe.WaitAsync();
+ try
+ {
+ _functionCallingReceived += (sender, e) => eventHandler(sender, e);
+ }
+ finally
+ {
+ _mutexSubscribe.Release();
+ }
+ return true;
+ }
+
+ ///
+ /// Subscribe to a FunctionCallRequest event from the Deepgram API
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ await _mutexSubscribe.WaitAsync();
+ try
+ {
+ _functionCallRequestReceived += (sender, e) => eventHandler(sender, e);
+ }
+ finally
+ {
+ _mutexSubscribe.Release();
+ }
+ return true;
+ }
+
+ ///
+ /// Subscribe to a UserStartedSpeaking event from the Deepgram API
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ await _mutexSubscribe.WaitAsync();
+ try
+ {
+ _userStartedSpeakingReceived += (sender, e) => eventHandler(sender, e);
+ }
+ finally
+ {
+ _mutexSubscribe.Release();
+ }
+ return true;
+ }
+
+ ///
+ /// Subscribe to a Welcome event from the Deepgram API
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ await _mutexSubscribe.WaitAsync();
+ try
+ {
+ _welcomeReceived += (sender, e) => eventHandler(sender, e);
+ }
+ finally
+ {
+ _mutexSubscribe.Release();
+ }
+ return true;
+ }
+
+ ///
+ /// Subscribe to a SettingsApplied event from the Deepgram API
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ await _mutexSubscribe.WaitAsync();
+ try
+ {
+ _settingsAppliedReceived += (sender, e) => eventHandler(sender, e);
+ }
+ finally
+ {
+ _mutexSubscribe.Release();
+ }
+ return true;
+ }
+
+ ///
+ /// Subscribe to an InjectionRefused event from the Deepgram API
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ await _mutexSubscribe.WaitAsync();
+ try
+ {
+ _injectionRefusedReceived += (sender, e) => eventHandler(sender, e);
+ }
+ finally
+ {
+ _mutexSubscribe.Release();
+ }
+ return true;
+ }
+
+ ///
+ /// Subscribe to a InstructionsUpdated event from the Deepgram API
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ await _mutexSubscribe.WaitAsync();
+ try
+ {
+ _instructionsUpdatedReceived += (sender, e) => eventHandler(sender, e);
+ }
+ finally
+ {
+ _mutexSubscribe.Release();
+ }
+ return true;
+ }
+
+ ///
+ /// Subscribe to a SpeakUpdated event from the Deepgram API
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ await _mutexSubscribe.WaitAsync();
+ try
+ {
+ _speakUpdatedReceived += (sender, e) => eventHandler(sender, e);
+ }
+ finally
+ {
+ _mutexSubscribe.Release();
+ }
+ return true;
+ }
+
+ ///
+ /// Subscribe to an Close event from the Deepgram API
+ ///
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ // Create a new event handler that wraps the original one
+ EventHandler wrappedHandler = (sender, args) =>
+ {
+ // Cast the event arguments to the desired type
+ var castedArgs = new CloseResponse();
+ castedArgs.Copy(args);
+ if (castedArgs != null)
+ {
+ // Invoke the original event handler with the casted arguments
+ eventHandler(sender, castedArgs);
+ }
+ };
+
+ return await base.Subscribe(wrappedHandler);
+ }
+
+ ///
+ /// Subscribe to an Error event from the Deepgram API
+ ///
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ // Create a new event handler that wraps the original one
+ EventHandler wrappedHandler = (sender, args) =>
+ {
+ // Cast the event arguments to the desired type
+ var castedArgs = new ErrorResponse();
+ castedArgs.Copy(args);
+ if (castedArgs != null)
+ {
+ // Invoke the original event handler with the casted arguments
+ eventHandler(sender, castedArgs);
+ }
+ };
+
+ return await base.Subscribe(wrappedHandler);
+ }
+
+ ///
+ /// Subscribe to an Unhandled event from the Deepgram API
+ ///
+ ///
+ /// True if successful
+ public async Task Subscribe(EventHandler eventHandler)
+ {
+ // Create a new event handler that wraps the original one
+ EventHandler wrappedHandler = (sender, args) =>
+ {
+ // Cast the event arguments to the desired type
+ var castedArgs = new UnhandledResponse();
+ castedArgs.Copy(args);
+ if (castedArgs != null)
+ {
+ // Invoke the original event handler with the casted arguments
+ eventHandler(sender, castedArgs);
+ }
+ };
+
+ return await base.Subscribe(wrappedHandler);
+ }
+ #endregion
+
+ #region Send Functions
+ ///
+ /// Sends a KeepAlive message to Deepgram
+ ///
+ public async Task SendKeepAlive()
+ {
+ ControlMessage message = new ControlMessage(AgentClientTypes.KeepAlive);
+ byte[] data = Encoding.ASCII.GetBytes(message.ToString());
+ await SendMessageImmediately(data);
+ }
+ ///
+ /// Sends a Close message to Deepgram
+ ///
+ public override async Task SendClose(bool nullByte = false, CancellationTokenSource? _cancellationToken = null)
+ {
+ if (_clientWebSocket == null || !IsConnected())
+ {
+ Log.Warning("SendClose", "ClientWebSocket is null or not connected. Skipping...");
+ return;
+ }
+
+ // provide a cancellation token, or use the one in the class
+ var _cancelToken = _cancellationToken ?? _cancellationTokenSource;
+
+ Log.Debug("SendClose", "Sending Close Message Immediately...");
+ if (nullByte)
+ {
+ // send a close to Deepgram
+ await _mutexSend.WaitAsync(_cancelToken.Token);
+ try
+ {
+ await _clientWebSocket.SendAsync(new ArraySegment(new byte[1] { 0 }), WebSocketMessageType.Binary, true, _cancellationTokenSource.Token)
+ .ConfigureAwait(false);
+ }
+ finally
+ {
+ _mutexSend.Release();
+ }
+ return;
+ }
+
+ ControlMessage message = new ControlMessage(AgentClientTypes.Close);
+ byte[] data = Encoding.ASCII.GetBytes(message.ToString());
+ await SendMessageImmediately(data);
+ }
+ #endregion
+
+ internal async Task ProcessKeepAlive()
+ {
+ Log.Verbose("AgentWSClient.ProcessKeepAlive", "ENTER");
+
+ try
+ {
+ while (true)
+ {
+ Log.Verbose("ProcessKeepAlive", "Waiting for KeepAlive...");
+ await Task.Delay(5000, _cancellationTokenSource.Token);
+
+ if (_cancellationTokenSource.Token.IsCancellationRequested)
+ {
+ Log.Information("ProcessKeepAlive", "KeepAliveThread cancelled");
+ break;
+ }
+ if (!IsConnected())
+ {
+ Log.Debug("ProcessAutoFlush", "WebSocket is not connected. Exiting...");
+ break;
+ }
+
+ await SendKeepAlive();
+ }
+
+ Log.Verbose("ProcessKeepAlive", "Exit");
+ Log.Verbose("AgentWSClient.ProcessKeepAlive", "LEAVE");
+ }
+ catch (TaskCanceledException ex)
+ {
+ Log.Debug("ProcessKeepAlive", "KeepAliveThread cancelled.");
+ Log.Verbose("ProcessKeepAlive", $"KeepAliveThread cancelled. Info: {ex}");
+ Log.Verbose("AgentWSClient.ProcessKeepAlive", "LEAVE");
+ }
+ catch (Exception ex)
+ {
+ Log.Error("ProcessKeepAlive", $"{ex.GetType()} thrown {ex.Message}");
+ Log.Verbose("ProcessKeepAlive", $"Exception: {ex}");
+ Log.Verbose("AgentWSClient.ProcessKeepAlive", "LEAVE");
+ }
+ }
+
+ internal override void ProcessBinaryMessage(WebSocketReceiveResult result, MemoryStream ms)
+ {
+ try
+ {
+ Log.Debug("ProcessBinaryMessage", "Received WebSocketMessageType.Binary");
+
+ if (_audioReceived == null)
+ {
+ Log.Debug("ProcessBinaryMessage", "_audioReceived has no listeners");
+ Log.Verbose("ProcessBinaryMessage", "LEAVE");
+ return;
+ }
+
+ var audioResponse = new AudioResponse()
+ {
+ Stream = ms
+ };
+
+ Log.Debug("ProcessBinaryMessage", "Invoking AudioResponse");
+ InvokeParallel(_audioReceived, audioResponse);
+ }
+ catch (JsonException ex)
+ {
+ Log.Error("ProcessDataReceived", $"{ex.GetType()} thrown {ex.Message}");
+ Log.Verbose("ProcessDataReceived", $"Exception: {ex}");
+ Log.Verbose("SpeakWSClient.ProcessDataReceived", "LEAVE");
+ }
+ catch (Exception ex)
+ {
+ Log.Error("ProcessDataReceived", $"{ex.GetType()} thrown {ex.Message}");
+ Log.Verbose("ProcessDataReceived", $"Excepton: {ex}");
+ Log.Verbose("SpeakWSClient.ProcessDataReceived", "LEAVE");
+ }
+ }
+
+ internal override void ProcessTextMessage(WebSocketReceiveResult result, MemoryStream ms)
+ {
+ Log.Verbose("AgentWSClient.ProcessTextMessage", "ENTER");
+
+ ms.Seek(0, SeekOrigin.Begin);
+
+ var response = Encoding.UTF8.GetString(ms.ToArray());
+ if (response == null)
+ {
+ Log.Warning("ProcessTextMessage", "Response is null");
+ Log.Verbose("AgentWSClient.ProcessTextMessage", "LEAVE");
+ return;
+ }
+
+ try
+ {
+ Log.Verbose("ProcessTextMessage", $"raw response: {response}");
+ var data = JsonDocument.Parse(response);
+ var val = Enum.Parse(typeof(AgentType), data.RootElement.GetProperty("type").GetString()!);
+
+ Log.Verbose("ProcessTextMessage", $"Type: {val}");
+
+ switch (val)
+ {
+ case AgentType.Open:
+ case AgentType.Close:
+ case AgentType.Error:
+ Log.Debug("ProcessTextMessage", "Calling base.ProcessTextMessage...");
+ base.ProcessTextMessage(result, ms);
+ break;
+ case AgentType.AgentAudioDone:
+ var agentAudioDoneResponse = data.Deserialize();
+ if (_agentAudioDoneReceived == null)
+ {
+ Log.Debug("ProcessTextMessage", "_agentAudioDoneReceived has no listeners");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+ if (agentAudioDoneResponse == null)
+ {
+ Log.Warning("ProcessTextMessage", "AgentAudioDoneResponse is invalid");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+
+ Log.Debug("ProcessTextMessage", $"Invoking AgentAudioDoneResponse. event: {agentAudioDoneResponse}");
+ InvokeParallel(_agentAudioDoneReceived, agentAudioDoneResponse);
+ break;
+ case AgentType.AgentStartedSpeaking:
+ var agentStartedSpeakingResponse = data.Deserialize();
+ if (_agentStartedSpeakingReceived == null)
+ {
+ Log.Debug("ProcessTextMessage", "_agentStartedSpeakingReceived has no listeners");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+ if (agentStartedSpeakingResponse == null)
+ {
+ Log.Warning("ProcessTextMessage", "AgentStartedSpeakingResponse is invalid");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+
+ Log.Debug("ProcessTextMessage", $"Invoking AgentStartedSpeakingResponse. event: {agentStartedSpeakingResponse}");
+ InvokeParallel(_agentStartedSpeakingReceived, agentStartedSpeakingResponse);
+ break;
+ case AgentType.AgentThinking:
+ var agentThinkingResponse = data.Deserialize();
+ if (_agentThinkingReceived == null)
+ {
+ Log.Debug("ProcessTextMessage", "_agentThinkingReceived has no listeners");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+ if (agentThinkingResponse == null)
+ {
+ Log.Warning("ProcessTextMessage", "AgentThinkingResponse is invalid");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+
+ Log.Debug("ProcessTextMessage", $"Invoking AgentThinkingResponse. event: {agentThinkingResponse}");
+ InvokeParallel(_agentThinkingReceived, agentThinkingResponse);
+ break;
+ case AgentType.ConversationText:
+ var conversationTextResponse = data.Deserialize();
+ if (_conversationTextReceived == null)
+ {
+ Log.Debug("ProcessTextMessage", "_conversationTextReceived has no listeners");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+ if (conversationTextResponse == null)
+ {
+ Log.Warning("ProcessTextMessage", "ConversationTextResponse is invalid");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+
+ Log.Debug("ProcessTextMessage", $"Invoking ConversationTextResponse. event: {conversationTextResponse}");
+ InvokeParallel(_conversationTextReceived, conversationTextResponse);
+ break;
+ case AgentType.FunctionCalling:
+ var functionCallingResponse = data.Deserialize();
+ if (_functionCallingReceived == null)
+ {
+ Log.Debug("ProcessTextMessage", "_functionCallingReceived has no listeners");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+ if (functionCallingResponse == null)
+ {
+ Log.Warning("ProcessTextMessage", "FunctionCallingResponse is invalid");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+
+ Log.Debug("ProcessTextMessage", $"Invoking FunctionCallingResponse. event: {functionCallingResponse}");
+ InvokeParallel(_functionCallingReceived, functionCallingResponse);
+ break;
+ case AgentType.FunctionCallRequest:
+ var functionCallRequestResponse = data.Deserialize();
+ if (_functionCallRequestReceived == null)
+ {
+ Log.Debug("ProcessTextMessage", "_functionCallRequestReceived has no listeners");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+ if (functionCallRequestResponse == null)
+ {
+ Log.Warning("ProcessTextMessage", "FunctionCallRequestResponse is invalid");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+
+ Log.Debug("ProcessTextMessage", $"Invoking FunctionCallRequestResponse. event: {functionCallRequestResponse}");
+ InvokeParallel(_functionCallRequestReceived, functionCallRequestResponse);
+ break;
+ case AgentType.UserStartedSpeaking:
+ var userStartedSpeakingResponse = data.Deserialize();
+ if (_userStartedSpeakingReceived == null)
+ {
+ Log.Debug("ProcessTextMessage", "_userStartedSpeakingReceived has no listeners");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+ if (userStartedSpeakingResponse == null)
+ {
+ Log.Warning("ProcessTextMessage", "UserStartedSpeakingResponse is invalid");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+
+ Log.Debug("ProcessTextMessage", $"Invoking UserStartedSpeakingResponse. event: {userStartedSpeakingResponse}");
+ InvokeParallel(_userStartedSpeakingReceived, userStartedSpeakingResponse);
+ break;
+ case AgentType.Welcome:
+ var welcomeResponse = data.Deserialize();
+ if (_welcomeReceived == null)
+ {
+ Log.Debug("ProcessTextMessage", "_welcomeReceived has no listeners");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+ if (welcomeResponse == null)
+ {
+ Log.Warning("ProcessTextMessage", "WelcomeResponse is invalid");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+
+ Log.Debug("ProcessTextMessage", $"Invoking WelcomeResponse. event: {welcomeResponse}");
+ InvokeParallel(_welcomeReceived, welcomeResponse);
+ break;
+ case AgentType.SettingsApplied:
+ var settingsAppliedResponse = data.Deserialize();
+ if (_settingsAppliedReceived == null)
+ {
+ Log.Debug("ProcessTextMessage", "_settingsAppliedReceived has no listeners");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+ if (settingsAppliedResponse == null)
+ {
+ Log.Warning("ProcessTextMessage", "SettingsAppliedResponse is invalid");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+
+ Log.Debug("ProcessTextMessage", $"Invoking SettingsAppliedResponse. event: {settingsAppliedResponse}");
+ InvokeParallel(_settingsAppliedReceived, settingsAppliedResponse);
+ break;
+ case AgentType.InjectionRefused:
+ var injectionRefusedResponse = data.Deserialize();
+ if (_injectionRefusedReceived == null)
+ {
+ Log.Debug("ProcessTextMessage", "_injectionRefusedReceived has no listeners");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+ if (injectionRefusedResponse == null)
+ {
+ Log.Warning("ProcessTextMessage", "InjectionRefusedResponse is invalid");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+
+ Log.Debug("ProcessTextMessage", $"Invoking InjectionRefusedResponse. event: {injectionRefusedResponse}");
+ InvokeParallel(_injectionRefusedReceived, injectionRefusedResponse);
+ break;
+ case AgentType.InstructionsUpdated:
+ var instructionsUpdatedResponse = data.Deserialize();
+ if (_instructionsUpdatedReceived == null)
+ {
+ Log.Debug("ProcessTextMessage", "_instructionsUpdatedReceived has no listeners");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+ if (instructionsUpdatedResponse == null)
+ {
+ Log.Warning("ProcessTextMessage", "InstructionsUpdatedResponse is invalid");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+
+ Log.Debug("ProcessTextMessage", $"Invoking InstructionsUpdatedResponse. event: {instructionsUpdatedResponse}");
+ InvokeParallel(_instructionsUpdatedReceived, instructionsUpdatedResponse);
+ break;
+ case AgentType.SpeakUpdated:
+ var speakUpdatedResponse = data.Deserialize();
+ if (_speakUpdatedReceived == null)
+ {
+ Log.Debug("ProcessTextMessage", "_speakUpdatedReceived has no listeners");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+ if (speakUpdatedResponse == null)
+ {
+ Log.Warning("ProcessTextMessage", "SpeakUpdatedResponse is invalid");
+ Log.Verbose("ProcessTextMessage", "LEAVE");
+ return;
+ }
+
+ Log.Debug("ProcessTextMessage", $"Invoking SpeakUpdatedResponse. event: {speakUpdatedResponse}");
+ InvokeParallel(_speakUpdatedReceived, speakUpdatedResponse);
+ break;
+ default:
+ Log.Debug("ProcessTextMessage", "Calling base.ProcessTextMessage...");
+ base.ProcessTextMessage(result, ms);
+ break;
+ }
+
+ Log.Debug("ProcessTextMessage", "Succeeded");
+ Log.Verbose("AgentWSClient.ProcessTextMessage", "LEAVE");
+ }
+ catch (JsonException ex)
+ {
+ Log.Error("ProcessTextMessage", $"{ex.GetType()} thrown {ex.Message}");
+ Log.Verbose("ProcessTextMessage", $"Exception: {ex}");
+ Log.Verbose("AgentWSClient.ProcessTextMessage", "LEAVE");
+ }
+ catch (Exception ex)
+ {
+ Log.Error("ProcessTextMessage", $"{ex.GetType()} thrown {ex.Message}");
+ Log.Verbose("ProcessTextMessage", $"Exception: {ex}");
+ Log.Verbose("AgentWSClient.ProcessTextMessage", "LEAVE");
+ }
+ }
+
+ #region Helpers
+ ///
+ /// Get the URI for the WebSocket connection
+ ///
+ internal static Uri GetUri(IDeepgramClientOptions options)
+ {
+ var baseAddress = options.BaseAddress;
+
+ // if base address contains "api.deepgram.com", then replace with "agent.deepgram.com"
+ // which is the default URI for the Agent API. This will preserve the wss or ws prefix.
+ // If this is a custom URI, then the we don't need to modify anything because DeepgramWSClientOptions
+ // will attach the protocol to the URI and the URI will be used as is.
+ if (baseAddress.Contains("api.deepgram.com"))
+ {
+ Log.Debug("GetUri", "Replacing baseAddress with agent.deepgram.com");
+ baseAddress = baseAddress.Replace("api.deepgram.com", UriSegments.AGENT_URI);
+ }
+ Log.Debug("GetUri", $"baseAddress: {baseAddress}");
+
+ // if the base address has an v1, v2, etc remove it from the URI
+ Regex regex = new Regex(@"\b(\/v[0-9]+)\b", RegexOptions.IgnoreCase);
+ if (regex.IsMatch(baseAddress))
+ {
+ Log.Information("GetUri", $"BaseAddress contains API version: {baseAddress}");
+ baseAddress = regex.Replace(baseAddress, "");
+ Log.Debug("GetUri", $"BaseAddress: {baseAddress}");
+ }
+
+ return new Uri($"{baseAddress}/{UriSegments.AGENT}");
+ }
+ #endregion
+}
diff --git a/Deepgram/Clients/Agent/v2/Websocket/Constants.cs b/Deepgram/Clients/Agent/v2/Websocket/Constants.cs
new file mode 100644
index 00000000..3af39379
--- /dev/null
+++ b/Deepgram/Clients/Agent/v2/Websocket/Constants.cs
@@ -0,0 +1,15 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Clients.Agent.v2.WebSocket;
+
+///
+/// Headers of interest in the return values from the Deepgram Speak API.
+///
+public static class Constants
+{
+ // Default flush period
+ public const int DefaultFlushPeriodInMs = 500;
+}
+
diff --git a/Deepgram/Clients/Agent/v2/Websocket/ResponseEvent.cs b/Deepgram/Clients/Agent/v2/Websocket/ResponseEvent.cs
new file mode 100644
index 00000000..a09c315c
--- /dev/null
+++ b/Deepgram/Clients/Agent/v2/Websocket/ResponseEvent.cs
@@ -0,0 +1,11 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Clients.Agent.v2.WebSocket;
+
+public class ResponseEvent(T? response) : EventArgs
+{
+ public T? Response { get; set; } = response;
+}
+
diff --git a/Deepgram/Clients/Agent/v2/Websocket/UriSegments.cs b/Deepgram/Clients/Agent/v2/Websocket/UriSegments.cs
new file mode 100644
index 00000000..36ea03ff
--- /dev/null
+++ b/Deepgram/Clients/Agent/v2/Websocket/UriSegments.cs
@@ -0,0 +1,16 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Clients.Agent.v2.WebSocket;
+
+public static class UriSegments
+{
+ // overriding the default uri segments which is typically api.deepgram.com
+ // this is special for agent api for some odd reason
+ public const string AGENT_URI = "agent.deepgram.com";
+
+ //using constants instead of inline value(magic strings) make consistence
+ //across SDK And Test Projects Simpler and Easier to change
+ public const string AGENT = "agent";
+}
diff --git a/Deepgram/Clients/Interfaces/v2/IAgentWebSocketClient.cs b/Deepgram/Clients/Interfaces/v2/IAgentWebSocketClient.cs
new file mode 100644
index 00000000..1c2fdb23
--- /dev/null
+++ b/Deepgram/Clients/Interfaces/v2/IAgentWebSocketClient.cs
@@ -0,0 +1,189 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Models.Agent.v2.WebSocket;
+
+namespace Deepgram.Clients.Interfaces.v2;
+
+///
+/// Implements version 2 of the Agent Client.
+///
+public interface IAgentWebSocketClient
+{
+ #region Connect and Disconnect
+ ///
+ /// Connects to the Deepgram WebSocket API
+ ///
+ public Task Connect(SettingsConfigurationSchema options, CancellationTokenSource? cancelToken = null, Dictionary? addons = null,
+ Dictionary? headers = null);
+
+ ///
+ /// Disconnects from the Deepgram WebSocket API
+ ///
+ public Task Stop(CancellationTokenSource? cancelToken = null, bool nullByte = false);
+ #endregion
+
+ #region Subscribe Event
+ ///
+ /// Subscribe to an Open event from the Deepgram API
+ ///
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler);
+
+ ///
+ /// Subscribe to an Audio Binary event from the Deepgram API
+ ///
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler);
+
+ ///
+ /// Subscribe to a AgentAudioDone event from the Deepgram API
+ ///
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler);
+
+ ///
+ /// Subscribe to a AgentStartedSpeaking event from the Deepgram API
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler)
+;
+ ///
+ /// Subscribe to an AgentThinking event from the Deepgram API
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler);
+
+ ///
+ /// Subscribe to a ConversationText event from the Deepgram API
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler);
+
+ ///
+ /// Subscribe to a FunctionCalling event from the Deepgram API
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler);
+
+ ///
+ /// Subscribe to a FunctionCallRequest event from the Deepgram API
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler);
+
+ ///
+ /// Subscribe to a UserStartedSpeaking event from the Deepgram API
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler);
+
+ ///
+ /// Subscribe to a Welcome event from the Deepgram API
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler);
+
+ ///
+ /// Subscribe to a Close event from the Deepgram API
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler);
+
+ ///
+ /// Subscribe to an Unhandled event from the Deepgram API
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler);
+
+ ///
+ /// Subscribe to an Error event from the Deepgram API
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler);
+
+ ///
+ /// Subscribe to a SettingsApplied event from the Deepgram API
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler);
+
+ ///
+ /// Subscribe to an InjectionRefused event from the Deepgram API
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler);
+
+ ///
+ /// Subscribe to an InstructionsUpdated event from the Deepgram API
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler);
+
+ ///
+ /// Subscribe to a SpeakUpdated event from the Deepgram API
+ ///
+ /// True if successful
+ public Task Subscribe(EventHandler eventHandler);
+ #endregion
+
+ #region Send Functions
+ ///
+ /// Sends a KeepAlive message to Deepgram
+ ///
+ public Task SendKeepAlive();
+
+ ///
+ /// Sends a Close message to Deepgram
+ ///
+ public Task SendClose(bool nullByte = false, CancellationTokenSource? _cancellationToken = null);
+
+ ///
+ /// This method sends a binary message over the WebSocket connection.
+ ///
+ ///
+ /// The number of bytes from the data to send. Use `Constants.UseArrayLengthForSend` to send the entire array.
+ public void SendBinary(byte[] data, int length = Constants.UseArrayLengthForSend);
+
+ ///
+ /// This method sends a text message over the WebSocket connection.
+ ///
+ ///
+ /// The number of bytes from the data to send. Use `Constants.UseArrayLengthForSend` to send the entire array.
+ public void SendMessage(byte[] data, int length = Constants.UseArrayLengthForSend);
+
+ ///
+ /// This method sends a binary message over the WebSocket connection immediately without queueing.
+ ///
+ ///
+ /// The number of bytes from the data to send. Use `Constants.UseArrayLengthForSend` to send the entire array.
+ /// /// Provide a cancel token to be used for the send function or use the internal one
+ public Task SendBinaryImmediately(byte[] data, int length = Constants.UseArrayLengthForSend, CancellationTokenSource? _cancellationToken = null);
+
+ ///
+ /// This method sends a text message over the WebSocket connection immediately without queueing.
+ ///
+ ///
+ /// The number of bytes from the data to send. Use `Constants.UseArrayLengthForSend` to send the entire array.
+ /// /// Provide a cancel token to be used for the send function or use the internal one
+ public Task SendMessageImmediately(byte[] data, int length = Constants.UseArrayLengthForSend, CancellationTokenSource? _cancellationToken = null);
+ #endregion
+
+ #region Helpers
+ ///
+ /// Retrieves the connection state of the WebSocket
+ ///
+ /// Returns the connection state of the WebSocket
+ public WebSocketState State();
+
+ ///
+ /// Indicates whether the WebSocket is connected
+ ///
+ /// Returns true if the WebSocket is connected
+ public bool IsConnected();
+ #endregion
+}
diff --git a/Deepgram/Deepgram.csproj b/Deepgram/Deepgram.csproj
index a4126885..327d216c 100644
--- a/Deepgram/Deepgram.csproj
+++ b/Deepgram/Deepgram.csproj
@@ -83,6 +83,12 @@
+
+
+
+
+
+
diff --git a/Deepgram/Models/Agent/v2/WebSocket/Agent.cs b/Deepgram/Models/Agent/v2/WebSocket/Agent.cs
new file mode 100644
index 00000000..4635b17b
--- /dev/null
+++ b/Deepgram/Models/Agent/v2/WebSocket/Agent.cs
@@ -0,0 +1,28 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Agent.v2.WebSocket;
+
+public record Agent
+{
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("listen")]
+ public Listen Listen { get; set; } = new Listen();
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("think")]
+ public Think Think { get; set; } = new Think();
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("speak")]
+ public Speak Speak { get; set; } = new Speak();
+
+ ///
+ /// Override ToString method to serialize the object
+ ///
+ public override string ToString()
+ {
+ return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+ }
+}
diff --git a/Deepgram/Models/Agent/v2/WebSocket/AgentAudioDoneResponse.cs b/Deepgram/Models/Agent/v2/WebSocket/AgentAudioDoneResponse.cs
new file mode 100644
index 00000000..14f32020
--- /dev/null
+++ b/Deepgram/Models/Agent/v2/WebSocket/AgentAudioDoneResponse.cs
@@ -0,0 +1,24 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Agent.v2.WebSocket;
+
+public record AgentAudioDoneResponse
+{
+ ///
+ /// SettingsConfiguration event type.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("type")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public AgentType? Type { get; } = AgentType.AgentAudioDone;
+
+ ///
+ /// Override ToString method to serialize the object
+ ///
+ public override string ToString()
+ {
+ return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+ }
+}
diff --git a/Deepgram/Models/Agent/v2/WebSocket/AgentStartedSpeakingResponse.cs b/Deepgram/Models/Agent/v2/WebSocket/AgentStartedSpeakingResponse.cs
new file mode 100644
index 00000000..829efe9e
--- /dev/null
+++ b/Deepgram/Models/Agent/v2/WebSocket/AgentStartedSpeakingResponse.cs
@@ -0,0 +1,36 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Agent.v2.WebSocket;
+
+public record AgentStartedSpeakingResponse
+{
+ ///
+ /// SettingsConfiguration event type.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("type")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public AgentType? Type { get; } = AgentType.AgentStartedSpeaking;
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("total_latency")]
+ public decimal? TotalLatency { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("tts_latency")]
+ public decimal? TtsLatency { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("ttt_latency")]
+ public decimal? TttLatency { get; set; }
+
+ ///
+ /// Override ToString method to serialize the object
+ ///
+ public override string ToString()
+ {
+ return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+ }
+}
diff --git a/Deepgram/Models/Agent/v2/WebSocket/AgentThinkingResponse.cs b/Deepgram/Models/Agent/v2/WebSocket/AgentThinkingResponse.cs
new file mode 100644
index 00000000..79edcfc7
--- /dev/null
+++ b/Deepgram/Models/Agent/v2/WebSocket/AgentThinkingResponse.cs
@@ -0,0 +1,28 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Agent.v2.WebSocket;
+
+public record AgentThinkingResponse
+{
+ ///
+ /// SettingsConfiguration event type.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("type")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public AgentType? Type { get; } = AgentType.AgentThinking;
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("content")]
+ public string? Content { get; set; }
+
+ ///
+ /// Override ToString method to serialize the object
+ ///
+ public override string ToString()
+ {
+ return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+ }
+}
diff --git a/Deepgram/Models/Agent/v2/WebSocket/AgentType.cs b/Deepgram/Models/Agent/v2/WebSocket/AgentType.cs
new file mode 100644
index 00000000..1b6496c0
--- /dev/null
+++ b/Deepgram/Models/Agent/v2/WebSocket/AgentType.cs
@@ -0,0 +1,40 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Agent.v2.WebSocket;
+
+using Deepgram.Models.Common.v2.WebSocket;
+
+public enum AgentType
+{
+ Open = WebSocketType.Open,
+ Close = WebSocketType.Close,
+ Unhandled = WebSocketType.Unhandled,
+ Error = WebSocketType.Error,
+ Welcome,
+ ConversationText,
+ UserStartedSpeaking,
+ AgentThinking,
+ FunctionCallRequest,
+ FunctionCalling,
+ AgentStartedSpeaking,
+ AgentAudioDone,
+ Audio,
+ InjectionRefused,
+ SettingsApplied,
+ InstructionsUpdated,
+ SpeakUpdated,
+}
+
+public static class AgentClientTypes
+{
+ // user message types
+ public const string SettingsConfiguration = "SettingsConfiguration";
+ public const string UpdateInstructions = "UpdateInstructions";
+ public const string UpdateSpeak = "UpdateSpeak";
+ public const string InjectAgentMessage = "InjectAgentMessage";
+ public const string FunctionCallResponse = "FunctionCallResponse";
+ public const string KeepAlive = "KeepAlive";
+ public const string Close = "Close";
+}
diff --git a/Deepgram/Models/Agent/v2/WebSocket/Audio.cs b/Deepgram/Models/Agent/v2/WebSocket/Audio.cs
new file mode 100644
index 00000000..f1405cf7
--- /dev/null
+++ b/Deepgram/Models/Agent/v2/WebSocket/Audio.cs
@@ -0,0 +1,25 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Agent.v2.WebSocket;
+
+public record Audio
+{
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("input")]
+ public Input Input { get; set; } = new Input();
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("output")]
+ public Output Output { get; set; } = new Output();
+
+
+ ///
+ /// Override ToString method to serialize the object
+ ///
+ public override string ToString()
+ {
+ return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+ }
+}
diff --git a/Deepgram/Models/Agent/v2/WebSocket/AudioResponse.cs b/Deepgram/Models/Agent/v2/WebSocket/AudioResponse.cs
new file mode 100644
index 00000000..dd7377bb
--- /dev/null
+++ b/Deepgram/Models/Agent/v2/WebSocket/AudioResponse.cs
@@ -0,0 +1,30 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Agent.v2.WebSocket;
+
+public record AudioResponse : IDisposable
+{
+ ///
+ /// The type of speak response, defaults to Audio.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("type")]
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public AgentType? Type { get; set; } = AgentType.Audio;
+
+ ///
+ /// A stream of the audio file
+ ///
+ public MemoryStream? Stream { get; set; }
+
+ // NOTE: There isn't a ToString() function because this will cause an odd Exception to be thrown:
+ // InvalidOperationException: "Timeouts are not supported on this stream."
+
+ public void Dispose()
+ {
+ Stream?.Dispose();
+ Stream = null;
+ }
+}
diff --git a/Deepgram/Models/Agent/v2/WebSocket/CloseResponse.cs b/Deepgram/Models/Agent/v2/WebSocket/CloseResponse.cs
new file mode 100644
index 00000000..3652b178
--- /dev/null
+++ b/Deepgram/Models/Agent/v2/WebSocket/CloseResponse.cs
@@ -0,0 +1,11 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Agent.v2.WebSocket;
+
+using Common = Deepgram.Models.Common.v2.WebSocket;
+
+public record CloseResponse : Common.CloseResponse
+{
+}
diff --git a/Deepgram/Models/Agent/v2/WebSocket/Context.cs b/Deepgram/Models/Agent/v2/WebSocket/Context.cs
new file mode 100644
index 00000000..d1535358
--- /dev/null
+++ b/Deepgram/Models/Agent/v2/WebSocket/Context.cs
@@ -0,0 +1,25 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Agent.v2.WebSocket;
+
+public record Context
+{
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("messages")]
+ public List