Skip to content

Commit 2b6050c

Browse files
committed
Enhance message validation and content normalization for OpenAI API integration
1 parent 9aaf611 commit 2b6050c

File tree

3 files changed

+67
-8
lines changed

3 files changed

+67
-8
lines changed

src/config/validation.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,40 @@ function isValidMessage(value: unknown): value is OpenAIMessage {
3030
const role = value.role;
3131
const content = value.content;
3232

33-
return (
34-
typeof role === 'string' && ['system', 'user', 'assistant'].includes(role) && typeof content === 'string' && content.trim().length > 0
35-
);
33+
// Validate role
34+
if (typeof role !== 'string' || !['system', 'user', 'assistant'].includes(role)) {
35+
return false;
36+
}
37+
38+
// Handle different content types
39+
if (typeof content === 'string') {
40+
// Simple string content
41+
return content.trim().length > 0;
42+
} else if (Array.isArray(content)) {
43+
// Array of content objects (for multimodal messages)
44+
return content.length > 0 && content.every((item) => {
45+
if (!isObject(item)) return false;
46+
if (!('type' in item)) return false;
47+
48+
const type = item.type;
49+
if (typeof type !== 'string') return false;
50+
51+
// Handle text content
52+
if (type === 'text') {
53+
return 'text' in item && typeof item.text === 'string' && item.text.trim().length > 0;
54+
}
55+
56+
// Handle image content (if supported)
57+
if (type === 'image_url') {
58+
return 'image_url' in item && isObject(item.image_url) && 'url' in item.image_url;
59+
}
60+
61+
// Allow other content types to pass through
62+
return true;
63+
});
64+
}
65+
66+
return false;
3667
}
3768

3869
function validateOptionalNumber(value: unknown, fieldName: string, min?: number, max?: number): number | undefined {
@@ -71,9 +102,10 @@ export function validateChatBody(body: unknown): ChatCompletionsBody {
71102

72103
// Validate each message
73104
const validatedMessages: OpenAIMessage[] = [];
74-
for (const message of rawBody.messages) {
105+
for (let i = 0; i < rawBody.messages.length; i++) {
106+
const message = rawBody.messages[i];
75107
if (!isValidMessage(message)) {
76-
throw new Error('Each message must have valid role (system|user|assistant) and non-empty content string');
108+
throw new Error(`Message at index ${i} is invalid: must have valid role (system|user|assistant) and non-empty content. Received: ${JSON.stringify(message)}`);
77109
}
78110
validatedMessages.push(message);
79111
}

src/services/openaiMapper.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1-
import type { OpenAIMessage, ChatCompletionsBody, UpstreamChatCreate } from '../types/openai';
1+
import type { OpenAIMessage, ChatCompletionsBody, UpstreamChatCreate, OpenAIContentItem } from '../types/openai';
22
import { validateChatBody } from '../config/validation';
33

4+
function normalizeContent(content: string | OpenAIContentItem[]): string {
5+
if (typeof content === 'string') {
6+
return content;
7+
}
8+
9+
// For array content, extract text from all text items
10+
return content
11+
.filter((item): item is Extract<OpenAIContentItem, { type: 'text' }> => item.type === 'text')
12+
.map((item) => item.text)
13+
.join('\n');
14+
}
15+
416
export function toUpstreamPayload(body: ChatCompletionsBody, model: string): UpstreamChatCreate {
517
return {
618
model,
7-
messages: body.messages.map((m) => ({ role: m.role, content: m.content })),
19+
messages: body.messages.map((m) => ({ role: m.role, content: normalizeContent(m.content) })),
820
stream: !!body.stream,
921
temperature: body.temperature,
1022
top_p: body.top_p,

src/types/openai.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,24 @@
22
* OpenAI API Types
33
*/
44

5+
export interface OpenAITextContent {
6+
type: 'text';
7+
text: string;
8+
}
9+
10+
export interface OpenAIImageContent {
11+
type: 'image_url';
12+
image_url: {
13+
url: string;
14+
detail?: 'auto' | 'low' | 'high';
15+
};
16+
}
17+
18+
export type OpenAIContentItem = OpenAITextContent | OpenAIImageContent;
19+
520
export interface OpenAIMessage {
621
role: 'system' | 'user' | 'assistant';
7-
content: string;
22+
content: string | OpenAIContentItem[];
823
}
924

1025
export interface ChatCompletionsBody {

0 commit comments

Comments
 (0)