mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 18:30:39 +09:00
Refine issue chat activity and message chrome
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
3fea60c04c
commit
f593e116c1
7 changed files with 446 additions and 167 deletions
|
|
@ -85,6 +85,50 @@ describe("buildAssistantPartsFromTranscript", () => {
|
|||
});
|
||||
expect(result.notices).toEqual(["warn: noisy setup output"]);
|
||||
});
|
||||
|
||||
it("preserves transcript ordering when text and tool activity are interleaved", () => {
|
||||
const result = buildAssistantPartsFromTranscript([
|
||||
{ kind: "assistant", ts: "2026-04-06T12:00:00.000Z", text: "First." },
|
||||
{
|
||||
kind: "tool_call",
|
||||
ts: "2026-04-06T12:00:01.000Z",
|
||||
name: "read_file",
|
||||
toolUseId: "tool-1",
|
||||
input: { path: "ui/src/components/IssueChatThread.tsx" },
|
||||
},
|
||||
{ kind: "assistant", ts: "2026-04-06T12:00:02.000Z", text: "Second." },
|
||||
{
|
||||
kind: "tool_result",
|
||||
ts: "2026-04-06T12:00:03.000Z",
|
||||
toolUseId: "tool-1",
|
||||
content: "ok",
|
||||
isError: false,
|
||||
},
|
||||
{ kind: "thinking", ts: "2026-04-06T12:00:04.000Z", text: "Need one more check." },
|
||||
{
|
||||
kind: "tool_call",
|
||||
ts: "2026-04-06T12:00:05.000Z",
|
||||
name: "write_file",
|
||||
toolUseId: "tool-2",
|
||||
input: { path: "ui/src/lib/issue-chat-messages.ts" },
|
||||
},
|
||||
{
|
||||
kind: "tool_result",
|
||||
ts: "2026-04-06T12:00:06.000Z",
|
||||
toolUseId: "tool-2",
|
||||
content: "saved",
|
||||
isError: false,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(result.parts).toMatchObject([
|
||||
{ type: "text", text: "First." },
|
||||
{ type: "tool-call", toolCallId: "tool-1", toolName: "read_file", result: "ok" },
|
||||
{ type: "text", text: "Second." },
|
||||
{ type: "reasoning", text: "Need one more check." },
|
||||
{ type: "tool-call", toolCallId: "tool-2", toolName: "write_file", result: "saved" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildIssueChatMessages", () => {
|
||||
|
|
|
|||
|
|
@ -268,58 +268,46 @@ function createHistoricalRunMessage(run: IssueChatLinkedRun, agentMap?: Map<stri
|
|||
return message;
|
||||
}
|
||||
|
||||
function mergeAdjacentTextParts(parts: Array<TextMessagePart | ReasoningMessagePart>) {
|
||||
const merged: Array<TextMessagePart | ReasoningMessagePart> = [];
|
||||
for (const part of parts) {
|
||||
const previous = merged.at(-1);
|
||||
if (previous && previous.type === part.type && previous.parentId === part.parentId) {
|
||||
merged[merged.length - 1] = {
|
||||
...previous,
|
||||
text: `${previous.text}${part.text}`,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
merged.push(part);
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
export function buildAssistantPartsFromTranscript(entries: readonly IssueChatTranscriptEntry[]) {
|
||||
const textLikeParts: Array<TextMessagePart | ReasoningMessagePart> = [];
|
||||
const orderedParts: Array<TextMessagePart | ReasoningMessagePart | ToolCallMessagePart<JsonObject, unknown>> = [];
|
||||
const toolParts = new Map<string, ToolCallMessagePart<JsonObject, unknown>>();
|
||||
const toolOrder: string[] = [];
|
||||
const toolIndices = new Map<string, number>();
|
||||
const notices: string[] = [];
|
||||
|
||||
for (const [index, entry] of entries.entries()) {
|
||||
if (entry.kind === "assistant" && entry.text) {
|
||||
textLikeParts.push({ type: "text", text: entry.text });
|
||||
orderedParts.push({ type: "text", text: entry.text });
|
||||
continue;
|
||||
}
|
||||
if (entry.kind === "thinking" && entry.text) {
|
||||
textLikeParts.push({ type: "reasoning", text: entry.text });
|
||||
orderedParts.push({ type: "reasoning", text: entry.text });
|
||||
continue;
|
||||
}
|
||||
if (entry.kind === "tool_call") {
|
||||
const toolCallId = entry.toolUseId || `tool-${index}`;
|
||||
if (!toolParts.has(toolCallId)) {
|
||||
toolOrder.push(toolCallId);
|
||||
}
|
||||
toolParts.set(toolCallId, {
|
||||
const nextPart: ToolCallMessagePart<JsonObject, unknown> = {
|
||||
type: "tool-call",
|
||||
toolCallId,
|
||||
toolName: entry.name || "tool",
|
||||
args: normalizeToolArgs(entry.input),
|
||||
argsText: stringifyUnknown(entry.input),
|
||||
});
|
||||
};
|
||||
if (!toolParts.has(toolCallId)) {
|
||||
toolIndices.set(toolCallId, orderedParts.length);
|
||||
orderedParts.push(nextPart);
|
||||
} else {
|
||||
const existingIndex = toolIndices.get(toolCallId);
|
||||
if (existingIndex !== undefined) {
|
||||
orderedParts[existingIndex] = nextPart;
|
||||
}
|
||||
}
|
||||
toolParts.set(toolCallId, nextPart);
|
||||
continue;
|
||||
}
|
||||
if (entry.kind === "tool_result") {
|
||||
const toolCallId = entry.toolUseId || `tool-result-${index}`;
|
||||
const existing = toolParts.get(toolCallId);
|
||||
if (!existing) {
|
||||
toolOrder.push(toolCallId);
|
||||
}
|
||||
toolParts.set(toolCallId, {
|
||||
const nextPart: ToolCallMessagePart<JsonObject, unknown> = {
|
||||
type: "tool-call",
|
||||
toolCallId,
|
||||
toolName: existing?.toolName || entry.toolName || "tool",
|
||||
|
|
@ -327,7 +315,17 @@ export function buildAssistantPartsFromTranscript(entries: readonly IssueChatTra
|
|||
argsText: existing?.argsText ?? "",
|
||||
result: entry.content ?? "",
|
||||
isError: entry.isError === true,
|
||||
});
|
||||
};
|
||||
if (existing) {
|
||||
const existingIndex = toolIndices.get(toolCallId);
|
||||
if (existingIndex !== undefined) {
|
||||
orderedParts[existingIndex] = nextPart;
|
||||
}
|
||||
} else {
|
||||
toolIndices.set(toolCallId, orderedParts.length);
|
||||
orderedParts.push(nextPart);
|
||||
}
|
||||
toolParts.set(toolCallId, nextPart);
|
||||
continue;
|
||||
}
|
||||
if (entry.kind === "stderr" && entry.text) {
|
||||
|
|
@ -347,13 +345,25 @@ export function buildAssistantPartsFromTranscript(entries: readonly IssueChatTra
|
|||
}
|
||||
}
|
||||
|
||||
const mergedParts: Array<TextMessagePart | ReasoningMessagePart | ToolCallMessagePart<JsonObject, unknown>> = [];
|
||||
for (const part of orderedParts) {
|
||||
if (part.type === "tool-call") {
|
||||
mergedParts.push(part);
|
||||
continue;
|
||||
}
|
||||
const previous = mergedParts.at(-1);
|
||||
if (previous && previous.type === part.type && previous.parentId === part.parentId) {
|
||||
mergedParts[mergedParts.length - 1] = {
|
||||
...previous,
|
||||
text: `${previous.text}${part.text}`,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
mergedParts.push(part);
|
||||
}
|
||||
|
||||
return {
|
||||
parts: [
|
||||
...mergeAdjacentTextParts(textLikeParts),
|
||||
...toolOrder
|
||||
.map((toolCallId) => toolParts.get(toolCallId))
|
||||
.filter((part): part is ToolCallMessagePart<JsonObject, unknown> => Boolean(part)),
|
||||
],
|
||||
parts: mergedParts,
|
||||
notices,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue