Added UI to provide additional _meta values
This commit is contained in:
parent
f94f2d8203
commit
1c406eafbd
4 changed files with 188 additions and 19 deletions
|
|
@ -698,7 +698,11 @@ const App = () => {
|
|||
cacheToolOutputSchemas(response.tools);
|
||||
};
|
||||
|
||||
const callTool = async (name: string, params: Record<string, unknown>) => {
|
||||
const callTool = async (
|
||||
name: string,
|
||||
params: Record<string, unknown>,
|
||||
meta?: Record<string, unknown>,
|
||||
) => {
|
||||
lastToolCallOriginTabRef.current = currentTabRef.current;
|
||||
|
||||
try {
|
||||
|
|
@ -710,6 +714,7 @@ const App = () => {
|
|||
arguments: params,
|
||||
_meta: {
|
||||
progressToken: progressTokenRef.current++,
|
||||
...(meta ?? {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -1008,10 +1013,14 @@ const App = () => {
|
|||
setNextToolCursor(undefined);
|
||||
cacheToolOutputSchemas([]);
|
||||
}}
|
||||
callTool={async (name, params) => {
|
||||
callTool={async (
|
||||
name: string,
|
||||
params: Record<string, unknown>,
|
||||
meta?: Record<string, unknown>,
|
||||
) => {
|
||||
clearError("tools");
|
||||
setToolResult(null);
|
||||
await callTool(name, params);
|
||||
await callTool(name, params, meta);
|
||||
}}
|
||||
selectedTool={selectedTool}
|
||||
setSelectedTool={(tool) => {
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ const ToolResults = ({
|
|||
)}
|
||||
{structuredResult._meta && (
|
||||
<div className="mb-4">
|
||||
<h5 className="font-semibold mb-2 text-sm">Meta:</h5>
|
||||
<h5 className="font-semibold mb-2 text-sm">Meta Schema:</h5>
|
||||
<div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg">
|
||||
<JsonView data={structuredResult._meta} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,11 @@ const ToolsTab = ({
|
|||
tools: Tool[];
|
||||
listTools: () => void;
|
||||
clearTools: () => void;
|
||||
callTool: (name: string, params: Record<string, unknown>) => Promise<void>;
|
||||
callTool: (
|
||||
name: string,
|
||||
params: Record<string, unknown>,
|
||||
meta?: Record<string, unknown>,
|
||||
) => Promise<void>;
|
||||
selectedTool: Tool | null;
|
||||
setSelectedTool: (tool: Tool | null) => void;
|
||||
toolResult: CompatibilityCallToolResult | null;
|
||||
|
|
@ -55,6 +59,9 @@ const ToolsTab = ({
|
|||
const [isToolRunning, setIsToolRunning] = useState(false);
|
||||
const [isOutputSchemaExpanded, setIsOutputSchemaExpanded] = useState(false);
|
||||
const [isMetaExpanded, setIsMetaExpanded] = useState(false);
|
||||
const [metaEntries, setMetaEntries] = useState<
|
||||
{ id: string; key: string; value: string }[]
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const params = Object.entries(
|
||||
|
|
@ -221,6 +228,102 @@ const ToolsTab = ({
|
|||
);
|
||||
},
|
||||
)}
|
||||
<div className="pb-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h4 className="text-sm font-semibold">Meta:</h4>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-6 px-2"
|
||||
onClick={() =>
|
||||
setMetaEntries((prev) => [
|
||||
...prev,
|
||||
{
|
||||
id:
|
||||
(
|
||||
globalThis as unknown as {
|
||||
crypto?: { randomUUID?: () => string };
|
||||
}
|
||||
).crypto?.randomUUID?.() ||
|
||||
Math.random().toString(36).slice(2),
|
||||
key: "",
|
||||
value: "",
|
||||
},
|
||||
])
|
||||
}
|
||||
>
|
||||
Add Pair
|
||||
</Button>
|
||||
</div>
|
||||
{metaEntries.length === 0 ? (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
No meta pairs.
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-20">
|
||||
{metaEntries.map((entry, index) => (
|
||||
<div
|
||||
key={entry.id}
|
||||
className="flex items-center gap-2 w-full"
|
||||
>
|
||||
<Label
|
||||
htmlFor={`meta-key-${entry.id}`}
|
||||
className="text-xs shrink-0"
|
||||
>
|
||||
Key
|
||||
</Label>
|
||||
<Input
|
||||
id={`meta-key-${entry.id}`}
|
||||
value={entry.key}
|
||||
placeholder="e.g. requestId"
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setMetaEntries((prev) =>
|
||||
prev.map((m, i) =>
|
||||
i === index ? { ...m, key: value } : m,
|
||||
),
|
||||
);
|
||||
}}
|
||||
className="h-8 flex-1"
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`meta-value-${entry.id}`}
|
||||
className="text-xs shrink-0"
|
||||
>
|
||||
Value
|
||||
</Label>
|
||||
<Input
|
||||
id={`meta-value-${entry.id}`}
|
||||
value={entry.value}
|
||||
placeholder="e.g. 12345"
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setMetaEntries((prev) =>
|
||||
prev.map((m, i) =>
|
||||
i === index ? { ...m, value } : m,
|
||||
),
|
||||
);
|
||||
}}
|
||||
className="h-8 flex-1"
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-8 w-8 p-0 ml-auto shrink-0"
|
||||
onClick={() =>
|
||||
setMetaEntries((prev) =>
|
||||
prev.filter((_, i) => i !== index),
|
||||
)
|
||||
}
|
||||
aria-label={`Remove meta pair ${index + 1}`}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{selectedTool.outputSchema && (
|
||||
<div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
|
|
@ -262,7 +365,7 @@ const ToolsTab = ({
|
|||
selectedTool._meta && (
|
||||
<div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h4 className="text-sm font-semibold">Meta:</h4>
|
||||
<h4 className="text-sm font-semibold">Meta Schema:</h4>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
|
|
@ -295,7 +398,18 @@ const ToolsTab = ({
|
|||
onClick={async () => {
|
||||
try {
|
||||
setIsToolRunning(true);
|
||||
await callTool(selectedTool.name, params);
|
||||
const meta = metaEntries.reduce<Record<string, unknown>>(
|
||||
(acc, { key, value }) => {
|
||||
if (key.trim() !== "") acc[key] = value;
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
await callTool(
|
||||
selectedTool.name,
|
||||
params,
|
||||
Object.keys(meta).length ? meta : undefined,
|
||||
);
|
||||
} finally {
|
||||
setIsToolRunning(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,9 +109,13 @@ describe("ToolsTab", () => {
|
|||
fireEvent.click(submitButton);
|
||||
});
|
||||
|
||||
expect(defaultProps.callTool).toHaveBeenCalledWith(mockTools[1].name, {
|
||||
count: 42,
|
||||
});
|
||||
expect(defaultProps.callTool).toHaveBeenCalledWith(
|
||||
mockTools[1].name,
|
||||
{
|
||||
count: 42,
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it("should allow typing negative numbers", async () => {
|
||||
|
|
@ -130,9 +134,13 @@ describe("ToolsTab", () => {
|
|||
fireEvent.click(submitButton);
|
||||
});
|
||||
|
||||
expect(defaultProps.callTool).toHaveBeenCalledWith(mockTools[0].name, {
|
||||
num: -42,
|
||||
});
|
||||
expect(defaultProps.callTool).toHaveBeenCalledWith(
|
||||
mockTools[0].name,
|
||||
{
|
||||
num: -42,
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
it("should disable button and change text while tool is running", async () => {
|
||||
|
|
@ -580,20 +588,20 @@ describe("ToolsTab", () => {
|
|||
selectedTool: toolWithMeta,
|
||||
});
|
||||
|
||||
expect(screen.getByText("Meta:")).toBeInTheDocument();
|
||||
expect(screen.getByText("Meta Schema:")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("button", { name: /expand/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should toggle meta expansion", () => {
|
||||
it("should toggle meta schema expansion", () => {
|
||||
renderToolsTab({
|
||||
tools: [toolWithMeta],
|
||||
selectedTool: toolWithMeta,
|
||||
});
|
||||
|
||||
// There might be multiple Expand buttons (Output Schema, Meta). We need the one within Meta section
|
||||
const metaHeading = screen.getByText("Meta:");
|
||||
// There might be multiple Expand buttons (Output Schema, Meta Schema). We need the one within Meta Schema section
|
||||
const metaHeading = screen.getByText("Meta Schema:");
|
||||
const metaContainer = metaHeading.closest("div");
|
||||
expect(metaContainer).toBeTruthy();
|
||||
const toggleButton = within(metaContainer as HTMLElement).getByRole(
|
||||
|
|
@ -619,7 +627,45 @@ describe("ToolsTab", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("ToolResults Meta", () => {
|
||||
describe("Meta submission", () => {
|
||||
it("should send meta values when provided", async () => {
|
||||
const callToolMock = jest.fn(async () => {});
|
||||
|
||||
renderToolsTab({ selectedTool: mockTools[0], callTool: callToolMock });
|
||||
|
||||
// Add a meta key/value pair
|
||||
const addPairButton = screen.getByRole("button", { name: /add pair/i });
|
||||
await act(async () => {
|
||||
fireEvent.click(addPairButton);
|
||||
});
|
||||
|
||||
// Fill key and value
|
||||
const keyInputs = screen.getAllByLabelText(/key/i);
|
||||
const valueInputs = screen.getAllByLabelText(/value/i);
|
||||
expect(keyInputs.length).toBeGreaterThan(0);
|
||||
expect(valueInputs.length).toBeGreaterThan(0);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(keyInputs[0], { target: { value: "requestId" } });
|
||||
fireEvent.change(valueInputs[0], { target: { value: "abc123" } });
|
||||
});
|
||||
|
||||
// Run tool
|
||||
const runButton = screen.getByRole("button", { name: /run tool/i });
|
||||
await act(async () => {
|
||||
fireEvent.click(runButton);
|
||||
});
|
||||
|
||||
expect(callToolMock).toHaveBeenCalledTimes(1);
|
||||
expect(callToolMock).toHaveBeenLastCalledWith(
|
||||
mockTools[0].name,
|
||||
expect.any(Object),
|
||||
{ requestId: "abc123" },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ToolResults Meta Schema", () => {
|
||||
it("should display meta information when present in toolResult", () => {
|
||||
const resultWithMeta = {
|
||||
content: [],
|
||||
|
|
@ -632,7 +678,7 @@ describe("ToolsTab", () => {
|
|||
});
|
||||
|
||||
// Only ToolResults meta should be present since selectedTool has no _meta
|
||||
expect(screen.getAllByText("Meta:")).toHaveLength(1);
|
||||
expect(screen.getAllByText("Meta Schema:")).toHaveLength(1);
|
||||
expect(screen.getByText(/info/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/version/i)).toBeInTheDocument();
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue