Skip to content

Commit 769ed85

Browse files
feat: add v3 integration tests matching cloud exactly
1 parent 3d9f1bb commit 769ed85

5 files changed

Lines changed: 800 additions & 43 deletions

File tree

.stats.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 7
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-2132f9afd90a3b3c35b772f0de3ac8432cfe46ecfa22ec9c1ed4a3d0eda1ad41.yml
3-
openapi_spec_hash: 6da568c2948d8ab6000db4291e15a033
4-
config_hash: 64c9cc393de93af70e11dbf0b1ba9388
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-1c405024b4a17886e921871250a445362463b7ae6070cacc3d1927d59036730c.yml
3+
openapi_spec_hash: c4ea3735257a48ed105002eb7aaa095a
4+
config_hash: 85d56c7c196269badbd0b4a9dfb28d45

stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt

Lines changed: 191 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -773,26 +773,28 @@ private constructor(
773773
class Options
774774
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
775775
private constructor(
776-
private val model: JsonField<ModelConfig>,
776+
private val model: JsonField<Model>,
777777
private val timeout: JsonField<Double>,
778778
private val variables: JsonField<Variables>,
779779
private val additionalProperties: MutableMap<String, JsonValue>,
780780
) {
781781

782782
@JsonCreator
783783
private constructor(
784-
@JsonProperty("model") @ExcludeMissing model: JsonField<ModelConfig> = JsonMissing.of(),
784+
@JsonProperty("model") @ExcludeMissing model: JsonField<Model> = JsonMissing.of(),
785785
@JsonProperty("timeout") @ExcludeMissing timeout: JsonField<Double> = JsonMissing.of(),
786786
@JsonProperty("variables")
787787
@ExcludeMissing
788788
variables: JsonField<Variables> = JsonMissing.of(),
789789
) : this(model, timeout, variables, mutableMapOf())
790790

791791
/**
792+
* Model configuration object or model name string (e.g., 'openai/gpt-5-nano')
793+
*
792794
* @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if
793795
* the server responded with an unexpected value).
794796
*/
795-
fun model(): Optional<ModelConfig> = model.getOptional("model")
797+
fun model(): Optional<Model> = model.getOptional("model")
796798

797799
/**
798800
* Timeout in ms for the action
@@ -815,7 +817,7 @@ private constructor(
815817
*
816818
* Unlike [model], this method doesn't throw if the JSON field has an unexpected type.
817819
*/
818-
@JsonProperty("model") @ExcludeMissing fun _model(): JsonField<ModelConfig> = model
820+
@JsonProperty("model") @ExcludeMissing fun _model(): JsonField<Model> = model
819821

820822
/**
821823
* Returns the raw JSON value of [timeout].
@@ -854,7 +856,7 @@ private constructor(
854856
/** A builder for [Options]. */
855857
class Builder internal constructor() {
856858

857-
private var model: JsonField<ModelConfig> = JsonMissing.of()
859+
private var model: JsonField<Model> = JsonMissing.of()
858860
private var timeout: JsonField<Double> = JsonMissing.of()
859861
private var variables: JsonField<Variables> = JsonMissing.of()
860862
private var additionalProperties: MutableMap<String, JsonValue> = mutableMapOf()
@@ -867,16 +869,23 @@ private constructor(
867869
additionalProperties = options.additionalProperties.toMutableMap()
868870
}
869871

870-
fun model(model: ModelConfig) = model(JsonField.of(model))
872+
/** Model configuration object or model name string (e.g., 'openai/gpt-5-nano') */
873+
fun model(model: Model) = model(JsonField.of(model))
871874

872875
/**
873876
* Sets [Builder.model] to an arbitrary JSON value.
874877
*
875-
* You should usually call [Builder.model] with a well-typed [ModelConfig] value
876-
* instead. This method is primarily for setting the field to an undocumented or not yet
877-
* supported value.
878+
* You should usually call [Builder.model] with a well-typed [Model] value instead. This
879+
* method is primarily for setting the field to an undocumented or not yet supported
880+
* value.
878881
*/
879-
fun model(model: JsonField<ModelConfig>) = apply { this.model = model }
882+
fun model(model: JsonField<Model>) = apply { this.model = model }
883+
884+
/** Alias for calling [model] with `Model.ofConfig(config)`. */
885+
fun model(config: ModelConfig) = model(Model.ofConfig(config))
886+
887+
/** Alias for calling [model] with `Model.ofString(string)`. */
888+
fun model(string: String) = model(Model.ofString(string))
880889

881890
/** Timeout in ms for the action */
882891
fun timeout(timeout: Double) = timeout(JsonField.of(timeout))
@@ -963,6 +972,178 @@ private constructor(
963972
(if (timeout.asKnown().isPresent) 1 else 0) +
964973
(variables.asKnown().getOrNull()?.validity() ?: 0)
965974

975+
/** Model configuration object or model name string (e.g., 'openai/gpt-5-nano') */
976+
@JsonDeserialize(using = Model.Deserializer::class)
977+
@JsonSerialize(using = Model.Serializer::class)
978+
class Model
979+
private constructor(
980+
private val config: ModelConfig? = null,
981+
private val string: String? = null,
982+
private val _json: JsonValue? = null,
983+
) {
984+
985+
fun config(): Optional<ModelConfig> = Optional.ofNullable(config)
986+
987+
fun string(): Optional<String> = Optional.ofNullable(string)
988+
989+
fun isConfig(): Boolean = config != null
990+
991+
fun isString(): Boolean = string != null
992+
993+
fun asConfig(): ModelConfig = config.getOrThrow("config")
994+
995+
fun asString(): String = string.getOrThrow("string")
996+
997+
fun _json(): Optional<JsonValue> = Optional.ofNullable(_json)
998+
999+
fun <T> accept(visitor: Visitor<T>): T =
1000+
when {
1001+
config != null -> visitor.visitConfig(config)
1002+
string != null -> visitor.visitString(string)
1003+
else -> visitor.unknown(_json)
1004+
}
1005+
1006+
private var validated: Boolean = false
1007+
1008+
fun validate(): Model = apply {
1009+
if (validated) {
1010+
return@apply
1011+
}
1012+
1013+
accept(
1014+
object : Visitor<Unit> {
1015+
override fun visitConfig(config: ModelConfig) {
1016+
config.validate()
1017+
}
1018+
1019+
override fun visitString(string: String) {}
1020+
}
1021+
)
1022+
validated = true
1023+
}
1024+
1025+
fun isValid(): Boolean =
1026+
try {
1027+
validate()
1028+
true
1029+
} catch (e: StagehandInvalidDataException) {
1030+
false
1031+
}
1032+
1033+
/**
1034+
* Returns a score indicating how many valid values are contained in this object
1035+
* recursively.
1036+
*
1037+
* Used for best match union deserialization.
1038+
*/
1039+
@JvmSynthetic
1040+
internal fun validity(): Int =
1041+
accept(
1042+
object : Visitor<Int> {
1043+
override fun visitConfig(config: ModelConfig) = config.validity()
1044+
1045+
override fun visitString(string: String) = 1
1046+
1047+
override fun unknown(json: JsonValue?) = 0
1048+
}
1049+
)
1050+
1051+
override fun equals(other: Any?): Boolean {
1052+
if (this === other) {
1053+
return true
1054+
}
1055+
1056+
return other is Model && config == other.config && string == other.string
1057+
}
1058+
1059+
override fun hashCode(): Int = Objects.hash(config, string)
1060+
1061+
override fun toString(): String =
1062+
when {
1063+
config != null -> "Model{config=$config}"
1064+
string != null -> "Model{string=$string}"
1065+
_json != null -> "Model{_unknown=$_json}"
1066+
else -> throw IllegalStateException("Invalid Model")
1067+
}
1068+
1069+
companion object {
1070+
1071+
@JvmStatic fun ofConfig(config: ModelConfig) = Model(config = config)
1072+
1073+
@JvmStatic fun ofString(string: String) = Model(string = string)
1074+
}
1075+
1076+
/**
1077+
* An interface that defines how to map each variant of [Model] to a value of type [T].
1078+
*/
1079+
interface Visitor<out T> {
1080+
1081+
fun visitConfig(config: ModelConfig): T
1082+
1083+
fun visitString(string: String): T
1084+
1085+
/**
1086+
* Maps an unknown variant of [Model] to a value of type [T].
1087+
*
1088+
* An instance of [Model] can contain an unknown variant if it was deserialized from
1089+
* data that doesn't match any known variant. For example, if the SDK is on an older
1090+
* version than the API, then the API may respond with new variants that the SDK is
1091+
* unaware of.
1092+
*
1093+
* @throws StagehandInvalidDataException in the default implementation.
1094+
*/
1095+
fun unknown(json: JsonValue?): T {
1096+
throw StagehandInvalidDataException("Unknown Model: $json")
1097+
}
1098+
}
1099+
1100+
internal class Deserializer : BaseDeserializer<Model>(Model::class) {
1101+
1102+
override fun ObjectCodec.deserialize(node: JsonNode): Model {
1103+
val json = JsonValue.fromJsonNode(node)
1104+
1105+
val bestMatches =
1106+
sequenceOf(
1107+
tryDeserialize(node, jacksonTypeRef<ModelConfig>())?.let {
1108+
Model(config = it, _json = json)
1109+
},
1110+
tryDeserialize(node, jacksonTypeRef<String>())?.let {
1111+
Model(string = it, _json = json)
1112+
},
1113+
)
1114+
.filterNotNull()
1115+
.allMaxBy { it.validity() }
1116+
.toList()
1117+
return when (bestMatches.size) {
1118+
// This can happen if what we're deserializing is completely incompatible
1119+
// with all the possible variants (e.g. deserializing from boolean).
1120+
0 -> Model(_json = json)
1121+
1 -> bestMatches.single()
1122+
// If there's more than one match with the highest validity, then use the
1123+
// first completely valid match, or simply the first match if none are
1124+
// completely valid.
1125+
else -> bestMatches.firstOrNull { it.isValid() } ?: bestMatches.first()
1126+
}
1127+
}
1128+
}
1129+
1130+
internal class Serializer : BaseSerializer<Model>(Model::class) {
1131+
1132+
override fun serialize(
1133+
value: Model,
1134+
generator: JsonGenerator,
1135+
provider: SerializerProvider,
1136+
) {
1137+
when {
1138+
value.config != null -> generator.writeObject(value.config)
1139+
value.string != null -> generator.writeObject(value.string)
1140+
value._json != null -> generator.writeObject(value._json)
1141+
else -> throw IllegalStateException("Invalid Model")
1142+
}
1143+
}
1144+
}
1145+
}
1146+
9661147
/** Variables to substitute in the action instruction */
9671148
class Variables
9681149
@JsonCreator

0 commit comments

Comments
 (0)