diff --git a/src/api/providers/xai.ts b/src/api/providers/xai.ts index 0cd9cb0273..d1748b8e3e 100644 --- a/src/api/providers/xai.ts +++ b/src/api/providers/xai.ts @@ -15,7 +15,6 @@ import { DEFAULT_HEADERS } from "./constants" import { BaseProvider } from "./base-provider" import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index" import { handleOpenAIError } from "./utils/openai-error-handler" -import { isMcpTool } from "../../utils/mcp-name" const XAI_DEFAULT_TEMPERATURE = 0 @@ -56,11 +55,12 @@ export class XAIHandler extends BaseProvider implements SingleCompletionHandler /** * Convert tools from OpenAI Chat Completions format to Responses API format. - * Chat Completions: { type: "function", function: { name, description, parameters } } - * Responses API: { type: "function", name, description, parameters } + * Chat Completions: { type: "function", function: { name, description, parameters, strict } } + * Responses API: { type: "function", name, description, parameters, strict } * - * Uses base provider's convertToolSchemaForOpenAI() for schema hardening - * (additionalProperties: false, ensureAllRequired) and handles MCP tools. + * Uses base provider's convertToolsForOpenAI() for schema hardening + * (additionalProperties: false, ensureAllRequired) and MCP tool handling, + * then restructures to the flat Responses API tool shape. */ private mapResponseTools(tools?: any[]): any[] | undefined { const converted = this.convertToolsForOpenAI(tools) @@ -69,18 +69,13 @@ export class XAIHandler extends BaseProvider implements SingleCompletionHandler } return converted .filter((tool) => tool?.type === "function") - .map((tool) => { - const isMcp = isMcpTool(tool.function.name) - return { - type: "function", - name: tool.function.name, - description: tool.function.description, - parameters: isMcp - ? tool.function.parameters - : this.convertToolSchemaForOpenAI(tool.function.parameters), - strict: !isMcp, - } - }) + .map((tool) => ({ + type: "function", + name: tool.function.name, + description: tool.function.description, + parameters: tool.function.parameters, + strict: tool.function.strict, + })) } override async *createMessage( @@ -126,10 +121,7 @@ export class XAIHandler extends BaseProvider implements SingleCompletionHandler let stream: AsyncIterable try { - stream = (await this.client.responses.create({ - ...requestBody, - stream: true, - } as any)) as unknown as AsyncIterable + stream = (await this.client.responses.create(requestBody as any)) as unknown as AsyncIterable } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) const apiError = new ApiProviderError(errorMessage, this.providerName, model.id, "createMessage") diff --git a/src/api/transform/__tests__/responses-api-input.spec.ts b/src/api/transform/__tests__/responses-api-input.spec.ts index c57b61d895..496ff3b670 100644 --- a/src/api/transform/__tests__/responses-api-input.spec.ts +++ b/src/api/transform/__tests__/responses-api-input.spec.ts @@ -73,6 +73,35 @@ describe("convertToResponsesApiInput", () => { ]) }) + it("should convert URL image blocks to input_image", () => { + const messages: Anthropic.Messages.MessageParam[] = [ + { + role: "user", + content: [ + { + type: "image", + source: { type: "url", url: "https://example.com/image.png" } as any, + }, + ], + }, + ] + + const result = convertToResponsesApiInput(messages) + + expect(result).toEqual([ + { + role: "user", + content: [ + { + type: "input_image", + detail: "auto", + image_url: "https://example.com/image.png", + }, + ], + }, + ]) + }) + it("should convert tool_result to function_call_output", () => { const messages: Anthropic.Messages.MessageParam[] = [ { diff --git a/src/api/transform/responses-api-input.ts b/src/api/transform/responses-api-input.ts index a766dfef6e..9da6479647 100644 --- a/src/api/transform/responses-api-input.ts +++ b/src/api/transform/responses-api-input.ts @@ -72,13 +72,20 @@ export function convertToResponsesApiInput(messages: Anthropic.Messages.MessageP case "text": contentParts.push({ type: "input_text", text: part.text }) break - case "image": + case "image": { + // Support both base64 and URL image sources + const source = part.source as any + const imageUrl = + source.type === "url" && source.url + ? source.url + : `data:${part.source.media_type};base64,${part.source.data}` contentParts.push({ type: "input_image", detail: "auto", - image_url: `data:${part.source.media_type};base64,${part.source.data}`, + image_url: imageUrl, }) break + } case "tool_result": { // Flush any pending user content before the tool result if (contentParts.length > 0) {