Skip to content

Commit c67906a

Browse files
author
Zoo (VP)
committed
test(b17): add 4 coverage tests for mimo.ts edge cases
1 parent 2183469 commit c67906a

2 files changed

Lines changed: 1915 additions & 1762 deletions

File tree

src/api/providers/__tests__/mimo.spec.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,149 @@ describe("MimoHandler", () => {
657657
expect(mockCreate).toHaveBeenCalledTimes(1)
658658
})
659659

660+
it("should not retry when rejection is a non-Error value (parallel_tool_calls path)", async () => {
661+
// A non-Error rejection (e.g. a string) must not trigger the
662+
// parallel_tool_calls fallback. isParallelToolCallsRejected
663+
// returns false for non-Error values.
664+
mockCreate.mockRejectedValueOnce("network failure")
665+
666+
const messages: Anthropic.Messages.MessageParam[] = [
667+
{ role: "user", content: [{ type: "text", text: "Hello" }] },
668+
]
669+
670+
await expect(async () => {
671+
const stream = handler.createMessage("System prompt", messages, {
672+
taskId: "test-task",
673+
parallelToolCalls: false,
674+
})
675+
for await (const _chunk of stream) {
676+
// drain
677+
}
678+
}).rejects.toThrow()
679+
680+
expect(mockCreate).toHaveBeenCalledTimes(1)
681+
})
682+
683+
it("should not retry strict-schema fallback for non-400 errors", async () => {
684+
// A 500 error must NOT trigger the strict-schema fallback.
685+
// isStrictToolSchemaRejected returns false when status !== 400.
686+
const rejectionError = Object.assign(
687+
new Error("500 - Internal server error"),
688+
{ status: 500 },
689+
)
690+
mockCreate.mockRejectedValueOnce(rejectionError)
691+
692+
const tools: OpenAI.Chat.ChatCompletionTool[] = [
693+
{
694+
type: "function",
695+
function: { name: "read_file", description: "Read", parameters: {} },
696+
},
697+
]
698+
699+
const messages: Anthropic.Messages.MessageParam[] = [
700+
{ role: "user", content: [{ type: "text", text: "Hello" }] },
701+
]
702+
703+
await expect(async () => {
704+
const stream = handler.createMessage("System prompt", messages, { taskId: "test-task", tools })
705+
for await (const _chunk of stream) {
706+
// drain
707+
}
708+
}).rejects.toThrow()
709+
710+
expect(mockCreate).toHaveBeenCalledTimes(1)
711+
})
712+
713+
it("should not retry strict-schema fallback for non-Error rejections", async () => {
714+
// A non-Error rejection (e.g. a string) must not trigger the
715+
// strict-schema fallback. isStrictToolSchemaRejected returns
716+
// false for non-Error values.
717+
mockCreate.mockRejectedValueOnce("bad gateway")
718+
719+
const tools: OpenAI.Chat.ChatCompletionTool[] = [
720+
{
721+
type: "function",
722+
function: { name: "read_file", description: "Read", parameters: {} },
723+
},
724+
]
725+
726+
const messages: Anthropic.Messages.MessageParam[] = [
727+
{ role: "user", content: [{ type: "text", text: "Hello" }] },
728+
]
729+
730+
await expect(async () => {
731+
const stream = handler.createMessage("System prompt", messages, { taskId: "test-task", tools })
732+
for await (const _chunk of stream) {
733+
// drain
734+
}
735+
}).rejects.toThrow()
736+
737+
expect(mockCreate).toHaveBeenCalledTimes(1)
738+
})
739+
740+
it("should pass non-function tools through unchanged during strict-schema retry", async () => {
741+
// When the endpoint rejects strict tool schemas, the retry
742+
// strips strict from function tools but passes non-function
743+
// tools (e.g. type "code_interpreter") through unchanged.
744+
const rejectionError = Object.assign(new Error("400 - Unknown parameter: tools[0].function.strict"), {
745+
status: 400,
746+
})
747+
mockCreate.mockRejectedValueOnce(rejectionError)
748+
749+
// Second call (retry) succeeds
750+
mockCreate.mockImplementationOnce(async () => ({
751+
[Symbol.asyncIterator]: async function* () {
752+
yield {
753+
choices: [{ delta: { content: "Retried" }, index: 0 }],
754+
usage: null,
755+
}
756+
yield {
757+
choices: [{ delta: {}, index: 0, finish_reason: "stop" }],
758+
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
759+
}
760+
},
761+
}))
762+
763+
const tools: OpenAI.Chat.ChatCompletionTool[] = [
764+
{
765+
type: "function",
766+
function: {
767+
name: "read_file",
768+
description: "Read",
769+
parameters: { type: "object", properties: {} },
770+
strict: true,
771+
},
772+
},
773+
// Non-function tool — should pass through stripStrictFromTools unchanged
774+
{
775+
type: "code_interpreter" as OpenAI.Chat.ChatCompletionTool["type"],
776+
code_interpreter: { name: "code_interpreter" },
777+
} as unknown as OpenAI.Chat.ChatCompletionTool,
778+
]
779+
780+
const messages: Anthropic.Messages.MessageParam[] = [
781+
{ role: "user", content: [{ type: "text", text: "Hello" }] },
782+
]
783+
784+
const stream = handler.createMessage("System prompt", messages, { taskId: "test-task", tools })
785+
for await (const _chunk of stream) {
786+
// drain
787+
}
788+
789+
// The retry call should have been made
790+
expect(mockCreate).toHaveBeenCalledTimes(2)
791+
792+
// The retry call's tools should have the function tool with strict removed
793+
// and the non-function tool preserved unchanged
794+
const retryCallParams = mockCreate.mock.calls[1][0]
795+
expect(retryCallParams.tools).toBeDefined()
796+
expect(retryCallParams.tools).toHaveLength(2)
797+
// Function tool should have strict removed
798+
expect(retryCallParams.tools[0].function).not.toHaveProperty("strict")
799+
// Non-function tool should be preserved
800+
expect(retryCallParams.tools[1].type).toBe("code_interpreter")
801+
})
802+
660803
it("should send stream_options with include_usage", async () => {
661804
const messages: Anthropic.Messages.MessageParam[] = [
662805
{ role: "user", content: [{ type: "text", text: "Hello" }] },

0 commit comments

Comments
 (0)