mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Fix chunked assistant finalText assembly
This commit is contained in:
@@ -452,6 +452,121 @@ describe("AgentManager", () => {
|
||||
expect(refreshed?.runtimeInfo?.model).toBe("gpt-5.2-codex");
|
||||
});
|
||||
|
||||
test("runAgent assembles finalText from trailing assistant chunks", async () => {
|
||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
|
||||
const storagePath = join(workdir, "agents");
|
||||
const storage = new AgentStorage(storagePath, logger);
|
||||
const expectedFinalText =
|
||||
"```json\n{\"message\":\"Reserve space for archive button in sidebar agent list\"}\n```";
|
||||
|
||||
class ChunkedAssistantSession implements AgentSession {
|
||||
readonly provider = "codex" as const;
|
||||
readonly capabilities = TEST_CAPABILITIES;
|
||||
readonly id = randomUUID();
|
||||
|
||||
async run(): Promise<AgentRunResult> {
|
||||
return {
|
||||
sessionId: this.id,
|
||||
finalText: "",
|
||||
timeline: [],
|
||||
};
|
||||
}
|
||||
|
||||
async *stream(): AsyncGenerator<AgentStreamEvent> {
|
||||
yield { type: "turn_started", provider: this.provider };
|
||||
yield {
|
||||
type: "timeline",
|
||||
provider: this.provider,
|
||||
item: {
|
||||
type: "assistant_message",
|
||||
text: "```json\n{\"message\":\"Reserve space for archive button in side",
|
||||
},
|
||||
};
|
||||
yield {
|
||||
type: "timeline",
|
||||
provider: this.provider,
|
||||
item: {
|
||||
type: "assistant_message",
|
||||
text: "bar agent list\"}\n```",
|
||||
},
|
||||
};
|
||||
yield { type: "turn_completed", provider: this.provider };
|
||||
}
|
||||
|
||||
async *streamHistory(): AsyncGenerator<AgentStreamEvent> {}
|
||||
|
||||
async getRuntimeInfo() {
|
||||
return {
|
||||
provider: this.provider,
|
||||
sessionId: this.id,
|
||||
model: null,
|
||||
modeId: null,
|
||||
};
|
||||
}
|
||||
|
||||
async getAvailableModes() {
|
||||
return [];
|
||||
}
|
||||
|
||||
async getCurrentMode() {
|
||||
return null;
|
||||
}
|
||||
|
||||
async setMode(): Promise<void> {}
|
||||
|
||||
getPendingPermissions() {
|
||||
return [];
|
||||
}
|
||||
|
||||
async respondToPermission(): Promise<void> {}
|
||||
|
||||
describePersistence() {
|
||||
return {
|
||||
provider: this.provider,
|
||||
sessionId: this.id,
|
||||
};
|
||||
}
|
||||
|
||||
async interrupt(): Promise<void> {}
|
||||
|
||||
async close(): Promise<void> {}
|
||||
}
|
||||
|
||||
class ChunkedAssistantClient implements AgentClient {
|
||||
readonly provider = "codex" as const;
|
||||
readonly capabilities = TEST_CAPABILITIES;
|
||||
|
||||
async isAvailable(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
async createSession(): Promise<AgentSession> {
|
||||
return new ChunkedAssistantSession();
|
||||
}
|
||||
|
||||
async resumeSession(): Promise<AgentSession> {
|
||||
return new ChunkedAssistantSession();
|
||||
}
|
||||
}
|
||||
|
||||
const manager = new AgentManager({
|
||||
clients: {
|
||||
codex: new ChunkedAssistantClient(),
|
||||
},
|
||||
registry: storage,
|
||||
logger,
|
||||
idFactory: () => "00000000-0000-4000-8000-000000000113",
|
||||
});
|
||||
|
||||
const snapshot = await manager.createAgent({
|
||||
provider: "codex",
|
||||
cwd: workdir,
|
||||
});
|
||||
|
||||
const result = await manager.runAgent(snapshot.id, "generate commit message");
|
||||
expect(result.finalText).toBe(expectedFinalText);
|
||||
});
|
||||
|
||||
test("listAgents excludes internal agents", async () => {
|
||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
|
||||
const storagePath = join(workdir, "agents");
|
||||
|
||||
@@ -558,9 +558,6 @@ export class AgentManager {
|
||||
for await (const event of events) {
|
||||
if (event.type === "timeline") {
|
||||
timeline.push(event.item);
|
||||
if (event.item.type === "assistant_message") {
|
||||
finalText = event.item.text;
|
||||
}
|
||||
} else if (event.type === "turn_completed") {
|
||||
usage = event.usage;
|
||||
} else if (event.type === "turn_failed") {
|
||||
@@ -570,6 +567,8 @@ export class AgentManager {
|
||||
}
|
||||
}
|
||||
|
||||
finalText = this.getLastAssistantMessageFromTimeline(timeline) ?? "";
|
||||
|
||||
const agent = this.requireAgent(agentId);
|
||||
const sessionId = agent.persistence?.sessionId;
|
||||
if (!sessionId) {
|
||||
@@ -869,10 +868,16 @@ export class AgentManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.getLastAssistantMessageFromTimeline(agent.timeline);
|
||||
}
|
||||
|
||||
private getLastAssistantMessageFromTimeline(
|
||||
timeline: readonly AgentTimelineItem[]
|
||||
): string | null {
|
||||
// Collect the last contiguous assistant messages (Claude streams chunks)
|
||||
const chunks: string[] = [];
|
||||
for (let i = agent.timeline.length - 1; i >= 0; i--) {
|
||||
const item = agent.timeline[i];
|
||||
for (let i = timeline.length - 1; i >= 0; i--) {
|
||||
const item = timeline[i];
|
||||
if (item.type !== "assistant_message") {
|
||||
if (chunks.length) {
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user