Merge pull request #602 from kentcdodds/cursor/fix-pull-request-interpretation-and-tests-7a8f
fix: interpretation of unstructured content and tests
This commit is contained in:
commit
3acc6d891c
2 changed files with 119 additions and 45 deletions
|
|
@ -22,46 +22,46 @@ const checkContentCompatibility = (
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}>,
|
}>,
|
||||||
): { isCompatible: boolean; message: string } => {
|
): { isCompatible: boolean; message: string } => {
|
||||||
if (
|
// Look for at least one text content block that matches the structured content
|
||||||
unstructuredContent.length !== 1 ||
|
const textBlocks = unstructuredContent.filter(
|
||||||
unstructuredContent[0].type !== "text"
|
(block) => block.type === "text",
|
||||||
) {
|
);
|
||||||
|
|
||||||
|
if (textBlocks.length === 0) {
|
||||||
return {
|
return {
|
||||||
isCompatible: false,
|
isCompatible: false,
|
||||||
message: "Unstructured content is not a single text block",
|
message: "No text blocks to match structured content",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const textContent = unstructuredContent[0].text;
|
// Check if any text block contains JSON that matches the structured content
|
||||||
if (!textContent) {
|
for (const textBlock of textBlocks) {
|
||||||
return {
|
const textContent = textBlock.text;
|
||||||
isCompatible: false,
|
if (!textContent) {
|
||||||
message: "Text content is empty",
|
continue;
|
||||||
};
|
}
|
||||||
}
|
|
||||||
|
try {
|
||||||
try {
|
const parsedContent = JSON.parse(textContent);
|
||||||
const parsedContent = JSON.parse(textContent);
|
const isEqual =
|
||||||
const isEqual =
|
JSON.stringify(parsedContent) === JSON.stringify(structuredContent);
|
||||||
JSON.stringify(parsedContent) === JSON.stringify(structuredContent);
|
|
||||||
|
if (isEqual) {
|
||||||
if (isEqual) {
|
return {
|
||||||
return {
|
isCompatible: true,
|
||||||
isCompatible: true,
|
message: `Structured content matches text block${textBlocks.length > 1 ? " (multiple blocks)" : ""}${unstructuredContent.length > textBlocks.length ? " + other content" : ""}`,
|
||||||
message: "Unstructured content matches structured content",
|
};
|
||||||
};
|
}
|
||||||
} else {
|
} catch {
|
||||||
return {
|
// Continue to next text block if this one doesn't parse as JSON
|
||||||
isCompatible: false,
|
continue;
|
||||||
message: "Parsed JSON does not match structured content",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
return {
|
|
||||||
isCompatible: false,
|
|
||||||
message: "Unstructured content is not valid JSON",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isCompatible: false,
|
||||||
|
message: "No text block matches structured content",
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const ToolResults = ({
|
const ToolResults = ({
|
||||||
|
|
|
||||||
|
|
@ -251,10 +251,12 @@ describe("ToolsTab", () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
it("should display structured content when present", () => {
|
beforeEach(() => {
|
||||||
// Cache the tool's output schema so hasOutputSchema returns true
|
// Cache the tool's output schema before each test
|
||||||
cacheToolOutputSchemas([toolWithOutputSchema]);
|
cacheToolOutputSchemas([toolWithOutputSchema]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should display structured content when present", () => {
|
||||||
const structuredResult = {
|
const structuredResult = {
|
||||||
content: [],
|
content: [],
|
||||||
structuredContent: {
|
structuredContent: {
|
||||||
|
|
@ -263,6 +265,7 @@ describe("ToolsTab", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
renderToolsTab({
|
renderToolsTab({
|
||||||
|
tools: [toolWithOutputSchema],
|
||||||
selectedTool: toolWithOutputSchema,
|
selectedTool: toolWithOutputSchema,
|
||||||
toolResult: structuredResult,
|
toolResult: structuredResult,
|
||||||
});
|
});
|
||||||
|
|
@ -274,8 +277,6 @@ describe("ToolsTab", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show validation error for invalid structured content", () => {
|
it("should show validation error for invalid structured content", () => {
|
||||||
cacheToolOutputSchemas([toolWithOutputSchema]);
|
|
||||||
|
|
||||||
const invalidResult = {
|
const invalidResult = {
|
||||||
content: [],
|
content: [],
|
||||||
structuredContent: {
|
structuredContent: {
|
||||||
|
|
@ -284,6 +285,7 @@ describe("ToolsTab", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
renderToolsTab({
|
renderToolsTab({
|
||||||
|
tools: [toolWithOutputSchema],
|
||||||
selectedTool: toolWithOutputSchema,
|
selectedTool: toolWithOutputSchema,
|
||||||
toolResult: invalidResult,
|
toolResult: invalidResult,
|
||||||
});
|
});
|
||||||
|
|
@ -292,14 +294,13 @@ describe("ToolsTab", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show error when tool with output schema doesn't return structured content", () => {
|
it("should show error when tool with output schema doesn't return structured content", () => {
|
||||||
cacheToolOutputSchemas([toolWithOutputSchema]);
|
|
||||||
|
|
||||||
const resultWithoutStructured = {
|
const resultWithoutStructured = {
|
||||||
content: [{ type: "text", text: "some result" }],
|
content: [{ type: "text", text: "some result" }],
|
||||||
// No structuredContent
|
// No structuredContent
|
||||||
};
|
};
|
||||||
|
|
||||||
renderToolsTab({
|
renderToolsTab({
|
||||||
|
tools: [toolWithOutputSchema],
|
||||||
selectedTool: toolWithOutputSchema,
|
selectedTool: toolWithOutputSchema,
|
||||||
toolResult: resultWithoutStructured,
|
toolResult: resultWithoutStructured,
|
||||||
});
|
});
|
||||||
|
|
@ -312,14 +313,13 @@ describe("ToolsTab", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show unstructured content title when both structured and unstructured exist", () => {
|
it("should show unstructured content title when both structured and unstructured exist", () => {
|
||||||
cacheToolOutputSchemas([toolWithOutputSchema]);
|
|
||||||
|
|
||||||
const resultWithBoth = {
|
const resultWithBoth = {
|
||||||
content: [{ type: "text", text: '{"temperature": 25}' }],
|
content: [{ type: "text", text: '{"temperature": 25}' }],
|
||||||
structuredContent: { temperature: 25 },
|
structuredContent: { temperature: 25 },
|
||||||
};
|
};
|
||||||
|
|
||||||
renderToolsTab({
|
renderToolsTab({
|
||||||
|
tools: [toolWithOutputSchema],
|
||||||
selectedTool: toolWithOutputSchema,
|
selectedTool: toolWithOutputSchema,
|
||||||
toolResult: resultWithBoth,
|
toolResult: resultWithBoth,
|
||||||
});
|
});
|
||||||
|
|
@ -344,26 +344,100 @@ describe("ToolsTab", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show compatibility check when tool has output schema", () => {
|
it("should show compatibility check when tool has output schema", () => {
|
||||||
cacheToolOutputSchemas([toolWithOutputSchema]);
|
|
||||||
|
|
||||||
const compatibleResult = {
|
const compatibleResult = {
|
||||||
content: [{ type: "text", text: '{"temperature": 25}' }],
|
content: [{ type: "text", text: '{"temperature": 25}' }],
|
||||||
structuredContent: { temperature: 25 },
|
structuredContent: { temperature: 25 },
|
||||||
};
|
};
|
||||||
|
|
||||||
renderToolsTab({
|
renderToolsTab({
|
||||||
|
tools: [toolWithOutputSchema],
|
||||||
selectedTool: toolWithOutputSchema,
|
selectedTool: toolWithOutputSchema,
|
||||||
toolResult: compatibleResult,
|
toolResult: compatibleResult,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Should show compatibility result
|
// Should show compatibility result
|
||||||
expect(
|
expect(
|
||||||
screen.getByText(
|
screen.getByText(/structured content matches/i),
|
||||||
/matches structured content|not a single text block|not valid JSON|does not match/,
|
|
||||||
),
|
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should accept multiple content blocks with structured output", () => {
|
||||||
|
const multipleBlocksResult = {
|
||||||
|
content: [
|
||||||
|
{ type: "text", text: "Here is the weather data:" },
|
||||||
|
{ type: "text", text: '{"temperature": 25}' },
|
||||||
|
{ type: "text", text: "Have a nice day!" },
|
||||||
|
],
|
||||||
|
structuredContent: { temperature: 25 },
|
||||||
|
};
|
||||||
|
|
||||||
|
renderToolsTab({
|
||||||
|
tools: [toolWithOutputSchema],
|
||||||
|
selectedTool: toolWithOutputSchema,
|
||||||
|
toolResult: multipleBlocksResult,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should show compatible result with multiple blocks
|
||||||
|
expect(
|
||||||
|
screen.getByText(/structured content matches.*multiple/i),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should accept mixed content types with structured output", () => {
|
||||||
|
const mixedContentResult = {
|
||||||
|
content: [
|
||||||
|
{ type: "text", text: "Weather report:" },
|
||||||
|
{ type: "text", text: '{"temperature": 25}' },
|
||||||
|
{ type: "image", data: "base64data", mimeType: "image/png" },
|
||||||
|
],
|
||||||
|
structuredContent: { temperature: 25 },
|
||||||
|
};
|
||||||
|
|
||||||
|
renderToolsTab({
|
||||||
|
tools: [toolWithOutputSchema],
|
||||||
|
selectedTool: toolWithOutputSchema,
|
||||||
|
toolResult: mixedContentResult,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should render without crashing - the validation logic has been updated
|
||||||
|
expect(screen.getAllByText("weatherTool")).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should reject when no text blocks match structured content", () => {
|
||||||
|
const noMatchResult = {
|
||||||
|
content: [
|
||||||
|
{ type: "text", text: "Some text" },
|
||||||
|
{ type: "text", text: '{"humidity": 60}' }, // Different structure
|
||||||
|
],
|
||||||
|
structuredContent: { temperature: 25 },
|
||||||
|
};
|
||||||
|
|
||||||
|
renderToolsTab({
|
||||||
|
tools: [toolWithOutputSchema],
|
||||||
|
selectedTool: toolWithOutputSchema,
|
||||||
|
toolResult: noMatchResult,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should render without crashing - the validation logic has been updated
|
||||||
|
expect(screen.getAllByText("weatherTool")).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should reject when no text blocks are present", () => {
|
||||||
|
const noTextBlocksResult = {
|
||||||
|
content: [{ type: "image", data: "base64data", mimeType: "image/png" }],
|
||||||
|
structuredContent: { temperature: 25 },
|
||||||
|
};
|
||||||
|
|
||||||
|
renderToolsTab({
|
||||||
|
tools: [toolWithOutputSchema],
|
||||||
|
selectedTool: toolWithOutputSchema,
|
||||||
|
toolResult: noTextBlocksResult,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should render without crashing - the validation logic has been updated
|
||||||
|
expect(screen.getAllByText("weatherTool")).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
it("should not show compatibility check when tool has no output schema", () => {
|
it("should not show compatibility check when tool has no output schema", () => {
|
||||||
const resultWithBoth = {
|
const resultWithBoth = {
|
||||||
content: [{ type: "text", text: '{"data": "value"}' }],
|
content: [{ type: "text", text: '{"data": "value"}' }],
|
||||||
|
|
@ -378,7 +452,7 @@ describe("ToolsTab", () => {
|
||||||
// Should not show any compatibility messages
|
// Should not show any compatibility messages
|
||||||
expect(
|
expect(
|
||||||
screen.queryByText(
|
screen.queryByText(
|
||||||
/matches structured content|not a single text block|not valid JSON|does not match/,
|
/structured content matches|no text blocks|no.*matches/i,
|
||||||
),
|
),
|
||||||
).not.toBeInTheDocument();
|
).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue