fix: handle array content format in OpenAI messages#3
fix: handle array content format in OpenAI messages#3grassX1998 wants to merge 1 commit intoatalovesyou:mainfrom
Conversation
The OpenAI Chat Completions API allows message content to be either a
plain string or an array of content parts (e.g.
[{type: "text", text: "..."}]). When an upstream client sends array
content, the previous code passed it directly to template literals,
resulting in "[object Object]" being sent to Claude CLI.
Add extractContent() helper that normalises both string and array
formats into a single string, and update the OpenAIChatMessage type to
reflect the actual API spec.
There was a problem hiding this comment.
Pull request overview
This PR fixes a bug where OpenAI Chat Completions API messages with array-formatted content (e.g., [{type: "text", text: "Hello"}]) were incorrectly converted to [object Object] instead of extracting the actual text. The fix updates the type definitions to match the OpenAI API spec and adds content extraction logic to handle both string and array formats.
Changes:
- Updated
OpenAIChatMessage.contenttype to support both string and array formats per OpenAI API specification - Added
OpenAIContentPartinterface to define content part structure (text and image_url types) - Implemented
extractContent()helper function to normalize both content formats into plain strings
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/types/openai.ts | Added OpenAIContentPart interface and updated OpenAIChatMessage.content to union type |
| src/adapter/openai-to-cli.ts | Added extractContent() function and integrated it into messagesToPrompt() to handle array content |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export interface OpenAIContentPart { | ||
| type: "text" | "image_url"; | ||
| text?: string; | ||
| image_url?: { url: string; detail?: string }; | ||
| } |
There was a problem hiding this comment.
The OpenAIContentPart interface should use discriminated unions to properly represent the OpenAI API spec. Currently, both text and image_url fields are optional, which allows invalid states like having both or neither field. The correct type should be: {type: "text"; text: string} | {type: "image_url"; image_url: {url: string; detail?: string}}. This ensures type safety and matches the actual API behavior where the field name corresponds to the type.
| export interface OpenAIContentPart { | |
| type: "text" | "image_url"; | |
| text?: string; | |
| image_url?: { url: string; detail?: string }; | |
| } | |
| export type OpenAIContentPart = | |
| | { | |
| type: "text"; | |
| text: string; | |
| } | |
| | { | |
| type: "image_url"; | |
| image_url: { url: string; detail?: string }; | |
| }; |
| .map((part) => { | ||
| if (typeof part === "string") return part; | ||
| if (part && typeof part === "object") return part.text ?? ""; | ||
| return ""; | ||
| }) |
There was a problem hiding this comment.
The check for typeof part === "string" is inconsistent with the OpenAIContentPart type definition, which only allows objects with specific structure. According to the OpenAI API specification, content array elements should always be objects with a type field. Consider removing this check or updating the type definition if the API actually allows plain strings in the array.
| .map((part) => { | |
| if (typeof part === "string") return part; | |
| if (part && typeof part === "object") return part.text ?? ""; | |
| return ""; | |
| }) | |
| .map((part) => part.text ?? "") |
| return content | ||
| .map((part) => { | ||
| if (typeof part === "string") return part; | ||
| if (part && typeof part === "object") return part.text ?? ""; |
There was a problem hiding this comment.
The function silently ignores image_url content parts by only extracting the text field. When a message contains image content (type: "image_url"), this data is lost without any indication to the user. Consider either logging a warning when image content is encountered, or documenting this limitation in the function's docstring, since Claude CLI may not support image inputs.
|
I can verify that this works. |
…OpenAI messages
Summary
The OpenAI Chat Completions API allows message
contentto be either a plain string or an array of content parts (spec):{"role": "user", "content": [{"type": "text", "text": "Hello"}]}When an upstream client (e.g. OpenClaw/Clawdbot) sends content in array format, the current code passes it directly into template literals, which calls
toString()on the array elements and produces[object Object]instead of the actual message text.Changes
src/types/openai.ts: UpdateOpenAIChatMessage.contenttype tostring | OpenAIContentPart[]to match the OpenAI API spec. AddOpenAIContentPartinterface.src/adapter/openai-to-cli.ts: AddextractContent()helper that normalises both string and array content formats into a plain string. UpdatemessagesToPrompt()to use it.Before / After
content: [{type: "text", text: "Hello"}][object Object]content: [{type: "text", text: "Hello"}]HelloString content (
content: "Hello") continues to work unchanged.