168 lines
7.2 KiB
TypeScript
168 lines
7.2 KiB
TypeScript
// SPEC: PJ2026-0104010803 Workbench唯一投影 draft-2026-06-24-p1-opencode-message-part-authority.
|
|
// Responsibility: AgentRun prompt evidence, message-authority text normalization, and prompt hash/preview DTO fields.
|
|
|
|
import { createHash } from "node:crypto";
|
|
|
|
import { safeTraceId } from "./server-http-utils.ts";
|
|
|
|
export function agentRunPromptMetadataFromParams(params = {}, traceId = null) {
|
|
return agentRunPromptMetadataFromText(firstNonEmpty(params.message, params.prompt, params.text), traceId, "hwlab-code-agent-request", { fullText: true });
|
|
}
|
|
|
|
export function agentRunPromptMetadataFromSession(session = null, traceId = null) {
|
|
const snapshot = session?.session && typeof session.session === "object" ? session.session : null;
|
|
const fromTraceResult = agentRunPromptMetadataFromPayload(snapshot?.traceResults?.[traceId], traceId);
|
|
if (fromTraceResult) return fromTraceResult;
|
|
const messageText = agentRunPromptTextFromMessages(snapshot?.messages, traceId);
|
|
if (messageText) return agentRunPromptMetadataFromText(messageText, traceId, "agent-session-owner-message", { fullText: true });
|
|
const preview = firstNonEmpty(snapshot?.firstUserMessagePreview);
|
|
return preview ? agentRunPromptMetadataFromText(preview, traceId, "agent-session-first-user-preview", { fullText: false }) : null;
|
|
}
|
|
|
|
export function agentRunPromptMetadataFromText(value, traceId = null, source = "hwlab-code-agent-request", options = {}) {
|
|
const promptText = promptTextValue(value);
|
|
if (!promptText) return null;
|
|
return agentRunPromptMetadataFromParts({
|
|
traceId,
|
|
source,
|
|
promptSha256: options.fullText === true ? sha256Text(promptText) : undefined,
|
|
promptSummary: promptText,
|
|
promptPreview: promptText,
|
|
textChars: options.fullText === true ? promptText.length : undefined
|
|
});
|
|
}
|
|
|
|
export function agentRunPromptFields(metadata = null) {
|
|
if (!metadata) return {};
|
|
return compactObject({
|
|
promptId: metadata.promptId,
|
|
promptSha256: metadata.promptSha256,
|
|
promptSummary: metadata.promptSummary,
|
|
promptPreview: metadata.promptPreview,
|
|
prompt: metadata.prompt
|
|
}) ?? {};
|
|
}
|
|
|
|
export function agentRunPromptEventFields(metadata = null) {
|
|
if (!metadata) return {};
|
|
return compactObject({
|
|
promptId: metadata.promptId,
|
|
promptSha256: metadata.promptSha256,
|
|
promptSummary: metadata.promptSummary,
|
|
promptPreview: metadata.promptPreview
|
|
}) ?? {};
|
|
}
|
|
|
|
export function messageAuthorityTextValue(value) {
|
|
const extracted = promptTextRaw(value).replace(/\r\n?/gu, "\n");
|
|
if (!extracted.trim() || extracted.trim() === "[object Object]") return "";
|
|
return extracted;
|
|
}
|
|
|
|
function agentRunPromptMetadataFromPayload(payload = null, traceId = null) {
|
|
if (!payload || typeof payload !== "object") return null;
|
|
const prompt = payload.prompt && typeof payload.prompt === "object" ? payload.prompt : null;
|
|
const textValue = firstNonEmpty(prompt?.text, prompt?.summary, prompt?.preview, payload.promptSummary, payload.promptPreview);
|
|
const fromText = agentRunPromptMetadataFromText(textValue, traceId, prompt?.source ?? payload.promptSource ?? "agentrun-prompt-metadata", { fullText: Boolean(prompt?.text) });
|
|
return agentRunPromptMetadataFromParts({
|
|
traceId,
|
|
source: prompt?.source ?? payload.promptSource ?? fromText?.prompt?.source ?? "agentrun-prompt-metadata",
|
|
promptId: firstNonEmpty(payload.promptId, prompt?.id, fromText?.promptId),
|
|
promptSha256: firstNonEmpty(payload.promptSha256, prompt?.sha256, fromText?.promptSha256),
|
|
promptSummary: firstNonEmpty(payload.promptSummary, prompt?.summary, fromText?.promptSummary),
|
|
promptPreview: firstNonEmpty(payload.promptPreview, prompt?.preview, fromText?.promptPreview),
|
|
textChars: Number.isInteger(prompt?.textChars) ? prompt.textChars : fromText?.prompt?.textChars
|
|
});
|
|
}
|
|
|
|
function agentRunPromptTextFromMessages(messages = [], traceId = null) {
|
|
const list = Array.isArray(messages) ? messages : [];
|
|
const resolvedTraceId = safeTraceId(traceId);
|
|
const exact = list.find((message) => String(message?.role ?? "").toLowerCase() === "user" && (!resolvedTraceId || safeTraceId(message?.traceId) === resolvedTraceId));
|
|
const fallback = list.find((message) => String(message?.role ?? "").toLowerCase() === "user");
|
|
return firstNonEmpty(exact?.text, exact?.content, fallback?.text, fallback?.content);
|
|
}
|
|
|
|
function agentRunPromptMetadataFromParts({ traceId = null, source = "hwlab-code-agent-request", promptId = null, promptSha256 = null, promptSummary = null, promptPreview = null, textChars = undefined } = {}) {
|
|
const digest = firstNonEmpty(promptSha256);
|
|
const summary = clippedPromptText(firstNonEmpty(promptSummary, promptPreview), 240);
|
|
const preview = clippedPromptText(firstNonEmpty(promptPreview, promptSummary), 500);
|
|
const explicitId = firstNonEmpty(promptId);
|
|
if (!explicitId && !digest && !summary && !preview) return null;
|
|
const id = explicitId || agentRunPromptId(traceId, digest);
|
|
const prompt = compactObject({
|
|
id,
|
|
sha256: digest || undefined,
|
|
summary,
|
|
preview,
|
|
textChars: Number.isInteger(textChars) ? textChars : undefined,
|
|
source,
|
|
valuesPrinted: false
|
|
});
|
|
return compactObject({
|
|
promptId: id,
|
|
promptSha256: digest || undefined,
|
|
promptSummary: summary,
|
|
promptPreview: preview,
|
|
prompt,
|
|
valuesPrinted: false
|
|
}) ?? null;
|
|
}
|
|
|
|
function agentRunPromptId(traceId = null, digest = null) {
|
|
const traceSuffix = safeTraceId(traceId)?.slice(4).replace(/[^A-Za-z0-9_.:-]/gu, "_");
|
|
const digestSuffix = firstNonEmpty(digest)?.slice(0, 16);
|
|
const suffix = firstNonEmpty(traceSuffix, digestSuffix);
|
|
return suffix ? `prm_${suffix}` : undefined;
|
|
}
|
|
|
|
function clippedPromptText(value, limit) {
|
|
const promptText = promptTextValue(value);
|
|
if (!promptText) return undefined;
|
|
return promptText.length > limit ? `${promptText.slice(0, limit)}...` : promptText;
|
|
}
|
|
|
|
function promptTextValue(value) {
|
|
const extracted = promptTextRaw(value).replace(/\s+/gu, " ").trim();
|
|
if (!extracted || extracted === "[object Object]") return "";
|
|
return extracted;
|
|
}
|
|
|
|
function promptTextRaw(value) {
|
|
if (value === undefined || value === null) return "";
|
|
if (typeof value === "string") return value;
|
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
if (Array.isArray(value)) return value.map(promptTextRaw).filter(Boolean).join("\n");
|
|
if (typeof value === "object") {
|
|
for (const key of ["text", "content", "message", "prompt", "value", "summary", "preview", "title"]) {
|
|
const text = promptTextRaw(value[key]);
|
|
if (text) return text;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function sha256Text(value) {
|
|
return createHash("sha256").update(String(value)).digest("hex");
|
|
}
|
|
|
|
function compactObject(value) {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
const result = {};
|
|
for (const [key, item] of Object.entries(value)) {
|
|
if (item === undefined || item === null) continue;
|
|
if (Array.isArray(item) && item.length === 0) continue;
|
|
if (typeof item === "object" && !Array.isArray(item) && Object.keys(item).length === 0) continue;
|
|
result[key] = item;
|
|
}
|
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
}
|
|
|
|
function firstNonEmpty(...values) {
|
|
for (const value of values) {
|
|
const text = String(value ?? "").trim();
|
|
if (text) return text;
|
|
}
|
|
return null;
|
|
}
|