diff --git a/site/src/pages/AgentsPage/components/ChatElements/ModelSelector.tsx b/site/src/pages/AgentsPage/components/ChatElements/ModelSelector.tsx
index 4b56f0774565b..7a54115d699f0 100644
--- a/site/src/pages/AgentsPage/components/ChatElements/ModelSelector.tsx
+++ b/site/src/pages/AgentsPage/components/ChatElements/ModelSelector.tsx
@@ -68,7 +68,7 @@ interface ModelSelectorProps {
onReasoningEffortChange?: (value: string) => void;
}
-const formatContextLimit = (tokens: number): string => {
+export const formatContextLimit = (tokens: number): string => {
if (tokens >= 1_000_000) {
const m = tokens / 1_000_000;
return `${Number.isInteger(m) ? m : m.toFixed(1)}M`;
diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ListSubagentModelsTool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ListSubagentModelsTool.tsx
new file mode 100644
index 0000000000000..7c78424f6601f
--- /dev/null
+++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ListSubagentModelsTool.tsx
@@ -0,0 +1,140 @@
+import type React from "react";
+import { ScrollArea } from "#/components/ScrollArea/ScrollArea";
+import { ProviderIcon } from "#/pages/AISettingsPage/ProvidersPage/components/ProviderIcon";
+import { formatProviderLabel } from "#/utils/aiProviders";
+import { formatContextLimit } from "../ModelSelector";
+import { ToolCall } from "./ToolCall";
+import { asNumber, asRecord, asString, type ToolStatus } from "./utils";
+
+const ModelRow: React.FC<{ model: unknown }> = ({ model }) => {
+ const rec = asRecord(model);
+ if (!rec) {
+ return null;
+ }
+ const displayName = asString(rec.display_name).trim();
+ const modelName = asString(rec.model).trim();
+ const contextLimit = asNumber(rec.context_limit);
+ const context =
+ contextLimit !== undefined && contextLimit > 0
+ ? formatContextLimit(contextLimit)
+ : "";
+ const efforts = Array.isArray(rec.reasoning_efforts)
+ ? rec.reasoning_efforts.map(asString).filter((effort) => effort.trim())
+ : [];
+ const effortRange =
+ efforts.length > 1
+ ? `${efforts[0]} - ${efforts[efforts.length - 1]}`
+ : (efforts[0] ?? "");
+ const title = displayName || modelName || "Unknown model";
+
+ return (
+
+
+ {title}
+
+ {effortRange ? (
+
+ {effortRange}
+
+ ) : (
+
+ )}
+ {context ? (
+
+ ({context})
+
+ ) : (
+
+ )}
+
+ );
+};
+
+const ListSubagentModelsContent: React.FC<{ models: unknown[] }> = ({
+ models,
+}) => {
+ // Group by provider like the model picker so models from one provider
+ // sit under a single headed section instead of a flat list.
+ const grouped = new Map();
+ for (const model of models) {
+ const rec = asRecord(model);
+ if (!rec) {
+ continue;
+ }
+ const provider = asString(rec.provider).trim() || "unknown";
+ const group = grouped.get(provider);
+ if (group) {
+ group.push(model);
+ continue;
+ }
+ grouped.set(provider, [model]);
+ }
+
+ return (
+
+
+ {Array.from(grouped.entries(), ([provider, providerModels], index) => (
+
0
+ ? "border-0 border-t border-solid border-border-default"
+ : undefined
+ }
+ >
+
+
+
+ {formatProviderLabel(provider)}
+
+
+ {providerModels.map((model, modelIndex) => {
+ const rec = asRecord(model);
+ const configID = rec ? asString(rec.model_config_id).trim() : "";
+ return
;
+ })}
+
+ ))}
+
+
+ );
+};
+
+/**
+ * Collapsed-by-default rendering for `list_subagent_models` tool calls.
+ * Shows "Listed N subagent models" with a chevron; expanding reveals the
+ * models accepted by spawn_agent's model_config_id argument, grouped by
+ * provider like the chat model picker.
+ */
+export const ListSubagentModelsTool: React.FC<{
+ models: unknown[];
+ status: ToolStatus;
+ isError: boolean;
+ errorMessage?: string;
+}> = ({ models, status, isError, errorMessage }) => {
+ const hasContent = models.length > 0;
+ const isRunning = status === "running";
+
+ const label = isRunning
+ ? "Listing subagent models…"
+ : `Listed ${models.length} subagent ${models.length === 1 ? "model" : "models"}`;
+
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.stories.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.stories.tsx
index 3f6fd0b45e46d..89f51604dc3c3 100644
--- a/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.stories.tsx
+++ b/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.stories.tsx
@@ -138,6 +138,20 @@ const allToolShowcaseItems: ToolShowcaseItem[] = [
total: 1,
},
},
+ {
+ name: "list_subagent_models",
+ result: {
+ models: [
+ {
+ model_config_id: "model-1",
+ display_name: "Fast Model",
+ model: "fast-1",
+ provider: "openai",
+ is_default: true,
+ },
+ ],
+ },
+ },
{
name: "read_template",
args: { template_id: "template-1" },
@@ -1324,6 +1338,117 @@ export const ListAgentsError: Story = {
},
};
+// ---------------------------------------------------------------------------
+// ListSubagentModels stories
+// ---------------------------------------------------------------------------
+
+export const ListSubagentModelsCompleted: Story = {
+ args: {
+ name: "list_subagent_models",
+ status: "completed",
+ args: {},
+ result: {
+ models: [
+ {
+ model_config_id: "10000000-0000-0000-0000-000000000001",
+ display_name: "Fast Model",
+ model: "fast-1",
+ provider: "openai",
+ context_limit: 200_000,
+ is_default: true,
+ reasoning_efforts: ["low", "medium", "high"],
+ },
+ {
+ model_config_id: "20000000-0000-0000-0000-000000000002",
+ display_name: "Large Model",
+ model: "large-2",
+ provider: "anthropic",
+ context_limit: 1_000_000,
+ is_default: false,
+ reasoning_efforts: [
+ "none",
+ "minimal",
+ "low",
+ "medium",
+ "high",
+ "xhigh",
+ "max",
+ ],
+ },
+ {
+ model_config_id: "30000000-0000-0000-0000-000000000003",
+ display_name: "",
+ model: "gemini-3.6-flash",
+ provider: "google",
+ context_limit: 262_000,
+ is_default: false,
+ },
+ ],
+ },
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ const header = canvas.getByRole("button", {
+ name: /Listed 3 subagent models/,
+ });
+ expect(header).toBeInTheDocument();
+ await userEvent.click(header);
+ expect(canvas.getByText("OpenAI")).toBeInTheDocument();
+ expect(canvas.getByText("Anthropic")).toBeInTheDocument();
+ expect(canvas.getByText("Google")).toBeInTheDocument();
+ expect(canvas.getByText("Fast Model")).toBeInTheDocument();
+ expect(canvas.getByText("(200K)")).toBeInTheDocument();
+ expect(canvas.getByText("low - high")).toBeInTheDocument();
+ expect(canvas.getByText("Large Model")).toBeInTheDocument();
+ expect(canvas.getByText("(1M)")).toBeInTheDocument();
+ expect(canvas.getByText("none - max")).toBeInTheDocument();
+ expect(canvas.getByText("(262K)")).toBeInTheDocument();
+ expect(canvas.getByText("gemini-3.6-flash")).toBeInTheDocument();
+ },
+};
+
+export const ListSubagentModelsRunning: Story = {
+ args: {
+ name: "list_subagent_models",
+ status: "running",
+ args: {},
+ result: undefined,
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText("Listing subagent models…")).toBeInTheDocument();
+ },
+};
+
+export const ListSubagentModelsEmpty: Story = {
+ args: {
+ name: "list_subagent_models",
+ status: "completed",
+ args: {},
+ result: {
+ models: [],
+ },
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText("Listed 0 subagent models")).toBeInTheDocument();
+ },
+};
+
+export const ListSubagentModelsError: Story = {
+ args: {
+ name: "list_subagent_models",
+ status: "error",
+ isError: true,
+ args: {},
+ result: "list_subagent_models is only available on root chats",
+ },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ expect(canvas.getByText("Listed 0 subagent models")).toBeInTheDocument();
+ },
+};
+
// ---------------------------------------------------------------------------
// ListTemplates stories
// ---------------------------------------------------------------------------
diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx
index 299716a156edb..58e38f35d1373 100644
--- a/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx
+++ b/site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx
@@ -16,6 +16,7 @@ import { DiffFileHeader } from "./DiffFileHeader";
import { EditFilesTool } from "./EditFilesTool";
import { ExecuteTool as ExecuteToolComponent } from "./ExecuteTool";
import { ListAgentsTool } from "./ListAgentsTool";
+import { ListSubagentModelsTool } from "./ListSubagentModelsTool";
import { ListTemplatesTool } from "./ListTemplatesTool";
import { ProcessOutputTool } from "./ProcessOutputTool";
import { ProposePlanTool } from "./ProposePlanTool";
@@ -573,6 +574,24 @@ const ListAgentsRenderer: FC = ({
);
};
+const ListSubagentModelsRenderer: FC = ({
+ status,
+ result,
+ isError,
+}) => {
+ const rec = asRecord(result);
+ const models = rec && Array.isArray(rec.models) ? rec.models : [];
+
+ return (
+
+ );
+};
+
const ReadTemplateRenderer: FC = ({
status,
result,
@@ -1024,6 +1043,7 @@ export const toolRenderers: Record> = {
start_workspace: StartWorkspaceRenderer,
list_templates: ListTemplatesRenderer,
list_agents: ListAgentsRenderer,
+ list_subagent_models: ListSubagentModelsRenderer,
read_template: ReadTemplateRenderer,
read_skill: ReadSkillRenderer,
read_skill_file: ReadSkillFileRenderer,
diff --git a/site/src/pages/AgentsPage/components/ChatElements/tools/ToolIcon.tsx b/site/src/pages/AgentsPage/components/ChatElements/tools/ToolIcon.tsx
index b74f26d14835c..0d5cc6448b32d 100644
--- a/site/src/pages/AgentsPage/components/ChatElements/tools/ToolIcon.tsx
+++ b/site/src/pages/AgentsPage/components/ChatElements/tools/ToolIcon.tsx
@@ -38,6 +38,7 @@ export const toolIcons: Partial> = {
start_workspace: PowerIcon,
chat_summarized: BotIcon,
list_agents: BotIcon,
+ list_subagent_models: BotIcon,
thinking: LightbulbIcon,
propose_plan: RouteIcon,
ask_user_question: BadgeQuestionMarkIcon,