From 92b0b613427b0adb1f9cf149052f6d5bea71c00d Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Mon, 21 Apr 2025 13:57:04 +0200 Subject: [PATCH 01/20] Adding ai stuff --- _examples/ai/README.md | 57 ++ _examples/ai/go.mod | 7 + _examples/ai/go.sum | 2 + _examples/ai/main.go | 39 + _examples/ai/package-lock.json | 1540 ++++++++++++++++++++++++++++++++ _examples/ai/package.json | 14 + _examples/ai/tsconfig.json | 46 + _examples/ai/vitest.config.mts | 11 + _examples/ai/wrangler.jsonc | 53 ++ cloudflare/ai/call.go | 76 ++ cloudflare/ai/mock/call.go | 38 + 11 files changed, 1883 insertions(+) create mode 100644 _examples/ai/README.md create mode 100644 _examples/ai/go.mod create mode 100644 _examples/ai/go.sum create mode 100644 _examples/ai/main.go create mode 100644 _examples/ai/package-lock.json create mode 100644 _examples/ai/package.json create mode 100644 _examples/ai/tsconfig.json create mode 100644 _examples/ai/vitest.config.mts create mode 100644 _examples/ai/wrangler.jsonc create mode 100644 cloudflare/ai/call.go create mode 100644 cloudflare/ai/mock/call.go diff --git a/_examples/ai/README.md b/_examples/ai/README.md new file mode 100644 index 00000000..26f5ac57 --- /dev/null +++ b/_examples/ai/README.md @@ -0,0 +1,57 @@ +# worker-template-go + +- A template for starting a Cloudflare Worker project with Go. +- This template uses [`workers`](https://github.com/syumai/workers) package to run an HTTP server. + +## Notice + +- Go (not TinyGo) with many dependencies may exceed the size limit of the Worker (3MB for free plan, 10MB for paid plan). In that case, you can use the [TinyGo template](https://github.com/syumai/workers/tree/main/_templates/cloudflare/worker-tinygo) instead. + +## Usage + +- `main.go` includes simple HTTP server implementation. Feel free to edit this code and implement your own HTTP server. + +## Requirements + +- Node.js +- Go 1.24.0 or later + +## Getting Started + +- Create a new worker project using this template. + +```console +npm create cloudflare@latest -- --template github.com/syumai/workers/_templates/cloudflare/worker-go +``` + +- Initialize a project. + +```console +cd my-app +go mod init +go mod tidy +npm start # start running dev server +curl http://localhost:8787/hello # outputs "Hello!" +``` + +## Development + +### Commands + +``` +npm start # run dev server +# or +go run . # run dev server without Wrangler (Cloudflare-related features are not available) +npm run build # build Go Wasm binary +npm run deploy # deploy worker +``` + +### Testing dev server + +- Just send HTTP request using some tools like curl. + +``` +$ curl http://localhost:8787/ai +Hello! +``` + diff --git a/_examples/ai/go.mod b/_examples/ai/go.mod new file mode 100644 index 00000000..2ff09d38 --- /dev/null +++ b/_examples/ai/go.mod @@ -0,0 +1,7 @@ +module github.com/syumai/workers/_examples/ai + +go 1.22.2 + +require github.com/syumai/workers v0.28.1 + +replace github.com/syumai/workers => ../../ \ No newline at end of file diff --git a/_examples/ai/go.sum b/_examples/ai/go.sum new file mode 100644 index 00000000..358ad39a --- /dev/null +++ b/_examples/ai/go.sum @@ -0,0 +1,2 @@ +github.com/syumai/workers v0.28.1 h1:yDIwRwBQUsq/xP5efqTQHmTlZDJiH8jI4Ic/aUL8G0Y= +github.com/syumai/workers v0.28.1/go.mod h1:ZnqmdiHNBrbxOLrZ/HJ5jzHy6af9cmiNZk10R9NrIEA= diff --git a/_examples/ai/main.go b/_examples/ai/main.go new file mode 100644 index 00000000..83468b81 --- /dev/null +++ b/_examples/ai/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "io" + "net/http" + "os" + "strings" + + "github.com/syumai/workers" + "github.com/syumai/workers/cloudflare/ai" + // ai "github.com/syumai/workers/cloudflare/ai/mock" +) + +func main() { + http.HandleFunc("/ai", func(w http.ResponseWriter, req *http.Request) { + + // initialize KV namespace instance + aiCaller, err := ai.NewNamespace("AI") + if err != nil { + fmt.Fprintf(os.Stderr, "failed to init KV: %v", err) + os.Exit(1) + } + + countStr, err := aiCaller.Run("@cf/meta/llama-3.1-8b-instruct", map[string]interface{}{ + "prompt": "What is the origin of the phrase Hello, World", + }) + + if err != nil { + fmt.Println(w, "failed to get current count\n", err) + return + } + + fmt.Println(countStr) + + io.Copy(w, strings.NewReader(countStr)) + }) + workers.Serve(nil) // use http.DefaultServeMux +} diff --git a/_examples/ai/package-lock.json b/_examples/ai/package-lock.json new file mode 100644 index 00000000..7a3f7880 --- /dev/null +++ b/_examples/ai/package-lock.json @@ -0,0 +1,1540 @@ +{ + "name": "workers-ia", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "workers-ia", + "version": "0.0.0", + "devDependencies": { + "wrangler": "^4.7.2" + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.0.tgz", + "integrity": "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "mime": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.3.1.tgz", + "integrity": "sha512-Xq57Qd+ADpt6hibcVBO0uLG9zzRgyRhfCUgBT9s+g3+3Ivg5zDyVgLFy40ES1VdNcu8rPNSivm9A+kGP5IVaPg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.15", + "workerd": "^1.20250320.0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20250404.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250404.0.tgz", + "integrity": "sha512-+z67wjimn7pZDJI5Ibt2TtNxreFJdFPd5dBMmQqtIfkwrlIsL4PkjHYdiffO7guFP9UygomThuKUaBOU4JA15Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20250404.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250404.0.tgz", + "integrity": "sha512-MxFuWqR5bMcc92khreSlYOJxr0OIlJxABdrWQsaogWsxI6p7Df9gV1T36pqg+ERa9fVhkkGERkalt9DJYyYicA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20250404.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250404.0.tgz", + "integrity": "sha512-f4rNJ45376vGB6WHmxxiZ50nmxMws337EvWthhNAZTyeoTYmJUbZjjWCaHR8clWXN8LLK1Tu1bkjsih730X41g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20250404.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250404.0.tgz", + "integrity": "sha512-UW54a/vZG6W1oiA9PUSatQ0LLWrxnwAX7rN/bCFLiT6n51PP8KgpM1LzrIvQM80WjH/ufqopZJe/TSgrrSss6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20250404.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250404.0.tgz", + "integrity": "sha512-AJJP8vjJ6ioBzqUxVyByv5tE74z5LZ7G5To7w7dtYjWvKZzFo39bZZwGCGryHmH4yaOylXubFv72YVH8+Y4GSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.0.tgz", + "integrity": "sha512-64WYIf4UYcdLnbKn/umDlNjQDSS8AgZrI/R9+x5ilkUVFxXcA1Ebl+gQLc/6mERA4407Xof0R7wEyEuj091CVw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/as-table": { + "version": "1.0.55", + "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", + "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "printable-characters": "^1.0.42" + } + }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", + "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", + "dev": true, + "license": "MIT" + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/exit-hook": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", + "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exsolve": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.4.tgz", + "integrity": "sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-source": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", + "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "data-uri-to-buffer": "^2.0.0", + "source-map": "^0.6.1" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/miniflare": { + "version": "4.20250404.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250404.0.tgz", + "integrity": "sha512-OeNnXrOdgSoN5iDA+u1Ue3etOyPY89BJFFizMgGEPJvGTYL+kZhKqeBKeZiZIS7xjjlrfXNqleYGnCyomQ1pDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "acorn": "8.14.0", + "acorn-walk": "8.3.2", + "exit-hook": "2.2.1", + "glob-to-regexp": "0.4.1", + "stoppable": "1.1.0", + "undici": "^5.28.5", + "workerd": "1.20250404.0", + "ws": "8.18.0", + "youch": "3.3.4", + "zod": "3.22.3" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/printable-characters": { + "version": "1.0.42", + "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", + "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stacktracey": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", + "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "as-table": "^1.0.36", + "get-source": "^2.0.12" + } + }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/unenv": { + "version": "2.0.0-rc.15", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.15.tgz", + "integrity": "sha512-J/rEIZU8w6FOfLNz/hNKsnY+fFHWnu9MH4yRbSZF3xbbGHovcetXPs7sD+9p8L6CeNC//I9bhRYAOsBt2u7/OA==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "exsolve": "^1.0.4", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "ufo": "^1.5.4" + } + }, + "node_modules/workerd": { + "version": "1.20250404.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250404.0.tgz", + "integrity": "sha512-dvXsRdy49/vd4nPENpTDFjbPvR3XdPa8lJrxcnDKL1XtoioYXflq3ys8ljuu+X71ojqAAjnQj62AzrmmKM095g==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20250404.0", + "@cloudflare/workerd-darwin-arm64": "1.20250404.0", + "@cloudflare/workerd-linux-64": "1.20250404.0", + "@cloudflare/workerd-linux-arm64": "1.20250404.0", + "@cloudflare/workerd-windows-64": "1.20250404.0" + } + }, + "node_modules/wrangler": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.7.2.tgz", + "integrity": "sha512-fK9h7PL8wrrdSLCFXVDotoSHOebRmdNdB4VRkUDWOIyiP0Dx54TBfXTt3bXB98EYx7VT+vj6CNVnEC8gSCxt0w==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.4.0", + "@cloudflare/unenv-preset": "2.3.1", + "blake3-wasm": "2.1.5", + "esbuild": "0.24.2", + "miniflare": "4.20250404.0", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.15", + "workerd": "1.20250404.0" + }, + "bin": { + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2", + "sharp": "^0.33.5" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20250404.0" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/youch": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz", + "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cookie": "^0.7.1", + "mustache": "^4.2.0", + "stacktracey": "^2.1.8" + } + }, + "node_modules/zod": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", + "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/_examples/ai/package.json b/_examples/ai/package.json new file mode 100644 index 00000000..767af2ce --- /dev/null +++ b/_examples/ai/package.json @@ -0,0 +1,14 @@ +{ + "name": "workers-ia", + "version": "0.0.0", + "private": true, + "scripts": { + "build": "go run github.com/syumai/workers/cmd/workers-assets-gen -mode=go && GOOS=js GOARCH=wasm go build -o ./build/app.wasm .", + "deploy": "wrangler deploy", + "dev": "wrangler dev", + "start": "wrangler dev" + }, + "devDependencies": { + "wrangler": "^4.7.2" + } +} diff --git a/_examples/ai/tsconfig.json b/_examples/ai/tsconfig.json new file mode 100644 index 00000000..1abec239 --- /dev/null +++ b/_examples/ai/tsconfig.json @@ -0,0 +1,46 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "es2021", + /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "lib": ["es2021"], + /* Specify what JSX code is generated. */ + "jsx": "react-jsx", + + /* Specify what module code is generated. */ + "module": "es2022", + /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "Bundler", + /* Specify type package names to be included without being referenced in a source file. + "types": [ + "@cloudflare/workers-types/2023-07-01" + ], */ + /* Enable importing .json files */ + "resolveJsonModule": true, + + /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + "allowJs": true, + /* Enable error reporting in type-checked JavaScript files. */ + "checkJs": false, + + /* Disable emitting files from a compilation. */ + "noEmit": true, + + /* Ensure that each file can be safely transpiled without relying on other imports. */ + "isolatedModules": true, + /* Allow 'import x from y' when a module doesn't have a default export. */ + "allowSyntheticDefaultImports": true, + /* Ensure that casing is correct in imports. */ + "forceConsistentCasingInFileNames": true, + + /* Enable all strict type-checking options. */ + "strict": true, + + /* Skip type checking all .d.ts files. */ + "skipLibCheck": true + }, + "exclude": ["backup/test"], + "include": ["backup/worker-configuration.d.ts", "backup/src/**/*.ts"] +} diff --git a/_examples/ai/vitest.config.mts b/_examples/ai/vitest.config.mts new file mode 100644 index 00000000..977f64c4 --- /dev/null +++ b/_examples/ai/vitest.config.mts @@ -0,0 +1,11 @@ +import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config'; + +export default defineWorkersConfig({ + test: { + poolOptions: { + workers: { + wrangler: { configPath: './wrangler.jsonc' }, + }, + }, + }, +}); diff --git a/_examples/ai/wrangler.jsonc b/_examples/ai/wrangler.jsonc new file mode 100644 index 00000000..8ce4b88f --- /dev/null +++ b/_examples/ai/wrangler.jsonc @@ -0,0 +1,53 @@ +/** + * For more details on how to configure Wrangler, refer to: + * https://developers.cloudflare.com/workers/wrangler/configuration/ + */ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "workers-ia", + "main": "./build/worker.mjs", + "compatibility_date": "2025-04-06", + "build": { + "command": "npm run build" + }, + "observability": { + "enabled": true + }, + "ai": { + "binding": "AI" + } + /** + * Smart Placement + * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement + */ + // "placement": { "mode": "smart" }, + + /** + * Bindings + * Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including + * databases, object storage, AI inference, real-time communication and more. + * https://developers.cloudflare.com/workers/runtime-apis/bindings/ + */ + + /** + * Environment Variables + * https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables + */ + // "vars": { "MY_VARIABLE": "production_value" }, + /** + * Note: Use secrets to store sensitive data. + * https://developers.cloudflare.com/workers/configuration/secrets/ + */ + + /** + * Static Assets + * https://developers.cloudflare.com/workers/static-assets/binding/ + */ + // "assets": { "directory": "./public/", "binding": "ASSETS" }, + + /** + * Service Bindings (communicate between multiple Workers) + * https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings + */ + // "services": [{ "binding": "MY_SERVICE", "service": "my-service" }] +} diff --git a/cloudflare/ai/call.go b/cloudflare/ai/call.go new file mode 100644 index 00000000..5c59b5b4 --- /dev/null +++ b/cloudflare/ai/call.go @@ -0,0 +1,76 @@ +//go:build js && wasm + +package ai + +import ( + "fmt" + "io" + "syscall/js" + + "github.com/syumai/workers/cloudflare/internal/cfruntimecontext" + "github.com/syumai/workers/internal/jsutil" +) + +type AIInterface interface { + WaitUntil(task func()) + Run(key string, opts map[string]interface{}) (string, error) + RunReader(key string, opts map[string]interface{}) (io.Reader, error) +} + +type AI struct { + instance js.Value +} + +// NewNamespace returns Namespace for given variable name. +// - variable name must be defined in wrangler.toml as kv_namespace's binding. +// - if the given variable name doesn't exist on runtime context, returns error. +// - This function panics when a runtime context is not found. +func NewNamespace(varName string) (*AI, error) { + inst := cfruntimecontext.MustGetRuntimeContextEnv().Get(varName) + if inst.IsUndefined() { + return nil, fmt.Errorf("%s is undefined", varName) + } + return &AI{instance: inst}, nil +} + +func (ns *AI) WaitUntil(task func()) { + exCtx := ns.instance + exCtx.Call("run", jsutil.NewPromise(js.FuncOf(func(this js.Value, pArgs []js.Value) any { + resolve := pArgs[0] + go func() { + task() + resolve.Invoke(js.Undefined()) + }() + return js.Undefined() + }))) +} + +func mapToJS(opts map[string]interface{}, type_ string) js.Value { + obj := jsutil.NewObject() + for k, v := range opts { + obj.Set(k, v) + } + obj.Set("type", type_) + return obj +} + +func (ns *AI) Run(key string, opts map[string]interface{}) (string, error) { + p := ns.instance.Call("run", key, mapToJS(opts, "text")) + v, err := jsutil.AwaitPromise(p) + if err != nil { + return "", err + } + respString := js.Global().Get("JSON").Call("stringify", v).String() + return respString, nil +} + +// GetReader gets stream value by the specified key. +// - if a network error happens, returns error. +func (ns *AI) RunReader(key string, opts map[string]interface{}) (io.Reader, error) { + p := ns.instance.Call("run", key, mapToJS(opts, "stream")) + v, err := jsutil.AwaitPromise(p) + if err != nil { + return nil, err + } + return jsutil.ConvertReadableStreamToReadCloser(v), nil +} diff --git a/cloudflare/ai/mock/call.go b/cloudflare/ai/mock/call.go new file mode 100644 index 00000000..01c92c75 --- /dev/null +++ b/cloudflare/ai/mock/call.go @@ -0,0 +1,38 @@ +//go:build !js + +package mock + +import ( + "fmt" + "io" +) + +type AIMock struct { + instance string +} + +func NewNamespace(varName string) (*AIMock, error) { + fmt.Println("NewNamespace called with varName:", varName) + if varName == "" { + return nil, fmt.Errorf("%s is undefined", varName) + } + return &AIMock{instance: varName}, nil +} + +func (ns *AIMock) WaitUntil(task func()) { + fmt.Println("WaitUntil called") + go func() { + task() + }() + fmt.Println("Task completed") +} + +func (ns *AIMock) Run(key string, opts map[string]interface{}) (string, error) { + fmt.Println("Run called with key:", key, "and opts:", opts) + return "mocked response", nil +} + +func (ns *AIMock) RunReader(key string, opts map[string]interface{}) (io.Reader, error) { + fmt.Println("RunReader called with key:", key, "and opts:", opts) + return io.NopCloser(nil), nil +} From 2ea1211dc55274f32cd979b860c753aa9cfcc8bf Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Mon, 21 Apr 2025 14:10:54 +0200 Subject: [PATCH 02/20] renaming files and import --- _examples/ai/go.mod | 2 +- _examples/ai/go.sum | 2 - _examples/ai/main.go | 5 +-- cloudflare/ai/call.go | 78 ++++++++++---------------------------- cloudflare/ai/call_js.go | 76 +++++++++++++++++++++++++++++++++++++ cloudflare/ai/mock/call.go | 38 ------------------- 6 files changed, 99 insertions(+), 102 deletions(-) create mode 100644 cloudflare/ai/call_js.go delete mode 100644 cloudflare/ai/mock/call.go diff --git a/_examples/ai/go.mod b/_examples/ai/go.mod index 2ff09d38..0aac4b85 100644 --- a/_examples/ai/go.mod +++ b/_examples/ai/go.mod @@ -4,4 +4,4 @@ go 1.22.2 require github.com/syumai/workers v0.28.1 -replace github.com/syumai/workers => ../../ \ No newline at end of file +replace github.com/syumai/workers => ../../ diff --git a/_examples/ai/go.sum b/_examples/ai/go.sum index 358ad39a..e69de29b 100644 --- a/_examples/ai/go.sum +++ b/_examples/ai/go.sum @@ -1,2 +0,0 @@ -github.com/syumai/workers v0.28.1 h1:yDIwRwBQUsq/xP5efqTQHmTlZDJiH8jI4Ic/aUL8G0Y= -github.com/syumai/workers v0.28.1/go.mod h1:ZnqmdiHNBrbxOLrZ/HJ5jzHy6af9cmiNZk10R9NrIEA= diff --git a/_examples/ai/main.go b/_examples/ai/main.go index 83468b81..b90a1efc 100644 --- a/_examples/ai/main.go +++ b/_examples/ai/main.go @@ -8,14 +8,13 @@ import ( "strings" "github.com/syumai/workers" - "github.com/syumai/workers/cloudflare/ai" - // ai "github.com/syumai/workers/cloudflare/ai/mock" + ai "github.com/syumai/workers/cloudflare/ai" ) func main() { http.HandleFunc("/ai", func(w http.ResponseWriter, req *http.Request) { - // initialize KV namespace instance + // initialize AI namespace instance aiCaller, err := ai.NewNamespace("AI") if err != nil { fmt.Fprintf(os.Stderr, "failed to init KV: %v", err) diff --git a/cloudflare/ai/call.go b/cloudflare/ai/call.go index 5c59b5b4..01c92c75 100644 --- a/cloudflare/ai/call.go +++ b/cloudflare/ai/call.go @@ -1,76 +1,38 @@ -//go:build js && wasm +//go:build !js -package ai +package mock import ( "fmt" "io" - "syscall/js" - - "github.com/syumai/workers/cloudflare/internal/cfruntimecontext" - "github.com/syumai/workers/internal/jsutil" ) -type AIInterface interface { - WaitUntil(task func()) - Run(key string, opts map[string]interface{}) (string, error) - RunReader(key string, opts map[string]interface{}) (io.Reader, error) -} - -type AI struct { - instance js.Value +type AIMock struct { + instance string } -// NewNamespace returns Namespace for given variable name. -// - variable name must be defined in wrangler.toml as kv_namespace's binding. -// - if the given variable name doesn't exist on runtime context, returns error. -// - This function panics when a runtime context is not found. -func NewNamespace(varName string) (*AI, error) { - inst := cfruntimecontext.MustGetRuntimeContextEnv().Get(varName) - if inst.IsUndefined() { +func NewNamespace(varName string) (*AIMock, error) { + fmt.Println("NewNamespace called with varName:", varName) + if varName == "" { return nil, fmt.Errorf("%s is undefined", varName) } - return &AI{instance: inst}, nil -} - -func (ns *AI) WaitUntil(task func()) { - exCtx := ns.instance - exCtx.Call("run", jsutil.NewPromise(js.FuncOf(func(this js.Value, pArgs []js.Value) any { - resolve := pArgs[0] - go func() { - task() - resolve.Invoke(js.Undefined()) - }() - return js.Undefined() - }))) + return &AIMock{instance: varName}, nil } -func mapToJS(opts map[string]interface{}, type_ string) js.Value { - obj := jsutil.NewObject() - for k, v := range opts { - obj.Set(k, v) - } - obj.Set("type", type_) - return obj +func (ns *AIMock) WaitUntil(task func()) { + fmt.Println("WaitUntil called") + go func() { + task() + }() + fmt.Println("Task completed") } -func (ns *AI) Run(key string, opts map[string]interface{}) (string, error) { - p := ns.instance.Call("run", key, mapToJS(opts, "text")) - v, err := jsutil.AwaitPromise(p) - if err != nil { - return "", err - } - respString := js.Global().Get("JSON").Call("stringify", v).String() - return respString, nil +func (ns *AIMock) Run(key string, opts map[string]interface{}) (string, error) { + fmt.Println("Run called with key:", key, "and opts:", opts) + return "mocked response", nil } -// GetReader gets stream value by the specified key. -// - if a network error happens, returns error. -func (ns *AI) RunReader(key string, opts map[string]interface{}) (io.Reader, error) { - p := ns.instance.Call("run", key, mapToJS(opts, "stream")) - v, err := jsutil.AwaitPromise(p) - if err != nil { - return nil, err - } - return jsutil.ConvertReadableStreamToReadCloser(v), nil +func (ns *AIMock) RunReader(key string, opts map[string]interface{}) (io.Reader, error) { + fmt.Println("RunReader called with key:", key, "and opts:", opts) + return io.NopCloser(nil), nil } diff --git a/cloudflare/ai/call_js.go b/cloudflare/ai/call_js.go new file mode 100644 index 00000000..5c59b5b4 --- /dev/null +++ b/cloudflare/ai/call_js.go @@ -0,0 +1,76 @@ +//go:build js && wasm + +package ai + +import ( + "fmt" + "io" + "syscall/js" + + "github.com/syumai/workers/cloudflare/internal/cfruntimecontext" + "github.com/syumai/workers/internal/jsutil" +) + +type AIInterface interface { + WaitUntil(task func()) + Run(key string, opts map[string]interface{}) (string, error) + RunReader(key string, opts map[string]interface{}) (io.Reader, error) +} + +type AI struct { + instance js.Value +} + +// NewNamespace returns Namespace for given variable name. +// - variable name must be defined in wrangler.toml as kv_namespace's binding. +// - if the given variable name doesn't exist on runtime context, returns error. +// - This function panics when a runtime context is not found. +func NewNamespace(varName string) (*AI, error) { + inst := cfruntimecontext.MustGetRuntimeContextEnv().Get(varName) + if inst.IsUndefined() { + return nil, fmt.Errorf("%s is undefined", varName) + } + return &AI{instance: inst}, nil +} + +func (ns *AI) WaitUntil(task func()) { + exCtx := ns.instance + exCtx.Call("run", jsutil.NewPromise(js.FuncOf(func(this js.Value, pArgs []js.Value) any { + resolve := pArgs[0] + go func() { + task() + resolve.Invoke(js.Undefined()) + }() + return js.Undefined() + }))) +} + +func mapToJS(opts map[string]interface{}, type_ string) js.Value { + obj := jsutil.NewObject() + for k, v := range opts { + obj.Set(k, v) + } + obj.Set("type", type_) + return obj +} + +func (ns *AI) Run(key string, opts map[string]interface{}) (string, error) { + p := ns.instance.Call("run", key, mapToJS(opts, "text")) + v, err := jsutil.AwaitPromise(p) + if err != nil { + return "", err + } + respString := js.Global().Get("JSON").Call("stringify", v).String() + return respString, nil +} + +// GetReader gets stream value by the specified key. +// - if a network error happens, returns error. +func (ns *AI) RunReader(key string, opts map[string]interface{}) (io.Reader, error) { + p := ns.instance.Call("run", key, mapToJS(opts, "stream")) + v, err := jsutil.AwaitPromise(p) + if err != nil { + return nil, err + } + return jsutil.ConvertReadableStreamToReadCloser(v), nil +} diff --git a/cloudflare/ai/mock/call.go b/cloudflare/ai/mock/call.go deleted file mode 100644 index 01c92c75..00000000 --- a/cloudflare/ai/mock/call.go +++ /dev/null @@ -1,38 +0,0 @@ -//go:build !js - -package mock - -import ( - "fmt" - "io" -) - -type AIMock struct { - instance string -} - -func NewNamespace(varName string) (*AIMock, error) { - fmt.Println("NewNamespace called with varName:", varName) - if varName == "" { - return nil, fmt.Errorf("%s is undefined", varName) - } - return &AIMock{instance: varName}, nil -} - -func (ns *AIMock) WaitUntil(task func()) { - fmt.Println("WaitUntil called") - go func() { - task() - }() - fmt.Println("Task completed") -} - -func (ns *AIMock) Run(key string, opts map[string]interface{}) (string, error) { - fmt.Println("Run called with key:", key, "and opts:", opts) - return "mocked response", nil -} - -func (ns *AIMock) RunReader(key string, opts map[string]interface{}) (io.Reader, error) { - fmt.Println("RunReader called with key:", key, "and opts:", opts) - return io.NopCloser(nil), nil -} From 3e67fb0a7d960ed93f461146d3293d65c5570904 Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Mon, 21 Apr 2025 17:48:53 +0200 Subject: [PATCH 03/20] updated as other example readme --- _examples/ai/README.md | 57 +++++++++--------------------------------- 1 file changed, 12 insertions(+), 45 deletions(-) diff --git a/_examples/ai/README.md b/_examples/ai/README.md index 26f5ac57..531766d8 100644 --- a/_examples/ai/README.md +++ b/_examples/ai/README.md @@ -1,57 +1,24 @@ -# worker-template-go +# ai -- A template for starting a Cloudflare Worker project with Go. -- This template uses [`workers`](https://github.com/syumai/workers) package to run an HTTP server. +* This app show some examples of using Cloudflare AI. -## Notice +## Demo -- Go (not TinyGo) with many dependencies may exceed the size limit of the Worker (3MB for free plan, 10MB for paid plan). In that case, you can use the [TinyGo template](https://github.com/syumai/workers/tree/main/_templates/cloudflare/worker-tinygo) instead. - -## Usage - -- `main.go` includes simple HTTP server implementation. Feel free to edit this code and implement your own HTTP server. - -## Requirements - -- Node.js -- Go 1.24.0 or later - -## Getting Started - -- Create a new worker project using this template. - -```console -npm create cloudflare@latest -- --template github.com/syumai/workers/_templates/cloudflare/worker-go -``` - -- Initialize a project. - -```console -cd my-app -go mod init -go mod tidy -npm start # start running dev server -curl http://localhost:8787/hello # outputs "Hello!" -``` +* https://workers-ia.syumai.workers.dev/ ## Development -### Commands +### Requirements -``` -npm start # run dev server -# or -go run . # run dev server without Wrangler (Cloudflare-related features are not available) -npm run build # build Go Wasm binary -npm run deploy # deploy worker -``` +This project requires these tools to be installed globally. -### Testing dev server +* wrangler +* tinygo -- Just send HTTP request using some tools like curl. +### Commands ``` -$ curl http://localhost:8787/ai -Hello! +make dev # run dev server +make build # build Go Wasm binary +make deploy # deploy worker ``` - From ac0f467cf4b91e1f504ce36e401ae0f08acab215 Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Mon, 21 Apr 2025 17:49:05 +0200 Subject: [PATCH 04/20] update version --- _examples/ai/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_examples/ai/go.mod b/_examples/ai/go.mod index 0aac4b85..4c32f206 100644 --- a/_examples/ai/go.mod +++ b/_examples/ai/go.mod @@ -2,6 +2,6 @@ module github.com/syumai/workers/_examples/ai go 1.22.2 -require github.com/syumai/workers v0.28.1 +require github.com/syumai/workers v0.0.0 replace github.com/syumai/workers => ../../ From 6aca11946c6d1d90e55db735756dd7c02c8f9e1a Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Mon, 21 Apr 2025 17:54:11 +0200 Subject: [PATCH 05/20] change name --- _examples/ai/README.md | 2 +- _examples/ai/wrangler.jsonc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/_examples/ai/README.md b/_examples/ai/README.md index 531766d8..cd545cef 100644 --- a/_examples/ai/README.md +++ b/_examples/ai/README.md @@ -4,7 +4,7 @@ ## Demo -* https://workers-ia.syumai.workers.dev/ +* https://workers-ai-example.syumai.workers.dev/ ## Development diff --git a/_examples/ai/wrangler.jsonc b/_examples/ai/wrangler.jsonc index 8ce4b88f..f6c190a3 100644 --- a/_examples/ai/wrangler.jsonc +++ b/_examples/ai/wrangler.jsonc @@ -4,7 +4,7 @@ */ { "$schema": "node_modules/wrangler/config-schema.json", - "name": "workers-ia", + "name": "workers-ai-example", "main": "./build/worker.mjs", "compatibility_date": "2025-04-06", "build": { From 307967a961a3a8c7c4eb666861dd1cb70696599f Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Mon, 21 Apr 2025 17:56:23 +0200 Subject: [PATCH 06/20] remove unussed --- _examples/ai/tsconfig.json | 46 -------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 _examples/ai/tsconfig.json diff --git a/_examples/ai/tsconfig.json b/_examples/ai/tsconfig.json deleted file mode 100644 index 1abec239..00000000 --- a/_examples/ai/tsconfig.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ - - /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - "target": "es2021", - /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - "lib": ["es2021"], - /* Specify what JSX code is generated. */ - "jsx": "react-jsx", - - /* Specify what module code is generated. */ - "module": "es2022", - /* Specify how TypeScript looks up a file from a given module specifier. */ - "moduleResolution": "Bundler", - /* Specify type package names to be included without being referenced in a source file. - "types": [ - "@cloudflare/workers-types/2023-07-01" - ], */ - /* Enable importing .json files */ - "resolveJsonModule": true, - - /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ - "allowJs": true, - /* Enable error reporting in type-checked JavaScript files. */ - "checkJs": false, - - /* Disable emitting files from a compilation. */ - "noEmit": true, - - /* Ensure that each file can be safely transpiled without relying on other imports. */ - "isolatedModules": true, - /* Allow 'import x from y' when a module doesn't have a default export. */ - "allowSyntheticDefaultImports": true, - /* Ensure that casing is correct in imports. */ - "forceConsistentCasingInFileNames": true, - - /* Enable all strict type-checking options. */ - "strict": true, - - /* Skip type checking all .d.ts files. */ - "skipLibCheck": true - }, - "exclude": ["backup/test"], - "include": ["backup/worker-configuration.d.ts", "backup/src/**/*.ts"] -} From b46e9b5699f2a37626c03b0f1797b59f027b0948 Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Mon, 21 Apr 2025 17:57:58 +0200 Subject: [PATCH 07/20] organize import, message and var names --- _examples/ai/main.go | 16 +++++++++------- cloudflare/ai/call.go | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/_examples/ai/main.go b/_examples/ai/main.go index b90a1efc..b97a5b7b 100644 --- a/_examples/ai/main.go +++ b/_examples/ai/main.go @@ -8,31 +8,33 @@ import ( "strings" "github.com/syumai/workers" - ai "github.com/syumai/workers/cloudflare/ai" + "github.com/syumai/workers/cloudflare/ai" ) func main() { + http.HandleFunc("/ai", func(w http.ResponseWriter, req *http.Request) { // initialize AI namespace instance aiCaller, err := ai.NewNamespace("AI") if err != nil { - fmt.Fprintf(os.Stderr, "failed to init KV: %v", err) - os.Exit(1) + fmt.Fprintf(os.Stderr, "failed to init AI instance: %v", err) + return } - countStr, err := aiCaller.Run("@cf/meta/llama-3.1-8b-instruct", map[string]interface{}{ + aiJsonResultStr, err := aiCaller.Run("@cf/meta/llama-3.1-8b-instruct", map[string]interface{}{ "prompt": "What is the origin of the phrase Hello, World", }) if err != nil { - fmt.Println(w, "failed to get current count\n", err) + fmt.Println(w, "failed to get result from AI\n", err) return } - fmt.Println(countStr) + fmt.Println(aiJsonResultStr) - io.Copy(w, strings.NewReader(countStr)) + io.Copy(w, strings.NewReader(aiJsonResultStr)) }) + workers.Serve(nil) // use http.DefaultServeMux } diff --git a/cloudflare/ai/call.go b/cloudflare/ai/call.go index 01c92c75..7e585a80 100644 --- a/cloudflare/ai/call.go +++ b/cloudflare/ai/call.go @@ -1,6 +1,6 @@ //go:build !js -package mock +package ai import ( "fmt" From 71cc4fab784ddddd1d0253ff2105dcb66fc62f2a Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Mon, 21 Apr 2025 17:59:13 +0200 Subject: [PATCH 08/20] removed --- _examples/ai/vitest.config.mts | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 _examples/ai/vitest.config.mts diff --git a/_examples/ai/vitest.config.mts b/_examples/ai/vitest.config.mts deleted file mode 100644 index 977f64c4..00000000 --- a/_examples/ai/vitest.config.mts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config'; - -export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - wrangler: { configPath: './wrangler.jsonc' }, - }, - }, - }, -}); From 323bd4f774f599492b3ec30aafa977a050238340 Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Mon, 21 Apr 2025 18:02:01 +0200 Subject: [PATCH 09/20] remove comments --- _examples/ai/wrangler.jsonc | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/_examples/ai/wrangler.jsonc b/_examples/ai/wrangler.jsonc index f6c190a3..6afe0f1f 100644 --- a/_examples/ai/wrangler.jsonc +++ b/_examples/ai/wrangler.jsonc @@ -16,38 +16,4 @@ "ai": { "binding": "AI" } - /** - * Smart Placement - * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement - */ - // "placement": { "mode": "smart" }, - - /** - * Bindings - * Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including - * databases, object storage, AI inference, real-time communication and more. - * https://developers.cloudflare.com/workers/runtime-apis/bindings/ - */ - - /** - * Environment Variables - * https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables - */ - // "vars": { "MY_VARIABLE": "production_value" }, - /** - * Note: Use secrets to store sensitive data. - * https://developers.cloudflare.com/workers/configuration/secrets/ - */ - - /** - * Static Assets - * https://developers.cloudflare.com/workers/static-assets/binding/ - */ - // "assets": { "directory": "./public/", "binding": "ASSETS" }, - - /** - * Service Bindings (communicate between multiple Workers) - * https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings - */ - // "services": [{ "binding": "MY_SERVICE", "service": "my-service" }] } From 565d8fa322c7ae2b6ad4601d8ebb877412a879c5 Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Mon, 21 Apr 2025 18:03:12 +0200 Subject: [PATCH 10/20] remane --- cloudflare/ai/call.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cloudflare/ai/call.go b/cloudflare/ai/call.go index 7e585a80..1bbddf1d 100644 --- a/cloudflare/ai/call.go +++ b/cloudflare/ai/call.go @@ -7,19 +7,19 @@ import ( "io" ) -type AIMock struct { +type AI struct { instance string } -func NewNamespace(varName string) (*AIMock, error) { +func NewNamespace(varName string) (*AI, error) { fmt.Println("NewNamespace called with varName:", varName) if varName == "" { return nil, fmt.Errorf("%s is undefined", varName) } - return &AIMock{instance: varName}, nil + return &AI{instance: varName}, nil } -func (ns *AIMock) WaitUntil(task func()) { +func (ns *AI) WaitUntil(task func()) { fmt.Println("WaitUntil called") go func() { task() @@ -27,12 +27,12 @@ func (ns *AIMock) WaitUntil(task func()) { fmt.Println("Task completed") } -func (ns *AIMock) Run(key string, opts map[string]interface{}) (string, error) { +func (ns *AI) Run(key string, opts map[string]interface{}) (string, error) { fmt.Println("Run called with key:", key, "and opts:", opts) return "mocked response", nil } -func (ns *AIMock) RunReader(key string, opts map[string]interface{}) (io.Reader, error) { +func (ns *AI) RunReader(key string, opts map[string]interface{}) (io.Reader, error) { fmt.Println("RunReader called with key:", key, "and opts:", opts) return io.NopCloser(nil), nil } From e3520f6ab9531c2db4933cfcf0c0d26e1c6d3235 Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Mon, 21 Apr 2025 18:04:46 +0200 Subject: [PATCH 11/20] not used --- cloudflare/ai/call_js.go | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/cloudflare/ai/call_js.go b/cloudflare/ai/call_js.go index 5c59b5b4..2786a2d4 100644 --- a/cloudflare/ai/call_js.go +++ b/cloudflare/ai/call_js.go @@ -11,12 +11,6 @@ import ( "github.com/syumai/workers/internal/jsutil" ) -type AIInterface interface { - WaitUntil(task func()) - Run(key string, opts map[string]interface{}) (string, error) - RunReader(key string, opts map[string]interface{}) (io.Reader, error) -} - type AI struct { instance js.Value } @@ -48,7 +42,22 @@ func (ns *AI) WaitUntil(task func()) { func mapToJS(opts map[string]interface{}, type_ string) js.Value { obj := jsutil.NewObject() for k, v := range opts { - obj.Set(k, v) + + // if v is an array of bytes + if b, ok := v.([]byte); ok { + // ua := jsutil.NewUint8Array(len(b)) + // js.CopyBytesToJS(ua, b) + // obj.Set(k, ua) + + // "data" is a byte slice, so we need to convert it to a JS Uint8Array object + arrayConstructor := js.Global().Get("Uint8Array") + dataJS := arrayConstructor.New(len(b)) + js.CopyBytesToJS(dataJS, b) + obj.Set(k, dataJS) + } else { + obj.Set(k, v) + } + } obj.Set("type", type_) return obj From bae90eb8ababac8d761218ee7e4f0e1cb7ee1d22 Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Mon, 21 Apr 2025 18:11:58 +0200 Subject: [PATCH 12/20] Change reference, constructor and example --- _examples/ai/main.go | 2 +- cloudflare/ai/call.go | 2 +- cloudflare/ai/call_js.go | 7 +++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/_examples/ai/main.go b/_examples/ai/main.go index b97a5b7b..8ab4b03b 100644 --- a/_examples/ai/main.go +++ b/_examples/ai/main.go @@ -16,7 +16,7 @@ func main() { http.HandleFunc("/ai", func(w http.ResponseWriter, req *http.Request) { // initialize AI namespace instance - aiCaller, err := ai.NewNamespace("AI") + aiCaller, err := ai.New("AI") if err != nil { fmt.Fprintf(os.Stderr, "failed to init AI instance: %v", err) return diff --git a/cloudflare/ai/call.go b/cloudflare/ai/call.go index 1bbddf1d..b9026b04 100644 --- a/cloudflare/ai/call.go +++ b/cloudflare/ai/call.go @@ -11,7 +11,7 @@ type AI struct { instance string } -func NewNamespace(varName string) (*AI, error) { +func New(varName string) (*AI, error) { fmt.Println("NewNamespace called with varName:", varName) if varName == "" { return nil, fmt.Errorf("%s is undefined", varName) diff --git a/cloudflare/ai/call_js.go b/cloudflare/ai/call_js.go index 2786a2d4..72dc267a 100644 --- a/cloudflare/ai/call_js.go +++ b/cloudflare/ai/call_js.go @@ -11,15 +11,18 @@ import ( "github.com/syumai/workers/internal/jsutil" ) +// Namespace represents interface of Cloudflare Worker's KV namespace instance. +// - https://developers.cloudflare.com/workers-ai/configuration/bindings/#methods +// - https://github.com/cloudflare/workerd/blob/v1.20250421.0/types/defines/ai.d.ts#L1247 type AI struct { instance js.Value } // NewNamespace returns Namespace for given variable name. -// - variable name must be defined in wrangler.toml as kv_namespace's binding. +// - variable name must be defined in wrangler.toml as `ai` binding. // - if the given variable name doesn't exist on runtime context, returns error. // - This function panics when a runtime context is not found. -func NewNamespace(varName string) (*AI, error) { +func New(varName string) (*AI, error) { inst := cfruntimecontext.MustGetRuntimeContextEnv().Get(varName) if inst.IsUndefined() { return nil, fmt.Errorf("%s is undefined", varName) From db38cf2a37712f938636cb58a137a54cbdfa89c4 Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Mon, 21 Apr 2025 23:22:46 +0200 Subject: [PATCH 13/20] WIP --- _examples/ai/Makefile | 12 ++++++ _examples/ai/main.go | 92 +++++++++++++++++++++++++++++++++++++++- cloudflare/ai/call.go | 13 +++--- cloudflare/ai/call_js.go | 69 +++++++++++++++++------------- 4 files changed, 148 insertions(+), 38 deletions(-) create mode 100644 _examples/ai/Makefile diff --git a/_examples/ai/Makefile b/_examples/ai/Makefile new file mode 100644 index 00000000..019492c2 --- /dev/null +++ b/_examples/ai/Makefile @@ -0,0 +1,12 @@ +.PHONY: dev +dev: + wrangler dev + +.PHONY: build +build: + go run ../../cmd/workers-assets-gen + tinygo build -o ./build/app.wasm -target wasm -no-debug ./... + +.PHONY: deploy +deploy: + wrangler deploy diff --git a/_examples/ai/main.go b/_examples/ai/main.go index 8ab4b03b..9b259a55 100644 --- a/_examples/ai/main.go +++ b/_examples/ai/main.go @@ -1,16 +1,25 @@ package main import ( + "encoding/base64" + "encoding/json" "fmt" "io" + "log" "net/http" "os" "strings" "github.com/syumai/workers" "github.com/syumai/workers/cloudflare/ai" + "github.com/syumai/workers/cloudflare/fetch" ) +func handleError(w http.ResponseWriter, status int, msg string) { + w.WriteHeader(status) + w.Write([]byte(msg + "\n")) +} + func main() { http.HandleFunc("/ai", func(w http.ResponseWriter, req *http.Request) { @@ -22,7 +31,7 @@ func main() { return } - aiJsonResultStr, err := aiCaller.Run("@cf/meta/llama-3.1-8b-instruct", map[string]interface{}{ + aiJsonResultStr, err := aiCaller.Run("@cf/meta/llama-3.1-8b-instruct", map[string]any{ "prompt": "What is the origin of the phrase Hello, World", }) @@ -36,5 +45,86 @@ func main() { io.Copy(w, strings.NewReader(aiJsonResultStr)) }) + http.HandleFunc("/ai-text-to-image", func(w http.ResponseWriter, req *http.Request) { + + // initialize AI namespace instance + aiCaller, err := ai.New("AI") + if err != nil { + fmt.Fprintf(os.Stderr, "failed to init AI instance: %v", err) + os.Exit(1) + } + + aiJsonResultStr, err := aiCaller.Run("@cf/black-forest-labs/flux-1-schnell", map[string]any{ + "prompt": "a cyberpunk lizard", + }) + + if err != nil { + fmt.Println(w, "failed to get result from AI\n", err) + return + } + + fmt.Println(aiJsonResultStr) + + var response struct { + Image string `json:"image"` + } + err = json.Unmarshal([]byte(aiJsonResultStr), &response) + + // Decode the base64 string + imageData, err := base64.StdEncoding.DecodeString(response.Image) + if err != nil { + http.Error(w, "failed to decode image", http.StatusInternalServerError) + return + } + + // Set the appropriate content type for the image + w.Header().Set("Content-Type", "image/png") + + // Write the image data to the response + w.Write(imageData) + }) + + http.HandleFunc("/ai-image-to-image", func(w http.ResponseWriter, req *http.Request) { + + // initialize AI namespace instance + aiCaller, err := ai.New("AI") + if err != nil { + fmt.Fprintf(os.Stderr, "failed to init AI instance: %v", err) + os.Exit(1) + } + + r, err := fetch.NewRequest(req.Context(), "GET", "https://pub-1fb693cb11cc46b2b2f656f51e015a2c.r2.dev/dog.png", nil) + if err != nil { + handleError(w, http.StatusInternalServerError, "Internal Error") + log.Printf("failed to initialize proxy request: %v\n", err) + return + } + + cli := fetch.NewClient() + resp, err := cli.Do(r, nil) + if err != nil { + handleError(w, http.StatusInternalServerError, "Internal Error") + log.Printf("failed to execute proxy request: %v\n", err) + return + } + + defer resp.Body.Close() + + imgBytes, err := io.ReadAll(resp.Body) + if err != nil { + http.Error(w, "Error reading image", http.StatusInternalServerError) + return + } + + aiResult, err := aiCaller.RunReader("@cf/runwayml/stable-diffusion-v1-5-img2img", map[string]any{ + "prompt": "Change to a lion", + // "image": imgBytes, + "image_b64": base64.StdEncoding.EncodeToString(imgBytes), + }) + + // w.Header().Set("Content-Type", "image/png") + // io.Copy(w, strings.NewReader(aiResult)) + io.Copy(w, aiResult) + }) workers.Serve(nil) // use http.DefaultServeMux } diff --git a/cloudflare/ai/call.go b/cloudflare/ai/call.go index b9026b04..8382c1bd 100644 --- a/cloudflare/ai/call.go +++ b/cloudflare/ai/call.go @@ -19,20 +19,17 @@ func New(varName string) (*AI, error) { return &AI{instance: varName}, nil } -func (ns *AI) WaitUntil(task func()) { - fmt.Println("WaitUntil called") - go func() { - task() - }() - fmt.Println("Task completed") +func (ns *AI) Run(key string, opts map[string]any) (string, error) { + fmt.Println("Run called with key:", key, "and opts:", opts) + return "mocked response", nil } -func (ns *AI) Run(key string, opts map[string]interface{}) (string, error) { +func (ns *AI) RunWithOutJson(key string, opts map[string]any) (string, error) { fmt.Println("Run called with key:", key, "and opts:", opts) return "mocked response", nil } -func (ns *AI) RunReader(key string, opts map[string]interface{}) (io.Reader, error) { +func (ns *AI) RunReader(key string, opts map[string]any) (io.Reader, error) { fmt.Println("RunReader called with key:", key, "and opts:", opts) return io.NopCloser(nil), nil } diff --git a/cloudflare/ai/call_js.go b/cloudflare/ai/call_js.go index 72dc267a..77880995 100644 --- a/cloudflare/ai/call_js.go +++ b/cloudflare/ai/call_js.go @@ -30,44 +30,55 @@ func New(varName string) (*AI, error) { return &AI{instance: inst}, nil } -func (ns *AI) WaitUntil(task func()) { - exCtx := ns.instance - exCtx.Call("run", jsutil.NewPromise(js.FuncOf(func(this js.Value, pArgs []js.Value) any { - resolve := pArgs[0] - go func() { - task() - resolve.Invoke(js.Undefined()) - }() - return js.Undefined() - }))) -} - -func mapToJS(opts map[string]interface{}, type_ string) js.Value { +func mapToJS(opts map[string]any, type_ string) js.Value { obj := jsutil.NewObject() for k, v := range opts { - - // if v is an array of bytes if b, ok := v.([]byte); ok { - // ua := jsutil.NewUint8Array(len(b)) - // js.CopyBytesToJS(ua, b) - // obj.Set(k, ua) - - // "data" is a byte slice, so we need to convert it to a JS Uint8Array object - arrayConstructor := js.Global().Get("Uint8Array") - dataJS := arrayConstructor.New(len(b)) - js.CopyBytesToJS(dataJS, b) - obj.Set(k, dataJS) + ua := jsutil.NewUint8Array(len(b)) + js.CopyBytesToJS(ua, b) + obj.Set(k, ua) } else { obj.Set(k, v) } - } - obj.Set("type", type_) + switch type_ { + case "stream": + obj.Set("stream", true) + } return obj } -func (ns *AI) Run(key string, opts map[string]interface{}) (string, error) { - p := ns.instance.Call("run", key, mapToJS(opts, "text")) +func (ns *AI) RunWithOutJson(key string, opts map[string]any) (string, error) { + + logger := js.Global().Get("console").Get("log") + + p := ns.instance.Call("run", key, mapToJS(opts, "stream")) + v, err := jsutil.AwaitPromise(p) + if err != nil { + fmt.Println("err:", err) + return "", err + } + + logger.Invoke("v:", v) + + // logger := js.Global().Get("console").Get("log") + // logger.Invoke("AI response 1:", v) + + // // handle BLOB type (ArrayBuffer). + // src := jsutil.Uint8ArrayClass.New(v) + // dst := make([]byte, src.Length()) + // n := js.CopyBytesToGo(dst, src) + // if n != len(dst) { + // fmt.Println("Error copying bytes to Go slice") + // } + // logger.Invoke("AI response 2:", dst[:n]) + + respString := js.Global().Get("JSON").Call("stringify", v).String() + return respString, nil +} + +func (ns *AI) Run(key string, opts map[string]any) (string, error) { + p := ns.instance.Call("run", key, mapToJS(opts, "")) v, err := jsutil.AwaitPromise(p) if err != nil { return "", err @@ -78,7 +89,7 @@ func (ns *AI) Run(key string, opts map[string]interface{}) (string, error) { // GetReader gets stream value by the specified key. // - if a network error happens, returns error. -func (ns *AI) RunReader(key string, opts map[string]interface{}) (io.Reader, error) { +func (ns *AI) RunReader(key string, opts map[string]any) (io.Reader, error) { p := ns.instance.Call("run", key, mapToJS(opts, "stream")) v, err := jsutil.AwaitPromise(p) if err != nil { From 588d708ab6253f86ef72ae351f052694f280603a Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Tue, 22 Apr 2025 23:59:42 +0200 Subject: [PATCH 14/20] playing with new models --- _examples/ai/main.go | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/_examples/ai/main.go b/_examples/ai/main.go index 9b259a55..74c39fc5 100644 --- a/_examples/ai/main.go +++ b/_examples/ai/main.go @@ -122,9 +122,48 @@ func main() { "image_b64": base64.StdEncoding.EncodeToString(imgBytes), }) - // w.Header().Set("Content-Type", "image/png") - // io.Copy(w, strings.NewReader(aiResult)) io.Copy(w, aiResult) + + // aiResultStr, err := io.ReadAll(aiResult) + // if err != nil { + // http.Error(w, "Error reading AI result", http.StatusInternalServerError) + // return + // } + // reader := strings.NewReader(string(aiResultStr)) + + // // Leer todo como string + // buf := new(strings.Builder) + // _, err2 := io.Copy(buf, reader) + // if err2 != nil { + // panic(err) + // } + + // // Paso 1: quitar el prefijo "data: {", y el sufijo "}" + // line := strings.TrimPrefix(buf.String(), "data: {") + // line = strings.TrimSuffix(line, "}") + + // // Paso 2: separar los pares clave-valor + // parts := strings.Split(line, ",") + + // // Paso 3: construir el slice de bytes + // imageBytes := make([]byte, len(parts)) + // for _, part := range parts { + // kv := strings.Split(part, ":") + // keyStr := strings.Trim(kv[0], `"`) + // valStr := kv[1] + + // index, _ := strconv.Atoi(keyStr) + // value, _ := strconv.Atoi(valStr) + + // imageBytes[index] = byte(value) + // } + + // // ✅ Ya tienes los bytes de la imagen + // fmt.Println("Bytes:", strconv.Itoa(len(imageBytes))) + + // w.Header().Set("Content-Type", "image/png") + // w.Write(imageBytes) + }) workers.Serve(nil) // use http.DefaultServeMux } From 146aba9208a7e8cd9877e6e103e0826e97026ab0 Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Sun, 27 Apr 2025 16:04:44 +0200 Subject: [PATCH 15/20] Remove unused function ... and comments ... for later... --- cloudflare/ai/call_js.go | 33 +-------------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/cloudflare/ai/call_js.go b/cloudflare/ai/call_js.go index 77880995..e1f3c5bd 100644 --- a/cloudflare/ai/call_js.go +++ b/cloudflare/ai/call_js.go @@ -18,7 +18,7 @@ type AI struct { instance js.Value } -// NewNamespace returns Namespace for given variable name. +// New returns Namespace for given variable name. // - variable name must be defined in wrangler.toml as `ai` binding. // - if the given variable name doesn't exist on runtime context, returns error. // - This function panics when a runtime context is not found. @@ -48,35 +48,6 @@ func mapToJS(opts map[string]any, type_ string) js.Value { return obj } -func (ns *AI) RunWithOutJson(key string, opts map[string]any) (string, error) { - - logger := js.Global().Get("console").Get("log") - - p := ns.instance.Call("run", key, mapToJS(opts, "stream")) - v, err := jsutil.AwaitPromise(p) - if err != nil { - fmt.Println("err:", err) - return "", err - } - - logger.Invoke("v:", v) - - // logger := js.Global().Get("console").Get("log") - // logger.Invoke("AI response 1:", v) - - // // handle BLOB type (ArrayBuffer). - // src := jsutil.Uint8ArrayClass.New(v) - // dst := make([]byte, src.Length()) - // n := js.CopyBytesToGo(dst, src) - // if n != len(dst) { - // fmt.Println("Error copying bytes to Go slice") - // } - // logger.Invoke("AI response 2:", dst[:n]) - - respString := js.Global().Get("JSON").Call("stringify", v).String() - return respString, nil -} - func (ns *AI) Run(key string, opts map[string]any) (string, error) { p := ns.instance.Call("run", key, mapToJS(opts, "")) v, err := jsutil.AwaitPromise(p) @@ -87,8 +58,6 @@ func (ns *AI) Run(key string, opts map[string]any) (string, error) { return respString, nil } -// GetReader gets stream value by the specified key. -// - if a network error happens, returns error. func (ns *AI) RunReader(key string, opts map[string]any) (io.Reader, error) { p := ns.instance.Call("run", key, mapToJS(opts, "stream")) v, err := jsutil.AwaitPromise(p) From cd443b42cb48587e4b66061b8dbf1e51edcea69d Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Sun, 27 Apr 2025 16:12:29 +0200 Subject: [PATCH 16/20] Update doc --- _examples/ai/README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/_examples/ai/README.md b/_examples/ai/README.md index cd545cef..8d619473 100644 --- a/_examples/ai/README.md +++ b/_examples/ai/README.md @@ -22,3 +22,28 @@ make dev # run dev server make build # build Go Wasm binary make deploy # deploy worker ``` + +### Testing AI + +* With curl command below, you can test basic ai functionality for text generation + - see: https://developers.cloudflare.com/workers-ai/models/llama-3.1-8b-instruct/ + +``` +curl "http://localhost:8787/ai" +``` + +* With curl command below, you can test basic ai functionality to generate image and see in the browser + - see: https://developers.cloudflare.com/workers-ai/models/flux-1-schnell/ + +``` +curl "http://localhost:8787/ai-text-to-image" +``` + +* WIP - With curl command below, you can test basic ai functionality for + - see: https://developers.cloudflare.com/workers-ai/models/stable-diffusion-v1-5-img2img/ + +``` +curl "http://localhost:8787/ai-image-to-image" +``` + + From be67de1b6321bd3621ab7575fad769f94720baef Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Sat, 3 May 2025 10:05:46 +0200 Subject: [PATCH 17/20] remove log waiting to global aproach. --- cloudflare/ai/call.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cloudflare/ai/call.go b/cloudflare/ai/call.go index 8382c1bd..57611a65 100644 --- a/cloudflare/ai/call.go +++ b/cloudflare/ai/call.go @@ -12,7 +12,6 @@ type AI struct { } func New(varName string) (*AI, error) { - fmt.Println("NewNamespace called with varName:", varName) if varName == "" { return nil, fmt.Errorf("%s is undefined", varName) } @@ -20,16 +19,13 @@ func New(varName string) (*AI, error) { } func (ns *AI) Run(key string, opts map[string]any) (string, error) { - fmt.Println("Run called with key:", key, "and opts:", opts) return "mocked response", nil } func (ns *AI) RunWithOutJson(key string, opts map[string]any) (string, error) { - fmt.Println("Run called with key:", key, "and opts:", opts) return "mocked response", nil } func (ns *AI) RunReader(key string, opts map[string]any) (io.Reader, error) { - fmt.Println("RunReader called with key:", key, "and opts:", opts) return io.NopCloser(nil), nil } From 173d910eef761feb2e2eb617f23f16c692fdddb3 Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Sat, 3 May 2025 10:11:14 +0200 Subject: [PATCH 18/20] correct the comment --- _examples/ai/main.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/_examples/ai/main.go b/_examples/ai/main.go index 74c39fc5..c5a21bf3 100644 --- a/_examples/ai/main.go +++ b/_examples/ai/main.go @@ -124,6 +124,9 @@ func main() { io.Copy(w, aiResult) + // At this point we have the this in the screen + // "data: {"0":137,"1":80,"2":78,"3":71,"4":13,"5":10,"6":26,"7":10,"8":0,"9":0,"10":0,"11":13,"12":73,"13":72..." + // aiResultStr, err := io.ReadAll(aiResult) // if err != nil { // http.Error(w, "Error reading AI result", http.StatusInternalServerError) @@ -131,21 +134,21 @@ func main() { // } // reader := strings.NewReader(string(aiResultStr)) - // // Leer todo como string + // // Try to read the response as string... // buf := new(strings.Builder) // _, err2 := io.Copy(buf, reader) // if err2 != nil { // panic(err) // } - // // Paso 1: quitar el prefijo "data: {", y el sufijo "}" + // // First step: remove the prefix "data: {", and the suffix "}" // line := strings.TrimPrefix(buf.String(), "data: {") // line = strings.TrimSuffix(line, "}") - // // Paso 2: separar los pares clave-valor + // // Second step: split the string into parts ... "0":137,"1":80... // parts := strings.Split(line, ",") - // // Paso 3: construir el slice de bytes + // // Third step: extract the bytes in order to create the image // imageBytes := make([]byte, len(parts)) // for _, part := range parts { // kv := strings.Split(part, ":") @@ -158,7 +161,8 @@ func main() { // imageBytes[index] = byte(value) // } - // // ✅ Ya tienes los bytes de la imagen + // // You have an image... but couln't be displayed because the format is incorrect... + // // And dont not why... the PNG format start correct but don't display the image // fmt.Println("Bytes:", strconv.Itoa(len(imageBytes))) // w.Header().Set("Content-Type", "image/png") From d8a2ccab61ad7440941274efd2c6b22879631bf6 Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Mon, 5 May 2025 21:54:55 +0200 Subject: [PATCH 19/20] updated to big go --- _examples/ai/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_examples/ai/Makefile b/_examples/ai/Makefile index 019492c2..3140ac9c 100644 --- a/_examples/ai/Makefile +++ b/_examples/ai/Makefile @@ -4,8 +4,8 @@ dev: .PHONY: build build: - go run ../../cmd/workers-assets-gen - tinygo build -o ./build/app.wasm -target wasm -no-debug ./... + go run ../../cmd/workers-assets-gen -mode=go + GOOS=js GOARCH=wasm go build -o ./build/app.wasm . .PHONY: deploy deploy: From 23c9d4e421e253358d22bf72fdba8fecc85cf437 Mon Sep 17 00:00:00 2001 From: Jose Juan Montiel Date: Mon, 5 May 2025 21:57:14 +0200 Subject: [PATCH 20/20] update name --- _examples/ai/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_examples/ai/package.json b/_examples/ai/package.json index 767af2ce..c72e4c3d 100644 --- a/_examples/ai/package.json +++ b/_examples/ai/package.json @@ -1,5 +1,5 @@ { - "name": "workers-ia", + "name": "workers-ai-example", "version": "0.0.0", "private": true, "scripts": {