mirror of
https://github.com/anomalyco/opencode.git
synced 2026-10-29 03:53:03 +00:00
fix(ai): send OpenAI Chat options that were silently dropped (#58857)
This commit is contained in:
parent
9cbdffcb06
commit
d7e56bc25e
2 changed files with 66 additions and 2 deletions
|
|
@ -25,6 +25,7 @@ import {
|
|||
import { classifyProviderFailure } from "../provider-error.js"
|
||||
import { isRecord, JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js"
|
||||
import { cacheControl } from "./utils/cache.js"
|
||||
import { resolveParallelToolCalls } from "./open-responses.js"
|
||||
import { OpenResponsesOptions } from "./utils/open-responses-options.js"
|
||||
import { Lifecycle } from "./utils/lifecycle.js"
|
||||
import { ToolStream } from "./utils/tool-stream.js"
|
||||
|
|
@ -186,6 +187,11 @@ export const bodyFields = {
|
|||
store: Schema.optional(Schema.Boolean),
|
||||
prompt_cache_key: Schema.optional(Schema.String),
|
||||
reasoning_effort: Schema.optional(OpenResponsesOptions.ReasoningEffort),
|
||||
verbosity: Schema.optional(OpenResponsesOptions.TextVerbositySchema),
|
||||
service_tier: Schema.optional(OpenResponsesOptions.ServiceTierSchema),
|
||||
parallel_tool_calls: Schema.optional(Schema.Boolean),
|
||||
metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
||||
safety_identifier: Schema.optional(Schema.String),
|
||||
tool_stream: Schema.optional(Schema.Boolean),
|
||||
max_completion_tokens: Schema.optional(Schema.Number),
|
||||
max_tokens: Schema.optional(Schema.Number),
|
||||
|
|
@ -761,8 +767,10 @@ const detectZaiToolStream = (provider: string, baseURL: string | undefined, mode
|
|||
return true
|
||||
}
|
||||
|
||||
const lowerOptions = (request: LLMRequest, supportsStore: boolean) => {
|
||||
const lowerOptions = (request: LLMRequest, supportsStore: boolean, hasActiveTools: boolean) => {
|
||||
const options = OpenResponsesOptions.resolve(request)
|
||||
// OpenAI rejects `parallel_tool_calls` unless tools are present.
|
||||
const parallelToolCalls = hasActiveTools ? resolveParallelToolCalls(request, options) : undefined
|
||||
// Default off: strict providers 400 on unknown body fields, so only send
|
||||
// the key where compatibility explicitly allows it. Header-based affinity
|
||||
// (x-session-affinity, x-grok-conv-id, ...) is unaffected.
|
||||
|
|
@ -772,6 +780,11 @@ const lowerOptions = (request: LLMRequest, supportsStore: boolean) => {
|
|||
...(supportsStore ? { store: options.store ?? false } : {}),
|
||||
...(cacheKey ? { prompt_cache_key: cacheKey } : {}),
|
||||
...(options.reasoningEffort ? { reasoning_effort: options.reasoningEffort } : {}),
|
||||
...(options.textVerbosity ? { verbosity: options.textVerbosity } : {}),
|
||||
...(options.serviceTier ? { service_tier: options.serviceTier } : {}),
|
||||
...(parallelToolCalls !== undefined ? { parallel_tool_calls: parallelToolCalls } : {}),
|
||||
...(options.metadata ? { metadata: options.metadata } : {}),
|
||||
...(options.safetyIdentifier ? { safety_identifier: options.safetyIdentifier } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -822,7 +835,7 @@ export const fromRequest = Effect.fn("OpenAIChat.fromRequest")(function* (
|
|||
presence_penalty: generation?.presencePenalty,
|
||||
seed: generation?.seed,
|
||||
stop: generation?.stop,
|
||||
...lowerOptions(request, supportsStore),
|
||||
...lowerOptions(request, supportsStore, hasActiveTools),
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -312,6 +312,57 @@ describe("OpenAI Chat route", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("maps shared OpenAI options to Chat fields", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* compileRequest(
|
||||
LLM.request({
|
||||
model: OpenAI.configure({ baseURL: "https://api.openai.test/v1/", apiKey: "test" }).chat("gpt-5-nano"),
|
||||
prompt: "Hello",
|
||||
tools: [ToolDefinition.make({ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } })],
|
||||
providerOptions: {
|
||||
store: true,
|
||||
serviceTier: "flex",
|
||||
textVerbosity: "low",
|
||||
parallelToolCalls: false,
|
||||
metadata: { source: "test" },
|
||||
safetyIdentifier: "user-hash",
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
expect(prepared.body).toMatchObject({
|
||||
store: true,
|
||||
service_tier: "flex",
|
||||
verbosity: "low",
|
||||
parallel_tool_calls: false,
|
||||
metadata: { source: "test" },
|
||||
safety_identifier: "user-hash",
|
||||
})
|
||||
expect(prepared.body).not.toHaveProperty("text")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("maps disableParallelToolUse to parallel_tool_calls only when tools are present", () =>
|
||||
Effect.gen(function* () {
|
||||
const tool = ToolDefinition.make({ name: "lookup", description: "Lookup data", inputSchema: { type: "object" } })
|
||||
const withTools = yield* compileRequest(
|
||||
LLMRequest.update(request, { tools: [tool], toolChoice: { type: "auto", disableParallelToolUse: true } }),
|
||||
)
|
||||
const withoutTools = yield* compileRequest(
|
||||
LLMRequest.update(request, { toolChoice: { type: "auto", disableParallelToolUse: true } }),
|
||||
)
|
||||
const unset = yield* compileRequest(LLMRequest.update(request, { tools: [tool] }))
|
||||
|
||||
expect(withTools.body.parallel_tool_calls).toBe(false)
|
||||
expect(withoutTools.body).not.toHaveProperty("parallel_tool_calls")
|
||||
expect(unset.body).not.toHaveProperty("parallel_tool_calls")
|
||||
expect(unset.body).not.toHaveProperty("service_tier")
|
||||
expect(unset.body).not.toHaveProperty("verbosity")
|
||||
expect(unset.body).not.toHaveProperty("metadata")
|
||||
expect(unset.body).not.toHaveProperty("safety_identifier")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("maps the request prompt cache key when the compatibility flag is set", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* compileRequest(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue