Skip to content

Commit 9ac1e33

Browse files
committed
New doc setup
1 parent 5a8e66b commit 9ac1e33

37 files changed

Lines changed: 1399 additions & 1309 deletions

File tree

apps/sim/app/api/files/serve/[...path]/route.ts

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import type { NextRequest } from 'next/server'
55
import { NextResponse } from 'next/server'
66
import { fileServeParamsSchema, fileServeQuerySchema } from '@/lib/api/contracts/storage-transfer'
77
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
8+
import {
9+
DocCompileUserError,
10+
getE2BDocFormat,
11+
loadCompiledDocByExt,
12+
} from '@/lib/copilot/tools/server/files/doc-compile'
13+
import { isE2BDocEnabled } from '@/lib/core/config/feature-flags'
814
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
915
import { runSandboxTask } from '@/lib/execution/sandbox/run-task'
1016
import { CopilotFiles, isUsingCloudStorage } from '@/lib/uploads'
@@ -72,14 +78,55 @@ async function compileDocumentIfNeeded(
7278
if (raw) return { buffer, contentType: getContentType(filename) }
7379

7480
const ext = filename.slice(filename.lastIndexOf('.')).toLowerCase()
81+
const extNoDot = ext.replace(/^\./, '')
7582
const format = COMPILABLE_FORMATS[ext]
76-
if (!format) return { buffer, contentType: getContentType(filename) }
7783

78-
const magicLen = format.magic.length
79-
if (buffer.length >= magicLen && buffer.subarray(0, magicLen).equals(format.magic)) {
84+
// Already a binary file (uploaded or pre-compiled)? Serve as-is.
85+
if (format) {
86+
const magicLen = format.magic.length
87+
if (buffer.length >= magicLen && buffer.subarray(0, magicLen).equals(format.magic)) {
88+
return { buffer, contentType: getContentType(filename) }
89+
}
90+
}
91+
92+
// .xlsx is a ZIP container with no JS compile path. An uploaded/binary xlsx
93+
// must short-circuit here (it isn't in COMPILABLE_FORMATS) — otherwise every
94+
// xlsx open would utf-8-decode the whole binary and do an always-miss S3 GET.
95+
// Only a Python-source xlsx (UTF-8 text, no ZIP magic) falls through.
96+
if (
97+
extNoDot === 'xlsx' &&
98+
buffer.length >= ZIP_MAGIC.length &&
99+
buffer.subarray(0, ZIP_MAGIC.length).equals(ZIP_MAGIC)
100+
) {
80101
return { buffer, contentType: getContentType(filename) }
81102
}
82103

104+
// Generated docs render from a content-addressed compiled binary that is built
105+
// exactly ONCE per edit_content/create (at write time) and stored in S3. Serve
106+
// only LOADS it — it must never compile, or it would re-run E2B on every preview
107+
// fetch, including against the incomplete source mid-generation. A hit returns
108+
// the (possibly partial) committed doc; a miss in the E2B regime means the doc
109+
// is still being generated → 409, and the client polls until the artifact lands.
110+
if (workspaceId && (format || extNoDot === 'xlsx')) {
111+
const source = buffer.toString('utf-8')
112+
// Load the prebuilt artifact directly from S3 (content-addressed). No extra
113+
// in-memory layer here: the store is the source of truth, the client (react
114+
// query) already caches the bytes, and this branch never recomputes.
115+
const stored = await loadCompiledDocByExt(workspaceId, source, extNoDot)
116+
if (stored) {
117+
return { buffer: stored.buffer, contentType: stored.contentType }
118+
}
119+
120+
if (isE2BDocEnabled && getE2BDocFormat(filename)) {
121+
// Artifact not built yet (still generating, or the source didn't compile at
122+
// write time). Signal "not ready" without compiling — handled as 409.
123+
throw new DocCompileUserError('Document is still being generated')
124+
}
125+
}
126+
127+
if (!format) return { buffer, contentType: getContentType(filename) }
128+
129+
// E2B disabled and no stored artifact → compile JS source via isolated-vm.
83130
const code = buffer.toString('utf-8')
84131
const cacheKey = sha256Hex(`${ext}${code}${workspaceId ?? ''}`)
85132
const cached = compiledDocCache.get(cacheKey)
@@ -164,6 +211,17 @@ export const GET = withRouteHandler(
164211

165212
return await handleLocalFile(cloudKey, userId, raw, request.signal)
166213
} catch (error) {
214+
// An in-progress/incomplete doc source fails to compile — this is expected
215+
// mid-generation, not a server fault. Return 409 (not 500) so it isn't an
216+
// alarming error; the client re-fetches once the doc finishes (the serve
217+
// URL is busted on the file's updatedAt).
218+
if (error instanceof DocCompileUserError) {
219+
logger.info('Serve: document still compiling, returning 409', {
220+
message: error.message,
221+
})
222+
return NextResponse.json({ error: 'Document is still being generated' }, { status: 409 })
223+
}
224+
167225
logger.error('Error serving file:', error)
168226

169227
if (error instanceof FileNotFoundError) {

apps/sim/app/api/mcp/copilot/route.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { ensureWorkspaceAccess } from '@/lib/copilot/tools/handlers/access'
3131
import { prepareExecutionContext } from '@/lib/copilot/tools/handlers/context'
3232
import { DIRECT_TOOL_DEFS, SUBAGENT_TOOL_DEFS } from '@/lib/copilot/tools/mcp/definitions'
3333
import { env } from '@/lib/core/config/env'
34+
import { isE2BDocEnabled } from '@/lib/core/config/feature-flags'
3435
import { RateLimiter } from '@/lib/core/rate-limiter'
3536
import { getBaseUrl } from '@/lib/core/utils/urls'
3637
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
@@ -600,6 +601,7 @@ async function handleBuildToolCall(
600601
...(resolvedWorkflowName ? { workflowName: resolvedWorkflowName } : {}),
601602
...(resolvedWorkspaceId ? { workspaceId: resolvedWorkspaceId } : {}),
602603
...(workspaceContext ? { workspaceContext } : {}),
604+
...(isE2BDocEnabled ? { docCompiler: 'python' } : {}),
603605
userId,
604606
model: DEFAULT_COPILOT_MODEL,
605607
mode: 'agent',

apps/sim/app/api/mothership/execute/route.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import { runHeadlessCopilotLifecycle } from '@/lib/copilot/request/lifecycle/headless'
1515
import { requestExplicitStreamAbort } from '@/lib/copilot/request/session/explicit-abort'
1616
import type { StreamEvent } from '@/lib/copilot/request/types'
17+
import { isE2BDocEnabled } from '@/lib/core/config/feature-flags'
1718
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1819
import { buildMothershipToolsForRequest } from '@/lib/mothership/settings/runtime'
1920
import {
@@ -132,6 +133,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
132133
messageId,
133134
isHosted: true,
134135
workspaceContext,
136+
...(isE2BDocEnabled ? { docCompiler: 'python' } : {}),
135137
...(userMetadata ? { userMetadata } : {}),
136138
...(fileAttachments && fileAttachments.length > 0 ? { fileAttachments } : {}),
137139
...(integrationTools.length > 0 ? { integrationTools } : {}),

apps/sim/app/api/workspaces/[id]/_preview/create-preview-route.ts

Lines changed: 0 additions & 120 deletions
This file was deleted.

0 commit comments

Comments
 (0)