diff --git a/bun.lock b/bun.lock
index 743c2cb0..dc231b63 100644
--- a/bun.lock
+++ b/bun.lock
@@ -29,6 +29,7 @@
"version": "0.5.28",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
+ "@opencode/cloud-resource": "workspace:*",
"drizzle-orm": "0.41.0",
"postgres": "3.4.7",
"stripe": "18.0.0",
@@ -58,6 +59,12 @@
"typescript": "catalog:",
},
},
+ "cloud/resource": {
+ "name": "@opencode/cloud-resource",
+ "dependencies": {
+ "@cloudflare/workers-types": "^4.20250830.0",
+ },
+ },
"packages/function": {
"name": "@opencode/function",
"version": "0.5.28",
@@ -652,6 +659,8 @@
"@opencode/cloud-function": ["@opencode/cloud-function@workspace:cloud/function"],
+ "@opencode/cloud-resource": ["@opencode/cloud-resource@workspace:cloud/resource"],
+
"@opencode/function": ["@opencode/function@workspace:packages/function"],
"@opencode/web": ["@opencode/web@workspace:packages/web"],
@@ -3034,6 +3043,8 @@
"@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
+ "@opencode/cloud-resource/@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250830.0", "", {}, "sha512-uAGZFqEBFnCiwIokxMnrrtjIkT8qyGT1LACSScEUyW7nKmtD0Viykp9QZWrIlssyEp/MDB6XsdALF8y6upxpcg=="],
+
"@opentelemetry/instrumentation-grpc/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.27.0", "", {}, "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg=="],
"@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="],
diff --git a/cloud/app/package.json b/cloud/app/package.json
index 3a236e09..7e6eaf4b 100644
--- a/cloud/app/package.json
+++ b/cloud/app/package.json
@@ -3,7 +3,7 @@
"type": "module",
"scripts": {
"dev": "vinxi dev --host 0.0.0.0",
- "dev:remote": "bun sst shell --target=Solid --stage=dev bun dev",
+ "dev:remote": "bun sst shell --target=Console --stage=dev bun dev",
"build": "vinxi build",
"start": "vinxi start",
"version": "0.5.28"
diff --git a/cloud/app/src/global.d.ts b/cloud/app/src/global.d.ts
index a44606d2..dc6f10c2 100644
--- a/cloud/app/src/global.d.ts
+++ b/cloud/app/src/global.d.ts
@@ -1,2 +1 @@
///
-declare module "cloudflare:workers"
diff --git a/cloud/app/src/routes/debug/index.ts b/cloud/app/src/routes/debug/index.ts
new file mode 100644
index 00000000..1753cf59
--- /dev/null
+++ b/cloud/app/src/routes/debug/index.ts
@@ -0,0 +1,9 @@
+import type { APIEvent } from "@solidjs/start/server"
+import { Resource } from "@opencode/cloud-resource"
+import { json } from "@solidjs/router"
+
+export async function GET(evt: APIEvent) {
+ return json({
+ data: Resource.Database.host,
+ })
+}
diff --git a/cloud/app/src/routes/gateway/v1/chat/completions.ts b/cloud/app/src/routes/gateway/v1/chat/completions.ts
index 95c2c268..671b589c 100644
--- a/cloud/app/src/routes/gateway/v1/chat/completions.ts
+++ b/cloud/app/src/routes/gateway/v1/chat/completions.ts
@@ -1,4 +1,4 @@
-import { Resource } from "@opencode/cloud-core/util/resource.js"
+import { Resource } from "@opencode/cloud-resource"
import { Billing } from "@opencode/cloud-core/billing.js"
import type { APIEvent } from "@solidjs/start/server"
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
diff --git a/cloud/app/src/routes/stripe/webhook.ts b/cloud/app/src/routes/stripe/webhook.ts
index 29f0f248..61d14a64 100644
--- a/cloud/app/src/routes/stripe/webhook.ts
+++ b/cloud/app/src/routes/stripe/webhook.ts
@@ -5,7 +5,7 @@ import { BillingTable, PaymentTable } from "@opencode/cloud-core/schema/billing.
import { Identifier } from "@opencode/cloud-core/identifier.js"
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
import { Actor } from "@opencode/cloud-core/actor.js"
-import { Resource } from "@opencode/cloud-core/util/resource.js"
+import { Resource } from "@opencode/cloud-resource"
export async function POST(input: APIEvent) {
const body = await Billing.stripe().webhooks.constructEventAsync(
diff --git a/cloud/app/tsconfig.json b/cloud/app/tsconfig.json
index 7d5871a0..2a235217 100644
--- a/cloud/app/tsconfig.json
+++ b/cloud/app/tsconfig.json
@@ -1,5 +1,9 @@
{
+ "$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
+ "customConditions": [
+ "workers"
+ ],
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
@@ -10,10 +14,14 @@
"allowJs": true,
"strict": true,
"noEmit": true,
- "types": ["vinxi/types/client"],
+ "types": [
+ "vinxi/types/client"
+ ],
"isolatedModules": true,
"paths": {
- "~/*": ["./src/*"]
+ "~/*": [
+ "./src/*"
+ ]
}
}
}
diff --git a/cloud/core/drizzle.config.ts b/cloud/core/drizzle.config.ts
index 82c48783..fdecb6c5 100644
--- a/cloud/core/drizzle.config.ts
+++ b/cloud/core/drizzle.config.ts
@@ -1,5 +1,5 @@
+import { Resource } from "@opencode/cloud-resource"
import { defineConfig } from "drizzle-kit"
-import { Resource } from "./src/util/resource"
export default defineConfig({
out: "./migrations/",
diff --git a/cloud/core/package.json b/cloud/core/package.json
index 0078252d..cf80a061 100644
--- a/cloud/core/package.json
+++ b/cloud/core/package.json
@@ -6,6 +6,7 @@
"type": "module",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
+ "@opencode/cloud-resource": "workspace:*",
"drizzle-orm": "0.41.0",
"postgres": "3.4.7",
"stripe": "18.0.0",
diff --git a/cloud/core/src/billing.ts b/cloud/core/src/billing.ts
index 705999c2..620a7221 100644
--- a/cloud/core/src/billing.ts
+++ b/cloud/core/src/billing.ts
@@ -7,7 +7,7 @@ import { z } from "zod"
import { Identifier } from "./identifier"
import { centsToMicroCents } from "./util/price"
import { User } from "./user"
-import { Resource } from "./util/resource"
+import { Resource } from "@opencode/cloud-resource"
export namespace Billing {
export const stripe = () =>
diff --git a/cloud/core/src/drizzle/index.ts b/cloud/core/src/drizzle/index.ts
index 894f578f..aa5e3658 100644
--- a/cloud/core/src/drizzle/index.ts
+++ b/cloud/core/src/drizzle/index.ts
@@ -1,5 +1,5 @@
import { drizzle } from "drizzle-orm/postgres-js"
-import { Resource } from "../util/resource"
+import { Resource } from "@opencode/cloud-resource"
export * from "drizzle-orm"
import postgres from "postgres"
diff --git a/cloud/core/src/util/env.cloudflare.ts b/cloud/core/src/util/env.cloudflare.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/cloud/function/src/auth.ts b/cloud/function/src/auth.ts
index 79d7805d..17c3fa8e 100644
--- a/cloud/function/src/auth.ts
+++ b/cloud/function/src/auth.ts
@@ -9,7 +9,7 @@ import { CloudflareStorage } from "@openauthjs/openauth/storage/cloudflare"
import { Account } from "@opencode/cloud-core/account.js"
import { Workspace } from "@opencode/cloud-core/workspace.js"
import { Actor } from "@opencode/cloud-core/actor.js"
-import { Resource } from "@opencode/cloud-core/util/resource.js"
+import { Resource } from "@opencode/cloud-resource"
type Env = {
AuthStorage: KVNamespace
diff --git a/cloud/function/src/gateway.ts b/cloud/function/src/gateway.ts
index aed02b4d..ffbcc1b8 100644
--- a/cloud/function/src/gateway.ts
+++ b/cloud/function/src/gateway.ts
@@ -1,5 +1,4 @@
import { Hono, MiddlewareHandler } from "hono"
-import { Resource } from "@opencode/cloud-core/util/resource.js"
import { type ProviderMetadata, type LanguageModelUsage } from "ai"
import { createAnthropic } from "@ai-sdk/anthropic"
import { createOpenAI } from "@ai-sdk/openai"
@@ -10,6 +9,7 @@ import { Actor } from "@opencode/cloud-core/actor.js"
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
import { Billing } from "@opencode/cloud-core/billing.js"
+import { Resource } from "@opencode/cloud-resource"
type Env = {}
diff --git a/cloud/resource/bun.lock b/cloud/resource/bun.lock
new file mode 100644
index 00000000..56bdf44c
--- /dev/null
+++ b/cloud/resource/bun.lock
@@ -0,0 +1,13 @@
+{
+ "lockfileVersion": 1,
+ "workspaces": {
+ "": {
+ "dependencies": {
+ "@cloudflare/workers-types": "^4.20250830.0",
+ },
+ },
+ },
+ "packages": {
+ "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250830.0", "", {}, "sha512-uAGZFqEBFnCiwIokxMnrrtjIkT8qyGT1LACSScEUyW7nKmtD0Viykp9QZWrIlssyEp/MDB6XsdALF8y6upxpcg=="],
+ }
+}
diff --git a/cloud/resource/package.json b/cloud/resource/package.json
new file mode 100644
index 00000000..fd9fa6f3
--- /dev/null
+++ b/cloud/resource/package.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "https://json.schemastore.org/package.json",
+ "name": "@opencode/cloud-resource",
+ "dependencies": {
+ "@cloudflare/workers-types": "^4.20250830.0"
+ },
+ "exports": {
+ ".": {
+ "production": {
+ "import": "./resource.cloudflare.ts"
+ },
+ "import": "./resource.node.ts"
+ }
+ }
+}
diff --git a/cloud/core/src/util/resource.ts b/cloud/resource/resource.cloudflare.ts
similarity index 61%
rename from cloud/core/src/util/resource.ts
rename to cloud/resource/resource.cloudflare.ts
index 1543145d..ad3fbe2a 100644
--- a/cloud/core/src/util/resource.ts
+++ b/cloud/resource/resource.cloudflare.ts
@@ -1,14 +1,15 @@
-import { env } from "cloudflare:workers";
+import { env } from "cloudflare:workers"
export const Resource = new Proxy(
{},
{
get(_target, prop: string) {
if (prop in env) {
- const value = env[prop];
- return typeof value === "string" ? JSON.parse(value) : value;
+ // @ts-expect-error
+ const value = env[prop]
+ return typeof value === "string" ? JSON.parse(value) : value
}
- throw new Error(`"${prop}" is not linked in your sst.config.ts`);
+ throw new Error(`"${prop}" is not linked in your sst.config.ts (cloudflare)`)
},
- }
-) as Record;
+ },
+) as Record
diff --git a/cloud/resource/resource.node.ts b/cloud/resource/resource.node.ts
new file mode 100644
index 00000000..a1310219
--- /dev/null
+++ b/cloud/resource/resource.node.ts
@@ -0,0 +1,2 @@
+console.log(process.env)
+export { Resource } from "sst"
diff --git a/cloud/resource/sst-env.d.ts b/cloud/resource/sst-env.d.ts
new file mode 100644
index 00000000..9c4f5cc0
--- /dev/null
+++ b/cloud/resource/sst-env.d.ts
@@ -0,0 +1,92 @@
+/* This file is auto-generated by SST. Do not edit. */
+/* tslint:disable */
+/* eslint-disable */
+/* deno-fmt-ignore-file */
+
+import "sst"
+declare module "sst" {
+ export interface Resource {
+ "ANTHROPIC_API_KEY": {
+ "type": "sst.sst.Secret"
+ "value": string
+ }
+ "AUTH_API_URL": {
+ "type": "sst.sst.Linkable"
+ "value": string
+ }
+ "Console": {
+ "type": "sst.cloudflare.SolidStart"
+ "url": string
+ }
+ "DATABASE_PASSWORD": {
+ "type": "sst.sst.Secret"
+ "value": string
+ }
+ "DATABASE_USERNAME": {
+ "type": "sst.sst.Secret"
+ "value": string
+ }
+ "Database": {
+ "database": string
+ "host": string
+ "password": string
+ "port": number
+ "type": "sst.sst.Linkable"
+ "username": string
+ }
+ "GITHUB_APP_ID": {
+ "type": "sst.sst.Secret"
+ "value": string
+ }
+ "GITHUB_APP_PRIVATE_KEY": {
+ "type": "sst.sst.Secret"
+ "value": string
+ }
+ "GITHUB_CLIENT_ID_CONSOLE": {
+ "type": "sst.sst.Secret"
+ "value": string
+ }
+ "GITHUB_CLIENT_SECRET_CONSOLE": {
+ "type": "sst.sst.Secret"
+ "value": string
+ }
+ "GOOGLE_CLIENT_ID": {
+ "type": "sst.sst.Secret"
+ "value": string
+ }
+ "OPENAI_API_KEY": {
+ "type": "sst.sst.Secret"
+ "value": string
+ }
+ "STRIPE_SECRET_KEY": {
+ "type": "sst.sst.Secret"
+ "value": string
+ }
+ "STRIPE_WEBHOOK_SECRET": {
+ "type": "sst.sst.Linkable"
+ "value": string
+ }
+ "Web": {
+ "type": "sst.cloudflare.Astro"
+ "url": string
+ }
+ "ZHIPU_API_KEY": {
+ "type": "sst.sst.Secret"
+ "value": string
+ }
+ }
+}
+// cloudflare
+import * as cloudflare from "@cloudflare/workers-types";
+declare module "sst" {
+ export interface Resource {
+ "Api": cloudflare.Service
+ "AuthApi": cloudflare.Service
+ "AuthStorage": cloudflare.KVNamespace
+ "Bucket": cloudflare.R2Bucket
+ "GatewayApi": cloudflare.Service
+ }
+}
+
+import "sst"
+export {}
\ No newline at end of file
diff --git a/cloud/resource/tsconfig.json b/cloud/resource/tsconfig.json
new file mode 100644
index 00000000..829dbad0
--- /dev/null
+++ b/cloud/resource/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "extends": "@tsconfig/node22/tsconfig.json",
+ "compilerOptions": {
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "types": [
+ "@cloudflare/workers-types",
+ "node"
+ ]
+ }
+}