Skip to content

Commit 3acc6d8

Browse files
authored
Merge pull request #602 from kentcdodds/cursor/fix-pull-request-interpretation-and-tests-7a8f
fix: interpretation of unstructured content and tests
2 parents a7539d0 + 1756b01 commit 3acc6d8

File tree

2 files changed

+119
-45
lines changed

2 files changed

+119
-45
lines changed

client/src/components/ToolResults.tsx

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,46 +22,46 @@ const checkContentCompatibility = (
2222
[key: string]: unknown;
2323
}>,
2424
): { isCompatible: boolean; message: string } => {
25-
if (
26-
unstructuredContent.length !== 1 ||
27-
unstructuredContent[0].type !== "text"
28-
) {
29-
return {
30-
isCompatible: false,
31-
message: "Unstructured content is not a single text block",
32-
};
33-
}
25+
// Look for at least one text content block that matches the structured content
26+
const textBlocks = unstructuredContent.filter(
27+
(block) => block.type === "text",
28+
);
3429

35-
const textContent = unstructuredContent[0].text;
36-
if (!textContent) {
30+
if (textBlocks.length === 0) {
3731
return {
3832
isCompatible: false,
39-
message: "Text content is empty",
33+
message: "No text blocks to match structured content",
4034
};
4135
}
4236

43-
try {
44-
const parsedContent = JSON.parse(textContent);
45-
const isEqual =
46-
JSON.stringify(parsedContent) === JSON.stringify(structuredContent);
37+
// Check if any text block contains JSON that matches the structured content
38+
for (const textBlock of textBlocks) {
39+
const textContent = textBlock.text;
40+
if (!textContent) {
41+
continue;
42+
}
43+
44+
try {
45+
const parsedContent = JSON.parse(textContent);
46+
const isEqual =
47+
JSON.stringify(parsedContent) === JSON.stringify(structuredContent);
4748

48-
if (isEqual) {
49-
return {
50-
isCompatible: true,
51-
message: "Unstructured content matches structured content",
52-
};
53-
} else {
54-
return {
55-
isCompatible: false,
56-
message: "Parsed JSON does not match structured content",
57-
};
49+
if (isEqual) {
50+
return {
51+
isCompatible: true,
52+
message: `Structured content matches text block${textBlocks.length > 1 ? " (multiple blocks)" : ""}${unstructuredContent.length > textBlocks.length ? " + other content" : ""}`,
53+
};
54+
}
55+
} catch {
56+
// Continue to next text block if this one doesn't parse as JSON
57+
continue;
5858
}
59-
} catch {
60-
return {
61-
isCompatible: false,
62-
message: "Unstructured content is not valid JSON",
63-
};
6459
}
60+
61+
return {
62+
isCompatible: false,
63+
message: "No text block matches structured content",
64+
};
6565
};
6666

6767
const ToolResults = ({

client/src/components/__tests__/ToolsTab.test.tsx

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -251,10 +251,12 @@ describe("ToolsTab", () => {
251251
},
252252
};
253253

254-
it("should display structured content when present", () => {
255-
// Cache the tool's output schema so hasOutputSchema returns true
254+
beforeEach(() => {
255+
// Cache the tool's output schema before each test
256256
cacheToolOutputSchemas([toolWithOutputSchema]);
257+
});
257258

259+
it("should display structured content when present", () => {
258260
const structuredResult = {
259261
content: [],
260262
structuredContent: {
@@ -263,6 +265,7 @@ describe("ToolsTab", () => {
263265
};
264266

265267
renderToolsTab({
268+
tools: [toolWithOutputSchema],
266269
selectedTool: toolWithOutputSchema,
267270
toolResult: structuredResult,
268271
});
@@ -274,8 +277,6 @@ describe("ToolsTab", () => {
274277
});
275278

276279
it("should show validation error for invalid structured content", () => {
277-
cacheToolOutputSchemas([toolWithOutputSchema]);
278-
279280
const invalidResult = {
280281
content: [],
281282
structuredContent: {
@@ -284,6 +285,7 @@ describe("ToolsTab", () => {
284285
};
285286

286287
renderToolsTab({
288+
tools: [toolWithOutputSchema],
287289
selectedTool: toolWithOutputSchema,
288290
toolResult: invalidResult,
289291
});
@@ -292,14 +294,13 @@ describe("ToolsTab", () => {
292294
});
293295

294296
it("should show error when tool with output schema doesn't return structured content", () => {
295-
cacheToolOutputSchemas([toolWithOutputSchema]);
296-
297297
const resultWithoutStructured = {
298298
content: [{ type: "text", text: "some result" }],
299299
// No structuredContent
300300
};
301301

302302
renderToolsTab({
303+
tools: [toolWithOutputSchema],
303304
selectedTool: toolWithOutputSchema,
304305
toolResult: resultWithoutStructured,
305306
});
@@ -312,14 +313,13 @@ describe("ToolsTab", () => {
312313
});
313314

314315
it("should show unstructured content title when both structured and unstructured exist", () => {
315-
cacheToolOutputSchemas([toolWithOutputSchema]);
316-
317316
const resultWithBoth = {
318317
content: [{ type: "text", text: '{"temperature": 25}' }],
319318
structuredContent: { temperature: 25 },
320319
};
321320

322321
renderToolsTab({
322+
tools: [toolWithOutputSchema],
323323
selectedTool: toolWithOutputSchema,
324324
toolResult: resultWithBoth,
325325
});
@@ -344,26 +344,100 @@ describe("ToolsTab", () => {
344344
});
345345

346346
it("should show compatibility check when tool has output schema", () => {
347-
cacheToolOutputSchemas([toolWithOutputSchema]);
348-
349347
const compatibleResult = {
350348
content: [{ type: "text", text: '{"temperature": 25}' }],
351349
structuredContent: { temperature: 25 },
352350
};
353351

354352
renderToolsTab({
353+
tools: [toolWithOutputSchema],
355354
selectedTool: toolWithOutputSchema,
356355
toolResult: compatibleResult,
357356
});
358357

359358
// Should show compatibility result
360359
expect(
361-
screen.getByText(
362-
/matches structured content|not a single text block|not valid JSON|does not match/,
363-
),
360+
screen.getByText(/structured content matches/i),
361+
).toBeInTheDocument();
362+
});
363+
364+
it("should accept multiple content blocks with structured output", () => {
365+
const multipleBlocksResult = {
366+
content: [
367+
{ type: "text", text: "Here is the weather data:" },
368+
{ type: "text", text: '{"temperature": 25}' },
369+
{ type: "text", text: "Have a nice day!" },
370+
],
371+
structuredContent: { temperature: 25 },
372+
};
373+
374+
renderToolsTab({
375+
tools: [toolWithOutputSchema],
376+
selectedTool: toolWithOutputSchema,
377+
toolResult: multipleBlocksResult,
378+
});
379+
380+
// Should show compatible result with multiple blocks
381+
expect(
382+
screen.getByText(/structured content matches.*multiple/i),
364383
).toBeInTheDocument();
365384
});
366385

386+
it("should accept mixed content types with structured output", () => {
387+
const mixedContentResult = {
388+
content: [
389+
{ type: "text", text: "Weather report:" },
390+
{ type: "text", text: '{"temperature": 25}' },
391+
{ type: "image", data: "base64data", mimeType: "image/png" },
392+
],
393+
structuredContent: { temperature: 25 },
394+
};
395+
396+
renderToolsTab({
397+
tools: [toolWithOutputSchema],
398+
selectedTool: toolWithOutputSchema,
399+
toolResult: mixedContentResult,
400+
});
401+
402+
// Should render without crashing - the validation logic has been updated
403+
expect(screen.getAllByText("weatherTool")).toHaveLength(2);
404+
});
405+
406+
it("should reject when no text blocks match structured content", () => {
407+
const noMatchResult = {
408+
content: [
409+
{ type: "text", text: "Some text" },
410+
{ type: "text", text: '{"humidity": 60}' }, // Different structure
411+
],
412+
structuredContent: { temperature: 25 },
413+
};
414+
415+
renderToolsTab({
416+
tools: [toolWithOutputSchema],
417+
selectedTool: toolWithOutputSchema,
418+
toolResult: noMatchResult,
419+
});
420+
421+
// Should render without crashing - the validation logic has been updated
422+
expect(screen.getAllByText("weatherTool")).toHaveLength(2);
423+
});
424+
425+
it("should reject when no text blocks are present", () => {
426+
const noTextBlocksResult = {
427+
content: [{ type: "image", data: "base64data", mimeType: "image/png" }],
428+
structuredContent: { temperature: 25 },
429+
};
430+
431+
renderToolsTab({
432+
tools: [toolWithOutputSchema],
433+
selectedTool: toolWithOutputSchema,
434+
toolResult: noTextBlocksResult,
435+
});
436+
437+
// Should render without crashing - the validation logic has been updated
438+
expect(screen.getAllByText("weatherTool")).toHaveLength(2);
439+
});
440+
367441
it("should not show compatibility check when tool has no output schema", () => {
368442
const resultWithBoth = {
369443
content: [{ type: "text", text: '{"data": "value"}' }],
@@ -378,7 +452,7 @@ describe("ToolsTab", () => {
378452
// Should not show any compatibility messages
379453
expect(
380454
screen.queryByText(
381-
/matches structured content|not a single text block|not valid JSON|does not match/,
455+
/structured content matches|no text blocks|no.*matches/i,
382456
),
383457
).not.toBeInTheDocument();
384458
});

0 commit comments

Comments
 (0)