ix(ai): reject incomplete pending tool calls

Only finalize pending Responses tool calls after response.completed.
Reject incomplete responses with pending calls so partial input cannot be dispatched.
This commit is contained in:
Simon Klee 2026-07-15 23:31:58 +02:00
commit b77dbb511e
No known key found for this signature in database
GPG key ID: B91696044D47BEA3
2 changed files with 36 additions and 2 deletions

View file

@ -1,7 +1,7 @@
import { describe, expect } from "bun:test"
import { ConfigProvider, Effect, Layer, Stream } from "effect"
import { Headers, HttpClientRequest } from "effect/unstable/http"
import { LLM, LLMError, Message, Model, ToolCallPart, Usage } from "../../src"
import { LLM, LLMError, LLMEvent, Message, Model, ToolCallPart, Usage } from "../../src"
import { Auth, LLMClient, RequestExecutor, WebSocketExecutor } from "../../src/route"
import * as Azure from "../../src/providers/azure"
import * as OpenAI from "../../src/providers/openai"
@ -1286,6 +1286,35 @@ describe("OpenAI Responses route", () => {
}),
)
it.effect("rejects an incomplete pending function call", () =>
Effect.gen(function* () {
const body = sseEvents(
{
type: "response.output_item.added",
item: { type: "function_call", id: "item_1", call_id: "call_1", name: "patch", arguments: "" },
},
{
type: "response.incomplete",
response: { incomplete_details: { reason: "max_output_tokens" } },
},
)
const events: LLMEvent[] = []
const error = yield* LLMClient.stream(
LLM.updateRequest(request, {
tools: [{ name: "patch", description: "Apply a patch", inputSchema: { type: "object" } }],
}),
).pipe(
Stream.runForEach((event) => Effect.sync(() => events.push(event))),
Effect.flip,
Effect.provide(fixedResponse(body)),
)
expect(events.filter(LLMEvent.is.toolCall)).toEqual([])
expect(error.reason).toMatchObject({ _tag: "InvalidProviderOutput" })
expect(error.message).toContain("OpenAI Responses response incomplete with pending tool calls")
}),
)
it.effect("decodes web_search_call as provider-executed tool-call + tool-result", () =>
Effect.gen(function* () {
const item = {