feat(ai): add OpenAI Responses reasoning mode and context options (#58872)

This commit is contained in:
Aiden Cline
2026-10-24 23:22:31 -05:00
committed by GitHub
Unverified
parent 16cd50db22
commit ed623cc648
4 changed files with 64 additions and 3 deletions
+32 -2
View File
@@ -10,7 +10,7 @@ import { LLMRequest, type ToolDefinition, type ToolEntry } from "../schema/index
import { resolveEffortUpdates } from "../effort-updates.js"
import { OpenResponses } from "./open-responses.js"
import { OpenResponsesOptions } from "./utils/open-responses-options.js"
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js"
import { JsonObject, lenient, optionalArray, optionalNull, ProviderShared } from "./shared.js"
import { ResponsesHostedTools } from "./utils/responses-hosted-tools.js"
import { OpenResponsesChannel } from "./open-responses-channel.js"
import { ResponsesCompaction } from "./utils/responses-compaction.js"
@@ -31,6 +31,16 @@ export const ContextManagement = Schema.Array(
)
export type ContextManagement = typeof ContextManagement.Type
export const ReasoningMode = ProviderShared.knownString<"standard" | "pro">()
export type ReasoningMode = typeof ReasoningMode.Type
export const ReasoningContext = ProviderShared.knownString<"auto" | "current_turn" | "all_turns">()
export type ReasoningContext = typeof ReasoningContext.Type
const ReasoningOptions = Schema.Struct({
reasoningMode: lenient(ReasoningMode),
reasoningContext: lenient(ReasoningContext),
})
const OpenAIResponsesImageGenerationTool = Schema.Struct({
type: Schema.tag("image_generation"),
action: Schema.optional(Schema.Literals(["auto", "generate", "edit"])),
@@ -108,6 +118,14 @@ const OpenAIResponsesCoreFields = {
...OpenResponses.coreFields,
tools: optionalArray(OpenAIResponsesTools),
tool_choice: Schema.optional(OpenAIResponsesToolChoice),
reasoning: Schema.optional(
Schema.Struct({
effort: Schema.optional(OpenResponsesOptions.ReasoningEffort),
summary: Schema.optional(Schema.Literals(["auto", "concise", "detailed"])),
mode: Schema.optional(ReasoningMode),
context: Schema.optional(ReasoningContext),
}),
),
context_management: Schema.optional(
Schema.Array(
Schema.Struct({
@@ -141,6 +159,7 @@ const adapter = {
// or alongside automatic `context_management` compaction.
const supportsEffortUpdates = (request: LLMRequest) => {
if (request.providerOptions?.contextManagement !== undefined) return false
if (request.providerOptions?.reasoningMode === "pro") return false
if (Schema.is(Schema.Struct({ mode: Schema.Literal("pro") }))(request.http?.body?.reasoning)) return false
const override = request.model.compatibility?.supportsEffortUpdates
if (override !== undefined) return override
@@ -201,10 +220,21 @@ const fromRequest = Effect.fn("OpenAIResponses.fromRequest")(function* (request:
Schema.decodeUnknownEffect(Schema.UndefinedOr(ContextManagement)),
)(request.providerOptions?.contextManagement)
const options = OpenResponsesOptions.resolve(request)
const reasoning = Schema.decodeUnknownSync(ReasoningOptions)(request.providerOptions ?? {})
const updates = resolveEffortUpdates(request, options.reasoningEffort)
const generation = OpenResponses.lowerGeneration(request, { ...options, reasoningEffort: updates.effort })
return {
...(yield* OpenResponses.lowerConversation(updates.request, adapter)),
...OpenResponses.lowerGeneration(request, { ...options, reasoningEffort: updates.effort }),
...generation,
...(generation.reasoning || reasoning.reasoningMode || reasoning.reasoningContext
? {
reasoning: {
...generation.reasoning,
mode: reasoning.reasoningMode,
context: reasoning.reasoningContext,
},
}
: {}),
context_management: management?.map((edit) => ({ type: edit.type, compact_threshold: edit.compactThreshold })),
tools: request.tools.length === 0 ? undefined : yield* lowerTools(request),
tool_choice:
+3 -1
View File
@@ -1,6 +1,6 @@
import { mergeProviderOptions, type ProviderOptions } from "../schema/index.js"
import type { Options, ResponseIncludable, ServiceTier } from "../protocols/utils/open-responses-options.js"
import type { ContextManagement } from "../protocols/openai-responses.js"
import type { ContextManagement, ReasoningContext, ReasoningMode } from "../protocols/openai-responses.js"
// Mirrors OpenAI's `ResponseIncludable` union from the official SDK. Keep this
// in lockstep with `openai-node/src/resources/responses/responses.ts`.
@@ -11,6 +11,8 @@ export type OpenAIServiceTier = ServiceTier | "scale" | "fast" | "ultrafast"
export type OpenAIOptionsInput = Omit<Options, "serviceTier"> & {
/** Advanced in-band compaction. The caller owns checkpoint persistence and recovery. */
readonly contextManagement?: ContextManagement
readonly reasoningMode?: ReasoningMode
readonly reasoningContext?: ReasoningContext
readonly serviceTier?: OpenAIServiceTier
readonly [key: string]: unknown
}
+15
View File
@@ -449,6 +449,21 @@ describe("OpenAI Responses effort updates", () => {
}),
)
it.effect("strips markers when provider options select pro reasoning mode", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
LLM.request({
model: OpenAI.configure({ apiKey: "fixture" }).responses("gpt-6-sol"),
messages: conversation,
providerOptions: { reasoningEffort: "low", reasoningMode: "pro" },
}),
)
expect(updates(prepared.body)).toEqual([])
expect(prepared.body.reasoning).toEqual({ effort: "low", mode: "pro" })
}),
)
for (const [id, supported] of [
["gpt-6-astra", true],
["openai/gpt-6-astra", true],
@@ -317,6 +317,20 @@ describe("OpenAI Responses route", () => {
}),
)
it.effect("lowers reasoning mode and context into the reasoning object", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
LLMRequest.update(request, {
providerOptions: { reasoningEffort: "high", reasoningMode: "pro", reasoningContext: "current_turn" },
}),
)
expect(prepared.body.reasoning).toEqual({ effort: "high", mode: "pro", context: "current_turn" })
expect(prepared.body).not.toHaveProperty("reasoningMode")
expect(prepared.body).not.toHaveProperty("reasoningContext")
}),
)
it.effect("passes through custom OpenAI text verbosity strings", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(