fix(chat): wire question and todo cards
This commit is contained in:
@@ -8,6 +8,53 @@ export type AgentModel =
|
||||
export type PermissionReply = "once" | "always" | "reject";
|
||||
export type AgentApprovalMode = "request" | "always";
|
||||
|
||||
export type AgentQuestionStatus =
|
||||
| "pending"
|
||||
| "submitting"
|
||||
| "answered"
|
||||
| "rejected"
|
||||
| "error";
|
||||
|
||||
export type AgentQuestionRequest = {
|
||||
requestId: string;
|
||||
sessionId: string;
|
||||
questions: Array<{
|
||||
header: string;
|
||||
question: string;
|
||||
options: Array<{
|
||||
label: string;
|
||||
description: string;
|
||||
}>;
|
||||
multiple?: boolean;
|
||||
custom?: boolean;
|
||||
}>;
|
||||
tool?: {
|
||||
messageID: string;
|
||||
callID: string;
|
||||
};
|
||||
createdAt: number;
|
||||
repliedAt?: number;
|
||||
status: AgentQuestionStatus;
|
||||
answers?: string[][];
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export type AgentTodoItem = {
|
||||
id: string;
|
||||
content: string;
|
||||
status: "pending" | "in_progress" | "completed" | "cancelled";
|
||||
priority?: "low" | "medium" | "high";
|
||||
createdAt?: number;
|
||||
updatedAt?: number;
|
||||
};
|
||||
|
||||
export type AgentTodoUpdate = {
|
||||
sessionId: string;
|
||||
messageId?: string;
|
||||
todos: AgentTodoItem[];
|
||||
createdAt: number;
|
||||
};
|
||||
|
||||
export type StreamEvent =
|
||||
| {
|
||||
type: "state";
|
||||
@@ -64,6 +111,28 @@ export type StreamEvent =
|
||||
sessionId: string;
|
||||
requestId: string;
|
||||
reply: PermissionReply;
|
||||
}
|
||||
| {
|
||||
type: "question_request";
|
||||
sessionId: string;
|
||||
requestId: string;
|
||||
questions: AgentQuestionRequest["questions"];
|
||||
tool?: AgentQuestionRequest["tool"];
|
||||
createdAt: number;
|
||||
}
|
||||
| {
|
||||
type: "question_response";
|
||||
sessionId: string;
|
||||
requestId: string;
|
||||
answers?: string[][];
|
||||
rejected?: boolean;
|
||||
}
|
||||
| {
|
||||
type: "todo_update";
|
||||
sessionId: string;
|
||||
messageId?: string;
|
||||
todos: AgentTodoItem[];
|
||||
createdAt: number;
|
||||
};
|
||||
|
||||
type StreamOptions = {
|
||||
@@ -125,6 +194,80 @@ const resolveToolParams = (
|
||||
return isObjectRecord(params) ? params : {};
|
||||
};
|
||||
|
||||
const normalizeQuestionList = (value: unknown): AgentQuestionRequest["questions"] => {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return value
|
||||
.filter(isObjectRecord)
|
||||
.map((question) => ({
|
||||
header: typeof question.header === "string" ? question.header : "",
|
||||
question: typeof question.question === "string" ? question.question : "",
|
||||
options: Array.isArray(question.options)
|
||||
? question.options.filter(isObjectRecord).map((option) => ({
|
||||
label: typeof option.label === "string" ? option.label : "",
|
||||
description:
|
||||
typeof option.description === "string" ? option.description : "",
|
||||
}))
|
||||
: [],
|
||||
multiple: typeof question.multiple === "boolean" ? question.multiple : undefined,
|
||||
custom: typeof question.custom === "boolean" ? question.custom : undefined,
|
||||
}));
|
||||
};
|
||||
|
||||
const normalizeAnswers = (value: unknown): string[][] | undefined => {
|
||||
if (!Array.isArray(value)) return undefined;
|
||||
return value.map((answer) =>
|
||||
Array.isArray(answer)
|
||||
? answer.filter((item): item is string => typeof item === "string")
|
||||
: [],
|
||||
);
|
||||
};
|
||||
|
||||
const normalizeQuestionTool = (value: unknown): AgentQuestionRequest["tool"] => {
|
||||
if (!isObjectRecord(value)) return undefined;
|
||||
const messageID =
|
||||
typeof value.messageID === "string"
|
||||
? value.messageID
|
||||
: typeof value.message_id === "string"
|
||||
? value.message_id
|
||||
: undefined;
|
||||
const callID =
|
||||
typeof value.callID === "string"
|
||||
? value.callID
|
||||
: typeof value.call_id === "string"
|
||||
? value.call_id
|
||||
: undefined;
|
||||
return messageID && callID ? { messageID, callID } : undefined;
|
||||
};
|
||||
|
||||
const normalizeTodoStatus = (value: unknown): AgentTodoItem["status"] => {
|
||||
if (value === "in_progress" || value === "completed" || value === "cancelled") {
|
||||
return value;
|
||||
}
|
||||
return "pending";
|
||||
};
|
||||
|
||||
const normalizeTodoPriority = (value: unknown): AgentTodoItem["priority"] => {
|
||||
if (value === "low" || value === "medium" || value === "high") {
|
||||
return value;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const normalizeTodos = (value: unknown): AgentTodoItem[] => {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return value.filter(isObjectRecord).map((todo, index) => ({
|
||||
id:
|
||||
typeof todo.id === "string" && todo.id.trim()
|
||||
? todo.id
|
||||
: `todo-${index}`,
|
||||
content: typeof todo.content === "string" ? todo.content : "",
|
||||
status: normalizeTodoStatus(todo.status),
|
||||
priority: normalizeTodoPriority(todo.priority),
|
||||
createdAt: typeof todo.created_at === "number" ? todo.created_at : undefined,
|
||||
updatedAt: typeof todo.updated_at === "number" ? todo.updated_at : undefined,
|
||||
}));
|
||||
};
|
||||
|
||||
const emitParsedStreamEvent = (
|
||||
event: string,
|
||||
data: string,
|
||||
@@ -158,6 +301,11 @@ const emitParsedStreamEvent = (
|
||||
always?: unknown;
|
||||
created_at?: number;
|
||||
reply?: PermissionReply;
|
||||
questions?: unknown;
|
||||
answers?: unknown;
|
||||
rejected?: boolean;
|
||||
message_id?: string;
|
||||
todos?: unknown;
|
||||
};
|
||||
if (event === "state") {
|
||||
onEvent({
|
||||
@@ -244,6 +392,31 @@ const emitParsedStreamEvent = (
|
||||
requestId: parsed.request_id ?? "",
|
||||
reply: parsed.reply ?? "reject",
|
||||
});
|
||||
} else if (event === "question_request") {
|
||||
onEvent({
|
||||
type: "question_request",
|
||||
sessionId: parsed.session_id ?? "",
|
||||
requestId: parsed.request_id ?? "",
|
||||
questions: normalizeQuestionList(parsed.questions),
|
||||
tool: normalizeQuestionTool(parsed.tool),
|
||||
createdAt: parsed.created_at ?? Date.now(),
|
||||
});
|
||||
} else if (event === "question_response") {
|
||||
onEvent({
|
||||
type: "question_response",
|
||||
sessionId: parsed.session_id ?? "",
|
||||
requestId: parsed.request_id ?? "",
|
||||
answers: normalizeAnswers(parsed.answers),
|
||||
rejected: parsed.rejected === true,
|
||||
});
|
||||
} else if (event === "todo_update") {
|
||||
onEvent({
|
||||
type: "todo_update",
|
||||
sessionId: parsed.session_id ?? "",
|
||||
messageId: parsed.message_id,
|
||||
todos: normalizeTodos(parsed.todos),
|
||||
createdAt: parsed.created_at ?? Date.now(),
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
onEvent({
|
||||
@@ -443,6 +616,60 @@ export const replyAgentPermission = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const replyAgentQuestion = async (
|
||||
sessionId: string,
|
||||
requestId: string,
|
||||
answers: string[][],
|
||||
) => {
|
||||
const response = await apiFetch(
|
||||
`${config.AGENT_URL}/api/v1/agent/chat/question/${encodeURIComponent(requestId)}/reply`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
session_id: sessionId,
|
||||
answers,
|
||||
}),
|
||||
projectHeaderMode: "include",
|
||||
userHeaderMode: "include",
|
||||
skipAuthRedirect: true,
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const detail = await response.text();
|
||||
throw new Error(detail || `question reply failed: ${response.status}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const rejectAgentQuestion = async (
|
||||
sessionId: string,
|
||||
requestId: string,
|
||||
) => {
|
||||
const response = await apiFetch(
|
||||
`${config.AGENT_URL}/api/v1/agent/chat/question/${encodeURIComponent(requestId)}/reject`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
session_id: sessionId,
|
||||
}),
|
||||
projectHeaderMode: "include",
|
||||
userHeaderMode: "include",
|
||||
skipAuthRedirect: true,
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const detail = await response.text();
|
||||
throw new Error(detail || `question reject failed: ${response.status}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const forkAgentChat = async (sessionId: string | undefined, keepMessageCount: number) => {
|
||||
const response = await apiFetch(`${config.AGENT_URL}/api/v1/agent/chat/fork`, {
|
||||
method: "POST",
|
||||
|
||||
Reference in New Issue
Block a user