feat(context): verify product session scope
This commit is contained in:
+32
-1
@@ -13,6 +13,11 @@ import { type SessionMetadataStore } from "../sessions/metadataStore.js";
|
||||
import { type SessionTranscriptStore } from "../sessions/transcriptStore.js";
|
||||
import { type SessionUiStateStore } from "../sessions/uiStateStore.js";
|
||||
import { uiRegistryResponse } from "../uiEnvelope/registry.js";
|
||||
import {
|
||||
attachTrustedAgentContext,
|
||||
getLocalAgentContext,
|
||||
ProductContextError,
|
||||
} from "../context/localContext.js";
|
||||
import { registerChatAuxiliaryRoutes } from "./chatAuxiliaryRoutes.js";
|
||||
import { registerChatInteractionRoutes } from "./chatInteractionRoutes.js";
|
||||
import { createChatRunState } from "./chatRunState.js";
|
||||
@@ -48,6 +53,18 @@ export const buildChatRouter = (options: BuildChatRouterOptions) => {
|
||||
const chatRouter = Router();
|
||||
const { activeRuns, lastRunStatuses } = runState;
|
||||
|
||||
chatRouter.use((req, res, next) => {
|
||||
void attachTrustedAgentContext(req)
|
||||
.then(() => next())
|
||||
.catch((error) => {
|
||||
const status = error instanceof ProductContextError ? error.status : 503;
|
||||
res.status(status).json({
|
||||
code: "PRODUCT_CONTEXT_UNAVAILABLE",
|
||||
message: error instanceof Error ? error.message : "product identity verification failed",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
chatRouter.get("/models", (_req, res) => {
|
||||
res.json({
|
||||
default_model: resolveDefaultModel(config.OPENCODE_MODEL),
|
||||
@@ -63,13 +80,27 @@ export const buildChatRouter = (options: BuildChatRouterOptions) => {
|
||||
res.json(frontendActionRegistryResponse);
|
||||
});
|
||||
|
||||
chatRouter.post("/frontend-actions/:action_id/result", (req, res) => {
|
||||
chatRouter.post("/frontend-actions/:action_id/result", async (req, res) => {
|
||||
const sessionId = req.header("x-agent-session-id")?.trim() ?? "";
|
||||
if (!sessionId || req.params.action_id !== req.body?.actionId) {
|
||||
res.status(400).json({ message: "invalid frontend action result" });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const context = getLocalAgentContext(req);
|
||||
const sessionRecord = await sessionMetadataStore.get(
|
||||
{
|
||||
actorKey: context.actorKey,
|
||||
projectId: context.projectId,
|
||||
projectKey: context.projectKey,
|
||||
userId: context.userId,
|
||||
},
|
||||
sessionId,
|
||||
);
|
||||
if (!sessionRecord) {
|
||||
res.status(404).json({ message: "session not found" });
|
||||
return;
|
||||
}
|
||||
const outcome = frontendActionCoordinator.submitResult(sessionId, req.body);
|
||||
res.json({
|
||||
accepted: true,
|
||||
|
||||
@@ -59,7 +59,7 @@ export const registerChatAuxiliaryRoutes = (
|
||||
return;
|
||||
}
|
||||
const result = await resultReferenceResolver.getFullAuthorized(resultRef, {
|
||||
actorKey: toActorKey(context.userId), clientSessionId, projectId: context.projectId,
|
||||
actorKey: context.actorKey, clientSessionId, projectId: context.projectId,
|
||||
}, { expectedKind: RESULT_REFERENCE_KIND.serverApiPayload });
|
||||
if (!result) {
|
||||
res.status(404).json({ message: "result_ref not found" });
|
||||
@@ -84,23 +84,25 @@ export const registerChatAuxiliaryRoutes = (
|
||||
}
|
||||
|
||||
try {
|
||||
frontendActionCoordinator.cancelSession(parsed.data.session_id);
|
||||
const authContext = getLocalAgentContext(req);
|
||||
const projectId = authContext.projectId;
|
||||
const userId = authContext.userId;
|
||||
const actorKey = toActorKey(userId);
|
||||
const projectKey = toProjectKey(projectId);
|
||||
const actorKey = authContext.actorKey;
|
||||
const projectKey = authContext.projectKey;
|
||||
const sessionRecord = await sessionMetadataStore.get(
|
||||
{ actorKey, projectId, projectKey, userId },
|
||||
parsed.data.session_id,
|
||||
);
|
||||
const binding = sessionRecord
|
||||
? await sessionBridge.abort({
|
||||
clientSessionId: sessionRecord.sessionId,
|
||||
sessionId: sessionRecord.sessionId,
|
||||
})
|
||||
: null;
|
||||
const run = activeRuns.get(parsed.data.session_id);
|
||||
if (!sessionRecord) {
|
||||
res.status(204).end();
|
||||
return;
|
||||
}
|
||||
frontendActionCoordinator.cancelSession(sessionRecord.sessionId);
|
||||
const binding = await sessionBridge.abort({
|
||||
clientSessionId: sessionRecord.sessionId,
|
||||
sessionId: sessionRecord.sessionId,
|
||||
});
|
||||
const run = activeRuns.get(sessionRecord.sessionId);
|
||||
if (run && run.status === "running") {
|
||||
run.status = "aborted";
|
||||
lastRunStatuses.set(parsed.data.session_id, "aborted");
|
||||
@@ -115,16 +117,14 @@ export const registerChatAuxiliaryRoutes = (
|
||||
progress: completeBackendProgress(message.progress),
|
||||
todos: cancelBackendTodos(message.todos),
|
||||
}));
|
||||
if (sessionRecord) {
|
||||
const currentState = await sessionUiStateStore.read(
|
||||
toSessionUiStateContext(sessionRecord.sessionId),
|
||||
);
|
||||
await sessionUiStateStore.write(toSessionUiStateContext(sessionRecord.sessionId), {
|
||||
sessionId: sessionRecord.sessionId,
|
||||
isTitleManuallyEdited: currentState?.isTitleManuallyEdited ?? false,
|
||||
messages: run.messages,
|
||||
});
|
||||
}
|
||||
const currentState = await sessionUiStateStore.read(
|
||||
toSessionUiStateContext(sessionRecord.sessionId),
|
||||
);
|
||||
await sessionUiStateStore.write(toSessionUiStateContext(sessionRecord.sessionId), {
|
||||
sessionId: sessionRecord.sessionId,
|
||||
isTitleManuallyEdited: currentState?.isTitleManuallyEdited ?? false,
|
||||
messages: run.messages,
|
||||
});
|
||||
for (const subscriber of run.subscribers) {
|
||||
subscriber.write("error", {
|
||||
session_id: parsed.data.session_id,
|
||||
@@ -135,11 +135,6 @@ export const registerChatAuxiliaryRoutes = (
|
||||
run.subscribers.clear();
|
||||
}
|
||||
|
||||
if (!binding && !run) {
|
||||
res.status(204).end();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
{
|
||||
clientSessionId: parsed.data.session_id,
|
||||
|
||||
@@ -91,8 +91,8 @@ export const registerChatInteractionRoutes = (
|
||||
const authContext = getLocalAgentContext(req);
|
||||
const projectId = authContext.projectId;
|
||||
const userId = authContext.userId;
|
||||
const actorKey = toActorKey(userId);
|
||||
const projectKey = toProjectKey(projectId);
|
||||
const actorKey = authContext.actorKey;
|
||||
const projectKey = authContext.projectKey;
|
||||
const sessionRecord = await sessionMetadataStore.get(
|
||||
{ actorKey, projectId, projectKey, userId },
|
||||
parsed.data.session_id,
|
||||
@@ -216,8 +216,8 @@ export const registerChatInteractionRoutes = (
|
||||
const authContext = getLocalAgentContext(req);
|
||||
const projectId = authContext.projectId;
|
||||
const userId = authContext.userId;
|
||||
const actorKey = toActorKey(userId);
|
||||
const projectKey = toProjectKey(projectId);
|
||||
const actorKey = authContext.actorKey;
|
||||
const projectKey = authContext.projectKey;
|
||||
const sessionRecord = await sessionMetadataStore.get(
|
||||
{ actorKey, projectId, projectKey, userId },
|
||||
parsed.data.session_id,
|
||||
@@ -348,8 +348,8 @@ export const registerChatInteractionRoutes = (
|
||||
const authContext = getLocalAgentContext(req);
|
||||
const projectId = authContext.projectId;
|
||||
const userId = authContext.userId;
|
||||
const actorKey = toActorKey(userId);
|
||||
const projectKey = toProjectKey(projectId);
|
||||
const actorKey = authContext.actorKey;
|
||||
const projectKey = authContext.projectKey;
|
||||
const sessionRecord = await sessionMetadataStore.get(
|
||||
{ actorKey, projectId, projectKey, userId },
|
||||
parsed.data.session_id,
|
||||
|
||||
@@ -73,8 +73,8 @@ export const registerChatSessionRoutes = (
|
||||
const authContext = getLocalAgentContext(req);
|
||||
const projectId = authContext.projectId;
|
||||
const userId = authContext.userId;
|
||||
const actorKey = toActorKey(userId);
|
||||
const projectKey = toProjectKey(projectId);
|
||||
const actorKey = authContext.actorKey;
|
||||
const projectKey = authContext.projectKey;
|
||||
const requestedSessionId = parsed.data.session_id?.trim();
|
||||
let sessionId = requestedSessionId;
|
||||
if (!sessionId) {
|
||||
@@ -112,8 +112,8 @@ export const registerChatSessionRoutes = (
|
||||
const authContext = getLocalAgentContext(req);
|
||||
const projectId = authContext.projectId;
|
||||
const userId = authContext.userId;
|
||||
const actorKey = toActorKey(userId);
|
||||
const projectKey = toProjectKey(projectId);
|
||||
const actorKey = authContext.actorKey;
|
||||
const projectKey = authContext.projectKey;
|
||||
const records = await sessionMetadataStore.list({
|
||||
actorKey,
|
||||
projectId,
|
||||
@@ -139,8 +139,8 @@ export const registerChatSessionRoutes = (
|
||||
const authContext = getLocalAgentContext(req);
|
||||
const projectId = authContext.projectId;
|
||||
const userId = authContext.userId;
|
||||
const actorKey = toActorKey(userId);
|
||||
const projectKey = toProjectKey(projectId);
|
||||
const actorKey = authContext.actorKey;
|
||||
const projectKey = authContext.projectKey;
|
||||
if (!sessionId) {
|
||||
res.status(400).json({ message: "session_id is required" });
|
||||
return;
|
||||
@@ -183,8 +183,8 @@ export const registerChatSessionRoutes = (
|
||||
const authContext = getLocalAgentContext(req);
|
||||
const projectId = authContext.projectId;
|
||||
const userId = authContext.userId;
|
||||
const actorKey = toActorKey(userId);
|
||||
const projectKey = toProjectKey(projectId);
|
||||
const actorKey = authContext.actorKey;
|
||||
const projectKey = authContext.projectKey;
|
||||
if (!sessionId) {
|
||||
res.status(400).json({ message: "session_id is required" });
|
||||
return;
|
||||
@@ -213,7 +213,7 @@ export const registerChatSessionRoutes = (
|
||||
session_id: sessionRecord.sessionId,
|
||||
messages: state?.messages ?? run?.messages ?? [],
|
||||
is_streaming: run?.status === "running",
|
||||
run_status: getSessionRunStatus(sessionRecord.sessionId) ?? "completed",
|
||||
run_status: getSessionRunStatus(sessionRecord.sessionId),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -254,8 +254,8 @@ export const registerChatSessionRoutes = (
|
||||
const authContext = getLocalAgentContext(req);
|
||||
const projectId = authContext.projectId;
|
||||
const userId = authContext.userId;
|
||||
const actorKey = toActorKey(userId);
|
||||
const projectKey = toProjectKey(projectId);
|
||||
const actorKey = authContext.actorKey;
|
||||
const projectKey = authContext.projectKey;
|
||||
if (!sessionId || !title) {
|
||||
res.status(400).json({ message: "session_id and title are required" });
|
||||
return;
|
||||
@@ -296,8 +296,8 @@ export const registerChatSessionRoutes = (
|
||||
const authContext = getLocalAgentContext(req);
|
||||
const projectId = authContext.projectId;
|
||||
const userId = authContext.userId;
|
||||
const actorKey = toActorKey(userId);
|
||||
const projectKey = toProjectKey(projectId);
|
||||
const actorKey = authContext.actorKey;
|
||||
const projectKey = authContext.projectKey;
|
||||
if (!sessionId) {
|
||||
res.status(400).json({ message: "session_id is required" });
|
||||
return;
|
||||
@@ -338,8 +338,8 @@ export const registerChatSessionRoutes = (
|
||||
const traceId = req.header("x-trace-id") ?? undefined;
|
||||
const userId = authContext.userId;
|
||||
|
||||
const actorKey = toActorKey(userId);
|
||||
const projectKey = toProjectKey(projectId);
|
||||
const actorKey = authContext.actorKey;
|
||||
const projectKey = authContext.projectKey;
|
||||
const sourceSessionId = parsed.data.session_id?.trim();
|
||||
const sourceSessionRecord = sourceSessionId
|
||||
? await sessionMetadataStore.get(
|
||||
|
||||
@@ -197,8 +197,8 @@ export const registerChatStreamRoute = (
|
||||
const projectId = authContext.projectId;
|
||||
const traceId = req.header("x-trace-id") ?? undefined;
|
||||
const userId = authContext.userId;
|
||||
const actorKey = toActorKey(userId);
|
||||
const projectKey = toProjectKey(projectId);
|
||||
const actorKey = authContext.actorKey;
|
||||
const projectKey = authContext.projectKey;
|
||||
const requestUiMessages = toUiMessages(parsed.data.messages);
|
||||
const promptText =
|
||||
parsed.data.message?.trim() || readLatestUserText(requestUiMessages);
|
||||
@@ -219,6 +219,10 @@ export const registerChatStreamRoute = (
|
||||
requestedSessionId,
|
||||
)
|
||||
: null;
|
||||
if (requestedSessionId && !existingSessionRecord) {
|
||||
res.status(404).json({ message: "session not found" });
|
||||
return;
|
||||
}
|
||||
const hadExistingRuntimeSession = Boolean(existingSessionRecord);
|
||||
|
||||
const { binding, requestContext, created } = await sessionBridge.resolve({
|
||||
@@ -227,6 +231,8 @@ export const registerChatStreamRoute = (
|
||||
projectId,
|
||||
traceId,
|
||||
userId,
|
||||
actorKey,
|
||||
projectKey,
|
||||
});
|
||||
setRuntimeSessionContext({
|
||||
...(getRuntimeSessionContext(binding.sessionId)!),
|
||||
|
||||
Reference in New Issue
Block a user