Compare commits

...

1 Commits

Author SHA1 Message Date
dayuan.jiang
0dd7b2383e WIP: Cloudflare Worker deployment setup 2025-12-08 10:31:10 +09:00
9 changed files with 8389 additions and 60 deletions

5
.gitignore vendored
View File

@@ -42,3 +42,8 @@ next-env.d.ts
push-via-ec2.sh push-via-ec2.sh
.claude/settings.local.json .claude/settings.local.json
.playwright-mcp/ .playwright-mcp/
# cloudflare
.open-next/
.dev.vars
.wrangler/

3
cloudflare-env.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
interface CloudflareEnv {
ASSETS: Fetcher
}

View File

@@ -1,7 +1,15 @@
import { LangfuseSpanProcessor } from "@langfuse/otel"
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node"
export function register() { export function register() {
// Skip on edge/worker runtime (Cloudflare Workers, Vercel Edge)
// OpenTelemetry Node SDK requires Node.js-specific APIs
if (
typeof process === "undefined" ||
!process.versions?.node ||
// @ts-expect-error - EdgeRuntime is a global in edge environments
typeof EdgeRuntime !== "undefined"
) {
return
}
// Skip telemetry if Langfuse env vars are not configured // Skip telemetry if Langfuse env vars are not configured
if (!process.env.LANGFUSE_PUBLIC_KEY || !process.env.LANGFUSE_SECRET_KEY) { if (!process.env.LANGFUSE_PUBLIC_KEY || !process.env.LANGFUSE_SECRET_KEY) {
console.warn( console.warn(
@@ -10,12 +18,16 @@ export function register() {
return return
} }
// Dynamic imports to avoid bundling Node.js-specific modules in edge builds
const { LangfuseSpanProcessor } = require("@langfuse/otel")
const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node")
const langfuseSpanProcessor = new LangfuseSpanProcessor({ const langfuseSpanProcessor = new LangfuseSpanProcessor({
publicKey: process.env.LANGFUSE_PUBLIC_KEY, publicKey: process.env.LANGFUSE_PUBLIC_KEY,
secretKey: process.env.LANGFUSE_SECRET_KEY, secretKey: process.env.LANGFUSE_SECRET_KEY,
baseUrl: process.env.LANGFUSE_BASEURL, baseUrl: process.env.LANGFUSE_BASEURL,
// Filter out Next.js HTTP request spans so AI SDK spans become root traces // Filter out Next.js HTTP request spans so AI SDK spans become root traces
shouldExportSpan: ({ otelSpan }) => { shouldExportSpan: ({ otelSpan }: { otelSpan: { name: string } }) => {
const spanName = otelSpan.name const spanName = otelSpan.name
// Skip Next.js HTTP infrastructure spans // Skip Next.js HTTP infrastructure spans
if ( if (

View File

@@ -4,10 +4,16 @@ import { azure, createAzure } from "@ai-sdk/azure"
import { createDeepSeek, deepseek } from "@ai-sdk/deepseek" import { createDeepSeek, deepseek } from "@ai-sdk/deepseek"
import { createGoogleGenerativeAI, google } from "@ai-sdk/google" import { createGoogleGenerativeAI, google } from "@ai-sdk/google"
import { createOpenAI, openai } from "@ai-sdk/openai" import { createOpenAI, openai } from "@ai-sdk/openai"
import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
import { createOpenRouter } from "@openrouter/ai-sdk-provider" import { createOpenRouter } from "@openrouter/ai-sdk-provider"
import { createOllama, ollama } from "ollama-ai-provider-v2" import { createOllama, ollama } from "ollama-ai-provider-v2"
// Detect if running in edge/worker runtime (Cloudflare Workers, Vercel Edge, etc.)
const isEdgeRuntime =
typeof process === "undefined" ||
!process.versions?.node ||
// @ts-expect-error - EdgeRuntime is a global in edge environments
typeof EdgeRuntime !== "undefined"
export type ProviderName = export type ProviderName =
| "bedrock" | "bedrock"
| "openai" | "openai"
@@ -166,12 +172,37 @@ export function getAIModel(): ModelConfig {
switch (provider) { switch (provider) {
case "bedrock": { case "bedrock": {
// Use credential provider chain for IAM role support (Amplify, Lambda, etc.) // Edge runtime (Cloudflare Workers, etc.) requires explicit credentials
// Falls back to env vars (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) for local dev // Node.js runtime can use IAM role chain (Amplify, Lambda, etc.)
const bedrockProvider = createAmazonBedrock({ const bedrockConfig: Parameters<typeof createAmazonBedrock>[0] = {
region: process.env.AWS_REGION || "us-west-2", region: process.env.AWS_REGION || "us-west-2",
credentialProvider: fromNodeProviderChain(), }
})
if (isEdgeRuntime) {
// Edge runtime: use explicit credentials from env vars
if (
!process.env.AWS_ACCESS_KEY_ID ||
!process.env.AWS_SECRET_ACCESS_KEY
) {
throw new Error(
"AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are required for Bedrock on edge runtime (Cloudflare Workers)",
)
}
bedrockConfig.accessKeyId = process.env.AWS_ACCESS_KEY_ID
bedrockConfig.secretAccessKey =
process.env.AWS_SECRET_ACCESS_KEY
if (process.env.AWS_SESSION_TOKEN) {
bedrockConfig.sessionToken = process.env.AWS_SESSION_TOKEN
}
} else {
// Node.js runtime: use credential provider chain for IAM role support
const {
fromNodeProviderChain,
} = require("@aws-sdk/credential-providers")
bedrockConfig.credentialProvider = fromNodeProviderChain()
}
const bedrockProvider = createAmazonBedrock(bedrockConfig)
model = bedrockProvider(modelId) model = bedrockProvider(modelId)
// Add Anthropic beta options if using Claude models via Bedrock // Add Anthropic beta options if using Claude models via Bedrock
if (modelId.includes("anthropic.claude")) { if (modelId.includes("anthropic.claude")) {

3
open-next.config.ts Normal file
View File

@@ -0,0 +1,3 @@
import { defineCloudflareConfig } from "@opennextjs/cloudflare"
export default defineCloudflareConfig()

8354
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,11 @@
"lint": "biome lint .", "lint": "biome lint .",
"format": "biome check --write .", "format": "biome check --write .",
"check": "biome ci", "check": "biome ci",
"prepare": "husky" "prepare": "husky",
"cf:build": "opennextjs-cloudflare build",
"cf:preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
"cf:deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
"cf:typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts"
}, },
"dependencies": { "dependencies": {
"@ai-sdk/amazon-bedrock": "^3.0.62", "@ai-sdk/amazon-bedrock": "^3.0.62",
@@ -25,6 +29,7 @@
"@langfuse/otel": "^4.4.4", "@langfuse/otel": "^4.4.4",
"@langfuse/tracing": "^4.4.9", "@langfuse/tracing": "^4.4.9",
"@next/third-parties": "^16.0.6", "@next/third-parties": "^16.0.6",
"@opennextjs/cloudflare": "^1.14.4",
"@openrouter/ai-sdk-provider": "^1.2.3", "@openrouter/ai-sdk-provider": "^1.2.3",
"@opentelemetry/exporter-trace-otlp-http": "^0.208.0", "@opentelemetry/exporter-trace-otlp-http": "^0.208.0",
"@opentelemetry/sdk-trace-node": "^2.2.0", "@opentelemetry/sdk-trace-node": "^2.2.0",
@@ -79,6 +84,7 @@
"husky": "^9.1.7", "husky": "^9.1.7",
"lint-staged": "^16.2.7", "lint-staged": "^16.2.7",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5",
"wrangler": "^4.53.0"
} }
} }

View File

@@ -24,6 +24,7 @@
}, },
"include": [ "include": [
"next-env.d.ts", "next-env.d.ts",
"cloudflare-env.d.ts",
"**/*.ts", "**/*.ts",
"**/*.tsx", "**/*.tsx",
".next/types/**/*.ts", ".next/types/**/*.ts",

8
wrangler.toml Normal file
View File

@@ -0,0 +1,8 @@
main = ".open-next/worker.js"
name = "next-ai-draw-io"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
[assets]
directory = ".open-next/assets"
binding = "ASSETS"