fix(agentrun): 继承 Aipod 会话持久身份

This commit is contained in:
Codex
2026-07-13 10:36:04 +02:00
parent 7340302ae4
commit eb63823b66
2 changed files with 233 additions and 1 deletions
+61 -1
View File
@@ -1027,7 +1027,13 @@ export function agentRunSessionRunPolicyDisclosure(runBody: Record<string, unkno
}
export async function sessionSendWithAipodRest(sessionId: string, aipod: string, args: string[]): Promise<Record<string, unknown>> {
const rendered = await agentRunRestRequest("agentrun aipod-specs render", "POST", `/api/v1/aipod-specs/${encodeURIComponent(aipod)}/render`, await aipodRenderInputFromArgs(args, 2, { sessionId }));
const existing = await fetchAgentRunSessionOrNull(sessionId, args);
const renderInput = inheritDurableSessionAipodIdentity(
await aipodRenderInputFromArgs(args, 2, { sessionId }),
sessionId,
existing,
);
const rendered = await agentRunRestRequest("agentrun aipod-specs render", "POST", `/api/v1/aipod-specs/${encodeURIComponent(aipod)}/render`, renderInput);
const renderedData = record(innerData(rendered));
const task = normalizeAipodRenderedQueueTask(record(renderedData.queueTask), args, aipod);
if (Object.keys(task).length === 0) throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} render did not return queueTask`);
@@ -1079,6 +1085,60 @@ export async function sessionSendWithAipodRest(sessionId: string, aipod: string,
};
}
export function inheritDurableSessionAipodIdentity(
renderInput: Record<string, unknown>,
sessionId: string,
existing: Record<string, unknown> | null,
): Record<string, unknown> {
if (existing === null) return renderInput;
const durableSessionId = stringOrNull(existing.sessionId);
const tenantId = stringOrNull(existing.tenantId);
const projectId = stringOrNull(existing.projectId);
if (durableSessionId !== sessionId || tenantId === null || projectId === null) {
throw new AgentRunRestError("schema-mismatch", `session ${sessionId} durable identity is incomplete or mismatched`, {
details: { sessionId, valuesPrinted: false },
});
}
assertDurableSessionBoundary(renderInput.tenantId, tenantId, sessionId, "tenantId");
assertDurableSessionBoundary(renderInput.projectId, projectId, sessionId, "projectId");
const requestedSessionRef = record(renderInput.sessionRef);
const requestedSessionId = stringOrNull(requestedSessionRef.sessionId);
if (requestedSessionId !== null && requestedSessionId !== sessionId) {
throw durableSessionBoundaryError(sessionId, "sessionRef.sessionId");
}
const conversationId = stringOrNull(existing.conversationId);
const threadId = stringOrNull(existing.threadId);
const expiresAt = stringOrNull(existing.expiresAt);
return {
...renderInput,
tenantId,
projectId,
sessionId,
sessionRef: {
sessionId,
...(conversationId === null ? {} : { conversationId }),
...(threadId === null ? {} : { threadId }),
...(expiresAt === null ? {} : { expiresAt }),
metadata: {
...record(requestedSessionRef.metadata),
...record(existing.metadata),
},
},
};
}
function assertDurableSessionBoundary(value: unknown, durable: string, sessionId: string, field: string): void {
if (value === undefined || value === null || value === durable) return;
throw durableSessionBoundaryError(sessionId, field);
}
function durableSessionBoundaryError(sessionId: string, field: string): AgentRunRestError {
return new AgentRunRestError("tenant-policy-denied", "sessionRef cannot be reused across tenant or project boundary", {
httpStatus: 403,
details: { sessionId, field, valuesPrinted: false },
});
}
export async function fetchAgentRunSessionOrNull(sessionId: string, args: string[]): Promise<Record<string, unknown> | null> {
try {
return record(innerData(await agentRunRestRequest("agentrun sessions show", "GET", `/api/v1/sessions/${encodeURIComponent(sessionId)}${agentRunQuery(args, ["reader-id"])}`)));