Skip to content

fix: handle array content format in OpenAI messages#3

Open
grassX1998 wants to merge 1 commit intoatalovesyou:mainfrom
grassX1998:fix/handle-array-content-format
Open

fix: handle array content format in OpenAI messages#3
grassX1998 wants to merge 1 commit intoatalovesyou:mainfrom
grassX1998:fix/handle-array-content-format

Conversation

@grassX1998
Copy link

Summary

The OpenAI Chat Completions API allows message content to 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: Update OpenAIChatMessage.content type to string | OpenAIContentPart[] to match the OpenAI API spec. Add OpenAIContentPart interface.
  • src/adapter/openai-to-cli.ts: Add extractContent() helper that normalises both string and array content formats into a plain string. Update messagesToPrompt() to use it.

Before / After

Request Claude CLI receives
Before content: [{type: "text", text: "Hello"}] [object Object]
After content: [{type: "text", text: "Hello"}] Hello

String content (content: "Hello") continues to work unchanged.

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.
Copilot AI review requested due to automatic review settings February 1, 2026 11:31
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.content type to support both string and array formats per OpenAI API specification
  • Added OpenAIContentPart interface 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.

Comment on lines +10 to +14
export interface OpenAIContentPart {
type: "text" | "image_url";
text?: string;
image_url?: { url: string; detail?: string };
}
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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 };
};

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +70
.map((part) => {
if (typeof part === "string") return part;
if (part && typeof part === "object") return part.text ?? "";
return "";
})
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
.map((part) => {
if (typeof part === "string") return part;
if (part && typeof part === "object") return part.text ?? "";
return "";
})
.map((part) => part.text ?? "")

Copilot uses AI. Check for mistakes.
return content
.map((part) => {
if (typeof part === "string") return part;
if (part && typeof part === "object") return part.text ?? "";
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@aaldere1
Copy link

aaldere1 commented Feb 4, 2026

I can verify that this works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants