922 lines
40 KiB
TypeScript
922 lines
40 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: Code Agent HTTP result, turn, inspect, and cancel resources.
|
|
|
|
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 { 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";
|
|
|
|
import {
|
|
normalizeCodeAgentProviderProfile,
|
|
firstNonEmptyValue,
|
|
recordCodeAgentSessionInputFact,
|
|
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
|
|
} from "./server-code-agent-http-support.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";
|
|
|
|
export async function handleCodeAgentChatResultHttp(request, response, url, options) {
|
|
const parts = url.pathname.split("/").filter(Boolean);
|
|
const traceId = decodeURIComponent(parts[4] ?? "");
|
|
if (!safeTraceId(traceId)) {
|
|
sendJson(response, 400, {
|
|
error: {
|
|
code: "invalid_trace_id",
|
|
message: "traceId must start with trc_ and contain only safe identifier characters"
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
const context = await measureCodeAgentHttpPhase(options, "result_projection_read", () => readCodeAgentCompatProjection(traceId, options));
|
|
if (context.statusCode) {
|
|
sendJson(response, context.statusCode, context.body);
|
|
return;
|
|
}
|
|
const result = context.result;
|
|
if (result && result.status !== "running" && !codeAgentResultUsesProjectionTerminalAuthority(result)) {
|
|
sendJson(response, 200, codeAgentCompatProjectionPayload(compactCodeAgentChatResultPayload(result, options), context));
|
|
return;
|
|
}
|
|
if (context.found) {
|
|
const body = codeAgentTurnStatusPayload({ traceId, result, snapshot: context.trace, resultPollError: null, refreshError: context.refreshError ?? null, options });
|
|
const resultPollTerminalReady = codeAgentResultPollTerminalReady(body);
|
|
sendJson(response, resultPollTerminalReady ? 200 : 202, codeAgentCompatProjectionPayload({
|
|
accepted: body.ok,
|
|
shortConnection: true,
|
|
...body,
|
|
waitingFor: resultPollTerminalReady ? body.waitingFor ?? context.trace?.waitingFor ?? "workbench-projection" : "agentrun-result",
|
|
...(body.terminal === true && !resultPollTerminalReady ? { status: "running", running: true, terminal: false } : {})
|
|
}, context));
|
|
return;
|
|
}
|
|
sendJson(response, 404, {
|
|
...codeAgentCompatProjectionPayload({ traceId, status: "unknown" }, context),
|
|
error: {
|
|
code: "code_agent_result_not_found",
|
|
message: `No Code Agent result is registered for ${traceId}`
|
|
}
|
|
});
|
|
}
|
|
|
|
export async function handleCodeAgentTurnHttp(request, response, url, options) {
|
|
const startedAt = nowMs();
|
|
const parts = url.pathname.split("/").filter(Boolean);
|
|
const traceId = decodeURIComponent(parts[3] ?? "");
|
|
if (!safeTraceId(traceId)) {
|
|
const body = {
|
|
ok: false,
|
|
status: "unknown",
|
|
running: false,
|
|
terminal: false,
|
|
error: {
|
|
code: "invalid_trace_id",
|
|
message: "traceId must start with trc_ and contain only safe identifier characters"
|
|
}
|
|
};
|
|
recordCodeAgentTurnReadMetric(options, url, 400, body, startedAt);
|
|
sendJson(response, 400, body);
|
|
return;
|
|
}
|
|
const requestOptions = codeAgentRequestScopedOptions(options);
|
|
let completed = false;
|
|
const emitTurnStatusReadSpan = (name, attributes = {}, error = null) => {
|
|
void emitCodeAgentOtelSpan(name, traceId, requestOptions.env ?? process.env, {
|
|
startTimeMs: startedAt,
|
|
status: error ? "error" : undefined,
|
|
error,
|
|
attributes: {
|
|
"http.method": "GET",
|
|
"http.route": "/v1/agent/turns/:traceId",
|
|
durationMs: nowMs() - startedAt,
|
|
...attributes
|
|
}
|
|
});
|
|
};
|
|
response.once?.("close", () => {
|
|
if (completed) return;
|
|
emitTurnStatusReadSpan("turn_status_read.client_closed", {
|
|
status: "client_closed",
|
|
phase: "response_close",
|
|
terminal: false
|
|
}, new Error("Code Agent turn status client connection closed before response completed."));
|
|
});
|
|
try {
|
|
const projectMismatch = await measureCodeAgentHttpPhase(requestOptions, "turn_project_check", () => traceProjectMismatchSnapshot(traceId, url, requestOptions, "turn"));
|
|
if (projectMismatch) {
|
|
completed = true;
|
|
recordCodeAgentTurnReadMetric(options, url, projectMismatch.statusCode, projectMismatch.body, startedAt);
|
|
emitTurnStatusReadSpan("turn_status_read", { "http.status_code": projectMismatch.statusCode, status: projectMismatch.body?.status ?? null, terminal: projectMismatch.body?.terminal ?? null, phase: "project_check" });
|
|
sendJson(response, projectMismatch.statusCode, projectMismatch.body);
|
|
return;
|
|
}
|
|
const resolved = await measureCodeAgentHttpPhase(requestOptions, "turn_resolve_status", () => resolveCodeAgentTurnStatusSnapshot(traceId, requestOptions));
|
|
completed = true;
|
|
recordCodeAgentTurnReadMetric(options, url, resolved.statusCode, resolved.body, startedAt);
|
|
emitTurnStatusReadSpan("turn_status_read", { "http.status_code": resolved.statusCode, status: resolved.body?.status ?? null, terminal: resolved.body?.terminal ?? null, phase: "resolved" });
|
|
sendJson(response, resolved.statusCode, resolved.body);
|
|
} catch (error) {
|
|
completed = true;
|
|
const body = {
|
|
ok: false,
|
|
status: "unknown",
|
|
running: false,
|
|
terminal: false,
|
|
traceId,
|
|
error: {
|
|
code: error?.code ?? "turn_status_read_failed",
|
|
message: error?.message ?? "Code Agent turn status read failed."
|
|
},
|
|
valuesRedacted: true
|
|
};
|
|
recordCodeAgentTurnReadMetric(options, url, 500, body, startedAt);
|
|
emitTurnStatusReadSpan("turn_status_read", { "http.status_code": 500, status: body.status, terminal: false, phase: "failed", errorCode: body.error.code }, error);
|
|
sendJson(response, 500, body);
|
|
}
|
|
}
|
|
|
|
async function resolveCodeAgentTurnStatusSnapshot(traceId, options) {
|
|
const context = await readCodeAgentCompatProjection(traceId, options);
|
|
if (context.statusCode) return context;
|
|
const body = await measureCodeAgentHttpPhase(options, "turn_payload", () => codeAgentTurnStatusPayload({
|
|
traceId,
|
|
result: context.result,
|
|
snapshot: context.trace,
|
|
resultPollError: null,
|
|
refreshError: context.refreshError ?? null,
|
|
options
|
|
}));
|
|
return { statusCode: body.ok ? 200 : 404, body: codeAgentCompatProjectionPayload(body, context) };
|
|
}
|
|
|
|
function codeAgentRequestScopedOptions(options = {}) {
|
|
return {
|
|
...options,
|
|
codeAgentTraceSessionCache: options.codeAgentTraceSessionCache instanceof Map ? options.codeAgentTraceSessionCache : new Map()
|
|
};
|
|
}
|
|
|
|
async function measureCodeAgentHttpPhase(options, phase, callback) {
|
|
const perf = options?.backendPerformance;
|
|
return perf && typeof perf.measure === "function" ? await perf.measure(phase, callback) : await callback();
|
|
}
|
|
|
|
function forbiddenTurnSnapshot(traceId) {
|
|
return {
|
|
statusCode: 403,
|
|
body: {
|
|
ok: false,
|
|
status: "unknown",
|
|
running: false,
|
|
terminal: false,
|
|
traceId,
|
|
error: {
|
|
code: "agent_session_owner_required",
|
|
message: "Only the session owner or admin can read this Code Agent turn status"
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
async function readCodeAgentCompatProjection(traceId, options = {}) {
|
|
const readModel = createWorkbenchReadModel(options, options.actor ?? null);
|
|
const session = await readModel.getSessionByTraceId(traceId);
|
|
let result = readModel.resultForTrace(traceId) ?? projectedSessionTraceResult(session, traceId);
|
|
if (result && !canAccessOwnedResult(result, options.actor)) return forbiddenTurnSnapshot(traceId);
|
|
if (result?.ownerUserId && !readModel.canReadOwner(result.ownerUserId)) return forbiddenTurnSnapshot(traceId);
|
|
const projectionTrace = await readModel.traceSnapshot(traceId);
|
|
let trace = traceSnapshotWithTerminalEvidence(projectionTrace, result, traceId, null);
|
|
const projection = readModel.projectionDiagnostics({ traceId, result, trace, refreshError: null });
|
|
recordCodeAgentProjectionMetrics(options, { result, trace, projection, refreshError: null });
|
|
const found = Boolean(result || session || (trace && trace.status !== "missing") || trace?.persisted === true);
|
|
return { result, session, trace, projection, found, refreshError: null };
|
|
}
|
|
|
|
function projectedSessionTraceResult(session, traceId) {
|
|
const safeId = safeTraceId(traceId);
|
|
if (!session || !safeId) return null;
|
|
const snapshot = session.session && typeof session.session === "object" ? session.session : null;
|
|
const traceResults = snapshot?.traceResults && typeof snapshot.traceResults === "object" ? snapshot.traceResults : null;
|
|
const stored = traceResults?.[safeId] && typeof traceResults[safeId] === "object" ? traceResults[safeId] : null;
|
|
if (!stored) return null;
|
|
return {
|
|
...stored,
|
|
traceId: safeId,
|
|
conversationId: safeConversationId(stored.conversationId ?? session.conversationId) || null,
|
|
sessionId: safeSessionId(stored.sessionId ?? session.id) || null,
|
|
threadId: safeOpaqueId(stored.threadId ?? session.threadId) || null,
|
|
ownerUserId: session.ownerUserId ?? stored.ownerUserId ?? null,
|
|
ownerRole: session.ownerRole ?? stored.ownerRole ?? null,
|
|
status: stored.status ?? session.status ?? null,
|
|
valuesRedacted: true
|
|
};
|
|
}
|
|
|
|
function codeAgentCompatProjectionPayload(payload = {}, context = {}) {
|
|
const traceId = safeTraceId(payload.traceId ?? context.trace?.traceId) ?? null;
|
|
const projection = context.projection && typeof context.projection === "object" ? context.projection : {};
|
|
return {
|
|
...payload,
|
|
projection,
|
|
projectionStatus: projection.projectionStatus ?? null,
|
|
projectionHealth: projection.projectionHealth ?? null,
|
|
lastProjectedSeq: projection.lastProjectedSeq ?? null,
|
|
sourceRunId: projection.sourceRunId ?? null,
|
|
sourceCommandId: projection.sourceCommandId ?? null,
|
|
staleMs: projection.staleMs ?? null,
|
|
blocker: projection.blocker ?? null,
|
|
workbench: traceId ? {
|
|
detailOnly: true,
|
|
turnDetailUrl: `/v1/workbench/turns/${encodeURIComponent(traceId)}`,
|
|
traceEventsDetailUrl: `/v1/workbench/traces/${encodeURIComponent(traceId)}/events`
|
|
} : null,
|
|
valuesRedacted: true,
|
|
secretMaterialStored: false
|
|
};
|
|
}
|
|
|
|
function codeAgentResultPollTerminalReady(body = {}) {
|
|
if (body?.terminal !== true) return false;
|
|
return Boolean(messageAuthorityTextValue(body.finalResponse ?? body.reply ?? body.terminalEvidence?.finalResponse));
|
|
}
|
|
|
|
async function traceProjectMismatchSnapshot(traceId, url, options, resourceKind) {
|
|
const requestedProjectId = textValue(url.searchParams.get("projectId"));
|
|
if (!requestedProjectId || !options.accessController?.getAgentSessionByTraceId) return null;
|
|
const session = await getCodeAgentSessionByTraceId(traceId, options);
|
|
if (!session) return null;
|
|
if (session.ownerUserId && options.actor?.role !== "admin" && session.ownerUserId !== options.actor?.id) return forbiddenTurnSnapshot(traceId);
|
|
const actualProjectId = textValue(session.projectId);
|
|
if (!actualProjectId || actualProjectId === requestedProjectId) return null;
|
|
return {
|
|
statusCode: 404,
|
|
body: {
|
|
ok: false,
|
|
status: "not_found",
|
|
running: false,
|
|
terminal: false,
|
|
traceId,
|
|
error: {
|
|
code: "trace_project_mismatch",
|
|
message: `${resourceKind === "turn" ? "Agent turn" : "Agent trace"} is not visible in the requested project`
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
async function getCodeAgentSessionByTraceId(traceId, options = {}) {
|
|
const safeId = safeTraceId(traceId);
|
|
if (!safeId || typeof options.accessController?.getAgentSessionByTraceId !== "function") return null;
|
|
const cache = options.codeAgentTraceSessionCache;
|
|
if (cache instanceof Map) {
|
|
if (cache.has(safeId)) return cache.get(safeId);
|
|
const session = await options.accessController.getAgentSessionByTraceId(safeId);
|
|
cache.set(safeId, session ?? null);
|
|
return session ?? null;
|
|
}
|
|
return await options.accessController.getAgentSessionByTraceId(safeId);
|
|
}
|
|
|
|
export function codeAgentTurnStatusPayload({ traceId, result, snapshot, resultPollError, refreshError, options }) {
|
|
const resultObject = result && typeof result === "object" ? result : null;
|
|
const snapshotObject = snapshot && typeof snapshot === "object" ? snapshot : null;
|
|
const projectionTerminalAuthority = codeAgentResultUsesProjectionTerminalAuthority(resultObject);
|
|
const lifecycle = codeAgentTurnLifecycleFields(traceId, resultObject ?? snapshotObject ?? {});
|
|
const events = Array.isArray(snapshotObject?.events) ? snapshotObject.events : Array.isArray(resultObject?.runnerTrace?.events) ? resultObject.runnerTrace.events : [];
|
|
const lastEvent = events.at(-1) ?? null;
|
|
const finalResponse = projectionTerminalAuthority
|
|
? snapshotObject?.finalResponse ?? snapshotObject?.terminalEvidence?.finalResponse ?? codeAgentFinalResponseEvidence(snapshotObject ?? {}, traceId)
|
|
: resultObject?.finalResponse ?? snapshotObject?.finalResponse ?? snapshotObject?.terminalEvidence?.finalResponse ?? codeAgentFinalResponseEvidence(resultObject ?? snapshotObject ?? {}, traceId);
|
|
const terminalStatus = projectionTerminalAuthority
|
|
? codeAgentProjectionTerminalStatus(snapshotObject)
|
|
: codeAgentAuthoritativeTerminalStatus(resultObject, snapshotObject, traceId);
|
|
const terminalSealBlocked = Boolean(terminalStatus && isTurnTerminalStatus(terminalStatus) && !codeAgentTerminalFinalText(finalResponse, projectionTerminalAuthority ? null : resultObject, snapshotObject));
|
|
const rawStatus = projectionTerminalAuthority
|
|
? normalizeTurnStatus(
|
|
terminalStatus,
|
|
codeAgentRunningStatus(snapshotObject?.status),
|
|
codeAgentRunningStatus(snapshotObject?.traceStatus),
|
|
codeAgentRunningStatus(snapshotObject?.runnerTrace?.status),
|
|
codeAgentRunningStatus(resultObject?.status),
|
|
"running"
|
|
)
|
|
: normalizeTurnStatus(
|
|
terminalStatus,
|
|
resultObject?.agentRun?.commandState,
|
|
resultObject?.agentRun?.status,
|
|
resultObject?.agentRun?.runStatus,
|
|
codeAgentRunningStatus(snapshotObject?.status),
|
|
codeAgentRunningStatus(snapshotObject?.traceStatus),
|
|
codeAgentRunningStatus(snapshotObject?.runnerTrace?.status),
|
|
codeAgentRunningStatus(resultObject?.status),
|
|
resultObject?.agentRun?.terminalStatus,
|
|
snapshotObject?.terminalEvidence?.traceSummary?.terminalStatus
|
|
);
|
|
const status = terminalSealBlocked ? "running" : rawStatus;
|
|
const found = Boolean(resultObject || (snapshotObject && snapshotObject.status !== "missing") || snapshotObject?.persisted === true);
|
|
const running = isTurnRunningStatus(status);
|
|
const terminal = isTurnTerminalStatus(status);
|
|
const runnerTrace = snapshotObject && snapshotObject.status !== "missing" ? snapshotObject : resultObject?.runnerTrace ?? null;
|
|
const timingFields = codeAgentResultTimingFields(resultObject, snapshotObject, runnerTrace);
|
|
return {
|
|
ok: found,
|
|
action: "code-agent.turn.status",
|
|
contractVersion: "code-agent-turn-status-v1",
|
|
status: found ? status ?? "unknown" : "unknown",
|
|
running: found ? running : false,
|
|
terminal: found ? terminal : false,
|
|
traceId,
|
|
...lifecycle,
|
|
conversationId: safeConversationId(resultObject?.conversationId ?? snapshotObject?.conversationId) || null,
|
|
sessionId: safeSessionId(resultObject?.sessionId ?? resultObject?.session?.sessionId ?? snapshotObject?.sessionId) || null,
|
|
threadId: safeOpaqueId(resultObject?.threadId ?? resultObject?.session?.threadId ?? snapshotObject?.threadId) || null,
|
|
updatedAt: textValue(resultObject?.updatedAt ?? resultObject?.agentRun?.updatedAt ?? snapshotObject?.updatedAt ?? timingFields.lastEventAt ?? timingFields.startedAt) || null,
|
|
...timingFields,
|
|
lastEventLabel: textValue(snapshotObject?.lastEventLabel ?? runnerTrace?.lastEventLabel ?? lastEvent?.label ?? lastEvent?.type) || null,
|
|
waitingFor: terminalSealBlocked ? "final_response" : textValue(snapshotObject?.waitingFor ?? runnerTrace?.waitingFor) || null,
|
|
terminalObserved: Boolean(terminalStatus),
|
|
terminalObservedStatus: terminalStatus ?? null,
|
|
terminalSealBlocked,
|
|
resultUrl: `/v1/agent/chat/result/${encodeURIComponent(traceId)}`,
|
|
traceUrl: `/v1/agent/traces/${encodeURIComponent(traceId)}`,
|
|
turnUrl: `/v1/agent/turns/${encodeURIComponent(traceId)}`,
|
|
runnerTrace: runnerTrace ? compactRunnerTraceForResult(runnerTrace, resultTraceEventLimit(options)) : null,
|
|
agentRun: resultObject?.agentRun ?? snapshotObject?.agentRun ?? null,
|
|
terminalEvidence: snapshotObject?.terminalEvidence ?? null,
|
|
finalResponse,
|
|
traceSummary: resultObject?.traceSummary ?? snapshotObject?.traceSummary ?? snapshotObject?.terminalEvidence?.traceSummary ?? null,
|
|
retention: snapshotObject?.retention ?? null,
|
|
eventCount: numberOrNull(snapshotObject?.eventCount ?? runnerTrace?.eventCount ?? events.length),
|
|
error: resultPollError || refreshError
|
|
? codeAgentRefreshErrorPayload(resultPollError ?? refreshError, traceId, resultObject?.agentRun ?? snapshotObject?.agentRun, "turn_status_degraded")
|
|
: resultObject?.error ?? snapshotObject?.error ?? null,
|
|
valuesRedacted: true,
|
|
secretMaterialStored: false
|
|
};
|
|
}
|
|
|
|
function codeAgentResultUsesProjectionTerminalAuthority(result = null) {
|
|
const agentRun = result?.agentRun && typeof result.agentRun === "object" ? result.agentRun : null;
|
|
if (!agentRun || agentRun.adapter !== "agentrun-v01") return false;
|
|
return agentRun.durableDispatch === true
|
|
|| Boolean(textValue(agentRun.dispatchIntentId ?? agentRun.commandId));
|
|
}
|
|
|
|
function codeAgentProjectionTerminalStatus(snapshotObject = null) {
|
|
const status = normalizeTurnStatus(
|
|
snapshotObject?.terminalEvidence?.status,
|
|
snapshotObject?.terminalEvidence?.traceSummary?.terminalStatus,
|
|
snapshotObject?.status
|
|
);
|
|
return status && isTurnTerminalStatus(status) && codeAgentSnapshotHasTerminalAuthority(snapshotObject) ? status : null;
|
|
}
|
|
|
|
function codeAgentTerminalFinalText(finalResponse = null, resultObject = null, snapshotObject = null) {
|
|
const candidates = [
|
|
finalResponse,
|
|
resultObject?.finalResponse,
|
|
snapshotObject?.finalResponse,
|
|
snapshotObject?.terminalEvidence?.finalResponse,
|
|
resultObject?.terminalEvidence?.finalResponse
|
|
];
|
|
for (const value of candidates) {
|
|
const text = conversationText(value);
|
|
if (text) return text;
|
|
}
|
|
return codeAgentFinalResponseText(resultObject ?? {}) || codeAgentFinalResponseText(snapshotObject ?? {});
|
|
}
|
|
|
|
function codeAgentAuthoritativeTerminalStatus(resultObject, snapshotObject, traceId) {
|
|
const sealedPayload = codeAgentPayloadHasSealedFinalResponse(resultObject ?? {}) ? resultObject : codeAgentPayloadHasSealedFinalResponse(snapshotObject ?? {}) ? snapshotObject : null;
|
|
if (sealedPayload) {
|
|
const sealedStatus = normalizeTurnStatus(sealedPayload?.finalResponse?.status, sealedPayload?.terminalEvidence?.finalResponse?.status, sealedPayload?.agentRun?.terminalStatus, sealedPayload?.agentRun?.status, sealedPayload?.status);
|
|
return sealedStatus && isTurnTerminalStatus(sealedStatus) ? sealedStatus : "completed";
|
|
}
|
|
const snapshotStatus = normalizeTurnStatus(snapshotObject?.terminalEvidence?.traceSummary?.terminalStatus, snapshotObject?.terminalEvidence?.agentRun?.terminalStatus, snapshotObject?.terminalEvidence?.status, snapshotObject?.status);
|
|
if (snapshotStatus && isTurnTerminalStatus(snapshotStatus) && codeAgentSnapshotHasTerminalAuthority(snapshotObject)) return snapshotStatus;
|
|
const agentRunStatus = normalizeTurnStatus(resultObject?.agentRun?.terminalStatus, resultObject?.agentRun?.commandState, resultObject?.agentRun?.status, resultObject?.agentRun?.runStatus);
|
|
if (agentRunStatus && isTurnTerminalStatus(agentRunStatus)) return agentRunStatus;
|
|
const resultStatus = normalizeTurnStatus(resultObject?.status);
|
|
if (resultStatus && isTurnTerminalStatus(resultStatus) && codeAgentResultHasTerminalAuthority(resultObject, traceId)) return resultStatus;
|
|
return null;
|
|
}
|
|
|
|
function codeAgentSnapshotHasTerminalAuthority(snapshot = null) {
|
|
if (!snapshot || typeof snapshot !== "object") return false;
|
|
if (snapshot.terminal === true || snapshot.sealed === true) return true;
|
|
if (snapshot.terminalEvidence?.available === true || snapshot.terminalEvidence?.source) return true;
|
|
const events = Array.isArray(snapshot.events) ? snapshot.events : [];
|
|
return events.some((event) => event?.terminal === true);
|
|
}
|
|
|
|
function codeAgentResultHasTerminalAuthority(result = null, traceId = null) {
|
|
if (!result || typeof result !== "object") return false;
|
|
if (result.terminal === true || result.sealed === true) return true;
|
|
if (result.error || result.blocker) return true;
|
|
if (codeAgentPayloadHasSealedFinalResponse(result)) return true;
|
|
if (agentRunTerminalTraceEvidence(result, traceId)) return true;
|
|
return Boolean(textValue(result.finishedAt ?? result.completedAt ?? result.endedAt));
|
|
}
|
|
|
|
function codeAgentRefreshErrorPayload(error, traceId, agentRun, fallbackCode) {
|
|
return {
|
|
code: error?.code ?? fallbackCode,
|
|
layer: error?.layer ?? "agentrun",
|
|
category: error?.category ?? (error?.code === "agentrun_timeout" ? "upstream-timeout" : "upstream-refresh-failed"),
|
|
retryable: error?.retryable !== false,
|
|
message: error?.message ?? "Code Agent turn status refresh degraded",
|
|
traceId,
|
|
runId: agentRun?.runId ?? error?.runId ?? null,
|
|
commandId: agentRun?.commandId ?? error?.commandId ?? null,
|
|
method: error?.method ?? null,
|
|
route: error?.route ?? error?.path ?? null,
|
|
timeoutMs: numberOrNull(error?.timeoutMs),
|
|
timeoutStage: error?.timeoutStage ?? null,
|
|
upstreamStatus: numberOrNull(error?.statusCode),
|
|
managerHost: error?.managerHost ?? null,
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
function isTurnRunningStatus(status) {
|
|
return status === "running";
|
|
}
|
|
|
|
function codeAgentRunningStatus(value) {
|
|
const status = normalizeTurnStatus(value);
|
|
return isTurnRunningStatus(status) ? status : null;
|
|
}
|
|
|
|
function isTurnTerminalStatus(status) {
|
|
return CODE_AGENT_TERMINAL_STATUSES.has(String(status ?? "").trim().toLowerCase().replace(/_/gu, "-"));
|
|
}
|
|
|
|
export async function handleCodeAgentInspectHttp(request, response, url, options) {
|
|
const query = {
|
|
conversationId: safeConversationId(url.searchParams.get("conversationId")),
|
|
sessionId: safeSessionId(url.searchParams.get("sessionId")),
|
|
threadId: safeOpaqueId(url.searchParams.get("threadId")),
|
|
traceId: safeTraceId(url.searchParams.get("traceId"))
|
|
};
|
|
const sessionRegistry = options.sessionRegistry;
|
|
const traceStore = options.traceStore ?? defaultCodeAgentTraceStore;
|
|
const manager = options.codexStdioManager;
|
|
const managerDescription = manager && typeof manager.describe === "function" ? manager.describe() : null;
|
|
const managerRecentSessions = Array.isArray(managerDescription?.recentSessions) ? managerDescription.recentSessions : [];
|
|
const directManagerSession = query.sessionId && manager && typeof manager.get === "function"
|
|
? manager.get(query.sessionId, { conversationId: query.conversationId })
|
|
: null;
|
|
const matchedManagerSession = directManagerSession ?? managerRecentSessions.find((session) => sessionMatchesInspectQuery(session, query)) ?? null;
|
|
const registryInspect = sessionRegistry && typeof sessionRegistry.inspect === "function"
|
|
? sessionRegistry.inspect(query)
|
|
: { ok: false, status: "unavailable", traceIds: [], session: null, conversationFacts: null };
|
|
const resultEvidence = await codeAgentInspectResultEvidence(query.traceId, options);
|
|
const session = matchedManagerSession ?? registryInspect.session ?? resultEvidence.session ?? null;
|
|
const conversationFacts = registryInspect.conversationFacts ?? resultEvidence.conversationFacts ?? null;
|
|
const requestedRunnerTrace = query.traceId ? traceStore.snapshot(query.traceId) : null;
|
|
const requestedTraceFound = Boolean(requestedRunnerTrace && requestedRunnerTrace.status !== "missing");
|
|
const traceIds = uniqueStrings([
|
|
requestedTraceFound ? query.traceId : null,
|
|
session?.currentTraceId,
|
|
session?.lastTraceId,
|
|
conversationFacts?.latestTraceId,
|
|
resultEvidence.latestTraceId,
|
|
...(Array.isArray(conversationFacts?.traceIds) ? conversationFacts.traceIds : []),
|
|
...(Array.isArray(registryInspect.traceIds) ? registryInspect.traceIds : [])
|
|
]);
|
|
const latestTraceId = traceIds[0] ?? null;
|
|
const runnerTrace = latestTraceId
|
|
? latestTraceId === query.traceId && requestedRunnerTrace
|
|
? requestedRunnerTrace
|
|
: traceStore.snapshot(latestTraceId)
|
|
: null;
|
|
const found = Boolean(registryInspect.ok || session || latestTraceId);
|
|
sendJson(response, found ? 200 : 404, {
|
|
ok: found,
|
|
action: "code-agent.chat.inspect",
|
|
status: found ? "found" : "not_found",
|
|
query,
|
|
session,
|
|
conversationFacts,
|
|
traceIds,
|
|
latestTraceId,
|
|
traceUrl: latestTraceId ? `/v1/agent/traces/${encodeURIComponent(latestTraceId)}` : null,
|
|
resultUrl: latestTraceId ? `/v1/agent/chat/result/${encodeURIComponent(latestTraceId)}` : null,
|
|
runnerTrace,
|
|
valuesRedacted: true,
|
|
secretMaterialStored: false
|
|
});
|
|
}
|
|
|
|
async function codeAgentInspectResultEvidence(traceId, options = {}) {
|
|
if (!safeTraceId(traceId)) return { session: null, conversationFacts: null, latestTraceId: null };
|
|
const cached = options.codeAgentChatResults?.get?.(traceId) ?? null;
|
|
const persisted = cached ? null : await loadPersistedAgentRunResult(traceId, options);
|
|
const result = cached ?? persisted ?? null;
|
|
if (!result || typeof result !== "object") return { session: null, conversationFacts: null, latestTraceId: null };
|
|
const agentRun = result.agentRun && typeof result.agentRun === "object" ? result.agentRun : null;
|
|
const resultSession = result.session && typeof result.session === "object" ? result.session : null;
|
|
const sessionReuse = result.sessionReuse && typeof result.sessionReuse === "object" ? result.sessionReuse : null;
|
|
const providerTrace = result.providerTrace && typeof result.providerTrace === "object" ? result.providerTrace : null;
|
|
const sessionId = safeSessionId(result.sessionId ?? resultSession?.sessionId ?? sessionReuse?.sessionId ?? agentRun?.sessionId) || null;
|
|
const conversationId = safeConversationId(result.conversationId ?? resultSession?.conversationId ?? sessionReuse?.conversationId ?? agentRun?.conversationId) || null;
|
|
const threadId = safeOpaqueId(result.threadId ?? resultSession?.threadId ?? sessionReuse?.threadId ?? providerTrace?.threadId ?? agentRun?.threadId) || null;
|
|
const status = textValue(resultSession?.status ?? result.status ?? agentRun?.status) || null;
|
|
const updatedAt = textValue(result.updatedAt ?? agentRun?.updatedAt ?? resultSession?.updatedAt) || null;
|
|
const session = sessionId || conversationId || threadId ? {
|
|
sessionId,
|
|
conversationId,
|
|
threadId,
|
|
status,
|
|
lastTraceId: traceId,
|
|
source: "code-agent-result",
|
|
agentRun: agentRun ? agentRunSessionEvidence(result).agentRun : null,
|
|
valuesRedacted: true,
|
|
secretMaterialStored: false
|
|
} : null;
|
|
const conversationFacts = conversationId ? {
|
|
conversationId,
|
|
sessionId,
|
|
threadId,
|
|
latestTraceId: traceId,
|
|
traceIds: [traceId],
|
|
turnCount: 1,
|
|
source: "code-agent-result",
|
|
latestStatus: status,
|
|
updatedAt,
|
|
valuesRedacted: true,
|
|
secretMaterialStored: false
|
|
} : null;
|
|
return { session, conversationFacts, latestTraceId: traceId };
|
|
}
|
|
|
|
function sessionMatchesInspectQuery(session, query) {
|
|
if (!session || typeof session !== "object") return false;
|
|
if (query.sessionId && session.sessionId === query.sessionId) return true;
|
|
if (query.threadId && session.threadId === query.threadId) return true;
|
|
if (query.conversationId && session.conversationId === query.conversationId) return true;
|
|
if (query.traceId && (session.currentTraceId === query.traceId || session.lastTraceId === query.traceId)) return true;
|
|
return false;
|
|
}
|
|
|
|
export async function handleCodeAgentCancelHttp(request, response, options) {
|
|
const body = await readBody(request, options.bodyLimitBytes);
|
|
let params = {};
|
|
|
|
try {
|
|
params = body ? JSON.parse(body) : {};
|
|
} catch (error) {
|
|
sendJson(response, 400, {
|
|
...createCodeAgentErrorPayload({
|
|
code: "parse_error",
|
|
message: "Invalid JSON body",
|
|
reason: error.message,
|
|
traceId: getHeader(request, "x-trace-id") || "trc_unassigned",
|
|
layer: "api",
|
|
retryable: true
|
|
})
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
sendJson(response, 400, {
|
|
...createCodeAgentErrorPayload({
|
|
code: "invalid_params",
|
|
message: "Code Agent cancel body must be a JSON object",
|
|
traceId: getHeader(request, "x-trace-id") || "trc_unassigned",
|
|
layer: "api",
|
|
retryable: true
|
|
})
|
|
});
|
|
return;
|
|
}
|
|
|
|
const traceId = safeTraceId(getHeader(request, "x-trace-id") || params.traceId);
|
|
const traceStore = options.traceStore ?? defaultCodeAgentTraceStore;
|
|
const snapshot = traceId ? traceStore.snapshot(traceId) : null;
|
|
const durableResult = traceId ? await loadPersistedAgentRunResult(traceId, options) : null;
|
|
const currentResult = durableResult ?? (traceId ? options.codeAgentChatResults?.get(traceId) : null);
|
|
if (currentResult?.agentRun?.commandId) {
|
|
const mismatch = codeAgentCancelScopeMismatch(params, currentResult);
|
|
if (mismatch) {
|
|
traceStore.append(traceId, {
|
|
type: "cancel",
|
|
status: "blocked",
|
|
label: "agentrun:cancel:scope_mismatch",
|
|
errorCode: "cancel_scope_mismatch",
|
|
message: `Cancel request ${mismatch.field} does not match the trace owner; AgentRun cancel was not forwarded.`,
|
|
requested: mismatch.requested,
|
|
expected: mismatch.expected,
|
|
waitingFor: "cancel-scope",
|
|
valuesPrinted: false
|
|
});
|
|
sendJson(response, 409, cancelBlockedPayload({
|
|
code: "cancel_scope_mismatch",
|
|
message: `取消请求的 ${mismatch.field} 与 trace 归属不一致,已拒绝转发 AgentRun cancel,避免误取消其他 session。`,
|
|
traceId,
|
|
conversationId: safeConversationId(params.conversationId) || safeConversationId(currentResult.conversationId) || null,
|
|
sessionId: safeSessionId(params.sessionId) || safeSessionId(currentResult.sessionId) || null,
|
|
runnerTrace: traceStore.snapshot(traceId),
|
|
status: "blocked"
|
|
}));
|
|
return;
|
|
}
|
|
await recordCodeAgentSessionInputFact({
|
|
params: {
|
|
...params,
|
|
sessionId: currentResult.sessionId ?? currentResult.session?.sessionId ?? params.sessionId,
|
|
messageId: codeAgentTurnLifecycleFields(traceId, currentResult).userMessageId,
|
|
ownerUserId: options.actor?.id,
|
|
ownerRole: options.actor?.role
|
|
},
|
|
options,
|
|
traceId,
|
|
delivery: "cancel",
|
|
status: "promoted",
|
|
commandId: currentResult.agentRun.commandId
|
|
});
|
|
const payload = await cancelAgentRunChatTurn({ traceId, currentResult, options, traceStore });
|
|
if (payload) {
|
|
sendJson(response, 200, payload);
|
|
return;
|
|
}
|
|
}
|
|
const sessionId = safeSessionId(params.sessionId) || safeSessionId(snapshot?.sessionId);
|
|
const conversationId = safeConversationId(params.conversationId);
|
|
const manager = options.codexStdioManager;
|
|
|
|
if (!traceId) {
|
|
sendJson(response, 400, cancelBlockedPayload({
|
|
code: "cancel_trace_missing",
|
|
message: "traceId is required to cancel the current Code Agent request.",
|
|
traceId: "trc_unassigned",
|
|
conversationId,
|
|
sessionId
|
|
}));
|
|
return;
|
|
}
|
|
|
|
if (!manager || typeof manager.get !== "function" || typeof manager.cancel !== "function") {
|
|
traceStore.append(traceId, {
|
|
type: "cancel",
|
|
status: "unsupported",
|
|
label: "cancel:unsupported",
|
|
errorCode: "cancel_unsupported",
|
|
message: "Codex stdio cancel/interrupt backend is not available on this runtime.",
|
|
waitingFor: "session-control"
|
|
});
|
|
const runnerTrace = traceStore.snapshot(traceId);
|
|
sendJson(response, 501, cancelBlockedPayload({
|
|
code: "cancel_unsupported",
|
|
message: "当前后端不支持 Code Agent interrupt/cancel;本次按 unsupported/degraded 返回,不会静默假装已中断。",
|
|
traceId,
|
|
conversationId,
|
|
sessionId,
|
|
runnerTrace,
|
|
status: "degraded",
|
|
unsupported: true,
|
|
degraded: true
|
|
}));
|
|
return;
|
|
}
|
|
|
|
if (!sessionId) {
|
|
traceStore.append(traceId, {
|
|
type: "cancel",
|
|
status: "blocked",
|
|
label: "cancel:not_cancelable",
|
|
errorCode: "cancel_session_missing",
|
|
message: "Cancel request did not include a bound Codex stdio sessionId.",
|
|
waitingFor: "session-binding"
|
|
});
|
|
sendJson(response, 409, cancelBlockedPayload({
|
|
code: "cancel_session_missing",
|
|
message: "当前请求尚未暴露可取消的 Codex stdio sessionId;页面不能只隐藏 UI,已保留输入和 trace。",
|
|
traceId,
|
|
conversationId,
|
|
sessionId: null,
|
|
runnerTrace: traceStore.snapshot(traceId)
|
|
}));
|
|
return;
|
|
}
|
|
|
|
const currentSession = manager.get(sessionId, { conversationId }) ?? null;
|
|
if (!currentSession || !["busy", "creating"].includes(currentSession.status)) {
|
|
traceStore.append(traceId, {
|
|
type: "cancel",
|
|
status: "blocked",
|
|
label: "cancel:not_in_flight",
|
|
errorCode: "cancel_not_in_flight",
|
|
message: `Session ${sessionId} is not an in-flight Codex stdio request.`,
|
|
sessionId,
|
|
sessionStatus: currentSession?.status ?? "missing"
|
|
});
|
|
sendJson(response, 409, cancelBlockedPayload({
|
|
code: currentSession ? "cancel_not_in_flight" : "cancel_session_not_found",
|
|
message: currentSession
|
|
? `当前 session 状态为 ${currentSession.status},没有可取消的 in-flight Codex stdio 请求。`
|
|
: `没有找到 sessionId=${sessionId} 的 Codex stdio session。`,
|
|
traceId,
|
|
conversationId,
|
|
sessionId,
|
|
session: currentSession,
|
|
runnerTrace: traceStore.snapshot(traceId)
|
|
}));
|
|
return;
|
|
}
|
|
|
|
const canceledSession = manager.cancel(sessionId, {
|
|
traceId,
|
|
conversationId,
|
|
reason: "user_cancel"
|
|
});
|
|
traceStore.append(traceId, {
|
|
type: "cancel",
|
|
status: "canceled",
|
|
label: "cancel:canceled",
|
|
message: "User canceled the current Codex stdio request.",
|
|
sessionId,
|
|
sessionStatus: canceledSession?.status ?? "canceled",
|
|
sessionLifecycleStatus: codeAgentSessionLifecycleSummary({ session: canceledSession, status: "canceled" }).status,
|
|
waitingFor: "user-retry",
|
|
terminal: true
|
|
});
|
|
const runnerTraceSnapshot = traceStore.snapshot(traceId);
|
|
const sessionSummary = codeAgentSessionLifecycleSummary({
|
|
session: canceledSession,
|
|
runnerTrace: runnerTraceSnapshot,
|
|
status: "canceled"
|
|
});
|
|
const runnerTrace = {
|
|
...runnerTraceSnapshot,
|
|
sessionLifecycleStatus: sessionSummary.status
|
|
};
|
|
const payload = {
|
|
accepted: true,
|
|
canceled: true,
|
|
status: "canceled",
|
|
conversationId: conversationId ?? canceledSession?.conversationId ?? null,
|
|
sessionId,
|
|
traceId,
|
|
session: canceledSession,
|
|
sessionLifecycleStatus: sessionSummary.status,
|
|
sessionLifecycle: sessionSummary,
|
|
sessionSummary,
|
|
runnerTrace,
|
|
lastTraceEvent: runnerTrace.lastEvent,
|
|
retryable: true,
|
|
error: {
|
|
code: "codex_stdio_canceled",
|
|
layer: "session",
|
|
category: "canceled",
|
|
retryable: true,
|
|
message: "user canceled current Code Agent request",
|
|
userMessage: "当前 Codex stdio 请求已取消;输入、sessionId、traceId 和最后 trace event 已保留,可重试上一条消息。",
|
|
traceId,
|
|
route: "/v1/agent/chat/cancel",
|
|
toolName: "codex-stdio.cancel"
|
|
},
|
|
userMessage: "当前 Codex stdio 请求已取消;输入、sessionId、traceId 和最后 trace event 已保留,可重试上一条消息。",
|
|
updatedAt: new Date().toISOString()
|
|
};
|
|
await recordCodeAgentSessionOwner({ payload, params: { ...params, traceId, ownerUserId: options.actor?.id, ownerRole: options.actor?.role }, options, status: "canceled" });
|
|
recordCodeAgentConversationFact(payload, options);
|
|
options.codeAgentChatResults?.set(traceId, annotateOwner(payload, { ownerUserId: options.actor?.id, ownerRole: options.actor?.role }));
|
|
sendJson(response, 200, payload);
|
|
}
|
|
|
|
export { codeAgentCompatProjectionPayload, codeAgentRequestScopedOptions, measureCodeAgentHttpPhase, readCodeAgentCompatProjection, traceProjectMismatchSnapshot };
|