Skip to content

Conversation

@ladenedge
Copy link
Contributor

@ladenedge ladenedge commented Oct 19, 2025

This change tries to make the disconnection handler a little more forgiving of different input combinations. It addresses #81. Changes are:

  1. I removed the "invalid payload" error handler -- I don't think we want to be in the business of making explicit handlers for every status type/error condition. Easier to just report the property values and let the user interpret them.
  2. The close status/description is always reported if it is present.
  3. The exception is always handled if it is present. (And the ErrorOccurred handler is invoked in this case.)
  4. The Disconnected handler is always called -- it only depends what its arguments are. Personally I would recommend refactoring this to simply pass info out to the client so you can provide a strongly-typed EventArgs, but I didn't want to break compatibility and this gets the job done. (Also, the user can always add her own DisconnectionHappened handler if you decide to merge Offer a new event 'ClientCreated' that is invoked before a websocket connection is attempted #80.)

With this patch you still get the following in the normal case:

MultiModalLiveClient: WebSocket connection closed normally.

But when a non-normal status comes in, you get something like this:

MultiModalLiveClient: WebSocket connection closed with status "PolicyViolation": API keys are not supported by this API.

Very helpful, I think!

(PS. This is my last bug/PR for now. Thanks a lot for your attention, and for the great library! 🫶 )

Summary by CodeRabbit

  • Bug Fixes
    • Improved error messages and logging when WebSocket connections disconnect, providing clearer status information about the reason for disconnection.
    • Enhanced error reporting during connection failures to better distinguish between different closure scenarios.

@coderabbitai
Copy link

coderabbitai bot commented Oct 19, 2025

Walkthrough

The PR replaces the logging method LogConnectionClosedWithInvalidPyload with LogConnectionClosedWithStatus, which now accepts an optional WebSocket close status parameter. The MultiModalLiveClient's WebSocket disconnect handling is refactored to preserve and surface close-status descriptions through the new logging method and conditional error event propagation.

Changes

Cohort / File(s) Summary
Logging method replacement
src/GenerativeAI.Live/Logging/LoggingExtensions.cs
Replaces LogConnectionClosedWithInvalidPyload (EventId 113) with LogConnectionClosedWithStatus (EventId 114). New method accepts nullable WebSocketCloseStatus and string closeStatusDescription parameters. Updates log message to include status code and description; refreshes XML documentation.
Disconnect handling refactor
src/GenerativeAI.Live/Models/MultiModalLiveClient.cs
Reworks WebSocket disconnect path to replace condition checks on DisconnectionType.Error and specific CloseStatus values with null-check-based flow on info.CloseStatusDescription. Logs via new LogConnectionClosedWithStatus when description exists; raises ErrorMessageEventArgs conditionally based on status description presence; preserves existing error logging and exception handling.

Sequence Diagram

sequenceDiagram
    participant WS as WebSocket
    participant Client as MultiModalLiveClient
    participant Logger as Logger
    participant Event as Event Handler
    
    rect rgb(240, 248, 255)
    note over WS,Event: New Flow: Close with Status Description
    WS->>Client: OnWebSocketClosed (with status description)
    Client->>Logger: LogConnectionClosedWithStatus(closeStatus, description)
    Logger->>Logger: Log with EventId 114
    Client->>Event: Raise Disconnected(ErrorMessageEventArgs)
    end
    
    rect rgb(255, 240, 240)
    note over WS,Event: New Flow: Close without Description
    WS->>Client: OnWebSocketClosed (no status description)
    Client->>Logger: Log generic closure
    Client->>Event: Raise Disconnected(EventArgs.Empty)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

The changes involve understanding the replacement of one logging method with an enhanced version and tracing through conditional disconnect-handling logic. The refactoring introduces a clearer null-check-based flow, but requires careful verification that the old and new event-propagation paths are logically equivalent and properly handle all cases.

Possibly related issues

Possibly related PRs

Poem

🐰 Hop, hop, with clearer logs to trace,
Status codes now show their face,
Error descriptions, no longer lost,
The disconnect path, refined across the frost!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Refactored DisconnectionHappened handler to always report errors" accurately identifies the core change in the changeset—a refactoring of the disconnection handling logic in the MultiModalLiveClient combined with updates to the logging extension methods. The title captures the main theme of ensuring consistent error and status reporting. However, the title is somewhat narrow in its characterization of what's being reported: the PR objectives indicate the changes involve reporting not only errors/exceptions but also close status and description information for user interpretation. The term "errors" could be considered slightly misleading since the PR equally emphasizes reporting close status details regardless of whether they represent error conditions.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/GenerativeAI.Live/Logging/LoggingExtensions.cs (1)

32-40: EventId collision confirmed: fix duplicate EventId 102 by assigning unique IDs.

Verified that EventId 102 is duplicated in both LogConnectionClosedWithError and LogConnectionClosed methods. Proposed replacements (EventId 115 for LogConnectionClosed and 116 for LogConnectionClosedWithError) are both available and will not create new conflicts.

-[LoggerMessage(EventId = 102, Level = LogLevel.Error, Message = "WebSocket connection closed with error: {ErrorType}")]
+[LoggerMessage(EventId = 116, Level = LogLevel.Error, Message = "WebSocket connection closed with error: {ErrorType}")]
 public static partial void LogConnectionClosedWithError(this ILogger logger, DisconnectionType errorType, Exception exception);
 
-[LoggerMessage(EventId = 102, Level = LogLevel.Information, Message = "WebSocket connection closed normally.")]
+[LoggerMessage(EventId = 115, Level = LogLevel.Information, Message = "WebSocket connection closed normally.")]
 public static partial void LogConnectionClosed(this ILogger logger);
🧹 Nitpick comments (1)
src/GenerativeAI.Live/Logging/LoggingExtensions.cs (1)

126-133: Don’t hard-wire Error level for all status closures; add an Info variant and let caller choose.

NormalClosure with a description is not an error. Provide an Info-level overload and use Error-level only for non-normal statuses.

 /// <summary>
-/// Logs an error message indicating that the WebSocket connection was closed with a status code and description.
+/// Logs a message indicating that the WebSocket connection was closed with a status code and description.
 /// </summary>
 /// <param name="logger">The logger to log the message to.</param>
 /// <param name="closeStatus">Indicates the reason why the remote endpoint initiated the close handshake.</param>
 /// <param name="closeStatusDescription">The description of the close status that caused the connection to close.</param>
-[LoggerMessage(EventId = 114, Level = LogLevel.Error, Message = "WebSocket connection closed with status {CloseStatus}: {CloseStatusDescription}")]
-public static partial void LogConnectionClosedWithStatus(this ILogger logger, WebSocketCloseStatus? closeStatus, string closeStatusDescription);
+[LoggerMessage(EventId = 113, Level = LogLevel.Information, Message = "WebSocket connection closed with status {CloseStatus}: {CloseStatusDescription}")]
+public static partial void LogConnectionClosedWithStatusInfo(this ILogger logger, WebSocketCloseStatus? closeStatus, string closeStatusDescription);
+
+[LoggerMessage(EventId = 114, Level = LogLevel.Error, Message = "WebSocket connection closed with status {CloseStatus}: {CloseStatusDescription}")]
+public static partial void LogConnectionClosedWithStatus(this ILogger logger, WebSocketCloseStatus? closeStatus, string closeStatusDescription);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c2bbf3 and cd92cdd.

📒 Files selected for processing (2)
  • src/GenerativeAI.Live/Logging/LoggingExtensions.cs (1 hunks)
  • src/GenerativeAI.Live/Models/MultiModalLiveClient.cs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/GenerativeAI.Live/Models/MultiModalLiveClient.cs (1)
src/GenerativeAI.Live/Events/ErrorMessageEventArgs.cs (2)
  • ErrorMessageEventArgs (6-20)
  • ErrorMessageEventArgs (16-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build (9.0.x)

Comment on lines +554 to 573
if (info.Exception is not null)
{
_logger?.LogConnectionClosedWithError(info.Type, info.Exception!);
ErrorOccurred?.Invoke(this, new ErrorEventArgs(info.Exception!));
ErrorOccurred?.Invoke(this, new ErrorEventArgs(info.Exception!));
}
else if (info.CloseStatus == WebSocketCloseStatus.InvalidPayloadData)
{
//log info.CloseStatusDescription
_logger?.LogConnectionClosedWithInvalidPyload(info.CloseStatusDescription!);
}
else if (info.CloseStatus == WebSocketCloseStatus.InternalServerError && !string.IsNullOrEmpty(info.CloseStatusDescription))
{
_logger?.LogConnectionClosedWithError(info.Type, info.Exception!);
Disconnected?.Invoke(this, new ErrorMessageEventArgs(info.CloseStatusDescription));

if (!string.IsNullOrEmpty(info.CloseStatusDescription))
{
_logger?.LogConnectionClosedWithStatus(info.CloseStatus, info.CloseStatusDescription);
}
else
{
_logger?.LogConnectionClosed();
Disconnected?.Invoke(this, EventArgs.Empty);
}

var eventArgs = !string.IsNullOrEmpty(info.CloseStatusDescription) ?
new ErrorMessageEventArgs(info.CloseStatusDescription) :
EventArgs.Empty;
Disconnected?.Invoke(this, eventArgs);
});
Copy link

@coderabbitai coderabbitai bot Oct 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Normal closure with a description is logged as Error; also skip-status when description is null.

  • Any NormalClosure that includes a description (e.g., client Stop("Client Disconnecting")) will be logged via LogConnectionClosedWithStatus at Error level. This skews telemetry for expected disconnects.
  • If a status exists but CloseStatusDescription is null/empty, you fall back to LogConnectionClosed and lose the status detail, contrary to the PR goal of “always report status/description when present.”
  • Consider emitting Disconnected args that include both status and description (string) for now.

Proposed fix (choose severity by status, always include status when available, and enrich Disconnected args):

 _client.DisconnectionHappened.Subscribe(info =>
 {
     if (info.Exception is not null)
     {
         _logger?.LogConnectionClosedWithError(info.Type, info.Exception!);
         ErrorOccurred?.Invoke(this, new ErrorEventArgs(info.Exception!));
     }
-
-    if (!string.IsNullOrEmpty(info.CloseStatusDescription))
-    { 
-        _logger?.LogConnectionClosedWithStatus(info.CloseStatus, info.CloseStatusDescription);
-    }
-    else
-    {
-        _logger?.LogConnectionClosed();
-    }
-
-    var eventArgs = !string.IsNullOrEmpty(info.CloseStatusDescription) ?
-        new ErrorMessageEventArgs(info.CloseStatusDescription) :
-        EventArgs.Empty;
-    Disconnected?.Invoke(this, eventArgs);
+    var hasStatus = info.CloseStatus.HasValue;
+    var hasDescription = !string.IsNullOrEmpty(info.CloseStatusDescription);
+    var description = hasDescription ? info.CloseStatusDescription! : "(no description)";
+
+    if (hasStatus || hasDescription)
+    {
+        if (info.CloseStatus == WebSocketCloseStatus.NormalClosure)
+            _logger?.LogConnectionClosedWithStatusInfo(info.CloseStatus, description);
+        else
+            _logger?.LogConnectionClosedWithStatus(info.CloseStatus, description);
+    }
+    else
+    {
+        _logger?.LogConnectionClosed();
+    }
+
+    var disconnectedArgs = (hasStatus || hasDescription)
+        ? new ErrorMessageEventArgs(hasStatus ? $"{info.CloseStatus}: {description}" : description)
+        : EventArgs.Empty;
+    Disconnected?.Invoke(this, disconnectedArgs);
 });

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/GenerativeAI.Live/Models/MultiModalLiveClient.cs around lines 554 to 573,
the current logic logs NormalClosure with a description as an error, drops
CloseStatus when Description is null/empty (calling LogConnectionClosed), and
raises Disconnected with only a description; change it to (1) map log level to
severity based on CloseStatus (treat NormalClosure as Information, others as
Error/Warning as appropriate) and call LogConnectionClosedWithStatus whenever
CloseStatus is present regardless of description, (2) avoid using
LogConnectionClosedWithError for expected NormalClosure with description, and
(3) create and raise a richer Disconnected event args type that includes both
CloseStatus (nullable) and CloseStatusDescription (string) and pass that instead
of EventArgs.Empty so consumers always get status+description when available.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not how my tests behave, but it's probably a good code review. I'll see if I can adjust the handler a bit to account for these comments.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the LogConnectionClosedWithStatus method definition to verify log level
cat src/GenerativeAI.Live/Logging/LoggingExtensions.cs

Length of output: 8067


@ladenedge Thank you for taking another look at this!

I've confirmed that LogConnectionClosedWithStatus is indeed defined with LogLevel.Error (EventId 114) in the LoggingExtensions.cs file, so when CloseStatusDescription is present, it will log as an error regardless of whether it's a NormalClosure or not. The fallback LogConnectionClosed() method correctly uses LogLevel.Information.

Your tests might not be hitting the path where a description is present with NormalClosure, which would explain the different behavior you're seeing.

If you'd like, you could consider:

  1. Adding a separate logging method like LogConnectionClosedWithStatusInfo at Information level for normal closures
  2. Or parameterizing the log level based on the CloseStatus value within the handler

Either way, I appreciate your openness to the feedback! Let me know if you'd like any help thinking through the adjustments. 🐰


🧬 Code Graph Analysis Results

<src/GenerativeAI.Live/Extensions/WebSocketClientExtensions.cs> (lines 17-26)

public static IWebsocketClient WithReconnect(this ClientWebSocket webSocketClient, string url)
{
    var client = new WebsocketClient(new Uri(url), () => webSocketClient)
    {
        IsReconnectionEnabled = true,
        ReconnectTimeout = TimeSpan.FromSeconds(30)
    };

    return client;
}

<src/GenerativeAI.Live/Events/MessageReceivedEventArgs.cs> (lines 8-23)

public class MessageReceivedEventArgs : EventArgs
{
    /// <summary>
    /// Gets the payload of the received message.
    /// </summary>
    public BidiResponsePayload Payload { get; }

    /// <summary>
    /// Initializes a new instance of the MessageReceivedEventArgs class.
    /// </summary>
    /// <param name="payload">The payload of the received message.</param>
    public MessageReceivedEventArgs(BidiResponsePayload payload)
    {
        Payload = payload;
    }
}

<src/GenerativeAI.Live/Events/AudioBufferReceivedEventArgs.cs> (lines 8-40)

public class AudioBufferReceivedEventArgs : EventArgs
{
    /// <summary>
    /// Gets or sets the audio data buffer.
    /// </summary>
    public byte[] Buffer { get; set; }

    /// <summary>
    /// Gets or sets the header information for the audio data.
    /// </summary>
    public AudioHeaderInfo HeaderInfo { get; set; }
    
    /// <summary>
    /// Gets or sets the transcription of the input audio.
    /// </summary>
    public Transcription? InputTranscription { get; set; }
    
    /// <summary>
    /// Gets or sets the transcription of the output audio.
    /// </summary>
    public Transcription? OutputTranscription { get; set; }

    /// <summary>
    /// Initializes a new instance of the AudioBufferReceivedEventArgs class.
    /// </summary>
    /// <param name="buffer">The audio buffer data.</param>
    /// <param name="audioHeaderInfo">The audio header information.</param>
    public AudioBufferReceivedEventArgs(byte[] buffer, AudioHeaderInfo audioHeaderInfo)
    {
        this.Buffer = buffer;
        HeaderInfo = audioHeaderInfo;
    }
}

<src/GenerativeAI.Live/Events/ErrorMessageEventArgs.cs> (lines 6-20)

public class ErrorMessageEventArgs : EventArgs
{
    /// <summary>
    /// Gets the payload of the received message.
    /// </summary>
    public string ErrorMessage { get; }

    /// <summary>
    /// Initializes a new instance of the class.
    /// </summary>
    public ErrorMessageEventArgs(string errorMessage)
    {
        ErrorMessage = errorMessage;
    }
}

<src/GenerativeAI.Types/MultimodalLive/BidiClientPayload.cs> (lines 10-35)

public class BidiClientPayload
{
    /// <summary>
    /// Gets or sets the setup message for a bidirectional content generation session.
    /// </summary>
    [JsonPropertyName("setup")]
    public BidiGenerateContentSetup? Setup { get; set; }

    /// <summary>
    /// Gets or sets an incremental update of the current conversation delivered from the client.
    /// </summary>
    [JsonPropertyName("clientContent")]
    public BidiGenerateContentClientContent? ClientContent { get; set; }

    /// <summary>
    /// Gets or sets user input that is sent in real time.
    /// </summary>
    [JsonPropertyName("realtimeInput")]
    public BidiGenerateContentRealtimeInput? RealtimeInput { get; set; }

    /// <summary>
    /// Gets or sets a client generated response to a <see cref="FunctionCall"/> received from the server.
    /// </summary>
    [JsonPropertyName("toolResponse")]
    public BidiGenerateContentToolResponse? ToolResponse { get; set; }
}

<src/GenerativeAI.Types/MultimodalLive/BidiGenerateContentSetup.cs> (lines 10-76)

public class BidiGenerateContentSetup
{
    /// <summary>
    /// Required. The model's resource name. This serves as an ID for the Model to use.
    /// Format: <c>models/{model}</c>
    /// </summary>
    [JsonPropertyName("model")]
    public string? Model { get; set; }

    /// <summary>
    /// Generation config.
    /// The following fields are not supported:
    /// - <c>responseLogprobs</c>
    /// - <c>responseMimeType</c>
    /// - <c>logprobs</c>
    /// - <c>responseSchema</c>
    /// - <c>stopSequence</c>
    /// - <c>routingConfig</c>
    /// - <c>audioTimestamp</c>
    /// </summary>
    [JsonPropertyName("generationConfig")]
    public GenerationConfig? GenerationConfig { get; set; }

    /// <summary>
    /// The user provided system instructions for the model.
    /// Note: Only text should be used in parts. Content in each part will be in a separate paragraph.
    /// </summary>
    [JsonPropertyName("systemInstruction")]
    public Content? SystemInstruction { get; set; }

    /// <summary>
    /// A list of <see cref="Tool"/> the model may use to generate the next response.
    /// A <see cref="Tool"/> is a piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model.
    /// </summary>
    [JsonPropertyName("tools")]
    public Tool[]? Tools { get; set; }

    /// <summary>
    /// Configures output audio transcription settings.
    /// </summary>
    [JsonPropertyName("outputAudioTranscription")]
    public AudioTranscriptionConfig? OutputAudioTranscription { get; set; }

    /// <summary>
    /// Configures input audio transcription settings.
    /// </summary>
    [JsonPropertyName("inputAudioTranscription")]
    public AudioTranscriptionConfig? InputAudioTranscription { get; set; }
    /// <summary>
    /// Configures context window compression mechanism. If included, server will compress context window to fit into given length.
    /// </summary>
    [JsonPropertyName("contextWindowCompression")]
    public ContextWindowCompressionConfig? ContextWindowCompression { get; set; }
    
    /// <summary>
    /// Configures the proactivity of the model. This allows the model to respond proactively to the input and to ignore irrelevant input.
    /// </summary>
    [JsonPropertyName("proactivity")]
    public ProactivityConfig? Proactivity { get; set; }

    /// <summary>
    /// Configures session resumption mechanism. If included server will send SessionResumptionUpdate messages.
    /// </summary>
    [JsonPropertyName("sessionResumption")]
    public SessionResumptionConfig? SessionResumption { get; set; }

}

<src/GenerativeAI.Types/MultimodalLive/BidiGenerateContentToolCall.cs> (lines 9-16)

public class BidiGenerateContentToolCall
{
    /// <summary>
    /// Output only. The function call to be executed.
    /// </summary>
    [JsonPropertyName("functionCalls")]
    public FunctionCall[]? FunctionCalls { get; set; }
}

<src/GenerativeAI.Types/MultimodalLive/BidiGenerateContentToolResponse.cs> (lines 10-19)

public class 
    
    BidiGenerateContentToolResponse
{
    /// <summary>
    /// The response to the function calls.
    /// </summary>
    [JsonPropertyName("functionResponses")]
    public FunctionResponse[]? FunctionResponses { get; set; }
}

<src/GenerativeAI.Types/MultimodalLive/BidiGenerateContentRealtimeInput.cs> (lines 15-22)

public class BidiGenerateContentRealtimeInput
{
    /// <summary>
    /// Inlined bytes data for media input.
    /// </summary>
    [JsonPropertyName("mediaChunks")]
    public Blob[]? MediaChunks { get; set; }
}

<src/GenerativeAI.Types/ContentGeneration/Tools/Tool.cs> (lines 11-49)

public class Tool
{
    /// <summary>
    /// Optional. A list of FunctionDeclarations available to the model that can be used for function calling.
    /// The model or system does not execute the function. Instead the defined function may be returned
    /// as a <see cref="Part.FunctionCall"/> with arguments to the client side for execution.
    /// The model may decide to call a subset of these functions by populating
    /// <see cref="Part.FunctionCall"/> in the response. The next conversation turn may contain
    /// a <see cref="Part.FunctionResponse"/> with the <see cref="Content.Role"/> "function"
    /// generation context for the next model turn.
    /// </summary>
    [JsonPropertyName("functionDeclarations")]
    public List<FunctionDeclaration>? FunctionDeclarations { get; set; }

    /// <summary>
    /// Optional. Retrieval tool that is powered by Google search.
    /// </summary>
    [JsonPropertyName("googleSearchRetrieval")]
    public GoogleSearchRetrievalTool? GoogleSearchRetrieval { get; set; }

    /// <summary>
    /// Optional. Enables the model to execute code as part of generation.
    /// </summary>
    [JsonPropertyName("codeExecution")]
    public CodeExecutionTool? CodeExecution { get; set; }

    /// <summary>
    /// Optional. GoogleSearch tool type. Tool to support Google Search in Model. Powered by Google.
    /// </summary>
    [JsonPropertyName("googleSearch")]
    public GoogleSearchTool? GoogleSearch { get; set; }
    
    /// <summary>
    /// Optional. Retrieval tool type. System will always execute the provided retrieval tool(s) to get external knowledge to answer the prompt. Retrieval results are presented to the model for generation.
    /// </summary>
    [JsonPropertyName("retrieval")]
    public VertexRetrievalTool? Retrieval { get; set; }
    
}

<src/GenerativeAI.Live/Events/TextChunkReceivedArgs.cs> (lines 9-31)

public class TextChunkReceivedArgs : EventArgs
{
    /// <summary>
    /// Gets or sets the text of the received chunk.
    /// </summary>
    public string Text { get; set; }

    /// <summary>
    /// Gets or sets a value indicating whether the turn is finished.
    /// </summary>
    public bool IsTurnFinish { get; set; }

    /// <summary>
    /// Initializes a new instance of the TextChunkReceivedArgs class.
    /// </summary>
    /// <param name="text">The text of the received chunk.</param>
    /// <param name="isTurnFinish">A value indicating whether the turn is finished.</param>
    public TextChunkReceivedArgs(string text, bool isTurnFinish)
    {
        this.Text = text;
        this.IsTurnFinish = isTurnFinish;
    }
}

<src/GenerativeAI.Live/Helper/AudioHelper.cs> (lines 9-130)

public static class AudioHelper
{
    /// <summary>
    /// Adds a WAV file header to the given raw audio data.
    /// </summary>
    public static byte[] AddWaveHeader(byte[] audioData, int numberOfChannels, int sampleRate, int bitsPerSample2)
    {
#if NET6_0_OR_GREATER
        ArgumentNullException.ThrowIfNull(audioData);
#else
        if (audioData == null)
            throw new ArgumentNullException(nameof(audioData));
#endif
        // ... constructs WAV header and returns combined buffer ...
    }

    /// <summary>
    /// Validates whether the given byte array contains a valid WAV file header.
    /// </summary>
    public static bool IsValidWaveHeader(byte[] buffer)
    {
        if (buffer == null || buffer.Length < 44) // Minimum WAV header size
        {
            return false;
        }

        using (var stream = new MemoryStream(buffer))
        using (var reader = new BinaryReader(stream))
        {
            try
            {
                // RIFF, fmt, data checks...
                // returns true/false accordingly
            }
            catch (Exception)
            {
                return false;
            }
        }
    }
}

<src/GenerativeAI/Constants/Roles.cs> (lines 6-28)

public static class Roles
{
    /// <summary>
    /// Represents the role of a user interacting with the system.
    /// </summary>
    public const string User = "user";

    /// <summary>
    /// Represents the role assigned to the AI model in the system.
    /// </summary>

    public const string Model = "model";

    /// <summary>
    /// Represents the role for functions invoked during the system's operation.
    /// </summary>
    public const string Function = "function";

    /// <summary>
    /// Represents the system's internal role for handling instructions or operations.
    /// </summary>
    public const string System = "system";
}

<src/GenerativeAI/Types/MultimodalLive/BidiGenerateContentServerContent.cs> (lines 63-76)

public class Transcription
{
    /// <summary>
    /// The bool indicates the end of the transcription.
    /// </summary>
    [JsonPropertyName("finished")]
    public bool? Finished { get; set; }

    /// <summary>
    /// Transcription text.
    /// </summary>
    [JsonPropertyName("text")]
    public string? Text { get; set; }
}

<src/GenerativeAI/Types/MultimodalLive/LiveServerSessionResumptionUpdate.cs> (lines 8-29)

public class LiveServerSessionResumptionUpdate
{
    /// <summary>
    /// A token that can be used by the client to resume the session.
    /// This token encapsulates the state of the session on the server side.
    /// Optional: This field might be present if the server successfully captured a resumption point.
    /// </summary>
    [JsonPropertyName("resumptionToken")]
    public string? ResumptionToken { get; set; }

    /// <summary>
    /// Optional. A message from the server regarding the session resumption status (e.g., success, error, pending).
    /// </summary>
    [JsonPropertyName("message")]
    public string? Message { get; set; }

    /// <summary>
    /// Optional. Indicates the status of the session resumption process.
    /// </summary>
    [JsonPropertyName("status")]
    public SessionResumptionStatus? Status { get; set; }
}

<src/GenerativeAI/Types/MultimodalLive/BidiGenerateContentServerContent.cs> (not listed in snippets but contextually related)

@gunpal5 gunpal5 merged commit 67fb929 into gunpal5:main Oct 20, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants