From 1883d19d583bcb5af9edfe5a338340c3c1ca55c6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 17:46:11 +0000 Subject: [PATCH 1/8] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 391cde3..57aa7a5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 8 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/stagehand-6f6bfb81d092f30a5e2005328c97d61b9ea36132bb19e9e79e55294b9534ce20.yml -openapi_spec_hash: f3fc1e3688a38dc2c28f7178f7d534e5 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/stagehand-e629569417ad17cad5c73180109b4c3ae778f38063fc72146fa82f82de145911.yml +openapi_spec_hash: 42e4eedbc0fcc772bb271191a067bce1 config_hash: 1fb12ae9b478488bc1e56bfbdc210b01 From 8b3d6b04a2bb9883e434c90e263e723aec3c12b2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 18:23:02 +0000 Subject: [PATCH 2/8] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 57aa7a5..094ac49 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 8 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/stagehand-e629569417ad17cad5c73180109b4c3ae778f38063fc72146fa82f82de145911.yml -openapi_spec_hash: 42e4eedbc0fcc772bb271191a067bce1 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/stagehand-49b40c7425adba9e67fc102838c5216c45ca1f7ef4c10823c5665fd413538504.yml +openapi_spec_hash: 6880dc029df2e88dfe8943c0dec5a3a5 config_hash: 1fb12ae9b478488bc1e56bfbdc210b01 From 37f80f3936fc9a85e6e6079381c41ef4e3793472 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 18:25:46 +0000 Subject: [PATCH 3/8] feat: [feat]: add `ignoreSelectors` to `observe()` --- .stats.yml | 4 +-- .../Sessions/SessionObserveParamsTest.cs | 30 +++++++++++++++++++ .../Models/Sessions/SessionObserveParams.cs | 26 ++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 094ac49..0339c57 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 8 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/stagehand-49b40c7425adba9e67fc102838c5216c45ca1f7ef4c10823c5665fd413538504.yml -openapi_spec_hash: 6880dc029df2e88dfe8943c0dec5a3a5 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/stagehand-eae8400fade7b2c8329c4148f56de92e147c34c0feecb420c015aab6544a9acc.yml +openapi_spec_hash: 0a9eff1ac1d464e89cbd9db64709b08a config_hash: 1fb12ae9b478488bc1e56bfbdc210b01 diff --git a/src/Stagehand.Tests/Models/Sessions/SessionObserveParamsTest.cs b/src/Stagehand.Tests/Models/Sessions/SessionObserveParamsTest.cs index e615f51..a86c04a 100644 --- a/src/Stagehand.Tests/Models/Sessions/SessionObserveParamsTest.cs +++ b/src/Stagehand.Tests/Models/Sessions/SessionObserveParamsTest.cs @@ -20,6 +20,7 @@ public void FieldRoundtrip_Works() Instruction = "Find all clickable navigation links", Options = new() { + IgnoreSelectors = ["nav", ".cookie-banner", "#sidebar-ads"], Model = new ModelConfig() { ModelName = "openai/gpt-5.4-mini", @@ -51,6 +52,7 @@ public void FieldRoundtrip_Works() string expectedInstruction = "Find all clickable navigation links"; SessionObserveParamsOptions expectedOptions = new() { + IgnoreSelectors = ["nav", ".cookie-banner", "#sidebar-ads"], Model = new ModelConfig() { ModelName = "openai/gpt-5.4-mini", @@ -132,6 +134,7 @@ public void OptionalNullableParamsUnsetAreNotSet_Works() Instruction = "Find all clickable navigation links", Options = new() { + IgnoreSelectors = ["nav", ".cookie-banner", "#sidebar-ads"], Model = new ModelConfig() { ModelName = "openai/gpt-5.4-mini", @@ -171,6 +174,7 @@ public void OptionalNullableParamsSetToNullAreSetToNull_Works() Instruction = "Find all clickable navigation links", Options = new() { + IgnoreSelectors = ["nav", ".cookie-banner", "#sidebar-ads"], Model = new ModelConfig() { ModelName = "openai/gpt-5.4-mini", @@ -260,6 +264,7 @@ public void CopyConstructor_Works() Instruction = "Find all clickable navigation links", Options = new() { + IgnoreSelectors = ["nav", ".cookie-banner", "#sidebar-ads"], Model = new ModelConfig() { ModelName = "openai/gpt-5.4-mini", @@ -299,6 +304,7 @@ public void FieldRoundtrip_Works() { var model = new SessionObserveParamsOptions { + IgnoreSelectors = ["nav", ".cookie-banner", "#sidebar-ads"], Model = new ModelConfig() { ModelName = "openai/gpt-5.4-mini", @@ -323,6 +329,7 @@ public void FieldRoundtrip_Works() }, }; + List expectedIgnoreSelectors = ["nav", ".cookie-banner", "#sidebar-ads"]; SessionObserveParamsOptionsModel expectedModel = new ModelConfig() { ModelName = "openai/gpt-5.4-mini", @@ -346,6 +353,12 @@ public void FieldRoundtrip_Works() { "rememberMe", true }, }; + Assert.NotNull(model.IgnoreSelectors); + Assert.Equal(expectedIgnoreSelectors.Count, model.IgnoreSelectors.Count); + for (int i = 0; i < expectedIgnoreSelectors.Count; i++) + { + Assert.Equal(expectedIgnoreSelectors[i], model.IgnoreSelectors[i]); + } Assert.Equal(expectedModel, model.Model); Assert.Equal(expectedSelector, model.Selector); Assert.Equal(expectedTimeout, model.Timeout); @@ -364,6 +377,7 @@ public void SerializationRoundtrip_Works() { var model = new SessionObserveParamsOptions { + IgnoreSelectors = ["nav", ".cookie-banner", "#sidebar-ads"], Model = new ModelConfig() { ModelName = "openai/gpt-5.4-mini", @@ -402,6 +416,7 @@ public void FieldRoundtripThroughSerialization_Works() { var model = new SessionObserveParamsOptions { + IgnoreSelectors = ["nav", ".cookie-banner", "#sidebar-ads"], Model = new ModelConfig() { ModelName = "openai/gpt-5.4-mini", @@ -433,6 +448,7 @@ public void FieldRoundtripThroughSerialization_Works() ); Assert.NotNull(deserialized); + List expectedIgnoreSelectors = ["nav", ".cookie-banner", "#sidebar-ads"]; SessionObserveParamsOptionsModel expectedModel = new ModelConfig() { ModelName = "openai/gpt-5.4-mini", @@ -456,6 +472,12 @@ public void FieldRoundtripThroughSerialization_Works() { "rememberMe", true }, }; + Assert.NotNull(deserialized.IgnoreSelectors); + Assert.Equal(expectedIgnoreSelectors.Count, deserialized.IgnoreSelectors.Count); + for (int i = 0; i < expectedIgnoreSelectors.Count; i++) + { + Assert.Equal(expectedIgnoreSelectors[i], deserialized.IgnoreSelectors[i]); + } Assert.Equal(expectedModel, deserialized.Model); Assert.Equal(expectedSelector, deserialized.Selector); Assert.Equal(expectedTimeout, deserialized.Timeout); @@ -474,6 +496,7 @@ public void Validation_Works() { var model = new SessionObserveParamsOptions { + IgnoreSelectors = ["nav", ".cookie-banner", "#sidebar-ads"], Model = new ModelConfig() { ModelName = "openai/gpt-5.4-mini", @@ -506,6 +529,8 @@ public void OptionalNonNullablePropertiesUnsetAreNotSet_Works() { var model = new SessionObserveParamsOptions { }; + Assert.Null(model.IgnoreSelectors); + Assert.False(model.RawData.ContainsKey("ignoreSelectors")); Assert.Null(model.Model); Assert.False(model.RawData.ContainsKey("model")); Assert.Null(model.Selector); @@ -530,12 +555,15 @@ public void OptionalNonNullablePropertiesSetToNullAreNotSet_Works() var model = new SessionObserveParamsOptions { // Null should be interpreted as omitted for these properties + IgnoreSelectors = null, Model = null, Selector = null, Timeout = null, Variables = null, }; + Assert.Null(model.IgnoreSelectors); + Assert.False(model.RawData.ContainsKey("ignoreSelectors")); Assert.Null(model.Model); Assert.False(model.RawData.ContainsKey("model")); Assert.Null(model.Selector); @@ -552,6 +580,7 @@ public void OptionalNonNullablePropertiesSetToNullValidation_Works() var model = new SessionObserveParamsOptions { // Null should be interpreted as omitted for these properties + IgnoreSelectors = null, Model = null, Selector = null, Timeout = null, @@ -566,6 +595,7 @@ public void CopyConstructor_Works() { var model = new SessionObserveParamsOptions { + IgnoreSelectors = ["nav", ".cookie-banner", "#sidebar-ads"], Model = new ModelConfig() { ModelName = "openai/gpt-5.4-mini", diff --git a/src/Stagehand/Models/Sessions/SessionObserveParams.cs b/src/Stagehand/Models/Sessions/SessionObserveParams.cs index 62a87ed..fcc16f9 100644 --- a/src/Stagehand/Models/Sessions/SessionObserveParams.cs +++ b/src/Stagehand/Models/Sessions/SessionObserveParams.cs @@ -1,5 +1,6 @@ using System.Collections.Frozen; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Net.Http; using System.Text; @@ -229,6 +230,30 @@ public override int GetHashCode() )] public sealed record class SessionObserveParamsOptions : JsonModel { + /// + /// Selectors for elements and subtrees that should be excluded from observation + /// + public IReadOnlyList? IgnoreSelectors + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct>("ignoreSelectors"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set?>( + "ignoreSelectors", + value == null ? null : ImmutableArray.ToImmutableArray(value) + ); + } + } + /// /// Model configuration object or model name string (e.g., 'openai/gpt-5-nano') /// @@ -323,6 +348,7 @@ public IReadOnlyDictionary? Variabl /// public override void Validate() { + _ = this.IgnoreSelectors; this.Model?.Validate(); _ = this.Selector; _ = this.Timeout; From c20c6e01fb40ff0ab9547bf733f6c735e0949e4e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 9 May 2026 02:04:47 +0000 Subject: [PATCH 4/8] fix(internal): disable default HttpClient timeout as we have our own --- src/Stagehand/Core/ClientOptions.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Stagehand/Core/ClientOptions.cs b/src/Stagehand/Core/ClientOptions.cs index fc9fa76..df853dc 100644 --- a/src/Stagehand/Core/ClientOptions.cs +++ b/src/Stagehand/Core/ClientOptions.cs @@ -21,9 +21,16 @@ public record struct ClientOptions() /// /// The HTTP client to use for making requests in the SDK. + /// + /// Note: The HttpClient has a built-in timeout, which defaults to 100 seconds. + /// When passing a custom HttpClient, this timeout may conflict with the SDK's + /// own timeout handler and cause premature cancellation. /// public HttpClient HttpClient { get; set; } = - new(new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.Available }); + new(new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.Available }) + { + Timeout = global::System.Threading.Timeout.InfiniteTimeSpan, + }; Lazy _baseUrl = new(() => Environment.GetEnvironmentVariable("STAGEHAND_API_URL") From d5b8a144a36d2e91701a2e87c0fe714e131ba2c2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 02:00:49 +0000 Subject: [PATCH 5/8] ci: pin GitHub Actions to commit SHAs Pin all GitHub Actions referenced in generated workflows (both first-party `actions/*` and third-party) to immutable commit SHAs. Updating pinned actions is now a deliberate codegen-side bump rather than implicit on every workflow run. --- .github/workflows/ci.yml | 12 ++++++------ .github/workflows/publish-nuget.yml | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c0087c..ca841c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,10 +22,10 @@ jobs: if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@55ec9447dda3d1cf6bd587150f3262f30ee10815 # v3.4.2 with: dotnet-version: '8.0.x' @@ -41,10 +41,10 @@ jobs: if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@55ec9447dda3d1cf6bd587150f3262f30ee10815 # v3.4.2 with: dotnet-version: '8.0.x' @@ -57,10 +57,10 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/stagehand-csharp' && 'depot-windows-2022' || 'windows-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 with: dotnet-version: '8.0.x' diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml index 2ff27c6..7b8f246 100644 --- a/.github/workflows/publish-nuget.yml +++ b/.github/workflows/publish-nuget.yml @@ -12,10 +12,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 with: dotnet-version: '8.0.x' From 8fa49262dca9eda52850f3226279b7ee97228f39 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 19:16:19 +0000 Subject: [PATCH 6/8] feat: STG-1756 add Vertex auth params to Stagehand spec --- .stats.yml | 4 +- .../Models/Sessions/ModelConfigTest.cs | 791 +++++++++++++++++ .../Models/Sessions/SessionActParamsTest.cs | 308 +++++++ .../Sessions/SessionExecuteParamsTest.cs | 792 ++++++++++++++++++ .../Sessions/SessionExtractParamsTest.cs | 308 +++++++ .../Sessions/SessionObserveParamsTest.cs | 308 +++++++ .../Services/SessionServiceTest.cs | 88 ++ src/Stagehand/Core/ModelBase.cs | 1 + src/Stagehand/Models/Sessions/ModelConfig.cs | 733 ++++++++++++++++ 9 files changed, 3331 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 0339c57..220445b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 8 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/stagehand-eae8400fade7b2c8329c4148f56de92e147c34c0feecb420c015aab6544a9acc.yml -openapi_spec_hash: 0a9eff1ac1d464e89cbd9db64709b08a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/stagehand-e77d6b15f0a94b16a54ef87a84d2cabe49eb11cff5ceba76f00dd788ff483eab.yml +openapi_spec_hash: a1dab7fe72a772d188a15305124ebd73 config_hash: 1fb12ae9b478488bc1e56bfbdc210b01 diff --git a/src/Stagehand.Tests/Models/Sessions/ModelConfigTest.cs b/src/Stagehand.Tests/Models/Sessions/ModelConfigTest.cs index bdfc359..1020652 100644 --- a/src/Stagehand.Tests/Models/Sessions/ModelConfigTest.cs +++ b/src/Stagehand.Tests/Models/Sessions/ModelConfigTest.cs @@ -16,19 +16,64 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; string expectedModelName = "openai/gpt-5.4-mini"; string expectedApiKey = "sk-some-openai-api-key"; string expectedBaseUrl = "https://api.openai.com/v1"; + GoogleAuthOptions expectedGoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }; Dictionary expectedHeaders = new() { { "foo", "string" } }; + string expectedLocation = "us-central1"; + string expectedProject = "my-gcp-project"; ApiEnum expectedProvider = ModelConfigProvider.OpenAI; Assert.Equal(expectedModelName, model.ModelName); Assert.Equal(expectedApiKey, model.ApiKey); Assert.Equal(expectedBaseUrl, model.BaseUrl); + Assert.Equal(expectedGoogleAuthOptions, model.GoogleAuthOptions); Assert.NotNull(model.Headers); Assert.Equal(expectedHeaders.Count, model.Headers.Count); foreach (var item in expectedHeaders) @@ -37,6 +82,8 @@ public void FieldRoundtrip_Works() Assert.Equal(value, model.Headers[item.Key]); } + Assert.Equal(expectedLocation, model.Location); + Assert.Equal(expectedProject, model.Project); Assert.Equal(expectedProvider, model.Provider); } @@ -48,7 +95,29 @@ public void SerializationRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; @@ -69,7 +138,29 @@ public void FieldRoundtripThroughSerialization_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; @@ -83,12 +174,35 @@ public void FieldRoundtripThroughSerialization_Works() string expectedModelName = "openai/gpt-5.4-mini"; string expectedApiKey = "sk-some-openai-api-key"; string expectedBaseUrl = "https://api.openai.com/v1"; + GoogleAuthOptions expectedGoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }; Dictionary expectedHeaders = new() { { "foo", "string" } }; + string expectedLocation = "us-central1"; + string expectedProject = "my-gcp-project"; ApiEnum expectedProvider = ModelConfigProvider.OpenAI; Assert.Equal(expectedModelName, deserialized.ModelName); Assert.Equal(expectedApiKey, deserialized.ApiKey); Assert.Equal(expectedBaseUrl, deserialized.BaseUrl); + Assert.Equal(expectedGoogleAuthOptions, deserialized.GoogleAuthOptions); Assert.NotNull(deserialized.Headers); Assert.Equal(expectedHeaders.Count, deserialized.Headers.Count); foreach (var item in expectedHeaders) @@ -97,6 +211,8 @@ public void FieldRoundtripThroughSerialization_Works() Assert.Equal(value, deserialized.Headers[item.Key]); } + Assert.Equal(expectedLocation, deserialized.Location); + Assert.Equal(expectedProject, deserialized.Project); Assert.Equal(expectedProvider, deserialized.Provider); } @@ -108,7 +224,29 @@ public void Validation_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; @@ -124,8 +262,14 @@ public void OptionalNonNullablePropertiesUnsetAreNotSet_Works() Assert.False(model.RawData.ContainsKey("apiKey")); Assert.Null(model.BaseUrl); Assert.False(model.RawData.ContainsKey("baseURL")); + Assert.Null(model.GoogleAuthOptions); + Assert.False(model.RawData.ContainsKey("googleAuthOptions")); Assert.Null(model.Headers); Assert.False(model.RawData.ContainsKey("headers")); + Assert.Null(model.Location); + Assert.False(model.RawData.ContainsKey("location")); + Assert.Null(model.Project); + Assert.False(model.RawData.ContainsKey("project")); Assert.Null(model.Provider); Assert.False(model.RawData.ContainsKey("provider")); } @@ -148,7 +292,10 @@ public void OptionalNonNullablePropertiesSetToNullAreNotSet_Works() // Null should be interpreted as omitted for these properties ApiKey = null, BaseUrl = null, + GoogleAuthOptions = null, Headers = null, + Location = null, + Project = null, Provider = null, }; @@ -156,8 +303,14 @@ public void OptionalNonNullablePropertiesSetToNullAreNotSet_Works() Assert.False(model.RawData.ContainsKey("apiKey")); Assert.Null(model.BaseUrl); Assert.False(model.RawData.ContainsKey("baseURL")); + Assert.Null(model.GoogleAuthOptions); + Assert.False(model.RawData.ContainsKey("googleAuthOptions")); Assert.Null(model.Headers); Assert.False(model.RawData.ContainsKey("headers")); + Assert.Null(model.Location); + Assert.False(model.RawData.ContainsKey("location")); + Assert.Null(model.Project); + Assert.False(model.RawData.ContainsKey("project")); Assert.Null(model.Provider); Assert.False(model.RawData.ContainsKey("provider")); } @@ -172,7 +325,10 @@ public void OptionalNonNullablePropertiesSetToNullValidation_Works() // Null should be interpreted as omitted for these properties ApiKey = null, BaseUrl = null, + GoogleAuthOptions = null, Headers = null, + Location = null, + Project = null, Provider = null, }; @@ -187,7 +343,29 @@ public void CopyConstructor_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; @@ -197,6 +375,617 @@ public void CopyConstructor_Works() } } +public class GoogleAuthOptionsTest : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new GoogleAuthOptions + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }; + + Credentials expectedCredentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }; + string expectedProjectID = "projectId"; + Scopes expectedScopes = "string"; + string expectedUniverseDomain = "universeDomain"; + + Assert.Equal(expectedCredentials, model.Credentials); + Assert.Equal(expectedProjectID, model.ProjectID); + Assert.Equal(expectedScopes, model.Scopes); + Assert.Equal(expectedUniverseDomain, model.UniverseDomain); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new GoogleAuthOptions + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new GoogleAuthOptions + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + Credentials expectedCredentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }; + string expectedProjectID = "projectId"; + Scopes expectedScopes = "string"; + string expectedUniverseDomain = "universeDomain"; + + Assert.Equal(expectedCredentials, deserialized.Credentials); + Assert.Equal(expectedProjectID, deserialized.ProjectID); + Assert.Equal(expectedScopes, deserialized.Scopes); + Assert.Equal(expectedUniverseDomain, deserialized.UniverseDomain); + } + + [Fact] + public void Validation_Works() + { + var model = new GoogleAuthOptions + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }; + + model.Validate(); + } + + [Fact] + public void OptionalNonNullablePropertiesUnsetAreNotSet_Works() + { + var model = new GoogleAuthOptions { }; + + Assert.Null(model.Credentials); + Assert.False(model.RawData.ContainsKey("credentials")); + Assert.Null(model.ProjectID); + Assert.False(model.RawData.ContainsKey("projectId")); + Assert.Null(model.Scopes); + Assert.False(model.RawData.ContainsKey("scopes")); + Assert.Null(model.UniverseDomain); + Assert.False(model.RawData.ContainsKey("universeDomain")); + } + + [Fact] + public void OptionalNonNullablePropertiesUnsetValidation_Works() + { + var model = new GoogleAuthOptions { }; + + model.Validate(); + } + + [Fact] + public void OptionalNonNullablePropertiesSetToNullAreNotSet_Works() + { + var model = new GoogleAuthOptions + { + // Null should be interpreted as omitted for these properties + Credentials = null, + ProjectID = null, + Scopes = null, + UniverseDomain = null, + }; + + Assert.Null(model.Credentials); + Assert.False(model.RawData.ContainsKey("credentials")); + Assert.Null(model.ProjectID); + Assert.False(model.RawData.ContainsKey("projectId")); + Assert.Null(model.Scopes); + Assert.False(model.RawData.ContainsKey("scopes")); + Assert.Null(model.UniverseDomain); + Assert.False(model.RawData.ContainsKey("universeDomain")); + } + + [Fact] + public void OptionalNonNullablePropertiesSetToNullValidation_Works() + { + var model = new GoogleAuthOptions + { + // Null should be interpreted as omitted for these properties + Credentials = null, + ProjectID = null, + Scopes = null, + UniverseDomain = null, + }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new GoogleAuthOptions + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }; + + GoogleAuthOptions copied = new(model); + + Assert.Equal(model, copied); + } +} + +public class CredentialsTest : TestBase +{ + [Fact] + public void FieldRoundtrip_Works() + { + var model = new Credentials + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }; + + string expectedClientEmail = "client_email"; + string expectedPrivateKey = "private_key"; + string expectedAuthProviderX509CertUrl = "https://example.com"; + string expectedAuthUri = "https://example.com"; + string expectedClientID = "client_id"; + string expectedClientX509CertUrl = "https://example.com"; + string expectedPrivateKeyID = "private_key_id"; + string expectedProjectID = "project_id"; + string expectedTokenUri = "https://example.com"; + ApiEnum expectedType = CredentialsType.ServiceAccount; + string expectedUniverseDomain = "universe_domain"; + + Assert.Equal(expectedClientEmail, model.ClientEmail); + Assert.Equal(expectedPrivateKey, model.PrivateKey); + Assert.Equal(expectedAuthProviderX509CertUrl, model.AuthProviderX509CertUrl); + Assert.Equal(expectedAuthUri, model.AuthUri); + Assert.Equal(expectedClientID, model.ClientID); + Assert.Equal(expectedClientX509CertUrl, model.ClientX509CertUrl); + Assert.Equal(expectedPrivateKeyID, model.PrivateKeyID); + Assert.Equal(expectedProjectID, model.ProjectID); + Assert.Equal(expectedTokenUri, model.TokenUri); + Assert.Equal(expectedType, model.Type); + Assert.Equal(expectedUniverseDomain, model.UniverseDomain); + } + + [Fact] + public void SerializationRoundtrip_Works() + { + var model = new Credentials + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }; + + string json = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(model, deserialized); + } + + [Fact] + public void FieldRoundtripThroughSerialization_Works() + { + var model = new Credentials + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }; + + string element = JsonSerializer.Serialize(model, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize( + element, + ModelBase.SerializerOptions + ); + Assert.NotNull(deserialized); + + string expectedClientEmail = "client_email"; + string expectedPrivateKey = "private_key"; + string expectedAuthProviderX509CertUrl = "https://example.com"; + string expectedAuthUri = "https://example.com"; + string expectedClientID = "client_id"; + string expectedClientX509CertUrl = "https://example.com"; + string expectedPrivateKeyID = "private_key_id"; + string expectedProjectID = "project_id"; + string expectedTokenUri = "https://example.com"; + ApiEnum expectedType = CredentialsType.ServiceAccount; + string expectedUniverseDomain = "universe_domain"; + + Assert.Equal(expectedClientEmail, deserialized.ClientEmail); + Assert.Equal(expectedPrivateKey, deserialized.PrivateKey); + Assert.Equal(expectedAuthProviderX509CertUrl, deserialized.AuthProviderX509CertUrl); + Assert.Equal(expectedAuthUri, deserialized.AuthUri); + Assert.Equal(expectedClientID, deserialized.ClientID); + Assert.Equal(expectedClientX509CertUrl, deserialized.ClientX509CertUrl); + Assert.Equal(expectedPrivateKeyID, deserialized.PrivateKeyID); + Assert.Equal(expectedProjectID, deserialized.ProjectID); + Assert.Equal(expectedTokenUri, deserialized.TokenUri); + Assert.Equal(expectedType, deserialized.Type); + Assert.Equal(expectedUniverseDomain, deserialized.UniverseDomain); + } + + [Fact] + public void Validation_Works() + { + var model = new Credentials + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }; + + model.Validate(); + } + + [Fact] + public void OptionalNonNullablePropertiesUnsetAreNotSet_Works() + { + var model = new Credentials { ClientEmail = "client_email", PrivateKey = "private_key" }; + + Assert.Null(model.AuthProviderX509CertUrl); + Assert.False(model.RawData.ContainsKey("auth_provider_x509_cert_url")); + Assert.Null(model.AuthUri); + Assert.False(model.RawData.ContainsKey("auth_uri")); + Assert.Null(model.ClientID); + Assert.False(model.RawData.ContainsKey("client_id")); + Assert.Null(model.ClientX509CertUrl); + Assert.False(model.RawData.ContainsKey("client_x509_cert_url")); + Assert.Null(model.PrivateKeyID); + Assert.False(model.RawData.ContainsKey("private_key_id")); + Assert.Null(model.ProjectID); + Assert.False(model.RawData.ContainsKey("project_id")); + Assert.Null(model.TokenUri); + Assert.False(model.RawData.ContainsKey("token_uri")); + Assert.Null(model.Type); + Assert.False(model.RawData.ContainsKey("type")); + Assert.Null(model.UniverseDomain); + Assert.False(model.RawData.ContainsKey("universe_domain")); + } + + [Fact] + public void OptionalNonNullablePropertiesUnsetValidation_Works() + { + var model = new Credentials { ClientEmail = "client_email", PrivateKey = "private_key" }; + + model.Validate(); + } + + [Fact] + public void OptionalNonNullablePropertiesSetToNullAreNotSet_Works() + { + var model = new Credentials + { + ClientEmail = "client_email", + PrivateKey = "private_key", + + // Null should be interpreted as omitted for these properties + AuthProviderX509CertUrl = null, + AuthUri = null, + ClientID = null, + ClientX509CertUrl = null, + PrivateKeyID = null, + ProjectID = null, + TokenUri = null, + Type = null, + UniverseDomain = null, + }; + + Assert.Null(model.AuthProviderX509CertUrl); + Assert.False(model.RawData.ContainsKey("auth_provider_x509_cert_url")); + Assert.Null(model.AuthUri); + Assert.False(model.RawData.ContainsKey("auth_uri")); + Assert.Null(model.ClientID); + Assert.False(model.RawData.ContainsKey("client_id")); + Assert.Null(model.ClientX509CertUrl); + Assert.False(model.RawData.ContainsKey("client_x509_cert_url")); + Assert.Null(model.PrivateKeyID); + Assert.False(model.RawData.ContainsKey("private_key_id")); + Assert.Null(model.ProjectID); + Assert.False(model.RawData.ContainsKey("project_id")); + Assert.Null(model.TokenUri); + Assert.False(model.RawData.ContainsKey("token_uri")); + Assert.Null(model.Type); + Assert.False(model.RawData.ContainsKey("type")); + Assert.Null(model.UniverseDomain); + Assert.False(model.RawData.ContainsKey("universe_domain")); + } + + [Fact] + public void OptionalNonNullablePropertiesSetToNullValidation_Works() + { + var model = new Credentials + { + ClientEmail = "client_email", + PrivateKey = "private_key", + + // Null should be interpreted as omitted for these properties + AuthProviderX509CertUrl = null, + AuthUri = null, + ClientID = null, + ClientX509CertUrl = null, + PrivateKeyID = null, + ProjectID = null, + TokenUri = null, + Type = null, + UniverseDomain = null, + }; + + model.Validate(); + } + + [Fact] + public void CopyConstructor_Works() + { + var model = new Credentials + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }; + + Credentials copied = new(model); + + Assert.Equal(model, copied); + } +} + +public class CredentialsTypeTest : TestBase +{ + [Theory] + [InlineData(CredentialsType.ServiceAccount)] + public void Validation_Works(CredentialsType rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + value.Validate(); + } + + [Fact] + public void InvalidEnumValidationThrows_Works() + { + var value = JsonSerializer.Deserialize>( + JsonSerializer.SerializeToElement("invalid value"), + ModelBase.SerializerOptions + ); + + Assert.NotNull(value); + Assert.Throws(() => value.Validate()); + } + + [Theory] + [InlineData(CredentialsType.ServiceAccount)] + public void SerializationRoundtrip_Works(CredentialsType rawValue) + { + // force implicit conversion because Theory can't do that for us + ApiEnum value = rawValue; + + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize>( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(value, deserialized); + } + + [Fact] + public void InvalidEnumSerializationRoundtrip_Works() + { + var value = JsonSerializer.Deserialize>( + JsonSerializer.SerializeToElement("invalid value"), + ModelBase.SerializerOptions + ); + string json = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize>( + json, + ModelBase.SerializerOptions + ); + + Assert.Equal(value, deserialized); + } +} + +public class ScopesTest : TestBase +{ + [Fact] + public void StringValidationWorks() + { + Scopes value = "string"; + value.Validate(); + } + + [Fact] + public void StringsValidationWorks() + { + Scopes value = new(["string"]); + value.Validate(); + } + + [Fact] + public void StringSerializationRoundtripWorks() + { + Scopes value = "string"; + string element = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize(element, ModelBase.SerializerOptions); + + Assert.Equal(value, deserialized); + } + + [Fact] + public void StringsSerializationRoundtripWorks() + { + Scopes value = new(["string"]); + string element = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); + var deserialized = JsonSerializer.Deserialize(element, ModelBase.SerializerOptions); + + Assert.Equal(value, deserialized); + } +} + public class ModelConfigProviderTest : TestBase { [Theory] @@ -205,6 +994,7 @@ public class ModelConfigProviderTest : TestBase [InlineData(ModelConfigProvider.Google)] [InlineData(ModelConfigProvider.Microsoft)] [InlineData(ModelConfigProvider.Bedrock)] + [InlineData(ModelConfigProvider.Vertex)] public void Validation_Works(ModelConfigProvider rawValue) { // force implicit conversion because Theory can't do that for us @@ -230,6 +1020,7 @@ public void InvalidEnumValidationThrows_Works() [InlineData(ModelConfigProvider.Google)] [InlineData(ModelConfigProvider.Microsoft)] [InlineData(ModelConfigProvider.Bedrock)] + [InlineData(ModelConfigProvider.Vertex)] public void SerializationRoundtrip_Works(ModelConfigProvider rawValue) { // force implicit conversion because Theory can't do that for us diff --git a/src/Stagehand.Tests/Models/Sessions/SessionActParamsTest.cs b/src/Stagehand.Tests/Models/Sessions/SessionActParamsTest.cs index 9982b63..05e126d 100644 --- a/src/Stagehand.Tests/Models/Sessions/SessionActParamsTest.cs +++ b/src/Stagehand.Tests/Models/Sessions/SessionActParamsTest.cs @@ -25,7 +25,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = Sessions::CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = Sessions::ModelConfigProvider.OpenAI, }, Timeout = 30000, @@ -55,7 +77,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = Sessions::CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = Sessions::ModelConfigProvider.OpenAI, }, Timeout = 30000, @@ -132,7 +176,29 @@ public void OptionalNullableParamsUnsetAreNotSet_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = Sessions::CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = Sessions::ModelConfigProvider.OpenAI, }, Timeout = 30000, @@ -170,7 +236,29 @@ public void OptionalNullableParamsSetToNullAreSetToNull_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = Sessions::CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = Sessions::ModelConfigProvider.OpenAI, }, Timeout = 30000, @@ -263,7 +351,29 @@ public void CopyConstructor_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = Sessions::CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = Sessions::ModelConfigProvider.OpenAI, }, Timeout = 30000, @@ -358,7 +468,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = Sessions::CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = Sessions::ModelConfigProvider.OpenAI, }, Timeout = 30000, @@ -381,7 +513,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = Sessions::CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = Sessions::ModelConfigProvider.OpenAI, }; double expectedTimeout = 30000; @@ -420,7 +574,29 @@ public void SerializationRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = Sessions::CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = Sessions::ModelConfigProvider.OpenAI, }, Timeout = 30000, @@ -457,7 +633,29 @@ public void FieldRoundtripThroughSerialization_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = Sessions::CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = Sessions::ModelConfigProvider.OpenAI, }, Timeout = 30000, @@ -487,7 +685,29 @@ public void FieldRoundtripThroughSerialization_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = Sessions::CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = Sessions::ModelConfigProvider.OpenAI, }; double expectedTimeout = 30000; @@ -526,7 +746,29 @@ public void Validation_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = Sessions::CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = Sessions::ModelConfigProvider.OpenAI, }, Timeout = 30000, @@ -611,7 +853,29 @@ public void CopyConstructor_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = Sessions::CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = Sessions::ModelConfigProvider.OpenAI, }, Timeout = 30000, @@ -645,7 +909,29 @@ public void ConfigValidationWorks() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = Sessions::CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = Sessions::ModelConfigProvider.OpenAI, }; value.Validate(); @@ -666,7 +952,29 @@ public void ConfigSerializationRoundtripWorks() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = Sessions::CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = Sessions::ModelConfigProvider.OpenAI, }; string element = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); diff --git a/src/Stagehand.Tests/Models/Sessions/SessionExecuteParamsTest.cs b/src/Stagehand.Tests/Models/Sessions/SessionExecuteParamsTest.cs index 20aaaca..116df0d 100644 --- a/src/Stagehand.Tests/Models/Sessions/SessionExecuteParamsTest.cs +++ b/src/Stagehand.Tests/Models/Sessions/SessionExecuteParamsTest.cs @@ -24,7 +24,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Mode = Mode.Cua, @@ -33,7 +55,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Provider = Provider.OpenAI, @@ -66,7 +110,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Mode = Mode.Cua, @@ -75,7 +141,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Provider = Provider.OpenAI, @@ -118,7 +206,29 @@ public void OptionalNonNullableParamsUnsetAreNotSet_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Mode = Mode.Cua, @@ -127,7 +237,29 @@ public void OptionalNonNullableParamsUnsetAreNotSet_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Provider = Provider.OpenAI, @@ -169,7 +301,29 @@ public void OptionalNonNullableParamsSetToNullAreNotSet_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Mode = Mode.Cua, @@ -178,7 +332,29 @@ public void OptionalNonNullableParamsSetToNullAreNotSet_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Provider = Provider.OpenAI, @@ -224,7 +400,29 @@ public void OptionalNullableParamsUnsetAreNotSet_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Mode = Mode.Cua, @@ -233,7 +431,29 @@ public void OptionalNullableParamsUnsetAreNotSet_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Provider = Provider.OpenAI, @@ -274,7 +494,29 @@ public void OptionalNullableParamsSetToNullAreSetToNull_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Mode = Mode.Cua, @@ -283,7 +525,29 @@ public void OptionalNullableParamsSetToNullAreSetToNull_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Provider = Provider.OpenAI, @@ -326,7 +590,29 @@ public void Url_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Mode = Mode.Cua, @@ -335,7 +621,29 @@ public void Url_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Provider = Provider.OpenAI, @@ -390,7 +698,29 @@ public void AddHeadersToRequest_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Mode = Mode.Cua, @@ -399,7 +729,29 @@ public void AddHeadersToRequest_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Provider = Provider.OpenAI, @@ -448,7 +800,29 @@ public void CopyConstructor_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Mode = Mode.Cua, @@ -457,7 +831,29 @@ public void CopyConstructor_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Provider = Provider.OpenAI, @@ -500,7 +896,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Mode = Mode.Cua, @@ -509,7 +927,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Provider = Provider.OpenAI, @@ -522,7 +962,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; ApiEnum expectedMode = Mode.Cua; @@ -531,7 +993,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; ApiEnum expectedProvider = Provider.OpenAI; @@ -556,7 +1040,29 @@ public void SerializationRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Mode = Mode.Cua, @@ -565,7 +1071,29 @@ public void SerializationRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Provider = Provider.OpenAI, @@ -592,7 +1120,29 @@ public void FieldRoundtripThroughSerialization_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Mode = Mode.Cua, @@ -601,7 +1151,29 @@ public void FieldRoundtripThroughSerialization_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Provider = Provider.OpenAI, @@ -621,7 +1193,29 @@ public void FieldRoundtripThroughSerialization_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; ApiEnum expectedMode = Mode.Cua; @@ -630,7 +1224,29 @@ public void FieldRoundtripThroughSerialization_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; ApiEnum expectedProvider = Provider.OpenAI; @@ -655,7 +1271,29 @@ public void Validation_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Mode = Mode.Cua, @@ -664,7 +1302,29 @@ public void Validation_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Provider = Provider.OpenAI, @@ -757,7 +1417,29 @@ public void CopyConstructor_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Mode = Mode.Cua, @@ -766,7 +1448,29 @@ public void CopyConstructor_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Provider = Provider.OpenAI, @@ -789,7 +1493,29 @@ public void ModelConfigValidationWorks() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; value.Validate(); @@ -810,7 +1536,29 @@ public void ModelConfigSerializationRoundtripWorks() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; string element = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); @@ -906,7 +1654,29 @@ public void ConfigValidationWorks() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; value.Validate(); @@ -927,7 +1697,29 @@ public void ConfigSerializationRoundtripWorks() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; string element = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); diff --git a/src/Stagehand.Tests/Models/Sessions/SessionExtractParamsTest.cs b/src/Stagehand.Tests/Models/Sessions/SessionExtractParamsTest.cs index acbc7a0..a888c3b 100644 --- a/src/Stagehand.Tests/Models/Sessions/SessionExtractParamsTest.cs +++ b/src/Stagehand.Tests/Models/Sessions/SessionExtractParamsTest.cs @@ -26,7 +26,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "#main-content", @@ -50,7 +72,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "#main-content", @@ -137,7 +181,29 @@ public void OptionalNullableParamsUnsetAreNotSet_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "#main-content", @@ -169,7 +235,29 @@ public void OptionalNullableParamsSetToNullAreSetToNull_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "#main-content", @@ -251,7 +339,29 @@ public void CopyConstructor_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "#main-content", @@ -283,7 +393,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "#main-content", @@ -296,7 +428,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; string expectedSelector = "#main-content"; @@ -324,7 +478,29 @@ public void SerializationRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "#main-content", @@ -351,7 +527,29 @@ public void FieldRoundtripThroughSerialization_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "#main-content", @@ -371,7 +569,29 @@ public void FieldRoundtripThroughSerialization_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; string expectedSelector = "#main-content"; @@ -399,7 +619,29 @@ public void Validation_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "#main-content", @@ -480,7 +722,29 @@ public void CopyConstructor_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "#main-content", @@ -503,7 +767,29 @@ public void ConfigValidationWorks() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; value.Validate(); @@ -524,7 +810,29 @@ public void ConfigSerializationRoundtripWorks() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; string element = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); diff --git a/src/Stagehand.Tests/Models/Sessions/SessionObserveParamsTest.cs b/src/Stagehand.Tests/Models/Sessions/SessionObserveParamsTest.cs index a86c04a..d180d7f 100644 --- a/src/Stagehand.Tests/Models/Sessions/SessionObserveParamsTest.cs +++ b/src/Stagehand.Tests/Models/Sessions/SessionObserveParamsTest.cs @@ -26,7 +26,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "nav", @@ -58,7 +80,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "nav", @@ -140,7 +184,29 @@ public void OptionalNullableParamsUnsetAreNotSet_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "nav", @@ -180,7 +246,29 @@ public void OptionalNullableParamsSetToNullAreSetToNull_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "nav", @@ -270,7 +358,29 @@ public void CopyConstructor_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "nav", @@ -310,7 +420,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "nav", @@ -335,7 +467,29 @@ public void FieldRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; string expectedSelector = "nav"; @@ -383,7 +537,29 @@ public void SerializationRoundtrip_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "nav", @@ -422,7 +598,29 @@ public void FieldRoundtripThroughSerialization_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "nav", @@ -454,7 +652,29 @@ public void FieldRoundtripThroughSerialization_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; string expectedSelector = "nav"; @@ -502,7 +722,29 @@ public void Validation_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "nav", @@ -601,7 +843,29 @@ public void CopyConstructor_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Selector = "nav", @@ -636,7 +900,29 @@ public void ConfigValidationWorks() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; value.Validate(); @@ -657,7 +943,29 @@ public void ConfigSerializationRoundtripWorks() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; string element = JsonSerializer.Serialize(value, ModelBase.SerializerOptions); diff --git a/src/Stagehand.Tests/Services/SessionServiceTest.cs b/src/Stagehand.Tests/Services/SessionServiceTest.cs index bf5d2b0..902d715 100644 --- a/src/Stagehand.Tests/Services/SessionServiceTest.cs +++ b/src/Stagehand.Tests/Services/SessionServiceTest.cs @@ -58,7 +58,29 @@ public async Task Execute_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Mode = Mode.Cua, @@ -67,7 +89,29 @@ public async Task Execute_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Provider = Provider.OpenAI, @@ -107,7 +151,29 @@ public async Task ExecuteStreaming_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Mode = Mode.Cua, @@ -116,7 +182,29 @@ public async Task ExecuteStreaming_Works() ModelName = "openai/gpt-5.4-mini", ApiKey = "sk-some-openai-api-key", BaseUrl = "https://api.openai.com/v1", + GoogleAuthOptions = new() + { + Credentials = new() + { + ClientEmail = "client_email", + PrivateKey = "private_key", + AuthProviderX509CertUrl = "https://example.com", + AuthUri = "https://example.com", + ClientID = "client_id", + ClientX509CertUrl = "https://example.com", + PrivateKeyID = "private_key_id", + ProjectID = "project_id", + TokenUri = "https://example.com", + Type = CredentialsType.ServiceAccount, + UniverseDomain = "universe_domain", + }, + ProjectID = "projectId", + Scopes = "string", + UniverseDomain = "universeDomain", + }, Headers = new Dictionary() { { "foo", "string" } }, + Location = "us-central1", + Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, Provider = Provider.OpenAI, diff --git a/src/Stagehand/Core/ModelBase.cs b/src/Stagehand/Core/ModelBase.cs index 08c436c..652df35 100644 --- a/src/Stagehand/Core/ModelBase.cs +++ b/src/Stagehand/Core/ModelBase.cs @@ -21,6 +21,7 @@ protected ModelBase(ModelBase modelBase) Converters = { new FrozenDictionaryConverterFactory(), + new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), new ApiEnumConverter(), diff --git a/src/Stagehand/Models/Sessions/ModelConfig.cs b/src/Stagehand/Models/Sessions/ModelConfig.cs index 1fcacce..60bb520 100644 --- a/src/Stagehand/Models/Sessions/ModelConfig.cs +++ b/src/Stagehand/Models/Sessions/ModelConfig.cs @@ -1,5 +1,6 @@ using System.Collections.Frozen; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; @@ -67,6 +68,27 @@ public string? BaseUrl } } + /// + /// google-auth-library options used to authenticate Vertex AI models + /// + public GoogleAuthOptions? GoogleAuthOptions + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("googleAuthOptions"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("googleAuthOptions", value); + } + } + /// /// Custom headers sent with every request to the model provider /// @@ -91,6 +113,48 @@ public IReadOnlyDictionary? Headers } } + /// + /// Google Cloud location for Vertex AI models + /// + public string? Location + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("location"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("location", value); + } + } + + /// + /// Google Cloud project ID for Vertex AI models + /// + public string? Project + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("project"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("project", value); + } + } + /// /// AI provider for the model (or provide a baseURL endpoint instead) /// @@ -118,7 +182,10 @@ public override void Validate() _ = this.ModelName; _ = this.ApiKey; _ = this.BaseUrl; + this.GoogleAuthOptions?.Validate(); _ = this.Headers; + _ = this.Location; + _ = this.Project; this.Provider?.Validate(); } @@ -164,6 +231,669 @@ public ModelConfig FromRawUnchecked(IReadOnlyDictionary raw ModelConfig.FromRawUnchecked(rawData); } +/// +/// google-auth-library options used to authenticate Vertex AI models +/// +[JsonConverter(typeof(JsonModelConverter))] +public sealed record class GoogleAuthOptions : JsonModel +{ + /// + /// Google Cloud service account credentials + /// + public Credentials? Credentials + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("credentials"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("credentials", value); + } + } + + /// + /// Google Cloud project ID used by google-auth-library + /// + public string? ProjectID + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("projectId"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("projectId", value); + } + } + + /// + /// Google auth scopes for the desired API request + /// + public Scopes? Scopes + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("scopes"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("scopes", value); + } + } + + /// + /// Google Cloud universe domain + /// + public string? UniverseDomain + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("universeDomain"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("universeDomain", value); + } + } + + /// + public override void Validate() + { + this.Credentials?.Validate(); + _ = this.ProjectID; + this.Scopes?.Validate(); + _ = this.UniverseDomain; + } + + public GoogleAuthOptions() { } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public GoogleAuthOptions(GoogleAuthOptions googleAuthOptions) + : base(googleAuthOptions) { } +#pragma warning restore CS8618 + + public GoogleAuthOptions(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + GoogleAuthOptions(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static GoogleAuthOptions FromRawUnchecked( + IReadOnlyDictionary rawData + ) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } +} + +class GoogleAuthOptionsFromRaw : IFromRawJson +{ + /// + public GoogleAuthOptions FromRawUnchecked(IReadOnlyDictionary rawData) => + GoogleAuthOptions.FromRawUnchecked(rawData); +} + +/// +/// Google Cloud service account credentials +/// +[JsonConverter(typeof(JsonModelConverter))] +public sealed record class Credentials : JsonModel +{ + public required string ClientEmail + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("client_email"); + } + init { this._rawData.Set("client_email", value); } + } + + public required string PrivateKey + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNotNullClass("private_key"); + } + init { this._rawData.Set("private_key", value); } + } + + public string? AuthProviderX509CertUrl + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("auth_provider_x509_cert_url"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("auth_provider_x509_cert_url", value); + } + } + + public string? AuthUri + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("auth_uri"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("auth_uri", value); + } + } + + public string? ClientID + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("client_id"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("client_id", value); + } + } + + public string? ClientX509CertUrl + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("client_x509_cert_url"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("client_x509_cert_url", value); + } + } + + public string? PrivateKeyID + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("private_key_id"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("private_key_id", value); + } + } + + public string? ProjectID + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("project_id"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("project_id", value); + } + } + + public string? TokenUri + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("token_uri"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("token_uri", value); + } + } + + public ApiEnum? Type + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass>("type"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("type", value); + } + } + + public string? UniverseDomain + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableClass("universe_domain"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("universe_domain", value); + } + } + + /// + public override void Validate() + { + _ = this.ClientEmail; + _ = this.PrivateKey; + _ = this.AuthProviderX509CertUrl; + _ = this.AuthUri; + _ = this.ClientID; + _ = this.ClientX509CertUrl; + _ = this.PrivateKeyID; + _ = this.ProjectID; + _ = this.TokenUri; + this.Type?.Validate(); + _ = this.UniverseDomain; + } + + public Credentials() { } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + public Credentials(Credentials credentials) + : base(credentials) { } +#pragma warning restore CS8618 + + public Credentials(IReadOnlyDictionary rawData) + { + this._rawData = new(rawData); + } + +#pragma warning disable CS8618 + [SetsRequiredMembers] + Credentials(FrozenDictionary rawData) + { + this._rawData = new(rawData); + } +#pragma warning restore CS8618 + + /// + public static Credentials FromRawUnchecked(IReadOnlyDictionary rawData) + { + return new(FrozenDictionary.ToFrozenDictionary(rawData)); + } +} + +class CredentialsFromRaw : IFromRawJson +{ + /// + public Credentials FromRawUnchecked(IReadOnlyDictionary rawData) => + Credentials.FromRawUnchecked(rawData); +} + +[JsonConverter(typeof(CredentialsTypeConverter))] +public enum CredentialsType +{ + ServiceAccount, +} + +sealed class CredentialsTypeConverter : JsonConverter +{ + public override CredentialsType Read( + ref Utf8JsonReader reader, + System::Type typeToConvert, + JsonSerializerOptions options + ) + { + return JsonSerializer.Deserialize(ref reader, options) switch + { + "service_account" => CredentialsType.ServiceAccount, + _ => (CredentialsType)(-1), + }; + } + + public override void Write( + Utf8JsonWriter writer, + CredentialsType value, + JsonSerializerOptions options + ) + { + JsonSerializer.Serialize( + writer, + value switch + { + CredentialsType.ServiceAccount => "service_account", + _ => throw new StagehandInvalidDataException( + string.Format("Invalid value '{0}' in {1}", value, nameof(value)) + ), + }, + options + ); + } +} + +/// +/// Google auth scopes for the desired API request +/// +[JsonConverter(typeof(ScopesConverter))] +public record class Scopes : ModelBase +{ + public object? Value { get; } = null; + + JsonElement? _element = null; + + public JsonElement Json + { + get + { + return this._element ??= JsonSerializer.SerializeToElement( + this.Value, + ModelBase.SerializerOptions + ); + } + } + + public Scopes(string value, JsonElement? element = null) + { + this.Value = value; + this._element = element; + } + + public Scopes(IReadOnlyList value, JsonElement? element = null) + { + this.Value = ImmutableArray.ToImmutableArray(value); + this._element = element; + } + + public Scopes(JsonElement element) + { + this._element = element; + } + + /// + /// Returns true and sets the out parameter if the instance was constructed with a variant of + /// type . + /// + /// Consider using or if you need to handle every variant. + /// + /// + /// + /// if (instance.TryPickString(out var value)) { + /// // `value` is of type `string` + /// Console.WriteLine(value); + /// } + /// + /// + /// + public bool TryPickString([NotNullWhen(true)] out string? value) + { + value = this.Value as string; + return value != null; + } + + /// + /// Returns true and sets the out parameter if the instance was constructed with a variant of + /// type where T is a string. + /// + /// Consider using or if you need to handle every variant. + /// + /// + /// + /// if (instance.TryPickStrings(out var value)) { + /// // `value` is of type `IReadOnlyList<string>` + /// Console.WriteLine(value); + /// } + /// + /// + /// + public bool TryPickStrings([NotNullWhen(true)] out IReadOnlyList? value) + { + value = this.Value as IReadOnlyList; + return value != null; + } + + /// + /// Calls the function parameter corresponding to the variant the instance was constructed with. + /// + /// Use the TryPick method(s) if you don't need to handle every variant, or + /// if you need your function parameters to return something. + /// + /// + /// Thrown when the instance was constructed with an unknown variant (e.g. deserialized from raw data + /// that doesn't match any variant's expected shape). + /// + /// + /// + /// + /// instance.Switch( + /// (string value) => {...}, + /// (IReadOnlyList<string> value) => {...} + /// ); + /// + /// + /// + public void Switch( + System::Action @string, + System::Action> strings + ) + { + switch (this.Value) + { + case string value: + @string(value); + break; + case IReadOnlyList value: + strings(value); + break; + default: + throw new StagehandInvalidDataException("Data did not match any variant of Scopes"); + } + } + + /// + /// Calls the function parameter corresponding to the variant the instance was constructed with and + /// returns its result. + /// + /// Use the TryPick method(s) if you don't need to handle every variant, or + /// if you don't need your function parameters to return a value. + /// + /// + /// Thrown when the instance was constructed with an unknown variant (e.g. deserialized from raw data + /// that doesn't match any variant's expected shape). + /// + /// + /// + /// + /// var result = instance.Match( + /// (string value) => {...}, + /// (IReadOnlyList<string> value) => {...} + /// ); + /// + /// + /// + public T Match( + System::Func @string, + System::Func, T> strings + ) + { + return this.Value switch + { + string value => @string(value), + IReadOnlyList value => strings(value), + _ => throw new StagehandInvalidDataException( + "Data did not match any variant of Scopes" + ), + }; + } + + public static implicit operator Scopes(string value) => new(value); + + public static implicit operator Scopes(List value) => new((IReadOnlyList)value); + + /// + /// Validates that the instance was constructed with a known variant and that this variant is valid + /// (based on its own Validate method). + /// + /// This is useful for instances constructed from raw JSON data (e.g. deserialized from an API response). + /// + /// + /// Thrown when the instance does not pass validation. + /// + /// + public override void Validate() + { + if (this.Value == null) + { + throw new StagehandInvalidDataException("Data did not match any variant of Scopes"); + } + } + + public virtual bool Equals(Scopes? other) => + other != null + && this.VariantIndex() == other.VariantIndex() + && JsonElement.DeepEquals(this.Json, other.Json); + + public override int GetHashCode() + { + return 0; + } + + public override string ToString() => + JsonSerializer.Serialize( + FriendlyJsonPrinter.PrintValue(this.Json), + ModelBase.ToStringSerializerOptions + ); + + int VariantIndex() + { + return this.Value switch + { + string _ => 0, + IReadOnlyList _ => 1, + _ => -1, + }; + } +} + +sealed class ScopesConverter : JsonConverter +{ + public override Scopes? Read( + ref Utf8JsonReader reader, + System::Type typeToConvert, + JsonSerializerOptions options + ) + { + var element = JsonSerializer.Deserialize(ref reader, options); + try + { + var deserialized = JsonSerializer.Deserialize(element, options); + if (deserialized != null) + { + return new(deserialized, element); + } + } + catch (System::Exception e) when (e is JsonException || e is StagehandInvalidDataException) + { + // ignore + } + + try + { + var deserialized = JsonSerializer.Deserialize>(element, options); + if (deserialized != null) + { + return new(deserialized, element); + } + } + catch (System::Exception e) when (e is JsonException || e is StagehandInvalidDataException) + { + // ignore + } + + return new(element); + } + + public override void Write(Utf8JsonWriter writer, Scopes value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value.Json, options); + } +} + /// /// AI provider for the model (or provide a baseURL endpoint instead) /// @@ -175,6 +905,7 @@ public enum ModelConfigProvider Google, Microsoft, Bedrock, + Vertex, } sealed class ModelConfigProviderConverter : JsonConverter @@ -192,6 +923,7 @@ JsonSerializerOptions options "google" => ModelConfigProvider.Google, "microsoft" => ModelConfigProvider.Microsoft, "bedrock" => ModelConfigProvider.Bedrock, + "vertex" => ModelConfigProvider.Vertex, _ => (ModelConfigProvider)(-1), }; } @@ -211,6 +943,7 @@ JsonSerializerOptions options ModelConfigProvider.Google => "google", ModelConfigProvider.Microsoft => "microsoft", ModelConfigProvider.Bedrock => "bedrock", + ModelConfigProvider.Vertex => "vertex", _ => throw new StagehandInvalidDataException( string.Format("Invalid value '{0}' in {1}", value, nameof(value)) ), From 1dae7c22aa40af570d387fa871ee10ea4c0b9084 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 21:00:22 +0000 Subject: [PATCH 7/8] feat: Add `screenshot` option to Extract --- .stats.yml | 4 ++-- .../Sessions/SessionExtractParamsTest.cs | 20 ++++++++++++++++ .../Models/Sessions/SessionExtractParams.cs | 23 +++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 220445b..9043f09 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 8 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/stagehand-e77d6b15f0a94b16a54ef87a84d2cabe49eb11cff5ceba76f00dd788ff483eab.yml -openapi_spec_hash: a1dab7fe72a772d188a15305124ebd73 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/stagehand-80502d74c1be605e77d45ff2b54297fe34ce85dbad1e8f2dfa30ba6d09601219.yml +openapi_spec_hash: fd62f768756a400c3ecd695bfcf3845a config_hash: 1fb12ae9b478488bc1e56bfbdc210b01 diff --git a/src/Stagehand.Tests/Models/Sessions/SessionExtractParamsTest.cs b/src/Stagehand.Tests/Models/Sessions/SessionExtractParamsTest.cs index a888c3b..db68005 100644 --- a/src/Stagehand.Tests/Models/Sessions/SessionExtractParamsTest.cs +++ b/src/Stagehand.Tests/Models/Sessions/SessionExtractParamsTest.cs @@ -51,6 +51,7 @@ public void FieldRoundtrip_Works() Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, + Screenshot = false, Selector = "#main-content", Timeout = 30000, }, @@ -97,6 +98,7 @@ public void FieldRoundtrip_Works() Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, + Screenshot = false, Selector = "#main-content", Timeout = 30000, }; @@ -206,6 +208,7 @@ public void OptionalNullableParamsUnsetAreNotSet_Works() Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, + Screenshot = false, Selector = "#main-content", Timeout = 30000, }, @@ -260,6 +263,7 @@ public void OptionalNullableParamsSetToNullAreSetToNull_Works() Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, + Screenshot = false, Selector = "#main-content", Timeout = 30000, }, @@ -364,6 +368,7 @@ public void CopyConstructor_Works() Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, + Screenshot = false, Selector = "#main-content", Timeout = 30000, }, @@ -418,6 +423,7 @@ public void FieldRoundtrip_Works() Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, + Screenshot = false, Selector = "#main-content", Timeout = 30000, }; @@ -453,6 +459,7 @@ public void FieldRoundtrip_Works() Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; + bool expectedScreenshot = false; string expectedSelector = "#main-content"; double expectedTimeout = 30000; @@ -463,6 +470,7 @@ public void FieldRoundtrip_Works() Assert.Equal(expectedIgnoreSelectors[i], model.IgnoreSelectors[i]); } Assert.Equal(expectedModel, model.Model); + Assert.Equal(expectedScreenshot, model.Screenshot); Assert.Equal(expectedSelector, model.Selector); Assert.Equal(expectedTimeout, model.Timeout); } @@ -503,6 +511,7 @@ public void SerializationRoundtrip_Works() Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, + Screenshot = false, Selector = "#main-content", Timeout = 30000, }; @@ -552,6 +561,7 @@ public void FieldRoundtripThroughSerialization_Works() Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, + Screenshot = false, Selector = "#main-content", Timeout = 30000, }; @@ -594,6 +604,7 @@ public void FieldRoundtripThroughSerialization_Works() Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }; + bool expectedScreenshot = false; string expectedSelector = "#main-content"; double expectedTimeout = 30000; @@ -604,6 +615,7 @@ public void FieldRoundtripThroughSerialization_Works() Assert.Equal(expectedIgnoreSelectors[i], deserialized.IgnoreSelectors[i]); } Assert.Equal(expectedModel, deserialized.Model); + Assert.Equal(expectedScreenshot, deserialized.Screenshot); Assert.Equal(expectedSelector, deserialized.Selector); Assert.Equal(expectedTimeout, deserialized.Timeout); } @@ -644,6 +656,7 @@ public void Validation_Works() Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, + Screenshot = false, Selector = "#main-content", Timeout = 30000, }; @@ -660,6 +673,8 @@ public void OptionalNonNullablePropertiesUnsetAreNotSet_Works() Assert.False(model.RawData.ContainsKey("ignoreSelectors")); Assert.Null(model.Model); Assert.False(model.RawData.ContainsKey("model")); + Assert.Null(model.Screenshot); + Assert.False(model.RawData.ContainsKey("screenshot")); Assert.Null(model.Selector); Assert.False(model.RawData.ContainsKey("selector")); Assert.Null(model.Timeout); @@ -682,6 +697,7 @@ public void OptionalNonNullablePropertiesSetToNullAreNotSet_Works() // Null should be interpreted as omitted for these properties IgnoreSelectors = null, Model = null, + Screenshot = null, Selector = null, Timeout = null, }; @@ -690,6 +706,8 @@ public void OptionalNonNullablePropertiesSetToNullAreNotSet_Works() Assert.False(model.RawData.ContainsKey("ignoreSelectors")); Assert.Null(model.Model); Assert.False(model.RawData.ContainsKey("model")); + Assert.Null(model.Screenshot); + Assert.False(model.RawData.ContainsKey("screenshot")); Assert.Null(model.Selector); Assert.False(model.RawData.ContainsKey("selector")); Assert.Null(model.Timeout); @@ -704,6 +722,7 @@ public void OptionalNonNullablePropertiesSetToNullValidation_Works() // Null should be interpreted as omitted for these properties IgnoreSelectors = null, Model = null, + Screenshot = null, Selector = null, Timeout = null, }; @@ -747,6 +766,7 @@ public void CopyConstructor_Works() Project = "my-gcp-project", Provider = ModelConfigProvider.OpenAI, }, + Screenshot = false, Selector = "#main-content", Timeout = 30000, }; diff --git a/src/Stagehand/Models/Sessions/SessionExtractParams.cs b/src/Stagehand/Models/Sessions/SessionExtractParams.cs index a549d13..953fcd0 100644 --- a/src/Stagehand/Models/Sessions/SessionExtractParams.cs +++ b/src/Stagehand/Models/Sessions/SessionExtractParams.cs @@ -301,6 +301,28 @@ public SessionExtractParamsOptionsModel? Model } } + /// + /// When true, include a screenshot of the current viewport in the extraction + /// LLM call. Defaults to false. + /// + public bool? Screenshot + { + get + { + this._rawData.Freeze(); + return this._rawData.GetNullableStruct("screenshot"); + } + init + { + if (value == null) + { + return; + } + + this._rawData.Set("screenshot", value); + } + } + /// /// CSS selector to scope extraction to a specific element /// @@ -348,6 +370,7 @@ public override void Validate() { _ = this.IgnoreSelectors; this.Model?.Validate(); + _ = this.Screenshot; _ = this.Selector; _ = this.Timeout; } From 244b0c4e344b49fa651d33f277b3449db7508847 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 21:00:49 +0000 Subject: [PATCH 8/8] release: 3.21.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 15 +++++++++++++++ src/Stagehand/Stagehand.csproj | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d11c8fc..eba8a04 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.20.0" + ".": "3.21.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 65365ce..9b90249 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 3.21.0 (2026-05-20) + +Full Changelog: [v3.20.0...v3.21.0](https://github.com/browserbase/stagehand-net/compare/v3.20.0...v3.21.0) + +### Features + +* [feat]: add `ignoreSelectors` to `observe()` ([37f80f3](https://github.com/browserbase/stagehand-net/commit/37f80f3936fc9a85e6e6079381c41ef4e3793472)) +* Add `screenshot` option to Extract ([1dae7c2](https://github.com/browserbase/stagehand-net/commit/1dae7c22aa40af570d387fa871ee10ea4c0b9084)) +* STG-1756 add Vertex auth params to Stagehand spec ([8fa4926](https://github.com/browserbase/stagehand-net/commit/8fa49262dca9eda52850f3226279b7ee97228f39)) + + +### Bug Fixes + +* **internal:** disable default HttpClient timeout as we have our own ([c20c6e0](https://github.com/browserbase/stagehand-net/commit/c20c6e01fb40ff0ab9547bf733f6c735e0949e4e)) + ## 3.20.0 (2026-05-06) Full Changelog: [v3.19.3...v3.20.0](https://github.com/browserbase/stagehand-net/compare/v3.19.3...v3.20.0) diff --git a/src/Stagehand/Stagehand.csproj b/src/Stagehand/Stagehand.csproj index c81c48b..0acca9d 100644 --- a/src/Stagehand/Stagehand.csproj +++ b/src/Stagehand/Stagehand.csproj @@ -3,7 +3,7 @@ Stagehand C# Stagehand - 3.20.0 + 3.21.0 The official .NET library for the Stagehand API. Library README.md