refactor: trim user turn transcript API

This commit is contained in:
Shakker
2026-05-26 15:24:50 +01:00
committed by Shakker
parent d55fe4b6ae
commit 8bbd4baa9a
2 changed files with 20 additions and 107 deletions

View File

@@ -9,10 +9,8 @@ import { createMockPluginRegistry } from "openclaw/plugin-sdk/plugin-test-runtim
import { castAgentMessage } from "openclaw/plugin-sdk/test-fixtures";
import { afterEach, describe, expect, it } from "vitest";
import {
appendInlineUserTurnTranscriptMessage,
appendUserTurnTranscriptMessage,
buildPersistedUserTurnMediaInputsFromFields,
buildPersistedUserTurnMediaFields,
buildPersistedUserTurnMessage,
createUserTurnTranscriptRecorder,
mergePreparedUserTurnMessageForRuntime,
@@ -137,71 +135,6 @@ describe("user turn transcript persistence", () => {
});
});
describe("buildPersistedUserTurnMediaFields", () => {
it("omits media fields when there is no structured media", () => {
expect(buildPersistedUserTurnMediaFields(undefined)).toEqual({});
expect(buildPersistedUserTurnMediaFields([])).toEqual({});
expect(buildPersistedUserTurnMediaFields([{ path: " ", contentType: "image/png" }])).toEqual(
{},
);
});
it("builds aligned transcript media fields from structured media facts", () => {
expect(
buildPersistedUserTurnMediaFields([
{ path: "/tmp/a.png", contentType: "image/png" },
{ path: "/tmp/b.jpg", contentType: "image/jpeg" },
]),
).toEqual({
MediaPath: "/tmp/a.png",
MediaPaths: ["/tmp/a.png", "/tmp/b.jpg"],
MediaType: "image/png",
MediaTypes: ["image/png", "image/jpeg"],
});
});
it("uses url-backed media when no local path is available", () => {
expect(
buildPersistedUserTurnMediaFields([
{ url: "media://inbound/photo.png", contentType: "image/png" },
]),
).toEqual({
MediaPath: "media://inbound/photo.png",
MediaPaths: ["media://inbound/photo.png"],
MediaType: "image/png",
MediaTypes: ["image/png"],
});
});
it("falls back to kind and then octet-stream for media types", () => {
expect(
buildPersistedUserTurnMediaFields([
{ path: "/tmp/doc", kind: "document" },
{ path: "/tmp/blob" },
]),
).toEqual({
MediaPath: "/tmp/doc",
MediaPaths: ["/tmp/doc", "/tmp/blob"],
MediaType: "document",
MediaTypes: ["document", "application/octet-stream"],
});
});
it("keeps media paths and types aligned when incomplete entries are skipped", () => {
expect(
buildPersistedUserTurnMediaFields([
{ contentType: "image/png" },
{ path: "/tmp/b.jpg", contentType: "image/jpeg" },
]),
).toEqual({
MediaPath: "/tmp/b.jpg",
MediaPaths: ["/tmp/b.jpg"],
MediaType: "image/jpeg",
MediaTypes: ["image/jpeg"],
});
});
});
describe("buildPersistedUserTurnMessage", () => {
it("builds a plain user transcript message for text-only turns", () => {
expect(
@@ -419,11 +352,11 @@ describe("user turn transcript persistence", () => {
]);
});
it("uses inline update mode through the convenience wrapper", async () => {
it("uses inline update mode by default", async () => {
const dir = createTempDir("openclaw-user-turn-append-inline-");
const transcriptPath = path.join(dir, "session.jsonl");
const appended = await appendInlineUserTurnTranscriptMessage({
const appended = await appendUserTurnTranscriptMessage({
transcriptPath,
sessionId: "session-1",
sessionKey: "main",
@@ -509,14 +442,14 @@ describe("user turn transcript persistence", () => {
const dir = createTempDir("openclaw-user-turn-redacted-idempotent-");
const transcriptPath = path.join(dir, "session.jsonl");
await appendInlineUserTurnTranscriptMessage({
await appendUserTurnTranscriptMessage({
transcriptPath,
input: {
text: "secret prompt",
idempotencyKey: "chat-run-1:user",
},
});
await appendInlineUserTurnTranscriptMessage({
await appendUserTurnTranscriptMessage({
transcriptPath,
input: {
text: "secret prompt",

View File

@@ -9,14 +9,14 @@ import { logVerbose } from "../globals.js";
import { mimeTypeFromFilePath } from "../media/mime.js";
import { emitSessionTranscriptUpdate } from "./transcript-events.js";
export type PersistedUserTurnMediaInput = {
type PersistedUserTurnMediaInput = {
path?: string | null;
url?: string | null;
contentType?: string | null;
kind?: string | null;
};
export type PersistedUserTurnMediaFields = {
type PersistedUserTurnMediaFields = {
MediaPath?: string;
MediaPaths?: string[];
MediaType?: string;
@@ -33,11 +33,9 @@ export type UserTurnInput = {
mediaOnlyText?: string;
};
export type BuildPersistedUserTurnMessageParams = UserTurnInput;
type UserTurnTranscriptUpdateMode = "inline" | "file-only" | "none";
export type UserTurnTranscriptUpdateMode = "inline" | "file-only" | "none";
export type AppendUserTurnTranscriptMessageParams = {
type AppendUserTurnTranscriptMessageParams = {
transcriptPath: string;
input?: UserTurnInput;
message?: PersistedUserTurnMessage;
@@ -49,12 +47,7 @@ export type AppendUserTurnTranscriptMessageParams = {
updateMode?: UserTurnTranscriptUpdateMode;
};
export type AppendInlineUserTurnTranscriptMessageParams = Omit<
AppendUserTurnTranscriptMessageParams,
"updateMode"
>;
export type PersistUserTurnTranscriptParams = {
type PersistUserTurnTranscriptParams = {
input?: UserTurnInput;
message?: PersistedUserTurnMessage;
sessionId: string;
@@ -69,12 +62,12 @@ export type PersistUserTurnTranscriptParams = {
updateMode?: UserTurnTranscriptUpdateMode;
};
export type UserTurnTranscriptPersistenceTarget = Omit<
type UserTurnTranscriptPersistenceTarget = Omit<
PersistUserTurnTranscriptParams,
"input" | "message" | "updateMode"
>;
export type UserTurnTranscriptFileTarget = {
type UserTurnTranscriptFileTarget = {
transcriptPath: string;
sessionId?: string;
agentId?: string;
@@ -83,18 +76,16 @@ export type UserTurnTranscriptFileTarget = {
config?: OpenClawConfig;
};
export type UserTurnTranscriptTarget =
| UserTurnTranscriptPersistenceTarget
| UserTurnTranscriptFileTarget;
type UserTurnTranscriptTarget = UserTurnTranscriptPersistenceTarget | UserTurnTranscriptFileTarget;
export type UserTurnTranscriptPersistResult = {
type UserTurnTranscriptPersistResult = {
sessionFile: string;
sessionEntry: SessionEntry | undefined;
messageId: string;
message: PersistedUserTurnMessage;
};
export type UserTurnTranscriptTargetResolver =
type UserTurnTranscriptTargetResolver =
| UserTurnTranscriptTarget
| (() => UserTurnTranscriptTarget | undefined | Promise<UserTurnTranscriptTarget | undefined>);
@@ -117,7 +108,7 @@ export type UserTurnTranscriptRecorder = {
}) => Promise<UserTurnTranscriptPersistResult | undefined>;
};
export type CreateUserTurnTranscriptRecorderParams = {
type CreateUserTurnTranscriptRecorderParams = {
input?: UserTurnInput;
message?: PersistedUserTurnMessage;
target: UserTurnTranscriptTargetResolver;
@@ -126,7 +117,7 @@ export type CreateUserTurnTranscriptRecorderParams = {
onPersistenceError?: (error: unknown) => void;
};
export type PersistedUserTurnTextFieldSource = {
type PersistedUserTurnTextFieldSource = {
Transcript?: string | null;
RawBody?: string | null;
CommandBody?: string | null;
@@ -135,12 +126,12 @@ export type PersistedUserTurnTextFieldSource = {
BodyStripped?: string | null;
};
export type ResolvePersistedUserTurnTextOptions = {
type ResolvePersistedUserTurnTextOptions = {
hasMedia?: boolean;
fallback?: string | null;
};
export type PersistedUserTurnMediaFieldSource = {
type PersistedUserTurnMediaFieldSource = {
MediaPath?: string | null;
MediaPaths?: readonly (string | null | undefined)[] | null;
MediaUrl?: string | null;
@@ -285,7 +276,7 @@ export function buildPersistedUserTurnMediaInputsFromFields(
return media;
}
export function buildPersistedUserTurnMediaFields(
function buildPersistedUserTurnMediaFields(
media: readonly PersistedUserTurnMediaInput[] | null | undefined,
): PersistedUserTurnMediaFields {
const entries = Array.isArray(media) ? media : [];
@@ -305,9 +296,7 @@ export function buildPersistedUserTurnMediaFields(
};
}
export function buildPersistedUserTurnMessage(
params: BuildPersistedUserTurnMessageParams,
): PersistedUserTurnMessage {
export function buildPersistedUserTurnMessage(params: UserTurnInput): PersistedUserTurnMessage {
const mediaFields = buildPersistedUserTurnMediaFields(params.media);
const hasMedia = Boolean(mediaFields.MediaPath);
const text = normalizeTranscriptText(params.text);
@@ -440,15 +429,6 @@ export async function appendUserTurnTranscriptMessage(
};
}
export async function appendInlineUserTurnTranscriptMessage(
params: AppendInlineUserTurnTranscriptMessageParams,
): ReturnType<typeof appendUserTurnTranscriptMessage> {
return await appendUserTurnTranscriptMessage({
...params,
updateMode: "inline",
});
}
export async function persistUserTurnTranscript(
params: PersistUserTurnTranscriptParams,
): Promise<UserTurnTranscriptPersistResult | undefined> {