Skip to content

Commit 7281629

Browse files
committed
fix: properly handle multi-turn conversations for codex-mini-latest
- Updated convertMessagesToInput to include both user and assistant messages - Added role prefixes (User:/Assistant:) to maintain conversation context - Added comprehensive test for multi-turn conversation handling - Fixes the issue raised by daniel-lxs about missing assistant messages
1 parent 985ab4d commit 7281629

File tree

2 files changed

+69
-12
lines changed

2 files changed

+69
-12
lines changed

src/api/providers/__tests__/openai-native.spec.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ describe("OpenAiNativeHandler", () => {
482482
expect(mockResponsesCreate).toHaveBeenCalledWith({
483483
model: "codex-mini-latest",
484484
instructions: systemPrompt,
485-
input: "Hello!",
485+
input: "User: Hello!",
486486
stream: true,
487487
})
488488

@@ -519,6 +519,57 @@ describe("OpenAiNativeHandler", () => {
519519
}
520520
}).rejects.toThrow("OpenAI Responses API error: This model is only supported in v1/responses")
521521
})
522+
523+
it("should handle multi-turn conversations with both user and assistant messages", async () => {
524+
const multiTurnMessages: Anthropic.Messages.MessageParam[] = [
525+
{
526+
role: "user",
527+
content: "What is TypeScript?",
528+
},
529+
{
530+
role: "assistant",
531+
content: "TypeScript is a typed superset of JavaScript.",
532+
},
533+
{
534+
role: "user",
535+
content: "Can you give me an example?",
536+
},
537+
]
538+
539+
mockResponsesCreate.mockImplementation(async (options) => {
540+
// Verify that the input includes both user and assistant messages
541+
expect(options.input).toContain("User: What is TypeScript?")
542+
expect(options.input).toContain("Assistant: TypeScript is a typed superset of JavaScript.")
543+
expect(options.input).toContain("User: Can you give me an example?")
544+
expect(options.stream).toBe(true)
545+
546+
return {
547+
[Symbol.asyncIterator]: async function* () {
548+
yield { type: "response.output_text.delta", delta: "Here's an example:" }
549+
yield { type: "response.output_text.delta", delta: " const x: number = 5;" }
550+
yield { type: "response.completed" }
551+
},
552+
}
553+
})
554+
555+
const responseStream = handler.createMessage(systemPrompt, multiTurnMessages)
556+
const chunks: any[] = []
557+
for await (const chunk of responseStream) {
558+
chunks.push(chunk)
559+
}
560+
561+
expect(mockResponsesCreate).toHaveBeenCalledWith({
562+
model: "codex-mini-latest",
563+
instructions: systemPrompt,
564+
input: "User: What is TypeScript?\n\nAssistant: TypeScript is a typed superset of JavaScript.\n\nUser: Can you give me an example?",
565+
stream: true,
566+
})
567+
568+
const textChunks = chunks.filter((chunk) => chunk.type === "text")
569+
expect(textChunks).toHaveLength(2)
570+
expect(textChunks[0].text).toBe("Here's an example:")
571+
expect(textChunks[1].text).toBe(" const x: number = 5;")
572+
})
522573
})
523574

524575
describe("getModel", () => {

src/api/providers/openai-native.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -159,19 +159,25 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
159159
}
160160

161161
private convertMessagesToInput(messages: Anthropic.Messages.MessageParam[]): string {
162-
// Extract only user messages and convert to plain text
163-
// This is specific to the codex-mini-latest model's requirements
162+
// Convert both user and assistant messages to maintain conversation context
163+
// The v1/responses endpoint expects a formatted conversation string
164164
return messages
165165
.map((msg) => {
166-
if (msg.role === "user") {
167-
if (typeof msg.content === "string") {
168-
return msg.content
169-
} else if (Array.isArray(msg.content)) {
170-
return msg.content
171-
.filter((part) => part.type === "text")
172-
.map((part) => part.text)
173-
.join("\n")
174-
}
166+
let content = ""
167+
168+
// Extract text content from the message
169+
if (typeof msg.content === "string") {
170+
content = msg.content
171+
} else if (Array.isArray(msg.content)) {
172+
content = msg.content
173+
.filter((part) => part.type === "text")
174+
.map((part) => part.text)
175+
.join("\n")
176+
}
177+
178+
// Add role prefix to maintain conversation structure
179+
if (content) {
180+
return msg.role === "user" ? `User: ${content}` : `Assistant: ${content}`
175181
}
176182
return ""
177183
})

0 commit comments

Comments
 (0)