feat(sampling): replace InlineSamplingRequest responseText with draftResult (#1239) (#1250)

Closes the Phase 5 audit partial: the inline card's prop interface now
matches SamplingRequestPanel's, accepting an optional CreateMessageResult
draft instead of a flat scalar. Lets future hook-layer wiring share a
single draft store between the inline card and the full panel.

Rendering:
- No draft → existing layout (request preview, model hints, action
  buttons), with the readonly placeholder textarea removed.
- Text draft → 2-line preview of draftResult.content.text (lineClamp 2).
- Image / audio draft → "[Image content]" / "[Audio content]" label,
  matching the ContentViewer fallback convention.

Updates the three consumer stories that threaded responseText="" through
(PendingClientRequests, ToolsScreen) — no callback signatures changed.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cliff Hall 2026-04-27 18:29:58 -04:00 • committed by GitHub
commit c04f96ffb8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 56 additions and 28 deletions

View file

@ -1,5 +1,8 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import type { CreateMessageRequestParams } from "@modelcontextprotocol/sdk/types.js";
import type {
CreateMessageRequestParams,
CreateMessageResult,
} from "@modelcontextprotocol/sdk/types.js";
import { fn } from "storybook/test";
import { InlineSamplingRequest } from "./InlineSamplingRequest";
@ -16,13 +19,31 @@ const defaultRequest: CreateMessageRequestParams = {
maxTokens: 1024,
};
const textDraft: CreateMessageResult = {
role: "assistant",
model: "claude-sonnet-4-20250514",
content: {
type: "text",
text: "The code looks good overall. Consider extracting the repeated logic into a helper function and adding type annotations to the public API.",
},
};
const imageDraft: CreateMessageResult = {
role: "assistant",
model: "claude-sonnet-4-20250514",
content: {
type: "image",
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
mimeType: "image/png",
},
};
const meta: Meta<typeof InlineSamplingRequest> = {
title: "Groups/InlineSamplingRequest",
component: InlineSamplingRequest,
args: {
request: defaultRequest,
queuePosition: "1 of 1",
responseText: "",
onAutoRespond: fn(),
onEditAndSend: fn(),
onReject: fn(),
@ -49,10 +70,15 @@ export const WithModelHints: Story = {
},
};
export const PrefilledResponse: Story = {
export const WithTextDraft: Story = {
args: {
responseText:
"The code looks good overall. Consider extracting the repeated logic into a helper function.",
draftResult: textDraft,
},
};
export const WithNonTextDraft: Story = {
args: {
draftResult: imageDraft,
},
};

View file

@ -1,18 +1,13 @@
import {
Badge,
Button,
Group,
Paper,
Stack,
Text,
Textarea,
} from "@mantine/core";
import type { CreateMessageRequestParams } from "@modelcontextprotocol/sdk/types.js";
import { Badge, Button, Group, Paper, Stack, Text } from "@mantine/core";
import type {
CreateMessageRequestParams,
CreateMessageResult,
} from "@modelcontextprotocol/sdk/types.js";
export interface InlineSamplingRequestProps {
request: CreateMessageRequestParams;
queuePosition: string;
responseText: string;
draftResult?: CreateMessageResult;
onAutoRespond: () => void;
onEditAndSend: () => void;
onReject: () => void;
@ -35,6 +30,11 @@ const PreviewText = Text.withProps({
lineClamp: 2,
});
const DraftPreview = Text.withProps({
size: "sm",
lineClamp: 2,
});
const DetailButton = Button.withProps({
variant: "subtle",
size: "xs",
@ -67,6 +67,17 @@ function extractPreview(request: CreateMessageRequestParams): string {
return content.type === "text" ? content.text : `[${content.type}]`;
}
function extractDraftPreview(draft: CreateMessageResult): string {
switch (draft.content.type) {
case "text":
return draft.content.text;
case "image":
return "[Image content]";
case "audio":
return "[Audio content]";
}
}
function extractModelHints(
request: CreateMessageRequestParams,
): string[] | undefined {
@ -82,7 +93,7 @@ function formatModelHints(hints: string[]): string {
export function InlineSamplingRequest({
request,
queuePosition,
responseText,
draftResult,
onAutoRespond,
onEditAndSend,
onReject,
@ -90,6 +101,7 @@ export function InlineSamplingRequest({
}: InlineSamplingRequestProps) {
const modelHints = extractModelHints(request);
const messagePreview = extractPreview(request);
const draftPreview = draftResult ? extractDraftPreview(draftResult) : null;
return (
<RequestContainer>
@ -105,14 +117,7 @@ export function InlineSamplingRequest({
<DetailButton onClick={onViewDetails}>View Details</DetailButton>
<Textarea
size="sm"
value={responseText}
placeholder="Response..."
autosize
minRows={2}
readOnly
/>
{draftPreview !== null && <DraftPreview>{draftPreview}</DraftPreview>}
<ActionsRow>
<CompactButton onClick={onAutoRespond}>Auto-respond</CompactButton>

View file

@ -57,7 +57,6 @@ export const SingleSampling: Story = {
maxTokens: 1024,
}}
queuePosition="1 of 1"
responseText=""
onAutoRespond={fn()}
onEditAndSend={fn()}
onReject={fn()}
@ -102,7 +101,6 @@ export const MultipleMixed: Story = {
maxTokens: 1024,
}}
queuePosition="1 of 2"
responseText=""
onAutoRespond={fn()}
onEditAndSend={fn()}
onReject={fn()}

View file

@ -287,7 +287,6 @@ export const WithPendingRequests: Story = {
maxTokens: 1024,
}}
queuePosition="1 of 2"
responseText=""
onAutoRespond={fn()}
onEditAndSend={fn()}
onReject={fn()}