From 40007057010aef7e006ca1a23a2ad27575c01160 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Tue, 25 Nov 2025 12:43:22 -0500 Subject: [PATCH] core(enterprise): use aws4fetch for smaller bundle size on edge runtimes --- bun.lock | 1 + packages/enterprise/package.json | 1 + packages/enterprise/src/core/storage.ts | 95 ++++++++++--------------- 3 files changed, 40 insertions(+), 57 deletions(-) diff --git a/bun.lock b/bun.lock index b7949547b..171e06e58 100644 --- a/bun.lock +++ b/bun.lock @@ -171,6 +171,7 @@ "@solidjs/meta": "catalog:", "@solidjs/router": "catalog:", "@solidjs/start": "catalog:", + "aws4fetch": "^1.0.20", "hono": "catalog:", "hono-openapi": "catalog:", "luxon": "catalog:", diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 6f94a4b2c..c562358d1 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -13,6 +13,7 @@ "dependencies": { "@opencode-ai/util": "workspace:*", "@opencode-ai/ui": "workspace:*", + "aws4fetch": "^1.0.20", "@pierre/precision-diffs": "catalog:", "@solidjs/router": "catalog:", "@solidjs/start": "catalog:", diff --git a/packages/enterprise/src/core/storage.ts b/packages/enterprise/src/core/storage.ts index db03e2160..ee711458b 100644 --- a/packages/enterprise/src/core/storage.ts +++ b/packages/enterprise/src/core/storage.ts @@ -1,10 +1,4 @@ -import { - S3Client, - PutObjectCommand, - GetObjectCommand, - DeleteObjectCommand, - ListObjectsV2Command, -} from "@aws-sdk/client-s3" +import { AwsClient } from "aws4fetch" import { lazy } from "@opencode-ai/util/lazy" export namespace Storage { @@ -15,81 +9,68 @@ export namespace Storage { list(prefix: string): Promise } - function createAdapter(client: S3Client, bucket: string): Adapter { + function createAdapter(client: AwsClient, endpoint: string, bucket: string): Adapter { + const base = `${endpoint}/${bucket}` return { async read(path: string): Promise { - try { - const command = new GetObjectCommand({ - Bucket: bucket, - Key: path, - }) - const response = await client.send(command) - if (!response.Body) return undefined - return response.Body.transformToString() - } catch (e: any) { - if (e.name === "NoSuchKey") return undefined - throw e - } + const response = await client.fetch(`${base}/${path}`) + if (response.status === 404) return undefined + if (!response.ok) throw new Error(`Failed to read ${path}: ${response.status}`) + return response.text() }, async write(path: string, value: string): Promise { - const command = new PutObjectCommand({ - Bucket: bucket, - Key: path, - Body: value, - ContentType: "application/json", + const response = await client.fetch(`${base}/${path}`, { + method: "PUT", + body: value, + headers: { + "Content-Type": "application/json", + }, }) - await client.send(command) + if (!response.ok) throw new Error(`Failed to write ${path}: ${response.status}`) }, async remove(path: string): Promise { - const command = new DeleteObjectCommand({ - Bucket: bucket, - Key: path, + const response = await client.fetch(`${base}/${path}`, { + method: "DELETE", }) - await client.send(command) + if (!response.ok) throw new Error(`Failed to remove ${path}: ${response.status}`) }, async list(prefix: string): Promise { - const command = new ListObjectsV2Command({ - Bucket: bucket, - Prefix: prefix, - }) - const response = await client.send(command) - return response.Contents?.map((c) => c.Key!) || [] + const params = new URLSearchParams({ "list-type": "2", prefix }) + const response = await client.fetch(`${base}?${params}`) + if (!response.ok) throw new Error(`Failed to list ${prefix}: ${response.status}`) + const xml = await response.text() + const keys: string[] = [] + const regex = /([^<]+)<\/Key>/g + let match + while ((match = regex.exec(xml)) !== null) { + keys.push(match[1]) + } + return keys }, } } function s3(): Adapter { const bucket = process.env.OPENCODE_STORAGE_BUCKET! - const client = new S3Client({ - region: process.env.OPENCODE_STORAGE_REGION, - credentials: process.env.OPENCODE_STORAGE_ACCESS_KEY_ID - ? { - accessKeyId: process.env.OPENCODE_STORAGE_ACCESS_KEY_ID!, - secretAccessKey: process.env.OPENCODE_STORAGE_SECRET_ACCESS_KEY!, - } - : undefined, + const region = process.env.OPENCODE_STORAGE_REGION || "us-east-1" + const client = new AwsClient({ + region, + accessKeyId: process.env.OPENCODE_STORAGE_ACCESS_KEY_ID!, + secretAccessKey: process.env.OPENCODE_STORAGE_SECRET_ACCESS_KEY!, }) - return createAdapter(client, bucket) + return createAdapter(client, `https://s3.${region}.amazonaws.com`, bucket) } function r2() { const accountId = process.env.OPENCODE_STORAGE_ACCOUNT_ID! - const accessKeyId = process.env.OPENCODE_STORAGE_ACCESS_KEY_ID! - const secretAccessKey = process.env.OPENCODE_STORAGE_SECRET_ACCESS_KEY! - const bucket = process.env.OPENCODE_STORAGE_BUCKET! - - const client = new S3Client({ - region: "auto", - endpoint: `https://${accountId}.r2.cloudflarestorage.com`, - credentials: { - accessKeyId, - secretAccessKey, - }, + const client = new AwsClient({ + accessKeyId: process.env.OPENCODE_STORAGE_ACCESS_KEY_ID!, + secretAccessKey: process.env.OPENCODE_STORAGE_SECRET_ACCESS_KEY!, }) - return createAdapter(client, bucket) + return createAdapter(client, `https://${accountId}.r2.cloudflarestorage.com`, process.env.OPENCODE_STORAGE_BUCKET!) } const adapter = lazy(() => {