Merge pull request #1256 from pikasTech/fix/issue-1252-prompt-metadata

fix: expose Code Agent prompt metadata
This commit is contained in:
Lyon
2026-06-15 16:34:14 +08:00
committed by GitHub
3 changed files with 241 additions and 2 deletions
+128 -1
View File
@@ -1,4 +1,4 @@
import { randomUUID } from "node:crypto";
import { createHash, randomUUID } from "node:crypto";
import { defaultCodeAgentTraceStore } from "./code-agent-trace-store.ts";
import { decorateCodeAgentSession } from "./code-agent-session-lifecycle.ts";
@@ -215,6 +215,7 @@ export function initialAgentRunChatResult({ params = {}, options = {}, traceId }
const env = options.env ?? process.env;
const timestamp = nowIso(options.now);
const backendProfile = resolveAgentRunBackendProfile(env, params);
const promptEvidence = agentRunPromptMetadataFromParams(params, traceId);
return {
accepted: true,
status: "running",
@@ -234,6 +235,7 @@ export function initialAgentRunChatResult({ params = {}, options = {}, traceId }
sessionMode: AGENTRUN_SESSION_MODE,
implementationType: AGENTRUN_IMPLEMENTATION_TYPE,
threadContinuityPolicy: THREAD_CONTINUITY_POLICY,
...agentRunPromptFields(promptEvidence),
agentRun: {
adapter: ADAPTER_ID,
status: "pending-dispatch",
@@ -257,6 +259,7 @@ export async function submitAgentRunChatTurn({ params = {}, options = {}, traceI
const startedAt = nowIso(options.now);
const ownerApiKey = await resolveOwnerApiKey({ params, options, now: () => nowIso(options.now) });
const toolCapabilities = await resolveToolCapabilities({ params, options });
const promptEvidence = agentRunPromptMetadataFromParams(params, traceId);
traceStore.ensure(traceId, agentRunTraceMeta(env, params));
traceStore.append(traceId, {
@@ -267,6 +270,7 @@ export async function submitAgentRunChatTurn({ params = {}, options = {}, traceI
waitingFor: "agentrun-run-reuse-or-create",
adapter: ADAPTER_ID,
managerHost: new URL(managerUrl).hostname,
...agentRunPromptEventFields(promptEvidence),
valuesPrinted: false
}, agentRunTraceMeta(env, params));
@@ -674,6 +678,7 @@ export async function loadPersistedAgentRunResult(traceId, options = {}) {
if (!agentRun?.runId) return null;
const backendProfile = firstNonEmpty(agentRun.backendProfile, "deepseek");
const status = firstNonEmpty(session?.status === "canceled" ? "canceled" : null, agentRun.terminalStatus, agentRun.commandState, agentRun.status, "running");
const promptEvidence = agentRunPromptMetadataFromSession(session, safeId);
return {
accepted: true,
status,
@@ -683,6 +688,7 @@ export async function loadPersistedAgentRunResult(traceId, options = {}) {
sessionId: safeSessionId(session?.id) || agentRun.sessionId || null,
threadId: safeOpaqueId(session?.threadId) || agentRun.threadId || null,
messageId: `msg_${safeId.slice(4)}`,
...agentRunPromptFields(promptEvidence),
createdAt: session?.startedAt ?? session?.updatedAt ?? nowIso(options.now),
updatedAt: session?.updatedAt ?? nowIso(options.now),
ownerUserId: session?.ownerUserId,
@@ -879,6 +885,121 @@ function rejectRemovedResourceWorkspaceFiles(value) {
throw adapterError("legacy_workspace_files_removed", "workspaceFiles/resourceWorkspaceFiles are removed; use AgentRun ResourceBundleRef kind=gitbundle with bundles[]");
}
function agentRunPromptMetadataFromParams(params = {}, traceId = null) {
return agentRunPromptMetadataFromText(firstNonEmpty(params.message, params.prompt, params.text), traceId, "hwlab-code-agent-request", { fullText: true });
}
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;
}
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 agentRunPromptMetadataFromText(value, traceId = null, source = "hwlab-code-agent-request", options = {}) {
const promptText = String(value ?? "").trim();
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
});
}
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 agentRunPromptFields(metadata = null) {
if (!metadata) return {};
return compactObject({
promptId: metadata.promptId,
promptSha256: metadata.promptSha256,
promptSummary: metadata.promptSummary,
promptPreview: metadata.promptPreview,
prompt: metadata.prompt
}) ?? {};
}
function agentRunPromptEventFields(metadata = null) {
if (!metadata) return {};
return compactObject({
promptId: metadata.promptId,
promptSha256: metadata.promptSha256,
promptSummary: metadata.promptSummary,
promptPreview: metadata.promptPreview
}) ?? {};
}
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 = String(value ?? "").trim();
if (!promptText) return undefined;
return promptText.length > limit ? `${promptText.slice(0, limit)}...` : promptText;
}
function sha256Text(value) {
return createHash("sha256").update(String(value)).digest("hex");
}
function adapterError(code, message, details = {}) {
return Object.assign(new Error(message), {
code,
@@ -889,12 +1010,15 @@ function adapterError(code, message, details = {}) {
function buildAgentRunCommandInput({ params, traceId, backendProfile, sessionId }) {
const prompt = String(params.message ?? params.prompt ?? "").trim();
const promptEvidence = agentRunPromptMetadataFromText(prompt, traceId, "agentrun-command-payload", { fullText: true });
const threadId = safeOpaqueId(params.threadId);
return {
type: "turn",
payload: {
prompt,
message: prompt,
...agentRunPromptEventFields(promptEvidence),
...(promptEvidence?.prompt ? { promptMetadata: promptEvidence.prompt } : {}),
traceId,
projectId: firstNonEmpty(params.projectId) || null,
conversationId: safeConversationId(params.conversationId) || null,
@@ -913,12 +1037,15 @@ function buildAgentRunCommandInput({ params, traceId, backendProfile, sessionId
function buildAgentRunSteerCommandInput({ params, traceId, steerTraceId, mapped }) {
const prompt = String(params.message ?? params.prompt ?? params.text ?? "").trim();
const promptEvidence = agentRunPromptMetadataFromText(prompt, steerTraceId, "agentrun-steer-command-payload", { fullText: true });
return {
type: "steer",
payload: {
prompt,
message: prompt,
text: prompt,
...agentRunPromptEventFields(promptEvidence),
...(promptEvidence?.prompt ? { promptMetadata: promptEvidence.prompt } : {}),
traceId: steerTraceId,
targetTraceId: traceId,
targetCommandId: mapped.agentRun.commandId,
+3
View File
@@ -346,7 +346,10 @@ function normalizeTraceEvent(event, { traceId, seq, now, fallbackRunnerKind } =
exitCode: Number.isInteger(event.exitCode) ? event.exitCode : undefined,
durationMs: typeof event.durationMs === "number" ? Math.max(0, Math.trunc(event.durationMs)) : undefined,
outputBytes: typeof event.outputBytes === "number" ? Math.max(0, Math.trunc(event.outputBytes)) : undefined,
promptId: safeText(event.promptId, 180),
promptSha256: safeText(event.promptSha256, 80),
promptSummary: safeText(event.promptSummary, 240),
promptPreview: safeText(event.promptPreview, 500),
outputSummary: safeText(event.outputSummary, 400),
stdoutSummary: safeText(event.stdoutSummary, 600),
stderrSummary: safeText(event.stderrSummary, 600),
+110 -1
View File
@@ -1,5 +1,5 @@
import { randomUUID } from "node:crypto";
import { createHash, randomUUID } from "node:crypto";
import { M3_IO_CONTROL_ROUTE, M3_STATUS_ROUTE, describeM3StatusLive, handleM3IoControl } from "./m3-io-control.ts";
import {
@@ -1803,6 +1803,7 @@ function codeAgentSessionOwnerEvidence(payload = {}, params = {}) {
const traceSummary = codeAgentTraceSummaryEvidence(payload, traceId, finalResponse);
const messages = codeAgentConversationMessagesEvidence(payload, params, traceId, finalResponse);
const firstUserMessagePreview = messages.find((message) => message.role === "user")?.text?.slice(0, 240) ?? null;
const promptEvidence = codeAgentPromptMetadata(payload, params, traceId);
const traceResult = codeAgentTraceResultEvidence(payload, params, traceId, finalResponse, traceSummary);
return {
provider: payload.provider ?? null,
@@ -1817,6 +1818,7 @@ function codeAgentSessionOwnerEvidence(payload = {}, params = {}) {
threadId: payload.session?.threadId ?? payload.sessionReuse?.threadId ?? payload.threadId ?? params.threadId ?? null,
traceId,
projectId: payload.projectId ?? params.projectId ?? null,
...codeAgentPromptFields(promptEvidence),
finalResponse,
traceSummary,
...(traceResult ? { traceResults: { [traceResult.traceId]: traceResult } } : {}),
@@ -1838,6 +1840,7 @@ function codeAgentTraceResultEvidence(payload = {}, params = {}, traceId = null,
const conversationId = safeConversationId(payload.conversationId ?? agentRun?.conversationId ?? resultSession?.conversationId ?? sessionReuse?.conversationId ?? params.conversationId) || null;
const sessionId = safeSessionId(payload.sessionId ?? resultSession?.sessionId ?? sessionReuse?.sessionId ?? params.sessionId) || null;
const threadId = safeOpaqueId(payload.threadId ?? resultSession?.threadId ?? sessionReuse?.threadId ?? params.threadId ?? agentRun?.threadId) || null;
const promptEvidence = codeAgentPromptMetadata(payload, params, resolvedTraceId);
if (!agentRun && !finalResponse && !traceSummary) return null;
return {
traceId: resolvedTraceId,
@@ -1845,6 +1848,7 @@ function codeAgentTraceResultEvidence(payload = {}, params = {}, traceId = null,
conversationId,
sessionId,
threadId,
...codeAgentPromptFields(promptEvidence),
messageId: finalResponse?.messageId ?? payload.messageId ?? null,
createdAt: payload.createdAt ?? finalResponse?.createdAt ?? null,
updatedAt: payload.updatedAt ?? finalResponse?.updatedAt ?? null,
@@ -1857,6 +1861,105 @@ function codeAgentTraceResultEvidence(payload = {}, params = {}, traceId = null,
};
}
function codeAgentPromptMetadata(payload = {}, params = {}, traceId = null) {
const prompt = payload?.prompt && typeof payload.prompt === "object" ? payload.prompt : null;
const rawPrompt = firstNonEmptyValue(
params.message,
params.prompt,
params.text,
payload.userMessage,
typeof payload.prompt === "string" ? payload.prompt : null
);
const fromText = codeAgentPromptMetadataFromText(rawPrompt, traceId, "code-agent-request", { fullText: Boolean(rawPrompt) });
return codeAgentPromptMetadataFromParts({
traceId,
source: prompt?.source ?? payload.promptSource ?? fromText?.prompt?.source ?? "code-agent-request",
promptId: firstNonEmptyValue(payload.promptId, prompt?.id, fromText?.promptId),
promptSha256: firstNonEmptyValue(payload.promptSha256, prompt?.sha256, fromText?.promptSha256),
promptSummary: firstNonEmptyValue(payload.promptSummary, prompt?.summary, fromText?.promptSummary),
promptPreview: firstNonEmptyValue(payload.promptPreview, prompt?.preview, fromText?.promptPreview),
textChars: Number.isInteger(prompt?.textChars) ? prompt.textChars : fromText?.prompt?.textChars
});
}
function codeAgentPromptMetadataFromText(value, traceId = null, source = "code-agent-request", options = {}) {
const promptText = String(value ?? "").trim();
if (!promptText) return null;
return codeAgentPromptMetadataFromParts({
traceId,
source,
promptSha256: options.fullText === true ? sha256Text(promptText) : undefined,
promptSummary: promptText,
promptPreview: promptText,
textChars: options.fullText === true ? promptText.length : undefined
});
}
function codeAgentPromptMetadataFromParts({ traceId = null, source = "code-agent-request", promptId = null, promptSha256 = null, promptSummary = null, promptPreview = null, textChars = undefined } = {}) {
const digest = firstNonEmptyValue(promptSha256);
const summary = clippedPromptText(firstNonEmptyValue(promptSummary, promptPreview), 240);
const preview = clippedPromptText(firstNonEmptyValue(promptPreview, promptSummary), 500);
const explicitId = firstNonEmptyValue(promptId);
if (!explicitId && !digest && !summary && !preview) return null;
const id = explicitId || codeAgentPromptId(traceId, digest);
const prompt = compactCodeAgentObject({
id,
sha256: digest || undefined,
summary,
preview,
textChars: Number.isInteger(textChars) ? textChars : undefined,
source,
valuesPrinted: false
});
return compactCodeAgentObject({
promptId: id,
promptSha256: digest || undefined,
promptSummary: summary,
promptPreview: preview,
prompt,
valuesPrinted: false
}) ?? null;
}
function codeAgentPromptFields(metadata = null) {
if (!metadata) return {};
return compactCodeAgentObject({
promptId: metadata.promptId,
promptSha256: metadata.promptSha256,
promptSummary: metadata.promptSummary,
promptPreview: metadata.promptPreview,
prompt: metadata.prompt
}) ?? {};
}
function codeAgentPromptId(traceId = null, digest = null) {
const traceSuffix = safeTraceId(traceId)?.slice(4).replace(/[^A-Za-z0-9_.:-]/gu, "_");
const digestSuffix = firstNonEmptyValue(digest)?.slice(0, 16);
const suffix = firstNonEmptyValue(traceSuffix, digestSuffix);
return suffix ? `prm_${suffix}` : undefined;
}
function clippedPromptText(value, limit) {
const promptText = String(value ?? "").trim();
if (!promptText) return undefined;
return promptText.length > limit ? `${promptText.slice(0, limit)}...` : promptText;
}
function sha256Text(value) {
return createHash("sha256").update(String(value)).digest("hex");
}
function compactCodeAgentObject(value) {
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 codeAgentBillingReservationEvidence(value = null) {
if (!value || typeof value !== "object") return null;
const reservationId = textValue(value.reservationId);
@@ -2212,6 +2315,7 @@ function traceSnapshotWithTerminalEvidence(snapshot, result, traceId, refreshErr
conversationId: evidence.conversationId,
sessionId: evidence.sessionId,
threadId: evidence.threadId,
...codeAgentPromptFields(evidence),
agentRun: evidence.agentRun,
finalResponse: evidence.finalResponse,
traceSummary: evidence.traceSummary,
@@ -2228,6 +2332,7 @@ function traceSnapshotWithTerminalEvidence(snapshot, result, traceId, refreshErr
conversationId: evidence.conversationId,
sessionId: evidence.sessionId,
threadId: evidence.threadId,
...codeAgentPromptFields(evidence),
agentRun: evidence.agentRun,
traceSummary: evidence.traceSummary,
finalResponse: evidence.finalResponse,
@@ -2243,6 +2348,7 @@ function traceSnapshotWithAttachedTerminalEvidence(snapshot, evidence, refreshEr
conversationId: snapshot.conversationId ?? evidence.conversationId,
sessionId: snapshot.sessionId ?? evidence.sessionId,
threadId: snapshot.threadId ?? evidence.threadId,
...codeAgentPromptFields(evidence),
agentRun: snapshot.agentRun ?? evidence.agentRun,
finalResponse: snapshot.finalResponse ?? evidence.finalResponse,
traceSummary: snapshot.traceSummary ?? evidence.traceSummary,
@@ -2265,6 +2371,7 @@ function agentRunTerminalTraceEvidence(result, traceId) {
if (!result || typeof result !== "object") return null;
const storedSummary = result.traceSummary && typeof result.traceSummary === "object" && result.traceSummary.source === "agentrun-command-result" ? result.traceSummary : null;
const finalResponse = agentRunTerminalFinalResponse(result, traceId);
const promptEvidence = codeAgentPromptMetadata(result, {}, traceId);
const agentRun = result.agentRun && typeof result.agentRun === "object" ? {
adapter: result.agentRun.adapter ?? null,
runId: result.agentRun.runId ?? null,
@@ -2294,6 +2401,7 @@ function agentRunTerminalTraceEvidence(result, traceId) {
conversationId: safeConversationId(result.conversationId) || null,
sessionId: safeSessionId(result.sessionId) || null,
threadId: safeOpaqueId(result.threadId) || null,
...codeAgentPromptFields(promptEvidence),
agentRun,
finalResponse,
traceSummary
@@ -2405,6 +2513,7 @@ function terminalEvidencePayload(evidence) {
conversationId: evidence.conversationId,
sessionId: evidence.sessionId,
threadId: evidence.threadId,
...codeAgentPromptFields(evidence),
agentRun: evidence.agentRun,
traceSummary: evidence.traceSummary,
finalResponse: evidence.finalResponse,