Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/server/plugins/auth-check.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ async function authCheck(
);
request.session.roleResolvedAt = Date.now();
} catch (err) {
console.error("[AuthCheck] Role resolution failed:", (err as Error).message);
request.log.error({ err }, "ldap_role_resolution_failed");
request.session.role = "denied";
}
}
Expand Down
8 changes: 7 additions & 1 deletion src/server/plugins/trace.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ const tracePlugin: FastifyPluginAsync = async function tracePlugin(fastify) {
request.headers["x-trace-id"] = traceId;

request.traceId = traceId;
request.requestId = randomUUID();
request.requestId =
(request.headers["x-request-id"] as string) || randomUUID();
const childBindings = {
request_id: request.requestId,
trace_id: request.traceId,
};
request.log = request.log.child(childBindings);
});

fastify.addHook("onSend", async (request, reply, payload) => {
Expand Down
57 changes: 31 additions & 26 deletions src/server/router/proxy.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,13 +321,13 @@ async function ensureFreshTokens(

const refreshed = await sso.getNewAccessTokenUsingRefreshToken(token, {});
session.token = refreshed.token;
fastify.log.info('Access token refreshed before agent call');
request.log.info('Access token refreshed before agent call');
return {
accessToken: refreshed.token.access_token,
refreshToken: refreshed.token.refresh_token ?? null,
};
} catch (err) {
fastify.log.error({ err }, 'Token refresh failed');
request.log.error({ err }, 'Token refresh failed');
return { accessToken: null, refreshToken: null, refreshFailed: true };
}
}
Expand Down Expand Up @@ -397,6 +397,7 @@ async function proxyRoutes(fastify: FastifyInstance) {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'X-Trace-ID': traceId,
'X-Request-ID': request.requestId,
};
if (accessToken) {
headers['Authorization'] = `Bearer ${accessToken}`;
Expand Down Expand Up @@ -426,27 +427,27 @@ async function proxyRoutes(fastify: FastifyInstance) {
});
};

fastify.log.info({ traceId, thread_id }, 'Creating thread');
request.log.info({ traceId, thread_id }, 'Creating thread');
let threadResp = await createThread();

if (threadResp.status === 401) {
fastify.log.warn({ traceId }, 'Thread creation got 401, retrying once');
request.log.warn({ traceId }, 'Thread creation got 401, retrying once');
threadResp = await createThread();
}

if (!threadResp.ok) {
const body = await threadResp.text();
fastify.log.error(
request.log.error(
{ traceId, status: threadResp.status, body },
'Thread creation failed',
);
return reply.status(threadResp.status).send({ error: 'Thread creation failed' });
}
fastify.log.info({ traceId }, 'Thread ready');
request.log.info({ traceId }, 'Thread ready');

// ── 2. Start a streaming run on that thread ──
const runUrl = `${getAgentHost()}/threads/${thread_id}/runs/stream`;
fastify.log.info({ traceId, runUrl }, 'Starting streaming run');
request.log.info({ traceId, runUrl }, 'Starting streaming run');

const runBody: Record<string, unknown> = {
assistant_id: 'agent',
Expand All @@ -473,7 +474,7 @@ async function proxyRoutes(fastify: FastifyInstance) {

if (!runResp.ok) {
const body = await runResp.text();
fastify.log.error(
request.log.error(
{ traceId, status: runResp.status, body },
'Agent run/stream failed',
);
Expand All @@ -490,6 +491,7 @@ async function proxyRoutes(fastify: FastifyInstance) {
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'X-Trace-ID': traceId,
'X-Request-ID': request.requestId,
'X-Accel-Buffering': 'no',
});
reply.raw.flushHeaders();
Expand Down Expand Up @@ -527,7 +529,7 @@ async function proxyRoutes(fastify: FastifyInstance) {
try {
({ done, value } = await reader.read());
} catch {
fastify.log.warn({ traceId }, 'Agent stream connection lost — agent may have been killed');
request.log.warn({ traceId }, 'Agent stream connection lost — agent may have been killed');
break;
}
if (done) {
Expand Down Expand Up @@ -557,7 +559,7 @@ async function proxyRoutes(fastify: FastifyInstance) {
if (sseType === 'metadata') {
try {
const parsed = JSON.parse(sseData) as Record<string, unknown>;
fastify.log.info({ traceId, sseType, parsedKeys: Object.keys(parsed) }, 'Raw metadata SSE from agent');
request.log.info({ traceId, sseType, parsedKeys: Object.keys(parsed) }, 'Raw metadata SSE from agent');

if (
typeof parsed === 'object' &&
Expand All @@ -579,7 +581,7 @@ async function proxyRoutes(fastify: FastifyInstance) {
chunkId++;
}
} catch {
fastify.log.debug({ traceId, sseType, sseData }, 'Unparseable metadata SSE');
request.log.debug({ traceId, sseType, sseData }, 'Unparseable metadata SSE');
}
continue;
}
Expand All @@ -598,7 +600,7 @@ async function proxyRoutes(fastify: FastifyInstance) {
reply.raw.write(`data: ${JSON.stringify(interruptChunk)}\n\n`);
chunkId++;
interruptEmittedFromStream = true;
fastify.log.info({ traceId }, 'Emitted HITL interrupt from updates stream');
request.log.info({ traceId }, 'Emitted HITL interrupt from updates stream');
}
}

Expand Down Expand Up @@ -682,7 +684,7 @@ async function proxyRoutes(fastify: FastifyInstance) {
chunkId++;
}
} catch {
fastify.log.debug({ traceId, sseType, sseData }, 'Unparseable SSE data');
request.log.debug({ traceId, sseType, sseData }, 'Unparseable SSE data');
}
}
}
Expand Down Expand Up @@ -729,15 +731,15 @@ async function proxyRoutes(fastify: FastifyInstance) {
};
reply.raw.write(`data: ${JSON.stringify(interruptChunk)}\n\n`);
chunkId++;
fastify.log.info({ traceId }, 'Emitted HITL interrupt from thread state (post-stream fallback)');
request.log.info({ traceId }, 'Emitted HITL interrupt from thread state (post-stream fallback)');
}
}
} catch (err) {
fastify.log.warn({ traceId, err }, 'Failed to check thread state for interrupts');
request.log.warn({ traceId, err }, 'Failed to check thread state for interrupts');
}

if (!streamEndedNormally) {
fastify.log.warn({ traceId }, 'Agent stream ended abnormally — signalling error to frontend');
request.log.warn({ traceId }, 'Agent stream ended abnormally — signalling error to frontend');
const errChunk = {
type: 'error',
message: 'Agent connection lost during streaming. Recovery in progress.',
Expand All @@ -747,20 +749,20 @@ async function proxyRoutes(fastify: FastifyInstance) {
chunkId++;
}

fastify.log.info({ traceId, chunkId, streamEndedNormally }, 'Stream complete');
request.log.info({ traceId, chunkId, streamEndedNormally }, 'Stream complete');
invalidateThreadStateCache(thread_id);
reply.raw.end('data: [DONE]\n\n');
}
} catch (error: unknown) {
const errName = (error as Error).name;
if (errName === 'AbortError') {
fastify.log.info({ traceId }, 'Client disconnected, stream aborted');
request.log.info({ traceId }, 'Client disconnected, stream aborted');
return;
}
if (errName === 'TimeoutError') {
fastify.log.warn({ traceId }, 'Agent stream timed out');
request.log.warn({ traceId }, 'Agent stream timed out');
} else {
fastify.log.error({ traceId, err: error }, 'Proxy stream error');
request.log.error({ traceId, err: error }, 'Proxy stream error');
}
if (reply.raw.headersSent) {
reply.raw.end();
Expand Down Expand Up @@ -796,6 +798,7 @@ async function proxyRoutes(fastify: FastifyInstance) {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'X-Trace-ID': traceId,
'X-Request-ID': request.requestId,
};
if (accessToken) {
headers['Authorization'] = `Bearer ${accessToken}`;
Expand All @@ -807,7 +810,7 @@ async function proxyRoutes(fastify: FastifyInstance) {
const method = options?.method ?? request.method;
const queryString = buildForwardedQueryString(request.query as Record<string, unknown>);
const agentUrl = `${getAgentHost()}/${agentPath.replace(/^\//, '')}${queryString}`;
fastify.log.info({ traceId, method, agentUrl }, 'Proxying request to agent');
request.log.info({ traceId, method, agentUrl }, 'Proxying request to agent');

try {
const fetchOptions: RequestInit = {
Expand All @@ -831,7 +834,7 @@ async function proxyRoutes(fastify: FastifyInstance) {
}
return reply.send(await agentResponse.text());
} catch (error) {
fastify.log.error({ traceId, err: error }, 'Proxy error');
request.log.error({ traceId, err: error }, 'Proxy error');
return reply.status(502).send({ error: 'Failed to connect to agent service' });
}
}
Expand Down Expand Up @@ -937,6 +940,7 @@ async function proxyRoutes(fastify: FastifyInstance) {

const headers: Record<string, string> = {
'X-Trace-ID': traceId,
'X-Request-ID': request.requestId,
};
const hasBody = request.body !== undefined && request.body !== null;
if (request.method !== 'GET' && (request.method !== 'DELETE' || hasBody)) {
Expand All @@ -955,14 +959,14 @@ async function proxyRoutes(fastify: FastifyInstance) {
try {
const queryString = buildForwardedQueryString(request.query as Record<string, unknown>);
const agentUrl = `${getAgentHost()}/${path}${queryString}`;
fastify.log.info({ traceId, method: request.method, agentUrl }, 'Proxying request to agent');
request.log.info({ traceId, method: request.method, agentUrl }, 'Proxying request to agent');

// Check BFF cache for GET /threads/{id}/state
const threadStateMatch = request.method === 'GET' && path.match(/^threads\/([^/]+)\/state$/);
if (threadStateMatch) {
const cached = getCachedThreadState(threadStateMatch[1]);
if (cached) {
fastify.log.info({ traceId }, 'Thread state cache HIT');
request.log.info({ traceId }, 'Thread state cache HIT');
reply.header('X-Trace-ID', traceId);
reply.header('Content-Type', 'application/json');
reply.header('X-Cache', 'HIT');
Expand Down Expand Up @@ -1000,7 +1004,7 @@ async function proxyRoutes(fastify: FastifyInstance) {

return reply.send(responseBody);
} catch (error) {
fastify.log.error({ traceId, err: error }, 'Proxy error');
request.log.error({ traceId, err: error }, 'Proxy error');
return reply.status(502).send({ error: 'Failed to connect to agent service' });
}
}
Expand All @@ -1018,6 +1022,7 @@ async function proxyRoutes(fastify: FastifyInstance) {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'X-Trace-ID': traceId,
'X-Request-ID': request.requestId,
};

if (accessToken) {
Expand All @@ -1038,7 +1043,7 @@ async function proxyRoutes(fastify: FastifyInstance) {
const responseBody = await agentResponse.text();
return reply.send(responseBody);
} catch (error) {
fastify.log.error({ traceId, err: error }, 'Feedback proxy error');
request.log.error({ traceId, err: error }, 'Feedback proxy error');
return reply.status(502).send({ error: 'Failed to send feedback' });
}
});
Expand Down
Loading