Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/adapters/openai-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1156,7 +1156,11 @@ function normalizeXaiToolParameters(parameters: unknown): Record<string, unknown
if (!isXaiObjectSchema(parameters)) return undefined;
const resolved = resolveXaiSchemaRefs(parameters, parameters);
if (!isXaiObjectSchema(resolved)) return undefined;
const variants = expandXaiRootObjectSchemas(resolved);

const normalizedRoot = { ...resolved };
delete normalizedRoot.$schema;

const variants = expandXaiRootObjectSchemas(normalizedRoot);
if (!variants) return undefined;
if (variants.length === 1) {
return xaiVariantIsConcreteObject(variants[0]) ? variants[0] : undefined;
Expand All @@ -1166,7 +1170,7 @@ function normalizeXaiToolParameters(parameters: unknown): Record<string, unknown
if (!additionalProperties.ok) return undefined;
if (!xaiPropertyMergeIsLossless(variants)) return undefined;

const metadata = Object.fromEntries(Object.entries(resolved).filter(([key]) => key !== "oneOf" && key !== "anyOf" && key !== "type"));
const metadata = Object.fromEntries(Object.entries(normalizedRoot).filter(([key]) => key !== "oneOf" && key !== "anyOf" && key !== "type"));
delete metadata.properties;
delete metadata.required;
delete metadata.additionalProperties;
Expand All @@ -1180,6 +1184,7 @@ function normalizeXaiToolParameters(parameters: unknown): Record<string, unknown
propertyValues.set(name, values);
}
}

const properties = Object.fromEntries(
[...propertyValues].map(([name, values]) => [name, mergeXaiPropertySchemas(values)]),
);
Expand Down
121 changes: 121 additions & 0 deletions tests/xai-tool-schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { describe, expect, test } from "bun:test";
import { createOpenAIChatAdapter } from "../src/adapters/openai-chat";
import type { OcxParsedRequest, OcxTool } from "../src/types";

function parsedRequest(tool: OcxTool): Parameters<ReturnType<typeof createOpenAIChatAdapter>["buildRequest"]>[0] {
return {
modelId: "grok-4.6",
context: {
messages: [
{
role: "user",
content: "run the command",
timestamp: 0,
},
],
tools: [tool],
},
stream: true,
options: {},
} as never;
}

function xaiAdapter() {
return createOpenAIChatAdapter({
adapter: "openai-chat",
baseUrl: "https://cli-chat-proxy.grok.com/v1",
apiKey: "k",
});
}

describe("xAI Grok CLI tool schema normalization", () => {
test("keeps Claude Code tools with a root $schema", () => {
const tool: OcxTool = {
name: "Bash",
description: "Execute a shell command",
parameters: {
$schema: "https://json-schema.org/draft/2020-12/schema",
type: "object",
properties: {
command: {
type: "string",
},
},
required: ["command"],
additionalProperties: false,
},
};

const body = JSON.parse(
xaiAdapter().buildRequest(parsedRequest(tool)).body,
) as {
tools?: Array<{
type: string;
function: {
name: string;
parameters: Record<string, unknown>;
};
}>;
};

expect(body.tools).toHaveLength(1);
expect(body.tools?.[0]?.function.name).toBe("Bash");
expect(body.tools?.[0]?.function.parameters).toEqual({
type: "object",
properties: {
command: {
type: "string",
},
},
required: ["command"],
additionalProperties: false,
});
expect(body.tools?.[0]?.function.parameters).not.toHaveProperty("$schema");
});

test("does not reintroduce $schema when flattening a root union", () => {
const tool: OcxTool = {
name: "Bash",
parameters: {
$schema: "https://json-schema.org/draft/2020-12/schema",
oneOf: [
{
type: "object",
properties: {
command: {
type: "string",
},
},
required: ["command"],
additionalProperties: false,
},
{
type: "object",
properties: {
command: {
type: "string",
minLength: 1,
},
},
required: ["command"],
additionalProperties: false,
},
],
},
};

const body = JSON.parse(
xaiAdapter().buildRequest(parsedRequest(tool)).body,
) as {
tools?: Array<{
function: {
parameters: Record<string, unknown>;
};
}>;
};

expect(body.tools).toHaveLength(1);
expect(body.tools?.[0]?.function.parameters).not.toHaveProperty("$schema");
expect(body.tools?.[0]?.function.parameters).not.toHaveProperty("oneOf");
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
});
Loading