1615 lines
67 KiB
TypeScript
1615 lines
67 KiB
TypeScript
// SPEC: PJ2026-0104010803 Workbench唯一投影 draft-2026-06-20-p2-terminal-outbox-recovery; PJ2026-010403 API契约 draft-2026-06-20-p2-terminal-outbox-recovery; PJ2026-010205 HWLAB接入 draft-2026-06-25-p0-session-warm-runner-contract; PJ2026-0102 Agent编排 draft-2026-06-17-r0; PJ2026-01060505 Workbench Performance draft-2026-06-17-p0.
|
|
// Responsibility: Shared Code Agent HTTP lifecycle, billing, ownership, projection-read, and response helpers.
|
|
|
|
import { createHash, randomUUID } from "node:crypto";
|
|
|
|
import { M3_IO_CONTROL_ROUTE, M3_STATUS_ROUTE, describeM3StatusLive, handleM3IoControl } from "./m3-io-control.ts";
|
|
import {
|
|
agentRunSessionEvidence,
|
|
cancelAgentRunChatTurn,
|
|
codeAgentAgentRunAdapterEnabled,
|
|
initialAgentRunChatResult,
|
|
loadPersistedAgentRunResult,
|
|
steerAgentRunChatTurn,
|
|
submitAgentRunChatTurn
|
|
} from "./code-agent-agentrun-adapter.ts";
|
|
import { createCodeAgentErrorPayload, handleCodeAgentChat } from "./code-agent-chat.ts";
|
|
import { defaultCodeAgentTraceStore } from "./code-agent-trace-store.ts";
|
|
import { codeAgentSessionLifecycleSummary } from "./code-agent-session-lifecycle.ts";
|
|
import { messageAuthorityTextValue } from "./code-agent-agentrun-prompt.ts";
|
|
import { recordWorkbenchSessionOwner } from "./workbench-projection-writer.ts";
|
|
import { createWorkbenchReadModel } from "./workbench-read-model.ts";
|
|
import { codeAgentOtelTraceFields, emitCodeAgentOtelSpan } from "./otel-trace.ts";
|
|
import {
|
|
firstHeaderValue,
|
|
getHeader,
|
|
parsePositiveInteger,
|
|
positiveInteger,
|
|
readBody,
|
|
safeConversationId,
|
|
safeOpaqueId,
|
|
safeSessionId,
|
|
safeTraceId,
|
|
sendJson,
|
|
truthyFlag
|
|
} from "./server-http-utils.ts";
|
|
|
|
const DEFAULT_CODE_AGENT_RESULT_TRACE_EVENT_LIMIT = 120;
|
|
const DEFAULT_CODE_AGENT_TRACE_PAGE_LIMIT = 100;
|
|
const MAX_CODE_AGENT_TRACE_PAGE_LIMIT = 100;
|
|
const DEFAULT_CODE_AGENT_TURN_STATUS_REFRESH_TIMEOUT_MS = 2500;
|
|
const DEFAULT_CODE_AGENT_PROJECT_ID = "prj_hwpod_workbench";
|
|
const DEFAULT_CODE_AGENT_PROVIDER_PROFILE = "deepseek";
|
|
const CODE_AGENT_TERMINAL_STATUSES = new Set(["completed", "failed", "blocked", "timeout", "cancelled", "canceled"]);
|
|
const CODE_AGENT_PROVIDER_PROFILE_ALIASES = Object.freeze({ codex: "codex-api" });
|
|
const CODE_AGENT_PROVIDER_PROFILE_ID_PATTERN = /^[a-z0-9][a-z0-9.-]{0,63}$/u;
|
|
const CODE_AGENT_PROVIDER_PROFILE_LABELS = Object.freeze({
|
|
"deepseek": "DeepSeek",
|
|
"codex-api": "Codex API",
|
|
"gpt.pika": "gpt.pika",
|
|
"minimax-m3": "MiniMax-M3 via AgentRun",
|
|
"runtime-default": "运行默认"
|
|
});
|
|
const DEFAULT_CODE_AGENT_CODEX_API_MODEL = "gpt-5.5";
|
|
const DEFAULT_CODE_AGENT_CODEX_API_BASE_URL = "http://127.0.0.1:49280/responses";
|
|
const DEFAULT_CODE_AGENT_DEEPSEEK_MODEL = "deepseek-chat";
|
|
const DEFAULT_CODE_AGENT_MINIMAX_M3_MODEL = "MiniMax-M3";
|
|
|
|
|
|
function normalizeCodeAgentProviderProfile(value) {
|
|
const text = String(value ?? DEFAULT_CODE_AGENT_PROVIDER_PROFILE).trim().toLowerCase();
|
|
if (!text) return DEFAULT_CODE_AGENT_PROVIDER_PROFILE;
|
|
if (text === "runtime-default") return text;
|
|
const normalized = CODE_AGENT_PROVIDER_PROFILE_ALIASES[text] ?? text;
|
|
return CODE_AGENT_PROVIDER_PROFILE_ID_PATTERN.test(normalized) ? normalized : DEFAULT_CODE_AGENT_PROVIDER_PROFILE;
|
|
}
|
|
|
|
function firstNonEmptyValue(...values) {
|
|
for (const value of values) {
|
|
const text = String(value ?? "").trim();
|
|
if (text) return text;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function recordCodeAgentSessionInputFact({ params = {}, options = {}, traceId, delivery = "queue", status = "admitted", messageId = null, commandId = null, agentRun = null, error = null } = {}) {
|
|
const runtimeStore = options.runtimeStore ?? null;
|
|
if (typeof runtimeStore?.writeWorkbenchFacts !== "function") return null;
|
|
const fact = buildCodeAgentSessionInputFact({ params, options, traceId, delivery, status, messageId, commandId, agentRun, error });
|
|
if (!fact) return null;
|
|
return runtimeStore.writeWorkbenchFacts({ facts: { inputs: [fact] } }, codeAgentSessionInputRequestMeta(fact, params, options));
|
|
}
|
|
|
|
function buildCodeAgentSessionInputFact({ params = {}, options = {}, traceId, delivery = "queue", status = "admitted", messageId = null, commandId = null, agentRun = null, error = null } = {}) {
|
|
const resolvedTraceId = safeTraceId(traceId ?? params.traceId) || null;
|
|
const lifecycle = codeAgentTurnLifecycleFields(resolvedTraceId, params);
|
|
const sessionId = safeSessionId(params.sessionId ?? params.session?.sessionId) || null;
|
|
if (!sessionId) return null;
|
|
const resolvedMessageId = safeMessageId(messageId ?? params.messageId ?? lifecycle.userMessageId) || lifecycle.userMessageId;
|
|
const resolvedCommandId = textValue(commandId ?? agentRun?.commandId ?? params.commandId ?? params.agentRun?.commandId) || null;
|
|
const normalizedDelivery = codeAgentInputDelivery(delivery);
|
|
const normalizedStatus = codeAgentInputStatus(status);
|
|
const now = new Date().toISOString();
|
|
const sourceEventId = `${sessionId}:${lifecycle.turnId}:${normalizedDelivery}:${resolvedTraceId ?? resolvedCommandId ?? resolvedMessageId}`;
|
|
// Command allocation happens after the initial admitting write. Keep one stable
|
|
// row across admitting -> blocked/failed/promoted transitions for the trace.
|
|
const inputId = stableCodeAgentInputId({ sessionId, turnId: lifecycle.turnId, traceId: resolvedTraceId, messageId: resolvedMessageId, delivery: normalizedDelivery, sourceEventId });
|
|
return {
|
|
inputId,
|
|
sessionId,
|
|
turnId: lifecycle.turnId,
|
|
traceId: resolvedTraceId,
|
|
messageId: resolvedMessageId,
|
|
commandId: resolvedCommandId,
|
|
runId: textValue(agentRun?.runId ?? params.agentRun?.runId) || null,
|
|
dispatchIntentId: textValue(agentRun?.dispatchIntentId ?? params.agentRun?.dispatchIntentId) || null,
|
|
runnerJobId: textValue(agentRun?.runnerJobId ?? params.agentRun?.runnerJobId) || null,
|
|
durableDispatch: agentRun?.durableDispatch === true || params.agentRun?.durableDispatch === true,
|
|
delivery: normalizedDelivery,
|
|
status: normalizedStatus,
|
|
errorCode: textValue(error?.code ?? error?.error?.code) || null,
|
|
sourceEventId,
|
|
promptHash: params.message || params.prompt || params.text ? sha256Text(params.message ?? params.prompt ?? params.text).slice(0, 16) : null,
|
|
textBytes: params.message || params.prompt || params.text ? Buffer.byteLength(String(params.message ?? params.prompt ?? params.text), "utf8") : 0,
|
|
valuesRedacted: true,
|
|
secretMaterialStored: false,
|
|
createdAt: now,
|
|
updatedAt: now
|
|
};
|
|
}
|
|
|
|
function codeAgentSessionInputRequestMeta(fact, params = {}, options = {}) {
|
|
return {
|
|
sessionId: fact.sessionId,
|
|
traceId: fact.traceId,
|
|
turnId: fact.turnId,
|
|
messageId: fact.messageId,
|
|
ownerUserId: options.actor?.id ?? params.ownerUserId,
|
|
projectId: params.projectId ?? DEFAULT_CODE_AGENT_PROJECT_ID,
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
function stableCodeAgentInputId(value) {
|
|
const payload = Object.fromEntries(Object.entries(value ?? {}).filter(([, item]) => item !== undefined && item !== null && item !== "").sort(([left], [right]) => left.localeCompare(right)));
|
|
return `wsi_${sha256Text(JSON.stringify(payload)).slice(0, 32)}`;
|
|
}
|
|
|
|
function codeAgentInputDelivery(value) {
|
|
const text = textValue(value).toLowerCase();
|
|
return ["queue", "steer", "cancel"].includes(text) ? text : "queue";
|
|
}
|
|
|
|
function codeAgentInputStatus(value) {
|
|
const text = textValue(value).toLowerCase().replace(/-/gu, "_");
|
|
return ["admitting", "admitted", "promoted", "blocked", "failed", "canceled", "completed"].includes(text) ? text : "admitted";
|
|
}
|
|
|
|
function codeAgentResultTimingFields(...sources) {
|
|
const records = sources.filter((source) => source && typeof source === "object");
|
|
const timingSource = records.map((source) => source.timing).find((timing) => timing && typeof timing === "object") ?? {};
|
|
const startedAt = firstTimestampIso(timingSource.startedAt, ...records.map((source) => source.startedAt), ...records.map((source) => source.createdAt));
|
|
const lastEventAt = firstLatestTimestampIso(timingSource.lastEventAt, ...records.map((source) => source.lastEventAt), ...records.map((source) => source.updatedAt), ...records.map((source) => source.createdAt));
|
|
const finishedAt = firstLatestTimestampIso(timingSource.finishedAt, ...records.map((source) => source.finishedAt), ...records.map((source) => source.completedAt));
|
|
const durationMs = numberOrNull(timingSource.durationMs ?? records.map((source) => source.durationMs).find((value) => numberOrNull(value) !== null));
|
|
const observedAt = timestampIsoOrNull(timingSource.observedAt);
|
|
const lastEventAgeMs = numberOrNull(timingSource.lastEventAgeMs);
|
|
if (!startedAt && !lastEventAt && !finishedAt && durationMs === null && lastEventAgeMs === null) return {};
|
|
const timing = {
|
|
...(timingSource && typeof timingSource === "object" ? timingSource : {}),
|
|
startedAt: startedAt ?? null,
|
|
lastEventAt: lastEventAt ?? null,
|
|
finishedAt: finishedAt ?? null,
|
|
durationMs,
|
|
observedAt: observedAt ?? null,
|
|
lastEventAgeMs,
|
|
valuesRedacted: timingSource.valuesRedacted !== false
|
|
};
|
|
return {
|
|
timing,
|
|
startedAt: timing.startedAt,
|
|
lastEventAt: timing.lastEventAt,
|
|
finishedAt: timing.finishedAt,
|
|
durationMs: timing.durationMs
|
|
};
|
|
}
|
|
|
|
function firstTimestampIso(...values) {
|
|
for (const value of values) {
|
|
const timestamp = timestampIsoOrNull(value);
|
|
if (timestamp) return timestamp;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function firstLatestTimestampIso(...values) {
|
|
let latest = null;
|
|
let latestMs = Number.NEGATIVE_INFINITY;
|
|
for (const value of values) {
|
|
const timestamp = timestampIsoOrNull(value);
|
|
if (!timestamp) continue;
|
|
const ms = Date.parse(timestamp);
|
|
if (!Number.isFinite(ms) || ms < latestMs) continue;
|
|
latest = timestamp;
|
|
latestMs = ms;
|
|
}
|
|
return latest;
|
|
}
|
|
|
|
function codeAgentChatShortConnectionRequested(request, params, options = {}) {
|
|
const header = firstHeaderValue(request, "prefer");
|
|
const explicit = firstHeaderValue(request, "x-hwlab-short-connection");
|
|
const envValue = options.env?.HWLAB_CODE_AGENT_CHAT_SHORT_CONNECTION;
|
|
return codeAgentAgentRunAdapterEnabled(options.env ?? process.env) ||
|
|
/(?:^|,)\s*respond-async\s*(?:,|$)/iu.test(String(header ?? "")) ||
|
|
truthyFlag(explicit) ||
|
|
params?.shortConnection === true ||
|
|
truthyFlag(envValue);
|
|
}
|
|
|
|
async function preflightCodeAgentBilling({ params = {}, options = {}, traceId, response, sendResponse = true } = {}) {
|
|
const client = options.userBillingClient;
|
|
if (!codeAgentBillingEnabled(options) || !client?.configured || !options.userBillingAuth?.active) return null;
|
|
const estimatedCredits = parsePositiveInteger(options.env?.HWLAB_USER_BILLING_CODE_AGENT_ESTIMATED_CREDITS, 1);
|
|
const body = {
|
|
...(options.actorApiKeySecret ? { apiKey: options.actorApiKeySecret } : { userId: options.actor?.id }),
|
|
serviceId: "hwlab-code-agent",
|
|
estimatedCredits,
|
|
idempotencyKey: `code-agent:${traceId}:preflight`,
|
|
metadata: codeAgentBillingMetadata(params, traceId, { stage: "preflight" })
|
|
};
|
|
const result = await client.billingPreflight(body);
|
|
if (result.ok && result.body?.allowed !== false) return { reservation: sanitizeBillingReservation(result.body) };
|
|
const status = [402, 403, 429].includes(Number(result.status)) ? Number(result.status) : 503;
|
|
const payload = {
|
|
ok: false,
|
|
accepted: false,
|
|
status: status === 402 ? "payment_required" : status === 403 ? "not_entitled" : status === 429 ? "rate_limited" : "billing_unavailable",
|
|
traceId,
|
|
error: {
|
|
code: result.error?.code ?? (status === 402 ? "insufficient_credits" : "user_billing_preflight_failed"),
|
|
layer: "billing",
|
|
retryable: status === 429 || status === 503,
|
|
message: result.error?.message ?? "Code Agent billing preflight failed",
|
|
route: "/v1/agent/chat"
|
|
},
|
|
blocker: {
|
|
code: result.error?.code ?? (status === 402 ? "insufficient_credits" : "user_billing_preflight_failed"),
|
|
layer: "billing",
|
|
retryable: status === 429 || status === 503,
|
|
summary: result.error?.message ?? "Code Agent billing preflight failed"
|
|
},
|
|
valuesRedacted: true
|
|
};
|
|
if (sendResponse && response) sendJson(response, status, payload);
|
|
return { blocked: true, statusCode: status, payload, error: payload.error, blocker: payload.blocker };
|
|
}
|
|
|
|
async function recordCodeAgentBillingUsage({ payload = {}, params = {}, options = {} } = {}) {
|
|
const reservation = params.userBillingReservation ?? payload.userBillingReservation;
|
|
const reservationId = typeof reservation?.reservationId === "string" ? reservation.reservationId : "";
|
|
const client = options.userBillingClient;
|
|
if (payload.billing?.recorded === true || payload.billing?.released === true || payload.status === "running") return payload.billing ?? null;
|
|
if (!reservationId || !client?.configured) return null;
|
|
const traceId = safeTraceId(payload.traceId ?? params.traceId);
|
|
const usedTokens = codeAgentUsedTokens(payload);
|
|
const usedCredits = parsePositiveInteger(options.env?.HWLAB_USER_BILLING_CODE_AGENT_USED_CREDITS, 1);
|
|
const result = await client.billingRecord({
|
|
reservationId,
|
|
serviceId: "hwlab-code-agent",
|
|
usedCredits,
|
|
usedTokens,
|
|
idempotencyKey: `code-agent:${traceId}:record`,
|
|
metadata: codeAgentBillingMetadata(params, traceId, { stage: "record", status: payload.status ?? "unknown" })
|
|
});
|
|
const billing = result.ok ? sanitizeBillingRecord(result.body, reservation) : {
|
|
recorded: false,
|
|
reservationId,
|
|
errorCode: result.error?.code ?? "user_billing_record_failed",
|
|
valuesRedacted: true
|
|
};
|
|
payload.billing = billing;
|
|
const traceStore = options.traceStore ?? defaultCodeAgentTraceStore;
|
|
if (traceId) {
|
|
traceStore.append(traceId, {
|
|
type: "billing",
|
|
status: result.ok ? "recorded" : "degraded",
|
|
label: result.ok ? "billing:recorded" : "billing:record_failed",
|
|
reservationId,
|
|
recordId: result.ok ? result.body?.recordId ?? null : null,
|
|
errorCode: result.ok ? null : billing.errorCode,
|
|
valuesPrinted: false
|
|
});
|
|
}
|
|
return billing;
|
|
}
|
|
|
|
async function finalizeCodeAgentBillingUsage({ payload = {}, params = {}, options = {} } = {}) {
|
|
if (payload.billing?.recorded === true || payload.billing?.released === true) return payload.billing;
|
|
if (payload.status === "running") return null;
|
|
if (payload.status === "completed") {
|
|
return recordCodeAgentBillingUsage({ payload, params, options });
|
|
}
|
|
return releaseCodeAgentBillingReservation({ payload, params, options });
|
|
}
|
|
|
|
async function releaseCodeAgentBillingReservation({ payload = {}, params = {}, options = {} } = {}) {
|
|
const reservation = params.userBillingReservation ?? payload.userBillingReservation;
|
|
const reservationId = typeof reservation?.reservationId === "string" ? reservation.reservationId : "";
|
|
const client = options.userBillingClient;
|
|
if (payload.billing?.recorded === true || payload.billing?.released === true || !reservationId || !client?.configured) return payload.billing ?? null;
|
|
const traceId = safeTraceId(payload.traceId ?? params.traceId);
|
|
const releaseReason = String(payload.status ?? "not_completed").trim() || "not_completed";
|
|
const releaseBody = {
|
|
reservationId,
|
|
serviceId: "hwlab-code-agent",
|
|
idempotencyKey: `code-agent:${traceId}:release`,
|
|
reason: releaseReason,
|
|
metadata: codeAgentBillingMetadata(params, traceId, {
|
|
stage: "release",
|
|
status: payload.status ?? "unknown",
|
|
errorCode: payload.error?.code ?? payload.blocker?.code ?? null
|
|
})
|
|
};
|
|
const result = typeof client.billingRelease === "function"
|
|
? await client.billingRelease(releaseBody)
|
|
: { ok: false, error: { code: "user_billing_release_not_supported" } };
|
|
const billing = result.ok ? sanitizeBillingRelease(result.body, reservation) : {
|
|
released: false,
|
|
reservationId,
|
|
errorCode: result.error?.code ?? "user_billing_release_failed",
|
|
valuesRedacted: true
|
|
};
|
|
payload.billing = billing;
|
|
const traceStore = options.traceStore ?? defaultCodeAgentTraceStore;
|
|
if (traceId) {
|
|
traceStore.append(traceId, {
|
|
type: "billing",
|
|
status: result.ok ? "released" : "degraded",
|
|
label: result.ok ? "billing:released" : "billing:release_failed",
|
|
reservationId,
|
|
reason: releaseReason,
|
|
errorCode: result.ok ? null : billing.errorCode,
|
|
valuesPrinted: false
|
|
});
|
|
}
|
|
return billing;
|
|
}
|
|
|
|
function withCodeAgentBillingReservation(payload = {}, params = {}) {
|
|
if (!payload || typeof payload !== "object" || payload.userBillingReservation) return payload;
|
|
const reservation = params.userBillingReservation;
|
|
return reservation ? { ...payload, userBillingReservation: reservation } : payload;
|
|
}
|
|
|
|
function codeAgentBillingEnabled(options = {}) {
|
|
const env = options.env ?? process.env;
|
|
if (String(env.HWLAB_USER_BILLING_CODE_AGENT_ENABLED ?? "").trim() === "0") return false;
|
|
return Boolean(options.userBillingClient?.configured);
|
|
}
|
|
|
|
function codeAgentBillingMetadata(params = {}, traceId, extra = {}) {
|
|
return {
|
|
traceId,
|
|
conversationId: safeConversationId(params.conversationId) || null,
|
|
sessionId: safeSessionId(params.sessionId) || null,
|
|
projectId: textValue(params.projectId) || null,
|
|
route: "/v1/agent/chat",
|
|
resource: "code-agent-turn",
|
|
...extra,
|
|
valuesRedacted: true
|
|
};
|
|
}
|
|
|
|
function sanitizeBillingReservation(value = {}) {
|
|
return {
|
|
allowed: value.allowed === true,
|
|
reservationId: textValue(value.reservationId) || null,
|
|
resourceType: textValue(value.resourceType) || null,
|
|
planId: textValue(value.planId) || null,
|
|
estimatedCredits: Number.isFinite(Number(value.estimatedCredits)) ? Number(value.estimatedCredits) : null,
|
|
estimatedTokens: Number.isFinite(Number(value.estimatedTokens)) ? Number(value.estimatedTokens) : null,
|
|
expiresAt: textValue(value.expiresAt) || null,
|
|
valuesRedacted: true
|
|
};
|
|
}
|
|
|
|
function sanitizeBillingRecord(value = {}, reservation = {}) {
|
|
return {
|
|
recorded: true,
|
|
reservationId: textValue(reservation.reservationId) || null,
|
|
recordId: textValue(value.recordId) || null,
|
|
credits: Number.isFinite(Number(value.credits)) ? Number(value.credits) : null,
|
|
balance: Number.isFinite(Number(value.balance)) ? Number(value.balance) : null,
|
|
valuesRedacted: true
|
|
};
|
|
}
|
|
|
|
function sanitizeBillingRelease(value = {}, reservation = {}) {
|
|
return {
|
|
released: true,
|
|
reservationId: textValue(value.reservationId) || textValue(reservation.reservationId) || null,
|
|
releasedCredits: Number.isFinite(Number(value.releasedCredits)) ? Number(value.releasedCredits) : null,
|
|
status: textValue(value.status) || "cancelled",
|
|
valuesRedacted: true
|
|
};
|
|
}
|
|
|
|
function codeAgentUsedTokens(payload = {}) {
|
|
const usage = payload.usage && typeof payload.usage === "object" ? payload.usage : null;
|
|
const traceSummary = payload.traceSummary && typeof payload.traceSummary === "object" ? payload.traceSummary : null;
|
|
const candidates = [
|
|
usage?.totalTokens,
|
|
usage?.total_tokens,
|
|
usage?.tokens,
|
|
Number(usage?.inputTokens ?? usage?.input_tokens ?? 0) + Number(usage?.outputTokens ?? usage?.output_tokens ?? 0),
|
|
traceSummary?.tokenCount,
|
|
traceSummary?.tokens
|
|
];
|
|
for (const value of candidates) {
|
|
const parsed = Number(value);
|
|
if (Number.isFinite(parsed) && parsed > 0) return Math.ceil(parsed);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function normalizeTurnStatus(...values) {
|
|
for (const value of values) {
|
|
const text = textValue(value).toLowerCase().replace(/_/gu, "-");
|
|
if (!text) continue;
|
|
if (["accepted", "pending", "processing", "running", "busy", "creating", "queued", "in-flight"].includes(text)) return "running";
|
|
if (["completed", "done", "succeeded", "success"].includes(text)) return "completed";
|
|
if (["failed", "failure", "error", "stale", "thread-resume-failed", "aborted", "interrupted", "expired"].includes(text)) return "failed";
|
|
if (["cancelled", "canceled"].includes(text)) return "canceled";
|
|
if (["blocked", "timeout"].includes(text)) return text;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function recordCodeAgentConversationFact(payload = {}, options = {}) {
|
|
const registry = options.sessionRegistry;
|
|
const conversationId = safeConversationId(payload.conversationId);
|
|
if (!conversationId || typeof registry?.recordFact !== "function") return null;
|
|
try {
|
|
return registry.recordFact(conversationId, {
|
|
kind: payload.status === "completed" ? "runner_turn" : "runner_turn_terminal",
|
|
conversationId,
|
|
sessionId: payload.sessionId,
|
|
traceId: payload.traceId,
|
|
provider: payload.provider,
|
|
backend: payload.backend,
|
|
workspace: payload.workspace,
|
|
sandbox: payload.sandbox,
|
|
session: payload.session,
|
|
sessionMode: payload.sessionMode,
|
|
sessionReuse: payload.sessionReuse,
|
|
implementationType: payload.implementationType,
|
|
runner: payload.runner,
|
|
runnerTrace: payload.runnerTrace,
|
|
partialContext: payload.partialContext ?? payload.runnerTrace?.partialContext,
|
|
capabilityLevel: payload.capabilityLevel,
|
|
toolCalls: payload.toolCalls,
|
|
skills: payload.skills
|
|
}, { now: options.now });
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function recordCodeAgentSessionOwner({ payload = {}, params = {}, options = {}, status = "active", preserveLastTraceId = false } = {}) {
|
|
const ownerUserId = options.actor?.id ?? params.ownerUserId;
|
|
if (!ownerUserId || !options.accessController?.recordAgentSessionOwner) return null;
|
|
const traceId = safeTraceId(payload.traceId ?? params.traceId);
|
|
const session = payload.session && typeof payload.session === "object" ? payload.session : null;
|
|
const sessionReuse = payload.sessionReuse && typeof payload.sessionReuse === "object" ? payload.sessionReuse : null;
|
|
const sessionId = safeSessionId(payload.sessionId ?? session?.sessionId ?? sessionReuse?.sessionId ?? params.sessionId) || null;
|
|
const conversationId = safeConversationId(payload.conversationId ?? session?.conversationId ?? sessionReuse?.conversationId ?? params.conversationId) || null;
|
|
const threadId = safeOpaqueId(session?.threadId ?? sessionReuse?.threadId ?? payload.threadId ?? params.threadId) || null;
|
|
return recordWorkbenchSessionOwner({
|
|
accessController: options.accessController,
|
|
traceId,
|
|
ownerUserId,
|
|
ownerRole: options.actor?.role ?? params.ownerRole ?? null,
|
|
sessionId,
|
|
projectId: params.projectId ?? payload.projectId ?? null,
|
|
conversationId,
|
|
threadId,
|
|
status,
|
|
preserveLastTraceId,
|
|
session: codeAgentSessionOwnerEvidence(payload, params)
|
|
});
|
|
}
|
|
|
|
function codeAgentOwnerStatusForResult(result = {}) {
|
|
const sessionStatus = textValue(result?.session?.status ?? result?.sessionSummary?.status ?? result?.sessionLifecycleStatus);
|
|
if (sessionStatus === "thread-resume-failed") return "thread-resume-failed";
|
|
if (codeAgentFreshContinuationFailure(result)) return "thread-resume-failed";
|
|
if (codeAgentPayloadHasSealedFinalResponse(result)) return "completed";
|
|
if (result?.status === "completed") return "completed";
|
|
if (result?.status === "canceled" || result?.status === "cancelled") return "canceled";
|
|
return result?.status ?? "active";
|
|
}
|
|
|
|
function textValue(value) {
|
|
return String(value ?? "").trim();
|
|
}
|
|
|
|
function timestampIsoOrNow(value = null) {
|
|
const parsed = Date.parse(String(value ?? ""));
|
|
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : new Date().toISOString();
|
|
}
|
|
|
|
function timestampIsoOrNull(value = null) {
|
|
const parsed = Date.parse(String(value ?? ""));
|
|
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
|
|
}
|
|
|
|
function codeAgentSessionOwnerEvidence(payload = {}, params = {}) {
|
|
const traceId = payload.traceId ?? params.traceId ?? null;
|
|
const finalResponse = codeAgentFinalResponseEvidence(payload, traceId);
|
|
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);
|
|
const providerProfile = codeAgentProviderProfileEvidence(payload, params);
|
|
const continuation = codeAgentContinuationEvidence(payload, params);
|
|
const sessionLifecycleStatus = continuation.requiresFreshSession ? "thread-resume-failed" : payload.sessionLifecycleStatus ?? null;
|
|
return {
|
|
...(providerProfile ? { providerProfile, codeAgentProviderProfile: providerProfile } : {}),
|
|
provider: payload.provider ?? null,
|
|
model: payload.model ?? null,
|
|
backend: payload.backend ?? null,
|
|
status: payload.status ?? null,
|
|
sessionMode: payload.sessionMode ?? payload.session?.sessionMode ?? null,
|
|
sessionLifecycleStatus,
|
|
continuation,
|
|
runnerKind: payload.runner?.kind ?? payload.runnerTrace?.runnerKind ?? null,
|
|
conversationId: payload.conversationId ?? params.conversationId ?? null,
|
|
sessionId: payload.sessionId ?? payload.session?.sessionId ?? payload.sessionReuse?.sessionId ?? params.sessionId ?? null,
|
|
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 } } : {}),
|
|
...(messages.length ? { messages, messageCount: messages.length } : {}),
|
|
...(firstUserMessagePreview ? { firstUserMessagePreview } : {}),
|
|
...agentRunSessionEvidence(payload),
|
|
secretMaterialStored: false,
|
|
valuesRedacted: true
|
|
};
|
|
}
|
|
|
|
function codeAgentContinuationEvidence(payload = {}, params = {}) {
|
|
const failure = codeAgentFreshContinuationFailure(payload);
|
|
const providerProfile = codeAgentProviderProfileEvidence(payload, params);
|
|
const traceId = safeTraceId(payload.traceId ?? params.traceId) || null;
|
|
if (!failure) {
|
|
return {
|
|
requiresFreshSession: false,
|
|
providerProfile,
|
|
traceId,
|
|
valuesRedacted: true
|
|
};
|
|
}
|
|
return {
|
|
requiresFreshSession: true,
|
|
reasonCode: failure.code,
|
|
reason: failure.message,
|
|
staleThreadId: safeOpaqueId(payload.session?.threadId ?? payload.sessionReuse?.threadId ?? payload.threadId ?? params.threadId ?? payload.agentRun?.threadId) || null,
|
|
providerProfile,
|
|
traceId,
|
|
valuesRedacted: true
|
|
};
|
|
}
|
|
|
|
function codeAgentFreshContinuationFailure(payload = {}) {
|
|
if (!payload || typeof payload !== "object") return null;
|
|
const sessionStatus = String(payload.session?.status ?? payload.sessionLifecycleStatus ?? payload.sessionReuse?.status ?? "").trim().toLowerCase().replace(/_/gu, "-");
|
|
if (sessionStatus === "thread-resume-failed") return { code: "thread-resume-failed", message: "previous AgentRun thread/session resume failed", valuesRedacted: true };
|
|
const status = normalizeTurnStatus(payload.status, payload.agentRun?.terminalStatus, payload.agentRun?.commandState, payload.agentRun?.status, payload.session?.status, payload.sessionReuse?.status);
|
|
if (!status || !["failed", "blocked", "timeout"].includes(status)) return null;
|
|
const code = normalizeCodeAgentFailureKind(firstNonEmptyValue(
|
|
payload.error?.code,
|
|
payload.blocker?.code,
|
|
payload.providerTrace?.failureKind,
|
|
payload.providerTrace?.errorCode,
|
|
payload.agentRun?.failureKind,
|
|
payload.agentRun?.errorCode,
|
|
payload.transient?.failureKind,
|
|
payload.transient?.errorCode
|
|
));
|
|
const message = firstNonEmptyValue(
|
|
payload.error?.message,
|
|
payload.error?.userMessage,
|
|
payload.blocker?.summary,
|
|
payload.blocker?.message,
|
|
payload.providerTrace?.failureMessage,
|
|
payload.providerTrace?.summary,
|
|
payload.agentRun?.failureMessage,
|
|
payload.agentRun?.message
|
|
) ?? "";
|
|
if (!codeAgentFailureRequiresFreshContinuation(code, message)) return null;
|
|
return { code: code ?? "stale-continuation", message: message || code || "stale continuation", valuesRedacted: true };
|
|
}
|
|
|
|
function normalizeCodeAgentFailureKind(value) {
|
|
const normalized = String(value ?? "").trim().toLowerCase().replace(/_/gu, "-");
|
|
return normalized || null;
|
|
}
|
|
|
|
function codeAgentFailureRequiresFreshContinuation(code, message = "") {
|
|
const failureKind = normalizeCodeAgentFailureKind(code);
|
|
if (["thread-resume-failed", "session-store-evicted", "provider-stream-disconnected"].includes(failureKind)) return true;
|
|
return /session stor(?:e|age).*evicted|pvc-backed session|no rollout found for thread id|no rollout found|thread\/resume failed|provider stream disconnected/iu.test(String(message ?? ""));
|
|
}
|
|
|
|
function codeAgentProviderProfileEvidence(payload = {}, params = {}) {
|
|
const raw = firstNonEmptyValue(
|
|
params.providerProfile,
|
|
params.codeAgentProviderProfile,
|
|
payload.providerProfile,
|
|
payload.codeAgentProviderProfile,
|
|
payload.agentRun?.backendProfile,
|
|
payload.providerTrace?.backendProfile,
|
|
payload.backendProfile
|
|
);
|
|
if (!raw) return null;
|
|
const normalized = normalizeCodeAgentProviderProfile(raw);
|
|
return normalized === "runtime-default" ? null : normalized;
|
|
}
|
|
|
|
function codeAgentTraceResultEvidence(payload = {}, params = {}, traceId = null, finalResponse = null, traceSummary = null) {
|
|
const resolvedTraceId = safeTraceId(traceId);
|
|
if (!resolvedTraceId) return null;
|
|
const agentRun = agentRunSessionEvidence(payload).agentRun ?? null;
|
|
const userBillingReservation = codeAgentBillingReservationEvidence(payload.userBillingReservation ?? params.userBillingReservation);
|
|
const resultSession = payload.session && typeof payload.session === "object" ? payload.session : null;
|
|
const sessionReuse = payload.sessionReuse && typeof payload.sessionReuse === "object" ? payload.sessionReuse : 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,
|
|
status: codeAgentPayloadHasSealedFinalResponse(payload) ? "completed" : payload.status ?? agentRun?.terminalStatus ?? agentRun?.commandState ?? null,
|
|
conversationId,
|
|
sessionId,
|
|
threadId,
|
|
...codeAgentPromptFields(promptEvidence),
|
|
messageId: finalResponse?.messageId ?? payload.messageId ?? null,
|
|
createdAt: payload.createdAt ?? finalResponse?.createdAt ?? null,
|
|
updatedAt: payload.updatedAt ?? finalResponse?.updatedAt ?? null,
|
|
finalResponse,
|
|
traceSummary,
|
|
agentRun,
|
|
...(userBillingReservation ? { userBillingReservation } : {}),
|
|
valuesRedacted: true,
|
|
secretMaterialStored: false
|
|
};
|
|
}
|
|
|
|
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);
|
|
if (!reservationId) return null;
|
|
return {
|
|
reservationId,
|
|
resourceType: textValue(value.resourceType) || null,
|
|
planId: textValue(value.planId) || null,
|
|
estimatedCredits: numberOrNull(value.estimatedCredits),
|
|
estimatedTokens: numberOrNull(value.estimatedTokens),
|
|
expiresAt: textValue(value.expiresAt) || null,
|
|
valuesRedacted: true,
|
|
secretMaterialStored: false
|
|
};
|
|
}
|
|
|
|
function codeAgentConversationMessagesEvidence(payload = {}, params = {}, traceId = null, finalResponse = null) {
|
|
const userText = boundedConversationMessageText(params.message ?? params.prompt ?? payload.userMessage ?? payload.prompt);
|
|
if (!userText) return [];
|
|
const resolvedTraceId = safeTraceId(traceId) || null;
|
|
const lifecycle = codeAgentTurnLifecycleFields(resolvedTraceId, payload);
|
|
const session = payload.session && typeof payload.session === "object" ? payload.session : null;
|
|
const sessionReuse = payload.sessionReuse && typeof payload.sessionReuse === "object" ? payload.sessionReuse : null;
|
|
const conversationId = safeConversationId(payload.conversationId ?? session?.conversationId ?? sessionReuse?.conversationId ?? params.conversationId) || null;
|
|
const sessionId = safeSessionId(payload.sessionId ?? session?.sessionId ?? sessionReuse?.sessionId ?? params.sessionId) || null;
|
|
const threadId = safeOpaqueId(session?.threadId ?? sessionReuse?.threadId ?? payload.threadId ?? params.threadId) || null;
|
|
const createdAt = payload.createdAt ?? payload.reply?.createdAt ?? new Date().toISOString();
|
|
const updatedAt = payload.updatedAt ?? createdAt;
|
|
const userMessage = {
|
|
id: lifecycle.userMessageId,
|
|
messageId: lifecycle.userMessageId,
|
|
role: "user",
|
|
title: "用户",
|
|
text: userText,
|
|
status: "sent",
|
|
traceId: resolvedTraceId,
|
|
turnId: lifecycle.turnId,
|
|
conversationId,
|
|
sessionId,
|
|
threadId,
|
|
createdAt,
|
|
updatedAt,
|
|
source: "code-agent-submit",
|
|
secretMaterialStored: false,
|
|
valuesRedacted: true
|
|
};
|
|
const agentText = boundedConversationMessageText(
|
|
finalResponse?.text
|
|
?? payload.reply?.content
|
|
?? payload.message?.content
|
|
?? payload.assistantText
|
|
?? payload.error?.userMessage
|
|
?? payload.error?.message
|
|
?? payload.blocker?.userMessage
|
|
?? payload.blocker?.message
|
|
?? payload.blocker?.summary
|
|
);
|
|
const agentStatus = codeAgentConversationAgentMessageStatus(payload);
|
|
const agentMessage = {
|
|
id: lifecycle.assistantMessageId,
|
|
messageId: lifecycle.assistantMessageId,
|
|
role: "agent",
|
|
title: agentStatus === "failed" ? "Code Agent 返回阻塞" : agentStatus === "running" ? "Code Agent 处理中" : "Code Agent 回复",
|
|
text: agentText || (agentStatus === "running" ? "" : "Code Agent 请求已结束,请查看 Trace 详情。"),
|
|
status: agentStatus,
|
|
traceId: resolvedTraceId,
|
|
turnId: lifecycle.turnId,
|
|
conversationId,
|
|
sessionId,
|
|
threadId,
|
|
createdAt: payload.reply?.createdAt ?? updatedAt,
|
|
updatedAt,
|
|
source: "code-agent-result",
|
|
...(payload.runnerTrace && typeof payload.runnerTrace === "object" ? { runnerTrace: codeAgentConversationRunnerTraceEvidence(payload.runnerTrace, resolvedTraceId) } : {}),
|
|
secretMaterialStored: false,
|
|
valuesRedacted: true
|
|
};
|
|
return [userMessage, agentMessage];
|
|
}
|
|
|
|
function boundedConversationMessageText(value, limit = 12000) {
|
|
const text = conversationText(value);
|
|
if (!text) return "";
|
|
return text.length > limit ? text.slice(0, limit) : text;
|
|
}
|
|
|
|
function conversationText(value) {
|
|
const extracted = conversationTextRaw(value).replace(/\r\n?/gu, "\n");
|
|
if (!extracted.trim() || extracted.trim() === "[object Object]") return "";
|
|
return extracted;
|
|
}
|
|
|
|
function conversationTextRaw(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(conversationTextRaw).filter(Boolean).join("\n");
|
|
if (typeof value === "object") {
|
|
for (const key of ["text", "content", "message", "prompt", "value", "summary", "preview", "title"]) {
|
|
const text = conversationTextRaw(value[key]);
|
|
if (text) return text;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function codeAgentMessageTraceSuffix(traceId) {
|
|
const text = safeTraceId(traceId) || `local_${Date.now().toString(36)}`;
|
|
return text.replace(/^trc_/u, "").replace(/[^A-Za-z0-9_.:-]/gu, "_").slice(0, 48) || "trace";
|
|
}
|
|
|
|
function codeAgentTurnLifecycleFields(traceId, source = {}) {
|
|
const resolvedTraceId = safeTraceId(source?.traceId ?? traceId) || safeTraceId(traceId) || null;
|
|
const turnId = safeTurnId(source?.turnId) || resolvedTraceId;
|
|
const traceSuffix = codeAgentMessageTraceSuffix(resolvedTraceId);
|
|
return {
|
|
turnId,
|
|
userMessageId: safeMessageId(source?.userMessageId ?? source?.userMessage?.messageId) || codeAgentLifecycleMessageId(traceSuffix, "user"),
|
|
assistantMessageId: safeMessageId(source?.assistantMessageId ?? source?.assistantMessage?.messageId) || codeAgentLifecycleMessageId(traceSuffix, "agent")
|
|
};
|
|
}
|
|
|
|
function codeAgentLifecycleMessageId(traceSuffix, role) {
|
|
return `msg_${traceSuffix}_${role}`;
|
|
}
|
|
|
|
function safeTurnId(value) {
|
|
const text = textValue(value);
|
|
return /^turn_[A-Za-z0-9_.:-]+$/u.test(text) ? text : null;
|
|
}
|
|
|
|
function safeMessageId(value) {
|
|
const text = textValue(value);
|
|
return /^msg_[A-Za-z0-9_.:-]+$/u.test(text) ? text : null;
|
|
}
|
|
|
|
function codeAgentConversationAgentMessageStatus(payload = {}) {
|
|
const status = textValue(payload.status ?? payload.agentRun?.terminalStatus ?? payload.commandState ?? payload.runStatus).toLowerCase();
|
|
if (codeAgentPayloadHasSealedFinalResponse(payload)) return "completed";
|
|
if (["completed", "done", "success"].includes(status)) return "completed";
|
|
if (["failed", "blocked", "error", "timeout", "canceled", "cancelled"].includes(status)) return "failed";
|
|
if (payload.error || payload.blocker) return "failed";
|
|
return "running";
|
|
}
|
|
|
|
function codeAgentPayloadHasSealedFinalResponse(payload = {}) {
|
|
if (!payload || typeof payload !== "object") return false;
|
|
if (payload.error || payload.blocker) return false;
|
|
if (!codeAgentFinalResponseText(payload)) return false;
|
|
const agentRun = payload.agentRun && typeof payload.agentRun === "object" ? payload.agentRun : {};
|
|
return isTraceCommandTerminalStatus(payload.status) ||
|
|
isTraceCommandTerminalStatus(agentRun.status) ||
|
|
isTraceCommandTerminalStatus(agentRun.runStatus) ||
|
|
isTraceCommandTerminalStatus(agentRun.commandState) ||
|
|
isTraceCommandTerminalStatus(agentRun.terminalStatus);
|
|
}
|
|
|
|
function codeAgentConversationRunnerTraceEvidence(runnerTrace = {}, traceId = null) {
|
|
const events = Array.isArray(runnerTrace.events) ? runnerTrace.events : [];
|
|
const lastEvent = runnerTrace.lastEvent && typeof runnerTrace.lastEvent === "object" ? runnerTrace.lastEvent : events.at(-1) ?? null;
|
|
const eventCount = Number.isFinite(Number(runnerTrace.eventCount)) ? Number(runnerTrace.eventCount) : events.length;
|
|
return {
|
|
traceId: safeTraceId(runnerTrace.traceId ?? traceId) || traceId || null,
|
|
status: runnerTrace.status ?? lastEvent?.status ?? null,
|
|
sessionId: runnerTrace.sessionId ?? null,
|
|
threadId: runnerTrace.threadId ?? null,
|
|
runId: runnerTrace.runId ?? lastEvent?.runId ?? null,
|
|
commandId: runnerTrace.commandId ?? lastEvent?.commandId ?? null,
|
|
eventCount,
|
|
lastEvent: lastEvent ? {
|
|
label: lastEvent.label ?? null,
|
|
status: lastEvent.status ?? null,
|
|
terminal: lastEvent.terminal === true,
|
|
createdAt: lastEvent.createdAt ?? null,
|
|
message: boundedConversationMessageText(lastEvent.message ?? lastEvent.text, 1000),
|
|
valuesPrinted: false
|
|
} : null,
|
|
eventsCompacted: true,
|
|
fullTraceLoaded: false,
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
function codeAgentFinalResponseEvidence(payload = {}, traceId = null) {
|
|
const text = codeAgentFinalResponseText(payload);
|
|
if (!text) return null;
|
|
return {
|
|
text,
|
|
textChars: text.length,
|
|
messageId: payload.finalResponse?.messageId ?? payload.reply?.messageId ?? payload.messageId ?? null,
|
|
role: payload.reply?.role ?? payload.message?.role ?? "assistant",
|
|
status: codeAgentPayloadHasSealedFinalResponse(payload) ? "completed" : payload.finalResponse?.status ?? payload.status ?? null,
|
|
traceId,
|
|
createdAt: payload.finalResponse?.createdAt ?? payload.reply?.createdAt ?? payload.createdAt ?? null,
|
|
updatedAt: payload.finalResponse?.updatedAt ?? payload.updatedAt ?? null,
|
|
source: payload.finalResponse?.source ?? "code-agent-result",
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
function codeAgentFinalResponseText(payload = {}) {
|
|
for (const value of [
|
|
payload?.finalResponse?.text,
|
|
payload?.finalResponse?.content,
|
|
payload?.finalResponse,
|
|
payload?.reply?.content,
|
|
payload?.reply,
|
|
payload?.message?.content,
|
|
payload?.message,
|
|
payload?.assistantText,
|
|
payload?.finalText,
|
|
payload?.text,
|
|
payload?.content
|
|
]) {
|
|
const text = conversationText(value);
|
|
if (text) return text;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function codeAgentTraceSummaryEvidence(payload = {}, traceId = null, finalResponse = null) {
|
|
const runnerTrace = payload.runnerTrace && typeof payload.runnerTrace === "object" ? payload.runnerTrace : null;
|
|
const events = Array.isArray(runnerTrace?.events) ? runnerTrace.events : [];
|
|
const terminalEvent = [...events].reverse().find((event) => event?.terminal === true) ?? runnerTrace?.lastEvent ?? null;
|
|
return {
|
|
traceId,
|
|
source: "code-agent-derived-session-evidence",
|
|
sourceEventCount: Number.isFinite(Number(runnerTrace?.eventCount)) ? Number(runnerTrace.eventCount) : events.length,
|
|
terminalStatus: payload.agentRun?.terminalStatus ?? payload.status ?? terminalEvent?.status ?? null,
|
|
lastEventLabel: terminalEvent?.label ?? null,
|
|
finalAssistantRow: finalResponse ? {
|
|
role: finalResponse.role,
|
|
status: finalResponse.status,
|
|
textChars: finalResponse.textChars,
|
|
textPreview: finalResponse.text.slice(0, 240),
|
|
messageId: finalResponse.messageId,
|
|
valuesPrinted: false
|
|
} : null,
|
|
agentRun: payload.agentRun ? {
|
|
runId: payload.agentRun.runId ?? null,
|
|
commandId: payload.agentRun.commandId ?? null,
|
|
attemptId: payload.agentRun.attemptId ?? null,
|
|
runnerId: payload.agentRun.runnerId ?? null,
|
|
jobName: payload.agentRun.jobName ?? null,
|
|
namespace: payload.agentRun.namespace ?? null,
|
|
lastSeq: payload.agentRun.lastSeq ?? null,
|
|
valuesPrinted: false
|
|
} : null,
|
|
updatedAt: payload.updatedAt ?? null,
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
function annotateOwner(payload, params = {}) {
|
|
if (!params.ownerUserId) return payload;
|
|
return {
|
|
...payload,
|
|
ownerUserId: params.ownerUserId,
|
|
ownerRole: params.ownerRole ?? null
|
|
};
|
|
}
|
|
|
|
function canAccessOwnedResult(result, actor) {
|
|
if (!result?.ownerUserId || !actor) return true;
|
|
return actor.role === "admin" || actor.id === result.ownerUserId;
|
|
}
|
|
|
|
function isCodeAgentResultCanceled(result) {
|
|
return result?.status === "canceled" || result?.canceled === true || result?.error?.code === "codex_stdio_canceled";
|
|
}
|
|
|
|
function codeAgentCancelScopeMismatch(params = {}, result = {}) {
|
|
return cancelScopeFieldMismatch("conversationId", safeConversationId(params.conversationId), cancelExpectedValues(safeConversationId,
|
|
result.conversationId,
|
|
result.agentRun?.conversationId,
|
|
result.session?.conversationId,
|
|
result.sessionReuse?.conversationId,
|
|
result.runnerTrace?.conversationId
|
|
)) ?? cancelScopeFieldMismatch("sessionId", safeSessionId(params.sessionId), cancelExpectedValues(safeSessionId,
|
|
result.sessionId,
|
|
result.session?.sessionId,
|
|
result.sessionReuse?.sessionId,
|
|
result.agentRun?.hwlabSessionId
|
|
)) ?? cancelScopeFieldMismatch("threadId", safeOpaqueId(params.threadId), cancelExpectedValues(safeOpaqueId,
|
|
result.threadId,
|
|
result.agentRun?.threadId,
|
|
result.session?.threadId,
|
|
result.sessionReuse?.threadId,
|
|
result.runnerTrace?.threadId
|
|
));
|
|
}
|
|
|
|
function cancelExpectedValues(normalize, ...values) {
|
|
return [...new Set(values.map((value) => normalize(value)).filter(Boolean))];
|
|
}
|
|
|
|
function cancelScopeFieldMismatch(field, requested, expectedValues) {
|
|
if (!requested || expectedValues.length === 0 || expectedValues.includes(requested)) return null;
|
|
return { field, requested, expected: expectedValues[0] };
|
|
}
|
|
|
|
function cancelBlockedPayload({
|
|
code,
|
|
message,
|
|
traceId,
|
|
conversationId = null,
|
|
sessionId = null,
|
|
session = null,
|
|
runnerTrace = null,
|
|
status = "failed",
|
|
unsupported = false,
|
|
degraded = false
|
|
}) {
|
|
const sessionSummary = codeAgentSessionLifecycleSummary({
|
|
session,
|
|
runnerTrace,
|
|
status,
|
|
unsupported,
|
|
degraded,
|
|
error: { code, userMessage: message, message }
|
|
});
|
|
return {
|
|
accepted: false,
|
|
canceled: false,
|
|
status,
|
|
conversationId,
|
|
sessionId,
|
|
traceId,
|
|
session,
|
|
sessionLifecycleStatus: sessionSummary.status,
|
|
sessionLifecycle: sessionSummary,
|
|
sessionSummary,
|
|
unsupported,
|
|
degraded,
|
|
runnerTrace,
|
|
lastTraceEvent: runnerTrace?.lastEvent ?? null,
|
|
error: {
|
|
code,
|
|
layer: "session",
|
|
category: "cancel_blocked",
|
|
retryable: true,
|
|
userMessage: message,
|
|
message,
|
|
traceId,
|
|
route: "/v1/agent/chat/cancel",
|
|
toolName: "codex-stdio.cancel"
|
|
},
|
|
blocker: {
|
|
code,
|
|
layer: "session",
|
|
category: "cancel_blocked",
|
|
retryable: true,
|
|
summary: message,
|
|
userMessage: message,
|
|
traceId,
|
|
route: "/v1/agent/chat/cancel",
|
|
toolName: "codex-stdio.cancel"
|
|
}
|
|
};
|
|
}
|
|
|
|
function isTraceCommandTerminalStatus(status) {
|
|
return CODE_AGENT_TERMINAL_STATUSES.has(String(status ?? "").trim().toLowerCase().replace(/_/gu, "-"));
|
|
}
|
|
|
|
function traceSnapshotWithTerminalEvidence(snapshot, result, traceId, refreshError = null) {
|
|
const evidence = agentRunTerminalTraceEvidence(result, traceId);
|
|
if (snapshot && snapshot.status !== "missing") return traceSnapshotWithAttachedTerminalEvidence(snapshot, evidence, refreshError);
|
|
if (!evidence) return {
|
|
...snapshot,
|
|
ok: false,
|
|
traceStatus: "missing",
|
|
retention: traceRetentionSummary("missing")
|
|
};
|
|
return {
|
|
...snapshot,
|
|
ok: true,
|
|
status: "expired",
|
|
traceStatus: "expired",
|
|
projectionStatus: "projecting",
|
|
projectionHealth: "degraded",
|
|
persisted: true,
|
|
retention: traceRetentionSummary("expired"),
|
|
conversationId: evidence.conversationId,
|
|
sessionId: evidence.sessionId,
|
|
threadId: evidence.threadId,
|
|
...codeAgentPromptFields(evidence),
|
|
agentRun: evidence.agentRun,
|
|
finalResponse: evidence.finalResponse,
|
|
traceSummary: evidence.traceSummary,
|
|
terminalEvidence: {
|
|
available: true,
|
|
source: evidence.source,
|
|
refresh: refreshError ? {
|
|
attempted: true,
|
|
ok: false,
|
|
code: refreshError?.code ?? "agentrun_trace_refresh_failed",
|
|
message: refreshError?.message ?? "AgentRun trace events could not be refreshed; command result remains authoritative for final response.",
|
|
valuesPrinted: false
|
|
} : { attempted: false, ok: null },
|
|
conversationId: evidence.conversationId,
|
|
sessionId: evidence.sessionId,
|
|
threadId: evidence.threadId,
|
|
...codeAgentPromptFields(evidence),
|
|
agentRun: evidence.agentRun,
|
|
traceSummary: evidence.traceSummary,
|
|
finalResponse: evidence.finalResponse,
|
|
valuesPrinted: false
|
|
}
|
|
};
|
|
}
|
|
|
|
function traceSnapshotWithAttachedTerminalEvidence(snapshot, evidence, refreshError = null) {
|
|
if (!evidence) return snapshot;
|
|
return {
|
|
...snapshot,
|
|
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,
|
|
terminalEvidence: snapshot.terminalEvidence ?? (refreshError ? {
|
|
available: true,
|
|
source: evidence.source,
|
|
refresh: {
|
|
attempted: true,
|
|
ok: false,
|
|
code: refreshError?.code ?? "agentrun_trace_refresh_failed",
|
|
message: refreshError?.message ?? "AgentRun trace events could not be refreshed; live events may be partial.",
|
|
valuesPrinted: false
|
|
},
|
|
valuesPrinted: false
|
|
} : undefined)
|
|
};
|
|
}
|
|
|
|
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,
|
|
commandId: result.agentRun.commandId ?? null,
|
|
attemptId: result.agentRun.attemptId ?? null,
|
|
runnerId: result.agentRun.runnerId ?? null,
|
|
jobName: result.agentRun.jobName ?? null,
|
|
namespace: result.agentRun.namespace ?? null,
|
|
terminalStatus: result.agentRun.terminalStatus ?? null,
|
|
lastSeq: result.agentRun.lastSeq ?? null,
|
|
valuesPrinted: false
|
|
} : null;
|
|
const terminalStatus = normalizeTurnStatus(result.agentRun?.terminalStatus, result.agentRun?.commandState, result.agentRun?.status, result.agentRun?.runStatus);
|
|
if (!isTraceCommandTerminalStatus(terminalStatus)) return null;
|
|
if (!agentRun?.runId || !agentRun?.commandId || (!storedSummary && !finalResponse)) return null;
|
|
const traceSummary = {
|
|
traceId,
|
|
source: "agentrun-command-result",
|
|
sourceEventCount: numberOrNull(storedSummary?.sourceEventCount ?? storedSummary?.eventCount ?? result.runnerTrace?.eventCount),
|
|
renderedRowSummary: storedSummary?.renderedRowSummary ?? null,
|
|
noiseEventCount: numberOrNull(storedSummary?.noiseEventCount),
|
|
omittedNoiseCount: numberOrNull(storedSummary?.omittedNoiseCount),
|
|
terminalStatus: storedSummary?.terminalStatus ?? terminalStatus,
|
|
updatedAt: storedSummary?.updatedAt ?? result.updatedAt ?? null,
|
|
valuesPrinted: false
|
|
};
|
|
return {
|
|
source: "agentrun-command-result",
|
|
conversationId: safeConversationId(result.conversationId) || null,
|
|
sessionId: safeSessionId(result.sessionId) || null,
|
|
threadId: safeOpaqueId(result.threadId) || null,
|
|
...codeAgentPromptFields(promptEvidence),
|
|
agentRun,
|
|
finalResponse,
|
|
traceSummary
|
|
};
|
|
}
|
|
|
|
function agentRunTerminalFinalResponse(result, traceId) {
|
|
const stored = result.finalResponse && typeof result.finalResponse === "object" ? result.finalResponse : null;
|
|
const text = textValue(stored?.text ?? result.reply?.content ?? result.message?.content ?? result.assistantText);
|
|
if (!text) return null;
|
|
const storedTraceId = safeTraceId(stored?.traceId ?? result.traceId) || null;
|
|
if (storedTraceId && storedTraceId !== traceId) return null;
|
|
return {
|
|
text,
|
|
textChars: text.length,
|
|
messageId: stored?.messageId ?? result.reply?.messageId ?? result.messageId ?? null,
|
|
role: stored?.role ?? result.reply?.role ?? "assistant",
|
|
status: stored?.status ?? result.status ?? null,
|
|
traceId: traceId,
|
|
createdAt: stored?.createdAt ?? result.reply?.createdAt ?? result.createdAt ?? null,
|
|
updatedAt: stored?.updatedAt ?? result.updatedAt ?? null,
|
|
source: "agentrun-command-result",
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
function traceRetentionSummary(status) {
|
|
return {
|
|
traceStatus: status,
|
|
liveTraceStore: status === "missing" ? "missing" : "expired-or-evicted",
|
|
policy: "live trace events are short-term storage; terminal final response and summary are resolved from AgentRun command result by traceId and commandId",
|
|
replacementEvidence: status === "missing" ? "resolve the trace through AgentRun command registry/result or report the trace as missing" : "use terminalEvidence.finalResponse, terminalEvidence.traceSummary, conversationId, sessionId, threadId, and agentRun ids",
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
function numberOrNull(value) {
|
|
const parsed = Number(value);
|
|
return Number.isFinite(parsed) ? parsed : null;
|
|
}
|
|
|
|
function createCodeAgentChatResultStore({ maxResults = 256 } = {}) {
|
|
const limit = positiveInteger(maxResults, 256);
|
|
const results = new Map();
|
|
function set(traceId, payload) {
|
|
const id = safeTraceId(traceId);
|
|
if (!id) return;
|
|
results.set(id, {
|
|
...payload,
|
|
traceId: id,
|
|
cachedAt: new Date().toISOString()
|
|
});
|
|
while (results.size > limit) {
|
|
const oldest = results.keys().next().value;
|
|
if (!oldest) break;
|
|
results.delete(oldest);
|
|
}
|
|
}
|
|
return {
|
|
set,
|
|
get: (traceId) => results.get(safeTraceId(traceId)) ?? null,
|
|
clear: () => results.clear()
|
|
};
|
|
}
|
|
|
|
function compactCodeAgentChatResultPayload(payload, options = {}) {
|
|
if (!payload || typeof payload !== "object") return payload;
|
|
const limit = resultTraceEventLimit(options);
|
|
const terminalEvidence = agentRunTerminalTraceEvidence(payload, payload.traceId);
|
|
const { userBillingReservation, ...publicPayload } = payload;
|
|
return {
|
|
...publicPayload,
|
|
...codeAgentTurnLifecycleFields(publicPayload.traceId, publicPayload),
|
|
...(terminalEvidence ? { terminalEvidence: terminalEvidencePayload(terminalEvidence) } : {}),
|
|
...(publicPayload.runnerTrace && typeof publicPayload.runnerTrace === "object"
|
|
? { runnerTrace: compactRunnerTraceForResult(publicPayload.runnerTrace, limit) }
|
|
: {})
|
|
};
|
|
}
|
|
|
|
function terminalEvidencePayload(evidence) {
|
|
return {
|
|
available: true,
|
|
source: evidence.source,
|
|
conversationId: evidence.conversationId,
|
|
sessionId: evidence.sessionId,
|
|
threadId: evidence.threadId,
|
|
...codeAgentPromptFields(evidence),
|
|
agentRun: evidence.agentRun,
|
|
traceSummary: evidence.traceSummary,
|
|
finalResponse: evidence.finalResponse,
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
function compactRunnerTraceForResult(runnerTrace, limit = DEFAULT_CODE_AGENT_RESULT_TRACE_EVENT_LIMIT) {
|
|
if (!runnerTrace || typeof runnerTrace !== "object") return runnerTrace;
|
|
const events = Array.isArray(runnerTrace.events) ? runnerTrace.events : [];
|
|
const total = Number.isInteger(runnerTrace.eventCount) ? runnerTrace.eventCount : events.length;
|
|
const effectiveLimit = Math.max(8, positiveInteger(limit, DEFAULT_CODE_AGENT_RESULT_TRACE_EVENT_LIMIT));
|
|
if (events.length <= effectiveLimit) {
|
|
return {
|
|
...runnerTrace,
|
|
eventCount: total,
|
|
eventsCompacted: false
|
|
};
|
|
}
|
|
const headCount = Math.min(24, Math.max(1, Math.floor(effectiveLimit / 4)));
|
|
const tailCount = Math.max(1, effectiveLimit - headCount - 1);
|
|
const omitted = Math.max(0, events.length - headCount - tailCount);
|
|
const head = events.slice(0, headCount);
|
|
const tail = events.slice(events.length - tailCount);
|
|
const marker = {
|
|
traceId: runnerTrace.traceId ?? head[0]?.traceId ?? tail[0]?.traceId ?? null,
|
|
type: "trace",
|
|
stage: "trace",
|
|
status: "truncated",
|
|
label: "trace:compacted",
|
|
createdAt: tail[0]?.createdAt ?? runnerTrace.updatedAt ?? new Date().toISOString(),
|
|
message: `Compatibility result response compacted ${omitted} trace events; fetch /v1/agent/traces/${encodeURIComponent(runnerTrace.traceId ?? "")} for the full trace.`,
|
|
omittedEventCount: omitted,
|
|
retainedEventCount: head.length + tail.length,
|
|
totalEventCount: total,
|
|
valuesPrinted: false
|
|
};
|
|
const compactEvents = [...head, marker, ...tail];
|
|
return {
|
|
...runnerTrace,
|
|
eventCount: total,
|
|
events: compactEvents,
|
|
eventLabels: compactEvents.map(traceEventLabel).filter(Boolean),
|
|
eventsCompacted: true,
|
|
eventWindow: {
|
|
mode: "head-tail",
|
|
retained: compactEvents.length,
|
|
omitted,
|
|
total
|
|
}
|
|
};
|
|
}
|
|
|
|
function traceEventLabel(event) {
|
|
return typeof event === "string" ? event : event?.label;
|
|
}
|
|
|
|
function resultTraceEventLimit(options = {}) {
|
|
return positiveInteger(
|
|
options.env?.HWLAB_CODE_AGENT_RESULT_TRACE_EVENT_LIMIT,
|
|
DEFAULT_CODE_AGENT_RESULT_TRACE_EVENT_LIMIT
|
|
);
|
|
}
|
|
|
|
function createCodeAgentM3HwlabApiRequestJson(options = {}) {
|
|
return async (targetUrl, request = {}) => {
|
|
const url = new URL(targetUrl);
|
|
const route = url.pathname;
|
|
if (route === M3_IO_CONTROL_ROUTE && request.method === "POST") {
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
body: await handleM3IoControl(request.body ?? {}, {
|
|
runtimeStore: options.runtimeStore,
|
|
now: options.now,
|
|
env: options.env,
|
|
requestJson: options.m3IoRequestJson
|
|
})
|
|
};
|
|
}
|
|
if (route === M3_STATUS_ROUTE && request.method === "GET") {
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
body: await describeM3StatusLive(options)
|
|
};
|
|
}
|
|
return {
|
|
ok: false,
|
|
status: 404,
|
|
body: {
|
|
error: {
|
|
code: "not_found",
|
|
message: `Unsupported Code Agent M3 HWLAB API route ${route}`
|
|
}
|
|
},
|
|
error: `Unsupported Code Agent M3 HWLAB API route ${route}`
|
|
};
|
|
};
|
|
}
|
|
function recordCodeAgentProjectionMetrics(options = {}, { result = null, trace = null, projection = null, refreshError = null } = {}) {
|
|
const performanceStore = options.backendPerformanceStore ?? options.backendPerformance;
|
|
if (!performanceStore?.recordWorkbenchProjection || !projection) return;
|
|
const sourceLatestSeq = sourceLatestSeqFromResult(result);
|
|
const lastProjectedSeq = Number(projection.lastProjectedSeq ?? 0);
|
|
if (!Number.isFinite(sourceLatestSeq) && !Number.isFinite(lastProjectedSeq)) return;
|
|
performanceStore.recordWorkbenchProjection({
|
|
sourceLatestSeq: Number.isFinite(sourceLatestSeq) ? sourceLatestSeq : lastProjectedSeq,
|
|
lastProjectedSeq: Number.isFinite(lastProjectedSeq) ? lastProjectedSeq : 0,
|
|
sourceLatestEventAt: sourceLatestAtFromResult(result, trace),
|
|
projectedLatestEventAt: projection.updatedAt ?? trace?.updatedAt ?? result?.updatedAt ?? null,
|
|
terminalSourceAt: terminalSourceAtFromResult(result),
|
|
terminalProjectedAt: projection.projectionStatus === "caught-up" ? projection.updatedAt ?? trace?.updatedAt ?? result?.updatedAt ?? null : null,
|
|
status: projection?.status ?? "unknown",
|
|
reason: projectionReason(projection, refreshError),
|
|
projectionStatus: projection.projectionStatus ?? "unknown",
|
|
source: "workbench_read_model"
|
|
});
|
|
}
|
|
|
|
function recordCodeAgentTurnReadMetric(options = {}, url, statusCode, body = {}, startedAt = nowMs()) {
|
|
const performanceStore = options.backendPerformanceStore ?? options.backendPerformance;
|
|
if (!performanceStore?.recordWorkbenchTurnRead) return;
|
|
performanceStore.recordWorkbenchTurnRead({
|
|
route: url?.pathname ?? "/v1/agent/turns/:id",
|
|
status: body?.status ?? statusClassLabel(statusCode),
|
|
degradedReason: turnReadDegradedReason(body),
|
|
durationMs: nowMs() - startedAt,
|
|
responseBytes: responseBodyBytes(body),
|
|
timedOut: statusCode === 504 || body?.error?.code === "timeout" || body?.refreshError?.code === "agentrun_timeout"
|
|
});
|
|
}
|
|
|
|
function turnReadDegradedReason(body = {}) {
|
|
if (body?.refreshError?.code) return body.refreshError.code;
|
|
if (body?.projection?.blocker?.code) return body.projection.blocker.code;
|
|
if (body?.blocker?.code) return body.blocker.code;
|
|
if (body?.error?.code) return body.error.code;
|
|
if (body?.projectionStatus === "projecting") return "projecting";
|
|
if (body?.projectionStatus === "blocked") return "projection_blocked";
|
|
return "none";
|
|
}
|
|
|
|
function responseBodyBytes(body = {}) {
|
|
try { return Buffer.byteLength(JSON.stringify(body)); } catch { return 0; }
|
|
}
|
|
|
|
function statusClassLabel(value) {
|
|
const status = Number(value);
|
|
if (!Number.isFinite(status) || status <= 0) return "unknown";
|
|
return `${Math.floor(status / 100)}xx`;
|
|
}
|
|
|
|
function sourceLatestSeqFromResult(result = null) {
|
|
const candidates = [
|
|
result?.agentRun?.lastSeq,
|
|
result?.traceSummary?.agentRun?.lastSeq,
|
|
result?.traceSummary?.lastSeq,
|
|
result?.providerTrace?.lastSeq,
|
|
result?.runnerTrace?.lastEvent?.sourceSeq,
|
|
result?.runnerTrace?.lastEvent?.seq,
|
|
result?.runnerTrace?.eventCount
|
|
];
|
|
for (const value of candidates) {
|
|
const seq = Number(value);
|
|
if (Number.isFinite(seq) && seq >= 0) return Math.floor(seq);
|
|
}
|
|
return NaN;
|
|
}
|
|
|
|
function sourceLatestAtFromResult(result = null, trace = null) {
|
|
return result?.traceSummary?.updatedAt
|
|
?? result?.agentRun?.updatedAt
|
|
?? result?.providerTrace?.updatedAt
|
|
?? result?.runnerTrace?.updatedAt
|
|
?? trace?.updatedAt
|
|
?? result?.updatedAt
|
|
?? null;
|
|
}
|
|
|
|
function terminalSourceAtFromResult(result = null) {
|
|
if (!result) return null;
|
|
const terminalStatus = result?.agentRun?.terminalStatus ?? result?.traceSummary?.terminalStatus ?? result?.status;
|
|
if (!isTraceCommandTerminalStatus(terminalStatus)) return null;
|
|
return result?.traceSummary?.updatedAt ?? result?.agentRun?.updatedAt ?? result?.updatedAt ?? null;
|
|
}
|
|
|
|
function projectionReason(projection = null, refreshError = null) {
|
|
if (refreshError) return refreshError.code ?? "refresh_error";
|
|
if (projection?.blocker) return projection.blocker.code ?? "projection_blocked";
|
|
if (projection?.projectionStatus === "projecting") return "projecting";
|
|
if (projection?.projectionStatus === "caught-up") return "none";
|
|
return projection?.projectionStatus ?? "unknown";
|
|
}
|
|
|
|
function nowMs() {
|
|
if (typeof performance !== "undefined" && typeof performance.now === "function") return performance.now();
|
|
return Date.now();
|
|
}
|
|
|
|
function uniqueStrings(values) {
|
|
return [...new Set(values.map((value) => String(value ?? "").trim()).filter(Boolean))];
|
|
}
|
|
|
|
export {
|
|
normalizeCodeAgentProviderProfile,
|
|
firstNonEmptyValue,
|
|
recordCodeAgentSessionInputFact,
|
|
buildCodeAgentSessionInputFact,
|
|
stableCodeAgentInputId,
|
|
codeAgentInputDelivery,
|
|
codeAgentInputStatus,
|
|
codeAgentResultTimingFields,
|
|
firstTimestampIso,
|
|
firstLatestTimestampIso,
|
|
codeAgentChatShortConnectionRequested,
|
|
preflightCodeAgentBilling,
|
|
recordCodeAgentBillingUsage,
|
|
finalizeCodeAgentBillingUsage,
|
|
releaseCodeAgentBillingReservation,
|
|
withCodeAgentBillingReservation,
|
|
codeAgentBillingEnabled,
|
|
codeAgentBillingMetadata,
|
|
sanitizeBillingReservation,
|
|
sanitizeBillingRecord,
|
|
sanitizeBillingRelease,
|
|
codeAgentUsedTokens,
|
|
normalizeTurnStatus,
|
|
recordCodeAgentConversationFact,
|
|
recordCodeAgentSessionOwner,
|
|
codeAgentOwnerStatusForResult,
|
|
textValue,
|
|
timestampIsoOrNow,
|
|
timestampIsoOrNull,
|
|
codeAgentSessionOwnerEvidence,
|
|
codeAgentContinuationEvidence,
|
|
codeAgentFreshContinuationFailure,
|
|
normalizeCodeAgentFailureKind,
|
|
codeAgentFailureRequiresFreshContinuation,
|
|
codeAgentProviderProfileEvidence,
|
|
codeAgentTraceResultEvidence,
|
|
codeAgentPromptMetadata,
|
|
codeAgentPromptMetadataFromText,
|
|
codeAgentPromptMetadataFromParts,
|
|
codeAgentPromptFields,
|
|
codeAgentPromptId,
|
|
clippedPromptText,
|
|
sha256Text,
|
|
compactCodeAgentObject,
|
|
codeAgentBillingReservationEvidence,
|
|
codeAgentConversationMessagesEvidence,
|
|
boundedConversationMessageText,
|
|
conversationText,
|
|
conversationTextRaw,
|
|
codeAgentMessageTraceSuffix,
|
|
codeAgentTurnLifecycleFields,
|
|
codeAgentLifecycleMessageId,
|
|
safeTurnId,
|
|
safeMessageId,
|
|
codeAgentConversationAgentMessageStatus,
|
|
codeAgentPayloadHasSealedFinalResponse,
|
|
codeAgentConversationRunnerTraceEvidence,
|
|
codeAgentFinalResponseEvidence,
|
|
codeAgentFinalResponseText,
|
|
codeAgentTraceSummaryEvidence,
|
|
annotateOwner,
|
|
canAccessOwnedResult,
|
|
isCodeAgentResultCanceled,
|
|
codeAgentCancelScopeMismatch,
|
|
cancelExpectedValues,
|
|
cancelScopeFieldMismatch,
|
|
cancelBlockedPayload,
|
|
isTraceCommandTerminalStatus,
|
|
traceSnapshotWithTerminalEvidence,
|
|
traceSnapshotWithAttachedTerminalEvidence,
|
|
agentRunTerminalTraceEvidence,
|
|
agentRunTerminalFinalResponse,
|
|
traceRetentionSummary,
|
|
numberOrNull,
|
|
createCodeAgentChatResultStore,
|
|
compactCodeAgentChatResultPayload,
|
|
terminalEvidencePayload,
|
|
compactRunnerTraceForResult,
|
|
traceEventLabel,
|
|
resultTraceEventLimit,
|
|
createCodeAgentM3HwlabApiRequestJson,
|
|
recordCodeAgentProjectionMetrics,
|
|
recordCodeAgentTurnReadMetric,
|
|
turnReadDegradedReason,
|
|
responseBodyBytes,
|
|
statusClassLabel,
|
|
sourceLatestSeqFromResult,
|
|
sourceLatestAtFromResult,
|
|
terminalSourceAtFromResult,
|
|
projectionReason,
|
|
nowMs,
|
|
uniqueStrings
|
|
};
|