新增优化流事件契约

This commit is contained in:
czj
2026-07-01 13:08:44 +08:00
parent f9b8bae714
commit 382f468267
2 changed files with 109 additions and 0 deletions
@@ -0,0 +1,42 @@
import { describe, expect, it } from "vitest";
import {
encodeOptimizationStreamEvent,
parseOptimizationStreamChunk,
type OptimizationStreamEvent,
} from "../stream-events";
describe("optimization stream events", () => {
it("encodes each event as one JSON line", () => {
const event: OptimizationStreamEvent = {
type: "draft_started",
job_id: "job_123",
message: "正在生成优化草稿",
};
expect(encodeOptimizationStreamEvent(event)).toBe(
'{"type":"draft_started","job_id":"job_123","message":"正在生成优化草稿"}\n',
);
});
it("parses chunked NDJSON while preserving incomplete lines", () => {
const first = parseOptimizationStreamChunk(
"",
'{"type":"job_created","job":{"id":"job_',
);
expect(first.events).toEqual([]);
expect(first.remainder).toBe('{"type":"job_created","job":{"id":"job_');
const second = parseOptimizationStreamChunk(
first.remainder,
'123"}}\n{"type":"draft_started","job_id":"job_123","message":"正在生成"}\n{"type":"qa_started"',
);
expect(second.events).toEqual([
{ type: "job_created", job: { id: "job_123" } },
{ type: "draft_started", job_id: "job_123", message: "正在生成" },
]);
expect(second.remainder).toBe('{"type":"qa_started"');
});
});
+67
View File
@@ -0,0 +1,67 @@
import type {
OptimizationFactCard,
OptimizedArticle,
QaReport,
} from "../domain/types";
export type OptimizationStreamStage =
| "input"
| "job"
| "fact_card"
| "draft"
| "qa"
| "rewrite"
| "final";
export type OptimizationStreamEvent =
| { type: "job_created"; job: { id: string } }
| {
type: "fact_card_ready";
job_id: string;
fact_card: OptimizationFactCard;
}
| { type: "draft_started"; job_id: string; message: string }
| { type: "draft_ready"; job_id: string; article: OptimizedArticle }
| { type: "qa_started"; job_id: string; message: string }
| { type: "qa_ready"; job_id: string; qa_report: QaReport }
| { type: "rewrite_started"; job_id: string; round: number }
| {
type: "rewrite_ready";
job_id: string;
round: number;
article: OptimizedArticle;
}
| {
type: "final_ready";
job_id: string;
optimized_article: OptimizedArticle;
qa_report: QaReport;
export_paths: Record<string, string>;
}
| {
type: "failed";
job_id?: string;
stage: OptimizationStreamStage;
error: string;
};
export function encodeOptimizationStreamEvent(
event: OptimizationStreamEvent,
) {
return `${JSON.stringify(event)}\n`;
}
export function parseOptimizationStreamChunk(
previousRemainder: string,
chunk: string,
) {
const text = previousRemainder + chunk;
const lines = text.split(/\n/);
const remainder = lines.pop() ?? "";
const events = lines
.map((line) => line.trim())
.filter(Boolean)
.map((line) => JSON.parse(line) as OptimizationStreamEvent);
return { events, remainder };
}