Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 6 additions & 26 deletions Google.GenAI/.editorconfig
Original file line number Diff line number Diff line change
@@ -1,36 +1,16 @@
# editorconfig.org

# top-most EditorConfig file
root = true

# Default settings:
# A newline ending every file
# Use 2 spaces as indentation
# Trim trailing whitespace
[*]
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true

[*.cs]
# Rule: Remove unused using directives
# IDE0005: Using directive is unnecessary
# Setting severity to 'suggestion', 'warning', or 'error' will enable the analyzer.
# Code cleanup tools (like in Visual Studio or `dotnet format`) can then remove them.
# 'silent' would still report it for `dotnet format` but not show in IDE as a squiggle.
# 'none' would disable the check entirely.
dotnet_diagnostic.IDE0005.severity = error
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = true
csharp_preferred_using_directive_placement = outside_namespace
indent_size = 2

[*.csproj]
# Indentation
indent_style = space
indent_size = 2

[*.nuspec]
indent_size = 2
# ONLY remove unused imports (IDE0005)
# All other formatting is disabled to avoid "Unmerged change" comments in multi-targeted projects
dotnet_diagnostic.IDE0005.severity = none
dotnet_diagnostic.IDE0055.severity = none



20 changes: 10 additions & 10 deletions Google.GenAI/ApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@
string path,
string requestJson,
HttpOptions? requestHttpOptions,
[EnumeratorCancellation] CancellationToken cancellationToken = default);

Check warning on line 143 in Google.GenAI/ApiClient.cs

View workflow job for this annotation

GitHub Actions / unit-e2e-tests

The EnumeratorCancellationAttribute applied to parameter 'cancellationToken' will have no effect. The attribute is only effective on a parameter of type CancellationToken in an async-iterator method returning IAsyncEnumerable

/// <summary>
/// Returns the library version string.
Expand Down Expand Up @@ -181,7 +181,7 @@
}

var currentHeaders = this.HttpOptions.Headers ?? new Dictionary<string, string>(ImmutableDictionary<string, string>.Empty);
var newHeaders = optionsToApply.Headers ?? new Dictionary<string, string>(ImmutableDictionary<string, string>.Empty);

Check warning on line 184 in Google.GenAI/ApiClient.cs

View workflow job for this annotation

GitHub Actions / unit-e2e-tests

Dereference of a possibly null reference.

var headersBuilder = ImmutableDictionary.CreateBuilder<string, string>(StringComparer.OrdinalIgnoreCase);

Expand Down Expand Up @@ -289,24 +289,24 @@
/// <returns>A <see cref="ValueTask"/> that represents the asynchronous dispose operation.</returns>
public virtual ValueTask DisposeAsync()
{
Dispose();
Dispose();
#if NETSTANDARD2_1
return new ValueTask(Task.CompletedTask);
return new ValueTask(Task.CompletedTask);
#else
return ValueTask.CompletedTask;
#endif
}

private static string GetSdkVersion()
{
// This reads AssemblyInformationalVersionAttribute from the assembly,
// which is generated during build from the <Version> property.
// Google.GenAI.csproj imports ReleaseVersion.xml to set <Version>,
// so this effectively reads the version from ReleaseVersion.xml.
return typeof(ApiClient)
.Assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
?.InformationalVersion ?? "";
// This reads AssemblyInformationalVersionAttribute from the assembly,
// which is generated during build from the <Version> property.
// Google.GenAI.csproj imports ReleaseVersion.xml to set <Version>,
// so this effectively reads the version from ReleaseVersion.xml.
return typeof(ApiClient)
.Assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
?.InformationalVersion ?? "";
}
}
}
24 changes: 12 additions & 12 deletions Google.GenAI/ApiResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,17 @@ public void Dispose()
/// resources; false to release only unmanaged resources.</param>
protected void Dispose(bool disposing)
{
// This atomic check now happens FIRST, guaranteeing it runs only once.
if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
{
return;
}
// This atomic check now happens FIRST, guaranteeing it runs only once.
if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
{
return;
}

if (disposing)
{
// This is the new "hook" for derived classes.
DisposeManagedResources();
}
if (disposing)
{
// This is the new "hook" for derived classes.
DisposeManagedResources();
}
}
protected virtual void DisposeManagedResources() { }

Expand All @@ -74,9 +74,9 @@ protected virtual void DisposeManagedResources() { }
/// <returns>A <see cref="ValueTask"/> that represents the asynchronous dispose operation.</returns>
public virtual ValueTask DisposeAsync()
{
Dispose();
Dispose();
#if NETSTANDARD2_1
return new ValueTask(Task.CompletedTask);
return new ValueTask(Task.CompletedTask);
#else
return ValueTask.CompletedTask;
#endif
Expand Down
57 changes: 33 additions & 24 deletions Google.GenAI/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@

using Google.Apis.Auth.OAuth2;

namespace Google.GenAI {
namespace Google.GenAI
{
/// <summary>
/// Client for making synchronous requests.
/// Using this client to make a request to Gemini Developer API or Vertex AI API.
/// </summary>
public sealed class Client : IDisposable, IAsyncDisposable {
public sealed class Client : IDisposable, IAsyncDisposable
{
private static string? geminiBaseUrl = null;
private static string? vertexBaseUrl = null;
internal readonly ApiClient _apiClient;
Expand Down Expand Up @@ -49,11 +51,15 @@ public sealed class Client : IDisposable, IAsyncDisposable {
/// project/location and API key are set together.</exception>
public Client(bool? vertexAI = null, string? apiKey = null, ICredential? credential = null,
string? project = null, string? location = null,
Types.HttpOptions? httpOptions = null) {
Types.HttpOptions? httpOptions = null)
{
bool resolvedVertexAI;
if (vertexAI.HasValue) {
if (vertexAI.HasValue)
{
resolvedVertexAI = vertexAI.Value;
} else {
}
else
{
string? vertexAIEnv = Environment.GetEnvironmentVariable("GOOGLE_GENAI_USE_VERTEXAI");
resolvedVertexAI = vertexAIEnv != null && vertexAIEnv.ToLower() == "true";
}
Expand All @@ -63,7 +69,8 @@ public Client(bool? vertexAI = null, string? apiKey = null, ICredential? credent
project = project ?? projectEnv;
location = location ?? locationEnv;
apiKey = apiKey ?? apiKeyEnv;
if (project != null || location != null) {
if (project != null || location != null)
{
if (apiKey != null && projectEnv == null && apiKeyEnv == null)
throw new ArgumentException(
"Project/location and API key are mutually exclusive in the client initializer.");
Expand All @@ -86,7 +93,8 @@ public Client(bool? vertexAI = null, string? apiKey = null, ICredential? credent
Models = new Models(_apiClient);
}

static string? inferBaseUrl(bool vertexAI, Types.HttpOptions? httpOptions) {
static string? inferBaseUrl(bool vertexAI, Types.HttpOptions? httpOptions)
{
if (httpOptions?.BaseUrl != null)
return httpOptions.BaseUrl;
if (vertexAI)
Expand All @@ -95,7 +103,8 @@ public Client(bool? vertexAI = null, string? apiKey = null, ICredential? credent
return geminiBaseUrl ?? Environment.GetEnvironmentVariable("GOOGLE_GEMINI_BASE_URL");
}

public static void setDefaultBaseUrl(string? vertexBaseUrl, string? geminiBaseUrl) {
public static void setDefaultBaseUrl(string? vertexBaseUrl, string? geminiBaseUrl)
{
Client.vertexBaseUrl = vertexBaseUrl;
Client.geminiBaseUrl = geminiBaseUrl;
}
Expand All @@ -105,21 +114,21 @@ public static void setDefaultBaseUrl(string? vertexBaseUrl, string? geminiBaseUr
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
Dispose(true);
GC.SuppressFinalize(this);
}

private void Dispose(bool disposing)
{
if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
{
return;
}
if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
{
return;
}

if (disposing)
{
_apiClient.Dispose();
}
if (disposing)
{
_apiClient.Dispose();
}
}

/// <summary>
Expand All @@ -128,12 +137,12 @@ private void Dispose(bool disposing)
/// <returns>A <see cref="ValueTask"/> that represents the asynchronous dispose operation.</returns>
public async ValueTask DisposeAsync()
{
if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
{
return;
}
await _apiClient.DisposeAsync();
GC.SuppressFinalize(this);
if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
{
return;
}
await _apiClient.DisposeAsync();
GC.SuppressFinalize(this);
}
}
}
46 changes: 23 additions & 23 deletions Google.GenAI/HttpApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,32 +165,32 @@ public override async IAsyncEnumerable<ApiResponse> RequestStreamAsync(
private async Task<HttpRequestMessage> CreateHttpRequestAsync(HttpMethod httpMethod, string path,
string requestJson, Types.HttpOptions? requestHttpOptions)
{
bool queryBaseModel = httpMethod.Equals("GET") && path.StartsWith("publishers/google/models");
if (this.VertexAI && !path.StartsWith("projects/") && !queryBaseModel)
{
path = $"projects/{Project}/locations/{Location}/{path}";
}
bool queryBaseModel = httpMethod.Equals("GET") && path.StartsWith("publishers/google/models");
if (this.VertexAI && !path.StartsWith("projects/") && !queryBaseModel)
{
path = $"projects/{Project}/locations/{Location}/{path}";
}

Types.HttpOptions mergedHttpOptions = MergeHttpOptions(requestHttpOptions);
string requestUrl;
if (string.IsNullOrEmpty(mergedHttpOptions.ApiVersion))
{
requestUrl = $"{mergedHttpOptions.BaseUrl}/{path}";
}
else
{
requestUrl = $"{mergedHttpOptions.BaseUrl}/{mergedHttpOptions.ApiVersion}/{path}";
}
Types.HttpOptions mergedHttpOptions = MergeHttpOptions(requestHttpOptions);
string requestUrl;
if (string.IsNullOrEmpty(mergedHttpOptions.ApiVersion))
{
requestUrl = $"{mergedHttpOptions.BaseUrl}/{path}";
}
else
{
requestUrl = $"{mergedHttpOptions.BaseUrl}/{mergedHttpOptions.ApiVersion}/{path}";
}

var request = new HttpRequestMessage(httpMethod, requestUrl);
await SetHeadersAsync(request, mergedHttpOptions);
var request = new HttpRequestMessage(httpMethod, requestUrl);
await SetHeadersAsync(request, mergedHttpOptions);

if (httpMethod.Method.Equals("POST", StringComparison.OrdinalIgnoreCase) ||
httpMethod.Method.Equals("PATCH", StringComparison.OrdinalIgnoreCase))
{
request.Content = new StringContent(requestJson, System.Text.Encoding.UTF8, "application/json");
}
return request;
if (httpMethod.Method.Equals("POST", StringComparison.OrdinalIgnoreCase) ||
httpMethod.Method.Equals("PATCH", StringComparison.OrdinalIgnoreCase))
{
request.Content = new StringContent(requestJson, System.Text.Encoding.UTF8, "application/json");
}
return request;
}

private static async Task ThrowFromErrorResponse(HttpResponseMessage response)
Expand Down
2 changes: 1 addition & 1 deletion Google.GenAI/HttpApiResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public override HttpResponseHeaders GetHeaders()
/// </summary>
protected override void DisposeManagedResources()
{
_response?.Dispose();
_response?.Dispose();
}
}
}
10 changes: 5 additions & 5 deletions Google.GenAI/Live.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,20 @@ private Uri GetServerUri()
var baseUri = new Uri(baseUrl);
var uriBuilder = new UriBuilder(baseUri)
{
Scheme = baseUri.Scheme == "http" ? "ws" : "wss"
Scheme = baseUri.Scheme == "http" ? "ws" : "wss"
};

string wsBaseUrl = uriBuilder.Uri.ToString().TrimEnd('/');

if (_apiClient.VertexAI)
{
string apiVersion = _apiClient.HttpOptions?.ApiVersion ?? "v1beta1";
return new Uri($"{wsBaseUrl}/ws/google.cloud.aiplatform.{apiVersion}.LlmBidiService/BidiGenerateContent");
string apiVersion = _apiClient.HttpOptions?.ApiVersion ?? "v1beta1";
return new Uri($"{wsBaseUrl}/ws/google.cloud.aiplatform.{apiVersion}.LlmBidiService/BidiGenerateContent");
}
else
{
string apiVersion = _apiClient.HttpOptions?.ApiVersion ?? "v1beta";
return new Uri($"{wsBaseUrl}/ws/google.ai.generativelanguage.{apiVersion}.GenerativeService.BidiGenerateContent");
string apiVersion = _apiClient.HttpOptions?.ApiVersion ?? "v1beta";
return new Uri($"{wsBaseUrl}/ws/google.ai.generativelanguage.{apiVersion}.GenerativeService.BidiGenerateContent");
}
}
catch (UriFormatException e)
Expand Down
Loading
Loading