From 113c151af8da3721bcba9a6bb2455168ee9c336c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 17:18:42 +0000 Subject: [PATCH 01/44] codegen metadata --- .stats.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index a03dd75..3467380 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-419940ce988c43313660d30a5bb5b5c2d89b3b19a0f80fe050331e0f4e8c58d2.yml -openapi_spec_hash: a621ca69697ebba7286cbf9e475c46ad -config_hash: 74111faa0876db6b053526281c444498 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-8210daebb6c6b76559ae5c0ca4c0a9189f444da8afb5db1a544395a66ae14a2f.yml +openapi_spec_hash: 2255c1c27382b4227754d64d85258a82 +config_hash: 46336540a7b06c89308171858ad8238c From fb7c6014b4f2e3fdb4a2379f086ce532006feb33 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 17:54:24 +0000 Subject: [PATCH 02/44] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 3467380..cb872bb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-8210daebb6c6b76559ae5c0ca4c0a9189f444da8afb5db1a544395a66ae14a2f.yml -openapi_spec_hash: 2255c1c27382b4227754d64d85258a82 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-991d1530002115ecec027f98cad357d39ca1ece6784f62d48e6740b8830e1104.yml +openapi_spec_hash: 303329893ced56b2c129fb9fd666144e config_hash: 46336540a7b06c89308171858ad8238c From b525f2cd230e54aeb3005c1acffe5053b22ef5df Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 18:08:57 +0000 Subject: [PATCH 03/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index cb872bb..9affb56 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-991d1530002115ecec027f98cad357d39ca1ece6784f62d48e6740b8830e1104.yml openapi_spec_hash: 303329893ced56b2c129fb9fd666144e -config_hash: 46336540a7b06c89308171858ad8238c +config_hash: 6638c79b9a4c739a7f1154eb16321d48 From 1ca0b08d3acb556ff9270dfff26dbea6c7da3e79 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 18:09:30 +0000 Subject: [PATCH 04/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 9affb56..d6bcc31 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-991d1530002115ecec027f98cad357d39ca1ece6784f62d48e6740b8830e1104.yml openapi_spec_hash: 303329893ced56b2c129fb9fd666144e -config_hash: 6638c79b9a4c739a7f1154eb16321d48 +config_hash: 1bbded545069024fcb805e1f6c6a9985 From 978a0369e1d43ae322b7477f2366f03aac34993f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 19:04:51 +0000 Subject: [PATCH 05/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index d6bcc31..156799b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-991d1530002115ecec027f98cad357d39ca1ece6784f62d48e6740b8830e1104.yml openapi_spec_hash: 303329893ced56b2c129fb9fd666144e -config_hash: 1bbded545069024fcb805e1f6c6a9985 +config_hash: 2357dc3b80abe40de8d2dc4885928306 From ede49b6c8137a59bcbfc1d2cc82a0fdda805ea11 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:42:34 +0000 Subject: [PATCH 06/44] feat(api): manual updates --- .stats.yml | 2 +- LICENSE | 202 +----------------- .../main/kotlin/stagehand.publish.gradle.kts | 2 +- .../java/com/stagehand/api/example/Main.java | 155 +++++++------- 4 files changed, 82 insertions(+), 279 deletions(-) diff --git a/.stats.yml b/.stats.yml index 156799b..01457a6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-991d1530002115ecec027f98cad357d39ca1ece6784f62d48e6740b8830e1104.yml openapi_spec_hash: 303329893ced56b2c129fb9fd666144e -config_hash: 2357dc3b80abe40de8d2dc4885928306 +config_hash: 3f1eb6ae44a4987a7c0c8a9d95035975 diff --git a/LICENSE b/LICENSE index 6b24314..a7b82c2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,7 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +Copyright 2026 stagehand - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - 1. Definitions. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2025 Stagehand - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/buildSrc/src/main/kotlin/stagehand.publish.gradle.kts b/buildSrc/src/main/kotlin/stagehand.publish.gradle.kts index facd8ed..f424b58 100644 --- a/buildSrc/src/main/kotlin/stagehand.publish.gradle.kts +++ b/buildSrc/src/main/kotlin/stagehand.publish.gradle.kts @@ -15,7 +15,7 @@ configure { licenses { license { - name.set("Apache-2.0") + name.set("MIT") } } diff --git a/stagehand-java-example/src/main/java/com/stagehand/api/example/Main.java b/stagehand-java-example/src/main/java/com/stagehand/api/example/Main.java index e2c7a9f..366386c 100644 --- a/stagehand-java-example/src/main/java/com/stagehand/api/example/Main.java +++ b/stagehand-java-example/src/main/java/com/stagehand/api/example/Main.java @@ -5,7 +5,6 @@ import com.browserbase.api.core.JsonValue; import com.browserbase.api.core.RequestOptions; import com.browserbase.api.models.sessions.*; - import java.time.Duration; import java.util.List; import java.util.Map; @@ -35,34 +34,32 @@ public static void main(String[] args) { StagehandClient client = StagehandOkHttpClient.fromEnv(); // Start a new browser session - SessionStartResponse startResponse = client.sessions().start( - SessionStartParams.builder() - .modelName("openai/gpt-5-nano") - .build() - ); + SessionStartResponse startResponse = client.sessions() + .start(SessionStartParams.builder() + .modelName("openai/gpt-5-nano") + .build()); String sessionId = startResponse.data().sessionId(); System.out.println("Session started: " + sessionId); try { // Navigate to Hacker News - client.sessions().navigate( - SessionNavigateParams.builder() - .id(sessionId) - .url("https://news.ycombinator.com") - .build() - ); + client.sessions() + .navigate(SessionNavigateParams.builder() + .id(sessionId) + .url("https://news.ycombinator.com") + .build()); System.out.println("Navigated to Hacker News"); // Observe to find possible actions - looking for the comments link - SessionObserveResponse observeResponse = client.sessions().observe( - SessionObserveParams.builder() - .id(sessionId) - .instruction("find the link to view comments for the top post") - .build() - ); - - List results = observeResponse.data().result(); + SessionObserveResponse observeResponse = client.sessions() + .observe(SessionObserveParams.builder() + .id(sessionId) + .instruction("find the link to view comments for the top post") + .build()); + + List results = + observeResponse.data().result(); System.out.println("Found " + results.size() + " possible actions"); if (results.isEmpty()) { @@ -76,36 +73,34 @@ public static void main(String[] args) { System.out.println("Acting on: " + action.description()); // Pass the structured action to Act - SessionActResponse actResponse = client.sessions().act( - SessionActParams.builder() - .id(sessionId) - .input(action) - .build() - ); + SessionActResponse actResponse = client.sessions() + .act(SessionActParams.builder().id(sessionId).input(action).build()); System.out.println("Act completed: " + actResponse.data().result().message()); // Extract data from the page // We're now on the comments page, so extract the top comment text - SessionExtractResponse extractResponse = client.sessions().extract( - SessionExtractParams.builder() - .id(sessionId) - .instruction("extract the text of the top comment on this page") - .schema(SessionExtractParams.Schema.builder() - .putAdditionalProperty("type", JsonValue.from("object")) - .putAdditionalProperty("properties", JsonValue.from(Map.of( - "commentText", Map.of( - "type", "string", - "description", "The text content of the top comment" - ), - "author", Map.of( - "type", "string", - "description", "The username of the comment author" - ) - ))) - .putAdditionalProperty("required", JsonValue.from(List.of("commentText"))) - .build()) - .build() - ); + SessionExtractResponse extractResponse = client.sessions() + .extract(SessionExtractParams.builder() + .id(sessionId) + .instruction("extract the text of the top comment on this page") + .schema(SessionExtractParams.Schema.builder() + .putAdditionalProperty("type", JsonValue.from("object")) + .putAdditionalProperty( + "properties", + JsonValue.from(Map.of( + "commentText", + Map.of( + "type", "string", + "description", + "The text content of the top comment"), + "author", + Map.of( + "type", "string", + "description", + "The username of the comment author")))) + .putAdditionalProperty("required", JsonValue.from(List.of("commentText"))) + .build()) + .build()); // Get the extracted result JsonValue extractedResult = extractResponse.data()._result(); @@ -125,42 +120,44 @@ public static void main(String[] args) { // Use the Agent to find the author's profile // Execute runs an autonomous agent that can navigate and interact with pages // Use a longer timeout (5 minutes) since agent execution can take a while - SessionExecuteResponse executeResponse = client.sessions().execute( - SessionExecuteParams.builder() - .id(sessionId) - .executeOptions(SessionExecuteParams.ExecuteOptions.builder() - .instruction(String.format( - "Find any personal website, GitHub, LinkedIn, or other best profile URL for the Hacker News user '%s'. " + - "Click on their username to go to their profile page and look for any links they have shared. " + - "Use Google Search with their username or other details from their profile if you dont find any direct links.", - author - )) - .maxSteps(15.0) - .build()) - .agentConfig(SessionExecuteParams.AgentConfig.builder() - .model(ModelConfig.ofModelConfigObject( - ModelConfig.ModelConfigObject.builder() - .modelName("openai/gpt-5-nano") - .apiKey(System.getenv("MODEL_API_KEY")) - .build() - )) - .cua(false) - .build()) - .build(), - RequestOptions.builder().timeout(Duration.ofMinutes(5)).build() - ); - - System.out.println("Agent completed: " + executeResponse.data().result().message()); - System.out.println("Agent success: " + executeResponse.data().result().success()); - System.out.println("Agent actions taken: " + executeResponse.data().result().actions().size()); + SessionExecuteResponse executeResponse = client.sessions() + .execute( + SessionExecuteParams.builder() + .id(sessionId) + .executeOptions(SessionExecuteParams.ExecuteOptions.builder() + .instruction(String.format( + "Find any personal website, GitHub, LinkedIn, or other best" + + " profile URL for the Hacker News user '%s'. Click on their" + + " username to go to their profile page and look for any" + + " links they have shared. Use Google Search with their" + + " username or other details from their profile if you dont" + + " find any direct links.", + author)) + .maxSteps(15.0) + .build()) + .agentConfig(SessionExecuteParams.AgentConfig.builder() + .model(ModelConfig.ofModelConfigObject( + ModelConfig.ModelConfigObject.builder() + .modelName("openai/gpt-5-nano") + .apiKey(System.getenv("MODEL_API_KEY")) + .build())) + .cua(false) + .build()) + .build(), + RequestOptions.builder() + .timeout(Duration.ofMinutes(5)) + .build()); + + System.out.println( + "Agent completed: " + executeResponse.data().result().message()); + System.out.println( + "Agent success: " + executeResponse.data().result().success()); + System.out.println("Agent actions taken: " + + executeResponse.data().result().actions().size()); } finally { // End the session to clean up resources - client.sessions().end( - SessionEndParams.builder() - .id(sessionId) - .build() - ); + client.sessions().end(SessionEndParams.builder().id(sessionId).build()); System.out.println("Session ended"); } } From cd4901f33b3a0418478dc756d3f229c87aa4c848 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 22:10:12 +0000 Subject: [PATCH 07/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 01457a6..2f45fa4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-991d1530002115ecec027f98cad357d39ca1ece6784f62d48e6740b8830e1104.yml openapi_spec_hash: 303329893ced56b2c129fb9fd666144e -config_hash: 3f1eb6ae44a4987a7c0c8a9d95035975 +config_hash: 0e6aecd610184fc5d011e30698df9a56 From 0fd25f863ae26f1c347ebc6d94eac21fc2405542 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 22:13:52 +0000 Subject: [PATCH 08/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 2f45fa4..7650758 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-991d1530002115ecec027f98cad357d39ca1ece6784f62d48e6740b8830e1104.yml openapi_spec_hash: 303329893ced56b2c129fb9fd666144e -config_hash: 0e6aecd610184fc5d011e30698df9a56 +config_hash: 3906b0eb870b3efb11f24b0069668c94 From 2eeff3f873b660ee82531f9aa6d3c8d41a0e1e15 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 22:15:52 +0000 Subject: [PATCH 09/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 7650758..f4e4a76 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-991d1530002115ecec027f98cad357d39ca1ece6784f62d48e6740b8830e1104.yml openapi_spec_hash: 303329893ced56b2c129fb9fd666144e -config_hash: 3906b0eb870b3efb11f24b0069668c94 +config_hash: 3ad8653f1cf35720ac8f2f8643969e15 From 0c5d92201b8936d7c51b7c488d35113a57cfe52d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 22:17:43 +0000 Subject: [PATCH 10/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index f4e4a76..936a1e9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-991d1530002115ecec027f98cad357d39ca1ece6784f62d48e6740b8830e1104.yml openapi_spec_hash: 303329893ced56b2c129fb9fd666144e -config_hash: 3ad8653f1cf35720ac8f2f8643969e15 +config_hash: d2eff937029f6230c90a974d0b64e024 From 0e9432ff81563486ab7d2975b6090204e42b4b5c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 22:19:29 +0000 Subject: [PATCH 11/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 936a1e9..fbdd509 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-991d1530002115ecec027f98cad357d39ca1ece6784f62d48e6740b8830e1104.yml openapi_spec_hash: 303329893ced56b2c129fb9fd666144e -config_hash: d2eff937029f6230c90a974d0b64e024 +config_hash: dbb44d668902bdea601850a0db94302b From ac6342a2fe554d7c068834c0b88567ae00c4d460 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 22:30:48 +0000 Subject: [PATCH 12/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index fbdd509..8a75e9b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-991d1530002115ecec027f98cad357d39ca1ece6784f62d48e6740b8830e1104.yml openapi_spec_hash: 303329893ced56b2c129fb9fd666144e -config_hash: dbb44d668902bdea601850a0db94302b +config_hash: e8739aa4b8fb23a89fbf1f3b1ba2a52f From 405811ce9662ccdd018c798ee07817041eab3399 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 22:37:01 +0000 Subject: [PATCH 13/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 8a75e9b..ee5126e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-991d1530002115ecec027f98cad357d39ca1ece6784f62d48e6740b8830e1104.yml openapi_spec_hash: 303329893ced56b2c129fb9fd666144e -config_hash: e8739aa4b8fb23a89fbf1f3b1ba2a52f +config_hash: d4df55e4b30aac2d8d0982be97f837c4 From ec581cf6dcb166547521e8e64b1239876fd5a527 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 00:02:29 +0000 Subject: [PATCH 14/44] feat: x-stainless-any fix, optional frame id, ModelConfigString fix --- .stats.yml | 4 +- .../api/models/sessions/ModelConfig.kt | 59 +++++++++++-------- .../api/models/sessions/SessionActParams.kt | 45 ++++++-------- .../api/models/sessions/SessionEndParams.kt | 21 +------ .../models/sessions/SessionExecuteParams.kt | 45 ++++++-------- .../models/sessions/SessionExtractParams.kt | 45 ++++++-------- .../models/sessions/SessionNavigateParams.kt | 31 ++++------ .../models/sessions/SessionObserveParams.kt | 45 ++++++-------- .../api/models/sessions/SessionStartParams.kt | 38 +++++------- .../api/models/sessions/ModelConfigTest.kt | 18 +++--- .../models/sessions/SessionActParamsTest.kt | 20 ++----- .../models/sessions/SessionEndParamsTest.kt | 12 +--- .../sessions/SessionExecuteParamsTest.kt | 20 ++----- .../sessions/SessionExtractParamsTest.kt | 20 ++----- .../sessions/SessionNavigateParamsTest.kt | 12 +--- .../sessions/SessionObserveParamsTest.kt | 20 ++----- .../models/sessions/SessionStartParamsTest.kt | 12 +--- .../api/services/ErrorHandlingTest.kt | 18 ------ .../api/services/ServiceParamsTest.kt | 5 +- .../services/async/SessionServiceAsyncTest.kt | 28 +++------ .../services/blocking/SessionServiceTest.kt | 28 +++------ .../api/proguard/ProGuardCompatibilityTest.kt | 2 +- 22 files changed, 185 insertions(+), 363 deletions(-) diff --git a/.stats.yml b/.stats.yml index ee5126e..ee1de31 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-991d1530002115ecec027f98cad357d39ca1ece6784f62d48e6740b8830e1104.yml -openapi_spec_hash: 303329893ced56b2c129fb9fd666144e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-07032b695bc66ecd76328d936b41e01cfad508a870928c09c185f5faa5ea66ab.yml +openapi_spec_hash: fca4b895ce36ad547fb015c3dd38821f config_hash: d4df55e4b30aac2d8d0982be97f837c4 diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/ModelConfig.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/ModelConfig.kt index ce0785b..f8467e9 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/ModelConfig.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/ModelConfig.kt @@ -30,34 +30,35 @@ import java.util.Optional import kotlin.jvm.optionals.getOrNull /** - * Model name string with provider prefix (e.g., 'openai/gpt-5-nano', 'anthropic/claude-4.5-opus') + * Model name string with provider prefix. Always use the format 'provider/model-name' (e.g., + * 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', 'google/gemini-2.0-flash') */ @JsonDeserialize(using = ModelConfig.Deserializer::class) @JsonSerialize(using = ModelConfig.Serializer::class) class ModelConfig private constructor( - private val name: String? = null, + private val string: String? = null, private val modelConfigObject: ModelConfigObject? = null, private val _json: JsonValue? = null, ) { /** - * Model name string with provider prefix (e.g., 'openai/gpt-5-nano', - * 'anthropic/claude-4.5-opus') + * Model name string with provider prefix. Always use the format 'provider/model-name' (e.g., + * 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', 'google/gemini-2.0-flash') */ - fun name(): Optional = Optional.ofNullable(name) + fun string(): Optional = Optional.ofNullable(string) fun modelConfigObject(): Optional = Optional.ofNullable(modelConfigObject) - fun isName(): Boolean = name != null + fun isString(): Boolean = string != null fun isModelConfigObject(): Boolean = modelConfigObject != null /** - * Model name string with provider prefix (e.g., 'openai/gpt-5-nano', - * 'anthropic/claude-4.5-opus') + * Model name string with provider prefix. Always use the format 'provider/model-name' (e.g., + * 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', 'google/gemini-2.0-flash') */ - fun asName(): String = name.getOrThrow("name") + fun asString(): String = string.getOrThrow("string") fun asModelConfigObject(): ModelConfigObject = modelConfigObject.getOrThrow("modelConfigObject") @@ -65,7 +66,7 @@ private constructor( fun accept(visitor: Visitor): T = when { - name != null -> visitor.visitName(name) + string != null -> visitor.visitString(string) modelConfigObject != null -> visitor.visitModelConfigObject(modelConfigObject) else -> visitor.unknown(_json) } @@ -79,7 +80,7 @@ private constructor( accept( object : Visitor { - override fun visitName(name: String) {} + override fun visitString(string: String) {} override fun visitModelConfigObject(modelConfigObject: ModelConfigObject) { modelConfigObject.validate() @@ -106,7 +107,7 @@ private constructor( internal fun validity(): Int = accept( object : Visitor { - override fun visitName(name: String) = 1 + override fun visitString(string: String) = 1 override fun visitModelConfigObject(modelConfigObject: ModelConfigObject) = modelConfigObject.validity() @@ -121,15 +122,15 @@ private constructor( } return other is ModelConfig && - name == other.name && + string == other.string && modelConfigObject == other.modelConfigObject } - override fun hashCode(): Int = Objects.hash(name, modelConfigObject) + override fun hashCode(): Int = Objects.hash(string, modelConfigObject) override fun toString(): String = when { - name != null -> "ModelConfig{name=$name}" + string != null -> "ModelConfig{string=$string}" modelConfigObject != null -> "ModelConfig{modelConfigObject=$modelConfigObject}" _json != null -> "ModelConfig{_unknown=$_json}" else -> throw IllegalStateException("Invalid ModelConfig") @@ -138,10 +139,11 @@ private constructor( companion object { /** - * Model name string with provider prefix (e.g., 'openai/gpt-5-nano', - * 'anthropic/claude-4.5-opus') + * Model name string with provider prefix. Always use the format 'provider/model-name' + * (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', + * 'google/gemini-2.0-flash') */ - @JvmStatic fun ofName(name: String) = ModelConfig(name = name) + @JvmStatic fun ofString(string: String) = ModelConfig(string = string) @JvmStatic fun ofModelConfigObject(modelConfigObject: ModelConfigObject) = @@ -154,10 +156,11 @@ private constructor( interface Visitor { /** - * Model name string with provider prefix (e.g., 'openai/gpt-5-nano', - * 'anthropic/claude-4.5-opus') + * Model name string with provider prefix. Always use the format 'provider/model-name' + * (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', + * 'google/gemini-2.0-flash') */ - fun visitName(name: String): T + fun visitString(string: String): T fun visitModelConfigObject(modelConfigObject: ModelConfigObject): T @@ -186,7 +189,7 @@ private constructor( ModelConfig(modelConfigObject = it, _json = json) }, tryDeserialize(node, jacksonTypeRef())?.let { - ModelConfig(name = it, _json = json) + ModelConfig(string = it, _json = json) }, ) .filterNotNull() @@ -212,7 +215,7 @@ private constructor( provider: SerializerProvider, ) { when { - value.name != null -> generator.writeObject(value.name) + value.string != null -> generator.writeObject(value.string) value.modelConfigObject != null -> generator.writeObject(value.modelConfigObject) value._json != null -> generator.writeObject(value._json) else -> throw IllegalStateException("Invalid ModelConfig") @@ -243,7 +246,9 @@ private constructor( ) : this(modelName, apiKey, baseUrl, provider, mutableMapOf()) /** - * Model name string (e.g., 'openai/gpt-5-nano', 'anthropic/claude-4.5-opus') + * Model name string with provider prefix. Always use the format 'provider/model-name' + * (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', + * 'google/gemini-2.0-flash') * * @throws StagehandInvalidDataException if the JSON field has an unexpected type or is * unexpectedly missing or null (e.g. if the server responded with an unexpected value). @@ -345,7 +350,11 @@ private constructor( additionalProperties = modelConfigObject.additionalProperties.toMutableMap() } - /** Model name string (e.g., 'openai/gpt-5-nano', 'anthropic/claude-4.5-opus') */ + /** + * Model name string with provider prefix. Always use the format 'provider/model-name' + * (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', + * 'google/gemini-2.0-flash') + */ fun modelName(modelName: String) = modelName(JsonField.of(modelName)) /** diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt index 182931c..92df30c 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt @@ -28,8 +28,6 @@ import com.fasterxml.jackson.databind.SerializerProvider import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonSerialize import com.fasterxml.jackson.module.kotlin.jacksonTypeRef -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter import java.util.Collections import java.util.Objects import java.util.Optional @@ -39,7 +37,6 @@ import kotlin.jvm.optionals.getOrNull class SessionActParams private constructor( private val id: String?, - private val xSentAt: OffsetDateTime?, private val xStreamResponse: XStreamResponse?, private val body: Body, private val additionalHeaders: Headers, @@ -49,9 +46,6 @@ private constructor( /** Unique session identifier */ fun id(): Optional = Optional.ofNullable(id) - /** ISO timestamp when request was sent */ - fun xSentAt(): Optional = Optional.ofNullable(xSentAt) - /** Whether to stream the response via SSE */ fun xStreamResponse(): Optional = Optional.ofNullable(xStreamResponse) @@ -125,7 +119,6 @@ private constructor( class Builder internal constructor() { private var id: String? = null - private var xSentAt: OffsetDateTime? = null private var xStreamResponse: XStreamResponse? = null private var body: Body.Builder = Body.builder() private var additionalHeaders: Headers.Builder = Headers.builder() @@ -134,7 +127,6 @@ private constructor( @JvmSynthetic internal fun from(sessionActParams: SessionActParams) = apply { id = sessionActParams.id - xSentAt = sessionActParams.xSentAt xStreamResponse = sessionActParams.xStreamResponse body = sessionActParams.body.toBuilder() additionalHeaders = sessionActParams.additionalHeaders.toBuilder() @@ -147,12 +139,6 @@ private constructor( /** Alias for calling [Builder.id] with `id.orElse(null)`. */ fun id(id: Optional) = id(id.getOrNull()) - /** ISO timestamp when request was sent */ - fun xSentAt(xSentAt: OffsetDateTime?) = apply { this.xSentAt = xSentAt } - - /** Alias for calling [Builder.xSentAt] with `xSentAt.orElse(null)`. */ - fun xSentAt(xSentAt: Optional) = xSentAt(xSentAt.getOrNull()) - /** Whether to stream the response via SSE */ fun xStreamResponse(xStreamResponse: XStreamResponse?) = apply { this.xStreamResponse = xStreamResponse @@ -191,7 +177,10 @@ private constructor( fun input(action: Action) = apply { body.input(action) } /** Target frame ID for the action */ - fun frameId(frameId: String) = apply { body.frameId(frameId) } + fun frameId(frameId: String?) = apply { body.frameId(frameId) } + + /** Alias for calling [Builder.frameId] with `frameId.orElse(null)`. */ + fun frameId(frameId: Optional) = frameId(frameId.getOrNull()) /** * Sets [Builder.frameId] to an arbitrary JSON value. @@ -343,7 +332,6 @@ private constructor( fun build(): SessionActParams = SessionActParams( id, - xSentAt, xStreamResponse, body.build(), additionalHeaders.build(), @@ -362,7 +350,6 @@ private constructor( override fun _headers(): Headers = Headers.builder() .apply { - xSentAt?.let { put("x-sent-at", DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(it)) } xStreamResponse?.let { put("x-stream-response", it.toString()) } putAll(additionalHeaders) } @@ -489,7 +476,10 @@ private constructor( fun input(action: Action) = input(Input.ofAction(action)) /** Target frame ID for the action */ - fun frameId(frameId: String) = frameId(JsonField.of(frameId)) + fun frameId(frameId: String?) = frameId(JsonField.ofNullable(frameId)) + + /** Alias for calling [Builder.frameId] with `frameId.orElse(null)`. */ + fun frameId(frameId: Optional) = frameId(frameId.getOrNull()) /** * Sets [Builder.frameId] to an arbitrary JSON value. @@ -799,8 +789,9 @@ private constructor( ) : this(model, timeout, variables, mutableMapOf()) /** - * Model name string with provider prefix (e.g., 'openai/gpt-5-nano', - * 'anthropic/claude-4.5-opus') + * Model name string with provider prefix. Always use the format 'provider/model-name' + * (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', + * 'google/gemini-2.0-flash') * * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if * the server responded with an unexpected value). @@ -881,8 +872,9 @@ private constructor( } /** - * Model name string with provider prefix (e.g., 'openai/gpt-5-nano', - * 'anthropic/claude-4.5-opus') + * Model name string with provider prefix. Always use the format 'provider/model-name' + * (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', + * 'google/gemini-2.0-flash') */ fun model(model: ModelConfig) = model(JsonField.of(model)) @@ -895,8 +887,8 @@ private constructor( */ fun model(model: JsonField) = apply { this.model = model } - /** Alias for calling [model] with `ModelConfig.ofName(name)`. */ - fun model(name: String) = model(ModelConfig.ofName(name)) + /** Alias for calling [model] with `ModelConfig.ofString(string)`. */ + fun model(string: String) = model(ModelConfig.ofString(string)) /** * Alias for calling [model] with `ModelConfig.ofModelConfigObject(modelConfigObject)`. @@ -1253,7 +1245,6 @@ private constructor( return other is SessionActParams && id == other.id && - xSentAt == other.xSentAt && xStreamResponse == other.xStreamResponse && body == other.body && additionalHeaders == other.additionalHeaders && @@ -1261,8 +1252,8 @@ private constructor( } override fun hashCode(): Int = - Objects.hash(id, xSentAt, xStreamResponse, body, additionalHeaders, additionalQueryParams) + Objects.hash(id, xStreamResponse, body, additionalHeaders, additionalQueryParams) override fun toString() = - "SessionActParams{id=$id, xSentAt=$xSentAt, xStreamResponse=$xStreamResponse, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" + "SessionActParams{id=$id, xStreamResponse=$xStreamResponse, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" } diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionEndParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionEndParams.kt index c861cc2..2114c89 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionEndParams.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionEndParams.kt @@ -15,8 +15,6 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter import com.fasterxml.jackson.annotation.JsonAnySetter import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonProperty -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter import java.util.Collections import java.util.Objects import java.util.Optional @@ -26,7 +24,6 @@ import kotlin.jvm.optionals.getOrNull class SessionEndParams private constructor( private val id: String?, - private val xSentAt: OffsetDateTime?, private val xStreamResponse: XStreamResponse?, private val body: Body, private val additionalHeaders: Headers, @@ -36,9 +33,6 @@ private constructor( /** Unique session identifier */ fun id(): Optional = Optional.ofNullable(id) - /** ISO timestamp when request was sent */ - fun xSentAt(): Optional = Optional.ofNullable(xSentAt) - /** Whether to stream the response via SSE */ fun xStreamResponse(): Optional = Optional.ofNullable(xStreamResponse) @@ -66,7 +60,6 @@ private constructor( class Builder internal constructor() { private var id: String? = null - private var xSentAt: OffsetDateTime? = null private var xStreamResponse: XStreamResponse? = null private var body: Body.Builder = Body.builder() private var additionalHeaders: Headers.Builder = Headers.builder() @@ -75,7 +68,6 @@ private constructor( @JvmSynthetic internal fun from(sessionEndParams: SessionEndParams) = apply { id = sessionEndParams.id - xSentAt = sessionEndParams.xSentAt xStreamResponse = sessionEndParams.xStreamResponse body = sessionEndParams.body.toBuilder() additionalHeaders = sessionEndParams.additionalHeaders.toBuilder() @@ -88,12 +80,6 @@ private constructor( /** Alias for calling [Builder.id] with `id.orElse(null)`. */ fun id(id: Optional) = id(id.getOrNull()) - /** ISO timestamp when request was sent */ - fun xSentAt(xSentAt: OffsetDateTime?) = apply { this.xSentAt = xSentAt } - - /** Alias for calling [Builder.xSentAt] with `xSentAt.orElse(null)`. */ - fun xSentAt(xSentAt: Optional) = xSentAt(xSentAt.getOrNull()) - /** Whether to stream the response via SSE */ fun xStreamResponse(xStreamResponse: XStreamResponse?) = apply { this.xStreamResponse = xStreamResponse @@ -239,7 +225,6 @@ private constructor( fun build(): SessionEndParams = SessionEndParams( id, - xSentAt, xStreamResponse, body.build(), additionalHeaders.build(), @@ -258,7 +243,6 @@ private constructor( override fun _headers(): Headers = Headers.builder() .apply { - xSentAt?.let { put("x-sent-at", DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(it)) } xStreamResponse?.let { put("x-stream-response", it.toString()) } putAll(additionalHeaders) } @@ -522,7 +506,6 @@ private constructor( return other is SessionEndParams && id == other.id && - xSentAt == other.xSentAt && xStreamResponse == other.xStreamResponse && body == other.body && additionalHeaders == other.additionalHeaders && @@ -530,8 +513,8 @@ private constructor( } override fun hashCode(): Int = - Objects.hash(id, xSentAt, xStreamResponse, body, additionalHeaders, additionalQueryParams) + Objects.hash(id, xStreamResponse, body, additionalHeaders, additionalQueryParams) override fun toString() = - "SessionEndParams{id=$id, xSentAt=$xSentAt, xStreamResponse=$xStreamResponse, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" + "SessionEndParams{id=$id, xStreamResponse=$xStreamResponse, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" } diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExecuteParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExecuteParams.kt index 813b432..65c7508 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExecuteParams.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExecuteParams.kt @@ -16,8 +16,6 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter import com.fasterxml.jackson.annotation.JsonAnySetter import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonProperty -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter import java.util.Collections import java.util.Objects import java.util.Optional @@ -27,7 +25,6 @@ import kotlin.jvm.optionals.getOrNull class SessionExecuteParams private constructor( private val id: String?, - private val xSentAt: OffsetDateTime?, private val xStreamResponse: XStreamResponse?, private val body: Body, private val additionalHeaders: Headers, @@ -37,9 +34,6 @@ private constructor( /** Unique session identifier */ fun id(): Optional = Optional.ofNullable(id) - /** ISO timestamp when request was sent */ - fun xSentAt(): Optional = Optional.ofNullable(xSentAt) - /** Whether to stream the response via SSE */ fun xStreamResponse(): Optional = Optional.ofNullable(xStreamResponse) @@ -112,7 +106,6 @@ private constructor( class Builder internal constructor() { private var id: String? = null - private var xSentAt: OffsetDateTime? = null private var xStreamResponse: XStreamResponse? = null private var body: Body.Builder = Body.builder() private var additionalHeaders: Headers.Builder = Headers.builder() @@ -121,7 +114,6 @@ private constructor( @JvmSynthetic internal fun from(sessionExecuteParams: SessionExecuteParams) = apply { id = sessionExecuteParams.id - xSentAt = sessionExecuteParams.xSentAt xStreamResponse = sessionExecuteParams.xStreamResponse body = sessionExecuteParams.body.toBuilder() additionalHeaders = sessionExecuteParams.additionalHeaders.toBuilder() @@ -134,12 +126,6 @@ private constructor( /** Alias for calling [Builder.id] with `id.orElse(null)`. */ fun id(id: Optional) = id(id.getOrNull()) - /** ISO timestamp when request was sent */ - fun xSentAt(xSentAt: OffsetDateTime?) = apply { this.xSentAt = xSentAt } - - /** Alias for calling [Builder.xSentAt] with `xSentAt.orElse(null)`. */ - fun xSentAt(xSentAt: Optional) = xSentAt(xSentAt.getOrNull()) - /** Whether to stream the response via SSE */ fun xStreamResponse(xStreamResponse: XStreamResponse?) = apply { this.xStreamResponse = xStreamResponse @@ -189,7 +175,10 @@ private constructor( } /** Target frame ID for the agent */ - fun frameId(frameId: String) = apply { body.frameId(frameId) } + fun frameId(frameId: String?) = apply { body.frameId(frameId) } + + /** Alias for calling [Builder.frameId] with `frameId.orElse(null)`. */ + fun frameId(frameId: Optional) = frameId(frameId.getOrNull()) /** * Sets [Builder.frameId] to an arbitrary JSON value. @@ -332,7 +321,6 @@ private constructor( fun build(): SessionExecuteParams = SessionExecuteParams( id, - xSentAt, xStreamResponse, body.build(), additionalHeaders.build(), @@ -351,7 +339,6 @@ private constructor( override fun _headers(): Headers = Headers.builder() .apply { - xSentAt?.let { put("x-sent-at", DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(it)) } xStreamResponse?.let { put("x-stream-response", it.toString()) } putAll(additionalHeaders) } @@ -495,7 +482,10 @@ private constructor( } /** Target frame ID for the agent */ - fun frameId(frameId: String) = frameId(JsonField.of(frameId)) + fun frameId(frameId: String?) = frameId(JsonField.ofNullable(frameId)) + + /** Alias for calling [Builder.frameId] with `frameId.orElse(null)`. */ + fun frameId(frameId: Optional) = frameId(frameId.getOrNull()) /** * Sets [Builder.frameId] to an arbitrary JSON value. @@ -633,8 +623,9 @@ private constructor( fun cua(): Optional = cua.getOptional("cua") /** - * Model name string with provider prefix (e.g., 'openai/gpt-5-nano', - * 'anthropic/claude-4.5-opus') + * Model name string with provider prefix. Always use the format 'provider/model-name' + * (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', + * 'google/gemini-2.0-flash') * * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if * the server responded with an unexpected value). @@ -737,8 +728,9 @@ private constructor( fun cua(cua: JsonField) = apply { this.cua = cua } /** - * Model name string with provider prefix (e.g., 'openai/gpt-5-nano', - * 'anthropic/claude-4.5-opus') + * Model name string with provider prefix. Always use the format 'provider/model-name' + * (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', + * 'google/gemini-2.0-flash') */ fun model(model: ModelConfig) = model(JsonField.of(model)) @@ -751,8 +743,8 @@ private constructor( */ fun model(model: JsonField) = apply { this.model = model } - /** Alias for calling [model] with `ModelConfig.ofName(name)`. */ - fun model(name: String) = model(ModelConfig.ofName(name)) + /** Alias for calling [model] with `ModelConfig.ofString(string)`. */ + fun model(string: String) = model(ModelConfig.ofString(string)) /** * Alias for calling [model] with `ModelConfig.ofModelConfigObject(modelConfigObject)`. @@ -1401,7 +1393,6 @@ private constructor( return other is SessionExecuteParams && id == other.id && - xSentAt == other.xSentAt && xStreamResponse == other.xStreamResponse && body == other.body && additionalHeaders == other.additionalHeaders && @@ -1409,8 +1400,8 @@ private constructor( } override fun hashCode(): Int = - Objects.hash(id, xSentAt, xStreamResponse, body, additionalHeaders, additionalQueryParams) + Objects.hash(id, xStreamResponse, body, additionalHeaders, additionalQueryParams) override fun toString() = - "SessionExecuteParams{id=$id, xSentAt=$xSentAt, xStreamResponse=$xStreamResponse, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" + "SessionExecuteParams{id=$id, xStreamResponse=$xStreamResponse, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" } diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExtractParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExtractParams.kt index 3ef55df..45507fe 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExtractParams.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExtractParams.kt @@ -16,8 +16,6 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter import com.fasterxml.jackson.annotation.JsonAnySetter import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonProperty -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter import java.util.Collections import java.util.Objects import java.util.Optional @@ -27,7 +25,6 @@ import kotlin.jvm.optionals.getOrNull class SessionExtractParams private constructor( private val id: String?, - private val xSentAt: OffsetDateTime?, private val xStreamResponse: XStreamResponse?, private val body: Body, private val additionalHeaders: Headers, @@ -37,9 +34,6 @@ private constructor( /** Unique session identifier */ fun id(): Optional = Optional.ofNullable(id) - /** ISO timestamp when request was sent */ - fun xSentAt(): Optional = Optional.ofNullable(xSentAt) - /** Whether to stream the response via SSE */ fun xStreamResponse(): Optional = Optional.ofNullable(xStreamResponse) @@ -123,7 +117,6 @@ private constructor( class Builder internal constructor() { private var id: String? = null - private var xSentAt: OffsetDateTime? = null private var xStreamResponse: XStreamResponse? = null private var body: Body.Builder = Body.builder() private var additionalHeaders: Headers.Builder = Headers.builder() @@ -132,7 +125,6 @@ private constructor( @JvmSynthetic internal fun from(sessionExtractParams: SessionExtractParams) = apply { id = sessionExtractParams.id - xSentAt = sessionExtractParams.xSentAt xStreamResponse = sessionExtractParams.xStreamResponse body = sessionExtractParams.body.toBuilder() additionalHeaders = sessionExtractParams.additionalHeaders.toBuilder() @@ -145,12 +137,6 @@ private constructor( /** Alias for calling [Builder.id] with `id.orElse(null)`. */ fun id(id: Optional) = id(id.getOrNull()) - /** ISO timestamp when request was sent */ - fun xSentAt(xSentAt: OffsetDateTime?) = apply { this.xSentAt = xSentAt } - - /** Alias for calling [Builder.xSentAt] with `xSentAt.orElse(null)`. */ - fun xSentAt(xSentAt: Optional) = xSentAt(xSentAt.getOrNull()) - /** Whether to stream the response via SSE */ fun xStreamResponse(xStreamResponse: XStreamResponse?) = apply { this.xStreamResponse = xStreamResponse @@ -173,7 +159,10 @@ private constructor( fun body(body: Body) = apply { this.body = body.toBuilder() } /** Target frame ID for the extraction */ - fun frameId(frameId: String) = apply { body.frameId(frameId) } + fun frameId(frameId: String?) = apply { body.frameId(frameId) } + + /** Alias for calling [Builder.frameId] with `frameId.orElse(null)`. */ + fun frameId(frameId: Optional) = frameId(frameId.getOrNull()) /** * Sets [Builder.frameId] to an arbitrary JSON value. @@ -341,7 +330,6 @@ private constructor( fun build(): SessionExtractParams = SessionExtractParams( id, - xSentAt, xStreamResponse, body.build(), additionalHeaders.build(), @@ -360,7 +348,6 @@ private constructor( override fun _headers(): Headers = Headers.builder() .apply { - xSentAt?.let { put("x-sent-at", DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(it)) } xStreamResponse?.let { put("x-stream-response", it.toString()) } putAll(additionalHeaders) } @@ -485,7 +472,10 @@ private constructor( } /** Target frame ID for the extraction */ - fun frameId(frameId: String) = frameId(JsonField.of(frameId)) + fun frameId(frameId: String?) = frameId(JsonField.ofNullable(frameId)) + + /** Alias for calling [Builder.frameId] with `frameId.orElse(null)`. */ + fun frameId(frameId: Optional) = frameId(frameId.getOrNull()) /** * Sets [Builder.frameId] to an arbitrary JSON value. @@ -638,8 +628,9 @@ private constructor( ) : this(model, selector, timeout, mutableMapOf()) /** - * Model name string with provider prefix (e.g., 'openai/gpt-5-nano', - * 'anthropic/claude-4.5-opus') + * Model name string with provider prefix. Always use the format 'provider/model-name' + * (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', + * 'google/gemini-2.0-flash') * * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if * the server responded with an unexpected value). @@ -718,8 +709,9 @@ private constructor( } /** - * Model name string with provider prefix (e.g., 'openai/gpt-5-nano', - * 'anthropic/claude-4.5-opus') + * Model name string with provider prefix. Always use the format 'provider/model-name' + * (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', + * 'google/gemini-2.0-flash') */ fun model(model: ModelConfig) = model(JsonField.of(model)) @@ -732,8 +724,8 @@ private constructor( */ fun model(model: JsonField) = apply { this.model = model } - /** Alias for calling [model] with `ModelConfig.ofName(name)`. */ - fun model(name: String) = model(ModelConfig.ofName(name)) + /** Alias for calling [model] with `ModelConfig.ofString(string)`. */ + fun model(string: String) = model(ModelConfig.ofString(string)) /** * Alias for calling [model] with `ModelConfig.ofModelConfigObject(modelConfigObject)`. @@ -1087,7 +1079,6 @@ private constructor( return other is SessionExtractParams && id == other.id && - xSentAt == other.xSentAt && xStreamResponse == other.xStreamResponse && body == other.body && additionalHeaders == other.additionalHeaders && @@ -1095,8 +1086,8 @@ private constructor( } override fun hashCode(): Int = - Objects.hash(id, xSentAt, xStreamResponse, body, additionalHeaders, additionalQueryParams) + Objects.hash(id, xStreamResponse, body, additionalHeaders, additionalQueryParams) override fun toString() = - "SessionExtractParams{id=$id, xSentAt=$xSentAt, xStreamResponse=$xStreamResponse, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" + "SessionExtractParams{id=$id, xStreamResponse=$xStreamResponse, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" } diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionNavigateParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionNavigateParams.kt index 2d63849..d848600 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionNavigateParams.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionNavigateParams.kt @@ -16,8 +16,6 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter import com.fasterxml.jackson.annotation.JsonAnySetter import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonProperty -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter import java.util.Collections import java.util.Objects import java.util.Optional @@ -27,7 +25,6 @@ import kotlin.jvm.optionals.getOrNull class SessionNavigateParams private constructor( private val id: String?, - private val xSentAt: OffsetDateTime?, private val xStreamResponse: XStreamResponse?, private val body: Body, private val additionalHeaders: Headers, @@ -37,9 +34,6 @@ private constructor( /** Unique session identifier */ fun id(): Optional = Optional.ofNullable(id) - /** ISO timestamp when request was sent */ - fun xSentAt(): Optional = Optional.ofNullable(xSentAt) - /** Whether to stream the response via SSE */ fun xStreamResponse(): Optional = Optional.ofNullable(xStreamResponse) @@ -128,7 +122,6 @@ private constructor( class Builder internal constructor() { private var id: String? = null - private var xSentAt: OffsetDateTime? = null private var xStreamResponse: XStreamResponse? = null private var body: Body.Builder = Body.builder() private var additionalHeaders: Headers.Builder = Headers.builder() @@ -137,7 +130,6 @@ private constructor( @JvmSynthetic internal fun from(sessionNavigateParams: SessionNavigateParams) = apply { id = sessionNavigateParams.id - xSentAt = sessionNavigateParams.xSentAt xStreamResponse = sessionNavigateParams.xStreamResponse body = sessionNavigateParams.body.toBuilder() additionalHeaders = sessionNavigateParams.additionalHeaders.toBuilder() @@ -150,12 +142,6 @@ private constructor( /** Alias for calling [Builder.id] with `id.orElse(null)`. */ fun id(id: Optional) = id(id.getOrNull()) - /** ISO timestamp when request was sent */ - fun xSentAt(xSentAt: OffsetDateTime?) = apply { this.xSentAt = xSentAt } - - /** Alias for calling [Builder.xSentAt] with `xSentAt.orElse(null)`. */ - fun xSentAt(xSentAt: Optional) = xSentAt(xSentAt.getOrNull()) - /** Whether to stream the response via SSE */ fun xStreamResponse(xStreamResponse: XStreamResponse?) = apply { this.xStreamResponse = xStreamResponse @@ -189,7 +175,10 @@ private constructor( fun url(url: JsonField) = apply { body.url(url) } /** Target frame ID for the navigation */ - fun frameId(frameId: String) = apply { body.frameId(frameId) } + fun frameId(frameId: String?) = apply { body.frameId(frameId) } + + /** Alias for calling [Builder.frameId] with `frameId.orElse(null)`. */ + fun frameId(frameId: Optional) = frameId(frameId.getOrNull()) /** * Sets [Builder.frameId] to an arbitrary JSON value. @@ -355,7 +344,6 @@ private constructor( fun build(): SessionNavigateParams = SessionNavigateParams( id, - xSentAt, xStreamResponse, body.build(), additionalHeaders.build(), @@ -374,7 +362,6 @@ private constructor( override fun _headers(): Headers = Headers.builder() .apply { - xSentAt?.let { put("x-sent-at", DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(it)) } xStreamResponse?.let { put("x-stream-response", it.toString()) } putAll(additionalHeaders) } @@ -519,7 +506,10 @@ private constructor( fun url(url: JsonField) = apply { this.url = url } /** Target frame ID for the navigation */ - fun frameId(frameId: String) = frameId(JsonField.of(frameId)) + fun frameId(frameId: String?) = frameId(JsonField.ofNullable(frameId)) + + /** Alias for calling [Builder.frameId] with `frameId.orElse(null)`. */ + fun frameId(frameId: Optional) = frameId(frameId.getOrNull()) /** * Sets [Builder.frameId] to an arbitrary JSON value. @@ -1150,7 +1140,6 @@ private constructor( return other is SessionNavigateParams && id == other.id && - xSentAt == other.xSentAt && xStreamResponse == other.xStreamResponse && body == other.body && additionalHeaders == other.additionalHeaders && @@ -1158,8 +1147,8 @@ private constructor( } override fun hashCode(): Int = - Objects.hash(id, xSentAt, xStreamResponse, body, additionalHeaders, additionalQueryParams) + Objects.hash(id, xStreamResponse, body, additionalHeaders, additionalQueryParams) override fun toString() = - "SessionNavigateParams{id=$id, xSentAt=$xSentAt, xStreamResponse=$xStreamResponse, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" + "SessionNavigateParams{id=$id, xStreamResponse=$xStreamResponse, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" } diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionObserveParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionObserveParams.kt index 37efafd..4ba5e2f 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionObserveParams.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionObserveParams.kt @@ -15,8 +15,6 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter import com.fasterxml.jackson.annotation.JsonAnySetter import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonProperty -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter import java.util.Collections import java.util.Objects import java.util.Optional @@ -28,7 +26,6 @@ import kotlin.jvm.optionals.getOrNull class SessionObserveParams private constructor( private val id: String?, - private val xSentAt: OffsetDateTime?, private val xStreamResponse: XStreamResponse?, private val body: Body, private val additionalHeaders: Headers, @@ -38,9 +35,6 @@ private constructor( /** Unique session identifier */ fun id(): Optional = Optional.ofNullable(id) - /** ISO timestamp when request was sent */ - fun xSentAt(): Optional = Optional.ofNullable(xSentAt) - /** Whether to stream the response via SSE */ fun xStreamResponse(): Optional = Optional.ofNullable(xStreamResponse) @@ -109,7 +103,6 @@ private constructor( class Builder internal constructor() { private var id: String? = null - private var xSentAt: OffsetDateTime? = null private var xStreamResponse: XStreamResponse? = null private var body: Body.Builder = Body.builder() private var additionalHeaders: Headers.Builder = Headers.builder() @@ -118,7 +111,6 @@ private constructor( @JvmSynthetic internal fun from(sessionObserveParams: SessionObserveParams) = apply { id = sessionObserveParams.id - xSentAt = sessionObserveParams.xSentAt xStreamResponse = sessionObserveParams.xStreamResponse body = sessionObserveParams.body.toBuilder() additionalHeaders = sessionObserveParams.additionalHeaders.toBuilder() @@ -131,12 +123,6 @@ private constructor( /** Alias for calling [Builder.id] with `id.orElse(null)`. */ fun id(id: Optional) = id(id.getOrNull()) - /** ISO timestamp when request was sent */ - fun xSentAt(xSentAt: OffsetDateTime?) = apply { this.xSentAt = xSentAt } - - /** Alias for calling [Builder.xSentAt] with `xSentAt.orElse(null)`. */ - fun xSentAt(xSentAt: Optional) = xSentAt(xSentAt.getOrNull()) - /** Whether to stream the response via SSE */ fun xStreamResponse(xStreamResponse: XStreamResponse?) = apply { this.xStreamResponse = xStreamResponse @@ -158,7 +144,10 @@ private constructor( fun body(body: Body) = apply { this.body = body.toBuilder() } /** Target frame ID for the observation */ - fun frameId(frameId: String) = apply { body.frameId(frameId) } + fun frameId(frameId: String?) = apply { body.frameId(frameId) } + + /** Alias for calling [Builder.frameId] with `frameId.orElse(null)`. */ + fun frameId(frameId: Optional) = frameId(frameId.getOrNull()) /** * Sets [Builder.frameId] to an arbitrary JSON value. @@ -315,7 +304,6 @@ private constructor( fun build(): SessionObserveParams = SessionObserveParams( id, - xSentAt, xStreamResponse, body.build(), additionalHeaders.build(), @@ -334,7 +322,6 @@ private constructor( override fun _headers(): Headers = Headers.builder() .apply { - xSentAt?.let { put("x-sent-at", DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(it)) } xStreamResponse?.let { put("x-stream-response", it.toString()) } putAll(additionalHeaders) } @@ -440,7 +427,10 @@ private constructor( } /** Target frame ID for the observation */ - fun frameId(frameId: String) = frameId(JsonField.of(frameId)) + fun frameId(frameId: String?) = frameId(JsonField.ofNullable(frameId)) + + /** Alias for calling [Builder.frameId] with `frameId.orElse(null)`. */ + fun frameId(frameId: Optional) = frameId(frameId.getOrNull()) /** * Sets [Builder.frameId] to an arbitrary JSON value. @@ -578,8 +568,9 @@ private constructor( ) : this(model, selector, timeout, mutableMapOf()) /** - * Model name string with provider prefix (e.g., 'openai/gpt-5-nano', - * 'anthropic/claude-4.5-opus') + * Model name string with provider prefix. Always use the format 'provider/model-name' + * (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', + * 'google/gemini-2.0-flash') * * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if * the server responded with an unexpected value). @@ -658,8 +649,9 @@ private constructor( } /** - * Model name string with provider prefix (e.g., 'openai/gpt-5-nano', - * 'anthropic/claude-4.5-opus') + * Model name string with provider prefix. Always use the format 'provider/model-name' + * (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', + * 'google/gemini-2.0-flash') */ fun model(model: ModelConfig) = model(JsonField.of(model)) @@ -672,8 +664,8 @@ private constructor( */ fun model(model: JsonField) = apply { this.model = model } - /** Alias for calling [model] with `ModelConfig.ofName(name)`. */ - fun model(name: String) = model(ModelConfig.ofName(name)) + /** Alias for calling [model] with `ModelConfig.ofString(string)`. */ + fun model(string: String) = model(ModelConfig.ofString(string)) /** * Alias for calling [model] with `ModelConfig.ofModelConfigObject(modelConfigObject)`. @@ -927,7 +919,6 @@ private constructor( return other is SessionObserveParams && id == other.id && - xSentAt == other.xSentAt && xStreamResponse == other.xStreamResponse && body == other.body && additionalHeaders == other.additionalHeaders && @@ -935,8 +926,8 @@ private constructor( } override fun hashCode(): Int = - Objects.hash(id, xSentAt, xStreamResponse, body, additionalHeaders, additionalQueryParams) + Objects.hash(id, xStreamResponse, body, additionalHeaders, additionalQueryParams) override fun toString() = - "SessionObserveParams{id=$id, xSentAt=$xSentAt, xStreamResponse=$xStreamResponse, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" + "SessionObserveParams{id=$id, xStreamResponse=$xStreamResponse, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" } diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionStartParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionStartParams.kt index 2d3ece2..85b3c01 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionStartParams.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionStartParams.kt @@ -29,8 +29,6 @@ import com.fasterxml.jackson.databind.SerializerProvider import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonSerialize import com.fasterxml.jackson.module.kotlin.jacksonTypeRef -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter import java.util.Collections import java.util.Objects import java.util.Optional @@ -42,21 +40,18 @@ import kotlin.jvm.optionals.getOrNull */ class SessionStartParams private constructor( - private val xSentAt: OffsetDateTime?, private val xStreamResponse: XStreamResponse?, private val body: Body, private val additionalHeaders: Headers, private val additionalQueryParams: QueryParams, ) : Params { - /** ISO timestamp when request was sent */ - fun xSentAt(): Optional = Optional.ofNullable(xSentAt) - /** Whether to stream the response via SSE */ fun xStreamResponse(): Optional = Optional.ofNullable(xStreamResponse) /** - * Model name to use for AI operations + * Model name to use for AI operations. Always use the format 'provider/model-name' (e.g., + * 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', 'google/gemini-2.0-flash') * * @throws StagehandInvalidDataException if the JSON field has an unexpected type or is * unexpectedly missing or null (e.g. if the server responded with an unexpected value). @@ -246,7 +241,6 @@ private constructor( /** A builder for [SessionStartParams]. */ class Builder internal constructor() { - private var xSentAt: OffsetDateTime? = null private var xStreamResponse: XStreamResponse? = null private var body: Body.Builder = Body.builder() private var additionalHeaders: Headers.Builder = Headers.builder() @@ -254,19 +248,12 @@ private constructor( @JvmSynthetic internal fun from(sessionStartParams: SessionStartParams) = apply { - xSentAt = sessionStartParams.xSentAt xStreamResponse = sessionStartParams.xStreamResponse body = sessionStartParams.body.toBuilder() additionalHeaders = sessionStartParams.additionalHeaders.toBuilder() additionalQueryParams = sessionStartParams.additionalQueryParams.toBuilder() } - /** ISO timestamp when request was sent */ - fun xSentAt(xSentAt: OffsetDateTime?) = apply { this.xSentAt = xSentAt } - - /** Alias for calling [Builder.xSentAt] with `xSentAt.orElse(null)`. */ - fun xSentAt(xSentAt: Optional) = xSentAt(xSentAt.getOrNull()) - /** Whether to stream the response via SSE */ fun xStreamResponse(xStreamResponse: XStreamResponse?) = apply { this.xStreamResponse = xStreamResponse @@ -290,7 +277,10 @@ private constructor( */ fun body(body: Body) = apply { this.body = body.toBuilder() } - /** Model name to use for AI operations */ + /** + * Model name to use for AI operations. Always use the format 'provider/model-name' (e.g., + * 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', 'google/gemini-2.0-flash') + */ fun modelName(modelName: String) = apply { body.modelName(modelName) } /** @@ -570,7 +560,6 @@ private constructor( */ fun build(): SessionStartParams = SessionStartParams( - xSentAt, xStreamResponse, body.build(), additionalHeaders.build(), @@ -583,7 +572,6 @@ private constructor( override fun _headers(): Headers = Headers.builder() .apply { - xSentAt?.let { put("x-sent-at", DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(it)) } xStreamResponse?.let { put("x-stream-response", it.toString()) } putAll(additionalHeaders) } @@ -656,7 +644,8 @@ private constructor( ) /** - * Model name to use for AI operations + * Model name to use for AI operations. Always use the format 'provider/model-name' (e.g., + * 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', 'google/gemini-2.0-flash') * * @throws StagehandInvalidDataException if the JSON field has an unexpected type or is * unexpectedly missing or null (e.g. if the server responded with an unexpected value). @@ -898,7 +887,11 @@ private constructor( additionalProperties = body.additionalProperties.toMutableMap() } - /** Model name to use for AI operations */ + /** + * Model name to use for AI operations. Always use the format 'provider/model-name' + * (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929', + * 'google/gemini-2.0-flash') + */ fun modelName(modelName: String) = modelName(JsonField.of(modelName)) /** @@ -7300,7 +7293,6 @@ private constructor( } return other is SessionStartParams && - xSentAt == other.xSentAt && xStreamResponse == other.xStreamResponse && body == other.body && additionalHeaders == other.additionalHeaders && @@ -7308,8 +7300,8 @@ private constructor( } override fun hashCode(): Int = - Objects.hash(xSentAt, xStreamResponse, body, additionalHeaders, additionalQueryParams) + Objects.hash(xStreamResponse, body, additionalHeaders, additionalQueryParams) override fun toString() = - "SessionStartParams{xSentAt=$xSentAt, xStreamResponse=$xStreamResponse, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" + "SessionStartParams{xStreamResponse=$xStreamResponse, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" } diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/ModelConfigTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/ModelConfigTest.kt index db47f8d..0190f8e 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/ModelConfigTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/ModelConfigTest.kt @@ -13,19 +13,19 @@ import org.junit.jupiter.api.assertThrows internal class ModelConfigTest { @Test - fun ofName() { - val name = "openai/gpt-5-nano" + fun ofString() { + val string = "openai/gpt-4o" - val modelConfig = ModelConfig.ofName(name) + val modelConfig = ModelConfig.ofString(string) - assertThat(modelConfig.name()).contains(name) + assertThat(modelConfig.string()).contains(string) assertThat(modelConfig.modelConfigObject()).isEmpty } @Test - fun ofNameRoundtrip() { + fun ofStringRoundtrip() { val jsonMapper = jsonMapper() - val modelConfig = ModelConfig.ofName("openai/gpt-5-nano") + val modelConfig = ModelConfig.ofString("openai/gpt-4o") val roundtrippedModelConfig = jsonMapper.readValue( @@ -40,7 +40,7 @@ internal class ModelConfigTest { fun ofModelConfigObject() { val modelConfigObject = ModelConfig.ModelConfigObject.builder() - .modelName("openai/gpt-5-nano") + .modelName("openai/gpt-4o") .apiKey("sk-some-openai-api-key") .baseUrl("https://api.openai.com/v1") .provider(ModelConfig.ModelConfigObject.Provider.OPENAI) @@ -48,7 +48,7 @@ internal class ModelConfigTest { val modelConfig = ModelConfig.ofModelConfigObject(modelConfigObject) - assertThat(modelConfig.name()).isEmpty + assertThat(modelConfig.string()).isEmpty assertThat(modelConfig.modelConfigObject()).contains(modelConfigObject) } @@ -58,7 +58,7 @@ internal class ModelConfigTest { val modelConfig = ModelConfig.ofModelConfigObject( ModelConfig.ModelConfigObject.builder() - .modelName("openai/gpt-5-nano") + .modelName("openai/gpt-4o") .apiKey("sk-some-openai-api-key") .baseUrl("https://api.openai.com/v1") .provider(ModelConfig.ModelConfigObject.Provider.OPENAI) diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionActParamsTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionActParamsTest.kt index 54acc4f..aec22f7 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionActParamsTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionActParamsTest.kt @@ -4,7 +4,6 @@ package com.browserbase.api.models.sessions import com.browserbase.api.core.JsonValue import com.browserbase.api.core.http.Headers -import java.time.OffsetDateTime import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -14,13 +13,12 @@ internal class SessionActParamsTest { fun create() { SessionActParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionActParams.XStreamResponse.TRUE) .input("Click the login button") .frameId("frameId") .options( SessionActParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .timeout(30000.0) .variables( SessionActParams.Options.Variables.builder() @@ -50,13 +48,12 @@ internal class SessionActParamsTest { val params = SessionActParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionActParams.XStreamResponse.TRUE) .input("Click the login button") .frameId("frameId") .options( SessionActParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .timeout(30000.0) .variables( SessionActParams.Options.Variables.builder() @@ -69,13 +66,7 @@ internal class SessionActParamsTest { val headers = params._headers() - assertThat(headers) - .isEqualTo( - Headers.builder() - .put("x-sent-at", "2025-01-15T10:30:00Z") - .put("x-stream-response", "true") - .build() - ) + assertThat(headers).isEqualTo(Headers.builder().put("x-stream-response", "true").build()) } @Test @@ -96,13 +87,12 @@ internal class SessionActParamsTest { val params = SessionActParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionActParams.XStreamResponse.TRUE) .input("Click the login button") .frameId("frameId") .options( SessionActParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .timeout(30000.0) .variables( SessionActParams.Options.Variables.builder() @@ -121,7 +111,7 @@ internal class SessionActParamsTest { assertThat(body.options()) .contains( SessionActParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .timeout(30000.0) .variables( SessionActParams.Options.Variables.builder() diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionEndParamsTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionEndParamsTest.kt index 9081b87..5d789fc 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionEndParamsTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionEndParamsTest.kt @@ -4,7 +4,6 @@ package com.browserbase.api.models.sessions import com.browserbase.api.core.JsonValue import com.browserbase.api.core.http.Headers -import java.time.OffsetDateTime import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -14,7 +13,6 @@ internal class SessionEndParamsTest { fun create() { SessionEndParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionEndParams.XStreamResponse.TRUE) ._forceBody(JsonValue.from(mapOf())) .build() @@ -34,20 +32,13 @@ internal class SessionEndParamsTest { val params = SessionEndParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionEndParams.XStreamResponse.TRUE) ._forceBody(JsonValue.from(mapOf())) .build() val headers = params._headers() - assertThat(headers) - .isEqualTo( - Headers.builder() - .put("x-sent-at", "2025-01-15T10:30:00Z") - .put("x-stream-response", "true") - .build() - ) + assertThat(headers).isEqualTo(Headers.builder().put("x-stream-response", "true").build()) } @Test @@ -64,7 +55,6 @@ internal class SessionEndParamsTest { val params = SessionEndParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionEndParams.XStreamResponse.TRUE) ._forceBody(JsonValue.from(mapOf())) .build() diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionExecuteParamsTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionExecuteParamsTest.kt index 2064b33..6b36c20 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionExecuteParamsTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionExecuteParamsTest.kt @@ -3,7 +3,6 @@ package com.browserbase.api.models.sessions import com.browserbase.api.core.http.Headers -import java.time.OffsetDateTime import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -13,12 +12,11 @@ internal class SessionExecuteParamsTest { fun create() { SessionExecuteParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionExecuteParams.XStreamResponse.TRUE) .agentConfig( SessionExecuteParams.AgentConfig.builder() .cua(true) - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .provider(SessionExecuteParams.AgentConfig.Provider.OPENAI) .systemPrompt("systemPrompt") .build() @@ -61,12 +59,11 @@ internal class SessionExecuteParamsTest { val params = SessionExecuteParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionExecuteParams.XStreamResponse.TRUE) .agentConfig( SessionExecuteParams.AgentConfig.builder() .cua(true) - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .provider(SessionExecuteParams.AgentConfig.Provider.OPENAI) .systemPrompt("systemPrompt") .build() @@ -85,13 +82,7 @@ internal class SessionExecuteParamsTest { val headers = params._headers() - assertThat(headers) - .isEqualTo( - Headers.builder() - .put("x-sent-at", "2025-01-15T10:30:00Z") - .put("x-stream-response", "true") - .build() - ) + assertThat(headers).isEqualTo(Headers.builder().put("x-stream-response", "true").build()) } @Test @@ -119,12 +110,11 @@ internal class SessionExecuteParamsTest { val params = SessionExecuteParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionExecuteParams.XStreamResponse.TRUE) .agentConfig( SessionExecuteParams.AgentConfig.builder() .cua(true) - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .provider(SessionExecuteParams.AgentConfig.Provider.OPENAI) .systemPrompt("systemPrompt") .build() @@ -147,7 +137,7 @@ internal class SessionExecuteParamsTest { .isEqualTo( SessionExecuteParams.AgentConfig.builder() .cua(true) - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .provider(SessionExecuteParams.AgentConfig.Provider.OPENAI) .systemPrompt("systemPrompt") .build() diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionExtractParamsTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionExtractParamsTest.kt index 54b80a6..1ee8821 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionExtractParamsTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionExtractParamsTest.kt @@ -4,7 +4,6 @@ package com.browserbase.api.models.sessions import com.browserbase.api.core.JsonValue import com.browserbase.api.core.http.Headers -import java.time.OffsetDateTime import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -14,13 +13,12 @@ internal class SessionExtractParamsTest { fun create() { SessionExtractParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionExtractParams.XStreamResponse.TRUE) .frameId("frameId") .instruction("Extract all product names and prices from the page") .options( SessionExtractParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .selector("#main-content") .timeout(30000.0) .build() @@ -48,13 +46,12 @@ internal class SessionExtractParamsTest { val params = SessionExtractParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionExtractParams.XStreamResponse.TRUE) .frameId("frameId") .instruction("Extract all product names and prices from the page") .options( SessionExtractParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .selector("#main-content") .timeout(30000.0) .build() @@ -68,13 +65,7 @@ internal class SessionExtractParamsTest { val headers = params._headers() - assertThat(headers) - .isEqualTo( - Headers.builder() - .put("x-sent-at", "2025-01-15T10:30:00Z") - .put("x-stream-response", "true") - .build() - ) + assertThat(headers).isEqualTo(Headers.builder().put("x-stream-response", "true").build()) } @Test @@ -92,13 +83,12 @@ internal class SessionExtractParamsTest { val params = SessionExtractParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionExtractParams.XStreamResponse.TRUE) .frameId("frameId") .instruction("Extract all product names and prices from the page") .options( SessionExtractParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .selector("#main-content") .timeout(30000.0) .build() @@ -118,7 +108,7 @@ internal class SessionExtractParamsTest { assertThat(body.options()) .contains( SessionExtractParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .selector("#main-content") .timeout(30000.0) .build() diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionNavigateParamsTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionNavigateParamsTest.kt index 3616da2..3e48bb2 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionNavigateParamsTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionNavigateParamsTest.kt @@ -3,7 +3,6 @@ package com.browserbase.api.models.sessions import com.browserbase.api.core.http.Headers -import java.time.OffsetDateTime import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -13,7 +12,6 @@ internal class SessionNavigateParamsTest { fun create() { SessionNavigateParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionNavigateParams.XStreamResponse.TRUE) .url("https://example.com") .frameId("frameId") @@ -46,7 +44,6 @@ internal class SessionNavigateParamsTest { val params = SessionNavigateParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionNavigateParams.XStreamResponse.TRUE) .url("https://example.com") .frameId("frameId") @@ -62,13 +59,7 @@ internal class SessionNavigateParamsTest { val headers = params._headers() - assertThat(headers) - .isEqualTo( - Headers.builder() - .put("x-sent-at", "2025-01-15T10:30:00Z") - .put("x-stream-response", "true") - .build() - ) + assertThat(headers).isEqualTo(Headers.builder().put("x-stream-response", "true").build()) } @Test @@ -89,7 +80,6 @@ internal class SessionNavigateParamsTest { val params = SessionNavigateParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionNavigateParams.XStreamResponse.TRUE) .url("https://example.com") .frameId("frameId") diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionObserveParamsTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionObserveParamsTest.kt index eeb111b..28cfb46 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionObserveParamsTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionObserveParamsTest.kt @@ -3,7 +3,6 @@ package com.browserbase.api.models.sessions import com.browserbase.api.core.http.Headers -import java.time.OffsetDateTime import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -13,13 +12,12 @@ internal class SessionObserveParamsTest { fun create() { SessionObserveParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionObserveParams.XStreamResponse.TRUE) .frameId("frameId") .instruction("Find all clickable navigation links") .options( SessionObserveParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .selector("nav") .timeout(30000.0) .build() @@ -42,13 +40,12 @@ internal class SessionObserveParamsTest { val params = SessionObserveParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionObserveParams.XStreamResponse.TRUE) .frameId("frameId") .instruction("Find all clickable navigation links") .options( SessionObserveParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .selector("nav") .timeout(30000.0) .build() @@ -57,13 +54,7 @@ internal class SessionObserveParamsTest { val headers = params._headers() - assertThat(headers) - .isEqualTo( - Headers.builder() - .put("x-sent-at", "2025-01-15T10:30:00Z") - .put("x-stream-response", "true") - .build() - ) + assertThat(headers).isEqualTo(Headers.builder().put("x-stream-response", "true").build()) } @Test @@ -81,13 +72,12 @@ internal class SessionObserveParamsTest { val params = SessionObserveParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionObserveParams.XStreamResponse.TRUE) .frameId("frameId") .instruction("Find all clickable navigation links") .options( SessionObserveParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .selector("nav") .timeout(30000.0) .build() @@ -101,7 +91,7 @@ internal class SessionObserveParamsTest { assertThat(body.options()) .contains( SessionObserveParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .selector("nav") .timeout(30000.0) .build() diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionStartParamsTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionStartParamsTest.kt index 4e4e0dc..d3cd55c 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionStartParamsTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionStartParamsTest.kt @@ -4,7 +4,6 @@ package com.browserbase.api.models.sessions import com.browserbase.api.core.JsonValue import com.browserbase.api.core.http.Headers -import java.time.OffsetDateTime import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -13,7 +12,6 @@ internal class SessionStartParamsTest { @Test fun create() { SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -159,7 +157,6 @@ internal class SessionStartParamsTest { fun headers() { val params = SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -306,13 +303,7 @@ internal class SessionStartParamsTest { val headers = params._headers() - assertThat(headers) - .isEqualTo( - Headers.builder() - .put("x-sent-at", "2025-01-15T10:30:00Z") - .put("x-stream-response", "true") - .build() - ) + assertThat(headers).isEqualTo(Headers.builder().put("x-stream-response", "true").build()) } @Test @@ -328,7 +319,6 @@ internal class SessionStartParamsTest { fun body() { val params = SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/ErrorHandlingTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/ErrorHandlingTest.kt index 6dab5ba..8f3f827 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/ErrorHandlingTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/ErrorHandlingTest.kt @@ -23,7 +23,6 @@ import com.github.tomakehurst.wiremock.client.WireMock.status import com.github.tomakehurst.wiremock.client.WireMock.stubFor import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo import com.github.tomakehurst.wiremock.junit5.WireMockTest -import java.time.OffsetDateTime import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.entry import org.junit.jupiter.api.BeforeEach @@ -75,7 +74,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -252,7 +250,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -429,7 +426,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -606,7 +602,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -783,7 +778,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -960,7 +954,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -1137,7 +1130,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -1314,7 +1306,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -1491,7 +1482,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -1668,7 +1658,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -1845,7 +1834,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -2022,7 +2010,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -2199,7 +2186,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -2376,7 +2362,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -2553,7 +2538,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -2730,7 +2714,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -2905,7 +2888,6 @@ internal class ErrorHandlingTest { assertThrows { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/ServiceParamsTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/ServiceParamsTest.kt index 33f0206..b01ce5f 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/ServiceParamsTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/ServiceParamsTest.kt @@ -17,7 +17,6 @@ import com.github.tomakehurst.wiremock.client.WireMock.stubFor import com.github.tomakehurst.wiremock.client.WireMock.verify import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo import com.github.tomakehurst.wiremock.junit5.WireMockTest -import java.time.OffsetDateTime import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test @@ -48,7 +47,6 @@ internal class ServiceParamsTest { sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) @@ -214,13 +212,12 @@ internal class ServiceParamsTest { sessionService.act( SessionActParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionActParams.XStreamResponse.TRUE) .input("Click the login button") .frameId("frameId") .options( SessionActParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .timeout(30000.0) .variables( SessionActParams.Options.Variables.builder() diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/async/SessionServiceAsyncTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/async/SessionServiceAsyncTest.kt index d457ad4..bd4953d 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/async/SessionServiceAsyncTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/async/SessionServiceAsyncTest.kt @@ -12,7 +12,6 @@ import com.browserbase.api.models.sessions.SessionExtractParams import com.browserbase.api.models.sessions.SessionNavigateParams import com.browserbase.api.models.sessions.SessionObserveParams import com.browserbase.api.models.sessions.SessionStartParams -import java.time.OffsetDateTime import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -36,13 +35,12 @@ internal class SessionServiceAsyncTest { sessionServiceAsync.act( SessionActParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionActParams.XStreamResponse.TRUE) .input("Click the login button") .frameId("frameId") .options( SessionActParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .timeout(30000.0) .variables( SessionActParams.Options.Variables.builder() @@ -74,13 +72,12 @@ internal class SessionServiceAsyncTest { sessionServiceAsync.actStreaming( SessionActParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionActParams.XStreamResponse.TRUE) .input("Click the login button") .frameId("frameId") .options( SessionActParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .timeout(30000.0) .variables( SessionActParams.Options.Variables.builder() @@ -113,7 +110,6 @@ internal class SessionServiceAsyncTest { sessionServiceAsync.end( SessionEndParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionEndParams.XStreamResponse.TRUE) ._forceBody(JsonValue.from(mapOf())) .build() @@ -139,12 +135,11 @@ internal class SessionServiceAsyncTest { sessionServiceAsync.execute( SessionExecuteParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionExecuteParams.XStreamResponse.TRUE) .agentConfig( SessionExecuteParams.AgentConfig.builder() .cua(true) - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .provider(SessionExecuteParams.AgentConfig.Provider.OPENAI) .systemPrompt("systemPrompt") .build() @@ -182,12 +177,11 @@ internal class SessionServiceAsyncTest { sessionServiceAsync.executeStreaming( SessionExecuteParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionExecuteParams.XStreamResponse.TRUE) .agentConfig( SessionExecuteParams.AgentConfig.builder() .cua(true) - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .provider(SessionExecuteParams.AgentConfig.Provider.OPENAI) .systemPrompt("systemPrompt") .build() @@ -226,13 +220,12 @@ internal class SessionServiceAsyncTest { sessionServiceAsync.extract( SessionExtractParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionExtractParams.XStreamResponse.TRUE) .frameId("frameId") .instruction("Extract all product names and prices from the page") .options( SessionExtractParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .selector("#main-content") .timeout(30000.0) .build() @@ -265,13 +258,12 @@ internal class SessionServiceAsyncTest { sessionServiceAsync.extractStreaming( SessionExtractParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionExtractParams.XStreamResponse.TRUE) .frameId("frameId") .instruction("Extract all product names and prices from the page") .options( SessionExtractParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .selector("#main-content") .timeout(30000.0) .build() @@ -305,7 +297,6 @@ internal class SessionServiceAsyncTest { sessionServiceAsync.navigate( SessionNavigateParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionNavigateParams.XStreamResponse.TRUE) .url("https://example.com") .frameId("frameId") @@ -340,13 +331,12 @@ internal class SessionServiceAsyncTest { sessionServiceAsync.observe( SessionObserveParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionObserveParams.XStreamResponse.TRUE) .frameId("frameId") .instruction("Find all clickable navigation links") .options( SessionObserveParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .selector("nav") .timeout(30000.0) .build() @@ -374,13 +364,12 @@ internal class SessionServiceAsyncTest { sessionServiceAsync.observeStreaming( SessionObserveParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionObserveParams.XStreamResponse.TRUE) .frameId("frameId") .instruction("Find all clickable navigation links") .options( SessionObserveParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .selector("nav") .timeout(30000.0) .build() @@ -408,7 +397,6 @@ internal class SessionServiceAsyncTest { val responseFuture = sessionServiceAsync.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/blocking/SessionServiceTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/blocking/SessionServiceTest.kt index b7d0e6b..41e40f0 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/blocking/SessionServiceTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/blocking/SessionServiceTest.kt @@ -12,7 +12,6 @@ import com.browserbase.api.models.sessions.SessionExtractParams import com.browserbase.api.models.sessions.SessionNavigateParams import com.browserbase.api.models.sessions.SessionObserveParams import com.browserbase.api.models.sessions.SessionStartParams -import java.time.OffsetDateTime import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -36,13 +35,12 @@ internal class SessionServiceTest { sessionService.act( SessionActParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionActParams.XStreamResponse.TRUE) .input("Click the login button") .frameId("frameId") .options( SessionActParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .timeout(30000.0) .variables( SessionActParams.Options.Variables.builder() @@ -73,13 +71,12 @@ internal class SessionServiceTest { sessionService.actStreaming( SessionActParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionActParams.XStreamResponse.TRUE) .input("Click the login button") .frameId("frameId") .options( SessionActParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .timeout(30000.0) .variables( SessionActParams.Options.Variables.builder() @@ -112,7 +109,6 @@ internal class SessionServiceTest { sessionService.end( SessionEndParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionEndParams.XStreamResponse.TRUE) ._forceBody(JsonValue.from(mapOf())) .build() @@ -137,12 +133,11 @@ internal class SessionServiceTest { sessionService.execute( SessionExecuteParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionExecuteParams.XStreamResponse.TRUE) .agentConfig( SessionExecuteParams.AgentConfig.builder() .cua(true) - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .provider(SessionExecuteParams.AgentConfig.Provider.OPENAI) .systemPrompt("systemPrompt") .build() @@ -179,12 +174,11 @@ internal class SessionServiceTest { sessionService.executeStreaming( SessionExecuteParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionExecuteParams.XStreamResponse.TRUE) .agentConfig( SessionExecuteParams.AgentConfig.builder() .cua(true) - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .provider(SessionExecuteParams.AgentConfig.Provider.OPENAI) .systemPrompt("systemPrompt") .build() @@ -223,13 +217,12 @@ internal class SessionServiceTest { sessionService.extract( SessionExtractParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionExtractParams.XStreamResponse.TRUE) .frameId("frameId") .instruction("Extract all product names and prices from the page") .options( SessionExtractParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .selector("#main-content") .timeout(30000.0) .build() @@ -261,13 +254,12 @@ internal class SessionServiceTest { sessionService.extractStreaming( SessionExtractParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionExtractParams.XStreamResponse.TRUE) .frameId("frameId") .instruction("Extract all product names and prices from the page") .options( SessionExtractParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .selector("#main-content") .timeout(30000.0) .build() @@ -301,7 +293,6 @@ internal class SessionServiceTest { sessionService.navigate( SessionNavigateParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionNavigateParams.XStreamResponse.TRUE) .url("https://example.com") .frameId("frameId") @@ -335,13 +326,12 @@ internal class SessionServiceTest { sessionService.observe( SessionObserveParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionObserveParams.XStreamResponse.TRUE) .frameId("frameId") .instruction("Find all clickable navigation links") .options( SessionObserveParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .selector("nav") .timeout(30000.0) .build() @@ -368,13 +358,12 @@ internal class SessionServiceTest { sessionService.observeStreaming( SessionObserveParams.builder() .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionObserveParams.XStreamResponse.TRUE) .frameId("frameId") .instruction("Find all clickable navigation links") .options( SessionObserveParams.Options.builder() - .model("openai/gpt-5-nano") + .model("openai/gpt-4o") .selector("nav") .timeout(30000.0) .build() @@ -402,7 +391,6 @@ internal class SessionServiceTest { val response = sessionService.start( SessionStartParams.builder() - .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionStartParams.XStreamResponse.TRUE) .modelName("openai/gpt-4o") .actTimeoutMs(0.0) diff --git a/stagehand-java-proguard-test/src/test/kotlin/com/browserbase/api/proguard/ProGuardCompatibilityTest.kt b/stagehand-java-proguard-test/src/test/kotlin/com/browserbase/api/proguard/ProGuardCompatibilityTest.kt index f3e08fa..7db4e74 100644 --- a/stagehand-java-proguard-test/src/test/kotlin/com/browserbase/api/proguard/ProGuardCompatibilityTest.kt +++ b/stagehand-java-proguard-test/src/test/kotlin/com/browserbase/api/proguard/ProGuardCompatibilityTest.kt @@ -77,7 +77,7 @@ internal class ProGuardCompatibilityTest { @Test fun modelConfigRoundtrip() { val jsonMapper = jsonMapper() - val modelConfig = ModelConfig.ofName("openai/gpt-5-nano") + val modelConfig = ModelConfig.ofString("openai/gpt-4o") val roundtrippedModelConfig = jsonMapper.readValue( From cb5323a5034a25ed82b7a09eb010542d5bad9d9c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 23:05:57 +0000 Subject: [PATCH 15/44] feat(api): manual updates --- .github/workflows/publish-sonatype.yml | 2 +- .stats.yml | 2 +- README.md | 3 + build.gradle.kts | 13 --- buildSrc/build.gradle.kts | 3 + .../src/main/kotlin/stagehand.java.gradle.kts | 9 -- .../main/kotlin/stagehand.publish.gradle.kts | 105 +++++++++--------- 7 files changed, 62 insertions(+), 75 deletions(-) diff --git a/.github/workflows/publish-sonatype.yml b/.github/workflows/publish-sonatype.yml index 6a27d98..67e28c7 100644 --- a/.github/workflows/publish-sonatype.yml +++ b/.github/workflows/publish-sonatype.yml @@ -33,7 +33,7 @@ jobs: export -- GPG_SIGNING_KEY_ID printenv -- GPG_SIGNING_KEY | gpg --batch --passphrase-fd 3 --import 3<<< "$GPG_SIGNING_PASSWORD" GPG_SIGNING_KEY_ID="$(gpg --with-colons --list-keys | awk -F : -- '/^pub:/ { getline; print "0x" substr($10, length($10) - 7) }')" - ./gradlew publish --no-configuration-cache + ./gradlew publishAndReleaseToMavenCentral --stacktrace -PmavenCentralUsername="$SONATYPE_USERNAME" -PmavenCentralPassword="$SONATYPE_PASSWORD" --no-configuration-cache env: SONATYPE_USERNAME: ${{ secrets.STAGEHAND_SONATYPE_USERNAME || secrets.SONATYPE_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.STAGEHAND_SONATYPE_PASSWORD || secrets.SONATYPE_PASSWORD }} diff --git a/.stats.yml b/.stats.yml index ee1de31..71adca4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-07032b695bc66ecd76328d936b41e01cfad508a870928c09c185f5faa5ea66ab.yml openapi_spec_hash: fca4b895ce36ad547fb015c3dd38821f -config_hash: d4df55e4b30aac2d8d0982be97f837c4 +config_hash: bf22187c626f0401180a332e3f3f6d8c diff --git a/README.md b/README.md index 8297c8d..bb35165 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,9 @@ If you're looking for other languages, you can find them ## What is Stagehand? +The Stagehand Java SDK is similar to the Stagehand Kotlin SDK but with minor differences that make it more ergonomic for use in Java, such as `Optional` instead of nullable values, `Stream` instead of `Sequence`, and `CompletableFuture` instead of suspend functions. + +It is generated with [Stainless](https://www.stainless.com/). Stagehand is a browser automation framework used to control web browsers with natural language and code. By combining the power of AI with the precision of code, Stagehand makes web automation flexible, maintainable, and actually reliable. diff --git a/build.gradle.kts b/build.gradle.kts index 7c2c0c2..49dfc3e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,4 @@ plugins { - id("io.github.gradle-nexus.publish-plugin") version "1.1.0" id("org.jetbrains.dokka") version "2.0.0" } @@ -35,15 +34,3 @@ tasks.named("dokkaJavadocCollector").configure { .filter { it.project.name != "stagehand-java" && it.name == "dokkaJavadocJar" } .forEach { mustRunAfter(it) } } - -nexusPublishing { - repositories { - sonatype { - nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) - snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) - - username.set(System.getenv("SONATYPE_USERNAME")) - password.set(System.getenv("SONATYPE_PASSWORD")) - } - } -} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 0b14135..c6dc92e 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,12 +1,15 @@ plugins { `kotlin-dsl` kotlin("jvm") version "1.9.20" + id("com.vanniktech.maven.publish") version "0.28.0" } repositories { gradlePluginPortal() + mavenCentral() } dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20") + implementation("com.vanniktech:gradle-maven-publish-plugin:0.28.0") } diff --git a/buildSrc/src/main/kotlin/stagehand.java.gradle.kts b/buildSrc/src/main/kotlin/stagehand.java.gradle.kts index 81d5d32..70fc33f 100644 --- a/buildSrc/src/main/kotlin/stagehand.java.gradle.kts +++ b/buildSrc/src/main/kotlin/stagehand.java.gradle.kts @@ -8,11 +8,6 @@ repositories { mavenCentral() } -configure { - withJavadocJar() - withSourcesJar() -} - java { toolchain { languageVersion.set(JavaLanguageVersion.of(21)) @@ -27,10 +22,6 @@ tasks.withType().configureEach { options.release.set(8) } -tasks.named("javadocJar") { - setZip64(true) -} - tasks.named("jar") { manifest { attributes(mapOf( diff --git a/buildSrc/src/main/kotlin/stagehand.publish.gradle.kts b/buildSrc/src/main/kotlin/stagehand.publish.gradle.kts index f424b58..f45a9d7 100644 --- a/buildSrc/src/main/kotlin/stagehand.publish.gradle.kts +++ b/buildSrc/src/main/kotlin/stagehand.publish.gradle.kts @@ -1,68 +1,71 @@ +import com.vanniktech.maven.publish.JavadocJar +import com.vanniktech.maven.publish.KotlinJvm +import com.vanniktech.maven.publish.MavenPublishBaseExtension +import com.vanniktech.maven.publish.SonatypeHost + plugins { - `maven-publish` - signing + id("com.vanniktech.maven.publish") +} + +publishing { + repositories { + if (project.hasProperty("publishLocal")) { + maven { + name = "LocalFileSystem" + url = uri("${rootProject.layout.buildDirectory.get()}/local-maven-repo") + } + } + } } -configure { - publications { - register("maven") { - from(components["java"]) +repositories { + gradlePluginPortal() + mavenCentral() +} - pom { - name.set("Stagehand API") - description.set("Stagehand SDK for AI browser automation [ALPHA]. This API allows clients to\nexecute browser automation tasks remotely on the Browserbase cloud.\n\nAll endpoints except /sessions/start require an active session ID. Responses are\nstreamed using Server-Sent Events (SSE) when the `x-stream-response: true`\nheader is provided.\n\nThis SDK is currently ALPHA software and is not production ready! Please try it\nand give us your feedback, stay tuned for upcoming release announcements!") - url.set("https://docs.stagehand.dev") +extra["signingInMemoryKey"] = System.getenv("GPG_SIGNING_KEY") +extra["signingInMemoryKeyId"] = System.getenv("GPG_SIGNING_KEY_ID") +extra["signingInMemoryKeyPassword"] = System.getenv("GPG_SIGNING_PASSWORD") - licenses { - license { - name.set("MIT") - } - } +configure { + if (!project.hasProperty("publishLocal")) { + signAllPublications() + publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) + } - developers { - developer { - name.set("Stagehand") - } - } + coordinates(project.group.toString(), project.name, project.version.toString()) + configure( + KotlinJvm( + javadocJar = JavadocJar.Dokka("dokkaJavadoc"), + sourcesJar = true, + ) + ) - scm { - connection.set("scm:git:git://github.com/browserbase/stagehand-java.git") - developerConnection.set("scm:git:git://github.com/browserbase/stagehand-java.git") - url.set("https://github.com/browserbase/stagehand-java") - } + pom { + name.set("Stagehand API") + description.set("Stagehand SDK for AI browser automation [ALPHA]. This API allows clients to\nexecute browser automation tasks remotely on the Browserbase cloud.\n\nAll endpoints except /sessions/start require an active session ID. Responses are\nstreamed using Server-Sent Events (SSE) when the `x-stream-response: true`\nheader is provided.\n\nThis SDK is currently ALPHA software and is not production ready! Please try it\nand give us your feedback, stay tuned for upcoming release announcements!") + url.set("https://docs.stagehand.dev") - versionMapping { - allVariants { - fromResolutionResult() - } - } + licenses { + license { + name.set("MIT") } } - } - repositories { - if (project.hasProperty("publishLocal")) { - maven { - name = "LocalFileSystem" - url = uri("${rootProject.layout.buildDirectory.get()}/local-maven-repo") + + developers { + developer { + name.set("Stagehand") } } - } -} -signing { - val signingKeyId = System.getenv("GPG_SIGNING_KEY_ID")?.ifBlank { null } - val signingKey = System.getenv("GPG_SIGNING_KEY")?.ifBlank { null } - val signingPassword = System.getenv("GPG_SIGNING_PASSWORD")?.ifBlank { null } - if (signingKey != null && signingPassword != null) { - useInMemoryPgpKeys( - signingKeyId, - signingKey, - signingPassword, - ) - sign(publishing.publications["maven"]) + scm { + connection.set("scm:git:git://github.com/browserbase/stagehand-java.git") + developerConnection.set("scm:git:git://github.com/browserbase/stagehand-java.git") + url.set("https://github.com/browserbase/stagehand-java") + } } } -tasks.named("publish") { - dependsOn(":closeAndReleaseSonatypeStagingRepository") +tasks.withType().configureEach { + isZip64 = true } From 2918f1366c3193d21548dec899d3ae053dbd350a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 04:11:29 +0000 Subject: [PATCH 16/44] chore(internal): clean up maven repo artifact script and add html documentation to repo root --- scripts/upload-artifacts | 44 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/scripts/upload-artifacts b/scripts/upload-artifacts index 729e6f2..df0c8d9 100755 --- a/scripts/upload-artifacts +++ b/scripts/upload-artifacts @@ -7,6 +7,8 @@ GREEN='\033[32m' RED='\033[31m' NC='\033[0m' # No Color +MAVEN_REPO_PATH="./build/local-maven-repo" + log_error() { local msg="$1" local headers="$2" @@ -24,7 +26,7 @@ upload_file() { if [ -f "$file_name" ]; then echo -e "${GREEN}Processing file: $file_name${NC}" - pkg_file_name="mvn${file_name#./build/local-maven-repo}" + pkg_file_name="mvn${file_name#"${MAVEN_REPO_PATH}"}" # Get signed URL for uploading artifact file signed_url_response=$(curl -X POST -G "$URL" \ @@ -47,6 +49,7 @@ upload_file() { md5|sha1|sha256|sha512) content_type="text/plain" ;; module) content_type="application/json" ;; pom|xml) content_type="application/xml" ;; + html) content_type="text/html" ;; *) content_type="application/octet-stream" ;; esac @@ -81,6 +84,41 @@ walk_tree() { done } +generate_instructions() { + cat << EOF > "$MAVEN_REPO_PATH/index.html" + + + + Maven Repo + + +

Stainless SDK Maven Repository

+

This is the Maven repository for your Stainless Java SDK build.

+ +

Directions

+

To use the uploaded Maven repository, add the following to your project's pom.xml:

+
<repositories>
+    <repository>
+        <id>stainless-sdk-repo</id>
+        <url>https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn</url>
+    </repository>
+</repositories>
+ +

If you're using Gradle, add the following to your build.gradle file:

+
repositories {
+    maven {
+        url 'https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn'
+    }
+}
+ + +EOF + upload_file "${MAVEN_REPO_PATH}/index.html" + + echo "Configure maven or gradle to use the repo located at 'https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn'" + echo "For more details, see the directions in https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn/index.html" +} + cd "$(dirname "$0")/.." echo "::group::Creating local Maven content" @@ -88,9 +126,9 @@ echo "::group::Creating local Maven content" echo "::endgroup::" echo "::group::Uploading to pkg.stainless.com" -walk_tree "./build/local-maven-repo" +walk_tree "$MAVEN_REPO_PATH" echo "::endgroup::" echo "::group::Generating instructions" -echo "Configure maven or gradle to use the repo located at 'https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn'" +generate_instructions echo "::endgroup::" From 63d6c361ba8510cedd21bbf355881dd603ff06f1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 05:21:58 +0000 Subject: [PATCH 17/44] chore: test on Jackson 2.14.0 to avoid encountering FasterXML/jackson-databind#3240 in tests fix: date time deserialization leniency --- README.md | 2 ++ stagehand-java-core/build.gradle.kts | 18 +++++----- .../com/browserbase/api/core/ObjectMappers.kt | 33 ++++++++++++------- .../api/models/sessions/ModelConfig.kt | 2 +- .../api/models/sessions/SessionActParams.kt | 2 +- .../browserbase/api/core/ObjectMappersTest.kt | 16 +++------ .../api/models/sessions/ModelConfigTest.kt | 17 +++++++--- stagehand-java-proguard-test/build.gradle.kts | 2 +- 8 files changed, 53 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index bb35165..ac8460d 100644 --- a/README.md +++ b/README.md @@ -558,6 +558,8 @@ If the SDK threw an exception, but you're _certain_ the version is compatible, t > [!CAUTION] > We make no guarantee that the SDK works correctly when the Jackson version check is disabled. +Also note that there are bugs in older Jackson versions that can affect the SDK. We don't work around all Jackson bugs ([example](https://github.com/FasterXML/jackson-databind/issues/3240)) and expect users to upgrade Jackson for those instead. + ## Network options ### Retries diff --git a/stagehand-java-core/build.gradle.kts b/stagehand-java-core/build.gradle.kts index 38bcf47..3cfe35b 100644 --- a/stagehand-java-core/build.gradle.kts +++ b/stagehand-java-core/build.gradle.kts @@ -5,14 +5,16 @@ plugins { configurations.all { resolutionStrategy { - // Compile and test against a lower Jackson version to ensure we're compatible with it. - // We publish with a higher version (see below) to ensure users depend on a secure version by default. - force("com.fasterxml.jackson.core:jackson-core:2.13.4") - force("com.fasterxml.jackson.core:jackson-databind:2.13.4") - force("com.fasterxml.jackson.core:jackson-annotations:2.13.4") - force("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.4") - force("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.4") - force("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.4") + // Compile and test against a lower Jackson version to ensure we're compatible with it. Note that + // we generally support 2.13.4, but test against 2.14.0 because 2.13.4 has some annoying (but + // niche) bugs (users should upgrade if they encounter them). We publish with a higher version + // (see below) to ensure users depend on a secure version by default. + force("com.fasterxml.jackson.core:jackson-core:2.14.0") + force("com.fasterxml.jackson.core:jackson-databind:2.14.0") + force("com.fasterxml.jackson.core:jackson-annotations:2.14.0") + force("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.14.0") + force("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.0") + force("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.0") } } diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ObjectMappers.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ObjectMappers.kt index 9ed574e..1d4e317 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ObjectMappers.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ObjectMappers.kt @@ -24,6 +24,7 @@ import java.io.InputStream import java.time.DateTimeException import java.time.LocalDate import java.time.LocalDateTime +import java.time.OffsetDateTime import java.time.ZonedDateTime import java.time.format.DateTimeFormatter import java.time.temporal.ChronoField @@ -36,7 +37,7 @@ fun jsonMapper(): JsonMapper = .addModule( SimpleModule() .addSerializer(InputStreamSerializer) - .addDeserializer(LocalDateTime::class.java, LenientLocalDateTimeDeserializer()) + .addDeserializer(OffsetDateTime::class.java, LenientOffsetDateTimeDeserializer()) ) .withCoercionConfig(LogicalType.Boolean) { it.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail) @@ -64,6 +65,12 @@ fun jsonMapper(): JsonMapper = .setCoercion(CoercionInputShape.Array, CoercionAction.Fail) .setCoercion(CoercionInputShape.Object, CoercionAction.Fail) } + .withCoercionConfig(LogicalType.DateTime) { + it.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail) + .setCoercion(CoercionInputShape.Float, CoercionAction.Fail) + .setCoercion(CoercionInputShape.Array, CoercionAction.Fail) + .setCoercion(CoercionInputShape.Object, CoercionAction.Fail) + } .withCoercionConfig(LogicalType.Array) { it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail) .setCoercion(CoercionInputShape.Integer, CoercionAction.Fail) @@ -124,10 +131,10 @@ private object InputStreamSerializer : BaseSerializer(InputStream:: } /** - * A deserializer that can deserialize [LocalDateTime] from datetimes, dates, and zoned datetimes. + * A deserializer that can deserialize [OffsetDateTime] from datetimes, dates, and zoned datetimes. */ -private class LenientLocalDateTimeDeserializer : - StdDeserializer(LocalDateTime::class.java) { +private class LenientOffsetDateTimeDeserializer : + StdDeserializer(OffsetDateTime::class.java) { companion object { @@ -141,7 +148,7 @@ private class LenientLocalDateTimeDeserializer : override fun logicalType(): LogicalType = LogicalType.DateTime - override fun deserialize(p: JsonParser, context: DeserializationContext?): LocalDateTime { + override fun deserialize(p: JsonParser, context: DeserializationContext): OffsetDateTime { val exceptions = mutableListOf() for (formatter in DATE_TIME_FORMATTERS) { @@ -149,18 +156,20 @@ private class LenientLocalDateTimeDeserializer : val temporal = formatter.parse(p.text) return when { - !temporal.isSupported(ChronoField.HOUR_OF_DAY) -> - LocalDate.from(temporal).atStartOfDay() - !temporal.isSupported(ChronoField.OFFSET_SECONDS) -> - LocalDateTime.from(temporal) - else -> ZonedDateTime.from(temporal).toLocalDateTime() - } + !temporal.isSupported(ChronoField.HOUR_OF_DAY) -> + LocalDate.from(temporal).atStartOfDay() + !temporal.isSupported(ChronoField.OFFSET_SECONDS) -> + LocalDateTime.from(temporal) + else -> ZonedDateTime.from(temporal).toLocalDateTime() + } + .atZone(context.timeZone.toZoneId()) + .toOffsetDateTime() } catch (e: DateTimeException) { exceptions.add(e) } } - throw JsonParseException(p, "Cannot parse `LocalDateTime` from value: ${p.text}").apply { + throw JsonParseException(p, "Cannot parse `OffsetDateTime` from value: ${p.text}").apply { exceptions.forEach { addSuppressed(it) } } } diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/ModelConfig.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/ModelConfig.kt index f8467e9..e104ae5 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/ModelConfig.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/ModelConfig.kt @@ -197,7 +197,7 @@ private constructor( .toList() return when (bestMatches.size) { // This can happen if what we're deserializing is completely incompatible with all - // the possible variants (e.g. deserializing from array). + // the possible variants (e.g. deserializing from boolean). 0 -> ModelConfig(_json = json) 1 -> bestMatches.single() // If there's more than one match with the highest validity, then use the first diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt index 92df30c..6a0794b 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt @@ -742,7 +742,7 @@ private constructor( .toList() return when (bestMatches.size) { // This can happen if what we're deserializing is completely incompatible with - // all the possible variants (e.g. deserializing from array). + // all the possible variants (e.g. deserializing from boolean). 0 -> Input(_json = json) 1 -> bestMatches.single() // If there's more than one match with the highest validity, then use the first diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/ObjectMappersTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/ObjectMappersTest.kt index d405053..fed5676 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/ObjectMappersTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/ObjectMappersTest.kt @@ -3,7 +3,7 @@ package com.browserbase.api.core import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.exc.MismatchedInputException import com.fasterxml.jackson.module.kotlin.readValue -import java.time.LocalDateTime +import java.time.OffsetDateTime import kotlin.reflect.KClass import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.catchThrowable @@ -58,14 +58,6 @@ internal class ObjectMappersTest { LONG to DOUBLE, LONG to INTEGER, CLASS to MAP, - // These aren't actually valid, but coercion configs don't work for String until - // v2.14.0: https://github.com/FasterXML/jackson-databind/issues/3240 - // We currently test on v2.13.4. - BOOLEAN to STRING, - FLOAT to STRING, - DOUBLE to STRING, - INTEGER to STRING, - LONG to STRING, ) } } @@ -84,7 +76,7 @@ internal class ObjectMappersTest { } } - enum class LenientLocalDateTimeTestCase(val string: String) { + enum class LenientOffsetDateTimeTestCase(val string: String) { DATE("1998-04-21"), DATE_TIME("1998-04-21T04:00:00"), ZONED_DATE_TIME_1("1998-04-21T04:00:00+03:00"), @@ -93,10 +85,10 @@ internal class ObjectMappersTest { @ParameterizedTest @EnumSource - fun readLocalDateTime_lenient(testCase: LenientLocalDateTimeTestCase) { + fun readOffsetDateTime_lenient(testCase: LenientOffsetDateTimeTestCase) { val jsonMapper = jsonMapper() val json = jsonMapper.writeValueAsString(testCase.string) - assertDoesNotThrow { jsonMapper().readValue(json) } + assertDoesNotThrow { jsonMapper().readValue(json) } } } diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/ModelConfigTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/ModelConfigTest.kt index 0190f8e..467ac79 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/ModelConfigTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/ModelConfigTest.kt @@ -9,6 +9,8 @@ import com.fasterxml.jackson.module.kotlin.jacksonTypeRef import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource internal class ModelConfigTest { @@ -74,10 +76,17 @@ internal class ModelConfigTest { assertThat(roundtrippedModelConfig).isEqualTo(modelConfig) } - @Test - fun incompatibleJsonShapeDeserializesToUnknown() { - val value = JsonValue.from(listOf("invalid", "array")) - val modelConfig = jsonMapper().convertValue(value, jacksonTypeRef()) + enum class IncompatibleJsonShapeTestCase(val value: JsonValue) { + BOOLEAN(JsonValue.from(false)), + INTEGER(JsonValue.from(-1)), + FLOAT(JsonValue.from(3.14)), + ARRAY(JsonValue.from(listOf("invalid", "array"))), + } + + @ParameterizedTest + @EnumSource + fun incompatibleJsonShapeDeserializesToUnknown(testCase: IncompatibleJsonShapeTestCase) { + val modelConfig = jsonMapper().convertValue(testCase.value, jacksonTypeRef()) val e = assertThrows { modelConfig.validate() } assertThat(e).hasMessageStartingWith("Unknown ") diff --git a/stagehand-java-proguard-test/build.gradle.kts b/stagehand-java-proguard-test/build.gradle.kts index 9641bb2..1f6dfc6 100644 --- a/stagehand-java-proguard-test/build.gradle.kts +++ b/stagehand-java-proguard-test/build.gradle.kts @@ -19,7 +19,7 @@ dependencies { testImplementation(kotlin("test")) testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3") testImplementation("org.assertj:assertj-core:3.25.3") - testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.4") + testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.0") } tasks.shadowJar { From 07236764dcf17210682b14d2f30b41b87a35061c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 05:25:14 +0000 Subject: [PATCH 18/44] chore(internal): improve maven repo docs --- scripts/upload-artifacts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/upload-artifacts b/scripts/upload-artifacts index df0c8d9..548d152 100755 --- a/scripts/upload-artifacts +++ b/scripts/upload-artifacts @@ -56,12 +56,13 @@ upload_file() { # Upload file upload_response=$(curl -v -X PUT \ --retry 5 \ + --retry-all-errors \ -D "$tmp_headers" \ -H "Content-Type: $content_type" \ --data-binary "@${file_name}" "$signed_url" 2>&1) if ! echo "$upload_response" | grep -q "HTTP/[0-9.]* 200"; then - log_error "Failed upload artifact file" "$tmp_headers" "$upload_response" + log_error "Failed to upload artifact file" "$tmp_headers" "$upload_response" fi # Insert small throttle to reduce rate limiting risk @@ -110,6 +111,10 @@ generate_instructions() { url 'https://pkg.stainless.com/s/${PROJECT}/${SHA}/mvn' } } + +

Once you've added the repository, you can include dependencies from it as usual. See your + project README + for more details.

EOF From 012dbc823262bf9707c4bac7ec161c79a0592e97 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 05:30:33 +0000 Subject: [PATCH 19/44] fix(client): disallow coercion from float to int --- .../src/main/kotlin/com/browserbase/api/core/ObjectMappers.kt | 1 + .../test/kotlin/com/browserbase/api/core/ObjectMappersTest.kt | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ObjectMappers.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ObjectMappers.kt index 1d4e317..06367fe 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ObjectMappers.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ObjectMappers.kt @@ -48,6 +48,7 @@ fun jsonMapper(): JsonMapper = } .withCoercionConfig(LogicalType.Integer) { it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail) + .setCoercion(CoercionInputShape.Float, CoercionAction.Fail) .setCoercion(CoercionInputShape.String, CoercionAction.Fail) .setCoercion(CoercionInputShape.Array, CoercionAction.Fail) .setCoercion(CoercionInputShape.Object, CoercionAction.Fail) diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/ObjectMappersTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/ObjectMappersTest.kt index fed5676..bc80611 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/ObjectMappersTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/ObjectMappersTest.kt @@ -46,11 +46,7 @@ internal class ObjectMappersTest { val VALID_CONVERSIONS = listOf( FLOAT to DOUBLE, - FLOAT to INTEGER, - FLOAT to LONG, DOUBLE to FLOAT, - DOUBLE to INTEGER, - DOUBLE to LONG, INTEGER to FLOAT, INTEGER to DOUBLE, INTEGER to LONG, From 77009b1b8512e7eb8f194fbc11564958d92f3028 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 05:31:24 +0000 Subject: [PATCH 20/44] chore(internal): update `actions/checkout` version --- .github/workflows/ci.yml | 6 +++--- .github/workflows/publish-sonatype.yml | 2 +- .github/workflows/release-doctor.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e136c7c..377c7f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Java uses: actions/setup-java@v4 @@ -47,7 +47,7 @@ jobs: if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Java uses: actions/setup-java@v4 @@ -85,7 +85,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/stagehand-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Java uses: actions/setup-java@v4 diff --git a/.github/workflows/publish-sonatype.yml b/.github/workflows/publish-sonatype.yml index 67e28c7..874ace6 100644 --- a/.github/workflows/publish-sonatype.yml +++ b/.github/workflows/publish-sonatype.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Java uses: actions/setup-java@v4 diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index a6e0235..e71595c 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'browserbase/stagehand-java' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Check release environment run: | From f8b09f098ce22d6e8dce3f46ff67cc199f3e10ab Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 05:33:18 +0000 Subject: [PATCH 21/44] fix(client): fully respect max retries fix(client): send retry count header for max retries 0 chore(internal): depend on packages directly in example --- .../api/client/okhttp/OkHttpClient.kt | 2 ++ .../api/core/http/RetryingHttpClient.kt | 20 +++++++++---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/OkHttpClient.kt b/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/OkHttpClient.kt index 116714c..dadd500 100644 --- a/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/OkHttpClient.kt +++ b/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/OkHttpClient.kt @@ -234,6 +234,8 @@ private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClien fun build(): OkHttpClient = OkHttpClient( okhttp3.OkHttpClient.Builder() + // `RetryingHttpClient` handles retries if the user enabled them. + .retryOnConnectionFailure(false) .connectTimeout(timeout.connect()) .readTimeout(timeout.read()) .writeTimeout(timeout.write()) diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/RetryingHttpClient.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/RetryingHttpClient.kt index 7e1a7aa..1305c6a 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/RetryingHttpClient.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/RetryingHttpClient.kt @@ -31,10 +31,6 @@ private constructor( ) : HttpClient { override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse { - if (!isRetryable(request) || maxRetries <= 0) { - return httpClient.execute(request, requestOptions) - } - var modifiedRequest = maybeAddIdempotencyHeader(request) // Don't send the current retry count in the headers if the caller set their own value. @@ -48,6 +44,10 @@ private constructor( modifiedRequest = setRetryCountHeader(modifiedRequest, retries) } + if (!isRetryable(modifiedRequest)) { + return httpClient.execute(modifiedRequest, requestOptions) + } + val response = try { val response = httpClient.execute(modifiedRequest, requestOptions) @@ -75,10 +75,6 @@ private constructor( request: HttpRequest, requestOptions: RequestOptions, ): CompletableFuture { - if (!isRetryable(request) || maxRetries <= 0) { - return httpClient.executeAsync(request, requestOptions) - } - val modifiedRequest = maybeAddIdempotencyHeader(request) // Don't send the current retry count in the headers if the caller set their own value. @@ -94,8 +90,12 @@ private constructor( val requestWithRetryCount = if (shouldSendRetryCount) setRetryCountHeader(request, retries) else request - return httpClient - .executeAsync(requestWithRetryCount, requestOptions) + val responseFuture = httpClient.executeAsync(requestWithRetryCount, requestOptions) + if (!isRetryable(requestWithRetryCount)) { + return responseFuture + } + + return responseFuture .handleAsync( fun( response: HttpResponse?, From 8b3f116aff59f0a870c8bb55410fcd37c122b708 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 05:34:12 +0000 Subject: [PATCH 22/44] chore(ci): upgrade `actions/setup-java` --- .github/workflows/ci.yml | 6 +++--- .github/workflows/publish-sonatype.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 377c7f1..49979dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: | @@ -50,7 +50,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: | @@ -88,7 +88,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: | diff --git a/.github/workflows/publish-sonatype.yml b/.github/workflows/publish-sonatype.yml index 874ace6..e0e380c 100644 --- a/.github/workflows/publish-sonatype.yml +++ b/.github/workflows/publish-sonatype.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: | From d35060f494e6f2659f9303ebf68d304dad85bc08 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 18:48:37 +0000 Subject: [PATCH 23/44] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 71adca4..7fb6541 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-07032b695bc66ecd76328d936b41e01cfad508a870928c09c185f5faa5ea66ab.yml openapi_spec_hash: fca4b895ce36ad547fb015c3dd38821f -config_hash: bf22187c626f0401180a332e3f3f6d8c +config_hash: 836c4460e94656a3e75bfe1eb8a677df From 57d6b94a1391ebd8b8023063f78d5b76bf91b65c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 01:37:02 +0000 Subject: [PATCH 24/44] feat: move Stainless compatibility transforms from gen-openapi.ts into stainless.yml --- .github/workflows/publish-sonatype.yml | 2 +- .stats.yml | 6 +- LICENSE | 202 ++++- README.md | 20 +- build.gradle.kts | 13 + buildSrc/build.gradle.kts | 3 - .../src/main/kotlin/stagehand.java.gradle.kts | 9 + .../main/kotlin/stagehand.publish.gradle.kts | 105 ++- .../api/models/sessions/ModelConfig.kt | 808 +++++++----------- .../api/models/sessions/SessionActParams.kt | 18 - .../models/sessions/SessionExecuteParams.kt | 18 - .../models/sessions/SessionExtractParams.kt | 18 - .../models/sessions/SessionObserveParams.kt | 18 - .../api/models/sessions/SessionStartParams.kt | 17 +- .../api/models/sessions/ModelConfigTest.kt | 79 +- .../models/sessions/SessionActParamsTest.kt | 36 +- .../sessions/SessionExecuteParamsTest.kt | 36 +- .../sessions/SessionExtractParamsTest.kt | 36 +- .../sessions/SessionObserveParamsTest.kt | 36 +- .../api/services/ServiceParamsTest.kt | 10 +- .../services/async/SessionServiceAsyncTest.kt | 73 +- .../services/blocking/SessionServiceTest.kt | 73 +- .../java/com/stagehand/api/example/Main.java | 9 +- .../api/proguard/ProGuardCompatibilityTest.kt | 15 - 24 files changed, 873 insertions(+), 787 deletions(-) diff --git a/.github/workflows/publish-sonatype.yml b/.github/workflows/publish-sonatype.yml index e0e380c..d01e5dd 100644 --- a/.github/workflows/publish-sonatype.yml +++ b/.github/workflows/publish-sonatype.yml @@ -33,7 +33,7 @@ jobs: export -- GPG_SIGNING_KEY_ID printenv -- GPG_SIGNING_KEY | gpg --batch --passphrase-fd 3 --import 3<<< "$GPG_SIGNING_PASSWORD" GPG_SIGNING_KEY_ID="$(gpg --with-colons --list-keys | awk -F : -- '/^pub:/ { getline; print "0x" substr($10, length($10) - 7) }')" - ./gradlew publishAndReleaseToMavenCentral --stacktrace -PmavenCentralUsername="$SONATYPE_USERNAME" -PmavenCentralPassword="$SONATYPE_PASSWORD" --no-configuration-cache + ./gradlew publish --no-configuration-cache env: SONATYPE_USERNAME: ${{ secrets.STAGEHAND_SONATYPE_USERNAME || secrets.SONATYPE_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.STAGEHAND_SONATYPE_PASSWORD || secrets.SONATYPE_PASSWORD }} diff --git a/.stats.yml b/.stats.yml index 7fb6541..9c0f6d9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-07032b695bc66ecd76328d936b41e01cfad508a870928c09c185f5faa5ea66ab.yml -openapi_spec_hash: fca4b895ce36ad547fb015c3dd38821f -config_hash: 836c4460e94656a3e75bfe1eb8a677df +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-d91dfd9ce727aefac56505ac86feb5cc665d3d5cef80ff17605f45e09b258251.yml +openapi_spec_hash: 0d1cae70e9d1debc5ac1a69fc665af37 +config_hash: 64c9cc393de93af70e11dbf0b1ba9388 diff --git a/LICENSE b/LICENSE index a7b82c2..d15d021 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,201 @@ -Copyright 2026 stagehand + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + 1. Definitions. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Stagehand + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index ac8460d..c22664e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ +# Stagehand Java API Library + + + +[![Maven Central](https://img.shields.io/maven-central/v/com.browserbase.api/stagehand-java)](https://central.sonatype.com/artifact/com.browserbase.api/stagehand-java/0.6.1) +[![javadoc](https://javadoc.io/badge2/com.browserbase.api/stagehand-java/0.6.1/javadoc.svg)](https://javadoc.io/doc/com.browserbase.api/stagehand-java/0.6.1) + + + +The Stagehand Java SDK provides convenient access to the [Stagehand REST API](https://docs.stagehand.dev) from applications written in Java.