test: trim overlapping tool input coverage

This commit is contained in:
Aiden Cline committed 2026-10-07 00:39:58 -05:00
1 parent 600c40e1b8
commit 3570dab95f
3 files changed
+21 -168

No files matched your search

@@ -1929,44 +1929,6 @@ describe("Anthropic Messages route", () => {
}),
)
for (const stopReason of ["tool_use", "max_tokens", "refusal"]) {
it.effect(`rejects incomplete local tool input before ${stopReason}`, () =>
Effect.gen(function* () {
const raw = '{"query":"partial'
const response = yield* LLMClient.generate(request).pipe(
Effect.provide(
fixedResponse(
sseEvents(
{ type: "message_start", message: { usage: { input_tokens: 5 } } },
{
type: "content_block_start",
index: 0,
content_block: { type: "tool_use", id: "call_1", name: "lookup" },
},
{ type: "content_block_delta", index: 0, delta: { type: "input_json_delta", partial_json: raw } },
{ type: "content_block_stop", index: 0 },
{ type: "message_delta", delta: { stop_reason: stopReason }, usage: { output_tokens: 1 } },
{ type: "message_stop" },
),
),
),
)
expect(response.toolCalls).toEqual([])
expect(response.events.find((event) => event.type === "tool-input-delta")).toMatchObject({
text: raw,
input: { query: "partial" },
})
expect(response.events.find((event) => event.type === "tool-input-error")).toEqual({
type: "tool-input-error",
id: "call_1",
name: "lookup",
raw,
})
}),
)
}
it.effect("keeps malformed server tool input terminal", () =>
Effect.gen(function* () {
const body = sseEvents(
-31
View File
@@ -145,37 +145,6 @@ describe("ToolStream", () => {
}),
)
it.effect("rejects malformed authoritative input instead of using valid deltas", () =>
Effect.gen(function* () {
const tools = ToolStream.start(ToolStream.empty<string>(), "item_1", {
id: "call_1",
name: "lookup",
input: '{"query":"weather"}',
})
const finished = yield* ToolStream.finishWithInput(ADAPTER, tools, "item_1", '{"query":"partial')
expect(finished).toEqual({
tools: {},
events: [
{ type: "tool-input-end", id: "call_1", name: "lookup" },
{ type: "tool-input-error", id: "call_1", name: "lookup", raw: '{"query":"partial' },
],
})
}),
)
it.effect("preserves empty input for zero-argument tools", () =>
Effect.gen(function* () {
const tools = ToolStream.start(ToolStream.empty<number>(), 0, { id: "call_1", name: "lookup" })
const finished = yield* ToolStream.finish(ADAPTER, tools, 0)
expect(finished.events).toEqual([
{ type: "tool-input-end", id: "call_1", name: "lookup" },
{ type: "tool-call", id: "call_1", name: "lookup", input: {} },
])
}),
)
it.effect("rejects malformed string escapes in final local input", () =>
Effect.gen(function* () {
const tools = ToolStream.start(ToolStream.empty<string>(), "item_1", {
+21 -99
View File
@@ -3721,45 +3721,6 @@ describe("SessionRunnerLLM", () => {
expect(systemTexts(s.requests[1])).toContain("Replacement context")
})
scenario("executes a completed tool call before the provider finishes the response", function* (s) {
yield* s.admit("Echo this")
const tools = yield* s.blockTools()
const finish = yield* Deferred.make<void>()
yield* s.llm.push(
Stream.fromIterable([
LLMEvent.stepStart({ index: 0 }),
LLMEvent.toolInputStart({ id: "call-early", name: "echo" }),
LLMEvent.toolInputDelta({ id: "call-early", name: "echo", text: '{"text":"hello"}' }),
LLMEvent.toolInputEnd({ id: "call-early", name: "echo" }),
LLMEvent.toolCall({ id: "call-early", name: "echo", input: { text: "hello" } }),
]).pipe(
Stream.concat(
Stream.fromEffect(Deferred.await(finish)).pipe(
Stream.flatMap(() =>
Stream.fromIterable([
LLMEvent.stepFinish({ index: 0, reason: { normalized: "tool-calls" } }),
LLMEvent.finish({ reason: { normalized: "tool-calls" } }),
]),
),
),
),
),
TestLLM.stop(),
)
const run = yield* s.resume.pipe(Effect.forkChild)
yield* tools.started
expect(requireAssistant(yield* s.context)).toMatchObject({
content: [{ type: "tool", id: "call-early", state: { status: "running", input: { text: "hello" } } }],
})
expect(yield* recordedEventTypes(sessionID)).not.toContain("session.step.streamed.1")
yield* Deferred.succeed(finish, undefined)
yield* tools.release
yield* Fiber.join(run)
expect(s.executions).toEqual(["hello"])
})
scenario("consumes the full provider stream before recording its boundary and settling local tools", function* (s) {
yield* s.admit("Echo this")
const request = yield* s.llm.gate
@@ -6071,68 +6032,29 @@ describe("SessionRunnerLLM", () => {
])
})
for (const finish of ["stop", "length"] as const) {
for (const ended of [false, true]) {
scenario(`settles unfinished tool input after ${finish} (input ended: ${ended})`, function* (s) {
const raw = '{"text":"unfinished-marker'
yield* s.llm.push(
TestLLM.complete(
{ reason: { normalized: finish } },
LLMEvent.toolCall({ id: "call-valid", name: "echo", input: { text: "valid" } }),
LLMEvent.toolInputStart({ id: "call-incomplete", name: "echo" }),
LLMEvent.toolInputDelta({ id: "call-incomplete", name: "echo", text: raw }),
...(ended ? [LLMEvent.toolInputEnd({ id: "call-incomplete", name: "echo" })] : []),
),
TestLLM.stop(),
)
scenario("settles unfinished tool input after an output limit", function* (s) {
yield* s.llm.push(
TestLLM.complete(
{ reason: { normalized: "length" } },
LLMEvent.toolInputStart({ id: "call-incomplete", name: "echo" }),
LLMEvent.toolInputDelta({ id: "call-incomplete", name: "echo", text: '{"text":"partial' }),
),
TestLLM.stop(),
)
yield* s.runPrompt("Recover unfinished tool input")
yield* s.runPrompt("Recover unfinished tool input")
expect(s.requests).toHaveLength(2)
expect(s.executions).toEqual(["valid"])
expect(JSON.stringify(s.requests[1])).not.toContain("unfinished-marker")
expect(s.requests[1]?.messages).toEqual(
expect.arrayContaining([
expect.objectContaining({
role: "tool",
content: expect.arrayContaining([
expect.objectContaining({
type: "tool-result",
id: "call-incomplete",
result: expect.objectContaining({ type: "error" }),
}),
]),
}),
]),
)
const assistant = requireAssistant(yield* s.context)
expect(assistant.error).toBeUndefined()
expect(assistant.content).toMatchObject([
{ type: "tool", id: "call-valid", state: { status: "completed" } },
{
type: "tool",
id: "call-incomplete",
executed: false,
state: { status: "error", error: { type: "tool.input-incomplete" } },
},
])
const events = yield* s.db
.select({ type: EventTable.type, data: EventTable.data })
.from(EventTable)
.where(eq(EventTable.aggregate_id, sessionID))
.all()
.pipe(Effect.orDie)
expect(
events.filter((event) => event.type === "session.tool.input.ended.1" && event.data.id === "call-incomplete"),
).toHaveLength(1)
expect(
events.filter((event) => event.type === "session.tool.failed.2" && event.data.id === "call-incomplete"),
).toHaveLength(1)
yield* replaySessionProjection(sessionID)
expect((yield* s.context).find((message) => message.id === assistant.id)).toEqual(assistant)
})
}
}
expect(s.requests).toHaveLength(2)
expect(s.executions).toEqual([])
expect(requireAssistant(yield* s.context).content).toMatchObject([
{
type: "tool",
id: "call-incomplete",
executed: false,
state: { status: "error", error: { type: "tool.input-incomplete" } },
},
])
})
scenario("continues after malformed local tool input without exposing raw arguments", function* (s) {
const marker = "raw-malformed-marker"