diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 052d78b7f..647ed9ca9 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "4.18.0"
+ ".": "4.19.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 766c6aefd..10450956d 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 136
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-64c3a646eb5dcad2b7ff7bd976c0e312b886676a542f6ffcd9a6c8503ae24c58.yml
-openapi_spec_hash: 91b1b7bf3c1a6b6c9c7507d4cac8fe2a
-config_hash: f8e6baff429cf000b8e4ba1da08dff47
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-bff810f46da56eff8d5e189b0d1f56ac07a8289723666138549d4239cad7c2ea.yml
+openapi_spec_hash: 7532ce5a6f490c8f5d1e079c76c70535
+config_hash: a1454ffd9612dee11f9d5a98e55eac9e
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 090330c7c..78ee60022 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,20 @@
# Changelog
+## 4.19.0 (2026-02-09)
+
+Full Changelog: [v4.18.0...v4.19.0](https://github.com/openai/openai-java/compare/v4.18.0...v4.19.0)
+
+### Features
+
+* **api:** add context_management to responses ([da0fb59](https://github.com/openai/openai-java/commit/da0fb59b0e0362ee68353829c97ac8b2944cd49b))
+* **api:** add webhook signature verification ([1823eca](https://github.com/openai/openai-java/commit/1823ecab53fd72ef7f7fdc7776e6ecd631307f7c))
+* **api:** responses context_management ([c0f2cd1](https://github.com/openai/openai-java/commit/c0f2cd1c854d6fca90c9000de46f042e64a8cbf5))
+
+
+### Chores
+
+* **internal:** upgrade AssertJ ([5c01787](https://github.com/openai/openai-java/commit/5c017872b071272eff021318dab9a4a774cfcd4c))
+
## 4.18.0 (2026-02-05)
Full Changelog: [v4.17.0...v4.18.0](https://github.com/openai/openai-java/compare/v4.17.0...v4.18.0)
diff --git a/README.md b/README.md
index be008a1ee..bad6ed44c 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
-[](https://central.sonatype.com/artifact/com.openai/openai-java/4.18.0)
-[](https://javadoc.io/doc/com.openai/openai-java/4.18.0)
+[](https://central.sonatype.com/artifact/com.openai/openai-java/4.19.0)
+[](https://javadoc.io/doc/com.openai/openai-java/4.19.0)
@@ -11,7 +11,7 @@ The OpenAI Java SDK provides convenient access to the [OpenAI REST API](https://
-The REST API documentation can be found on [platform.openai.com](https://platform.openai.com/docs). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.openai/openai-java/4.18.0).
+The REST API documentation can be found on [platform.openai.com](https://platform.openai.com/docs). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.openai/openai-java/4.19.0).
@@ -24,7 +24,7 @@ The REST API documentation can be found on [platform.openai.com](https://platfor
### Gradle
```kotlin
-implementation("com.openai:openai-java:4.18.0")
+implementation("com.openai:openai-java:4.19.0")
```
### Maven
@@ -33,7 +33,7 @@ implementation("com.openai:openai-java:4.18.0")
com.openai
openai-java
- 4.18.0
+ 4.19.0
```
@@ -1342,7 +1342,7 @@ If you're using Spring Boot, then you can use the SDK's [Spring Boot starter](ht
#### Gradle
```kotlin
-implementation("com.openai:openai-java-spring-boot-starter:4.18.0")
+implementation("com.openai:openai-java-spring-boot-starter:4.19.0")
```
#### Maven
@@ -1351,7 +1351,7 @@ implementation("com.openai:openai-java-spring-boot-starter:4.18.0")
com.openai
openai-java-spring-boot-starter
- 4.18.0
+ 4.19.0
```
diff --git a/build.gradle.kts b/build.gradle.kts
index bbfa8a077..1a7dcb638 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -8,7 +8,7 @@ repositories {
allprojects {
group = "com.openai"
- version = "4.18.0" // x-release-please-version
+ version = "4.19.0" // x-release-please-version
}
subprojects {
diff --git a/openai-java-client-okhttp/build.gradle.kts b/openai-java-client-okhttp/build.gradle.kts
index 272eaba29..753e4fbee 100644
--- a/openai-java-client-okhttp/build.gradle.kts
+++ b/openai-java-client-okhttp/build.gradle.kts
@@ -10,6 +10,6 @@ dependencies {
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
testImplementation(kotlin("test"))
- testImplementation("org.assertj:assertj-core:3.25.3")
+ testImplementation("org.assertj:assertj-core:3.27.7")
testImplementation("com.github.tomakehurst:wiremock-jre8:2.35.2")
}
diff --git a/openai-java-core/build.gradle.kts b/openai-java-core/build.gradle.kts
index ec4a8ca24..cddd7776f 100644
--- a/openai-java-core/build.gradle.kts
+++ b/openai-java-core/build.gradle.kts
@@ -38,7 +38,7 @@ dependencies {
testImplementation(kotlin("test"))
testImplementation(project(":openai-java-client-okhttp"))
testImplementation("com.github.tomakehurst:wiremock-jre8:2.35.2")
- testImplementation("org.assertj:assertj-core:3.25.3")
+ testImplementation("org.assertj:assertj-core:3.27.7")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.3")
testImplementation("org.junit-pioneer:junit-pioneer:1.9.1")
diff --git a/openai-java-core/src/main/kotlin/com/openai/models/images/ImageEditParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/images/ImageEditParams.kt
index 9b1799cb8..e836c56b0 100644
--- a/openai-java-core/src/main/kotlin/com/openai/models/images/ImageEditParams.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/models/images/ImageEditParams.kt
@@ -82,8 +82,9 @@ private constructor(
/**
* Control how much effort the model will exert to match the style and features, especially
- * facial features, of input images. This parameter is only supported for `gpt-image-1`.
- * Unsupported for `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`.
+ * facial features, of input images. This parameter is only supported for `gpt-image-1` and
+ * `gpt-image-1.5` and later models, unsupported for `gpt-image-1-mini`. Supports `high` and
+ * `low`. Defaults to `low`.
*
* @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
@@ -438,8 +439,9 @@ private constructor(
/**
* Control how much effort the model will exert to match the style and features, especially
- * facial features, of input images. This parameter is only supported for `gpt-image-1`.
- * Unsupported for `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`.
+ * facial features, of input images. This parameter is only supported for `gpt-image-1` and
+ * `gpt-image-1.5` and later models, unsupported for `gpt-image-1-mini`. Supports `high` and
+ * `low`. Defaults to `low`.
*/
fun inputFidelity(inputFidelity: InputFidelity?) = apply {
body.inputFidelity(inputFidelity)
@@ -917,8 +919,9 @@ private constructor(
/**
* Control how much effort the model will exert to match the style and features, especially
- * facial features, of input images. This parameter is only supported for `gpt-image-1`.
- * Unsupported for `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`.
+ * facial features, of input images. This parameter is only supported for `gpt-image-1` and
+ * `gpt-image-1.5` and later models, unsupported for `gpt-image-1-mini`. Supports `high` and
+ * `low`. Defaults to `low`.
*
* @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
@@ -1319,8 +1322,8 @@ private constructor(
/**
* Control how much effort the model will exert to match the style and features,
* especially facial features, of input images. This parameter is only supported for
- * `gpt-image-1`. Unsupported for `gpt-image-1-mini`. Supports `high` and `low`.
- * Defaults to `low`.
+ * `gpt-image-1` and `gpt-image-1.5` and later models, unsupported for
+ * `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`.
*/
fun inputFidelity(inputFidelity: InputFidelity?) =
inputFidelity(MultipartField.of(inputFidelity))
@@ -2005,8 +2008,9 @@ private constructor(
/**
* Control how much effort the model will exert to match the style and features, especially
- * facial features, of input images. This parameter is only supported for `gpt-image-1`.
- * Unsupported for `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`.
+ * facial features, of input images. This parameter is only supported for `gpt-image-1` and
+ * `gpt-image-1.5` and later models, unsupported for `gpt-image-1-mini`. Supports `high` and
+ * `low`. Defaults to `low`.
*/
class InputFidelity @JsonCreator private constructor(private val value: JsonField) :
Enum {
diff --git a/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseCreateParams.kt
index 834f54a49..c08c22777 100644
--- a/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseCreateParams.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseCreateParams.kt
@@ -24,6 +24,7 @@ import com.openai.core.JsonValue
import com.openai.core.Params
import com.openai.core.allMaxBy
import com.openai.core.checkKnown
+import com.openai.core.checkRequired
import com.openai.core.getOrThrow
import com.openai.core.http.Headers
import com.openai.core.http.QueryParams
@@ -65,6 +66,14 @@ private constructor(
*/
fun background(): Optional = body.background()
+ /**
+ * Context management configuration for this request.
+ *
+ * @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun contextManagement(): Optional> = body.contextManagement()
+
/**
* The conversation that this response belongs to. Items from this conversation are prepended to
* `input_items` for this response request. Input items and output items from this response are
@@ -378,6 +387,14 @@ private constructor(
*/
fun _background(): JsonField = body._background()
+ /**
+ * Returns the raw JSON value of [contextManagement].
+ *
+ * Unlike [contextManagement], this method doesn't throw if the JSON field has an unexpected
+ * type.
+ */
+ fun _contextManagement(): JsonField> = body._contextManagement()
+
/**
* Returns the raw JSON value of [conversation].
*
@@ -602,10 +619,10 @@ private constructor(
* This is generally only useful if you are already constructing the body separately.
* Otherwise, it's more convenient to use the top-level setters instead:
* - [background]
+ * - [contextManagement]
* - [conversation]
* - [include]
* - [input]
- * - [instructions]
* - etc.
*/
fun body(body: Body) = apply { this.body = body.toBuilder() }
@@ -635,6 +652,35 @@ private constructor(
*/
fun background(background: JsonField) = apply { body.background(background) }
+ /** Context management configuration for this request. */
+ fun contextManagement(contextManagement: List?) = apply {
+ body.contextManagement(contextManagement)
+ }
+
+ /** Alias for calling [Builder.contextManagement] with `contextManagement.orElse(null)`. */
+ fun contextManagement(contextManagement: Optional>) =
+ contextManagement(contextManagement.getOrNull())
+
+ /**
+ * Sets [Builder.contextManagement] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.contextManagement] with a well-typed
+ * `List` value instead. This method is primarily for setting the field
+ * to an undocumented or not yet supported value.
+ */
+ fun contextManagement(contextManagement: JsonField>) = apply {
+ body.contextManagement(contextManagement)
+ }
+
+ /**
+ * Adds a single [ContextManagement] to [Builder.contextManagement].
+ *
+ * @throws IllegalStateException if the field was previously set to a non-list.
+ */
+ fun addContextManagement(contextManagement: ContextManagement) = apply {
+ body.addContextManagement(contextManagement)
+ }
+
/**
* The conversation that this response belongs to. Items from this conversation are
* prepended to `input_items` for this response request. Input items and output items from
@@ -1595,6 +1641,7 @@ private constructor(
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val background: JsonField,
+ private val contextManagement: JsonField>,
private val conversation: JsonField,
private val include: JsonField>,
private val input: JsonField,
@@ -1629,6 +1676,9 @@ private constructor(
@JsonProperty("background")
@ExcludeMissing
background: JsonField = JsonMissing.of(),
+ @JsonProperty("context_management")
+ @ExcludeMissing
+ contextManagement: JsonField> = JsonMissing.of(),
@JsonProperty("conversation")
@ExcludeMissing
conversation: JsonField = JsonMissing.of(),
@@ -1699,6 +1749,7 @@ private constructor(
@JsonProperty("user") @ExcludeMissing user: JsonField = JsonMissing.of(),
) : this(
background,
+ contextManagement,
conversation,
include,
input,
@@ -1737,6 +1788,15 @@ private constructor(
*/
fun background(): Optional = background.getOptional("background")
+ /**
+ * Context management configuration for this request.
+ *
+ * @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun contextManagement(): Optional> =
+ contextManagement.getOptional("context_management")
+
/**
* The conversation that this response belongs to. Items from this conversation are
* prepended to `input_items` for this response request. Input items and output items from
@@ -2058,6 +2118,16 @@ private constructor(
@ExcludeMissing
fun _background(): JsonField = background
+ /**
+ * Returns the raw JSON value of [contextManagement].
+ *
+ * Unlike [contextManagement], this method doesn't throw if the JSON field has an unexpected
+ * type.
+ */
+ @JsonProperty("context_management")
+ @ExcludeMissing
+ fun _contextManagement(): JsonField> = contextManagement
+
/**
* Returns the raw JSON value of [conversation].
*
@@ -2309,6 +2379,7 @@ private constructor(
class Builder internal constructor() {
private var background: JsonField = JsonMissing.of()
+ private var contextManagement: JsonField>? = null
private var conversation: JsonField = JsonMissing.of()
private var include: JsonField>? = null
private var input: JsonField = JsonMissing.of()
@@ -2340,6 +2411,7 @@ private constructor(
@JvmSynthetic
internal fun from(body: Body) = apply {
background = body.background
+ contextManagement = body.contextManagement.map { it.toMutableList() }
conversation = body.conversation
include = body.include.map { it.toMutableList() }
input = body.input
@@ -2394,6 +2466,39 @@ private constructor(
*/
fun background(background: JsonField) = apply { this.background = background }
+ /** Context management configuration for this request. */
+ fun contextManagement(contextManagement: List?) =
+ contextManagement(JsonField.ofNullable(contextManagement))
+
+ /**
+ * Alias for calling [Builder.contextManagement] with `contextManagement.orElse(null)`.
+ */
+ fun contextManagement(contextManagement: Optional>) =
+ contextManagement(contextManagement.getOrNull())
+
+ /**
+ * Sets [Builder.contextManagement] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.contextManagement] with a well-typed
+ * `List` value instead. This method is primarily for setting the
+ * field to an undocumented or not yet supported value.
+ */
+ fun contextManagement(contextManagement: JsonField>) = apply {
+ this.contextManagement = contextManagement.map { it.toMutableList() }
+ }
+
+ /**
+ * Adds a single [ContextManagement] to [Builder.contextManagement].
+ *
+ * @throws IllegalStateException if the field was previously set to a non-list.
+ */
+ fun addContextManagement(contextManagement: ContextManagement) = apply {
+ this.contextManagement =
+ (this.contextManagement ?: JsonField.of(mutableListOf())).also {
+ checkKnown("contextManagement", it).add(contextManagement)
+ }
+ }
+
/**
* The conversation that this response belongs to. Items from this conversation are
* prepended to `input_items` for this response request. Input items and output items
@@ -3225,6 +3330,7 @@ private constructor(
fun build(): Body =
Body(
background,
+ (contextManagement ?: JsonMissing.of()).map { it.toImmutable() },
conversation,
(include ?: JsonMissing.of()).map { it.toImmutable() },
input,
@@ -3263,6 +3369,7 @@ private constructor(
}
background()
+ contextManagement().ifPresent { it.forEach { it.validate() } }
conversation().ifPresent { it.validate() }
include().ifPresent { it.forEach { it.validate() } }
input().ifPresent { it.validate() }
@@ -3309,6 +3416,7 @@ private constructor(
@JvmSynthetic
internal fun validity(): Int =
(if (background.asKnown().isPresent) 1 else 0) +
+ (contextManagement.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) +
(conversation.asKnown().getOrNull()?.validity() ?: 0) +
(include.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) +
(input.asKnown().getOrNull()?.validity() ?: 0) +
@@ -3343,6 +3451,7 @@ private constructor(
return other is Body &&
background == other.background &&
+ contextManagement == other.contextManagement &&
conversation == other.conversation &&
include == other.include &&
input == other.input &&
@@ -3375,6 +3484,7 @@ private constructor(
private val hashCode: Int by lazy {
Objects.hash(
background,
+ contextManagement,
conversation,
include,
input,
@@ -3408,7 +3518,227 @@ private constructor(
override fun hashCode(): Int = hashCode
override fun toString() =
- "Body{background=$background, conversation=$conversation, include=$include, input=$input, instructions=$instructions, maxOutputTokens=$maxOutputTokens, maxToolCalls=$maxToolCalls, metadata=$metadata, model=$model, parallelToolCalls=$parallelToolCalls, previousResponseId=$previousResponseId, prompt=$prompt, promptCacheKey=$promptCacheKey, promptCacheRetention=$promptCacheRetention, reasoning=$reasoning, safetyIdentifier=$safetyIdentifier, serviceTier=$serviceTier, store=$store, streamOptions=$streamOptions, temperature=$temperature, text=$text, toolChoice=$toolChoice, tools=$tools, topLogprobs=$topLogprobs, topP=$topP, truncation=$truncation, user=$user, additionalProperties=$additionalProperties}"
+ "Body{background=$background, contextManagement=$contextManagement, conversation=$conversation, include=$include, input=$input, instructions=$instructions, maxOutputTokens=$maxOutputTokens, maxToolCalls=$maxToolCalls, metadata=$metadata, model=$model, parallelToolCalls=$parallelToolCalls, previousResponseId=$previousResponseId, prompt=$prompt, promptCacheKey=$promptCacheKey, promptCacheRetention=$promptCacheRetention, reasoning=$reasoning, safetyIdentifier=$safetyIdentifier, serviceTier=$serviceTier, store=$store, streamOptions=$streamOptions, temperature=$temperature, text=$text, toolChoice=$toolChoice, tools=$tools, topLogprobs=$topLogprobs, topP=$topP, truncation=$truncation, user=$user, additionalProperties=$additionalProperties}"
+ }
+
+ class ContextManagement
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
+ private constructor(
+ private val type: JsonField,
+ private val compactThreshold: JsonField,
+ private val additionalProperties: MutableMap,
+ ) {
+
+ @JsonCreator
+ private constructor(
+ @JsonProperty("type") @ExcludeMissing type: JsonField = JsonMissing.of(),
+ @JsonProperty("compact_threshold")
+ @ExcludeMissing
+ compactThreshold: JsonField = JsonMissing.of(),
+ ) : this(type, compactThreshold, mutableMapOf())
+
+ /**
+ * The context management entry type. Currently only 'compaction' is supported.
+ *
+ * @throws OpenAIInvalidDataException if the JSON field has an unexpected type or is
+ * unexpectedly missing or null (e.g. if the server responded with an unexpected value).
+ */
+ fun type(): String = type.getRequired("type")
+
+ /**
+ * Token threshold at which compaction should be triggered for this entry.
+ *
+ * @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
+ */
+ fun compactThreshold(): Optional = compactThreshold.getOptional("compact_threshold")
+
+ /**
+ * Returns the raw JSON value of [type].
+ *
+ * Unlike [type], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("type") @ExcludeMissing fun _type(): JsonField = type
+
+ /**
+ * Returns the raw JSON value of [compactThreshold].
+ *
+ * Unlike [compactThreshold], this method doesn't throw if the JSON field has an unexpected
+ * type.
+ */
+ @JsonProperty("compact_threshold")
+ @ExcludeMissing
+ fun _compactThreshold(): JsonField = compactThreshold
+
+ @JsonAnySetter
+ private fun putAdditionalProperty(key: String, value: JsonValue) {
+ additionalProperties.put(key, value)
+ }
+
+ @JsonAnyGetter
+ @ExcludeMissing
+ fun _additionalProperties(): Map =
+ Collections.unmodifiableMap(additionalProperties)
+
+ fun toBuilder() = Builder().from(this)
+
+ companion object {
+
+ /**
+ * Returns a mutable builder for constructing an instance of [ContextManagement].
+ *
+ * The following fields are required:
+ * ```java
+ * .type()
+ * ```
+ */
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [ContextManagement]. */
+ class Builder internal constructor() {
+
+ private var type: JsonField? = null
+ private var compactThreshold: JsonField = JsonMissing.of()
+ private var additionalProperties: MutableMap = mutableMapOf()
+
+ @JvmSynthetic
+ internal fun from(contextManagement: ContextManagement) = apply {
+ type = contextManagement.type
+ compactThreshold = contextManagement.compactThreshold
+ additionalProperties = contextManagement.additionalProperties.toMutableMap()
+ }
+
+ /** The context management entry type. Currently only 'compaction' is supported. */
+ fun type(type: String) = type(JsonField.of(type))
+
+ /**
+ * Sets [Builder.type] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.type] with a well-typed [String] value instead. This
+ * method is primarily for setting the field to an undocumented or not yet supported
+ * value.
+ */
+ fun type(type: JsonField) = apply { this.type = type }
+
+ /** Token threshold at which compaction should be triggered for this entry. */
+ fun compactThreshold(compactThreshold: Long?) =
+ compactThreshold(JsonField.ofNullable(compactThreshold))
+
+ /**
+ * Alias for [Builder.compactThreshold].
+ *
+ * This unboxed primitive overload exists for backwards compatibility.
+ */
+ fun compactThreshold(compactThreshold: Long) =
+ compactThreshold(compactThreshold as Long?)
+
+ /**
+ * Alias for calling [Builder.compactThreshold] with `compactThreshold.orElse(null)`.
+ */
+ fun compactThreshold(compactThreshold: Optional) =
+ compactThreshold(compactThreshold.getOrNull())
+
+ /**
+ * Sets [Builder.compactThreshold] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.compactThreshold] with a well-typed [Long] value
+ * instead. This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun compactThreshold(compactThreshold: JsonField) = apply {
+ this.compactThreshold = compactThreshold
+ }
+
+ fun additionalProperties(additionalProperties: Map) = apply {
+ this.additionalProperties.clear()
+ putAllAdditionalProperties(additionalProperties)
+ }
+
+ fun putAdditionalProperty(key: String, value: JsonValue) = apply {
+ additionalProperties.put(key, value)
+ }
+
+ fun putAllAdditionalProperties(additionalProperties: Map) = apply {
+ this.additionalProperties.putAll(additionalProperties)
+ }
+
+ fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) }
+
+ fun removeAllAdditionalProperties(keys: Set) = apply {
+ keys.forEach(::removeAdditionalProperty)
+ }
+
+ /**
+ * Returns an immutable instance of [ContextManagement].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ *
+ * The following fields are required:
+ * ```java
+ * .type()
+ * ```
+ *
+ * @throws IllegalStateException if any required field is unset.
+ */
+ fun build(): ContextManagement =
+ ContextManagement(
+ checkRequired("type", type),
+ compactThreshold,
+ additionalProperties.toMutableMap(),
+ )
+ }
+
+ private var validated: Boolean = false
+
+ fun validate(): ContextManagement = apply {
+ if (validated) {
+ return@apply
+ }
+
+ type()
+ compactThreshold()
+ validated = true
+ }
+
+ fun isValid(): Boolean =
+ try {
+ validate()
+ true
+ } catch (e: OpenAIInvalidDataException) {
+ false
+ }
+
+ /**
+ * Returns a score indicating how many valid values are contained in this object
+ * recursively.
+ *
+ * Used for best match union deserialization.
+ */
+ @JvmSynthetic
+ internal fun validity(): Int =
+ (if (type.asKnown().isPresent) 1 else 0) +
+ (if (compactThreshold.asKnown().isPresent) 1 else 0)
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+
+ return other is ContextManagement &&
+ type == other.type &&
+ compactThreshold == other.compactThreshold &&
+ additionalProperties == other.additionalProperties
+ }
+
+ private val hashCode: Int by lazy {
+ Objects.hash(type, compactThreshold, additionalProperties)
+ }
+
+ override fun hashCode(): Int = hashCode
+
+ override fun toString() =
+ "ContextManagement{type=$type, compactThreshold=$compactThreshold, additionalProperties=$additionalProperties}"
}
/**
diff --git a/openai-java-core/src/main/kotlin/com/openai/models/responses/StructuredResponseCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/responses/StructuredResponseCreateParams.kt
index 563b6ae91..0eb7a6666 100644
--- a/openai-java-core/src/main/kotlin/com/openai/models/responses/StructuredResponseCreateParams.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/models/responses/StructuredResponseCreateParams.kt
@@ -116,6 +116,28 @@ class StructuredResponseCreateParams(
paramsBuilder.background(background)
}
+ /** @see ResponseCreateParams.Builder.contextManagement */
+ fun contextManagement(contextManagement: List?) =
+ apply {
+ paramsBuilder.contextManagement(contextManagement)
+ }
+
+ /** @see ResponseCreateParams.Builder.contextManagement */
+ fun contextManagement(
+ contextManagement: Optional>
+ ) = apply { paramsBuilder.contextManagement(contextManagement) }
+
+ /** @see ResponseCreateParams.Builder.contextManagement */
+ fun contextManagement(
+ contextManagement: JsonField>
+ ) = apply { paramsBuilder.contextManagement(contextManagement) }
+
+ /** @see ResponseCreateParams.Builder.addContextManagement */
+ fun addContextManagement(contextManagement: ResponseCreateParams.ContextManagement) =
+ apply {
+ paramsBuilder.addContextManagement(contextManagement)
+ }
+
/** @see ResponseCreateParams.Builder.conversation */
fun conversation(conversation: ResponseCreateParams.Conversation?) = apply {
paramsBuilder.conversation(conversation)
diff --git a/openai-java-core/src/main/kotlin/com/openai/models/responses/Tool.kt b/openai-java-core/src/main/kotlin/com/openai/models/responses/Tool.kt
index abe85bb00..7589d318a 100644
--- a/openai-java-core/src/main/kotlin/com/openai/models/responses/Tool.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/models/responses/Tool.kt
@@ -3889,8 +3889,9 @@ private constructor(
/**
* Control how much effort the model will exert to match the style and features, especially
- * facial features, of input images. This parameter is only supported for `gpt-image-1`.
- * Unsupported for `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`.
+ * facial features, of input images. This parameter is only supported for `gpt-image-1` and
+ * `gpt-image-1.5` and later models, unsupported for `gpt-image-1-mini`. Supports `high` and
+ * `low`. Defaults to `low`.
*
* @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
@@ -4161,8 +4162,8 @@ private constructor(
/**
* Control how much effort the model will exert to match the style and features,
* especially facial features, of input images. This parameter is only supported for
- * `gpt-image-1`. Unsupported for `gpt-image-1-mini`. Supports `high` and `low`.
- * Defaults to `low`.
+ * `gpt-image-1` and `gpt-image-1.5` and later models, unsupported for
+ * `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`.
*/
fun inputFidelity(inputFidelity: InputFidelity?) =
inputFidelity(JsonField.ofNullable(inputFidelity))
@@ -4689,8 +4690,9 @@ private constructor(
/**
* Control how much effort the model will exert to match the style and features, especially
- * facial features, of input images. This parameter is only supported for `gpt-image-1`.
- * Unsupported for `gpt-image-1-mini`. Supports `high` and `low`. Defaults to `low`.
+ * facial features, of input images. This parameter is only supported for `gpt-image-1` and
+ * `gpt-image-1.5` and later models, unsupported for `gpt-image-1-mini`. Supports `high` and
+ * `low`. Defaults to `low`.
*/
class InputFidelity @JsonCreator private constructor(private val value: JsonField) :
Enum {
diff --git a/openai-java-core/src/main/kotlin/com/openai/services/blocking/WebhookServiceImpl.kt b/openai-java-core/src/main/kotlin/com/openai/services/blocking/WebhookServiceImpl.kt
index 9eba83888..5f118bcee 100644
--- a/openai-java-core/src/main/kotlin/com/openai/services/blocking/WebhookServiceImpl.kt
+++ b/openai-java-core/src/main/kotlin/com/openai/services/blocking/WebhookServiceImpl.kt
@@ -79,7 +79,7 @@ class WebhookServiceImpl internal constructor(private val clientOptions: ClientO
try {
timestampHeader.toLong()
} catch (e: NumberFormatException) {
- throw IllegalArgumentException("Invalid webhook timestamp format", e)
+ throw InvalidWebhookSignatureException("Invalid webhook timestamp format", e)
}
val now = Instant.now(clientOptions.clock)
@@ -95,7 +95,11 @@ class WebhookServiceImpl internal constructor(private val clientOptions: ClientO
}
// The signature header can have multiple values, separated by spaces.
- val signatures = signatureHeader.split(" ").map { it.removePrefix("v1,") }
+ val signatures =
+ signatureHeader
+ .split("\\s+".toRegex())
+ .filter { it.isNotBlank() }
+ .map { it.removePrefix("v1,") }
// Decode the secret if it starts with whsec_
val decodedSecret =
@@ -119,7 +123,10 @@ class WebhookServiceImpl internal constructor(private val clientOptions: ClientO
// Accept if any signature matches using timing-safe comparison
val signatureMatches =
signatures.any { signature ->
- MessageDigest.isEqual(expectedSignature.toByteArray(), signature.toByteArray())
+ MessageDigest.isEqual(
+ expectedSignature.toByteArray(StandardCharsets.UTF_8),
+ signature.toByteArray(StandardCharsets.UTF_8),
+ )
}
if (!signatureMatches) {
diff --git a/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponseCreateParamsTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponseCreateParamsTest.kt
index da33364da..e1df6d796 100644
--- a/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponseCreateParamsTest.kt
+++ b/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponseCreateParamsTest.kt
@@ -18,6 +18,12 @@ internal class ResponseCreateParamsTest {
fun create() {
ResponseCreateParams.builder()
.background(true)
+ .addContextManagement(
+ ResponseCreateParams.ContextManagement.builder()
+ .type("type")
+ .compactThreshold(1000L)
+ .build()
+ )
.conversation("string")
.addInclude(ResponseIncludable.FILE_SEARCH_CALL_RESULTS)
.input("string")
@@ -90,6 +96,12 @@ internal class ResponseCreateParamsTest {
val params =
ResponseCreateParams.builder()
.background(true)
+ .addContextManagement(
+ ResponseCreateParams.ContextManagement.builder()
+ .type("type")
+ .compactThreshold(1000L)
+ .build()
+ )
.conversation("string")
.addInclude(ResponseIncludable.FILE_SEARCH_CALL_RESULTS)
.input("string")
@@ -159,6 +171,13 @@ internal class ResponseCreateParamsTest {
val body = params._body()
assertThat(body.background()).contains(true)
+ assertThat(body.contextManagement().getOrNull())
+ .containsExactly(
+ ResponseCreateParams.ContextManagement.builder()
+ .type("type")
+ .compactThreshold(1000L)
+ .build()
+ )
assertThat(body.conversation()).contains(ResponseCreateParams.Conversation.ofId("string"))
assertThat(body.include().getOrNull())
.containsExactly(ResponseIncludable.FILE_SEARCH_CALL_RESULTS)
diff --git a/openai-java-core/src/test/kotlin/com/openai/models/responses/StructuredResponseCreateParamsTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/responses/StructuredResponseCreateParamsTest.kt
index 107136296..3c909af72 100644
--- a/openai-java-core/src/test/kotlin/com/openai/models/responses/StructuredResponseCreateParamsTest.kt
+++ b/openai-java-core/src/test/kotlin/com/openai/models/responses/StructuredResponseCreateParamsTest.kt
@@ -65,6 +65,8 @@ internal class StructuredResponseCreateParamsTest {
private val RESPONSE_CONVERSATION_PARAM =
ResponseConversationParam.builder().id(STRING).build()
private val CONVERSATION = ResponseCreateParams.Conversation.ofId(STRING)
+ private val CONTEXT_MANAGEMENT =
+ ResponseCreateParams.ContextManagement.builder().type(STRING).build()
private val PROMPT_CACHE_RETENTION = ResponseCreateParams.PromptCacheRetention.IN_MEMORY
private val PROMPT_CACHE_RETENTION_OPTIONAL = Optional.of(PROMPT_CACHE_RETENTION)
private val PROMPT_CACHE_RETENTION_JSON_FIELD = JsonField.of(PROMPT_CACHE_RETENTION)
@@ -136,6 +138,10 @@ internal class StructuredResponseCreateParamsTest {
DelegationWriteTestCase("background", BOOLEAN),
DelegationWriteTestCase("background", OPTIONAL),
DelegationWriteTestCase("background", JSON_FIELD),
+ DelegationWriteTestCase("contextManagement", LIST),
+ DelegationWriteTestCase("contextManagement", OPTIONAL),
+ DelegationWriteTestCase("contextManagement", JSON_FIELD),
+ DelegationWriteTestCase("addContextManagement", CONTEXT_MANAGEMENT),
DelegationWriteTestCase("conversation", CONVERSATION),
DelegationWriteTestCase("conversation", OPTIONAL),
DelegationWriteTestCase("conversation", JSON_FIELD),
diff --git a/openai-java-core/src/test/kotlin/com/openai/services/async/ResponseServiceAsyncTest.kt b/openai-java-core/src/test/kotlin/com/openai/services/async/ResponseServiceAsyncTest.kt
index 5948f688e..d24c9ddf4 100644
--- a/openai-java-core/src/test/kotlin/com/openai/services/async/ResponseServiceAsyncTest.kt
+++ b/openai-java-core/src/test/kotlin/com/openai/services/async/ResponseServiceAsyncTest.kt
@@ -36,6 +36,12 @@ internal class ResponseServiceAsyncTest {
responseServiceAsync.create(
ResponseCreateParams.builder()
.background(true)
+ .addContextManagement(
+ ResponseCreateParams.ContextManagement.builder()
+ .type("type")
+ .compactThreshold(1000L)
+ .build()
+ )
.conversation("string")
.addInclude(ResponseIncludable.FILE_SEARCH_CALL_RESULTS)
.input("string")
@@ -122,6 +128,12 @@ internal class ResponseServiceAsyncTest {
responseServiceAsync.createStreaming(
ResponseCreateParams.builder()
.background(true)
+ .addContextManagement(
+ ResponseCreateParams.ContextManagement.builder()
+ .type("type")
+ .compactThreshold(1000L)
+ .build()
+ )
.conversation("string")
.addInclude(ResponseIncludable.FILE_SEARCH_CALL_RESULTS)
.input("string")
diff --git a/openai-java-core/src/test/kotlin/com/openai/services/async/WebhookServiceAsyncTest.kt b/openai-java-core/src/test/kotlin/com/openai/services/async/WebhookServiceAsyncTest.kt
new file mode 100644
index 000000000..f7325c5f9
--- /dev/null
+++ b/openai-java-core/src/test/kotlin/com/openai/services/async/WebhookServiceAsyncTest.kt
@@ -0,0 +1,82 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.openai.services.async
+
+import com.openai.TestServerExtension
+import com.openai.client.okhttp.OpenAIOkHttpClientAsync
+import com.openai.core.http.Headers
+import com.openai.models.webhooks.WebhookVerificationParams
+import java.time.Clock
+import java.time.Instant
+import java.time.ZoneOffset
+import kotlin.test.assertTrue
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.junit.jupiter.api.extension.ExtendWith
+
+@ExtendWith(TestServerExtension::class)
+internal class WebhookServiceAsyncTest {
+
+ private val testPayload =
+ """{"id": "evt_685c059ae3a481909bdc86819b066fb6", "object": "event", "created_at": 1750861210, "type": "response.completed", "data": {"id": "resp_123"}}"""
+ private val testSecret = "whsec_RdvaYFYUXuIFuEbvZHwMfYFhUf7aMYjYcmM24+Aj40c="
+ private val fixedTimestamp = "1750861210"
+ private val webhookId = "wh_685c059ae39c8190af8c71ed1022a24d"
+ private val validSignatureForSecret = "v1,gUAg4R2hWouRZqRQG4uJypNS8YK885G838+EHb4nKBY="
+ private val fixedClock =
+ Clock.fixed(Instant.ofEpochSecond(fixedTimestamp.toLong()), ZoneOffset.UTC)
+
+ @Test
+ fun unwrapWithValidSignatureAndSecret() {
+ val client =
+ OpenAIOkHttpClientAsync.builder()
+ .baseUrl(TestServerExtension.BASE_URL)
+ .apiKey("My API Key")
+ .clock(fixedClock)
+ .build()
+ val webhookServiceAsync = client.webhooks()
+
+ val headers =
+ Headers.builder()
+ .put("webhook-signature", validSignatureForSecret)
+ .put("webhook-timestamp", fixedTimestamp)
+ .put("webhook-id", webhookId)
+ .build()
+
+ val event =
+ webhookServiceAsync
+ .unwrap(
+ WebhookVerificationParams.builder()
+ .payload(testPayload)
+ .headers(headers)
+ .secret(testSecret)
+ .build()
+ )
+ .validate()
+
+ assertTrue(event.isResponseCompleted())
+ }
+
+ @Test
+ fun unwrapWithoutSecretShouldThrow() {
+ val client =
+ OpenAIOkHttpClientAsync.builder()
+ .baseUrl(TestServerExtension.BASE_URL)
+ .apiKey("My API Key")
+ .build()
+ val webhookServiceAsync = client.webhooks()
+
+ val headers =
+ Headers.builder()
+ .put("webhook-signature", validSignatureForSecret)
+ .put("webhook-timestamp", fixedTimestamp)
+ .put("webhook-id", webhookId)
+ .build()
+
+ assertThrows {
+ webhookServiceAsync.unwrap(
+ WebhookVerificationParams.builder().payload(testPayload).headers(headers).build()
+ )
+ }
+ }
+}
diff --git a/openai-java-core/src/test/kotlin/com/openai/services/blocking/ResponseServiceTest.kt b/openai-java-core/src/test/kotlin/com/openai/services/blocking/ResponseServiceTest.kt
index e0acab40c..e6adc4273 100644
--- a/openai-java-core/src/test/kotlin/com/openai/services/blocking/ResponseServiceTest.kt
+++ b/openai-java-core/src/test/kotlin/com/openai/services/blocking/ResponseServiceTest.kt
@@ -36,6 +36,12 @@ internal class ResponseServiceTest {
responseService.create(
ResponseCreateParams.builder()
.background(true)
+ .addContextManagement(
+ ResponseCreateParams.ContextManagement.builder()
+ .type("type")
+ .compactThreshold(1000L)
+ .build()
+ )
.conversation("string")
.addInclude(ResponseIncludable.FILE_SEARCH_CALL_RESULTS)
.input("string")
@@ -121,6 +127,12 @@ internal class ResponseServiceTest {
responseService.createStreaming(
ResponseCreateParams.builder()
.background(true)
+ .addContextManagement(
+ ResponseCreateParams.ContextManagement.builder()
+ .type("type")
+ .compactThreshold(1000L)
+ .build()
+ )
.conversation("string")
.addInclude(ResponseIncludable.FILE_SEARCH_CALL_RESULTS)
.input("string")
diff --git a/openai-java-core/src/test/kotlin/com/openai/services/blocking/WebhookServiceTest.kt b/openai-java-core/src/test/kotlin/com/openai/services/blocking/WebhookServiceTest.kt
index 3b20eba42..72f417683 100644
--- a/openai-java-core/src/test/kotlin/com/openai/services/blocking/WebhookServiceTest.kt
+++ b/openai-java-core/src/test/kotlin/com/openai/services/blocking/WebhookServiceTest.kt
@@ -44,6 +44,32 @@ internal class WebhookServiceTest {
.build()
}
+ @Test
+ fun unwrapWithValidSignatureAndSecret() {
+ val client = createClientWithFixedClock()
+ val webhookService = client.webhooks()
+
+ val headers =
+ Headers.builder()
+ .put("webhook-signature", validSignatureForSecret)
+ .put("webhook-timestamp", fixedTimestamp)
+ .put("webhook-id", webhookId)
+ .build()
+
+ val event =
+ webhookService
+ .unwrap(
+ WebhookVerificationParams.builder()
+ .payload(testPayload)
+ .headers(headers)
+ .secret(testSecret)
+ .build()
+ )
+ .validate()
+
+ assertTrue(event.isResponseCompleted())
+ }
+
@Test
fun unwrapWithoutSecretShouldThrow() {
val client =
@@ -123,6 +149,29 @@ internal class WebhookServiceTest {
}
}
+ @Test
+ fun verifySignatureWithInvalidTimestampFormat() {
+ val client = createClientWithFixedClock()
+ val webhookService = client.webhooks()
+
+ val headers =
+ Headers.builder()
+ .put("webhook-signature", "v1,invalid_signature")
+ .put("webhook-timestamp", "not_a_number")
+ .put("webhook-id", webhookId)
+ .build()
+
+ assertThrows {
+ webhookService.verifySignature(
+ WebhookVerificationParams.builder()
+ .payload(testPayload)
+ .headers(headers)
+ .secret(testSecret)
+ .build()
+ )
+ }
+ }
+
@Test
fun verifySignatureWithValidSignature() {
val client = createClientWithFixedClock()
diff --git a/openai-java-proguard-test/build.gradle.kts b/openai-java-proguard-test/build.gradle.kts
index 43c01fbda..b37b7c459 100644
--- a/openai-java-proguard-test/build.gradle.kts
+++ b/openai-java-proguard-test/build.gradle.kts
@@ -18,7 +18,7 @@ dependencies {
testImplementation(project(":openai-java"))
testImplementation(kotlin("test"))
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3")
- testImplementation("org.assertj:assertj-core:3.25.3")
+ testImplementation("org.assertj:assertj-core:3.27.7")
testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.0")
}
diff --git a/openai-java-spring-boot-starter/build.gradle.kts b/openai-java-spring-boot-starter/build.gradle.kts
index 20c06d076..fcf71dfe6 100644
--- a/openai-java-spring-boot-starter/build.gradle.kts
+++ b/openai-java-spring-boot-starter/build.gradle.kts
@@ -13,5 +13,5 @@ dependencies {
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor:2.7.18")
testImplementation("org.springframework.boot:spring-boot-starter-test:2.7.18")
- testImplementation("org.assertj:assertj-core:3.25.3")
+ testImplementation("org.assertj:assertj-core:3.27.7")
}