diff --git a/src/api/providers/__tests__/minimax.spec.ts b/src/api/providers/__tests__/minimax.spec.ts index d1e25358fab..d01dfdab0ef 100644 --- a/src/api/providers/__tests__/minimax.spec.ts +++ b/src/api/providers/__tests__/minimax.spec.ts @@ -279,6 +279,34 @@ describe("MiniMaxHandler", () => { undefined, ) }) + + it("should handle streaming chunks with null choices array", async () => { + const testContent = "Content after null choices" + + mockCreate.mockImplementationOnce(() => { + return { + [Symbol.asyncIterator]: () => ({ + next: vitest + .fn() + .mockResolvedValueOnce({ + done: false, + value: { choices: null }, + }) + .mockResolvedValueOnce({ + done: false, + value: { choices: [{ delta: { content: testContent } }] }, + }) + .mockResolvedValueOnce({ done: true }), + }), + } + }) + + const stream = handler.createMessage("system prompt", []) + const firstChunk = await stream.next() + + expect(firstChunk.done).toBe(false) + expect(firstChunk.value).toEqual({ type: "text", text: testContent }) + }) }) describe("Model Configuration", () => { diff --git a/src/api/providers/base-openai-compatible-provider.ts b/src/api/providers/base-openai-compatible-provider.ts index 86ddb25fdfd..2a240510a24 100644 --- a/src/api/providers/base-openai-compatible-provider.ts +++ b/src/api/providers/base-openai-compatible-provider.ts @@ -116,7 +116,15 @@ export abstract class BaseOpenAiCompatibleProvider ) for await (const chunk of stream) { - const delta = chunk.choices[0]?.delta + // Check for provider-specific error responses (e.g., MiniMax base_resp) + const chunkAny = chunk as any + if (chunkAny.base_resp?.status_code && chunkAny.base_resp.status_code !== 0) { + throw new Error( + `${this.providerName} API Error (${chunkAny.base_resp.status_code}): ${chunkAny.base_resp.status_msg || "Unknown error"}`, + ) + } + + const delta = chunk.choices?.[0]?.delta if (delta?.content) { for (const processedChunk of matcher.update(delta.content)) { @@ -155,7 +163,15 @@ export abstract class BaseOpenAiCompatibleProvider messages: [{ role: "user", content: prompt }], }) - return response.choices[0]?.message.content || "" + // Check for provider-specific error responses (e.g., MiniMax base_resp) + const responseAny = response as any + if (responseAny.base_resp?.status_code && responseAny.base_resp.status_code !== 0) { + throw new Error( + `${this.providerName} API Error (${responseAny.base_resp.status_code}): ${responseAny.base_resp.status_msg || "Unknown error"}`, + ) + } + + return response.choices?.[0]?.message.content || "" } catch (error) { throw handleOpenAIError(error, this.providerName) } diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 9100ff3c659..6b847be2d09 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -191,7 +191,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl let lastUsage for await (const chunk of stream) { - const delta = chunk.choices[0]?.delta ?? {} + const delta = chunk.choices?.[0]?.delta ?? {} if (delta.content) { for (const chunk of matcher.update(delta.content)) { @@ -242,7 +242,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl yield { type: "text", - text: response.choices[0]?.message.content || "", + text: response.choices?.[0]?.message.content || "", } yield this.processUsageMetrics(response.usage, modelInfo) @@ -290,7 +290,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl throw handleOpenAIError(error, this.providerName) } - return response.choices[0]?.message.content || "" + return response.choices?.[0]?.message.content || "" } catch (error) { if (error instanceof Error) { throw new Error(`${this.providerName} completion error: ${error.message}`) @@ -373,7 +373,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl yield { type: "text", - text: response.choices[0]?.message.content || "", + text: response.choices?.[0]?.message.content || "", } yield this.processUsageMetrics(response.usage) } @@ -381,7 +381,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl private async *handleStreamResponse(stream: AsyncIterable): ApiStream { for await (const chunk of stream) { - const delta = chunk.choices[0]?.delta + const delta = chunk.choices?.[0]?.delta if (delta?.content) { yield { type: "text",