mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
feat(platform): add generic ask_question copilot tool (#12647)
### Why / What / How **Why:** The copilot can ask clarifying questions in plain text, but that text gets collapsed into hidden "reasoning" UI when the LLM also calls tools in the same turn. This makes clarification questions invisible to users. The existing `ClarificationNeededResponse` model and `ClarificationQuestionsCard` UI component were built for this purpose but had no tool wiring them up. **What:** Adds a generic `ask_question` tool that produces a visible, interactive clarification card instead of collapsible plain text. Unlike the agent-generation-specific `clarify_agent_request` proposed in #12601, this tool is workflow-agnostic — usable for agent building, editing, troubleshooting, or any flow needing user input. **How:** - Backend: New `AskQuestionTool` reuses existing `ClarificationNeededResponse` model. Registered in `TOOL_REGISTRY` and `ToolName` permissions. - Frontend: New `AskQuestion/` renderer reuses `ClarificationQuestionsCard` from CreateAgent. Registered in `CUSTOM_TOOL_TYPES` (prevents collapse into reasoning) and `MessagePartRenderer`. - Guide: `agent_generation_guide.md` updated to reference `ask_question` for the clarification step. ### Changes 🏗️ - **`copilot/tools/ask_question.py`** — New generic tool: takes `question`, optional `options[]` and `keyword`, returns `ClarificationNeededResponse` - **`copilot/tools/__init__.py`** — Register `ask_question` in `TOOL_REGISTRY` - **`copilot/permissions.py`** — Add `ask_question` to `ToolName` literal - **`copilot/sdk/agent_generation_guide.md`** — Reference `ask_question` tool in clarification step - **`ChatMessagesContainer/helpers.ts`** — Add `tool-ask_question` to `CUSTOM_TOOL_TYPES` - **`MessagePartRenderer.tsx`** — Add switch case for `tool-ask_question` - **`AskQuestion/AskQuestion.tsx`** — Renderer reusing `ClarificationQuestionsCard` - **`AskQuestion/helpers.ts`** — Output parsing and animation text ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Backend format + pyright pass - [x] Frontend lint + types pass - [x] Pre-commit hooks pass - [ ] Manual test: copilot uses `ask_question` and card renders visibly (not collapsed)
This commit is contained in:
@@ -66,6 +66,7 @@ from pydantic import BaseModel, PrivateAttr
|
||||
ToolName = Literal[
|
||||
# Platform tools (must match keys in TOOL_REGISTRY)
|
||||
"add_understanding",
|
||||
"ask_question",
|
||||
"bash_exec",
|
||||
"browser_act",
|
||||
"browser_navigate",
|
||||
|
||||
@@ -6,16 +6,23 @@ from pathlib import Path
|
||||
class TestAgentGenerationGuideContainsClarifySection:
|
||||
"""The agent generation guide must include the clarification section."""
|
||||
|
||||
def test_guide_includes_clarify_before_building(self):
|
||||
def test_guide_includes_clarify_section(self):
|
||||
guide_path = Path(__file__).parent / "sdk" / "agent_generation_guide.md"
|
||||
content = guide_path.read_text(encoding="utf-8")
|
||||
assert "Clarifying Before Building" in content
|
||||
assert "Before or During Building" in content
|
||||
|
||||
def test_guide_mentions_find_block_for_clarification(self):
|
||||
guide_path = Path(__file__).parent / "sdk" / "agent_generation_guide.md"
|
||||
content = guide_path.read_text(encoding="utf-8")
|
||||
# find_block must appear in the clarification section (before the workflow)
|
||||
clarify_section = content.split("Clarifying Before Building")[1].split(
|
||||
clarify_section = content.split("Before or During Building")[1].split(
|
||||
"### Workflow"
|
||||
)[0]
|
||||
assert "find_block" in clarify_section
|
||||
|
||||
def test_guide_mentions_ask_question_tool(self):
|
||||
guide_path = Path(__file__).parent / "sdk" / "agent_generation_guide.md"
|
||||
content = guide_path.read_text(encoding="utf-8")
|
||||
clarify_section = content.split("Before or During Building")[1].split(
|
||||
"### Workflow"
|
||||
)[0]
|
||||
assert "ask_question" in clarify_section
|
||||
|
||||
@@ -3,17 +3,25 @@
|
||||
You can create, edit, and customize agents directly. You ARE the brain —
|
||||
generate the agent JSON yourself using block schemas, then validate and save.
|
||||
|
||||
### Clarifying Before Building
|
||||
### Clarifying — Before or During Building
|
||||
|
||||
Before starting the workflow below, check whether the user's goal is
|
||||
**ambiguous** — missing the output format, delivery channel, data source,
|
||||
or trigger. If so:
|
||||
1. Call `find_block` with a query targeting the ambiguous dimension to
|
||||
discover what the platform actually supports.
|
||||
2. Ask the user **one concrete question** grounded in the discovered
|
||||
Use `ask_question` whenever the user's intent is ambiguous — whether
|
||||
that's before starting or midway through the workflow. Common moments:
|
||||
|
||||
- **Before building**: output format, delivery channel, data source, or
|
||||
trigger is unspecified.
|
||||
- **During block discovery**: multiple blocks could fit and the user
|
||||
should choose.
|
||||
- **During JSON generation**: a wiring decision depends on user
|
||||
preference.
|
||||
|
||||
Steps:
|
||||
1. Call `find_block` (or another discovery tool) to learn what the
|
||||
platform actually supports for the ambiguous dimension.
|
||||
2. Call `ask_question` with a concrete question listing the discovered
|
||||
options (e.g. "The platform supports Gmail, Slack, and Google Docs —
|
||||
which should the agent use for delivery?").
|
||||
3. **Wait for the user's answer** before proceeding.
|
||||
3. **Wait for the user's answer** before continuing.
|
||||
|
||||
**Skip this** when the goal already specifies all dimensions (e.g.
|
||||
"scrape prices from Amazon and email me daily").
|
||||
|
||||
@@ -10,6 +10,7 @@ from backend.copilot.tracking import track_tool_called
|
||||
from .add_understanding import AddUnderstandingTool
|
||||
from .agent_browser import BrowserActTool, BrowserNavigateTool, BrowserScreenshotTool
|
||||
from .agent_output import AgentOutputTool
|
||||
from .ask_question import AskQuestionTool
|
||||
from .base import BaseTool
|
||||
from .bash_exec import BashExecTool
|
||||
from .connect_integration import ConnectIntegrationTool
|
||||
@@ -55,6 +56,7 @@ logger = logging.getLogger(__name__)
|
||||
# Single source of truth for all tools
|
||||
TOOL_REGISTRY: dict[str, BaseTool] = {
|
||||
"add_understanding": AddUnderstandingTool(),
|
||||
"ask_question": AskQuestionTool(),
|
||||
"create_agent": CreateAgentTool(),
|
||||
"customize_agent": CustomizeAgentTool(),
|
||||
"edit_agent": EditAgentTool(),
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
"""AskQuestionTool - Ask the user a clarifying question before proceeding."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from backend.copilot.model import ChatSession
|
||||
|
||||
from .base import BaseTool
|
||||
from .models import ClarificationNeededResponse, ClarifyingQuestion, ToolResponseBase
|
||||
|
||||
|
||||
class AskQuestionTool(BaseTool):
|
||||
"""Ask the user a clarifying question and wait for their answer.
|
||||
|
||||
Use this tool when the user's request is ambiguous and you need more
|
||||
information before proceeding. Call find_block or other discovery tools
|
||||
first to ground your question in real platform options, then call this
|
||||
tool with a concrete question listing those options.
|
||||
"""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "ask_question"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return (
|
||||
"Ask the user a clarifying question. Use when the request is "
|
||||
"ambiguous and you need to confirm intent, choose between options, "
|
||||
"or gather missing details before proceeding."
|
||||
)
|
||||
|
||||
@property
|
||||
def parameters(self) -> dict[str, Any]:
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"question": {
|
||||
"type": "string",
|
||||
"description": (
|
||||
"The concrete question to ask the user. Should list "
|
||||
"real options when applicable."
|
||||
),
|
||||
},
|
||||
"options": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": (
|
||||
"Options for the user to choose from "
|
||||
"(e.g. ['Email', 'Slack', 'Google Docs'])."
|
||||
),
|
||||
},
|
||||
"keyword": {
|
||||
"type": "string",
|
||||
"description": "Short label identifying what the question is about.",
|
||||
},
|
||||
},
|
||||
"required": ["question"],
|
||||
}
|
||||
|
||||
@property
|
||||
def requires_auth(self) -> bool:
|
||||
return False
|
||||
|
||||
async def _execute(
|
||||
self,
|
||||
user_id: str | None,
|
||||
session: ChatSession,
|
||||
**kwargs: Any,
|
||||
) -> ToolResponseBase:
|
||||
del user_id # unused; required by BaseTool contract
|
||||
question_raw = kwargs.get("question")
|
||||
if not isinstance(question_raw, str) or not question_raw.strip():
|
||||
raise ValueError("ask_question requires a non-empty 'question' string")
|
||||
question = question_raw.strip()
|
||||
raw_options = kwargs.get("options", [])
|
||||
if not isinstance(raw_options, list):
|
||||
raw_options = []
|
||||
options: list[str] = [str(o) for o in raw_options if o]
|
||||
raw_keyword = kwargs.get("keyword", "")
|
||||
keyword: str = str(raw_keyword) if raw_keyword else ""
|
||||
session_id = session.session_id if session else None
|
||||
|
||||
example = ", ".join(options) if options else None
|
||||
clarifying_question = ClarifyingQuestion(
|
||||
question=question,
|
||||
keyword=keyword,
|
||||
example=example,
|
||||
)
|
||||
return ClarificationNeededResponse(
|
||||
message=question,
|
||||
session_id=session_id,
|
||||
questions=[clarifying_question],
|
||||
)
|
||||
@@ -0,0 +1,99 @@
|
||||
"""Tests for AskQuestionTool."""
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.copilot.model import ChatSession
|
||||
from backend.copilot.tools.ask_question import AskQuestionTool
|
||||
from backend.copilot.tools.models import ClarificationNeededResponse
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def tool() -> AskQuestionTool:
|
||||
return AskQuestionTool()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def session() -> ChatSession:
|
||||
return ChatSession.new(user_id="test-user", dry_run=False)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_execute_with_options(tool: AskQuestionTool, session: ChatSession):
|
||||
result = await tool._execute(
|
||||
user_id=None,
|
||||
session=session,
|
||||
question="Which channel?",
|
||||
options=["Email", "Slack", "Google Docs"],
|
||||
keyword="channel",
|
||||
)
|
||||
|
||||
assert isinstance(result, ClarificationNeededResponse)
|
||||
assert result.message == "Which channel?"
|
||||
assert result.session_id == session.session_id
|
||||
assert len(result.questions) == 1
|
||||
|
||||
q = result.questions[0]
|
||||
assert q.question == "Which channel?"
|
||||
assert q.keyword == "channel"
|
||||
assert q.example == "Email, Slack, Google Docs"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_execute_without_options(tool: AskQuestionTool, session: ChatSession):
|
||||
result = await tool._execute(
|
||||
user_id=None,
|
||||
session=session,
|
||||
question="What format do you want?",
|
||||
)
|
||||
|
||||
assert isinstance(result, ClarificationNeededResponse)
|
||||
assert result.message == "What format do you want?"
|
||||
assert len(result.questions) == 1
|
||||
|
||||
q = result.questions[0]
|
||||
assert q.question == "What format do you want?"
|
||||
assert q.keyword == ""
|
||||
assert q.example is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_execute_with_keyword_only(tool: AskQuestionTool, session: ChatSession):
|
||||
result = await tool._execute(
|
||||
user_id=None,
|
||||
session=session,
|
||||
question="How often should it run?",
|
||||
keyword="trigger",
|
||||
)
|
||||
|
||||
assert isinstance(result, ClarificationNeededResponse)
|
||||
q = result.questions[0]
|
||||
assert q.keyword == "trigger"
|
||||
assert q.example is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_execute_rejects_empty_question(
|
||||
tool: AskQuestionTool, session: ChatSession
|
||||
):
|
||||
with pytest.raises(ValueError, match="non-empty"):
|
||||
await tool._execute(user_id=None, session=session, question="")
|
||||
|
||||
with pytest.raises(ValueError, match="non-empty"):
|
||||
await tool._execute(user_id=None, session=session, question=" ")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_execute_coerces_invalid_options(
|
||||
tool: AskQuestionTool, session: ChatSession
|
||||
):
|
||||
"""LLM may send options as a string instead of a list; should not crash."""
|
||||
result = await tool._execute(
|
||||
user_id=None,
|
||||
session=session,
|
||||
question="Pick one",
|
||||
options="not-a-list", # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
assert isinstance(result, ClarificationNeededResponse)
|
||||
q = result.questions[0]
|
||||
assert q.example is None
|
||||
@@ -3,6 +3,7 @@ import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
||||
import { ExclamationMarkIcon } from "@phosphor-icons/react";
|
||||
import { ToolUIPart, UIDataTypes, UIMessage, UITools } from "ai";
|
||||
import { useState } from "react";
|
||||
import { AskQuestionTool } from "../../../tools/AskQuestion/AskQuestion";
|
||||
import { ConnectIntegrationTool } from "../../../tools/ConnectIntegrationTool/ConnectIntegrationTool";
|
||||
import { CreateAgentTool } from "../../../tools/CreateAgent/CreateAgent";
|
||||
import { EditAgentTool } from "../../../tools/EditAgent/EditAgent";
|
||||
@@ -129,6 +130,8 @@ export function MessagePartRenderer({
|
||||
</MessageResponse>
|
||||
);
|
||||
}
|
||||
case "tool-ask_question":
|
||||
return <AskQuestionTool key={key} part={part as ToolUIPart} />;
|
||||
case "tool-find_block":
|
||||
return <FindBlocksTool key={key} part={part as ToolUIPart} />;
|
||||
case "tool-find_agent":
|
||||
|
||||
@@ -13,6 +13,7 @@ export type RenderSegment =
|
||||
| { kind: "collapsed-group"; parts: ToolUIPart[] };
|
||||
|
||||
const CUSTOM_TOOL_TYPES = new Set([
|
||||
"tool-ask_question",
|
||||
"tool-find_block",
|
||||
"tool-find_agent",
|
||||
"tool-find_library_agent",
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Text } from "@/components/atoms/Text/Text";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ChatTeardropDotsIcon, CheckCircleIcon } from "@phosphor-icons/react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { ClarifyingQuestion } from "../../clarifying-questions";
|
||||
import type { ClarifyingQuestion } from "../../tools/clarifying-questions";
|
||||
|
||||
interface Props {
|
||||
questions: ClarifyingQuestion[];
|
||||
@@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
import { ChatTeardropDotsIcon, WarningCircleIcon } from "@phosphor-icons/react";
|
||||
import type { ToolUIPart } from "ai";
|
||||
import { ClarificationQuestionsCard } from "../../components/ClarificationQuestionsCard/ClarificationQuestionsCard";
|
||||
import { useCopilotChatActions } from "../../components/CopilotChatActionsProvider/useCopilotChatActions";
|
||||
import { MorphingTextAnimation } from "../../components/MorphingTextAnimation/MorphingTextAnimation";
|
||||
import { normalizeClarifyingQuestions } from "../clarifying-questions";
|
||||
import {
|
||||
getAnimationText,
|
||||
getAskQuestionOutput,
|
||||
isClarificationOutput,
|
||||
isErrorOutput,
|
||||
} from "./helpers";
|
||||
|
||||
interface Props {
|
||||
part: ToolUIPart;
|
||||
}
|
||||
|
||||
export function AskQuestionTool({ part }: Props) {
|
||||
const text = getAnimationText(part);
|
||||
const { onSend } = useCopilotChatActions();
|
||||
|
||||
const isStreaming =
|
||||
part.state === "input-streaming" || part.state === "input-available";
|
||||
const isError = part.state === "output-error";
|
||||
|
||||
const output = getAskQuestionOutput(part);
|
||||
|
||||
function handleAnswers(answers: Record<string, string>) {
|
||||
if (!output || !isClarificationOutput(output)) return;
|
||||
const questions = normalizeClarifyingQuestions(output.questions ?? []);
|
||||
const message = questions
|
||||
.map((q) => {
|
||||
const answer = answers[q.keyword] || "";
|
||||
return `> ${q.question}\n\n${answer}`;
|
||||
})
|
||||
.join("\n\n");
|
||||
onSend(`**Here are my answers:**\n\n${message}\n\nPlease proceed.`);
|
||||
}
|
||||
|
||||
if (output && isClarificationOutput(output)) {
|
||||
return (
|
||||
<ClarificationQuestionsCard
|
||||
questions={normalizeClarifyingQuestions(output.questions ?? [])}
|
||||
message={output.message}
|
||||
sessionId={output.session_id}
|
||||
onSubmitAnswers={handleAnswers}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 py-2 text-sm text-muted-foreground">
|
||||
{isError || (output && isErrorOutput(output)) ? (
|
||||
<WarningCircleIcon size={16} className="text-red-500" />
|
||||
) : isStreaming ? (
|
||||
<ChatTeardropDotsIcon size={16} className="animate-pulse" />
|
||||
) : (
|
||||
<ChatTeardropDotsIcon size={16} />
|
||||
)}
|
||||
<MorphingTextAnimation
|
||||
text={text}
|
||||
className={isError ? "text-red-500" : undefined}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { ResponseType } from "@/app/api/__generated__/models/responseType";
|
||||
import type { ToolUIPart } from "ai";
|
||||
|
||||
interface ClarifyingQuestionPayload {
|
||||
question: string;
|
||||
keyword: string;
|
||||
example?: string;
|
||||
}
|
||||
|
||||
export interface AskQuestionOutput {
|
||||
type: string;
|
||||
message: string;
|
||||
questions: ClarifyingQuestionPayload[];
|
||||
session_id?: string;
|
||||
}
|
||||
|
||||
interface ErrorOutput {
|
||||
type: "error";
|
||||
message: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export type AskQuestionToolOutput = AskQuestionOutput | ErrorOutput;
|
||||
|
||||
function parseOutput(output: unknown): AskQuestionToolOutput | null {
|
||||
if (!output) return null;
|
||||
if (typeof output === "string") {
|
||||
try {
|
||||
return parseOutput(JSON.parse(output) as unknown);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (typeof output === "object" && output !== null) {
|
||||
const obj = output as Record<string, unknown>;
|
||||
if (
|
||||
obj.type === ResponseType.agent_builder_clarification_needed ||
|
||||
"questions" in obj
|
||||
) {
|
||||
return obj as unknown as AskQuestionOutput;
|
||||
}
|
||||
if (obj.type === "error" || "error" in obj) {
|
||||
return obj as unknown as ErrorOutput;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getAskQuestionOutput(
|
||||
part: ToolUIPart,
|
||||
): AskQuestionToolOutput | null {
|
||||
return parseOutput(part.output);
|
||||
}
|
||||
|
||||
export function isClarificationOutput(
|
||||
output: AskQuestionToolOutput,
|
||||
): output is AskQuestionOutput {
|
||||
return (
|
||||
output.type === ResponseType.agent_builder_clarification_needed ||
|
||||
"questions" in output
|
||||
);
|
||||
}
|
||||
|
||||
export function isErrorOutput(
|
||||
output: AskQuestionToolOutput,
|
||||
): output is ErrorOutput {
|
||||
return output.type === "error" || "error" in output;
|
||||
}
|
||||
|
||||
export function getAnimationText(part: ToolUIPart): string {
|
||||
switch (part.state) {
|
||||
case "input-streaming":
|
||||
case "input-available":
|
||||
return "Asking question...";
|
||||
case "output-available": {
|
||||
const output = parseOutput(part.output);
|
||||
if (output && isClarificationOutput(output)) return "Needs your input";
|
||||
if (output && isErrorOutput(output)) return "Failed to ask question";
|
||||
return "Asking question...";
|
||||
}
|
||||
case "output-error":
|
||||
return "Failed to ask question";
|
||||
default:
|
||||
return "Asking question...";
|
||||
}
|
||||
}
|
||||
@@ -13,13 +13,8 @@ import {
|
||||
ContentMessage,
|
||||
} from "../../components/ToolAccordion/AccordionContent";
|
||||
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
|
||||
import { ClarificationQuestionsCard } from "./components/ClarificationQuestionsCard";
|
||||
import { MiniGame } from "../../components/MiniGame/MiniGame";
|
||||
import { SuggestedGoalCard } from "./components/SuggestedGoalCard";
|
||||
import {
|
||||
buildClarificationAnswersMessage,
|
||||
normalizeClarifyingQuestions,
|
||||
} from "../clarifying-questions";
|
||||
import {
|
||||
AccordionIcon,
|
||||
formatMaybeJson,
|
||||
@@ -27,7 +22,6 @@ import {
|
||||
getCreateAgentToolOutput,
|
||||
isAgentPreviewOutput,
|
||||
isAgentSavedOutput,
|
||||
isClarificationNeededOutput,
|
||||
isErrorOutput,
|
||||
isSuggestedGoalOutput,
|
||||
ToolIcon,
|
||||
@@ -66,15 +60,6 @@ function getAccordionMeta(output: CreateAgentToolOutput | null) {
|
||||
description: `${output.node_count} block${output.node_count === 1 ? "" : "s"}`,
|
||||
};
|
||||
}
|
||||
if (isClarificationNeededOutput(output)) {
|
||||
const questions = output.questions ?? [];
|
||||
return {
|
||||
icon,
|
||||
title: "Needs clarification",
|
||||
description: `${questions.length} question${questions.length === 1 ? "" : "s"}`,
|
||||
expanded: true,
|
||||
};
|
||||
}
|
||||
if (isSuggestedGoalOutput(output)) {
|
||||
return {
|
||||
icon,
|
||||
@@ -107,15 +92,6 @@ export function CreateAgentTool({ part }: Props) {
|
||||
onSend(`Please create an agent with this goal: ${goal}`);
|
||||
}
|
||||
|
||||
function handleClarificationAnswers(answers: Record<string, string>) {
|
||||
const questions =
|
||||
output && isClarificationNeededOutput(output)
|
||||
? (output.questions ?? [])
|
||||
: [];
|
||||
|
||||
onSend(buildClarificationAnswersMessage(answers, questions, "create"));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="py-2">
|
||||
{isOperating && (
|
||||
@@ -148,44 +124,42 @@ export function CreateAgentTool({ part }: Props) {
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasExpandableContent &&
|
||||
!(output && isClarificationNeededOutput(output)) &&
|
||||
!(output && isAgentSavedOutput(output)) && (
|
||||
<ToolAccordion {...getAccordionMeta(output)}>
|
||||
{isOperating && (
|
||||
<ContentGrid>
|
||||
<MiniGame />
|
||||
<ContentHint>
|
||||
This could take a few minutes — play while you wait!
|
||||
</ContentHint>
|
||||
</ContentGrid>
|
||||
)}
|
||||
{hasExpandableContent && !(output && isAgentSavedOutput(output)) && (
|
||||
<ToolAccordion {...getAccordionMeta(output)}>
|
||||
{isOperating && (
|
||||
<ContentGrid>
|
||||
<MiniGame />
|
||||
<ContentHint>
|
||||
This could take a few minutes — play while you wait!
|
||||
</ContentHint>
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{output && isAgentPreviewOutput(output) && (
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
{output.description?.trim() && (
|
||||
<ContentCardDescription>
|
||||
{output.description}
|
||||
</ContentCardDescription>
|
||||
)}
|
||||
<ContentCodeBlock>
|
||||
{truncateText(formatMaybeJson(output.agent_json), 1600)}
|
||||
</ContentCodeBlock>
|
||||
</ContentGrid>
|
||||
)}
|
||||
{output && isAgentPreviewOutput(output) && (
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
{output.description?.trim() && (
|
||||
<ContentCardDescription>
|
||||
{output.description}
|
||||
</ContentCardDescription>
|
||||
)}
|
||||
<ContentCodeBlock>
|
||||
{truncateText(formatMaybeJson(output.agent_json), 1600)}
|
||||
</ContentCodeBlock>
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{output && isSuggestedGoalOutput(output) && (
|
||||
<SuggestedGoalCard
|
||||
message={output.message}
|
||||
suggestedGoal={output.suggested_goal}
|
||||
reason={output.reason}
|
||||
goalType={output.goal_type ?? "vague"}
|
||||
onUseSuggestedGoal={handleUseSuggestedGoal}
|
||||
/>
|
||||
)}
|
||||
</ToolAccordion>
|
||||
)}
|
||||
{output && isSuggestedGoalOutput(output) && (
|
||||
<SuggestedGoalCard
|
||||
message={output.message}
|
||||
suggestedGoal={output.suggested_goal}
|
||||
reason={output.reason}
|
||||
goalType={output.goal_type ?? "vague"}
|
||||
onUseSuggestedGoal={handleUseSuggestedGoal}
|
||||
/>
|
||||
)}
|
||||
</ToolAccordion>
|
||||
)}
|
||||
|
||||
{output && isAgentSavedOutput(output) && (
|
||||
<AgentSavedCard
|
||||
@@ -195,14 +169,6 @@ export function CreateAgentTool({ part }: Props) {
|
||||
agentPageLink={output.agent_page_link}
|
||||
/>
|
||||
)}
|
||||
|
||||
{output && isClarificationNeededOutput(output) && (
|
||||
<ClarificationQuestionsCard
|
||||
questions={normalizeClarifyingQuestions(output.questions ?? [])}
|
||||
message={output.message}
|
||||
onSubmitAnswers={handleClarificationAnswers}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { AgentPreviewResponse } from "@/app/api/__generated__/models/agentPreviewResponse";
|
||||
import type { AgentSavedResponse } from "@/app/api/__generated__/models/agentSavedResponse";
|
||||
import type { ClarificationNeededResponse } from "@/app/api/__generated__/models/clarificationNeededResponse";
|
||||
import type { ErrorResponse } from "@/app/api/__generated__/models/errorResponse";
|
||||
import { ResponseType } from "@/app/api/__generated__/models/responseType";
|
||||
import type { SuggestedGoalResponse } from "@/app/api/__generated__/models/suggestedGoalResponse";
|
||||
@@ -15,7 +14,6 @@ import { ScaleLoader } from "../../components/ScaleLoader/ScaleLoader";
|
||||
export type CreateAgentToolOutput =
|
||||
| AgentPreviewResponse
|
||||
| AgentSavedResponse
|
||||
| ClarificationNeededResponse
|
||||
| SuggestedGoalResponse
|
||||
| ErrorResponse;
|
||||
|
||||
@@ -35,7 +33,6 @@ function parseOutput(output: unknown): CreateAgentToolOutput | null {
|
||||
if (
|
||||
type === ResponseType.agent_builder_preview ||
|
||||
type === ResponseType.agent_builder_saved ||
|
||||
type === ResponseType.agent_builder_clarification_needed ||
|
||||
type === ResponseType.suggested_goal ||
|
||||
type === ResponseType.error
|
||||
) {
|
||||
@@ -45,7 +42,6 @@ function parseOutput(output: unknown): CreateAgentToolOutput | null {
|
||||
return output as AgentPreviewResponse;
|
||||
if ("agent_id" in output && "library_agent_id" in output)
|
||||
return output as AgentSavedResponse;
|
||||
if ("questions" in output) return output as ClarificationNeededResponse;
|
||||
if ("suggested_goal" in output) return output as SuggestedGoalResponse;
|
||||
if ("error" in output || "details" in output)
|
||||
return output as ErrorResponse;
|
||||
@@ -77,15 +73,6 @@ export function isAgentSavedOutput(
|
||||
);
|
||||
}
|
||||
|
||||
export function isClarificationNeededOutput(
|
||||
output: CreateAgentToolOutput,
|
||||
): output is ClarificationNeededResponse {
|
||||
return (
|
||||
output.type === ResponseType.agent_builder_clarification_needed ||
|
||||
"questions" in output
|
||||
);
|
||||
}
|
||||
|
||||
export function isSuggestedGoalOutput(
|
||||
output: CreateAgentToolOutput,
|
||||
): output is SuggestedGoalResponse {
|
||||
@@ -114,7 +101,6 @@ export function getAnimationText(part: {
|
||||
if (!output) return "Creating a new agent";
|
||||
if (isAgentSavedOutput(output)) return `Saved ${output.agent_name}`;
|
||||
if (isAgentPreviewOutput(output)) return `Preview "${output.agent_name}"`;
|
||||
if (isClarificationNeededOutput(output)) return "Needs clarification";
|
||||
if (isSuggestedGoalOutput(output)) return "Goal needs refinement";
|
||||
return "Error creating agent";
|
||||
}
|
||||
|
||||
@@ -14,11 +14,6 @@ import {
|
||||
ContentMessage,
|
||||
} from "../../components/ToolAccordion/AccordionContent";
|
||||
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
|
||||
import {
|
||||
buildClarificationAnswersMessage,
|
||||
normalizeClarifyingQuestions,
|
||||
} from "../clarifying-questions";
|
||||
import { ClarificationQuestionsCard } from "../CreateAgent/components/ClarificationQuestionsCard";
|
||||
import {
|
||||
AccordionIcon,
|
||||
formatMaybeJson,
|
||||
@@ -26,7 +21,6 @@ import {
|
||||
getEditAgentToolOutput,
|
||||
isAgentPreviewOutput,
|
||||
isAgentSavedOutput,
|
||||
isClarificationNeededOutput,
|
||||
isErrorOutput,
|
||||
ToolIcon,
|
||||
truncateText,
|
||||
@@ -69,14 +63,6 @@ function getAccordionMeta(output: EditAgentToolOutput | null): {
|
||||
description: `${output.node_count} block${output.node_count === 1 ? "" : "s"}`,
|
||||
};
|
||||
}
|
||||
if (isClarificationNeededOutput(output)) {
|
||||
const questions = output.questions ?? [];
|
||||
return {
|
||||
icon,
|
||||
title: "Needs clarification",
|
||||
description: `${questions.length} question${questions.length === 1 ? "" : "s"}`,
|
||||
};
|
||||
}
|
||||
return { icon, title: "" };
|
||||
}
|
||||
|
||||
@@ -96,15 +82,6 @@ export function EditAgentTool({ part }: Props) {
|
||||
// (errors are shown inline so they get replaced when retrying)
|
||||
const hasExpandableContent = !isError;
|
||||
|
||||
function handleClarificationAnswers(answers: Record<string, string>) {
|
||||
const questions =
|
||||
output && isClarificationNeededOutput(output)
|
||||
? (output.questions ?? [])
|
||||
: [];
|
||||
|
||||
onSend(buildClarificationAnswersMessage(answers, questions, "edit"));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="py-2">
|
||||
{isOperating && (
|
||||
@@ -132,34 +109,32 @@ export function EditAgentTool({ part }: Props) {
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasExpandableContent &&
|
||||
!(output && isClarificationNeededOutput(output)) &&
|
||||
!(output && isAgentSavedOutput(output)) && (
|
||||
<ToolAccordion {...getAccordionMeta(output)}>
|
||||
{isOperating && (
|
||||
<ContentGrid>
|
||||
<MiniGame />
|
||||
<ContentHint>
|
||||
This could take a few minutes — play while you wait!
|
||||
</ContentHint>
|
||||
</ContentGrid>
|
||||
)}
|
||||
{hasExpandableContent && !(output && isAgentSavedOutput(output)) && (
|
||||
<ToolAccordion {...getAccordionMeta(output)}>
|
||||
{isOperating && (
|
||||
<ContentGrid>
|
||||
<MiniGame />
|
||||
<ContentHint>
|
||||
This could take a few minutes — play while you wait!
|
||||
</ContentHint>
|
||||
</ContentGrid>
|
||||
)}
|
||||
|
||||
{output && isAgentPreviewOutput(output) && (
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
{output.description?.trim() && (
|
||||
<ContentCardDescription>
|
||||
{output.description}
|
||||
</ContentCardDescription>
|
||||
)}
|
||||
<ContentCodeBlock>
|
||||
{truncateText(formatMaybeJson(output.agent_json), 1600)}
|
||||
</ContentCodeBlock>
|
||||
</ContentGrid>
|
||||
)}
|
||||
</ToolAccordion>
|
||||
)}
|
||||
{output && isAgentPreviewOutput(output) && (
|
||||
<ContentGrid>
|
||||
<ContentMessage>{output.message}</ContentMessage>
|
||||
{output.description?.trim() && (
|
||||
<ContentCardDescription>
|
||||
{output.description}
|
||||
</ContentCardDescription>
|
||||
)}
|
||||
<ContentCodeBlock>
|
||||
{truncateText(formatMaybeJson(output.agent_json), 1600)}
|
||||
</ContentCodeBlock>
|
||||
</ContentGrid>
|
||||
)}
|
||||
</ToolAccordion>
|
||||
)}
|
||||
|
||||
{output && isAgentSavedOutput(output) && (
|
||||
<AgentSavedCard
|
||||
@@ -169,14 +144,6 @@ export function EditAgentTool({ part }: Props) {
|
||||
agentPageLink={output.agent_page_link}
|
||||
/>
|
||||
)}
|
||||
|
||||
{output && isClarificationNeededOutput(output) && (
|
||||
<ClarificationQuestionsCard
|
||||
questions={normalizeClarifyingQuestions(output.questions ?? [])}
|
||||
message={output.message}
|
||||
onSubmitAnswers={handleClarificationAnswers}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { AgentPreviewResponse } from "@/app/api/__generated__/models/agentPreviewResponse";
|
||||
import type { AgentSavedResponse } from "@/app/api/__generated__/models/agentSavedResponse";
|
||||
import type { ClarificationNeededResponse } from "@/app/api/__generated__/models/clarificationNeededResponse";
|
||||
import type { ErrorResponse } from "@/app/api/__generated__/models/errorResponse";
|
||||
import { ResponseType } from "@/app/api/__generated__/models/responseType";
|
||||
import {
|
||||
@@ -14,7 +13,6 @@ import { ScaleLoader } from "../../components/ScaleLoader/ScaleLoader";
|
||||
export type EditAgentToolOutput =
|
||||
| AgentPreviewResponse
|
||||
| AgentSavedResponse
|
||||
| ClarificationNeededResponse
|
||||
| ErrorResponse;
|
||||
|
||||
function parseOutput(output: unknown): EditAgentToolOutput | null {
|
||||
@@ -33,7 +31,6 @@ function parseOutput(output: unknown): EditAgentToolOutput | null {
|
||||
if (
|
||||
type === ResponseType.agent_builder_preview ||
|
||||
type === ResponseType.agent_builder_saved ||
|
||||
type === ResponseType.agent_builder_clarification_needed ||
|
||||
type === ResponseType.error
|
||||
) {
|
||||
return output as EditAgentToolOutput;
|
||||
@@ -42,7 +39,6 @@ function parseOutput(output: unknown): EditAgentToolOutput | null {
|
||||
return output as AgentPreviewResponse;
|
||||
if ("agent_id" in output && "library_agent_id" in output)
|
||||
return output as AgentSavedResponse;
|
||||
if ("questions" in output) return output as ClarificationNeededResponse;
|
||||
if ("error" in output || "details" in output)
|
||||
return output as ErrorResponse;
|
||||
}
|
||||
@@ -73,15 +69,6 @@ export function isAgentSavedOutput(
|
||||
);
|
||||
}
|
||||
|
||||
export function isClarificationNeededOutput(
|
||||
output: EditAgentToolOutput,
|
||||
): output is ClarificationNeededResponse {
|
||||
return (
|
||||
output.type === ResponseType.agent_builder_clarification_needed ||
|
||||
"questions" in output
|
||||
);
|
||||
}
|
||||
|
||||
export function isErrorOutput(
|
||||
output: EditAgentToolOutput,
|
||||
): output is ErrorResponse {
|
||||
@@ -102,7 +89,6 @@ export function getAnimationText(part: {
|
||||
if (!output) return "Editing the agent";
|
||||
if (isAgentSavedOutput(output)) return `Saved "${output.agent_name}"`;
|
||||
if (isAgentPreviewOutput(output)) return `Preview "${output.agent_name}"`;
|
||||
if (isClarificationNeededOutput(output)) return "Needs clarification";
|
||||
return "Error editing agent";
|
||||
}
|
||||
case "output-error":
|
||||
|
||||
Reference in New Issue
Block a user