fix(ai): classify malformed Responses tool calls (#40549)

Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
This commit is contained in:
opencode-agent[bot]
2026-08-04 20:40:36 -05:00
committed by GitHub
parent ab20a7b4a3
commit 62e5d73d45
2 changed files with 11 additions and 9 deletions
+9 -7
View File
@@ -264,6 +264,7 @@ export interface ParserState {
readonly providerMetadataKey: string
readonly tools: ToolStream.State<string>
readonly hasFunctionCall: boolean
readonly hasFunctionCallError: boolean
readonly lifecycle: Lifecycle.State
readonly messageItems: ReadonlySet<string>
readonly messagePhase: (value: unknown) => MessagePhase | null | undefined
@@ -608,15 +609,16 @@ const mapUsage = (usage: OpenResponsesUsage | null | undefined, providerMetadata
})
}
const mapFinishReason = (event: Event, hasFunctionCall: boolean): FinishReason => {
const mapFinishReason = (event: Event, hasFunctionCall: boolean, hasFunctionCallError: boolean): FinishReason => {
const reason = event.response?.incomplete_details?.reason
if (reason === "max_output_tokens") return "length"
if (reason === "content_filter") return "content-filter"
if (hasFunctionCallError) return "error"
if (reason === undefined || reason === null) {
if (hasFunctionCall) return "tool-calls"
if (event.type === "response.incomplete") return "unknown"
return "stop"
}
if (reason === "max_output_tokens") return "length"
if (reason === "content_filter") return "content-filter"
return hasFunctionCall ? "tool-calls" : "unknown"
}
@@ -896,9 +898,8 @@ const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (
{
...state,
lifecycle,
hasFunctionCall:
resultEvents.some((event) => LLMEvent.is.toolCall(event) || LLMEvent.is.toolInputError(event)) ||
state.hasFunctionCall,
hasFunctionCall: resultEvents.some(LLMEvent.is.toolCall) || state.hasFunctionCall,
hasFunctionCallError: resultEvents.some(LLMEvent.is.toolInputError) || state.hasFunctionCallError,
tools: result.tools,
},
events,
@@ -938,7 +939,7 @@ const onResponseFinish = (state: ParserState, event: Event): StepResult => {
const events: LLMEvent[] = []
const lifecycle = Lifecycle.finish(state.lifecycle, events, {
reason: {
normalized: mapFinishReason(event, state.hasFunctionCall),
normalized: mapFinishReason(event, state.hasFunctionCall, state.hasFunctionCallError),
raw: event.response?.incomplete_details?.reason,
},
usage: mapUsage(event.response?.usage, state.providerMetadataKey),
@@ -1031,6 +1032,7 @@ export const initial = (request: LLMRequest, extension: Extension = BASE): Parse
name: extension.name,
providerMetadataKey: request.model.route.providerMetadataKey ?? "openresponses",
hasFunctionCall: false,
hasFunctionCallError: false,
tools: ToolStream.empty<string>(),
lifecycle: Lifecycle.initial(),
messageItems: new Set<string>(),
@@ -1599,7 +1599,7 @@ describe("OpenAI Responses route", () => {
name: "lookup",
raw: '{"query":"partial',
})
expect(response.finishReason.normalized).toBe("tool-calls")
expect(response.finishReason.normalized).toBe("error")
expect(response.events.some(LLMEvent.is.toolCall)).toBeFalse()
}),
)
@@ -1626,7 +1626,7 @@ describe("OpenAI Responses route", () => {
name: "lookup",
raw: '{"query":"partial',
})
expect(response.finishReason.normalized).toBe("tool-calls")
expect(response.finishReason.normalized).toBe("error")
}),
)