feat: project durable agent conversations
This commit is contained in:
@@ -20,7 +20,7 @@ describe("projectConversation", () => {
|
||||
messageId: "user-1",
|
||||
rawText: "Build it",
|
||||
role: "user",
|
||||
status: "processing",
|
||||
status: "dispatching",
|
||||
}),
|
||||
row({ messageId: "assistant-1", role: "assistant", status: "queued" }),
|
||||
]);
|
||||
@@ -28,6 +28,21 @@ describe("projectConversation", () => {
|
||||
expect(state.messages).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("projects partial assistant text as streaming", () => {
|
||||
const state = projectConversation([
|
||||
row({
|
||||
messageId: "assistant-streaming",
|
||||
rawText: "Working",
|
||||
role: "assistant",
|
||||
status: "running",
|
||||
}),
|
||||
]);
|
||||
expect(state.streaming).toBe(true);
|
||||
expect(state.messages[0]?.parts).toEqual([
|
||||
{ state: "streaming", text: "Working", type: "text" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("projects completed Convex rows into renderable messages", () => {
|
||||
const state = projectConversation([
|
||||
row({
|
||||
|
||||
@@ -11,40 +11,67 @@ export interface ConversationRow {
|
||||
readonly messageId: string;
|
||||
readonly rawText: string;
|
||||
readonly role: "assistant" | "user";
|
||||
readonly status: "completed" | "failed" | "processing" | "queued";
|
||||
readonly status:
|
||||
| "aborted"
|
||||
| "completed"
|
||||
| "dispatching"
|
||||
| "failed"
|
||||
| "queued"
|
||||
| "running";
|
||||
}
|
||||
|
||||
export const projectConversation = (rows: readonly ConversationRow[]) => ({
|
||||
failedError: rows.findLast(
|
||||
(row) => row.role === "assistant" && row.status === "failed"
|
||||
)?.error,
|
||||
messages: rows
|
||||
.filter((row) => row.role === "user" || row.status === "completed")
|
||||
.map<ConversationMessage>((row) => ({
|
||||
id: row.messageId,
|
||||
parts: [
|
||||
...row.attachments.map((attachment) => ({
|
||||
filename: attachment.filename ?? undefined,
|
||||
id: attachment.id,
|
||||
mediaType: attachment.mediaType,
|
||||
type: "file" as const,
|
||||
url: attachment.url ?? undefined,
|
||||
})),
|
||||
...(row.rawText
|
||||
? [
|
||||
{
|
||||
state: "done" as const,
|
||||
text: row.rawText,
|
||||
type: "text" as const,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
role: row.role,
|
||||
})),
|
||||
pending: rows.some(
|
||||
export const projectConversation = (rows: readonly ConversationRow[]) => {
|
||||
const streaming = rows.some(
|
||||
(row) =>
|
||||
row.role === "assistant" &&
|
||||
(row.status === "queued" || row.status === "processing")
|
||||
),
|
||||
});
|
||||
row.status === "running" &&
|
||||
row.rawText.length > 0
|
||||
);
|
||||
return {
|
||||
failedError: rows.findLast(
|
||||
(row) => row.role === "assistant" && row.status === "failed"
|
||||
)?.error,
|
||||
messages: rows
|
||||
.filter(
|
||||
(row) =>
|
||||
row.role === "user" ||
|
||||
row.status === "completed" ||
|
||||
(row.role === "assistant" &&
|
||||
row.status === "running" &&
|
||||
row.rawText.length > 0)
|
||||
)
|
||||
.map<ConversationMessage>((row) => ({
|
||||
id: row.messageId,
|
||||
parts: [
|
||||
...row.attachments.map((attachment) => ({
|
||||
filename: attachment.filename ?? undefined,
|
||||
id: attachment.id,
|
||||
mediaType: attachment.mediaType,
|
||||
type: "file" as const,
|
||||
url: attachment.url ?? undefined,
|
||||
})),
|
||||
...(row.rawText
|
||||
? [
|
||||
{
|
||||
state:
|
||||
row.status === "running"
|
||||
? ("streaming" as const)
|
||||
: ("done" as const),
|
||||
text: row.rawText,
|
||||
type: "text" as const,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
role: row.role,
|
||||
})),
|
||||
pending: rows.some(
|
||||
(row) =>
|
||||
row.role === "assistant" &&
|
||||
(row.status === "queued" ||
|
||||
row.status === "dispatching" ||
|
||||
row.status === "running")
|
||||
),
|
||||
streaming,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user