diff --git a/packages/core/filestore/file-service.ts b/packages/core/filestore/file-service.ts index 96e287ed..ad6dccd3 100644 --- a/packages/core/filestore/file-service.ts +++ b/packages/core/filestore/file-service.ts @@ -70,3 +70,10 @@ export function getFileService(): FileService { } return _fileService; } + +/** + * Check if cloud file storage is available (e.g., S3 bucket configured) + */ +export function isFileStorageAvailable(): boolean { + return !!process.env.AWS_BUCKET_NAME; +} diff --git a/packages/core/runs/run-lifecycle.ts b/packages/core/runs/run-lifecycle.ts index d0dabe56..f1796597 100644 --- a/packages/core/runs/run-lifecycle.ts +++ b/packages/core/runs/run-lifecycle.ts @@ -3,9 +3,17 @@ * Context is passed through (not stored) for stateless operation. */ -import type { RequestOptions, RequestSource, Tool, ToolStepResult } from "@superglue/shared"; -import { RunStatus, RequestSource as RSrc } from "@superglue/shared"; +import type { + RequestOptions, + RequestSource, + StoredRunResults, + Tool, + ToolStepResult, +} from "@superglue/shared"; +import { RunStatus, RequestSource as RSrc, sampleResultObject } from "@superglue/shared"; import type { DataStore } from "../datastore/types.js"; +import { generateRunResultsUri, getRunResultsService } from "../ee/run-results-service.js"; +import { isFileStorageAvailable } from "../filestore/file-service.js"; import { NotificationService } from "../notifications/index.js"; import { logMessage } from "../utils/logs.js"; @@ -116,6 +124,17 @@ export class RunLifecycleManager { }, }); + // Fire-and-forget: Store run results to S3 if enabled + this.maybeStoreRunResults({ + runId: context.runId, + success: result.success, + data: result.data ?? null, + stepResults: result.stepResults ?? [], + toolPayload: result.payload ?? {}, + error: result.error, + storedAt: new Date(), + }); + // Send notification for failed runs (fire-and-forget) if (!result.success) { this.sendFailureNotification(context, result, completedAt); @@ -243,4 +262,52 @@ export class RunLifecycleManager { }) .catch((err) => logMessage("error", `Notification failed: ${err}`, this.metadata)); } + + /** + * Check if run results storage is enabled for this org (EE feature) + * Returns true if file storage is available AND org has the feature enabled + */ + private async isRunResultsStorageEnabled(): Promise { + if (!isFileStorageAvailable()) { + return false; + } + const orgSettings = await this.datastore.getOrgSettings({ orgId: this.orgId }); + return !!orgSettings?.preferences?.storeRunResults; + } + + /** + * Fire-and-forget: Check org settings and store run results to S3 if enabled + * Also updates the run with the storage URI in the database + */ + private maybeStoreRunResults(results: StoredRunResults): void { + setImmediate(async () => { + try { + const enabled = await this.isRunResultsStorageEnabled(); + if (!enabled) return; + + const storageUri = generateRunResultsUri(results.runId, this.orgId); + if (!storageUri) return; + + // Update run with storage URI + await this.datastore.updateRun({ + id: results.runId, + orgId: this.orgId, + updates: { + resultStorageUri: storageUri, + }, + }); + + // Upload to S3 with full (non-truncated) payload and result + await getRunResultsService().storeResults(storageUri, results, { orgId: this.orgId }); + + logMessage("debug", `Stored run results to S3: ${storageUri}`, this.metadata); + } catch (err) { + logMessage( + "warn", + `Failed to store run results for ${results.runId}: ${err}`, + this.metadata, + ); + } + }); + } } diff --git a/packages/shared/types.ts b/packages/shared/types.ts index c8084aa6..c5bdf515 100644 --- a/packages/shared/types.ts +++ b/packages/shared/types.ts @@ -406,6 +406,18 @@ export interface Run { requestSource?: RequestSource; traceId?: string; metadata: RunMetadata; + resultStorageUri?: string; // FileService URI where full results are stored (EE feature) +} + +// Stored run results in FileService (EE feature) +export interface StoredRunResults { + runId: string; + success: boolean; + data: any; + stepResults: ToolStepResult[]; + toolPayload: Record; + error?: string; + storedAt: Date; } export interface ApiCallArgs { diff --git a/packages/web/next-env.d.ts b/packages/web/next-env.d.ts index c4b7818f..9edff1c7 100644 --- a/packages/web/next-env.d.ts +++ b/packages/web/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.