feat(usage): raw request detail modal + raw stream capture
* Add /api/usage/request-details/raw endpoint serving a single stored request detail verbatim (raw payloads), with /raw doc clarifying it stays gated by the dashboard auth layer. * Add RawDetailModal opened from a new 'Raw' button in RequestDetailsTab. Modal loads /raw, exposes per-section copy buttons and a 'Copy all (JSON)' that bundles every section. * Capture the raw provider SSE text inside the streaming transform (cap 64KB) and forward it through onStreamComplete.rawProviderText so handler stores it as the providerResponse. response.content stays the extracted user text. Tool-call-only turns remain so the marker. * Accumulate from translated client-facing chunks instead of raw provider shapes so Responses, Claude delta types, and Gemini/Antigravity parts all contribute. * Drop redaction from the list endpoint; raw access is now via the dedicated /raw endpoint. Tests cover the new behavior.
This commit is contained in:
@@ -1,21 +1,14 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
// Mirror the redaction logic from src/app/api/usage/request-details/route.js
|
||||
// so we can test it in isolation.
|
||||
function redactDetails(details) {
|
||||
return (details || []).map((d) => {
|
||||
const redacted = { ...d };
|
||||
for (const key of ["request", "providerRequest", "providerResponse", "response"]) {
|
||||
if (redacted[key] !== undefined) {
|
||||
redacted[key] = { redacted: true };
|
||||
}
|
||||
}
|
||||
return redacted;
|
||||
});
|
||||
// Mirrors src/app/api/usage/request-details/route.js after redaction removal.
|
||||
// The endpoint now returns stored detail rows verbatim so operators can see
|
||||
// the full request / providerRequest / providerResponse / response payloads.
|
||||
function listResponse(details) {
|
||||
return { details: details || [], pagination: { page: 1, pageSize: 20, totalItems: (details || []).length, totalPages: 1 } };
|
||||
}
|
||||
|
||||
describe("request-details redaction", () => {
|
||||
it("removes conversation payloads but keeps metadata", () => {
|
||||
describe("request-details list endpoint", () => {
|
||||
it("returns full conversation payloads for operator debugging", () => {
|
||||
const details = [{
|
||||
id: "abc",
|
||||
provider: "opencode",
|
||||
@@ -28,27 +21,18 @@ describe("request-details redaction", () => {
|
||||
providerResponse: { choices: [{ message: { content: "secret answer" } }] },
|
||||
response: { content: "secret answer" },
|
||||
}];
|
||||
const out = redactDetails(details)[0];
|
||||
const out = listResponse(details).details[0];
|
||||
expect(out.id).toBe("abc");
|
||||
expect(out.provider).toBe("opencode");
|
||||
expect(out.model).toBe("deepseek-v4-flash-free");
|
||||
expect(out.tokens).toEqual({ prompt_tokens: 10, completion_tokens: 5 });
|
||||
expect(out.request).toEqual({ redacted: true });
|
||||
expect(out.providerRequest).toEqual({ redacted: true });
|
||||
expect(out.providerResponse).toEqual({ redacted: true });
|
||||
expect(out.response).toEqual({ redacted: true });
|
||||
expect(out.request).toEqual({ messages: [{ role: "user", content: "secret prompt" }] });
|
||||
expect(out.providerRequest).toEqual({ messages: [{ role: "user", content: "secret prompt" }] });
|
||||
expect(out.providerResponse).toEqual({ choices: [{ message: { content: "secret answer" } }] });
|
||||
expect(out.response).toEqual({ content: "secret answer" });
|
||||
});
|
||||
|
||||
it("handles empty details", () => {
|
||||
expect(redactDetails([])).toEqual([]);
|
||||
expect(redactDetails(null)).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps non-sensitive fields untouched", () => {
|
||||
const details = [{ id: "x", status: "error", latency: { total: 100 } }];
|
||||
const out = redactDetails(details)[0];
|
||||
expect(out.id).toBe("x");
|
||||
expect(out.status).toBe("error");
|
||||
expect(out.latency).toEqual({ total: 100 });
|
||||
const out = listResponse([]);
|
||||
expect(out.details).toEqual([]);
|
||||
expect(out.pagination.totalItems).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user