chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
import { type Span } from "@opentelemetry/api";
|
||||
import { type PrismaClientOrTransaction } from "@trigger.dev/database";
|
||||
import { env } from "~/env.server";
|
||||
import { findDisplayableEnvironment } from "~/models/runtimeEnvironment.server";
|
||||
import { chatSnapshotStorageKey } from "~/services/realtime/chatSnapshot.server";
|
||||
import { resolveSessionByIdOrExternalId } from "~/services/realtime/sessions.server";
|
||||
import { LEGACY_PLAYGROUND_TAG } from "~/services/sessionsRepository/sessionsRepository.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { generatePresignedUrl } from "~/v3/objectStore.server";
|
||||
import { runStore } from "~/v3/runStore.server";
|
||||
import { ServiceValidationError } from "~/v3/services/baseService.server";
|
||||
import { startActiveSpan } from "~/v3/tracer.server";
|
||||
|
||||
export type SessionDetail = NonNullable<Awaited<ReturnType<SessionPresenter["call"]>>>;
|
||||
|
||||
export class SessionPresenter {
|
||||
constructor(private readonly replica: PrismaClientOrTransaction) {}
|
||||
|
||||
public async call(args: {
|
||||
userId: string;
|
||||
environmentId: string;
|
||||
sessionParam: string;
|
||||
projectExternalRef: string;
|
||||
environmentSlug: string;
|
||||
}) {
|
||||
return startActiveSpan("SessionPresenter.call", (span) => this.#call(args, span), {
|
||||
attributes: {
|
||||
environmentId: args.environmentId,
|
||||
sessionParam: args.sessionParam,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async #call(
|
||||
{
|
||||
userId,
|
||||
environmentId,
|
||||
sessionParam,
|
||||
projectExternalRef,
|
||||
environmentSlug,
|
||||
}: {
|
||||
userId: string;
|
||||
environmentId: string;
|
||||
sessionParam: string;
|
||||
projectExternalRef: string;
|
||||
environmentSlug: string;
|
||||
},
|
||||
rootSpan: Span
|
||||
) {
|
||||
const session = await startActiveSpan("SessionPresenter.resolveSession", () =>
|
||||
resolveSessionByIdOrExternalId(this.replica, environmentId, sessionParam)
|
||||
);
|
||||
if (!session) {
|
||||
rootSpan.setAttribute("session.found", false);
|
||||
return null;
|
||||
}
|
||||
rootSpan.setAttribute("session.found", true);
|
||||
rootSpan.setAttribute("session.id", session.id);
|
||||
|
||||
const displayableEnvironment = await startActiveSpan(
|
||||
"SessionPresenter.findDisplayableEnvironment",
|
||||
() => findDisplayableEnvironment(environmentId, userId)
|
||||
);
|
||||
if (!displayableEnvironment) {
|
||||
throw new ServiceValidationError("No environment found");
|
||||
}
|
||||
|
||||
// Run history is append-only; latest first matches the runs list.
|
||||
// 50 covers the vast majority of sessions; longer histories link out
|
||||
// to the runs page via tag filter.
|
||||
const sessionRuns = await startActiveSpan("SessionPresenter.findSessionRuns", async (span) => {
|
||||
const rows = await this.replica.sessionRun.findMany({
|
||||
where: { sessionId: session.id },
|
||||
orderBy: { triggeredAt: "desc" },
|
||||
take: 50,
|
||||
select: {
|
||||
id: true,
|
||||
runId: true,
|
||||
reason: true,
|
||||
triggeredAt: true,
|
||||
},
|
||||
});
|
||||
span.setAttribute("sessionRuns.count", rows.length);
|
||||
return rows;
|
||||
});
|
||||
|
||||
const runIds = sessionRuns.map((r) => r.runId);
|
||||
const runs = await startActiveSpan("SessionPresenter.findRuns", async (span) => {
|
||||
span.setAttribute("runIds.count", runIds.length);
|
||||
return runIds.length > 0
|
||||
? runStore.findRuns(
|
||||
{
|
||||
where: { id: { in: runIds } },
|
||||
select: { id: true, friendlyId: true, status: true },
|
||||
},
|
||||
this.replica
|
||||
)
|
||||
: [];
|
||||
});
|
||||
const runsById = new Map(runs.map((r) => [r.id, r] as const));
|
||||
|
||||
const currentRun = session.currentRunId
|
||||
? (runsById.get(session.currentRunId) ??
|
||||
(await startActiveSpan("SessionPresenter.findCurrentRunFallback", () =>
|
||||
runStore.findRun(
|
||||
{ id: session.currentRunId! },
|
||||
{
|
||||
select: { id: true, friendlyId: true, status: true },
|
||||
},
|
||||
this.replica
|
||||
)
|
||||
)))
|
||||
: null;
|
||||
|
||||
// The dashboard SSE route is cookie-authed, so `publicAccessToken` is
|
||||
// unused — kept here to match the existing `AgentViewAuth` shape.
|
||||
const addressingKey = session.externalId ?? session.friendlyId;
|
||||
|
||||
// Presign a GET URL for the agent's S3 snapshot blob. The browser
|
||||
// fetches it directly, parses + validates, and seeds the
|
||||
// TriggerChatTransport with the full history + lastEventId before
|
||||
// opening the SSE. Presign succeeds regardless of whether the blob
|
||||
// exists; the frontend handles 404 gracefully.
|
||||
//
|
||||
// Snapshots are only written when no `hydrateMessages` hook is
|
||||
// registered — sessions that use `hydrateMessages` will 404 here
|
||||
// and the dashboard falls back to seq=0 SSE (which, post-trim,
|
||||
// shows only the most recent turn — accepted, those customers
|
||||
// have their own DB-backed dashboards).
|
||||
// Resolve the snapshot key via the SAME helper the SDK write + boot read
|
||||
// use (`chatSnapshotStorageKey`), so the dashboard GET hits the exact
|
||||
// object (and object store) the snapshot was written to. Recomputing a
|
||||
// bare key here was the bug: an unqualified key reads the base store while
|
||||
// the write applied OBJECT_STORE_DEFAULT_PROTOCOL, so they could diverge.
|
||||
let snapshotPresignedUrl: string | undefined;
|
||||
try {
|
||||
const signed = await startActiveSpan("SessionPresenter.presignSnapshot", async () =>
|
||||
generatePresignedUrl(
|
||||
projectExternalRef,
|
||||
environmentSlug,
|
||||
chatSnapshotStorageKey(session),
|
||||
"GET"
|
||||
)
|
||||
);
|
||||
if (signed.success) {
|
||||
snapshotPresignedUrl = signed.url;
|
||||
} else {
|
||||
logger.warn("SessionPresenter: snapshot presign failed", {
|
||||
sessionId: session.id,
|
||||
error: signed.error,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn("SessionPresenter: snapshot presign threw", {
|
||||
sessionId: session.id,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
id: session.id,
|
||||
friendlyId: session.friendlyId,
|
||||
externalId: session.externalId,
|
||||
type: session.type,
|
||||
taskIdentifier: session.taskIdentifier,
|
||||
isTest: session.isTest,
|
||||
// Hide the legacy "playground" tag (pre-isTest sessions) from display.
|
||||
tags: session.tags
|
||||
? [...session.tags]
|
||||
.filter((t) => t !== LEGACY_PLAYGROUND_TAG)
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
: [],
|
||||
metadata: session.metadata,
|
||||
triggerConfig: session.triggerConfig,
|
||||
streamBasinName: session.streamBasinName,
|
||||
closedAt: session.closedAt ? session.closedAt.toISOString() : undefined,
|
||||
closedReason: session.closedReason ?? undefined,
|
||||
expiresAt: session.expiresAt ? session.expiresAt.toISOString() : undefined,
|
||||
createdAt: session.createdAt.toISOString(),
|
||||
updatedAt: session.updatedAt.toISOString(),
|
||||
environment: displayableEnvironment,
|
||||
currentRun: currentRun
|
||||
? { friendlyId: currentRun.friendlyId, status: currentRun.status }
|
||||
: null,
|
||||
runs: sessionRuns.map((r) => {
|
||||
const run = runsById.get(r.runId);
|
||||
return {
|
||||
id: r.id,
|
||||
reason: r.reason,
|
||||
triggeredAt: r.triggeredAt.toISOString(),
|
||||
run: run ? { friendlyId: run.friendlyId, status: run.status } : null,
|
||||
};
|
||||
}),
|
||||
agentView: {
|
||||
publicAccessToken: "",
|
||||
apiOrigin: env.API_ORIGIN || env.LOGIN_ORIGIN,
|
||||
sessionId: addressingKey,
|
||||
initialMessages: [],
|
||||
snapshotPresignedUrl,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user