feat: added reconstructed raw SSE capture for stream provider SDKs

- Added reconstructed SSE event emission for OpenAI, Azure, and Anthropic streams.
- Added raw SSE text to debug report bundles, including raw-sse.txt output.
- Included dropped-record metadata in raw SSE text when events were trimmed.
- Updated raw SSE and sse-debug tests for observer-based capture and safety checks.
This commit is contained in:
can1357
2026-06-08 02:35:08 +02:00
parent 71624ee47b
commit 00e6e14cb4
15 changed files with 575 additions and 556 deletions
@@ -68,4 +68,27 @@ describe("RawSseDebugBuffer", () => {
expect(resolveRawSseDebugBuffer(owner)).toBe(buffer);
expect(buffer.snapshot().totalEvents).toBe(1);
});
it("keeps session-owned records captured before the viewer resolves the buffer", () => {
const session = { rawSseDebugBuffer: new RawSseDebugBuffer() };
session.rawSseDebugBuffer.recordResponse(
{ status: 200, requestId: "req_pre_viewer", headers: {}, metadata: { lastTransport: "sse" } },
model,
);
session.rawSseDebugBuffer.recordEvent(
{ event: "message_start", data: "{}", raw: ["event: message_start", "data: {}"] },
model,
);
session.rawSseDebugBuffer.recordEvent(
{ event: "message_stop", data: "{}", raw: ["event: message_stop", "data: {}"] },
model,
);
const buffer = resolveRawSseDebugBuffer(session);
expect(buffer).toBe(session.rawSseDebugBuffer);
expect(buffer.snapshot().totalEvents).toBe(2);
expect(buffer.toRawText()).toContain("requestId=req_pre_viewer");
expect(buffer.toRawText()).toContain("event: message_stop");
});
});
@@ -0,0 +1,76 @@
import { afterEach, describe, expect, it } from "bun:test";
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import type { Model } from "@oh-my-pi/pi-ai";
import { getConfigRootDir, setAgentDir } from "@oh-my-pi/pi-utils";
import { RawSseDebugBuffer } from "../../src/debug/raw-sse-buffer";
import { createReportBundle } from "../../src/debug/report-bundle";
const model: Model<"anthropic-messages"> = {
id: "claude-test",
name: "Claude Test",
api: "anthropic-messages",
provider: "anthropic",
baseUrl: "https://api.anthropic.com",
reasoning: true,
input: ["text"],
cost: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200_000,
maxTokens: 8_192,
};
const originalAgentDir = process.env.PI_CODING_AGENT_DIR;
const originalXdgStateHome = process.env.XDG_STATE_HOME;
const fallbackAgentDir = path.join(getConfigRootDir(), "agent");
let cleanupRoot: string | undefined;
afterEach(async () => {
if (originalXdgStateHome === undefined) {
delete process.env.XDG_STATE_HOME;
} else {
process.env.XDG_STATE_HOME = originalXdgStateHome;
}
if (originalAgentDir) {
setAgentDir(originalAgentDir);
} else {
setAgentDir(fallbackAgentDir);
delete process.env.PI_CODING_AGENT_DIR;
}
if (cleanupRoot) {
await fs.rm(cleanupRoot, { recursive: true, force: true });
cleanupRoot = undefined;
}
});
describe("raw SSE report bundle", () => {
it("includes captured raw SSE text and dropped-record disclosure", async () => {
cleanupRoot = await fs.mkdtemp(path.join(os.tmpdir(), "omp-raw-sse-report-"));
const xdgStateHome = path.join(cleanupRoot, "state");
await fs.mkdir(path.join(xdgStateHome, "omp"), { recursive: true });
process.env.XDG_STATE_HOME = xdgStateHome;
setAgentDir(fallbackAgentDir);
const buffer = new RawSseDebugBuffer();
buffer.recordResponse(
{ status: 200, requestId: "req_report", headers: {}, metadata: { lastTransport: "sse" } },
model,
);
for (let i = 0; i < 1_001; i++) {
buffer.recordEvent(
{ event: "message_delta", data: `{"i":${i}}`, raw: ["event: message_delta", `data: {"i":${i}}`] },
model,
);
}
const rawSseText = buffer.toRawText();
expect(rawSseText).toContain(": omp-debug-dropped records=");
expect(rawSseText).toContain("event: message_delta");
const result = await createReportBundle({ sessionFile: undefined, rawSseText });
expect(result.files).toContain("raw-sse.txt");
const archive = new Bun.Archive(await Bun.file(result.path).bytes());
const files = await archive.files();
expect(await files.get("raw-sse.txt")?.text()).toBe(rawSseText);
});
});